#!/usr/bin/env bash set -e if [[ -a '.env' ]]; then source .env fi help() { echo echo 'task [options]' echo echo 'commands:' echo # Define column widths local cmd_width=26 local opt_width=16 local desc_width=50 local column="| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" # Print table header printf "$column" 'Command' 'Option' 'Description' echo "|$(printf '%*s' $((cmd_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((opt_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((desc_width + 2)) '' | tr ' ' '-')|" # Env printf "$column" 'version' '' 'Show version of required tools.' printf "$column" 'info' '' 'Show info about env.' printf "$column" 'template-dotenv' '' 'Generate .env from .env.template.' printf "$column" 'export-kubeconfig' '[user][path]' 'Export kubeconfig file for service account.' printf "$column" 'load-kubeconfig-from-keepass' '[ctx]' 'Load kubeconfig file from KeePass.' printf "$column" 'save-kubeconfig-to-keepass' '[ctx]' 'Save kubeconfig file in KeePass.' # Docs printf "$column" 'install-vitepress' '' 'Install Node dependencies.' printf "$column" 'docs' '' 'Generate Helm chart readmes.' printf "$column" 'lint' '' 'Lint Helm charts.' printf "$column" 'dev-vitepress' '' 'Start VitePress dev server.' printf "$column" 'build-vitepress' '' 'Build site with VitePress.' printf "$column" 'serve-vitepress' '' 'Serve VitePress build.' # Host printf "$column" 'start-kind' '' 'Start kind Kubernetes cluster.' printf "$column" 'load-image' '[tag]' 'Load Docker image into kind cluster.' printf "$column" 'delete-kind' '' 'Delete kind Kubernetes cluster.' printf "$column" 'setup-hosts' '' 'Update hosts file.' printf "$column" 'forward' '[chart]' 'Forward port.' # Chart printf "$column" 'list-charts' '' 'List local Helm charts.' printf "$column" 'list-external-charts' '' 'List external Helm charts.' printf "$column" 'list-cluster' '' 'List contexts.' printf "$column" 'switch-cluster' '' 'Switch kubeconfig file.' printf "$column" 'show-context' '' 'Show active kubeconfig.' printf "$column" 'get-context-alias' '' 'Get alias of current context.' printf "$column" 'update-dependencies' '[chart]' 'Update dependencies for a chart.' printf "$column" 'install-chart' '[chart][values]' 'Install Helm chart.' printf "$column" 'upgrade-release' '[chart][values]' 'Upgrade Helm release.' printf "$column" 'uninstall-release' '[release]' 'Uninstall Helm release.' # kubectl printf "$column" 'get-secrets' '[chart]' 'Show secrets for chart.' printf "$column" 'exec-bash' '[chart]' 'Open bash shell in container.' printf "$column" 'logs' '[grep]' 'Select pods with grep and show logs.' printf "$column" 'list' '' 'List Helm deployments.' printf "$column" 'get-pods' '' 'List all pods.' printf "$column" 'login-oc' '' 'Refresh the auth token for OpenShift.' # Helm printf "$column" 'add-repos' '' 'Add the required Helm repos.' printf "$column" 'template-manifest' '[chart][values]' 'Template all charts with Helm.' printf "$column" 'package-repo' '[chart]' 'Package Helm charts.' printf "$column" 'index-repo' '' 'Index Helm chart.' printf "$column" 'remove-repo' '' 'Remove the kubernetes-build repo.' printf "$column" 'commit' '' 'Commit .tgz files.' # LLM printf "$column" 'commit-with-llm' '' 'Commit with llm generated commit message.' printf "$column" 'update-with-llm' '[glob][prompt]' 'Feed files matching glob to LLM and apply changes.' # Test printf "$column" 'test-chart-clusterIssuer' '' 'Test cluster issuer.' printf "$column" 'test-chart-odoo' '[clean]' 'Setup Odoo chart and dependencies.' printf "$column" 'test-chart-nextcloud' '[clean]' 'Setup Nextcloud chart and dependencies.' } # Default vars OC_PROJECT=${OC_PROJECT:="odoo"} CONTAINER_REGISTRY=${CONTAINER_REGISTRY:="mintsystem/"} CONTAINER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY" # Dynamic vars pass_entry=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]') keepass_entry=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]') if kubectl &> /dev/null; then current_context=$(kubectl config current-context 2> /dev/null) || current_context="" fi if kubens &> /dev/null; then namespace=$(kubens -c) fi get-context-alias() { local alias="$(basename "$(readlink "$HOME/.kube/config" 2>/dev/null)" 2>/dev/null | cut -d '.' -f 2)" echo "${alias:-loc}" } context_alias=$(get-context-alias) # Import commands clone-taskfile(){ if [[ ! -d "$HOME/taskfile.build" ]]; then echo -e "\033[38;5;214mGit\033[0m: Clone taskfile repo" git clone https://git.taskfile.build "$HOME/taskfile.build" else echo -e "\033[38;5;214mGit\033[0m: Pull taskfile repo" git -C "$HOME/taskfile.build" pull fi } if [[ -d "$HOME/taskfile.build/bin" ]]; then for file in "$HOME/taskfile.build/bin/"*; do if [[ -f "$file" ]]; then source "$file" fi done fi # Env Commands version() { kind version helm version } info() { echo "OpenShift-Project: $OC_PROJECT" echo "Current-Context: $current_context" echo "Context-Alias: $context_alias" echo "Namespace: $namespace" } install-vitepress() { pnpm install } setup-hosts() { echo 'Adding entries to /etc/hosts ...' # Cluster grep -qxF '127.0.0.1 knd.local' /etc/hosts || echo '127.0.0.1 knd.local' | sudo tee -a /etc/hosts > /dev/null # Odoo grep -qxF '127.0.0.1 odoo.local' /etc/hosts || echo '127.0.0.1 odoo.local' | sudo tee -a /etc/hosts > /dev/null grep -qxF '127.0.0.1 odoo.knd.local' /etc/hosts || echo '127.0.0.1 odoo.knd.local' | sudo tee -a /etc/hosts > /dev/null grep -qxF '127.0.0.1 staging.odoo.knd.local' /etc/hosts || echo '127.0.0.1 staging.odoo.knd.local' | sudo tee -a /etc/hosts > /dev/null # Odoo with different namespace grep -qxF '127.0.0.1 restic.local' /etc/hosts || echo '127.0.0.1 restic.local' | sudo tee -a /etc/hosts > /dev/null grep -qxF '127.0.0.1 restic.knd.local' /etc/hosts || echo '127.0.0.1 restic.knd.local' | sudo tee -a /etc/hosts > /dev/null grep -qxF '127.0.0.1 staging.restic.knd.local' /etc/hosts || echo '127.0.0.1 staging.restic.knd.local' | sudo tee -a /etc/hosts > /dev/null # Nextcloud grep -qxF '127.0.0.1 nextcloud.local' /etc/hosts || echo '127.0.0.1 nextcloud.local' | sudo tee -a /etc/hosts > /dev/null grep -qxF '127.0.0.1 nextcloud.knd.local' /etc/hosts || echo '127.0.0.1 nextcloud.knd.local' | sudo tee -a /etc/hosts > /dev/null # Collabora grep -qxF '127.0.0.1 doc.nextcloud.knd.local' /etc/hosts || echo '127.0.0.1 doc.nextcloud.knd.local' | sudo tee -a /etc/hosts > /dev/null # Hugo grep -qxF '127.0.0.1 hugo.knd.local' /etc/hosts || echo '127.0.0.1 hugo.knd.local' | sudo tee -a /etc/hosts > /dev/null # Vuepress grep -qxF '127.0.0.1 vuepress.knd.local' /etc/hosts || echo '127.0.0.1 vuepress.knd.local' | sudo tee -a /etc/hosts > /dev/null } test-chart-odoo() { if [[ "$1" = 'clean' ]]; then echo 'Cleanup environment' delete-kind else start-kind add-repos info # install-chart haproxy-ingress # kubectl wait --namespace haproxy-ingress --for=condition=Available --timeout=60s deployments/haproxy-ingress install-chart ingress-nginx kubectl wait --namespace ingress-nginx --for=condition=Available --timeout=60s deployments/ingress-nginx-controller install-chart cnpg kubectl wait --namespace cnpg-system --for=condition=Available --timeout=60s deployments/cnpg-cloudnative-pg update-dependencies odoo install-chart odoo test_values/odoo.knd.local.yaml fi } test-chart-nextcloud() { if [[ "$1" = 'clean' ]]; then echo 'Cleanup environment' delete-kind else start-kind add-repos info # install-chart haproxy-ingress # kubectl wait --namespace haproxy-ingress --for=condition=Available --timeout=60s deployments/haproxy-ingress install-chart ingress-nginx kubectl wait --namespace ingress-nginx --for=condition=Available --timeout=60s deployments/ingress-nginx-controller install-chart cnpg kubectl wait --namespace cnpg-system --for=condition=Available --timeout=60s deployments/cnpg-cloudnative-pg kubectl create secret generic nextcloud-creds --from-literal=NEXTCLOUD_ADMIN_USER="test" --from-literal=NEXTCLOUD_ADMIN_PASSWORD="test" install-chart nextcloud test_values/nextcloud.knd.local.yaml fi } export-kubeconfig() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi if test -z "$2"; then echo '\$2 is empty.'; exit; fi local kube_user=$1 local kube_output_file=$2 kubectl config set-cluster default --server=https://k3s.raspberrypi.build --kubeconfig=$kube_output_file kubectl config set-context default --cluster=default --user=$kube_user --kubeconfig=$kube_output_file kubectl config set-credentials "$kube_user" --token=$(kubectl create token $kube_user --duration=87600h) --kubeconfig=$kube_output_file kubectl config use-context default --kubeconfig=$kube_output_file echo "Kubeconfig exported for user $kube_user to file $kube_output_file" } # Host Commands start-kind() { if [[ "$context_alias" != "loc" ]]; then echo "Current context alias is '$context_alias' and not 'loc'." exit 1 fi echo 'Start kind.' kind create cluster } load-image() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi if kind &> /dev/null && kind get clusters | grep -q 'kind'; then echo 'Kind cluster is available. Checking for local Docker images...' if docker images -q "$1" > /dev/null; then echo 'Loading local Odoo image into Kind cluster...' kind load docker-image "$1" fi fi } delete-kind() { echo 'Delete kind.' kind delete cluster } forward() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi "forward-$1" } forward-postgres() { local port=5432 echo "Connect to http://localhost:$port url in your browser." kubectl port-forward service/postgres $port:5432 } forward-odoo() { local port=8080 echo "Open http://localhost:$port url in your browser." kubectl port-forward service/odoo $port:8069 } forward-odoo-staging() { local port=8080 echo "Open http://localhost:$port url in your browser." kubectl port-forward service/odoo-staging $port:8069 } forward-nextcloud() { local port=8080 echo "Open http://localhost:$port url in your browser." kubectl port-forward service/nextcloud $port:80 } forward-haproxy-ingress() { local port=8080 kubectl get ingresses echo "Open http://\$NAMESPACE.knd.local:$port url in your browser." kubectl port-forward -n haproxy-ingress svc/haproxy-ingress $port:80 } forward-ingress-nginx() { local port=8080 kubectl get ingresses echo "Open http://\$NAMESPACE.knd.local:$port url in your browser." kubectl port-forward -n ingress-nginx svc/ingress-nginx $port:80 } # Chart Commands list-charts() { for dir in */; do dir_name="${dir%/}" if [[ ! "$dir_name" =~ ^(public|values|test_values)$ ]] && ! git check-ignore -q "$dir_name" 2>/dev/null; then echo "$dir_name" fi done } list-external-charts() { echo 'cert-manager' echo 'cnpg' echo 'haproxy-ingress' echo 'ingress-nginx' echo 'k8up' } list-cluster() { for file in values/*.yaml; do local kind=$(yq eval '.kind' "$file") local alias=$(yq eval '.alias' "$file") if [[ -n "$kind" && "$kind" = 'cluster' ]]; then local base=$(basename "$file" .yaml) printf '%s (%s)\n' "$alias" "$base" fi done | sort } switch-cluster() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi local link_name="$HOME/.kube/config" echo "Remove $link_name" rm "$link_name" || true local target="$HOME/.kube/config.$1" touch "$target" echo "Setup symlink from $link_name to $target" ln -s "$target" "$link_name" chmod 0644 "$target" echo 'Select namespace' kubens } show-context() { basename "$(readlink "$HOME/.kube/config")" | cut -d. -f2 } update-dependencies() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi helm dependency update "$1" } install-chart() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi if [[ "$(get-context-alias)" = 'axo' ]]; then echo "Set oc project to $OC_PROJECT." oc project "$OC_PROJECT" fi local release_name="$(echo "$1" | tr '[:upper:]' '[:lower:]')" if [[ "$1" = 'clusterIssuer' ]]; then "install-$1" "$2" elif list-charts | grep -q "$1"; then echo "Install chart $1 as release $release_name:" helm install "$release_name" "$1" \ --values "$2" else "install-$1" "$2" fi } install-k8up() { echo 'Install k8up-io/k8up.' helm install k8up k8up-io/k8up } install-cnpg() { echo 'Install cnpg/cloudnative-pg.' helm upgrade --install cnpg \ --namespace cnpg-system \ --create-namespace \ cnpg/cloudnative-pg } install-k8up() { echo 'Install k8up-io/k8up.' helm install k8up k8up-io/k8up \ --create-namespace --namespace k8up } install-cert-manager() { echo 'Install jetstack/cert-manager.' helm install \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --create-namespace \ --version v1.18.2 \ --set crds.enabled=true \ --set config.featureGates.ACMEHTTP01IngressPathTypeExact=false # https://github.com/Infomaniak/cert-manager-webhook-infomaniak echo 'Apply Infomaniak webhook' kubectl apply -f https://github.com/Infomaniak/cert-manager-webhook-infomaniak/releases/download/v0.3.1/rendered-manifest.yaml } install-clusterIssuer() { echo 'Install clusterissuer.' helm install clusterissuer clusterIssuer \ --namespace cert-manager \ --values "$1" } install-haproxy-ingress() { echo 'Install haproxy-ingress/haproxy-ingress.' helm install haproxy-ingress haproxy-ingress/haproxy-ingress \ --create-namespace --namespace haproxy-ingress } install-ingress-nginx() { echo 'Install ingress-nginx/ingress-nginx.' helm install ingress-nginx ingress-nginx/ingress-nginx \ --create-namespace --namespace ingress-nginx } upgrade-release() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi local release_name="$(echo "$1" | tr '[:upper:]' '[:lower:]')" if list-charts | grep -q "$1"; then echo "Upgrade $1 chart for release $release_name:" helm upgrade "$release_name" "$1" \ --values "$2" else "upgrade-$1" "$2" fi } upgrade-cert-manager() { echo 'Upgrade jetstack/cert-manager.' helm upgrade \ cert-manager jetstack/cert-manager \ --namespace cert-manager \ --version v1.18.2 \ --set crds.enabled=true \ --set config.featureGates.ACMEHTTP01IngressPathTypeExact=false # https://github.com/Infomaniak/cert-manager-webhook-infomaniak echo 'Apply Infomaniak webhook' kubectl apply -f https://github.com/Infomaniak/cert-manager-webhook-infomaniak/releases/download/v0.3.1/rendered-manifest.yaml } upgrade-clusterIssuer() { echo 'Upgrade clusterIssuer.' helm upgrade \ clusterissuer clusterIssuer \ --namespace cert-manager \ --values "$2" } uninstall-release() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi local release_name="$(echo "$1" | tr '[:upper:]' '[:lower:]')" echo "Uninstall release $release_name:" helm uninstall "$release_name" } # kubectl Commands get-secrets() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi if [[ "$1" = 'postgres' ]]; then echo 'postgres/user:' kubectl get secret postgres -o jsonpath="{.data.user}" | base64 --decode && echo echo 'postgres/password:' kubectl get secret postgres -o jsonpath="{.data.password}" | base64 --decode && echo elif [[ "$1" = 'clusterIssuer' ]]; then echo 'cert-manager/infomaniak-api-credentials:' kubectl get secret -n cert-manager infomaniak-api-token -o jsonpath="{.data.access-token}" | base64 --decode && echo elif [[ "$1" = 'forgejoRunner' ]]; then echo 'forgejo-runner/forgejoInstanceToken:' kubectl get secret forgejo-runner -o jsonpath="{.data.forgejoInstanceToken}" | base64 --decode && echo elif [[ "$1" = 'nextcloud' ]]; then echo 'nextcloud/creds:' kubectl get secret nextcloud-creds -o jsonpath="{.data.NEXTCLOUD_ADMIN_USER}" | base64 --decode && echo echo 'nextcloud/password:' kubectl get secret nextcloud-creds -o jsonpath="{.data.NEXTCLOUD_ADMIN_PASSWORD}" | base64 --decode && echo fi } exec-bash() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi kubectl exec -it "$1" -- bash } logs() { local pod_id=$(kubectl get pods -o=name --all-namespaces | grep "$1") kubectl logs "$pod_id" } get-pods() { kubectl get pods } logs-haproxy-ingress() { local pod_id=$(kubectl get pods -o=name -n haproxy-ingress | grep 'haproxy-ingress') kubectl logs "$pod_id" -n haproxy-ingress } # Helm Commands template-manifest() { if test -z "$1"; then echo '\$1 is empty.'; exit; fi if test -z "$2"; then echo '\$2 is empty.'; exit; fi local release_name="$(echo "$1" | tr '[:upper:]' '[:lower:]')" echo "Template chart $1 as release ${release_name}..." if [[ "$1" = 'clusterIssuer' ]]; then helm --debug template "$release_name" clusterIssuer \ --namespace cert-manager \ --values "$2" > manifest.yaml elif list-charts | grep -q "$1"; then helm --debug template "$release_name" "$1" \ --values "$2" fi } list() { helm list } package-repo() { if [[ -n "$1" ]]; then echo "Package $1 Helm chart." helm package "$1" else echo 'Package all Helm charts.' index-repo for chart in $(list-charts); do helm package "$chart" done fi } commit() { git config user.name 'Mint Bot' git config user.email 'bot@mint-system.ch' git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/Mint-System/Kubernetes-Build" git add *.tgz git commit -m 'feat(helm): package revision update' git push || echo 'No changes to commit' } index-repo() { echo 'Update index.yaml' helm repo index . } add-repos(){ echo 'Add kubernetes-build.' helm repo add kubernetes-build https://kubernetes.build echo 'Add haproxy-ingress.' helm repo add haproxy-ingress https://haproxy-ingress.github.io/charts echo 'Add ingress-nginx.' helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx echo 'Add jetstack.' helm repo add jetstack https://charts.jetstack.io echo 'Add cnpg.' helm repo add cnpg https://cloudnative-pg.github.io/charts echo 'Add k8up.' helm repo add k8up-io https://k8up-io.github.io/k8up echo 'Add nextcloud' helm repo add nextcloud https://nextcloud.github.io/helm/ echo 'Add collabora' helm repo add collabora https://collaboraonline.github.io/online/ } remove-repo(){ echo 'Remove the kubernetes-build repo.' helm repo remove kubernetes-build } # Docs Commands docs() { for repo in $(list-charts | grep -v '^deploymentUpdater$'); do pnpm run generate-readme --values "$repo/values.yaml" -r "$repo/README.md" done } lint() { for chart in $(list-charts); do helm lint "$chart" done } dev-vitepress() { cp README.md index.md pnpm run docs:dev } build-vitepress() { cp README.md index.md pnpm run docs:build cp *.tgz .vitepress/dist cp index.yaml .vitepress/dist } serve-vitepress() { pnpm run docs:preview } if declare -f "$1" > /dev/null; then "$1" "${@:2}" else case "$1" in *) echo "Unknown command: $1" help exit 1 ;; esac fi