Initial Implementation of Better Deployment

This branch basically removes the necessity of a setup script and just deploys
This commit is contained in:
2024-10-14 02:36:17 -03:00
parent f6d74d3833
commit abe800dd5e
9 changed files with 170 additions and 445 deletions

39
.env.example Normal file
View File

@@ -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=

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
.vscode/ .vscode/
.env* .env*
!.env.example
.secret* .secret*
**/*.json **/*.json

13
Pipfile
View File

@@ -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"

37
Pipfile.lock generated
View File

@@ -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": {}
}

189
deploy.sh
View File

@@ -1,154 +1,63 @@
#!/bin/bash #!/bin/sh
function check_for_dependencies() { # eval "$(awk 'BEGIN{
if ! command -v kubectl &>/dev/null; then # for (i in ENVIRON) {
echo "kubectl could not be found" # if (i ~ /^(KUBE_)[a-zA-Z_][a-zA-Z0-9_]*$/) {
exit 1 # printf "export " i "_B64=";
fi # system("echo \"$"i"\" | base64 -w0");
if ! command -v jq &>/dev/null; then # print;
echo "jq could not be found" # }
exit 1 # }
fi # }' /dev/null)"
if ! command -v helm &>/dev/null; then
echo "helm could not be found"
exit 1 function read_env_file() {
if [[ -f $1 ]]; then
set -a && source $1 && set +a;
fi 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 \ function build_secret_envs() {
--for=condition=ready pod \ for i in $(env | grep -E '^KUBE_[a-zA-Z_][a-zA-Z0-9_]*=' | cut -d= -f1); do
--selector=app.kubernetes.io/component=controller \ eval "export ${i}_B64=$(echo ${!i} | base64 -w0)"
--timeout=120s done
} }
function configure_cert_manager() {
helm repo add jetstack https://charts.jetstack.io --force-update function deploy_kubernetes() {
helm repo update KUBE_FILES=(
helm install cert-manager jetstack/cert-manager \ "./template/portfolio-namespace.template.yaml"
--namespace cert-manager \ "./template/portfolio-secret.template.yml"
--create-namespace \ )
--version v1.14.2 \
--set installCRDs=true \ for file in ${KUBE_FILES[@]}; do
--timeout=600s 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() { function main() {
build_secret_envs
check_for_dependencies deploy_kubernetes
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
} }
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 <env_file>]"
;;
esac
done
main

View File

@@ -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[@]}"

208
setup.py
View File

@@ -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))

View File

@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: ${KUBE_NAMESPACE}

View File

@@ -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}