From abe800dd5e3f14802f172b94d1bb06c0dd96b3de Mon Sep 17 00:00:00 2001 From: Vitor Hideyoshi Date: Mon, 14 Oct 2024 02:36:17 -0300 Subject: [PATCH] Initial Implementation of Better Deployment This branch basically removes the necessity of a setup script and just deploys --- .env.example | 39 ++++ .gitignore | 1 + Pipfile | 13 -- Pipfile.lock | 37 ---- deploy.sh | 189 +++++-------------- refresh.sh | 47 ----- setup.py | 208 --------------------- template/portfolio-namespace.template.yaml | 4 + template/portfolio-secret.template.yml | 77 ++++++++ 9 files changed, 170 insertions(+), 445 deletions(-) create mode 100644 .env.example delete mode 100644 Pipfile delete mode 100644 Pipfile.lock delete mode 100755 refresh.sh delete mode 100644 setup.py create mode 100644 template/portfolio-namespace.template.yaml create mode 100644 template/portfolio-secret.template.yml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ed874d7 --- /dev/null +++ b/.env.example @@ -0,0 +1,39 @@ +KUBE_NAMESPACE= + +# Backend Secrets +KUBE_BACKEND_TOKEN_SECRET= +KUBE_BACKEND_ACCESS_TOKEN_DURATION= +KUBE_BACKEND_REFRESH_TOKEN_DURATION= +KUBE_BACKEND_DEFAULT_USER_FULL_NAME= +KUBE_BACKEND_DEFAULT_USER_EMAIL= +KUBE_BACKEND_DEFAULT_USER_USERNAME= +KUBE_BACKEND_DEFAULT_USER_PASSWORD= +KUBE_BACKEND_GOOGLE_CLIENT_ID= +KUBE_BACKEND_GOOGLE_CLIENT_SECRET= +KUBE_BACKEND_GOOGLE_REDIRECT_URL= +KUBE_BACKEND_GITHUB_CLIENT_ID= +KUBE_BACKEND_GITHUB_CLIENT_SECRET= +KUBE_BACKEND_GITHUB_REDIRECT_URL= + +# Frontend Secrets +KUBE_FRONTEND_URL= +KUBE_FRONTEND_BACKEND_URL= +KUBE_FRONTEND_OAUTH_URL= +KUBE_FRONTEND_GITHUB_USER= + +# Database Secrets (Postgres) +KUBE_DATABASE_NAME= +KUBE_DATABASE_USER= +KUBE_DATABASE_PASSWORD= + +# Redis Secrets +KUBE_REDIS_PASSWORD= + +# Storage Secrets (AWS S3) +KUBE_STORAGE_TYPE= +KUBE_STORAGE_AWS_ACCESS_KEY_ID= +KUBE_STORAGE_AWS_SECRET_ACCESS_KEY= +KUBE_STORAGE_AWS_REGION= +KUBE_STORAGE_AWS_BUCKET= +KUBE_STORAGE_VIRUS_CHECKER_TYPE= +KUBE_STORAGE_VIRUS_CHECKER_API_KEY= diff --git a/.gitignore b/.gitignore index d48d751..af4bec9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .vscode/ .env* +!.env.example .secret* **/*.json diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 70e71ab..0000000 --- a/Pipfile +++ /dev/null @@ -1,13 +0,0 @@ -[[source]] -url = "https://pypi.org/simple" -verify_ssl = true -name = "pypi" - -[packages] -python-dotenv = "*" -envsubst = "*" - -[dev-packages] - -[requires] -python_version = "3" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index 6f0e098..0000000 --- a/Pipfile.lock +++ /dev/null @@ -1,37 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "9127f5121153ee714035c045f3621c810565d451e8dd461576f24e165e55f73d" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "envsubst": { - "hashes": [ - "sha256:7188f9f2a046e45b63098ce5a7fd84126cbe9e5b73b8ff78eaf6e32122c0caaf", - "sha256:d8e402984a84dda4ea7a8a1f7afe1c41e54a1257cfb74c80cb8f991053d97b9b" - ], - "index": "pypi", - "version": "==0.1.5" - }, - "python-dotenv": { - "hashes": [ - "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba", - "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a" - ], - "index": "pypi", - "version": "==1.0.0" - } - }, - "develop": {} -} diff --git a/deploy.sh b/deploy.sh index 336a9f3..8031b3f 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,154 +1,63 @@ -#!/bin/bash +#!/bin/sh -function check_for_dependencies() { - if ! command -v kubectl &>/dev/null; then - echo "kubectl could not be found" - exit 1 - fi - if ! command -v jq &>/dev/null; then - echo "jq could not be found" - exit 1 - fi - if ! command -v helm &>/dev/null; then - echo "helm could not be found" - exit 1 +# eval "$(awk 'BEGIN{ +# for (i in ENVIRON) { +# if (i ~ /^(KUBE_)[a-zA-Z_][a-zA-Z0-9_]*$/) { +# printf "export " i "_B64="; +# system("echo \"$"i"\" | base64 -w0"); +# print; +# } +# } +# }' /dev/null)" + + +function read_env_file() { + if [[ -f $1 ]]; then + set -a && source $1 && set +a; fi } -function configure_nginx_ingress() { - helm upgrade --install ingress-nginx ingress-nginx \ - --repo https://kubernetes.github.io/ingress-nginx \ - --namespace ingress-nginx --create-namespace - kubectl wait --namespace ingress-nginx \ - --for=condition=ready pod \ - --selector=app.kubernetes.io/component=controller \ - --timeout=120s +function build_secret_envs() { + for i in $(env | grep -E '^KUBE_[a-zA-Z_][a-zA-Z0-9_]*=' | cut -d= -f1); do + eval "export ${i}_B64=$(echo ${!i} | base64 -w0)" + done } -function configure_cert_manager() { - helm repo add jetstack https://charts.jetstack.io --force-update - helm repo update - helm install cert-manager jetstack/cert-manager \ - --namespace cert-manager \ - --create-namespace \ - --version v1.14.2 \ - --set installCRDs=true \ - --timeout=600s + +function deploy_kubernetes() { + KUBE_FILES=( + "./template/portfolio-namespace.template.yaml" + "./template/portfolio-secret.template.yml" + ) + + for file in ${KUBE_FILES[@]}; do + echo -e "\n\n----------------------------------------------------\n" + echo -e "Deploying: $file\n" + echo -e "----------------------------------------------------\n\n\n" + + envsubst < $file + done } -function configure_postgres() { - helm repo add cnpg https://cloudnative-pg.github.io/charts - helm upgrade --install cnpg \ - --namespace portfolio \ - --create-namespace \ - cnpg/cloudnative-pg - - kubectl wait --for=condition=available \ - --timeout=600s \ - deployment.apps/cnpg-cloudnative-pg \ - -n portfolio - - kubectl apply -f ./deployment/postgres/cn-cluster.yaml - kubectl wait --for=condition=Ready \ - --timeout=600s \ - cluster/postgres-cn-cluster \ - -n portfolio -} - -function application_deploy() { - - kubectl create secret generic backend-secret -n portfolio \ - --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/backendSecret.json) - - kubectl create secret generic frontend-secret -n portfolio \ - --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/frontendSecret.json) - - kubectl create secret generic redis-secret -n portfolio \ - --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/redisSecret.json) - - kubectl create secret generic storage-secret -n portfolio \ - --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/storageSecret.json) - - kubectl apply -f ./deployment/redis - kubectl wait --for=condition=available \ - --timeout=600s \ - deployment.apps/redis-deployment \ - -n portfolio - - kubectl apply -f ./deployment/frontend - kubectl wait --for=condition=available \ - --timeout=600s \ - deployment.apps/frontend-deployment \ - -n portfolio - - kubectl apply -f ./deployment/storage - kubectl wait --for=condition=available \ - --timeout=600s \ - deployment.apps/storage-deployment \ - -n portfolio - - kubectl apply -f ./deployment/backend - kubectl wait --for=condition=available \ - --timeout=600s \ - deployment.apps/backend-deployment \ - -n portfolio - - kubectl apply -f \ - ./deployment/nginx-ingress - -} function main() { + build_secret_envs - check_for_dependencies - - if [[ $1 == "--local" || $1 == "-l" ]]; then - - function kubectl { - minikube kubectl -- $@ - } - - minikube start --driver kvm2 --cpus 2 --memory 4Gib - minikube addons enable ingress-dns - minikube addons enable ingress - - else - - configure_nginx_ingress - - fi - - configure_cert_manager - - kubectl apply -f ./deployment/portfolio-namespace.yaml - - configure_postgres - - application_deploy - - if [[ $1 == "--local" || $1 == "-l" ]]; then - - kubectl apply -f \ - ./deployment/cert-manager/cert-manager-issuer-dev.yaml - - kubectl apply -f \ - ./deployment/cert-manager/cert-manager-certificate.yaml - - echo "http://$(/usr/bin/minikube ip)" - - else - - kubectl apply -f \ - ./deployment/cert-manager/cert-manager-issuer.yaml - - kubectl apply -f \ - ./deployment/cert-manager/cert-manager-certificate.yaml - - fi - - exit 0 - + deploy_kubernetes } -main $1 + +while getopts ":f:" opt; do + case ${opt} in + f ) + echo "Reading env file: ${OPTARG}" + read_env_file ${OPTARG} + ;; + \? ) + echo "Usage: deploy.sh [-f ]" + ;; + esac +done + +main \ No newline at end of file diff --git a/refresh.sh b/refresh.sh deleted file mode 100755 index 7e905c2..0000000 --- a/refresh.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - - -function refresh_kubernetes_secrets() { - kubectl delete secret backend-secret -n portfolio - kubectl delete secret frontend-secret -n portfolio - kubectl delete secret postgres-secret -n portfolio - kubectl delete secret redis-secret -n portfolio - kubectl delete secret storage-secret -n portfolio - - kubectl create secret generic backend-secret -n portfolio --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/backendSecret.json) - kubectl create secret generic frontend-secret -n portfolio --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/frontendSecret.json) - kubectl create secret generic postgres-secret -n portfolio --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/postgresSecret.json) - kubectl create secret generic redis-secret -n portfolio --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/redisSecret.json) - kubectl create secret generic storage-secret -n portfolio --from-env-file <(jq -r "to_entries|map(\"\(.key)=\(.value|tostring)\")|.[]" ./deployment/secrets/storageSecret.json) -} - -function refresh_kubernetes_deployments() { - NAMESPACES=( - portfolio - ) - DEPLOYMENTS=("$@") - - for i in "${NAMESPACES[@]}"; do - for x in "${DEPLOYMENTS[@]}"; do - PODS=$(kubectl -n $i get pods --no-headers | awk '{print $1}' | grep $x | tr '\n' ' ') - kubectl -n $i delete pods $PODS - done - done -} - - -if [ $# -eq 0 ]; then - DEPLOYMENTS=( - "frontend-deployment" - "backend-deployment" - "storage-deployment" - "storage-processor-deployment" - ) -else - DEPLOYMENTS=("$@") -fi - -refresh_kubernetes_secrets - -refresh_kubernetes_deployments "${NAMESPACES[@]}" "${DEPLOYMENTS[@]}" - diff --git a/setup.py b/setup.py deleted file mode 100644 index 1769c86..0000000 --- a/setup.py +++ /dev/null @@ -1,208 +0,0 @@ -from base64 import b64decode, b64encode -from dotenv import load_dotenv -from envsubst import envsubst -from pathlib import Path, PosixPath -import argparse -import warnings -import json -import os - - -def write_template(template: str, output: str): - os.makedirs(os.path.dirname(output), exist_ok=True) - with open(template, 'r') as template,\ - open(output, 'w') as output: - output.write(envsubst(template.read())) - - -def configure_env_variables(environment: str): - if not environment in ("prod", "staging", "local"): - raise ValueError("Invalid Environment Selected") - - match environment: - case "local": - DOMAIN = "local.hideyoshi.com.br" - API_DOMAIN = "api.local.hideyoshi.com.br" - MASTER_NODE_LABEL = "minikube.k8s.io/name: minikube" - WORKER_NODE_LABEL = "minikube.k8s.io/name: minikube" - - case "staging": - DOMAIN = "staging.hideyoshi.com.br" - API_DOMAIN = "api.staging.hideyoshi.com.br" - MASTER_NODE_LABEL = "node_type: master" - WORKER_NODE_LABEL = "node_type: worker" - - case _: - DOMAIN = "hideyoshi.com.br" - API_DOMAIN = "api.hideyoshi.com.br" - MASTER_NODE_LABEL = "node_type: master" - WORKER_NODE_LABEL = "node_type: worker" - - os.environ["DOMAIN"] = DOMAIN - os.environ["API_DOMAIN"] = API_DOMAIN - os.environ["MASTER_NODE_LABEL"] = MASTER_NODE_LABEL - os.environ["WORKER_NODE_LABEL"] = WORKER_NODE_LABEL - - -def configure_templates(environment: str): - MAPPINS = [ - {"template": "template/cert-manager/cert-manager-certificate.template.yaml", "output": "deployment/cert-manager/cert-manager-certificate.yaml"}, - {"template": "template/nginx-ingress/nginx-ingress-root.template.yaml", "output": "deployment/nginx-ingress/nginx-ingress-root.yaml"}, - {"template": "template/postgres/cn-cluster.template.yaml", "output": "deployment/postgres/cn-cluster.yaml"}, - {"template": "template/frontend/frontend.template.yaml", "output": "deployment/frontend/frontend.yaml"}, - {"template": "template/backend/backend.template.yaml", "output": "deployment/backend/backend.yaml"}, - {"template": "template/storage/storage-processor.template.yaml", "output": "deployment/storage/storage-processor.yaml"}, - {"template": "template/storage/storage.template.yaml", "output": "deployment/storage/storage.yaml"}, - ] - - for mapping in MAPPINS: - write_template(mapping["template"], mapping["output"]) - - -def validate_backend_secret(secret: str): - required_keys = [ - 'tokenSecret', - 'accessTokenDuration', - 'refreshTokenDuration', - 'defaultUserFullName', - 'defaultUserEmail', - 'defaultUserUsername', - 'defaultUserPassword', - 'googleClientId', - 'googleClientSecret', - 'googleRedirectUrl', - 'githubClientId', - 'githubClientSecret', - 'githubRedirectUrl' - ] - - for key in required_keys: - if key not in secret: - raise ValueError(f"Key {key} not found in backendSecret") - - -def validate_frontend_secret(secret: str): - required_keys = [ - 'frontendPath', - 'backendUrl', - 'backendOAuthUrl', - 'githubUser' - ] - - for key in required_keys: - if key not in secret: - raise ValueError(f"Key {key} not found in frontendSecret") - - -def validate_postgres_secret(secret: str): - required_keys = [ - 'postgresUser', - 'postgresPassword', - 'postgresDatabase' - ] - - for key in required_keys: - if key not in secret: - raise ValueError(f"Key {key} not found in postgresSecret") - - - -def validate_redis_secret(secret: str): - required_keys = [ - 'redisPassword', - ] - - for key in required_keys: - if key not in secret: - raise ValueError(f"Key {key} not found in redisSecret") - - -def validate_storage_secret(secret: str): - required_keys = [ - 'storageType', - 'awsAccessKeyId', - 'awsSecretAccessKey', - 'awsRegion', - 'awsBucket', - 'virusCheckerType', - 'virusCheckerApiKey', - ] - - for key in required_keys: - if key not in secret: - raise ValueError(f"Key {key} not found in storageSecret") - - -def validate_env(env: dict): - required_secrets = [ - 'backendSecret', - 'frontendSecret', - 'postgresSecret', - 'redisSecret', - 'storageSecret', - ] - - for secret in required_secrets: - if secret not in env: - raise ValueError(f"Secret {secret} not found in env.json") - - if secret == 'backendSecret': - validate_backend_secret(env[secret]) - - if secret == 'frontendSecret': - validate_frontend_secret(env[secret]) - - if secret == 'postgresSecret': - validate_postgres_secret(env[secret]) - - if secret == 'redisSecret': - validate_redis_secret(env[secret]) - - if secret == 'storageSecret': - validate_storage_secret(env[secret]) - -def write_secrets_to_file(env: dict): - for key, secret in env.items(): - secrets_dir = Path("deployment", "secrets") - if not secrets_dir.exists(): - secrets_dir.mkdir() - - with open(secrets_dir.joinpath(f"{key}.json"), "w") as f: - json.dump(secret, f, indent=4) - - -def read_env_json(file: str) -> dict: - with open(file, "r") as f: - return json.load(f) - - -def main(file, environment): - env = read_env_json(file) - - validate_env(env) - - write_secrets_to_file(env) - - configure_env_variables(environment) - - configure_templates(environment) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(prog="Setup") - parser.add_argument( - "-f", "--file", - dest="file", - default=".env", - help="Secret file [default = .secret]" - ) - parser.add_argument( - "-e", "--environment", - dest="environment", - default="prod", - help="Selected Deployment Environment [default = prod, options = [prod, staging, dev]]" - ) - - args = parser.parse_args() - - main(**vars(args)) diff --git a/template/portfolio-namespace.template.yaml b/template/portfolio-namespace.template.yaml new file mode 100644 index 0000000..f068a10 --- /dev/null +++ b/template/portfolio-namespace.template.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: ${KUBE_NAMESPACE} \ No newline at end of file diff --git a/template/portfolio-secret.template.yml b/template/portfolio-secret.template.yml new file mode 100644 index 0000000..c27d3fd --- /dev/null +++ b/template/portfolio-secret.template.yml @@ -0,0 +1,77 @@ +apiVersion: v1 +kind: Secret +metadata: + name: backend-secret + namespace: ${KUBE_NAMESPACE} +type: Opaque +data: + backendTokenSecret: ${KUBE_BACKEND_TOKEN_SECRET_B64} + backendAccessTokenDuration: ${KUBE_BACKEND_ACCESS_TOKEN_DURATION_B64} + backendRefreshTokenDuration: ${KUBE_BACKEND_REFRESH_TOKEN_DURATION_B64} + backendDefaultUserFullName: ${KUBE_BACKEND_DEFAULT_USER_FULL_NAME_B64} + backendDefaultUserEmail: ${KUBE_BACKEND_DEFAULT_USER_EMAIL_B64} + backendDefaultUserUsername: ${KUBE_BACKEND_DEFAULT_USER_USERNAME_B64} + backendDefaultUserPassword: ${KUBE_BACKEND_DEFAULT_USER_PASSWORD_B64} + backendGoogleClientId: ${KUBE_BACKEND_GOOGLE_CLIENT_ID_B64} + backendGoogleClientSecret: ${KUBE_BACKEND_GOOGLE_CLIENT_SECRET_B64} + backendGoogleRedirectUrl: ${KUBE_BACKEND_GOOGLE_REDIRECT_URL_B64} + backendGithubClientId: ${KUBE_BACKEND_GITHUB_CLIENT_ID_B64} + backendGithubClientSecret: ${KUBE_BACKEND_GITHUB_CLIENT_SECRET_B64} + backendGithubRedirectUrl: ${KUBE_BACKEND_GITHUB_REDIRECT_URL_B64} + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: frontend-secret + namespace: ${KUBE_NAMESPACE} +type: Opaque +data: + frontendUrl: ${KUBE_FRONTEND_URL_B64} + frontendBackendUrl: ${KUBE_FRONTEND_BACKEND_URL_B64} + frontendOAuthUrl: ${KUBE_FRONTEND_OAUTH_URL_B64} + frontendGithubUser: ${KUBE_FRONTEND_GITHUB_USER_B64} + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: database-secret + namespace: ${KUBE_NAMESPACE} +type: Opaque +data: + databaseName: ${KUBE_DATABASE_NAME_B64} + databaseUser: ${KUBE_DATABASE_USER_B64} + databasePassword: ${KUBE_DATABASE_PASSWORD_B64} + + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: redis-secret + namespace: ${KUBE_NAMESPACE} +type: Opaque +data: + redisPassword: ${KUBE_REDIS_PASSWORD_B64} + +--- + +apiVersion: v1 +kind: Secret +metadata: + name: storage-secret + namespace: ${KUBE_NAMESPACE} +type: Opaque +data: + storageType: ${KUBE_STORAGE_TYPE_B64} + storageAwsAccessKeyId: ${KUBE_STORAGE_AWS_ACCESS_KEY_ID_B64} + storageAwsSecretAccessKey: ${KUBE_STORAGE_AWS_SECRET_ACCESS_KEY_B64} + storageAwsRegion: ${KUBE_STORAGE_AWS_REGION_B64} + storageAwsBucket: ${KUBE_STORAGE_AWS_BUCKET_B64} + storageVirusCheckerType: ${KUBE_STORAGE_VIRUS_CHECKER_TYPE_B64} + storageVirusCheckerApiKey: ${KUBE_STORAGE_VIRUS_CHECKER_API_KEY_B64} + \ No newline at end of file