├── tests ├── apps │ ├── python │ │ ├── null │ │ ├── task.py │ │ ├── CHECKS │ │ ├── check_deploy │ │ ├── app.json │ │ ├── release.py │ │ ├── worker.py │ │ ├── web.py │ │ └── Procfile │ └── dockerfile-procfile │ │ ├── CHECKS │ │ ├── check_deploy │ │ ├── worker.js │ │ ├── Dockerfile │ │ ├── app.json │ │ ├── package.json │ │ ├── Procfile │ │ └── web.js ├── shellcheck-exclude ├── deploy.bats ├── test_helper.bash ├── setup.sh ├── set.bats └── shellcheck-to-junit ├── .gitignore ├── templates ├── cert-manager-tls.json.sigil ├── volume-mounts.json.sigil ├── namespace.json.sigil ├── volumes.json.sigil ├── horizontal-pod-autoscaler-metric-resource.json.sigil ├── ingress.json.sigil ├── horizontal-pod-autoscaler-metric-pods.json.sigil ├── horizontal-pod-autoscaler-metric-external.json.sigil ├── pod-disruption-budget.json.sigil ├── horizontal-pod-autoscaler-metric-external-selector.json.sigil ├── ingress-rule.json.sigil ├── pvc.json.sigil ├── horizontal-pod-autoscaler-metric-ingress.json.sigil ├── certificate-issuer.json.sigil ├── horizontal-pod-autoscaler.json.sigil ├── service.json.sigil └── deployment.json.sigil ├── plugin.toml ├── report ├── subcommands ├── report ├── list-pvc ├── rolling-update ├── default ├── unmount-all ├── list-mount ├── autoscale-apply ├── autoscale-rule-list ├── mount ├── unmount ├── remove-pvc ├── pod-annotations-set ├── ingress-annotations-set ├── service-annotations-set ├── deployment-annotations-set ├── autoscale-rule-add ├── set ├── autoscale-set ├── autoscale-rule-remove ├── add-pvc └── show-manifest ├── core-post-deploy ├── post-domains-update ├── proxy-build-config ├── post-proxy-ports-update ├── .editorconfig ├── config ├── commands ├── post-delete ├── post-create ├── scheduler-run ├── scheduler-logs-failed ├── scheduler-tags-create ├── scheduler-tags-destroy ├── dependencies ├── post-extract ├── Vagrantfile ├── LICENSE.txt ├── pre-deploy-kubernetes-apply ├── scheduler-post-delete ├── scheduler-is-deployed ├── scheduler-logs ├── scheduler-stop ├── scheduler-app-status ├── install ├── scheduler-deploy ├── Makefile ├── README.md └── internal-functions /tests/apps/python/null: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | tmp 3 | -------------------------------------------------------------------------------- /templates/cert-manager-tls.json.sigil: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "hosts": [], 4 | "secretName": "app-ingress-tls" 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /templates/volume-mounts.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ $.VOLUME_NAME }}", 3 | "mountPath": "{{ $.MOUNT_PATH }}" 4 | } 5 | -------------------------------------------------------------------------------- /tests/apps/python/task.py: -------------------------------------------------------------------------------- 1 | def main(args): 2 | print(args) 3 | 4 | if __name__ == '__main__': 5 | main(sys.argv) 6 | -------------------------------------------------------------------------------- /tests/apps/python/CHECKS: -------------------------------------------------------------------------------- 1 | WAIT=2 # wait 2 seconds 2 | TIMEOUT=5 # set timeout to 5 seconds 3 | ATTEMPTS=2 # try 2 times 4 | 5 | / python/http.server 6 | -------------------------------------------------------------------------------- /templates/namespace.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "Namespace", 4 | "metadata": { 5 | "name": "{{ $.NAME }}" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/apps/dockerfile-procfile/CHECKS: -------------------------------------------------------------------------------- 1 | WAIT=2 # wait 2 seconds 2 | TIMEOUT=5 # set timeout to 5 seconds 3 | ATTEMPTS=2 # try 2 times 4 | 5 | / nodejs/express 6 | -------------------------------------------------------------------------------- /tests/apps/python/check_deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | output="$(curl -s -S "$1")" 4 | echo "$output" 5 | test "$output" == "python/http.server" 6 | -------------------------------------------------------------------------------- /tests/apps/dockerfile-procfile/check_deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | output="$(curl -s -S "$1")" 4 | echo "$output" 5 | test "$output" == "nodejs/express" 6 | -------------------------------------------------------------------------------- /templates/volumes.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ $.VOLUME_NAME }}", 3 | "persistentVolumeClaim": { 4 | "claimName": "{{ $.CLAIM_NAME }}" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/apps/dockerfile-procfile/worker.js: -------------------------------------------------------------------------------- 1 | function worker() { 2 | console.log('sleeping for 60 seconds'); 3 | setTimeout(worker, 60 * 1000); 4 | } 5 | 6 | worker(); 7 | -------------------------------------------------------------------------------- /tests/apps/dockerfile-procfile/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:4-alpine 2 | 3 | RUN apk add --no-cache bash 4 | 5 | COPY . /app 6 | WORKDIR /app 7 | RUN npm install 8 | 9 | CMD npm start 10 | -------------------------------------------------------------------------------- /plugin.toml: -------------------------------------------------------------------------------- 1 | [plugin] 2 | description = "dokku scheduler-kubernetes plugin" 3 | version = "0.21.0" 4 | [plugin.config] 5 | dependencies = [ 6 | "https://github.com/dokku/dokku-registry.git" 7 | ] 8 | -------------------------------------------------------------------------------- /tests/apps/python/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dokku": { 4 | "predeploy": "touch /app/predeploy.test", 5 | "postdeploy": "touch /app/postdeploy.test" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /report: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | 6 | cmd-scheduler-kubernetes-report-single "$@" 7 | -------------------------------------------------------------------------------- /tests/apps/dockerfile-procfile/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dokku": { 4 | "predeploy": "touch /app/predeploy.test", 5 | "postdeploy": "touch /app/postdeploy.test" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /subcommands/report: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | 6 | cmd-scheduler-kubernetes-report "$@" 7 | -------------------------------------------------------------------------------- /subcommands/list-pvc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | 6 | cmd-scheduler-kubernetes-list-pvc "$2" 7 | -------------------------------------------------------------------------------- /tests/apps/python/release.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | 4 | 5 | def main(): 6 | print("SECRET_KEY: {0}".format(os.getenv('SECRET_KEY'))) 7 | 8 | 9 | if __name__ == '__main__': 10 | main() 11 | -------------------------------------------------------------------------------- /tests/apps/python/worker.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | def main(): 5 | print("sleeping for 60 seconds") 6 | while True: 7 | time.sleep(60) 8 | 9 | 10 | if __name__ == "__main__": 11 | main() 12 | -------------------------------------------------------------------------------- /core-post-deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | 6 | fn-scheduler-kubernetes-ingress-build-config "$@" 7 | -------------------------------------------------------------------------------- /post-domains-update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | 6 | fn-scheduler-kubernetes-ingress-build-config "$@" 7 | -------------------------------------------------------------------------------- /proxy-build-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | 6 | fn-scheduler-kubernetes-ingress-build-config "$@" 7 | -------------------------------------------------------------------------------- /post-proxy-ports-update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | 6 | fn-scheduler-kubernetes-ingress-build-config "$@" 7 | -------------------------------------------------------------------------------- /subcommands/rolling-update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | 6 | cmd-scheduler-kubernetes-rolling-update "$2" 7 | -------------------------------------------------------------------------------- /subcommands/default: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | 6 | cmd-scheduler-kubernetes-help "scheduler-kubernetes:help" 7 | -------------------------------------------------------------------------------- /tests/apps/dockerfile-procfile/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-example", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "express": "4.17.x" 6 | }, 7 | "engines": { 8 | "node": "4.2.x" 9 | }, 10 | "scripts": { 11 | "start": "false" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/horizontal-pod-autoscaler-metric-resource.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Resource", 3 | "resource": { 4 | "name": "{{ $.TARGET_NAME }}", 5 | "target": { 6 | "type": "{{ $.TARGET_TYPE }}", 7 | "{{ $.TARGET_TYPE_KEY }}": {{ $.TARGET_VALUE }} 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /templates/ingress.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "networking.k8s.io/v1", 3 | "kind": "Ingress", 4 | "metadata": { 5 | "name": "app-ingress", 6 | "annotations": { 7 | "kubernetes.io/ingress.class": "nginx" 8 | } 9 | }, 10 | "spec": { 11 | "rules": [] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | indent_style = space 6 | indent_size = 2 7 | 8 | [Makefile] 9 | insert_final_newline = true 10 | indent_style = tab 11 | indent_size = 4 12 | 13 | [*.mk] 14 | insert_final_newline = true 15 | indent_style = tab 16 | indent_size = 4 -------------------------------------------------------------------------------- /templates/horizontal-pod-autoscaler-metric-pods.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Pods", 3 | "pods": { 4 | "metric": { 5 | "name": "{{ $.TARGET_NAME }}" 6 | }, 7 | "target": { 8 | "type": "{{ $.TARGET_TYPE }}", 9 | "{{ $.TARGET_TYPE_KEY }}": {{ $.TARGET_VALUE }} 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/apps/dockerfile-procfile/Procfile: -------------------------------------------------------------------------------- 1 | ############################### 2 | # DEVELOPMENT # 3 | ############################### 4 | 5 | # Procfile for development using the new threaded worker (scheduler, twitter stream and delayed job) 6 | cron: node worker.js 7 | web: node web.js 8 | worker: node worker.js 9 | -------------------------------------------------------------------------------- /templates/horizontal-pod-autoscaler-metric-external.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "type": "External", 3 | "external": { 4 | "metric": { 5 | "name": "{{ $.TARGET_NAME }}" 6 | }, 7 | "target": { 8 | "type": "{{ $.TARGET_TYPE }}", 9 | "{{ $.TARGET_TYPE_KEY }}": {{ $.TARGET_VALUE }} 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/apps/dockerfile-procfile/web.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | 3 | var app = express(); 4 | 5 | app.get('/', function(request, response) { 6 | response.send('nodejs/express'); 7 | }); 8 | 9 | var port = process.env.PORT || 5000; 10 | app.listen(port, function() { 11 | console.log("Listening on " + port); 12 | }); 13 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export SCHEDULER_KUBERNETES_ROOT=${SCHEDULER_KUBERNETES_ROOT:="$DOKKU_LIB_ROOT/config/scheduler-kubernetes"} 3 | 4 | export PLUGIN_COMMAND_PREFIX="scheduler-kubernetes" 5 | export PLUGIN_CONFIG_ROOT=${PLUGIN_CONFIG_ROOT:="$DOKKU_LIB_ROOT/config/$PLUGIN_COMMAND_PREFIX"} 6 | export PLUGIN_DATA_ROOT=$SCHEDULER_KUBERNETES_ROOT 7 | -------------------------------------------------------------------------------- /commands: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | 6 | case "$1" in 7 | help | scheduler-kubernetes:help) 8 | cmd-scheduler-kubernetes-help "$@" 9 | ;; 10 | 11 | *) 12 | exit "$DOKKU_NOT_IMPLEMENTED_EXIT" 13 | ;; 14 | 15 | esac 16 | -------------------------------------------------------------------------------- /templates/pod-disruption-budget.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "policy/v1", 3 | "kind": "PodDisruptionBudget", 4 | "metadata": { 5 | "name": "{{ $.APP }}", 6 | "labels": { 7 | "app": "{{ $.APP }}" 8 | } 9 | }, 10 | "spec": { 11 | "selector": { 12 | "matchLabels": { 13 | "app": "{{ $.APP }}" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /templates/horizontal-pod-autoscaler-metric-external-selector.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "type": "External", 3 | "external": { 4 | "metric": { 5 | "name": "{{ $.TARGET_NAME }}", 6 | "selector": "{{ $.TARGET_EXTRA }}" 7 | }, 8 | "target": { 9 | "type": "{{ $.TARGET_TYPE }}", 10 | "{{ $.TARGET_TYPE_KEY }}": {{ $.TARGET_VALUE }} 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /templates/ingress-rule.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "host": "{{ $.DOMAIN }}", 3 | "http": { 4 | "paths": [ 5 | { 6 | "backend": { 7 | "service": { 8 | "name": "{{ $.APP }}-{{ $.PROCESS_TYPE }}", 9 | "port": { 10 | "number": {{ $.PORT }} 11 | } 12 | } 13 | }, 14 | "path": "/", 15 | "pathType": "ImplementationSpecific" 16 | } 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /templates/pvc.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "PersistentVolumeClaim", 4 | "metadata": { 5 | "name": "{{ $.NAME }}", 6 | "labels": { 7 | "pvc": "{{ $.NAME }}" 8 | } 9 | }, 10 | "spec": { 11 | "accessModes": [ 12 | "{{ $.ACCESS_MODE }}" 13 | ], 14 | "resources": { 15 | "requests": { 16 | "storage": "{{ $.STORAGE }}Mi" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/horizontal-pod-autoscaler-metric-ingress.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Object", 3 | "object": { 4 | "metric": { 5 | "name": "{{ $.TARGET_NAME }}" 6 | }, 7 | "target": { 8 | "type": "{{ $.TARGET_TYPE }}", 9 | "{{ $.TARGET_TYPE_KEY }}": {{ $.TARGET_VALUE }} 10 | }, 11 | "describedObject": { 12 | "apiVersion": "networking.k8s.io/v1", 13 | "kind": "Ingress", 14 | "name": "{{ $.TARGET_EXTRA }}" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /post-delete: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 5 | 6 | scheduler-kubernetes-post-delete() { 7 | declare desc="scheduler-kubernetes post-delete plugin trigger" 8 | declare trigger="scheduler-kubernetes post-delete" 9 | declare APP="$1" 10 | local KUBE_ARGS NAMESPACE 11 | 12 | fn-plugin-property-destroy "scheduler-kubernetes" "$APP" 13 | } 14 | 15 | scheduler-kubernetes-post-delete "$@" 16 | -------------------------------------------------------------------------------- /post-create: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | 5 | scheduler-kubernetes-post-create() { 6 | declare desc="scheduler-kubernetes post-create plugin trigger" 7 | declare trigger="scheduler-kubernetes-post-create" 8 | declare APP="$1" 9 | 10 | mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/${APP}" 11 | chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/${APP}" 12 | } 13 | 14 | scheduler-kubernetes-post-create "$@" 15 | -------------------------------------------------------------------------------- /scheduler-run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | 6 | scheduler-kubernetes-scheduler-run() { 7 | declare desc="scheduler-kubernetes scheduler-run plugin trigger" 8 | declare trigger="scheduler-kubernetes scheduler-run" 9 | declare DOKKU_SCHEDULER="$1" APP="$2" 10 | 11 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 12 | return 13 | fi 14 | 15 | dokku_log_warn "Not implemented for kubernetes" 16 | } 17 | 18 | scheduler-kubernetes-scheduler-run "$@" 19 | -------------------------------------------------------------------------------- /scheduler-logs-failed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | 6 | scheduler-kubernetes-scheduler-logs-failed() { 7 | declare desc="scheduler-kubernetes scheduler-logs-failed plugin trigger" 8 | declare trigger="scheduler-kubernetes scheduler-logs-failed" 9 | declare DOKKU_SCHEDULER="$1" APP="$2" 10 | 11 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 12 | return 13 | fi 14 | 15 | dokku_log_warn "Not implemented for kubernetes" 16 | } 17 | 18 | scheduler-kubernetes-scheduler-logs-failed "$@" 19 | -------------------------------------------------------------------------------- /scheduler-tags-create: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | 6 | scheduler-kubernetes-scheduler-tags-create() { 7 | declare desc="scheduler-kubernetes scheduler-tags-create plugin trigger" 8 | declare trigger="scheduler-kubernetes scheduler-tags-create" 9 | declare DOKKU_SCHEDULER="$1" APP="$2" SOURCE_IMAGE="$3" TARGET_IMAGE="$4" 10 | 11 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 12 | return 13 | fi 14 | 15 | dokku_log_warn "Not implemented for kubernetes" 16 | } 17 | 18 | scheduler-kubernetes-scheduler-tags-create "$@" 19 | -------------------------------------------------------------------------------- /scheduler-tags-destroy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | 6 | scheduler-kubernetes-scheduler-tags-destroy() { 7 | declare desc="scheduler-kubernetes scheduler-tags-destroy plugin trigger" 8 | declare trigger="scheduler-kubernetes scheduler-tags-destroy" 9 | declare DOKKU_SCHEDULER="$1" APP="$2" IMAGE_REPO="$3" IMAGE_TAG="$4" 10 | 11 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 12 | return 13 | fi 14 | 15 | dokku_log_warn "Not implemented for kubernetes" 16 | } 17 | 18 | scheduler-kubernetes-scheduler-tags-destroy "$@" 19 | -------------------------------------------------------------------------------- /templates/certificate-issuer.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "cert-manager.io/v1", 3 | "kind": "ClusterIssuer", 4 | "metadata": { 5 | "name": "letsencrypt-prod" 6 | }, 7 | "spec": { 8 | "acme": { 9 | "email": "{{ $.CERT_MANAGER_EMAIL }}", 10 | "server": "https://acme-v02.api.letsencrypt.org/directory", 11 | "privateKeySecretRef": { 12 | "name": "letsencrypt-prod-private-key" 13 | }, 14 | "solvers": [ 15 | { 16 | "http01": { 17 | "ingress": { 18 | "class": "nginx" 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /templates/horizontal-pod-autoscaler.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "autoscaling/v2beta2", 3 | "kind": "HorizontalPodAutoscaler", 4 | "metadata": { 5 | "name": "{{ $.APP }}-{{ $.PROCESS_TYPE }}", 6 | "labels": { 7 | "app": "{{ $.APP }}", 8 | "process-type": "{{ $.PROCESS_TYPE }}", 9 | "app-process-type": "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 10 | } 11 | }, 12 | "spec": { 13 | "scaleTargetRef": { 14 | "apiVersion": "apps/v1", 15 | "kind": "Deployment", 16 | "name": "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 17 | }, 18 | "minReplicas": {{ $.MIN_REPLICAS }}, 19 | "maxReplicas": {{ $.MAX_REPLICAS }}, 20 | "metrics": [] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /templates/service.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "kind": "Service", 4 | "metadata": { 5 | "name": "{{ $.APP }}-{{ $.PROCESS_TYPE }}", 6 | "labels": { 7 | "app": "{{ $.APP }}", 8 | "process-type": "{{ $.PROCESS_TYPE }}", 9 | "app-process-type": "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 10 | } 11 | }, 12 | "spec": { 13 | "ports": [ 14 | { 15 | "protocol": "TCP", 16 | "port": {{ $.PORT }}, 17 | "targetPort": {{ $.PORT }} 18 | } 19 | ], 20 | "selector": { 21 | "app": "{{ $.APP }}", 22 | "process-type": "{{ $.PROCESS_TYPE }}", 23 | "app-process-type": "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dependencies: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 5 | 6 | scheduler-kubernetes-install-dependencies() { 7 | declare desc="scheduler-kubernetes install-dependencies plugin trigger" 8 | declare trigger="scheduler-kubernetes-install-dependencies" 9 | 10 | case "$DOKKU_DISTRO" in 11 | debian | ubuntu) 12 | apt-get install -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -qq -y jq 13 | ;; 14 | 15 | opensuse) 16 | zypper -q in -y jq 17 | ;; 18 | 19 | arch) 20 | pacman -S --noconfirm --noprogressbar --needed jq 21 | ;; 22 | esac 23 | } 24 | 25 | scheduler-kubernetes-install-dependencies "$@" 26 | -------------------------------------------------------------------------------- /tests/apps/python/web.py: -------------------------------------------------------------------------------- 1 | import http.server 2 | import json 3 | import os 4 | 5 | 6 | class GetHandler(http.server.BaseHTTPRequestHandler): 7 | def do_GET(self): 8 | self.send_response(200) 9 | self.send_header("Content-Type", "text/plain; charset=utf-8") 10 | self.end_headers() 11 | 12 | if self.path == '/': 13 | self.wfile.write("python/http.server".encode("utf-8")) 14 | else: 15 | data = json.dumps(dict(os.environ), sort_keys=True, indent=4) 16 | self.wfile.write(data.encode("utf-8")) 17 | 18 | 19 | if __name__ == "__main__": 20 | port = int(os.getenv("PORT", 5000)) 21 | server = http.server.HTTPServer(("0.0.0.0", port), GetHandler) 22 | print("Listening on port {0}".format(port)) 23 | server.serve_forever() 24 | -------------------------------------------------------------------------------- /subcommands/unmount-all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-unmount-all() { 9 | declare desc="unmount all volumes" 10 | declare cmd="scheduler-kubernetes:unmount-all" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare APP="$1" 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an App name" 14 | verify_app_name "$APP" 15 | 16 | dokku_log_info2_quiet "unmounting all volumes from app (Re-deploy Required) " 17 | fn-plugin-property-delete "scheduler-kubernetes" "$APP" "volumes" 18 | } 19 | 20 | cmd-scheduler-kubernetes-unmount-all "$@" 21 | -------------------------------------------------------------------------------- /subcommands/list-mount: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-list-mount() { 9 | declare desc="list mounted a volumes" 10 | declare cmd="scheduler-kubernetes:list-mount" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare APP="$1" 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an App name" 14 | verify_app_name "$APP" 15 | 16 | dokku_log_info2_quiet "Listing Mounts as Claim-Name:Path" 17 | VOLUMES="$(fn-plugin-property-list-get "scheduler-kubernetes" "$APP" "volumes")" 18 | for VOLUME in ${VOLUMES[@]}; do 19 | echo "$VOLUME" 20 | done 21 | } 22 | 23 | cmd-scheduler-kubernetes-list-mount "$@" 24 | -------------------------------------------------------------------------------- /post-extract: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | 6 | scheduler-kubernetes-post-extract() { 7 | declare desc="scheduler-kubernetes post-extract plugin trigger" 8 | declare trigger="scheduler-kubernetes-post-extract" 9 | declare APP="$1" TMP_WORK_DIR="$2" REV="$3" 10 | 11 | local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") 12 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 13 | return 14 | fi 15 | 16 | pushd "$TMP_WORK_DIR" >/dev/null 17 | for process_type in $(procfile-util list); do 18 | if echo "$process_type" | grep -vqE '^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$'; then 19 | dokku_log_fail "Invalid process type name specified for kubernetes deploy ($process_type)" 20 | fi 21 | done 22 | popd >/dev/null 23 | } 24 | 25 | scheduler-kubernetes-post-extract "$@" 26 | -------------------------------------------------------------------------------- /subcommands/autoscale-apply: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-autoscale-apply() { 9 | declare desc="apply autoscale settings for an app/proc-type combination" 10 | declare cmd="scheduler-kubernetes:autoscale-apply" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare APP="$1" PROC_TYPE="$2" 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 14 | [[ -z "$PROC_TYPE" ]] && dokku_log_fail "Please specify a process type to run the command on" 15 | 16 | verify_app_name "$APP" 17 | fn-scheduler-kubernetes-autoscale-apply "$APP" "$PROC_TYPE" 18 | } 19 | 20 | cmd-scheduler-kubernetes-autoscale-apply "$@" 21 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | BOX_NAME = ENV["BOX_NAME"] || "bento/ubuntu-18.04" 5 | BOX_MEMORY = ENV["BOX_MEMORY"] || "2048" 6 | DOKKU_VERSION = "master" 7 | 8 | Vagrant.configure(2) do |config| 9 | config.vm.box = BOX_NAME 10 | config.ssh.forward_agent = true 11 | 12 | config.vm.provider :virtualbox do |vb| 13 | vb.customize ["modifyvm", :id, "--memory", BOX_MEMORY] 14 | end 15 | 16 | config.vm.provider :vmware_fusion do |v, override| 17 | v.vmx["memsize"] = BOX_MEMORY 18 | end 19 | 20 | config.vm.define "default", primary: true do |vm| 21 | vm.vm.synced_folder File.dirname(__FILE__), "/vagrant" 22 | 23 | vm.vm.provision :shell, :inline => "apt -q update && apt -y -qq install git software-properties-common" 24 | vm.vm.provision :shell, :inline => "cd /vagrant && DOKKU_VERSION=#{DOKKU_VERSION} make setup" 25 | vm.vm.provision :shell, :inline => "cd /vagrant && DOKKU_TRACE=1 DOKKU_VERSION=#{DOKKU_VERSION} make test" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /subcommands/autoscale-rule-list: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-autoscale-rule-list() { 9 | declare desc="list autoscale rules for an app/proc-type combination" 10 | declare cmd="scheduler-kubernetes:autoscale-rule-list" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare APP="$1" PROC_TYPE="$2" 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 14 | [[ -z "$PROC_TYPE" ]] && dokku_log_fail "Please specify a process type to run the command on" 15 | 16 | verify_app_name "$APP" 17 | dokku_log_info2_quiet "Autoscaling rules for ${PROC_TYPE}" 18 | fn-plugin-property-list-get "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE" 19 | } 20 | 21 | cmd-scheduler-kubernetes-autoscale-rule-list "$@" 22 | -------------------------------------------------------------------------------- /subcommands/mount: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-mount() { 9 | declare desc="mount a volume" 10 | declare cmd="scheduler-kubernetes:mount" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare APP="$1" && CLAIM_NAME="$2" && MOUNT_PATH="$3" 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an App name" 14 | verify_app_name "$APP" 15 | [[ -z "$CLAIM_NAME" ]] && dokku_log_fail "Please specify a PVC claim name" 16 | [[ -z "$MOUNT_PATH" ]] && dokku_log_fail "Please specify a mount path" 17 | 18 | dokku_log_info2_quiet "Setting Claim name and Path to $CLAIM_NAME:$MOUNT_PATH (Re-deploy Required)" 19 | fn-plugin-property-list-add "scheduler-kubernetes" "$APP" "volumes" "$CLAIM_NAME:$MOUNT_PATH" 20 | } 21 | 22 | cmd-scheduler-kubernetes-mount "$@" 23 | -------------------------------------------------------------------------------- /subcommands/unmount: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-unmount() { 9 | declare desc="unmount a volume" 10 | declare cmd="scheduler-kubernetes:unmount" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare APP="$1" && CLAIM_NAME="$2" && MOUNT_PATH="$3" 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an App name" 14 | verify_app_name "$APP" 15 | [[ -z "$CLAIM_NAME" ]] && dokku_log_fail "Please specify a PVC claim name" 16 | [[ -z "$MOUNT_PATH" ]] && dokku_log_fail "Please specify a mount path" 17 | 18 | dokku_log_info2_quiet "Removing Claim Name and Path $CLAIM_NAME:$MOUNT_PATH (Re-deploy Required) " 19 | fn-plugin-property-list-remove "scheduler-kubernetes" "$APP" "volumes" "$CLAIM_NAME:$MOUNT_PATH" 20 | } 21 | 22 | cmd-scheduler-kubernetes-unmount "$@" 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2021 Jose Diaz-Gonzalez 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /pre-deploy-kubernetes-apply: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 4 | set -eo pipefail 5 | [[ $DOKKU_TRACE ]] && set -x 6 | 7 | scheduler-kubernetes-pre-deploy-kubernetes-apply() { 8 | declare desc="scheduler-kubernetes pre-deploy-kubernetes-apply plugin trigger" 9 | declare trigger="scheduler-kubernetes pre-deploy-kubernetes-apply" 10 | declare APP="$1" PROC_TYPE="$2" MANIFEST_FILE="$3" TYPE="$4" 11 | 12 | if [[ "$TYPE" != "deployment" ]]; then 13 | return 14 | fi 15 | 16 | if ! fn-scheduler-kubernetes-autoscale-in-use "$APP" "$PROC_TYPE"; then 17 | return 18 | fi 19 | 20 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 21 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 22 | 23 | jq 'del(.spec.replicas)' <"$MANIFEST_FILE" >"$TMP_FILE" 24 | mv "$TMP_FILE" "$MANIFEST_FILE" 25 | 26 | fn-scheduler-kubernetes-autoscale-apply "$APP" "$PROC_TYPE" 27 | } 28 | 29 | scheduler-kubernetes-pre-deploy-kubernetes-apply "$@" 30 | -------------------------------------------------------------------------------- /subcommands/remove-pvc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-remove-pvc() { 9 | declare desc="remove a persistent volume claim" 10 | declare cmd="scheduler-kubernetes:remove-pvc" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare NAME="$1" && shift 1 13 | [[ -z "$NAME" ]] && dokku_log_fail "Please specify a name for PVC" 14 | while getopts ":-:" opt "$@"; do 15 | case "$opt" in 16 | -) 17 | case "$OPTARG" in 18 | namespace) 19 | val="${!OPTIND}" 20 | OPTIND=$((OPTIND + 1)) 21 | local NAMESPACE="$val" 22 | ;; 23 | esac 24 | ;; 25 | esac 26 | done 27 | 28 | [[ -z "$NAMESPACE" ]] && NAMESPACE="default" 29 | 30 | fn-scheduler-kubernetes-remove-pvc "$NAME" "$NAMESPACE" 31 | } 32 | 33 | cmd-scheduler-kubernetes-remove-pvc "$@" 34 | -------------------------------------------------------------------------------- /subcommands/pod-annotations-set: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | set -eo pipefail 5 | [[ $DOKKU_TRACE ]] && set -x 6 | 7 | cmd-scheduler-kubernetes-pod-annotations-set() { 8 | declare desc="set or clear a scheduler-kubernetes pod annotation for an app" 9 | declare cmd="scheduler-kubernetes:pod-annotations-set" argv=("$@") 10 | [[ ${argv[0]} == "$cmd" ]] && shift 1 11 | declare APP="$1" KEY="$2" VALUE="$3" 12 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 13 | [[ -z "$KEY" ]] && dokku_log_fail "No key specified" 14 | 15 | verify_app_name "$APP" 16 | if [[ -n "$VALUE" ]]; then 17 | dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" 18 | fn-plugin-property-list-add "scheduler-kubernetes" "$APP" "pod-annotations" "$KEY $VALUE" 19 | else 20 | dokku_log_info2_quiet "Unsetting ${KEY}" 21 | fn-plugin-property-list-remove-by-prefix "scheduler-kubernetes" "$APP" "pod-annotations" "$KEY" 22 | fi 23 | } 24 | 25 | cmd-scheduler-kubernetes-pod-annotations-set "$@" 26 | -------------------------------------------------------------------------------- /subcommands/ingress-annotations-set: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | set -eo pipefail 5 | [[ $DOKKU_TRACE ]] && set -x 6 | 7 | cmd-scheduler-kubernetes-ingress-annotations-set() { 8 | declare desc="set or clear a scheduler-kubernetes ingress annotation for a namespace" 9 | declare cmd="scheduler-kubernetes:ingress-annotations-set" argv=("$@") 10 | [[ ${argv[0]} == "$cmd" ]] && shift 1 11 | declare APP_NAMESPACE="$1" KEY="$2" VALUE="$3" 12 | [[ -z "$APP_NAMESPACE" ]] && dokku_log_fail "Please specify a namespace" 13 | [[ -z "$KEY" ]] && dokku_log_fail "No key specified" 14 | 15 | if [[ -n "$VALUE" ]]; then 16 | dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" 17 | fn-plugin-property-list-add "scheduler-kubernetes" "$APP_NAMESPACE" "ingress-annotations" "$KEY $VALUE" 18 | else 19 | dokku_log_info2_quiet "Unsetting ${KEY}" 20 | fn-plugin-property-list-remove-by-prefix "scheduler-kubernetes" "$APP_NAMESPACE" "ingress-annotations" "$KEY" 21 | fi 22 | } 23 | 24 | cmd-scheduler-kubernetes-ingress-annotations-set "$@" 25 | -------------------------------------------------------------------------------- /subcommands/service-annotations-set: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | set -eo pipefail 5 | [[ $DOKKU_TRACE ]] && set -x 6 | 7 | cmd-scheduler-kubernetes-service-annotations-set() { 8 | declare desc="set or clear a scheduler-kubernetes service annotation for an app" 9 | declare cmd="scheduler-kubernetes:service-annotations-set" argv=("$@") 10 | [[ ${argv[0]} == "$cmd" ]] && shift 1 11 | declare APP="$1" KEY="$2" VALUE="$3" 12 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 13 | [[ -z "$KEY" ]] && dokku_log_fail "No key specified" 14 | 15 | verify_app_name "$APP" 16 | if [[ -n "$VALUE" ]]; then 17 | dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" 18 | fn-plugin-property-list-add "scheduler-kubernetes" "$APP" "service-annotations" "$KEY $VALUE" 19 | else 20 | dokku_log_info2_quiet "Unsetting ${KEY}" 21 | fn-plugin-property-list-remove-by-prefix "scheduler-kubernetes" "$APP" "service-annotations" "$KEY" 22 | fi 23 | } 24 | 25 | cmd-scheduler-kubernetes-service-annotations-set "$@" 26 | -------------------------------------------------------------------------------- /subcommands/deployment-annotations-set: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | set -eo pipefail 5 | [[ $DOKKU_TRACE ]] && set -x 6 | 7 | cmd-scheduler-kubernetes-deployment-annotations-set() { 8 | declare desc="set or clear a scheduler-kubernetes deployment annotation for an app" 9 | declare cmd="scheduler-kubernetes:deployment-annotations-set" argv=("$@") 10 | [[ ${argv[0]} == "$cmd" ]] && shift 1 11 | declare APP="$1" KEY="$2" VALUE="$3" 12 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 13 | [[ -z "$KEY" ]] && dokku_log_fail "No key specified" 14 | 15 | verify_app_name "$APP" 16 | if [[ -n "$VALUE" ]]; then 17 | dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" 18 | fn-plugin-property-list-add "scheduler-kubernetes" "$APP" "deployment-annotations" "$KEY $VALUE" 19 | else 20 | dokku_log_info2_quiet "Unsetting ${KEY}" 21 | fn-plugin-property-list-remove-by-prefix "scheduler-kubernetes" "$APP" "deployment-annotations" "$KEY" 22 | fi 23 | } 24 | 25 | cmd-scheduler-kubernetes-deployment-annotations-set "$@" 26 | -------------------------------------------------------------------------------- /scheduler-post-delete: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 5 | source "$PLUGIN_AVAILABLE_PATH/config/functions" 6 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 7 | 8 | scheduler-kubernetes-scheduler-post-delete() { 9 | declare desc="scheduler-kubernetes scheduler-post-delete plugin trigger" 10 | declare trigger="scheduler-kubernetes scheduler-post-delete" 11 | declare DOKKU_SCHEDULER="$1" APP="$2" 12 | local KUBE_ARGS NAMESPACE 13 | 14 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 15 | return 16 | fi 17 | 18 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 19 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 20 | KUBE_ARGS=() 21 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 22 | KUBE_ARGS+=("--namespace=$NAMESPACE") 23 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 24 | 25 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" delete deployments,hpa,pdb,pods,services -l app="$APP" 26 | } 27 | 28 | scheduler-kubernetes-scheduler-post-delete "$@" 29 | -------------------------------------------------------------------------------- /tests/shellcheck-exclude: -------------------------------------------------------------------------------- 1 | # SC1090 - Can't follow non-constant source. Use a directive to specify location - https://github.com/koalaman/shellcheck/wiki/SC1090 2 | # SC2034 - Variable appears unused. Verify it or export it - https://github.com/koalaman/shellcheck/wiki/SC2034 3 | # SC2124 - Assigning an array to a string! Assign as array, or use * instead of @ to concatenate - https://github.com/koalaman/shellcheck/wiki/SC2124 4 | # SC2128 - Expanding an array without an index only gives the first element - https://github.com/koalaman/shellcheck/wiki/SC2128 5 | # SC2155 - Declare and assign separately to avoid masking return values - https://github.com/koalaman/shellcheck/wiki/SC2155 6 | # SC2191 - The = here is literal. To assign by index, use ( [index]=value ) with no spaces. To keep as literal, quote it - https://github.com/koalaman/shellcheck/wiki/SC2191 7 | # SC2206 - Quote to prevent word splitting/globbing, or split robustly with mapfile or read -a - https://github.com/koalaman/shellcheck/wiki/SC2206 8 | # SC2207 - Prefer mapfile or read -a to split command output (or quote to avoid splitting) - https://github.com/koalaman/shellcheck/wiki/SC2207 9 | # SC2220 - Invalid flags are not handled. Add a *) case - https://github.com/koalaman/shellcheck/wiki/SC2220 10 | # SC2230 - which is non-standard. Use builtin 'command -v' instead - https://github.com/koalaman/shellcheck/wiki/SC2230 11 | -------------------------------------------------------------------------------- /scheduler-is-deployed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 6 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 7 | 8 | scheduler-kubernetes-scheduler-is-deployed() { 9 | declare desc="scheduler-kubernetes scheduler-is-deployed plugin trigger" 10 | declare trigger="scheduler-kubernetes scheduler-is-deployed" 11 | declare DOKKU_SCHEDULER="$1" APP="$2" 12 | local KUBE_OUTPUT 13 | 14 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 15 | return 16 | fi 17 | 18 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 19 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 20 | KUBE_ARGS=() 21 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "")" 22 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 23 | KUBE_ARGS+=("--namespace=$NAMESPACE") 24 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 25 | 26 | KUBE_OUTPUT="$("${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" get pods --selector app="$APP" -o name 2>/dev/null || true)" 27 | if [[ -n "$KUBE_OUTPUT" ]]; then 28 | return 0 29 | fi 30 | return 1 31 | } 32 | 33 | scheduler-kubernetes-scheduler-is-deployed "$@" 34 | -------------------------------------------------------------------------------- /subcommands/autoscale-rule-add: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-autoscale-rule-add() { 9 | declare desc="add an autoscale rule for an app/proc-type combination" 10 | declare cmd="scheduler-kubernetes:autoscale-rule-add" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare APP="$1" PROC_TYPE="$2" RULE="$3" 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 14 | [[ -z "$PROC_TYPE" ]] && dokku_log_fail "Please specify a process type to run the command on" 15 | [[ -z "$RULE" ]] && dokku_log_fail "No rule specified" 16 | 17 | verify_app_name "$APP" 18 | fn-scheduler-kubernetes-autoscale-rule-validate "$RULE" 19 | 20 | local METRIC_TYPE="$(echo "$RULE" | cut -d':' -f1)" 21 | local TARGET_NAME="$(echo "$RULE" | cut -d':' -f2)" 22 | 23 | dokku_log_info2_quiet "Setting rule for ${PROC_TYPE}: $RULE" 24 | fn-plugin-property-list-remove-by-prefix "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE" "$METRIC_TYPE:$TARGET_NAME" 2>/dev/null || true 25 | fn-plugin-property-list-add "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE" "$RULE" 26 | } 27 | 28 | cmd-scheduler-kubernetes-autoscale-rule-add "$@" 29 | -------------------------------------------------------------------------------- /subcommands/set: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | set -eo pipefail 5 | [[ $DOKKU_TRACE ]] && set -x 6 | 7 | cmd-scheduler-kubernetes-set() { 8 | declare desc="set or clear a scheduler-kubernetes property for an app" 9 | declare cmd="scheduler-kubernetes:set" argv=("$@") 10 | [[ ${argv[0]} == "$cmd" ]] && shift 1 11 | declare APP="$1" KEY="$2" VALUE="$3" 12 | local VALID_KEYS=("cert-manager-enabled" "imagePullSecrets" "ingress-enabled" "namespace" "pod-max-unavailable" "pod-min-available" "service-process-types") 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 14 | [[ -z "$KEY" ]] && dokku_log_fail "No key specified" 15 | 16 | verify_app_name "$APP" 17 | if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then 18 | dokku_log_fail "Invalid key specified, valid keys include: cert-manager-enabled, imagePullSecrets, ingress-enabled, namespace, pod-max-unavailable, pod-min-available, service-process-types" 19 | fi 20 | 21 | if [[ -n "$VALUE" ]]; then 22 | dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}" 23 | fn-plugin-property-write "scheduler-kubernetes" "$APP" "$KEY" "$VALUE" 24 | else 25 | dokku_log_info2_quiet "Unsetting ${KEY}" 26 | fn-plugin-property-delete "scheduler-kubernetes" "$APP" "$KEY" 27 | fi 28 | } 29 | 30 | cmd-scheduler-kubernetes-set "$@" 31 | -------------------------------------------------------------------------------- /subcommands/autoscale-set: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | set -eo pipefail 5 | [[ $DOKKU_TRACE ]] && set -x 6 | 7 | cmd-scheduler-kubernetes-autoscale-set() { 8 | declare desc="set or clear autoscale settings for an app/proc-type combination" 9 | declare cmd="scheduler-kubernetes:autoscale-set" argv=("$@") 10 | [[ ${argv[0]} == "$cmd" ]] && shift 1 11 | declare APP="$1" PROC_TYPE="$2" KEY="$3" VALUE="$4" 12 | local VALID_KEYS=("max-replicas" "min-replicas") 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 14 | [[ -z "$PROC_TYPE" ]] && dokku_log_fail "Please specify a process type to run the command on" 15 | [[ -z "$KEY" ]] && dokku_log_fail "No key specified" 16 | 17 | verify_app_name "$APP" 18 | if ! fn-in-array "$KEY" "${VALID_KEYS[@]}"; then 19 | dokku_log_fail "Invalid key specified, valid keys include: max-replicas, min-replicas" 20 | fi 21 | 22 | if [[ -n "$VALUE" ]]; then 23 | dokku_log_info2_quiet "Setting ${PROC_TYPE}.${KEY} to ${VALUE}" 24 | fn-plugin-property-write "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE.$KEY" "$VALUE" 25 | else 26 | dokku_log_info2_quiet "Unsetting ${PROC_TYPE}.${KEY}" 27 | fn-plugin-property-delete "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE.$KEY" 28 | fi 29 | } 30 | 31 | cmd-scheduler-kubernetes-autoscale-set "$@" 32 | -------------------------------------------------------------------------------- /scheduler-logs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 6 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 7 | 8 | scheduler-kubernetes-scheduler-logs() { 9 | declare desc="scheduler-kubernetes scheduler-logs plugin trigger" 10 | declare trigger="scheduler-kubernetes scheduler-logs" 11 | declare DOKKU_SCHEDULER="$1" APP="$2" PROCESS_TYPE="$3" TAIL="$4" PRETTY_PRINT="$5" NUM="$6" 12 | local KUBE_ARGS NAMESPACE 13 | 14 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 15 | return 16 | fi 17 | 18 | local LABEL="app=${APP}" 19 | if [[ -n "$PROCESS_TYPE" ]]; then 20 | LABEL="app-process-type=${APP}-${PROCESS_TYPE}" 21 | fi 22 | 23 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 24 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 25 | KUBE_ARGS=() 26 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 27 | KUBE_ARGS+=("--namespace=$NAMESPACE") 28 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 29 | 30 | local LOG_ARGS=() 31 | LOG_ARGS+=("--tail") 32 | LOG_ARGS+=("$NUM") 33 | [[ "$TAIL" == "true" ]] && LOG_ARGS+=("--follow") 34 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" logs -l "$LABEL" "${LOG_ARGS[@]}" 35 | } 36 | 37 | scheduler-kubernetes-scheduler-logs "$@" 38 | -------------------------------------------------------------------------------- /tests/deploy.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | load test_helper 3 | 4 | setup() { 5 | dokku apps:create $TEST_APP 6 | dokku config:set $TEST_APP DOKKU_SCHEDULER=kubernetes 7 | dokku registry:set $TEST_APP server docker.io 8 | dokku registry:set $TEST_APP image-repo dokku/$TEST_APP 9 | dokku scheduler-kubernetes:set $TEST_APP imagePullSecrets registry-credential 10 | } 11 | 12 | teardown() { 13 | dokku --force apps:destroy $TEST_APP 14 | } 15 | 16 | @test "(scheduler-kubernetes) help" { 17 | dokku scheduler-kubernetes:help 18 | } 19 | 20 | @test "(scheduler-kubernetes) deploy dockerfile" { 21 | if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_PASSWORD" ]]; then 22 | skip "Missing docker hub credentials" 23 | fi 24 | 25 | run deploy_app dockerfile-procfile 26 | echo "output: $output" 27 | echo "status: $status" 28 | assert_success 29 | 30 | run /bin/bash -c "dokku scheduler-kubernetes:show-manifest $TEST_APP" 31 | echo "output: $output" 32 | echo "status: $status" 33 | assert_failure 34 | } 35 | 36 | @test "(scheduler-kubernetes) deploy herokuish" { 37 | if [[ -z "$DOCKERHUB_USERNAME" ]] || [[ -z "$DOCKERHUB_PASSWORD" ]]; then 38 | skip "Missing docker hub credentials" 39 | fi 40 | 41 | run deploy_app python 42 | echo "output: $output" 43 | echo "status: $status" 44 | assert_success 45 | 46 | run /bin/bash -c "dokku scheduler-kubernetes:show-manifest $TEST_APP" 47 | echo "output: $output" 48 | echo "status: $status" 49 | assert_success 50 | } 51 | -------------------------------------------------------------------------------- /templates/deployment.json.sigil: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "apps/v1", 3 | "kind": "Deployment", 4 | "metadata": { 5 | "name": "{{ $.APP }}-{{ $.PROCESS_TYPE }}", 6 | "labels": { 7 | "app": "{{ $.APP }}", 8 | "process-type": "{{ $.PROCESS_TYPE }}", 9 | "app-process-type": "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 10 | } 11 | }, 12 | "spec": { 13 | "replicas": {{ $.PROCESS_COUNT }}, 14 | "revisionHistoryLimit": 5, 15 | "selector": { 16 | "matchLabels": { 17 | "app": "{{ $.APP }}", 18 | "process-type": "{{ $.PROCESS_TYPE }}", 19 | "app-process-type": "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 20 | } 21 | }, 22 | "template": { 23 | "metadata": { 24 | "labels": { 25 | "app": "{{ $.APP }}", 26 | "process-type": "{{ $.PROCESS_TYPE }}", 27 | "app-process-type": "{{ $.APP }}-{{ $.PROCESS_TYPE }}" 28 | } 29 | }, 30 | "spec": { 31 | "containers": [ 32 | { 33 | "env": [], 34 | "image": "{{ $.IMAGE }}", 35 | "imagePullPolicy": "Always", 36 | "name": "{{ $.APP }}-{{ $.PROCESS_TYPE }}", 37 | "ports": [ 38 | { 39 | "containerPort": {{ $.PORT }} 40 | } 41 | ], 42 | "resources": { 43 | "limits": { 44 | }, 45 | "requests": { 46 | } 47 | } 48 | } 49 | ] 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /scheduler-stop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 6 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 7 | 8 | scheduler-kubernetes-scheduler-stop() { 9 | declare desc="scheduler-kubernetes scheduler-stop plugin trigger" 10 | declare trigger="scheduler-kubernetes scheduler-stop" 11 | declare DOKKU_SCHEDULER="$1" APP="$2" REMOVE_CONTAINERS="$3" 12 | local KUBE_ARGS NAMESPACE 13 | 14 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 15 | return 16 | fi 17 | 18 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 19 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 20 | KUBE_ARGS=() 21 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 22 | KUBE_ARGS+=("--namespace=$NAMESPACE") 23 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 24 | 25 | while read -r line || [[ -n "$line" ]]; do 26 | [[ "$line" =~ ^#.* ]] && continue 27 | line="$(strip_inline_comments "$line")" 28 | PROC_TYPE=${line%%=*} 29 | PROC_COUNT=${line#*=} 30 | 31 | if "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" get deployment "${APP}-${PROC_TYPE}" 2>/dev/null; then 32 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" scale --replicas=0 "deployment/${APP}-${PROC_TYPE}" 33 | fi 34 | done < <(plugn trigger ps-current-scale "$APP") 35 | } 36 | 37 | scheduler-kubernetes-scheduler-stop "$@" 38 | -------------------------------------------------------------------------------- /subcommands/autoscale-rule-remove: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-autoscale-rule-remove() { 9 | declare desc="remove an autoscale rule for an app/proc-type combination" 10 | declare cmd="scheduler-kubernetes:autoscale-rule-remove" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare APP="$1" PROC_TYPE="$2" RULE="$3" 13 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 14 | [[ -z "$PROC_TYPE" ]] && dokku_log_fail "Please specify a process type to run the command on" 15 | [[ -z "$RULE" ]] && dokku_log_fail "No rule specified" 16 | 17 | verify_app_name "$APP" 18 | 19 | local METRIC_TYPE="$(echo "$RULE" | cut -d':' -f1)" 20 | local TARGET_NAME="$(echo "$RULE" | cut -d':' -f2)" 21 | 22 | local VALID_METRIC_TYPES=("external" "ingress" "pods" "resource") 23 | if ! fn-in-array "$METRIC_TYPE" "${VALID_METRIC_TYPES[@]}"; then 24 | dokku_log_fail "Invalid metric type '${METRIC_TYPE}'" 25 | fi 26 | 27 | if [[ -z "$TARGET_NAME" ]]; then 28 | dokku_log_fail "Missing target name in rule" 29 | fi 30 | 31 | dokku_log_info2_quiet "Removing rule for ${PROC_TYPE}: $METRIC_TYPE:$TARGET_NAME" 32 | fn-plugin-property-list-remove-by-prefix "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE" "$METRIC_TYPE:$TARGET_NAME" 33 | } 34 | 35 | cmd-scheduler-kubernetes-autoscale-rule-remove "$@" 36 | -------------------------------------------------------------------------------- /scheduler-app-status: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 6 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 7 | 8 | scheduler-app-status() { 9 | declare desc="fetches the status for a given app" 10 | declare trigger="scheduler-kubernetes scheduler-app-status" 11 | declare DOKKU_SCHEDULER="$1" APP="$2" 12 | local KUBE_OUTPUT 13 | 14 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 15 | return 16 | fi 17 | 18 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 19 | KUBE_ARGS=() 20 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 21 | KUBE_ARGS+=("--namespace=$NAMESPACE") 22 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 23 | 24 | local PROCS=0 RUNNING="" 25 | KUBE_OUTPUT="$("${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" get pods --selector app="$APP" -o custom-columns="STATUS:.status.conditions[?(@.type=='Ready')].status" --no-headers 2>/dev/null || true)" 26 | 27 | while IFS= read -r line; do 28 | if [[ "$line" == "True" ]]; then 29 | RUNNING+="0" 30 | else 31 | RUNNING+="1" 32 | fi 33 | PROCS=$((PROCS + 1)) 34 | done < <(printf '%s\n' "$KUBE_OUTPUT") 35 | 36 | if [[ "${#RUNNING}" -eq 0 ]] || [[ "${#RUNNING}" -ne 0 ]] && [[ "$RUNNING" != *"0"* ]]; then 37 | RUNNING="false" 38 | elif [[ "$RUNNING" != *"1"* ]] && [[ "${#RUNNING}" -ne 0 ]]; then 39 | RUNNING="true" 40 | else 41 | RUNNING="mixed" 42 | fi 43 | 44 | echo "$PROCS $RUNNING" 45 | } 46 | 47 | scheduler-app-status "$@" 48 | -------------------------------------------------------------------------------- /subcommands/add-pvc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | cmd-scheduler-kubernetes-add-pvc() { 9 | declare desc="add a persistent volume claim" 10 | declare cmd="scheduler-kubernetes:add-pvc" argv=("$@") 11 | [[ ${argv[0]} == "$cmd" ]] && shift 1 12 | declare NAME="$1" && STORAGE="$2" && shift 2 13 | [[ -z "$NAME" ]] && dokku_log_fail "Please specify a name for PVC" 14 | [[ -z "${STORAGE##*[!0-9]*}" ]] && dokku_log_fail "Please specify a numeric storage size for PVC" 15 | while getopts ":-:" opt "$@"; do 16 | case "$opt" in 17 | -) 18 | case "$OPTARG" in 19 | access-mode) 20 | val="${!OPTIND}" 21 | OPTIND=$((OPTIND + 1)) 22 | local ACCESS_MODE="$val" 23 | ;; 24 | storage-class-name) 25 | val="${!OPTIND}" 26 | OPTIND=$((OPTIND + 1)) 27 | local STORAGE_CLASS_NAME="$val" 28 | ;; 29 | namespace) 30 | val="${!OPTIND}" 31 | OPTIND=$((OPTIND + 1)) 32 | local NAMESPACE="$val" 33 | ;; 34 | esac 35 | ;; 36 | esac 37 | done 38 | 39 | [[ -z "$ACCESS_MODE" ]] && ACCESS_MODE="ReadWriteOnce" 40 | [[ "$ACCESS_MODE" == ReadWriteOnce ]] || [[ "$ACCESS_MODE" == ReadWriteMany ]] || [[ "$ACCESS_MODE" == ReadOnlyMany ]] || dokku_log_fail "Please specify PVC access mode as either ReadWriteOnce, ReadOnlyMany, ReadWriteMany" 41 | [[ -z "$STORAGE_CLASS_NAME" ]] && STORAGE_CLASS_NAME="" 42 | [[ -z "$NAMESPACE" ]] && NAMESPACE="default" 43 | 44 | fn-scheduler-kubernetes-add-pvc "$NAME" "$ACCESS_MODE" "$STORAGE" "$STORAGE_CLASS_NAME" "$NAMESPACE" 45 | } 46 | 47 | cmd-scheduler-kubernetes-add-pvc "$@" 48 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 6 | 7 | scheduler-kubernetes-install() { 8 | declare desc="scheduler-kubernetes install plugin trigger" 9 | declare trigger="scheduler-kubernetes-install" 10 | 11 | mkdir -p "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes" 12 | chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes" 13 | 14 | fn-plugin-property-setup "scheduler-kubernetes" 15 | 16 | local KUBECTL_VENDOR_URL="${KUBECTL_VENDOR_URL:-"https://dl.k8s.io"}" 17 | local KUBECTL_VERSION="${KUBECTL_VERSION:-v1.22.13}" 18 | if [[ ! -f "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl-${KUBECTL_VERSION}" ]]; then 19 | dokku_log_info1_quiet "Installing kubectl@${KUBECTL_VERSION}" 20 | curl -sL "${KUBECTL_VENDOR_URL}/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" -o "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl-${KUBECTL_VERSION}" 21 | cp "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl-${KUBECTL_VERSION}" "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" 22 | chmod +x "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" 23 | fi 24 | 25 | local KUBEDOG_VENDOR_URL="${KUBEDOG_VENDOR_URL:-"https://dl.bintray.com"}" 26 | local KUBEDOG_VERSION="${KUBEDOG_VERSION:-v0.3.4}" 27 | local KUBEDOG_URL="${KUBEDOG_URL:-"${KUBEDOG_VENDOR_URL}/flant/kubedog/${KUBEDOG_VERSION}/kubedog-linux-amd64-${KUBEDOG_VERSION}"}" 28 | 29 | # hardcode kubedog vendor for now 30 | KUBEDOG_URL=https://github.com/dokku/dokku-scheduler-kubernetes/releases/download/0.17.6/kubedog-linux-amd64-v0.3.4 31 | if [[ ! -f "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubedog-${KUBEDOG_VERSION}" ]]; then 32 | dokku_log_info1_quiet "Installing kubedog@${KUBEDOG_VERSION}" 33 | curl -sL "$KUBEDOG_URL" -o "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubedog-${KUBEDOG_VERSION}" 34 | cp "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubedog-${KUBEDOG_VERSION}" "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubedog" 35 | chmod +x "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubedog" 36 | fi 37 | 38 | if ! command -v jq &>/dev/null; then 39 | dokku_log_warn "This plugin script requires jq. Please call 'sudo dokku plugin:install-dependencies' to install this package." 40 | fi 41 | } 42 | 43 | scheduler-kubernetes-install "$@" 44 | -------------------------------------------------------------------------------- /tests/apps/python/Procfile: -------------------------------------------------------------------------------- 1 | ############################### 2 | # DEVELOPMENT # 3 | ############################### 4 | 5 | # Procfile for development using the new threaded worker (scheduler, twitter stream and delayed job) 6 | cron: python3 worker.py 7 | web: python3 web.py # testing inline comment 8 | worker: python3 worker.py 9 | custom: echo -n 10 | release: python3 release.py 11 | 12 | 13 | # Old version with separate processes (use this if you have issues with the threaded version) 14 | # web: bundle exec rails server 15 | # schedule: bundle exec rails runner bin/schedule.rb 16 | # twitter: bundle exec rails runner bin/twitter_stream.rb 17 | # dj: bundle exec script/delayed_job run 18 | 19 | ############################### 20 | # PRODUCTION # 21 | ############################### 22 | 23 | # You need to copy or link config/unicorn.rb.example to config/unicorn.rb for both production versions. 24 | # Have a look at the deployment guides, if you want to set up huginn on your server: 25 | # https://github.com/cantino/huginn/doc 26 | 27 | # Using the threaded worker (consumes less RAM but can run slower) 28 | # web: bundle exec unicorn -c config/unicorn.rb 29 | # jobs: bundle exec rails runner bin/threaded.rb 30 | 31 | # Old version with separate processes (use this if you have issues with the threaded version) 32 | # web: bundle exec unicorn -c config/unicorn.rb 33 | # schedule: bundle exec rails runner bin/schedule.rb 34 | # twitter: bundle exec rails runner bin/twitter_stream.rb 35 | # dj: bundle exec script/delayed_job run 36 | 37 | ############################### 38 | # Multiple DelayedJob workers # 39 | ############################### 40 | # Per default Huginn can just run one agent at a time. Using a lot of agents or calling slow 41 | # external services frequently might require more DelayedJob workers (an indicator for this is 42 | # a backlog in your 'Job Management' page). 43 | # Every uncommented line starts an additional DelayedJob worker. This works for development, production 44 | # and for the threaded and separate worker processes. Keep in mind one worker needs about 300MB of RAM. 45 | # 46 | #dj2: bundle exec script/delayed_job -i 2 run 47 | #dj3: bundle exec script/delayed_job -i 3 run 48 | #dj4: bundle exec script/delayed_job -i 4 run 49 | #dj5: bundle exec script/delayed_job -i 5 run 50 | #dj6: bundle exec script/delayed_job -i 6 run 51 | #dj7: bundle exec script/delayed_job -i 7 run 52 | #dj8: bundle exec script/delayed_job -i 8 run 53 | #dj9: bundle exec script/delayed_job -i 9 run 54 | #dj10: bundle exec script/delayed_job -i 10 run 55 | -------------------------------------------------------------------------------- /tests/test_helper.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export DOKKU_LIB_ROOT="/var/lib/dokku" 3 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 4 | 5 | UUID=$(uuidgen) 6 | TEST_APP="rdmtestapp-${UUID}" 7 | 8 | flunk() { 9 | { 10 | if [ "$#" -eq 0 ]; then 11 | cat - 12 | else 13 | echo "$*" 14 | fi 15 | } 16 | return 1 17 | } 18 | 19 | assert_equal() { 20 | if [ "$1" != "$2" ]; then 21 | { 22 | echo "expected: $1" 23 | echo "actual: $2" 24 | } | flunk 25 | fi 26 | } 27 | 28 | # ShellCheck doesn't know about $status from Bats 29 | # shellcheck disable=SC2154 30 | assert_exit_status() { 31 | assert_equal "$1" "$status" 32 | } 33 | 34 | # ShellCheck doesn't know about $status from Bats 35 | # shellcheck disable=SC2154 36 | # shellcheck disable=SC2120 37 | assert_success() { 38 | if [ "$status" -ne 0 ]; then 39 | flunk "command failed with exit status $status" 40 | elif [ "$#" -gt 0 ]; then 41 | assert_output "$1" 42 | fi 43 | } 44 | 45 | assert_failure() { 46 | if [[ "$status" -eq 0 ]]; then 47 | flunk "expected failed exit status" 48 | elif [[ "$#" -gt 0 ]]; then 49 | assert_output "$1" 50 | fi 51 | } 52 | 53 | assert_exists() { 54 | if [ ! -f "$1" ]; then 55 | flunk "expected file to exist: $1" 56 | fi 57 | } 58 | 59 | assert_contains() { 60 | if [[ "$1" != *"$2"* ]]; then 61 | flunk "expected $2 to be in: $1" 62 | fi 63 | } 64 | 65 | # ShellCheck doesn't know about $output from Bats 66 | # shellcheck disable=SC2154 67 | assert_output() { 68 | local expected 69 | if [ $# -eq 0 ]; then 70 | expected="$(cat -)" 71 | else 72 | expected="$1" 73 | fi 74 | assert_equal "$expected" "$output" 75 | } 76 | 77 | deploy_app() { 78 | declare APP_TYPE="$1" GIT_REMOTE="$2" CUSTOM_TEMPLATE="$3" CUSTOM_PATH="$4" 79 | local APP_TYPE=${APP_TYPE:="python"} 80 | local GIT_REMOTE=${GIT_REMOTE:="dokku@dokku.me:$TEST_APP"} 81 | local GIT_REMOTE_BRANCH=${GIT_REMOTE_BRANCH:="master"} 82 | local TMP=${CUSTOM_TMP:=$(mktemp -d "/tmp/dokku.me.XXXXX")} 83 | 84 | rmdir "$TMP" && cp -r "${BATS_TEST_DIRNAME}/../tests/apps/$APP_TYPE" "$TMP" 85 | 86 | # shellcheck disable=SC2086 87 | [[ -n "$CUSTOM_TEMPLATE" ]] && $CUSTOM_TEMPLATE $TEST_APP $TMP/$CUSTOM_PATH 88 | 89 | pushd "$TMP" &>/dev/null || exit 1 90 | [[ -z "$CUSTOM_TMP" ]] && trap 'popd &>/dev/null || true; rm -rf "$TMP"' RETURN INT TERM 91 | 92 | git init 93 | git config user.email "robot@example.com" 94 | git config user.name "Test Robot" 95 | echo "setting up remote: $GIT_REMOTE" 96 | git remote add target "$GIT_REMOTE" 97 | 98 | [[ -f gitignore ]] && mv gitignore .gitignore 99 | git add . 100 | git commit -m 'initial commit' 101 | git push target "master:${GIT_REMOTE_BRANCH}" || destroy_app $? 102 | } 103 | 104 | destroy_app() { 105 | local RC="$1" 106 | local RC=${RC:=0} 107 | local APP="$2" 108 | local TEST_APP=${APP:=$TEST_APP} 109 | dokku --force apps:destroy "$TEST_APP" 110 | return "$RC" 111 | } 112 | -------------------------------------------------------------------------------- /tests/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $TRACE ]] && set -x 4 | sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 762E3157 5 | echo "deb http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" | sudo tee /etc/apt/sources.list.d/nginx.list 6 | curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo apt-key add - 7 | 8 | sudo mkdir -p /etc/nginx 9 | sudo curl https://raw.githubusercontent.com/dokku/dokku/master/tests/dhparam.pem -o /etc/nginx/dhparam.pem 10 | 11 | echo "dokku dokku/skip_key_file boolean true" | sudo debconf-set-selections 12 | curl -sfL -o /tmp/bootstrap.sh https://raw.githubusercontent.com/dokku/dokku/master/bootstrap.sh 13 | if [[ "$DOKKU_VERSION" == "master" ]]; then 14 | sudo bash /tmp/bootstrap.sh 15 | else 16 | sudo DOKKU_TAG="$DOKKU_VERSION" bash /tmp/bootstrap.sh 17 | fi 18 | echo "Dokku version $DOKKU_VERSION" 19 | 20 | export DOKKU_LIB_ROOT="/var/lib/dokku" 21 | export DOKKU_PLUGINS_ROOT="$DOKKU_LIB_ROOT/plugins/available" 22 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 23 | sudo rm -rf "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" 24 | sudo mkdir -p "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/subcommands" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/scripts" "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/templates" 25 | sudo find ./ -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX" \; 26 | [[ -d "./scripts" ]] && sudo find ./scripts -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/scripts" \; 27 | [[ -d "./subcommands" ]] && sudo find ./subcommands -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/subcommands" \; 28 | [[ -d "./templates" ]] && sudo find ./templates -maxdepth 1 -type f -exec cp '{}' "$DOKKU_PLUGINS_ROOT/$PLUGIN_COMMAND_PREFIX/templates" \; 29 | sudo mkdir -p "$PLUGIN_CONFIG_ROOT" "$PLUGIN_DATA_ROOT" 30 | sudo dokku plugin:enable "$PLUGIN_COMMAND_PREFIX" 31 | sudo dokku plugin:install 32 | 33 | ## k3s testing setup 34 | 35 | # install the registry dependency 36 | # TODO: use a `plugn` command 37 | sudo dokku plugin:install git://github.com/dokku/dokku-registry.git registry 38 | 39 | # install k3s 40 | curl -sfL -o /tmp/k3s.sh https://get.k3s.io 41 | sudo sh /tmp/k3s.sh 42 | 43 | # setup kube config for dokku user 44 | sudo mkdir -p /home/dokku/.kube 45 | sudo cp -f /etc/rancher/k3s/k3s.yaml /home/dokku/.kube/config 46 | sudo chown -R dokku:dokku /home/dokku/.kube 47 | 48 | # ensure we can access the registry locally 49 | # TODO: run the registry locally somehow 50 | if [[ -n "$DOCKERHUB_USERNAME" ]] && [[ -n "$DOCKERHUB_PASSWORD" ]]; then 51 | export KUBECONFIG=/home/dokku/.kube/config 52 | sudo kubectl delete secret registry-credential || true 53 | sudo dokku registry:login docker.io "$DOCKERHUB_USERNAME" "$DOCKERHUB_PASSWORD" 54 | sudo kubectl create secret generic registry-credential \ 55 | --from-file=.dockerconfigjson=/home/dokku/.docker/config.json \ 56 | --type=kubernetes.io/dockerconfigjson 57 | else 58 | echo "Dockerhub username or password missing, skipping login to registry" 59 | fi 60 | -------------------------------------------------------------------------------- /subcommands/show-manifest: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 5 | 6 | cmd-scheduler-kubernetes-show-manifest() { 7 | declare desc="display the deployment or service manifest for a given app/process type combination" 8 | declare cmd="scheduler-kubernetes:show-manifest" argv=("$@") 9 | [[ ${argv[0]} == "$cmd" ]] && shift 1 10 | declare APP="$1" PROC_TYPE="${2:-web}" MANIFEST_TYPE="${3:-deployment}" 11 | 12 | [[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on" 13 | [[ -z "$PROC_TYPE" ]] && dokku_log_fail "No process type specified" 14 | [[ -z "$MANIFEST_TYPE" ]] && dokku_log_fail "No manifest type specified" 15 | 16 | verify_app_name "$APP" 17 | local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") 18 | [[ "$DOKKU_SCHEDULER" != "kubernetes" ]] && dokku_log_fail "Scheduler for $APP is set to $DOKKU_SCHEDULER" 19 | 20 | local DEPLOYMENT_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/deployment.json.sigil" 21 | local SERVICE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/service.json.sigil" 22 | local FOUND=false 23 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 24 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 25 | 26 | local IMAGE_TAG="$(get_running_image_tag "$APP")" 27 | local IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") 28 | 29 | local IMAGE_PULL_SECRETS="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "imagePullSecrets" "")" 30 | local SERVICE_PROCESS_TYPES=$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "service-process-types" "") 31 | local VALID_SERVICE_TYPES 32 | if [[ -n "$SERVICE_PROCESS_TYPES" ]]; then 33 | IFS=',' read -ra VALID_SERVICE_TYPES <<<"$SERVICE_PROCESS_TYPES" 34 | fi 35 | VALID_SERVICE_TYPES+=("web") 36 | 37 | local IMAGE_SOURCE_TYPE="dockerfile" 38 | is_image_herokuish_based "$IMAGE" "$APP" && IMAGE_SOURCE_TYPE="herokuish" 39 | 40 | while read -r line || [[ -n "$line" ]]; do 41 | [[ "$line" =~ ^#.* ]] && continue 42 | line="$(strip_inline_comments "$line")" 43 | CURRENT_PROC_TYPE=${line%%=*} 44 | PROC_COUNT=${line#*=} 45 | 46 | if [[ "$CURRENT_PROC_TYPE" != "$PROC_TYPE" ]]; then 47 | continue 48 | fi 49 | 50 | FOUND=true 51 | local SIGIL_PARAMS=(APP="$APP" IMAGE="$IMAGE" PROCESS_COUNT="$PROC_COUNT" PROCESS_TYPE="$PROC_TYPE" PORT="5000") 52 | 53 | sigil -f "$DEPLOYMENT_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$TMP_FILE" 54 | [[ "$PROC_TYPE" != "web" ]] && fn-strip-ports "$TMP_FILE" 55 | fn-set-env-vars "$APP" "$TMP_FILE" 56 | fn-set-ports "$APP" "$TMP_FILE" 57 | fn-set-resource-constraints "$APP" "$PROC_TYPE" "$TMP_FILE" 58 | fn-set-image-pull-secrets "$IMAGE_PULL_SECRETS" "$TMP_FILE" 59 | fn-set-healthchecks "$APP" "$PROC_TYPE" "$TMP_FILE" 60 | fn-set-deployment-annotations "$APP" "$TMP_FILE" 61 | fn-set-pod-annotations "$APP" "$TMP_FILE" 62 | fn-set-mount "$APP" "$TMP_FILE" 63 | if [[ "$IMAGE_SOURCE_TYPE" != "pack" ]]; then 64 | fn-set-command-and-args "$APP" "$PROC_TYPE" "$IMAGE_SOURCE_TYPE" "$TMP_FILE" 65 | fi 66 | 67 | plugn trigger pre-deploy-kubernetes-apply "$APP" "$PROC_TYPE" "$TMP_FILE" deployment 68 | if [[ "$MANIFEST_TYPE" == "deployment" ]]; then 69 | cat "$TMP_FILE" 70 | return 71 | fi 72 | 73 | if fn-in-array "$PROC_TYPE" "${VALID_SERVICE_TYPES[@]}"; then 74 | sigil -f "$SERVICE_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$TMP_FILE" 75 | fn-set-service-annotations "$APP" "$TMP_FILE" 76 | plugn trigger pre-deploy-kubernetes-apply "$APP" "$PROC_TYPE" "$TMP_FILE" service 77 | 78 | cat "$TMP_FILE" 79 | return 80 | fi 81 | done < <(plugn trigger ps-current-scale "$APP") 82 | 83 | if [[ "$FOUND" != "true" ]]; then 84 | dokku_log_fail "Unable to find $MANIFEST_TYPE for process type $PROC_TYPE" 85 | fi 86 | } 87 | 88 | cmd-scheduler-kubernetes-show-manifest "$@" 89 | -------------------------------------------------------------------------------- /scheduler-deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 6 | source "$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/internal-functions" 7 | 8 | scheduler-kubernetes-scheduler-deploy() { 9 | declare desc="deploys an image tag for a given application" 10 | declare trigger="scheduler-kubernetes scheduler-deploy" 11 | declare DOKKU_SCHEDULER="$1" APP="$2" IMAGE_TAG="$3" 12 | local DEPLOYMENT_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/deployment.json.sigil" 13 | local SERVICE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/service.json.sigil" 14 | local KUBE_ARGS NAMESPACE 15 | 16 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 17 | return 18 | fi 19 | 20 | local line PROC_TYPE PROC_COUNT CONTAINER_INDEX 21 | local DEPLOYMENT_ID="$(date +%s)" 22 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 23 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 24 | 25 | dokku_log_info2 "Deploying via kubernetes" 26 | IMAGE=$(get_deploying_app_image_name "$APP" "$IMAGE_TAG") 27 | 28 | fn-set-pod-disruption-constraints "$APP" 29 | 30 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 31 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 32 | KUBE_ARGS=() 33 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 34 | KUBE_ARGS+=("--namespace=$NAMESPACE") 35 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 36 | 37 | local IMAGE_PULL_SECRETS="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "imagePullSecrets" "")" 38 | 39 | local SERVICE_PROCESS_TYPES=$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "service-process-types" "") 40 | local VALID_SERVICE_TYPES 41 | if [[ -n "$SERVICE_PROCESS_TYPES" ]]; then 42 | IFS=',' read -ra VALID_SERVICE_TYPES <<<"$SERVICE_PROCESS_TYPES" 43 | fi 44 | VALID_SERVICE_TYPES+=("web") 45 | 46 | local IMAGE_SOURCE_TYPE="dockerfile" 47 | is_image_cnb_based "$IMAGE" && DOKKU_CNB=true 48 | is_image_herokuish_based "$IMAGE" "$APP" && DOKKU_HEROKUISH=true 49 | local IMAGE_SOURCE_TYPE="dockerfile" 50 | [[ "$DOKKU_HEROKUISH" == "true" ]] && IMAGE_SOURCE_TYPE="herokuish" 51 | [[ "$DOKKU_CNB" == "true" ]] && IMAGE_SOURCE_TYPE="pack" 52 | 53 | while read -r line || [[ -n "$line" ]]; do 54 | [[ "$line" =~ ^#.* ]] && continue 55 | line="$(strip_inline_comments "$line")" 56 | PROC_TYPE=${line%%=*} 57 | PROC_COUNT=${line#*=} 58 | 59 | dokku_log_info1 "Deploying ${PROC_TYPE} to ${PROC_COUNT}" 60 | 61 | local SIGIL_PARAMS=(APP="$APP" IMAGE="$IMAGE" PROCESS_COUNT="$PROC_COUNT" PROCESS_TYPE="$PROC_TYPE" PORT="5000") 62 | 63 | sigil -f "$DEPLOYMENT_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$TMP_FILE" 64 | [[ "$PROC_TYPE" != "web" ]] && fn-strip-ports "$TMP_FILE" 65 | fn-set-env-vars "$APP" "$TMP_FILE" 66 | fn-set-ports "$APP" "$TMP_FILE" 67 | fn-set-resource-constraints "$APP" "$PROC_TYPE" "$TMP_FILE" 68 | fn-set-image-pull-secrets "$IMAGE_PULL_SECRETS" "$TMP_FILE" 69 | fn-set-healthchecks "$APP" "$PROC_TYPE" "$TMP_FILE" 70 | fn-set-deployment-annotations "$APP" "$TMP_FILE" 71 | fn-set-pod-annotations "$APP" "$TMP_FILE" 72 | fn-set-mount "$APP" "$TMP_FILE" 73 | if [[ "$IMAGE_SOURCE_TYPE" != "pack" ]]; then 74 | fn-set-command-and-args "$APP" "$PROC_TYPE" "$IMAGE_SOURCE_TYPE" "$TMP_FILE" 75 | fi 76 | plugn trigger pre-deploy-kubernetes-apply "$APP" "$PROC_TYPE" "$TMP_FILE" deployment 77 | 78 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" apply -f "$TMP_FILE" | sed "s/^/ /" 79 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubedog" "${KUBE_ARGS[@]}" --timeout=600 rollout track deployment "${APP}-${PROC_TYPE}" 80 | 81 | if fn-in-array "$PROC_TYPE" "${VALID_SERVICE_TYPES[@]}"; then 82 | sigil -f "$SERVICE_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$TMP_FILE" 83 | fn-set-service-annotations "$APP" "$TMP_FILE" 84 | plugn trigger pre-deploy-kubernetes-apply "$APP" "$PROC_TYPE" "$TMP_FILE" service 85 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" apply -f "$TMP_FILE" | sed "s/^/ /" 86 | fi 87 | 88 | plugn trigger post-deploy-kubernetes-apply "$APP" "$PROC_TYPE" "$TMP_FILE" deployment 89 | done < <(plugn trigger ps-current-scale "$APP") 90 | 91 | dokku_log_info2 "Deploy complete" 92 | 93 | dokku_log_info1 "Running post-deploy" 94 | plugn trigger core-post-deploy "$APP" "" "" "$IMAGE_TAG" 95 | plugn trigger post-deploy "$APP" "" "" "$IMAGE_TAG" 96 | } 97 | 98 | scheduler-kubernetes-scheduler-deploy "$@" 99 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | HARDWARE = $(shell uname -m) 2 | SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]') 3 | SHFMT_VERSION = 3.0.2 4 | XUNIT_TO_GITHUB_VERSION = 0.3.0 5 | XUNIT_READER_VERSION = 0.1.0 6 | DOKKU_SSH_PORT ?= 22 7 | 8 | bats: 9 | ifeq ($(SYSTEM_NAME),darwin) 10 | ifneq ($(shell bats --version >/dev/null 2>&1 ; echo $$?),0) 11 | brew install bats-core 12 | endif 13 | else 14 | git clone https://github.com/bats-core/bats-core.git /tmp/bats 15 | cd /tmp/bats && sudo ./install.sh /usr/local 16 | rm -rf /tmp/bats 17 | endif 18 | 19 | shellcheck: 20 | ifneq ($(shell shellcheck --version >/dev/null 2>&1 ; echo $$?),0) 21 | ifeq ($(SYSTEM_NAME),darwin) 22 | brew install shellcheck 23 | else 24 | sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse' 25 | sudo rm -rf /var/lib/apt/lists/* && sudo apt-get clean 26 | sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck 27 | endif 28 | endif 29 | 30 | shfmt: 31 | ifneq ($(shell shfmt --version >/dev/null 2>&1 ; echo $$?),0) 32 | ifeq ($(shfmt),Darwin) 33 | brew install shfmt 34 | else 35 | wget -qO /tmp/shfmt https://github.com/mvdan/sh/releases/download/v$(SHFMT_VERSION)/shfmt_v$(SHFMT_VERSION)_linux_amd64 36 | chmod +x /tmp/shfmt 37 | sudo mv /tmp/shfmt /usr/local/bin/shfmt 38 | endif 39 | endif 40 | 41 | readlink: 42 | ifeq ($(shell uname),Darwin) 43 | ifeq ($(shell greadlink > /dev/null 2>&1 ; echo $$?),127) 44 | brew install coreutils 45 | endif 46 | ln -nfs `which greadlink` tests/bin/readlink 47 | endif 48 | 49 | ci-dependencies: shellcheck bats readlink 50 | 51 | lint-setup: 52 | @mkdir -p tmp/test-results/shellcheck tmp/shellcheck 53 | @find . -not -path '*/\.*' -type f | xargs file | grep text | awk -F ':' '{ print $$1 }' | xargs head -n1 | egrep -B1 "bash" | grep "==>" | awk '{ print $$2 }' > tmp/shellcheck/test-files 54 | @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' | cut -d' ' -f2 | paste -d, -s - > tmp/shellcheck/exclude 55 | 56 | lint: lint-setup 57 | # these are disabled due to their expansive existence in the codebase. we should clean it up though 58 | @cat tests/shellcheck-exclude | sed -n -e '/^# SC/p' 59 | @echo linting... 60 | @cat tmp/shellcheck/test-files | xargs shellcheck -e $(shell cat tmp/shellcheck/exclude) | tests/shellcheck-to-junit --output tmp/test-results/shellcheck/results.xml --files tmp/shellcheck/test-files --exclude $(shell cat tmp/shellcheck/exclude) 61 | 62 | unit-tests: 63 | @echo running unit tests... 64 | @mkdir -p tmp/test-results/bats 65 | @cd tests && echo "executing tests: $(shell cd tests ; ls *.bats | xargs)" 66 | cd tests && bats --formatter bats-format-junit -e -T -o ../tmp/test-results/bats *.bats 67 | 68 | tmp/xunit-reader: 69 | mkdir -p tmp 70 | curl -o tmp/xunit-reader.tgz -sL https://github.com/josegonzalez/go-xunit-reader/releases/download/v$(XUNIT_READER_VERSION)/xunit-reader_$(XUNIT_READER_VERSION)_$(SYSTEM_NAME)_$(HARDWARE).tgz 71 | tar xf tmp/xunit-reader.tgz -C tmp 72 | chmod +x tmp/xunit-reader 73 | 74 | tmp/xunit-to-github: 75 | mkdir -p tmp 76 | curl -o tmp/xunit-to-github.tgz -sL https://github.com/josegonzalez/go-xunit-to-github/releases/download/v$(XUNIT_TO_GITHUB_VERSION)/xunit-to-github_$(XUNIT_TO_GITHUB_VERSION)_$(SYSTEM_NAME)_$(HARDWARE).tgz 77 | tar xf tmp/xunit-to-github.tgz -C tmp 78 | chmod +x tmp/xunit-to-github 79 | 80 | setup: 81 | bash tests/setup.sh 82 | $(MAKE) ci-dependencies 83 | 84 | test: lint unit-tests 85 | 86 | report: tmp/xunit-reader tmp/xunit-to-github 87 | tmp/xunit-reader -p 'tmp/test-results/bats/*.xml' 88 | tmp/xunit-reader -p 'tmp/test-results/shellcheck/*.xml' 89 | ifdef TRAVIS_REPO_SLUG 90 | ifdef GITHUB_ACCESS_TOKEN 91 | ifneq ($(TRAVIS_PULL_REQUEST),false) 92 | tmp/xunit-to-github --skip-ok --job-url "$(TRAVIS_JOB_WEB_URL)" --pull-request-id "$(TRAVIS_PULL_REQUEST)" --repository-slug "$(TRAVIS_REPO_SLUG)" --title "DOKKU_VERSION=$(DOKKU_VERSION)" tmp/test-results/bats tmp/test-results/shellcheck 93 | endif 94 | endif 95 | endif 96 | 97 | .PHONY: clean 98 | clean: 99 | rm -f README.md 100 | 101 | .PHONY: generate 102 | generate: clean README.md 103 | 104 | .PHONY: README.md 105 | README.md: 106 | bin/generate 107 | 108 | setup-deploy-tests: 109 | ifdef ENABLE_DOKKU_TRACE 110 | echo "-----> Enable dokku trace" 111 | dokku trace:on 112 | endif 113 | @echo "Setting dokku.me in /etc/hosts" 114 | sudo /bin/bash -c "[[ `ping -c1 dokku.me >/dev/null 2>&1; echo $$?` -eq 0 ]] || echo \"127.0.0.1 dokku.me *.dokku.me www.test.app.dokku.me\" >> /etc/hosts" 115 | 116 | @echo "-----> Generating keypair..." 117 | mkdir -p /root/.ssh 118 | rm -f /root/.ssh/dokku_test_rsa* 119 | echo -e "y\n" | ssh-keygen -f /root/.ssh/dokku_test_rsa -t rsa -N '' 120 | chmod 700 /root/.ssh 121 | chmod 600 /root/.ssh/dokku_test_rsa 122 | chmod 644 /root/.ssh/dokku_test_rsa.pub 123 | 124 | @echo "-----> Setting up ssh config..." 125 | ifneq ($(shell ls /root/.ssh/config >/dev/null 2>&1 ; echo $$?),0) 126 | echo "Host dokku.me \\r\\n Port $(DOKKU_SSH_PORT) \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config 127 | echo "Host 127.0.0.1 \\r\\n Port 22333 \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config 128 | else ifeq ($(shell grep dokku.me /root/.ssh/config),) 129 | echo "Host dokku.me \\r\\n Port $(DOKKU_SSH_PORT) \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config 130 | echo "Host 127.0.0.1 \\r\\n Port 22333 \\r\\n RequestTTY yes \\r\\n IdentityFile /root/.ssh/dokku_test_rsa" >> /root/.ssh/config 131 | else 132 | sed --in-place 's/Port 22 \r/Port $(DOKKU_SSH_PORT) \r/g' /root/.ssh/config 133 | cat /root/.ssh/config 134 | endif 135 | 136 | ifneq ($(wildcard /etc/ssh/sshd_config),) 137 | sed --in-place "s/^#Port 22$\/Port 22/g" /etc/ssh/sshd_config 138 | ifeq ($(shell grep 22333 /etc/ssh/sshd_config),) 139 | sed --in-place "s:^Port 22:Port 22 \\nPort 22333:g" /etc/ssh/sshd_config 140 | endif 141 | service ssh restart 142 | endif 143 | 144 | @echo "-----> Installing SSH public key..." 145 | echo "" > /home/dokku/.ssh/authorized_keys 146 | sudo sshcommand acl-remove dokku test 147 | cat /root/.ssh/dokku_test_rsa.pub | sudo sshcommand acl-add dokku test 148 | chmod 700 /home/dokku/.ssh 149 | chmod 600 /home/dokku/.ssh/authorized_keys 150 | 151 | ifeq ($(shell grep dokku.me /home/dokku/VHOST 2>/dev/null),) 152 | @echo "-----> Setting default VHOST to dokku.me..." 153 | echo "dokku.me" > /home/dokku/VHOST 154 | endif 155 | ifeq ($(DOKKU_SSH_PORT), 22) 156 | $(MAKE) prime-ssh-known-hosts 157 | endif 158 | 159 | prime-ssh-known-hosts: 160 | @echo "-----> Intitial SSH connection to populate known_hosts..." 161 | @echo "=====> SSH dokku.me" 162 | ssh -o StrictHostKeyChecking=no dokku@dokku.me help >/dev/null 163 | @echo "=====> SSH 127.0.0.1" 164 | ssh -o StrictHostKeyChecking=no dokku@127.0.0.1 help >/dev/null 165 | -------------------------------------------------------------------------------- /tests/set.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | load test_helper 3 | 4 | setup() { 5 | dokku apps:create "$TEST_APP" 6 | } 7 | 8 | teardown() { 9 | dokku --force apps:destroy "$TEST_APP" 10 | } 11 | 12 | @test "($PLUGIN_COMMAND_PREFIX:hook:set) cert-manager-enabled" { 13 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-cert-manager-enabled" 14 | echo "output: $output" 15 | echo "status: $status" 16 | assert_success "" 17 | 18 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP cert-manager-enabled true" 19 | echo "output: $output" 20 | echo "status: $status" 21 | assert_success 22 | 23 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-cert-manager-enabled" 24 | echo "output: $output" 25 | echo "status: $status" 26 | assert_success "true" 27 | 28 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP cert-manager-enabled" 29 | echo "output: $output" 30 | echo "status: $status" 31 | assert_success 32 | 33 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-cert-manager-enabled" 34 | echo "output: $output" 35 | echo "status: $status" 36 | assert_success "" 37 | } 38 | 39 | @test "($PLUGIN_COMMAND_PREFIX:hook:set) imagePullSecrets" { 40 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-imagePullSecrets" 41 | echo "output: $output" 42 | echo "status: $status" 43 | assert_success "" 44 | 45 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP imagePullSecrets registry-credential" 46 | echo "output: $output" 47 | echo "status: $status" 48 | assert_success 49 | 50 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-imagePullSecrets" 51 | echo "output: $output" 52 | echo "status: $status" 53 | assert_success "registry-credential" 54 | 55 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP imagePullSecrets" 56 | echo "output: $output" 57 | echo "status: $status" 58 | assert_success 59 | 60 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-imagePullSecrets" 61 | echo "output: $output" 62 | echo "status: $status" 63 | assert_success "" 64 | } 65 | 66 | @test "($PLUGIN_COMMAND_PREFIX:hook:set) ingress-enabled" { 67 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-ingress-enabled" 68 | echo "output: $output" 69 | echo "status: $status" 70 | assert_success "false" 71 | 72 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP ingress-enabled true" 73 | echo "output: $output" 74 | echo "status: $status" 75 | assert_success 76 | 77 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-ingress-enabled" 78 | echo "output: $output" 79 | echo "status: $status" 80 | assert_success "true" 81 | 82 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP ingress-enabled" 83 | echo "output: $output" 84 | echo "status: $status" 85 | assert_success 86 | 87 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-ingress-enabled" 88 | echo "output: $output" 89 | echo "status: $status" 90 | assert_success "false" 91 | } 92 | 93 | @test "($PLUGIN_COMMAND_PREFIX:hook:set) namespace" { 94 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-namespace" 95 | echo "output: $output" 96 | echo "status: $status" 97 | assert_success "" 98 | 99 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP namespace some-namespace" 100 | echo "output: $output" 101 | echo "status: $status" 102 | assert_success 103 | 104 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-namespace" 105 | echo "output: $output" 106 | echo "status: $status" 107 | assert_success "some-namespace" 108 | 109 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP namespace" 110 | echo "output: $output" 111 | echo "status: $status" 112 | assert_success 113 | 114 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-namespace" 115 | echo "output: $output" 116 | echo "status: $status" 117 | assert_success "" 118 | } 119 | 120 | @test "($PLUGIN_COMMAND_PREFIX:hook:set) pod-max-unavailable" { 121 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-pod-max-unavailable" 122 | echo "output: $output" 123 | echo "status: $status" 124 | assert_success "" 125 | 126 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP pod-max-unavailable 1" 127 | echo "output: $output" 128 | echo "status: $status" 129 | assert_success 130 | 131 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-pod-max-unavailable" 132 | echo "output: $output" 133 | echo "status: $status" 134 | assert_success "1" 135 | 136 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP pod-max-unavailable" 137 | echo "output: $output" 138 | echo "status: $status" 139 | assert_success 140 | 141 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-pod-max-unavailable" 142 | echo "output: $output" 143 | echo "status: $status" 144 | assert_success "" 145 | } 146 | 147 | @test "($PLUGIN_COMMAND_PREFIX:hook:set) pod-min-available" { 148 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-pod-min-available" 149 | echo "output: $output" 150 | echo "status: $status" 151 | assert_success "" 152 | 153 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP pod-min-available 1" 154 | echo "output: $output" 155 | echo "status: $status" 156 | assert_success 157 | 158 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-pod-min-available" 159 | echo "output: $output" 160 | echo "status: $status" 161 | assert_success "1" 162 | 163 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP pod-min-available" 164 | echo "output: $output" 165 | echo "status: $status" 166 | assert_success 167 | 168 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-pod-min-available" 169 | echo "output: $output" 170 | echo "status: $status" 171 | assert_success "" 172 | } 173 | 174 | @test "($PLUGIN_COMMAND_PREFIX:hook:set) service-process-types" { 175 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-service-process-types" 176 | echo "output: $output" 177 | echo "status: $status" 178 | assert_success "" 179 | 180 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP service-process-types http,worker" 181 | echo "output: $output" 182 | echo "status: $status" 183 | assert_success 184 | 185 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-service-process-types" 186 | echo "output: $output" 187 | echo "status: $status" 188 | assert_success "http,worker" 189 | 190 | run /bin/bash -c "dokku scheduler-kubernetes:set $TEST_APP service-process-types" 191 | echo "output: $output" 192 | echo "status: $status" 193 | assert_success 194 | 195 | run /bin/bash -c "dokku scheduler-kubernetes:report $TEST_APP --scheduler-kubernetes-service-process-types" 196 | echo "output: $output" 197 | echo "status: $status" 198 | assert_success "" 199 | } 200 | -------------------------------------------------------------------------------- /tests/shellcheck-to-junit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import print_function 4 | import argparse 5 | import collections 6 | import datetime 7 | import re 8 | import socket 9 | import sys 10 | 11 | from xml.etree import ElementTree 12 | 13 | 14 | def CDATA(text=None): 15 | element = ElementTree.Element('![CDATA[') 16 | element.text = text 17 | return element 18 | 19 | 20 | def _serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs): 21 | 22 | if elem.tag == '![CDATA[': 23 | write("\n<{}{}]]>\n".format(elem.tag, elem.text)) 24 | if elem.tail: 25 | write(ElementTree._escape_cdata(elem.tail)) 26 | else: 27 | return ElementTree._original_serialize_xml(write, elem, qnames, namespaces,short_empty_elements, **kwargs) 28 | 29 | 30 | ElementTree._original_serialize_xml = ElementTree._serialize_xml 31 | ElementTree._serialize_xml = ElementTree._serialize['xml'] = _serialize_xml 32 | 33 | 34 | def read_in(): 35 | lines = sys.stdin.readlines() 36 | for i in range(len(lines)): 37 | lines[i] = lines[i].rstrip() 38 | return lines 39 | 40 | 41 | def process_lines(lines): 42 | files = {} 43 | current_file = None 44 | previous_line = None 45 | line_no = None 46 | new_issues = [] 47 | code = None 48 | 49 | RE_VIOLATION = re.compile(r"\^-- (SC[\w]+): (.*)") 50 | RE_VIOLATION_NEW = re.compile(r"\^[-]+\^ (SC[\w]+): (.*)") 51 | 52 | for line in lines: 53 | # start a new block 54 | if line == '': 55 | if current_file is not None: 56 | file_data = files.get(current_file, {}) 57 | files[current_file] = file_data 58 | 59 | issue_data = file_data.get(line_no, {}) 60 | issue_data['code'] = code 61 | files[current_file][line_no] = issue_data 62 | 63 | issues = issue_data.get('issues', []) 64 | issues.extend(new_issues) 65 | issue_data['issues'] = issues 66 | 67 | files[current_file][line_no] = issue_data 68 | 69 | code = None 70 | current_file = None 71 | line_no = None 72 | elif line.startswith('In ./') and not previous_line: 73 | current_file = line.split(' ')[1].replace('./', '') 74 | line_no = line.split(' ')[3] 75 | new_issues = [] 76 | code = None 77 | elif code is None and len(new_issues) == 0: 78 | code = line 79 | else: 80 | match = RE_VIOLATION.match(line.strip()) 81 | if not match: 82 | match = RE_VIOLATION_NEW.match(line.strip()) 83 | 84 | if not match: 85 | if 'https://www.shellcheck.net/wiki/SC' in line: 86 | continue 87 | if 'For more information:' == line: 88 | continue 89 | print('Error: Issue parsing line "{0}"'.format(line.strip())) 90 | else: 91 | new_issues.append({ 92 | 'shellcheck_id': match.group(1), 93 | 'message': match.group(2), 94 | 'original_message': line 95 | }) 96 | 97 | previous_line = line 98 | 99 | return files 100 | 101 | 102 | def output_junit(files, args): 103 | timestamp = datetime.datetime.now().replace(microsecond=0).isoformat() 104 | failures = 0 105 | for file, data in files.items(): 106 | for line, issue_data in data.items(): 107 | code = issue_data.get('code') 108 | for issue in issue_data.get('issues', []): 109 | failures += 1 110 | 111 | tests = 0 112 | if args.files: 113 | with open(args.files, 'r') as f: 114 | tests = len(f.readlines()) 115 | 116 | root = ElementTree.Element("testsuite", 117 | name="shellcheck", 118 | tests="{0}".format(tests), 119 | failures="{0}".format(failures), 120 | errors="0", 121 | skipped="0", 122 | timestamp=timestamp, 123 | time="0", 124 | hostname=socket.gethostname()) 125 | 126 | properties = ElementTree.SubElement(root, "properties") 127 | if args.exclude: 128 | ElementTree.SubElement(properties, 129 | "property", 130 | name="exclude", 131 | value=args.exclude) 132 | 133 | if args.files: 134 | with open(args.files, 'r') as f: 135 | lines = f.readlines() 136 | for i in range(len(lines)): 137 | file = lines[i].rstrip().replace('./', '') 138 | data = files.get(file, None) 139 | if data: 140 | for line, issue_data in data.items(): 141 | code = issue_data.get('code') 142 | for issue in issue_data.get('issues', []): 143 | testcase = ElementTree.SubElement(root, 144 | "testcase", 145 | classname=file, 146 | name=file, 147 | time="0") 148 | shellcheck_id = issue.get('shellcheck_id') 149 | message = 'line {0}: {1}'.format( 150 | line, issue.get('message')) 151 | original_message = issue.get('original_message') 152 | e = ElementTree.Element("failure", 153 | type=shellcheck_id, 154 | message=message) 155 | cdata = CDATA("\n".join([code, original_message])) 156 | e.append(cdata) 157 | testcase.append(e) 158 | ElementTree.SubElement(root, 159 | "testcase", 160 | classname=file, 161 | name=file, 162 | time="0") 163 | 164 | ElementTree.SubElement(root, "system-out") 165 | ElementTree.SubElement(root, "system-err") 166 | 167 | content = ElementTree.tostring(root, encoding='UTF-8', method='xml') 168 | if args.output: 169 | with open(args.output, 'w') as f: 170 | try: 171 | f.write(content) 172 | except TypeError: 173 | f.write(content.decode("utf-8")) 174 | 175 | 176 | def main(): 177 | parser = argparse.ArgumentParser( 178 | description='Process shellcheck output to junit.') 179 | parser.add_argument('--output', 180 | dest='output', 181 | action='store', 182 | default=None, 183 | help='file to write shellcheck output') 184 | parser.add_argument('--files', 185 | dest='files', 186 | action='store', 187 | default=None, 188 | help='a file containing a list of all files processed by shellcheck') 189 | parser.add_argument('--exclude', 190 | dest='exclude', 191 | action='store', 192 | default=None, 193 | help='a comma-separated list of rules being excluded by shellcheck') 194 | args = parser.parse_args() 195 | 196 | lines = read_in() 197 | files = process_lines(lines) 198 | files = collections.OrderedDict(sorted(files.items())) 199 | output_junit(files, args) 200 | for line in lines: 201 | print(line) 202 | 203 | 204 | if __name__ == '__main__': 205 | main() 206 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dokku-scheduler-kubernetes 2 | 3 | > If this plugin is missing a feature you need, consider [sponsoring development](https://github.com/dokku/.github/blob/master/SPONSORING.md). Pull requests always welcome! 4 | 5 | A Dokku plugin to integrate with kubernetes. 6 | 7 | ## Requirements 8 | 9 | - The `dokku-registry` plugin should be installed and configured for your app 10 | - A configured kubectl (`/home/dokku/.kube/config`) that can talk to your cluster 11 | - Dokku 0.20.4+ 12 | 13 | ## Installation 14 | 15 | You can install this plugin by issuing the command: 16 | 17 | ```shell 18 | dokku plugin:install https://github.com/dokku/dokku-scheduler-kubernetes 19 | ``` 20 | 21 | After the plugin has successfully been installed you need to install the plugin's dependencies by running the command: 22 | 23 | ```shell 24 | dokku plugin:install-dependencies 25 | ``` 26 | 27 | ## Functionality 28 | 29 | The following functionality has been implemented 30 | 31 | - Deployment and Service annotations 32 | - Domain proxy support via the Nginx Ingress Controller 33 | - Environment variables 34 | - Letsencrypt SSL Certificate integration via CertManager 35 | - Pod Disruption Budgets 36 | - Resource limits and reservations (reservations == kubernetes requests) 37 | - If no unit is specified, the values for memory are assumed to be in Mi (Megabytes) 38 | - Zero-downtime deploys via Deployment healthchecks 39 | - Traffic to non-web containers (via a configurable list) 40 | 41 | Unsupported at this time: 42 | 43 | - Custom docker-options (not applicable) 44 | - Deployment timeouts (planned: [#30](https://github.com/dokku/dokku-scheduler-kubernetes/issues/30)) 45 | - Dockerfile support (planned: [#29](https://github.com/dokku/dokku-scheduler-kubernetes/issues/29)) 46 | - Encrypted environment variables (planned: [#9](https://github.com/dokku/dokku-scheduler-kubernetes/issues/9)) 47 | - Proxy port integration (planned: [#29](https://github.com/dokku/dokku-scheduler-kubernetes/issues/29)) 48 | - Manual SSL Certificates (planned: [#28](https://github.com/dokku/dokku-scheduler-kubernetes/issues/28)) 49 | - The following scheduler commands are unimplemented: 50 | - `enter` (planned: [#12](https://github.com/dokku/dokku-scheduler-kubernetes/issues/12)) 51 | - `logs:failed` 52 | - `run` (planned: [#12](https://github.com/dokku/dokku-scheduler-kubernetes/issues/12)) 53 | 54 | > If this plugin is missing a feature you need, consider [sponsoring development](https://github.com/dokku/.github/blob/master/SPONSORING.md). Pull requests always welcome! 55 | 56 | ### Notes 57 | 58 | - Each `Procfile` entry will be turned into a kubernetes `Deployment` object. 59 | - Each `Procfile` entry name _must_ be a valid DNS subdomain. 60 | - The `web` process will also create a `Service` object. 61 | - Non-web processes can create a `Service` object via a configurable property. 62 | - All created Kubernetes objects are tracked to completion via `kubedog`. 63 | - All manifest templates are hardcoded in the plugin. 64 | 65 | ## Usage 66 | 67 | Set the scheduler to `kubernetes`. This can be done per-app or globally: 68 | 69 | ```shell 70 | # globally 71 | dokku scheduler:set --global selected kubernetes 72 | 73 | # per-app 74 | dokku scheduler:set $APP selected kubernetes 75 | ``` 76 | 77 | You also need to ensure your kubectl has the correct context specified: 78 | 79 | ```shell 80 | # as the dokku user 81 | kubectl config use-context YOUR_NAME 82 | ``` 83 | 84 | And configure your registry: 85 | 86 | ```shell 87 | dokku registry:set $APP server gcr.io/dokku/ 88 | ``` 89 | 90 | Assuming your Dokku installation can push to the registry and your kubeconfig is valid, Dokku will deploy the app against the cluster. 91 | 92 | The namespace in use for a particular app can be customized using the `:set` command. This will apply to all future invocations of the plugin, and will not modify any existing resources. If unspecified, the namespace in use is the cluster `default` namespace. The `scheduler-kubernetes` will create the namespace via a `kubectl apply`. 93 | 94 | ```shell 95 | dokku scheduler-kubernetes:set $APP namespace test 96 | ``` 97 | 98 | If deploying from a private docker registry and the cluster needs does not have open access to the registry, an `imagePullSecrets` value can be specified. This will be injected into the kubernetes deployment spec at deploy time. 99 | 100 | ```shell 101 | dokku scheduler-kubernetes:set $APP imagePullSecrets registry-credential 102 | ``` 103 | 104 | > See [this doc](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) for more details on creating an `imagePullSecrets` secret file. 105 | 106 | ### Service Ingress 107 | 108 | > This functionality assumes a helm-installed `nginx-ingress` controller: 109 | > 110 | > ```shell 111 | > helm install ingress-nginx ingress-nginx/ingress-nginx --set controller.publishService.enabled=true 112 | > ``` 113 | 114 | A Kubernetes Service object is created for each `web` process. Additionally, if the app has it's `proxy-type` set to `nginx-ingress`, then we will also create or update a Kubernetes ingress object within the namespace configured for the app. This can be set as follows: 115 | 116 | ```shell 117 | dokku config:set $APP DOKKU_APP_PROXY_TYPE=nginx-ingress 118 | ``` 119 | 120 | The ingress object has the following properties: 121 | 122 | - The name of the ingress object will be `app-ingress`. 123 | - All kubernetes-deployed apps within the same namespace are added to the ingress object. 124 | - Configured app domains are respected as unique rules. 125 | - The configured service port for each rule is hardcoded to `5000`. 126 | 127 | To modify the manifest before it gets applied to the cluster, use the `pre-kubernetes-ingress-apply` plugin trigger. 128 | 129 | Service objects can also be created for specific process types by configuring the `service-process-types` property. This is a comma-separated list that is specific to an individual application, and will always implicitly include the `web` process type. 130 | 131 | ```shell 132 | dokku scheduler-kubernetes:set $APP service-process-types http,worker 133 | ``` 134 | 135 | The PORT environment variable is hardcoded to 5000. No Ingress object is created for non-web processes. 136 | 137 | #### Automated SSL Integration via CertManager 138 | 139 | > This functionality assumes a helm-installed `cert-manager` CRD: 140 | > 141 | > ```shell 142 | > kubectl create namespace cert-manager 143 | > helm repo add jetstack https://charts.jetstack.io 144 | > helm upgrade cert-manager jetstack/cert-manager --namespace cert-manager --version v1.10.0 --set installCRDs=true --install 145 | > ``` 146 | 147 | At this time, the `scheduler-kubernetes` does not have support for custom SSL certificates. However, domains associated with an app can have a Letsencrypt SSL certificate provisioned automatically via the [CertManager](https://github.com/jetstack/cert-manager) Kubernetes add-on. 148 | 149 | To start using the CertManager, we will first need to set the issuer email 150 | 151 | ```shell 152 | dokku config:set --global CERT_MANAGER_EMAIL=your@email.tld 153 | ``` 154 | 155 | Next, any apps that will require cert-manager integration will need to have that enabled: 156 | 157 | ```shell 158 | dokku scheduler-kubernetes:set $APP cert-manager-enabled true 159 | ``` 160 | 161 | On the next deploy or domain name change, the CertManager entry will be automatically updated to fetch an SSL certificate for all domains associated with all applications on the same ingress object. 162 | 163 | ### Pod Disruption Budgets 164 | 165 | A PodDisruptionBudget object can be created, and will apply to all process types in an app. To configure this, the `pod-max-unavailable` and `pod-min-available` properties can be set: 166 | 167 | ```shell 168 | dokku scheduler-kubernetes:set $APP pod-min-available 1 169 | 170 | # available in kubernetes 1.7+ 171 | dokku scheduler-kubernetes:set $APP pod-max-unavailable 1 172 | ``` 173 | 174 | Pod Disruption Budgets will be updated on next deploy. 175 | 176 | ### Deployment Autoscaling 177 | 178 | > This feature requires an installed metric server, uses the `autoscaling/v2beta2` api, and will apply immediately. Only `resource` rules are supported when using the official metric-server, all others require the prometheus-operator and prometheus-adapter. 179 | 180 | By default, Kubernetes deployments are not set to autoscale, but a HorizontalPodAutoscaler object can be managed for an app on a per-process type basis - referenced as `$PROC_TYPE` below. Using the HorizontalPodAutoscaler will disable the normal usage of `ps:scale` for the specified app/process-type combination, as per Kubernetes best practices. 181 | 182 | At a minimum, both a min/max number of replicas must be set. 183 | 184 | ```shell 185 | # set the min number of replicas 186 | dokku scheduler-kubernetes:autoscale-set $APP $PROC_TYPE min-replicas 1 187 | 188 | # set the max number of replicas 189 | dokku scheduler-kubernetes:autoscale-set $APP $PROC_TYPE max-replicas 10 190 | ``` 191 | 192 | You also need to add autoscaling rules. These can be managed via the `:autoscale-rule-add` command. Adding a rule for a target-name/metric-type combination that already exists will override any existing rules. 193 | 194 | Rules can be added for the following metric types: 195 | 196 | - `external`: 197 | - format: `external:$NAME:$TYPE:$VALUE[:$SELECTOR]` 198 | - fields: 199 | - `$NAME`: The name of the external metric to track 200 | - `$TYPE` (valid values: `[AverageValue, Value]`): The type of the target. 201 | - `$VALUE`: The value to track. 202 | - `$SELECTOR` (optional): The selector to use for filtering to one or more specific metric series. 203 | - `ingress`: 204 | - format: `ingress:$NAME:$TYPE:$VALUE[:$INGRESS]` 205 | - fields: 206 | - `$NAME`: The name of the ingress metric to track. 207 | - `$TYPE` (valid values: `[AverageValue, Value]`): The type of the target. 208 | - `$VALUE`: The value to track. 209 | - `$INGRESS` (default: `app-ingress`): The name of the ingress object to filter on. 210 | - `pods` 211 | - format: `pods:$NAME:$TYPE:$VALUE` 212 | - fields: 213 | - `$NAME`: The name of the metric from the pod resource to track. 214 | - `$TYPE` (valid values: `[AverageValue]`): The type of the target. 215 | - `$VALUE`: The value to track. 216 | - `resource` 217 | - format: `resource:$NAME:$TYPE:$VALUE` 218 | - fields: 219 | - `$NAME` (valid values: `[cpu, memory]`): The name of the metric to track. 220 | - `$TYPE` (valid values: `[AverageValue, Utilization]`): The type of the target. 221 | - `$VALUE`: The value to track. 222 | 223 | ```shell 224 | # set the cpu average utilization target 225 | dokku scheduler-kubernetes:autoscale-rule-add $APP $PROC_TYPE resource:cpu:Utilization:50 226 | ``` 227 | 228 | Rules can be listed via the `autoscale-rule-list` command: 229 | 230 | ```shell 231 | dokku scheduler-kubernetes:autoscale-rule-list $APP $PROC_TYPE 232 | ``` 233 | 234 | Rules can be removed via the `:autoscale-rule-remove` command. This command takes the same arguments as the `autoscale-rule-add` command, though the value is optional. If a rule matching the specified arguments does not exist, the command will still return 0. 235 | 236 | ```shell 237 | # remove the cpu rule 238 | dokku scheduler-kubernetes:autoscale-rule-remove $APP $PROC_TYPE resource:cpu:Utilization:50 239 | 240 | # remove the cpu rule by prefix 241 | dokku scheduler-kubernetes:autoscale-rule-remove $APP $PROC_TYPE resource:cpu:Utilization 242 | ``` 243 | 244 | Autoscaling rules are applied automatically during the next deploy, though may be immediately applied through the `:autoscale-apply` command: 245 | 246 | ```shell 247 | dokku scheduler-kubernetes:autoscale-apply $APP $PROC_TYPE 248 | ``` 249 | ### Persistent Volume Claims (pvc) 250 | 251 | Pods use pvcs as volumes. For volumes that support multiple access modes, the user specifies which mode is desired when using their claim as a volume in a Pod. [See supported access modes by providers](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) 252 | 253 | ```shell 254 | # create a pvc 255 | dokku scheduler-kubernetes:add-pvc $NAME $SIZE [--access-mode $MODE][--namespace $NAMESPACE ][--storage-class-name $CLASS] 256 | ``` 257 | 258 | Fields: 259 | - `$NAME`: The name of the persistent volume claim 260 | - `$SIZE` is a numeric size of claim in MB. 261 | - `$MODE`: Access mode must be either of ReadWriteOnce, ReadOnlyMany or ReadWriteMany. Default is ReadWriteOnce 262 | - `$NAMESPACE` : The namespace for the pvc. Default is "default" 263 | - `$CLASS`: The storage class name. Default is k8s providers default storage class 264 | 265 | ```shell 266 | # list pvcs 267 | dokku scheduler-kubernetes:list-pvc [$NAMESPACE] 268 | ``` 269 | 270 | ```shell 271 | # delete pvc 272 | dokku scheduler-kubernetes:remove-pvc $NAME --namespace $NAMESPACE 273 | ``` 274 | 275 | #### Mounting a volume using PVC 276 | Pods access storage by using the claim as a volume. 277 | 278 | ```shell 279 | # mounting a volume (requires a re-deploy to take effect) 280 | dokku scheduler-kubernetes:mount $APP_NAME $PVC_NAME $CONTAINER_PATH 281 | ``` 282 | 283 | Fields: 284 | - `$APP_NAME`: The name of the app 285 | - `$PVC_NAME`: Name of persistent volume claim. Will be used as volume name. Claims must exist in the same namespace as the app. 286 | 287 | List mounted volumes for an app: 288 | ```shell 289 | # list mounted volumes 290 | dokku scheduler-kubernetes:list-mount $APP_NAME 291 | ``` 292 | 293 | Unmount a volume: 294 | ```shell 295 | # unmount a volume (requires a re-deploy to take effect) 296 | dokku scheduler-kubernetes:unmount $APP_NAME $PVC_NAME $CONTAINER_PATH 297 | ``` 298 | 299 | Unmount all volumes: 300 | ```shell 301 | # unmount all (requires a re-deploy to take effect) 302 | dokku scheduler-kubernetes:unmount-all $APP_NAME 303 | ``` 304 | 305 | ### Kubernetes Manifests 306 | 307 | > Warning: Running this command exposes app environment variables to stdout. 308 | 309 | The kubernetes manifest for a deployment or service can be displayed using the `:show-manifest` command. This manifest can be used to inspect what would be submitted to Kubernetes. 310 | 311 | ```shell 312 | # show the deployment manifest for the `web` process type 313 | dokku scheduler-kubernetes:show-manifest $APP $PROC_TYPE $MANIFEST_TYPE 314 | ``` 315 | 316 | This command can be used like so: 317 | 318 | ```shell 319 | # show the deployment manifest for the `web` process type 320 | dokku scheduler-kubernetes:show-manifest node-js-sample web 321 | 322 | 323 | # implicitly specify the deployment manifest 324 | dokku scheduler-kubernetes:show-manifest node-js-sample web deployment 325 | 326 | # show the service manifest for the `web` process type 327 | dokku scheduler-kubernetes:show-manifest node-js-sample web service 328 | ``` 329 | 330 | The command will exit non-zero if the specific manifest for the given app/process type combination is not found. 331 | 332 | ### Annotations 333 | 334 | > Warning: There is no validation for on annotation keys or values. 335 | 336 | #### Deployment Annotations 337 | 338 | These can be managed by the `:deployment-annotations-set` command. 339 | 340 | ```shell 341 | # command structure 342 | dokku scheduler-kubernetes:deployment-annotations-set $APP $ANNOTATION_NAME $ANNOTATION_VALUE 343 | 344 | # set example 345 | dokku scheduler-kubernetes:deployment-annotations-set node-js-sample pod.kubernetes.io/lifetime 86400s 346 | 347 | # unset example, leave the value empty 348 | dokku scheduler-kubernetes:deployment-annotations-set node-js-sample pod.kubernetes.io/lifetime 349 | ``` 350 | 351 | Currently, these apply globally to all processes within a deployed app. 352 | 353 | #### Pod Annotations 354 | 355 | These can be managed by the `:pod-annotations-set` command. 356 | 357 | ```shell 358 | # command structure 359 | dokku scheduler-kubernetes:pod-annotations-set $APP name value 360 | 361 | # set example 362 | dokku scheduler-kubernetes:pod-annotations-set node-js-sample pod.kubernetes.io/lifetime 86400s 363 | 364 | # unset example, leave the value empty 365 | dokku scheduler-kubernetes:pod-annotations-set node-js-sample pod.kubernetes.io/lifetime 366 | ``` 367 | 368 | Currently, these apply globally to all processes within a deployed app. 369 | 370 | #### Service Annotations 371 | 372 | These can be managed by the `:service-annotations-set` command. 373 | 374 | ```shell 375 | # command structure 376 | dokku scheduler-kubernetes:service-annotations-set $APP name value 377 | 378 | # set example 379 | dokku scheduler-kubernetes:service-annotations-set node-js-sample pod.kubernetes.io/lifetime 86400s 380 | 381 | # unset example, leave the value empty 382 | dokku scheduler-kubernetes:service-annotations-set node-js-sample pod.kubernetes.io/lifetime 383 | ``` 384 | 385 | Currently, they are applied to the `web` process, which is the only process for which a Kubernetes Service is created. 386 | 387 | #### Ingress Annotations on Namespaces 388 | 389 | These can be managed by the `:ingress-annotations-set` command. 390 | 391 | ```shell 392 | # command structure 393 | dokku scheduler-kubernetes:ingress-annotations-set $NAMESPACE name value 394 | 395 | # set example 396 | dokku scheduler-kubernetes:ingress-annotations-set my-namespace nginx.ingress.kubernetes.io/affinity cookie 397 | 398 | # unset example, leave the value empty 399 | dokku scheduler-kubernetes:ingress-annotations-set my-namespace nginx.ingress.kubernetes.io/affinity 400 | ``` 401 | 402 | Currently, these apply to all deployments within an namespace. 403 | ### Rolling Updates 404 | 405 | For deployments that use a `rollingUpdate` for rollouts, a `rollingUpdate` may be triggered at a later date via the `:rolling-update` command. 406 | 407 | ```shell 408 | dokku scheduler-kubernetes:rolling-update $APP 409 | ``` 410 | 411 | ### Health Checks 412 | 413 | Health checks for the app may be configured in `app.json`, based on [Kubernetes 414 | liveness and readiness 415 | probes](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/). 416 | All Kubernetes options that can occur within a [Probe 417 | object](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.14/#probe-v1-core) 418 | are supported, though syntax is JSON rather than YAML. The variable `$APP` may 419 | be used to represent the app name. 420 | 421 | If a process type is not configured for a given probe type (liveness or 422 | readiness), any probe of the same type for the `"*"` default process type is 423 | used instead. 424 | 425 |
Here (click the triangle to expand) is an example JSON for 426 | Kubernetes health checks.

427 | 428 | ```json 429 | { 430 | "healthchecks": { 431 | "web": { 432 | "readiness": { 433 | "httpGet": { 434 | "path": "/{{ $APP }}/readiness_check", 435 | "port": 5000 436 | }, 437 | "initialDelaySeconds": 5, 438 | "periodSeconds": 5 439 | } 440 | }, 441 | "*": { 442 | "liveness": { 443 | "exec": { 444 | "command": ["/bin/pidof", "/start"] 445 | }, 446 | "initialDelaySeconds": 5, 447 | "periodSeconds": 5 448 | }, 449 | "readiness": { 450 | "httpGet": { 451 | "path": "web processes override this.", 452 | "port": 5000 453 | }, 454 | "initialDelaySeconds": 5, 455 | "periodSeconds": 5 456 | } 457 | } 458 | } 459 | } 460 | ``` 461 |

462 | 463 | ## Plugin Triggers 464 | 465 | The following custom triggers are exposed by the plugin: 466 | 467 | ### `post-deploy-kubernetes-apply` 468 | 469 | - Description: Allows a user to interact with the `deployment` manifest after it has been submitted. 470 | - Invoked by: 471 | - Arguments: `$APP` `$PROC_TYPE` `$MANIFEST_FILE` `$MANIFEST_TYPE` 472 | - Example: 473 | 474 | ```shell 475 | #!/usr/bin/env bash 476 | 477 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 478 | 479 | # TODO 480 | ``` 481 | 482 | ### `pre-ingress-kubernetes-apply` 483 | 484 | - Description: Allows a user to interact with the `ingress` manifest before it has been submitted. 485 | - Invoked by: `core-post-deploy`, `post-domains-update`, `post-proxy-ports-update`, and `proxy-build-config` triggers 486 | - Arguments: `$APP` `$MANIFEST_FILE` 487 | - Example: 488 | 489 | ```shell 490 | #!/usr/bin/env bash 491 | 492 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 493 | 494 | # TODO 495 | ``` 496 | 497 | ### `pre-deploy-kubernetes-apply` 498 | 499 | - Description: Allows a user to interact with the `deployment|service` manifest before it has been submitted. 500 | - Invoked by: `scheduler-deploy` trigger and `:show-manifest` 501 | - Arguments: `$APP` `$PROC_TYPE` `$MANIFEST_FILE` `$MANIFEST_TYPE` 502 | - Example: 503 | 504 | ```shell 505 | #!/usr/bin/env bash 506 | 507 | set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x 508 | 509 | # TODO 510 | ``` 511 | -------------------------------------------------------------------------------- /internal-functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions" 4 | source "$PLUGIN_AVAILABLE_PATH/config/functions" 5 | source "$PLUGIN_AVAILABLE_PATH/domains/functions" 6 | set -eo pipefail 7 | [[ $DOKKU_TRACE ]] && set -x 8 | 9 | cmd-scheduler-kubernetes-rolling-update() { 10 | declare desc="force a rolling update" 11 | declare cmd="scheduler-kubernetes:rolling-update" argv=("$@") 12 | [[ ${argv[0]} == "$cmd" ]] && shift 1 13 | declare APP="$1" 14 | 15 | verify_app_name "$APP" 16 | local DOKKU_SCHEDULER=$(get_app_scheduler "$APP") 17 | if [[ "$DOKKU_SCHEDULER" != "kubernetes" ]]; then 18 | dokku_log_fail "Scheduler for $APP is set to $DOKKU_SCHEDULER" 19 | return 1 20 | fi 21 | 22 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 23 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 24 | KUBE_ARGS=() 25 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 26 | KUBE_ARGS+=("--namespace=$NAMESPACE") 27 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 28 | 29 | dokku_log_info1 "Triggering rolling-update for $APP" 30 | while read -r line || [[ -n "$line" ]]; do 31 | [[ "$line" =~ ^#.* ]] && continue 32 | line="$(strip_inline_comments "$line")" 33 | PROC_TYPE=${line%%=*} 34 | 35 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" patch deployment "${APP}-${PROC_TYPE}" --patch "{\"spec\":{\"template\":{\"metadata\":{\"labels\":{\"dokku.com/rolling-update-time\":\"$(date -u "+%Y-%m-%d-%H-%M-%S")\"}}}}}" | sed "s/^/ /" 36 | done < <(plugin trigger ps-current-scale "$APP") 37 | } 38 | 39 | cmd-scheduler-kubernetes-report() { 40 | declare desc="displays a scheduler-kubernetes report for one or more apps" 41 | declare cmd="scheduler-kubernetes:report" 42 | local INSTALLED_APPS=$(dokku_apps) 43 | local APP="$2" INFO_FLAG="$3" 44 | 45 | if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then 46 | INFO_FLAG="$APP" 47 | APP="" 48 | fi 49 | 50 | if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then 51 | INFO_FLAG="true" 52 | fi 53 | 54 | if [[ -z "$APP" ]]; then 55 | for app in $INSTALLED_APPS; do 56 | cmd-scheduler-kubernetes-report-single "$app" "$INFO_FLAG" | tee || true 57 | done 58 | else 59 | cmd-scheduler-kubernetes-report-single "$APP" "$INFO_FLAG" 60 | fi 61 | } 62 | 63 | cmd-scheduler-kubernetes-report-single() { 64 | declare APP="$1" INFO_FLAG="$2" 65 | if [[ "$INFO_FLAG" == "true" ]]; then 66 | INFO_FLAG="" 67 | fi 68 | verify_app_name "$APP" 69 | local flag_map=( 70 | "--scheduler-kubernetes-cert-manager-enabled: $(fn-plugin-property-get "scheduler-kubernetes" "$APP" "cert-manager-enabled" "")" 71 | "--scheduler-kubernetes-imagePullSecrets: $(fn-plugin-property-get "scheduler-kubernetes" "$APP" "imagePullSecrets" "")" 72 | "--scheduler-kubernetes-ingress-enabled: $(fn-plugin-property-get "scheduler-kubernetes" "$APP" "ingress-enabled" "false")" 73 | "--scheduler-kubernetes-namespace: $(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "")" 74 | "--scheduler-kubernetes-pod-max-unavailable: $(fn-plugin-property-get "scheduler-kubernetes" "$APP" "pod-max-unavailable" "")" 75 | "--scheduler-kubernetes-pod-min-available: $(fn-plugin-property-get "scheduler-kubernetes" "$APP" "pod-min-available" "")" 76 | "--scheduler-kubernetes-service-process-types: $(fn-plugin-property-get "scheduler-kubernetes" "$APP" "service-process-types" "")" 77 | ) 78 | 79 | if [[ -z "$INFO_FLAG" ]]; then 80 | dokku_log_info2_quiet "${APP} scheduler-kubernetes information" 81 | for flag in "${flag_map[@]}"; do 82 | key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" 83 | dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")" 84 | done 85 | else 86 | local match=false 87 | local value_exists=false 88 | for flag in "${flag_map[@]}"; do 89 | valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" 90 | if [[ "$flag" == "${INFO_FLAG}:"* ]]; then 91 | value=${flag#*: } 92 | size="${#value}" 93 | if [[ "$size" -ne 0 ]]; then 94 | echo "$value" && match=true && value_exists=true 95 | else 96 | match=true 97 | fi 98 | fi 99 | done 100 | [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" 101 | [[ "$value_exists" == "true" ]] || echo "" 102 | fi 103 | } 104 | 105 | scheduler_docker_local_help_content_func() { 106 | declare desc="return scheduler-kubernetes plugin help content" 107 | cat < , Apply autoscale settings for an app/proc-type combination 109 | scheduler-kubernetes:autoscale-rule-add , Add an autoscale rule for an app/proc-type combination 110 | scheduler-kubernetes:autoscale-rule-list , List autoscale rules for an app/proc-type combination 111 | scheduler-kubernetes:autoscale-rule-remove , Remove an autoscale rule for an app/proc-type combination 112 | scheduler-kubernetes:autoscale-set , Set or clear autoscale settings for an app/proc-type combination 113 | scheduler-kubernetes:deployment-annotations-set , Set or clear a scheduler-kubernetes deployment annotation for an app 114 | scheduler-kubernetes:pod-annotations-set , Set or clear a scheduler-kubernetes pod annotation for an app 115 | scheduler-kubernetes:ingress-annotations-set (), Set or clear a scheduler-kubernetes ingress annotation for a namespace 116 | scheduler-kubernetes:report [] [], Displays a scheduler-kubernetes report for one or more apps 117 | scheduler-kubernetes:rolling-update , Force a rolling update 118 | scheduler-kubernetes:service-annotations-set , Set or clear a scheduler-kubernetes service annotation for an app 119 | scheduler-kubernetes:set (), Set or clear a scheduler-kubernetes property for an app 120 | scheduler-kubernetes:show-manifest (), Display the deployment or service manifest for a given app/process type combination 121 | scheduler-kubernetes:add-pvc --access-mode --namespace --storage-class-name , Adds a Persistent Volume Claim (PVC) 122 | scheduler-kubernetes:remove-pvc --namespace , Remove Persistent Volume Claim in Namespace 123 | scheduler-kubernetes:list-pvc (), Display Persistent Volume Claims by Namespace 124 | scheduler-kubernetes:mount , Mount a Volume to Container Path for an app 125 | scheduler-kubernetes:unmount , Unmount a Volume from an app 126 | scheduler-kubernetes:unmount-all , Unmount all Volumes from an app 127 | scheduler-kubernetes:list-mount , List Mounted Volumes for an app 128 | help_content 129 | } 130 | 131 | cmd-scheduler-kubernetes-help() { 132 | if [[ $1 == "scheduler-kubernetes:help" ]]; then 133 | echo -e 'Usage: dokku scheduler-kubernetes[:COMMAND]' 134 | echo '' 135 | echo 'Manages the scheduler-kubernetes integration for an app.' 136 | echo '' 137 | echo 'Additional commands:' 138 | scheduler_docker_local_help_content_func | sort | column -c2 -t -s, 139 | echo '' 140 | elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then 141 | scheduler_docker_local_help_content_func 142 | else 143 | cat < /dev/null' RETURN INT TERM EXIT 154 | 155 | jq --argjson tls "$(sigil -f "$CERT_MANAGER_TLS_TEMPLATE")" '.spec.tls += $tls' <"$INGRESS_FILE" >"$TMP_FILE" 156 | mv "$TMP_FILE" "$INGRESS_FILE" 157 | 158 | for domain in ${CERT_MANAGER_DOMAINS[@]}; do 159 | DOMAIN="$domain" jq '.spec.tls[0].hosts += [env.DOMAIN]' <"$INGRESS_FILE" >"$TMP_FILE" 160 | mv "$TMP_FILE" "$INGRESS_FILE" 161 | done 162 | 163 | jq -M '.metadata.annotations["cert-manager.io/cluster-issuer"] = "letsencrypt-prod"' <"$INGRESS_FILE" >"$TMP_FILE" 164 | mv "$TMP_FILE" "$INGRESS_FILE" 165 | } 166 | 167 | fn-scheduler-kubernetes-add-ingress-rule() { 168 | declare APP="$1" PROC_TYPE="$2" PORT="$3" DOMAIN="$4" INGRESS_FILE="$5" 169 | local RULE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/ingress-rule.json.sigil" 170 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 171 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 172 | 173 | local SIGIL_PARAMS=(APP="$APP" PROCESS_TYPE="$PROC_TYPE" PORT="$PORT" DOMAIN="$DOMAIN") 174 | jq --argjson rule "$(sigil -f "$RULE_TEMPLATE" "${SIGIL_PARAMS[@]}")" '.spec.rules += [$rule]' <"$INGRESS_FILE" >"$TMP_FILE" 175 | mv "$TMP_FILE" "$INGRESS_FILE" 176 | } 177 | 178 | fn-scheduler-kubernetes-autoscale-in-use() { 179 | declare desc="checks if the horizontal pod autoscaler should be used for an app/proc-type combination" 180 | declare APP="$1" PROC_TYPE="$2" 181 | 182 | MAX_REPLICAS="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE.max-replicas" "")" 183 | MIN_REPLICAS="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE.min-replicas" "")" 184 | if [[ -z "$MAX_REPLICAS" ]] || [[ -z "$MIN_REPLICAS" ]]; then 185 | return 1 186 | fi 187 | 188 | RULES="$(fn-plugin-property-list-get "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE")" 189 | if [[ -z "$RULES" ]]; then 190 | return 1 191 | fi 192 | } 193 | 194 | fn-scheduler-kubernetes-autoscale-apply() { 195 | declare desc="applies horizontal pod autoscaler rules" 196 | declare APP="$1" PROC_TYPE="$2" 197 | 198 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 199 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 200 | KUBE_ARGS=() 201 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 202 | KUBE_ARGS+=("--namespace=$NAMESPACE") 203 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 204 | 205 | RULES="$(fn-plugin-property-list-get "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE")" 206 | if [[ -z "$RULES" ]]; then 207 | dokku_log_warn "No rules specified for $APP.$PROC_TYPE" 208 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" delete hpa -l app-process-type="$APP-$PROC_TYPE" 209 | return 210 | fi 211 | 212 | local INGRESS_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/horizontal-pod-autoscaler.json.sigil" 213 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 214 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 215 | 216 | MAX_REPLICAS="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE.max-replicas" "")" 217 | MIN_REPLICAS="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "autoscale.$PROC_TYPE.min-replicas" "")" 218 | if [[ -z "$MAX_REPLICAS" ]] || [[ -z "$MIN_REPLICAS" ]]; then 219 | dokku_log_warn "Must specify max-replicas and min-replicas for $APP.$PROC_TYPE" 220 | return 221 | fi 222 | 223 | local SIGIL_PARAMS=(APP="$APP" MAX_REPLICAS="$MAX_REPLICAS" MIN_REPLICAS="$MIN_REPLICAS" PROCESS_TYPE="$PROC_TYPE") 224 | sigil -f "$INGRESS_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$TMP_FILE" 225 | 226 | for RULE in ${RULES[@]}; do 227 | fn-scheduler-kubernetes-autoscale-add-hpa-rule "$RULE" "$TMP_FILE" 228 | done 229 | 230 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" apply -f "$TMP_FILE" | sed "s/^/ /" 231 | } 232 | 233 | fn-scheduler-kubernetes-autoscale-add-hpa-rule() { 234 | declare RULE="$1" HPA_FILE="$2" 235 | local METRIC_TYPE="$(echo "$RULE" | cut -d':' -f1)" 236 | local TARGET_NAME="$(echo "$RULE" | cut -d':' -f2)" 237 | local TARGET_TYPE="$(echo "$RULE" | cut -d':' -f3)" 238 | local TARGET_VALUE="$(echo "$RULE" | cut -d':' -f4)" 239 | local TARGET_EXTRA="$(echo "$RULE" | cut -d':' -f5)" 240 | 241 | local RULE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/horizontal-pod-autoscaler-metric-${METRIC_TYPE}.json.sigil" 242 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 243 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 244 | 245 | if [[ "$METRIC_TYPE" == "external" ]] && [[ -n "$TARGET_EXTRA" ]]; then 246 | RULE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/horizontal-pod-autoscaler-metric-${METRIC_TYPE}-selector.json.sigil" 247 | fi 248 | 249 | if [[ "$METRIC_TYPE" == "ingress" ]] && [[ -z "$TARGET_EXTRA" ]]; then 250 | TARGET_EXTRA="app-ingress" 251 | fi 252 | 253 | TARGET_TYPE_KEY="value" 254 | if [[ "$TARGET_TYPE" == "AverageValue" ]]; then 255 | TARGET_TYPE_KEY="averageValue" 256 | elif [[ "$TARGET_TYPE" == "Utilization" ]]; then 257 | TARGET_TYPE_KEY="averageUtilization" 258 | fi 259 | 260 | local SIGIL_PARAMS=(METRIC_TYPE="$METRIC_TYPE" TARGET_NAME="$TARGET_NAME" TARGET_TYPE="$TARGET_TYPE" TARGET_VALUE="$TARGET_VALUE" TARGET_EXTRA="$TARGET_EXTRA" TARGET_TYPE_KEY="$TARGET_TYPE_KEY") 261 | jq --argjson metric "$(sigil -f "$RULE_TEMPLATE" "${SIGIL_PARAMS[@]}")" '.spec.metrics += [$metric]' <"$HPA_FILE" >"$TMP_FILE" 262 | mv "$TMP_FILE" "$HPA_FILE" 263 | } 264 | 265 | fn-scheduler-kubernetes-autoscale-rule-validate() { 266 | declare desc="validate an autoscale rule" 267 | declare RULE="$1" 268 | local METRIC_TYPE TARGET_NAME TARGET_TYPE TARGET_VALUE 269 | 270 | METRIC_TYPE="$(echo "$RULE" | cut -d':' -f1)" 271 | TARGET_NAME="$(echo "$RULE" | cut -d':' -f2)" 272 | TARGET_TYPE="$(echo "$RULE" | cut -d':' -f3)" 273 | TARGET_VALUE="$(echo "$RULE" | cut -d':' -f4)" 274 | 275 | local VALID_METRIC_TYPES=("external" "ingress" "pods" "resource") 276 | if ! fn-in-array "$METRIC_TYPE" "${VALID_METRIC_TYPES[@]}"; then 277 | dokku_log_fail "Invalid metric type '${METRIC_TYPE}'" 278 | fi 279 | 280 | if [[ -z "$TARGET_NAME" ]]; then 281 | dokku_log_fail "Missing target name in rule" 282 | fi 283 | if [[ -z "$TARGET_TYPE" ]]; then 284 | dokku_log_fail "Missing target type in rule" 285 | fi 286 | if [[ -z "$TARGET_VALUE" ]]; then 287 | dokku_log_fail "Missing target value in rule" 288 | fi 289 | 290 | local VALID_TARGET_TYPES=() 291 | if [[ "$METRIC_TYPE" == "external" ]]; then 292 | VALID_TARGET_TYPES=("AverageValue" "Value") 293 | elif [[ "$METRIC_TYPE" == "ingress" ]]; then 294 | VALID_TARGET_TYPES=("AverageValue" "Value") 295 | elif [[ "$METRIC_TYPE" == "pods" ]]; then 296 | VALID_TARGET_TYPES=("AverageValue") 297 | elif [[ "$METRIC_TYPE" == "resource" ]]; then 298 | VALID_TARGET_TYPES=("AverageValue" "Utilization") 299 | local VALID_TARGET_NAMES=("cpu" "memory") 300 | if ! fn-in-array "$TARGET_NAME" "${VALID_TARGET_NAMES[@]}"; then 301 | dokku_log_fail "Invalid target name '${TARGET_NAME}', valid types: ${VALID_TARGET_NAMES[*]}" 302 | fi 303 | fi 304 | 305 | if ! fn-in-array "$TARGET_TYPE" "${VALID_TARGET_TYPES[@]}"; then 306 | dokku_log_fail "Invalid target type '${TARGET_TYPE}', valid types: ${VALID_TARGET_TYPES[*]}" 307 | fi 308 | } 309 | 310 | fn-scheduler-kubernetes-ingress-build-config() { 311 | declare desc="scheduler-kubernetes ingress-build-config" 312 | declare APP="$1" 313 | 314 | if [[ "$(plugn trigger proxy-type "$APP")" != "nginx-ingress" ]]; then 315 | return 316 | fi 317 | 318 | if [[ "$(is_app_vhost_enabled "$APP")" == "false" ]]; then 319 | dokku_log_info1 "VHOST support disabled" 320 | return 321 | fi 322 | 323 | if [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then 324 | domains_setup "$APP" 325 | fi 326 | 327 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 328 | local CERT_MANAGER_EMAIL="$(config_get --global CERT_MANAGER_EMAIL || true)" 329 | local CERT_MANAGER_DOMAINS=() 330 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 331 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 332 | 333 | if [[ -n "$CERT_MANAGER_EMAIL" ]]; then 334 | local CERTIFICATE_ISSUER_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/certificate-issuer.json.sigil" 335 | local SIGIL_PARAMS=(CERT_MANAGER_EMAIL="$CERT_MANAGER_EMAIL") 336 | sigil -f "$CERTIFICATE_ISSUER_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$TMP_FILE" 337 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" apply -f "$TMP_FILE" | sed "s/^/ /" 338 | fi 339 | 340 | local HAS_RULE=false 341 | local APP_NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 342 | local INGRESS_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/ingress.json.sigil" 343 | 344 | local SIGIL_PARAMS=() 345 | sigil -f "$INGRESS_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$TMP_FILE" 346 | 347 | for app in $(dokku_apps); do 348 | local DOKKU_VHOST_FILE="$DOKKU_ROOT/$app/VHOST" 349 | local namespace="$(fn-plugin-property-get "scheduler-kubernetes" "$app" "namespace" "default")" 350 | local cert_manager_enabled="$(fn-plugin-property-get "scheduler-kubernetes" "$app" "cert-manager-enabled" "")" 351 | 352 | if [[ "$APP_NAMESPACE" != "$namespace" ]]; then 353 | continue 354 | fi 355 | 356 | if [[ ! -f "$DOKKU_VHOST_FILE" ]]; then 357 | continue 358 | fi 359 | 360 | if [[ "$(plugn trigger proxy-type "$app")" != "nginx-ingress" ]]; then 361 | continue 362 | fi 363 | 364 | if [[ "$(is_app_vhost_enabled "$app")" == "false" ]]; then 365 | continue 366 | fi 367 | 368 | while read -r line || [[ -n "$line" ]]; do 369 | [[ "$line" =~ ^#.* ]] && continue 370 | line="$(strip_inline_comments "$line")" 371 | PROC_TYPE=${line%%=*} 372 | PROC_COUNT=${line#*=} 373 | 374 | if [[ "$PROC_TYPE" != "web" ]]; then 375 | continue 376 | fi 377 | 378 | while read -r DOMAIN || [[ -n "$DOMAIN" ]]; do 379 | if [[ "$cert_manager_enabled" == "true" ]]; then 380 | CERT_MANAGER_DOMAINS+=("$DOMAIN") 381 | fi 382 | 383 | HAS_RULE=true 384 | fn-scheduler-kubernetes-add-ingress-rule "$app" "$PROC_TYPE" "5000" "$DOMAIN" "$TMP_FILE" 385 | done <"$DOKKU_VHOST_FILE" 386 | done < <(plugn trigger ps-current-scale "$app") 387 | done 388 | 389 | if [[ "$HAS_RULE" == "false" ]]; then 390 | return 391 | fi 392 | 393 | local KUBE_ARGS=() 394 | KUBE_ARGS+=("--namespace=$APP_NAMESPACE") 395 | fn-scheduler-kubernetes-ensure-namespace "$APP_NAMESPACE" >/dev/null 396 | 397 | local SCHEME=http 398 | if [[ -n "$CERT_MANAGER_EMAIL" ]] && [[ -n "$CERT_MANAGER_DOMAINS" ]]; then 399 | local SCHEME=https 400 | fn-scheduler-kubernetes-add-cert-manager "$TMP_FILE" "${CERT_MANAGER_DOMAINS[@]}" 401 | fi 402 | 403 | fn-set-ingress-annotations "$APP_NAMESPACE" "$TMP_FILE" 404 | plugn trigger pre-kubernetes-ingress-apply "$APP" "$TMP_FILE" 405 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" apply -f "$TMP_FILE" | sed "s/^/ /" 406 | 407 | local URLS_PATH="$DOKKU_ROOT/$APP/URLS" 408 | local NONSSL_VHOSTS=$(get_app_domains "$APP") 409 | if [[ -n "$NONSSL_VHOSTS" ]]; then 410 | echo "# THIS FILE IS GENERATED BY DOKKU - DO NOT EDIT, YOUR CHANGES WILL BE OVERWRITTEN" >"$URLS_PATH" 411 | xargs -i echo "$SCHEME://{}" <<<"$(echo "${NONSSL_VHOSTS}" | tr ' ' '\n' | sort -u)" >>"$URLS_PATH" 412 | fi 413 | } 414 | 415 | fn-scheduler-kubernetes-ensure-namespace() { 416 | declare NAMESPACE="$1" 417 | if [[ "$NAMESPACE" = "default" ]]; then 418 | return 419 | fi 420 | 421 | local NAMESPACE_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/namespace.json.sigil" 422 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 423 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 424 | 425 | SIGIL_PARAMS=(NAME="$NAMESPACE") 426 | sigil -f "$NAMESPACE_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$TMP_FILE" 427 | 428 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" apply -f "$TMP_FILE" | sed "s/^/ /" 429 | } 430 | 431 | fn-set-deployment-annotations() { 432 | declare APP="$1" DEPLOYMENT_FILE="$2" 433 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 434 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 435 | 436 | ANNOTATION_COUNT="$(fn-plugin-property-list-length "scheduler-kubernetes" "$APP" "deployment-annotations")" 437 | if [[ "$ANNOTATION_COUNT" == 0 ]]; then 438 | return 439 | fi 440 | 441 | jq -M --argjson data "{}" '.metadata.annotations += $data' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 442 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 443 | 444 | while IFS="" read -r p || [ -n "$p" ]; do 445 | local KEY="$(echo "$p" | cut -d' ' -f1)" 446 | local VALUE="$(echo "$p" | cut -d' ' -f2)" 447 | KEY="$KEY" VALUE="$VALUE" jq -M '.metadata.annotations[env.KEY] = env.VALUE' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 448 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 449 | done < <(fn-plugin-property-list-get "scheduler-kubernetes" "$APP" "deployment-annotations") 450 | } 451 | 452 | fn-set-env-vars() { 453 | declare APP="$1" DEPLOYMENT_FILE="$2" 454 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 455 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 456 | 457 | jq -M --argjson data "$(config_export app "$APP" --format json-list --merged)" '.spec.template.spec.containers[0].env += $data' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 458 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 459 | } 460 | 461 | fn-set-healthchecks() { 462 | declare APP="$1" PROC_TYPE="$2" DEPLOYMENT_FILE="$3" 463 | local TMP_DIR=$(mktemp -d "/tmp/${FUNCNAME[0]}.XXXX") 464 | local APP_JSON_TMPL="$TMP_DIR/app.json.sigil" 465 | local APP_JSON_FILE="$TMP_DIR/app.json" 466 | local PROBE_FILE="$TMP_DIR/probe.json" 467 | local DEPLOYMENT_TMP_FILE="$TMP_DIR/deployment.json" 468 | trap 'rm -rf "$TMP_DIR" > /dev/null' RETURN INT TERM EXIT 469 | 470 | copy_from_image "$IMAGE" "app.json" "$APP_JSON_TMPL" 2>/dev/null || true 471 | 472 | if [[ ! -f "$APP_JSON_TMPL" ]]; then 473 | return 0 474 | fi 475 | 476 | # We use sigil templating to allow the health check configurations to refer 477 | # to the app name with the variable $APP. 478 | local SIGIL_PARAMS=(APP="$APP") 479 | sigil -f "$APP_JSON_TMPL" "${SIGIL_PARAMS[@]}" | cat -s >"$APP_JSON_FILE" 480 | 481 | for PROBE_TYPE in liveness readiness; do 482 | if jq -e -M ".healthchecks.\"$PROC_TYPE\".\"$PROBE_TYPE\"" <"$APP_JSON_FILE" >"$PROBE_FILE" \ 483 | || jq -e -M ".healthchecks.\"*\".\"$PROBE_TYPE\"" <"$APP_JSON_FILE" >"$PROBE_FILE"; then 484 | jq -M --argjson data "$(cat "$PROBE_FILE")" ".spec.template.spec.containers[0].${PROBE_TYPE}Probe += \$data" <"$DEPLOYMENT_FILE" >"$DEPLOYMENT_TMP_FILE" 485 | mv "$DEPLOYMENT_TMP_FILE" "$DEPLOYMENT_FILE" 486 | fi 487 | done 488 | } 489 | 490 | fn-set-image-pull-secrets() { 491 | declare IMAGE_PULL_SECRETS="$1" DEPLOYMENT_FILE="$2" 492 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 493 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 494 | 495 | if [[ -n "$IMAGE_PULL_SECRETS" ]]; then 496 | IMAGE_PULL_SECRETS="$IMAGE_PULL_SECRETS" jq -M ".spec.template.spec.imagePullSecrets[0].name = env.IMAGE_PULL_SECRETS" <"$DEPLOYMENT_FILE" >"$TMP_FILE" 497 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 498 | fi 499 | } 500 | 501 | fn-set-pod-annotations() { 502 | declare APP="$1" DEPLOYMENT_FILE="$2" 503 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 504 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 505 | 506 | ANNOTATION_COUNT="$(fn-plugin-property-list-length "scheduler-kubernetes" "$APP" "pod-annotations")" 507 | if [[ "$ANNOTATION_COUNT" == 0 ]]; then 508 | return 509 | fi 510 | 511 | jq -M --argjson data "{}" '.spec.template.metadata.annotations += $data' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 512 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 513 | 514 | while IFS="" read -r p || [ -n "$p" ]; do 515 | local KEY="$(echo "$p" | cut -d' ' -f1)" 516 | local VALUE="$(echo "$p" | cut -d' ' -f2)" 517 | KEY="$KEY" VALUE="$VALUE" jq -M '.spec.template.metadata.annotations[env.KEY] = env.VALUE' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 518 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 519 | done < <(fn-plugin-property-list-get "scheduler-kubernetes" "$APP" "pod-annotations") 520 | } 521 | 522 | fn-set-ports() { 523 | declare APP="$1" DEPLOYMENT_FILE="$2" 524 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 525 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 526 | 527 | if is_image_herokuish_based "$IMAGE"; then 528 | jq -M --argjson data "[{\"name\": \"PORT\", \"value\": \"5000\"}]" '.spec.template.spec.containers[0].env += $data' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 529 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 530 | fi 531 | } 532 | 533 | fn-set-pod-disruption-constraints() { 534 | declare APP="$1" 535 | local POD_TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 536 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 537 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 538 | 539 | MIN_AVAILABLE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "pod-min-available" "")" 540 | MAX_UNAVAILABLE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "pod-max-unavailable" "")" 541 | 542 | if [[ -z "$MIN_AVAILABLE" ]] && [[ -z "$MAX_UNAVAILABLE" ]]; then 543 | return 544 | fi 545 | 546 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 547 | KUBE_ARGS=() 548 | NAMESPACE="$(fn-plugin-property-get "scheduler-kubernetes" "$APP" "namespace" "default")" 549 | KUBE_ARGS+=("--namespace=$NAMESPACE") 550 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 551 | 552 | local SIGIL_PARAMS=(APP="$APP") 553 | sigil -f "$DEPLOYMENT_TEMPLATE" "${SIGIL_PARAMS[@]}" | cat -s >"$POD_TMP_FILE" 554 | 555 | if [[ -n "$MIN_AVAILABLE" ]]; then 556 | MIN_AVAILABLE="$MIN_AVAILABLE" jq -M '.spec.minAvailable = (env.MIN_AVAILABLE|tonumber)' <"$POD_TMP_FILE" >"$TMP_FILE" 557 | mv "$TMP_FILE" "$POD_TMP_FILE" 558 | fi 559 | 560 | if [[ -n "$MAX_UNAVAILABLE" ]]; then 561 | MAX_UNAVAILABLE="$MAX_UNAVAILABLE" jq -M '.spec.maxUnavailable = (env.MAX_UNAVAILABLE|tonumber)' <"$POD_TMP_FILE" >"$TMP_FILE" 562 | mv "$TMP_FILE" "$POD_TMP_FILE" 563 | fi 564 | 565 | dokku_log_info1 "Creating PodDisruptionBudget for ${APP}" 566 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" apply -f "$POD_TMP_FILE" | sed "s/^/ /" 567 | } 568 | 569 | fn-set-resource-constraints() { 570 | declare APP="$1" PROC_TYPE="$2" DEPLOYMENT_FILE="$3" 571 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 572 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 573 | 574 | RESOURCE_LIMITS_CPU=$(plugn trigger resource-get-property "$APP" "$PROC_TYPE" "limit" "cpu" 2>/dev/null || true) 575 | RESOURCE_LIMITS_NVIDIA_GPU=$(plugn trigger resource-get-property "$APP" "$PROC_TYPE" "limit" "nvidia-gpu" 2>/dev/null || true) 576 | RESOURCE_LIMITS_MEMORY=$(plugn trigger resource-get-property "$APP" "$PROC_TYPE" "limit" "memory" 2>/dev/null || true) 577 | RESOURCE_REQUESTS_CPU=$(plugn trigger resource-get-property "$APP" "$PROC_TYPE" "reserve" "cpu" 2>/dev/null || true) 578 | RESOURCE_REQUESTS_MEMORY=$(plugn trigger resource-get-property "$APP" "$PROC_TYPE" "reserve" "memory" 2>/dev/null || true) 579 | 580 | if [[ -n "$RESOURCE_LIMITS_CPU" ]]; then 581 | RESOURCE_LIMITS_CPU="$RESOURCE_LIMITS_CPU" jq -M ".spec.template.spec.containers[0].resources.limits.cpu = env.RESOURCE_LIMITS_CPU" <"$DEPLOYMENT_FILE" >"$TMP_FILE" 582 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 583 | fi 584 | 585 | if [[ -n "$RESOURCE_LIMITS_NVIDIA_GPU" ]] && [[ "$RESOURCE_LIMITS_NVIDIA_GPU" != "0" ]]; then 586 | re='^[0-9]+$' 587 | if [[ "$RESOURCE_LIMITS_NVIDIA_GPU" =~ $re ]]; then 588 | RESOURCE_LIMITS_NVIDIA_GPU="$RESOURCE_LIMITS_NVIDIA_GPU" jq -M '.spec.template.spec.containers[0].resources.limits["nvidia.com/gpu"] = (env.RESOURCE_LIMITS_NVIDIA_GPU|tonumber)' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 589 | else 590 | RESOURCE_LIMITS_NVIDIA_GPU="$RESOURCE_LIMITS_NVIDIA_GPU" jq -M '.spec.template.spec.containers[0].resources.limits["nvidia.com/gpu"] = env.RESOURCE_LIMITS_NVIDIA_GPU' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 591 | fi 592 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 593 | fi 594 | 595 | if [[ -n "$RESOURCE_LIMITS_MEMORY" ]]; then 596 | if [[ "$RESOURCE_LIMITS_MEMORY" =~ ^[0-9]+$ ]]; then 597 | RESOURCE_LIMITS_MEMORY="${RESOURCE_LIMITS_MEMORY}Mi" 598 | fi 599 | RESOURCE_LIMITS_MEMORY="$RESOURCE_LIMITS_MEMORY" jq -M ".spec.template.spec.containers[0].resources.limits.memory = env.RESOURCE_LIMITS_MEMORY" <"$DEPLOYMENT_FILE" >"$TMP_FILE" 600 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 601 | fi 602 | 603 | if [[ -n "$RESOURCE_REQUESTS_CPU" ]]; then 604 | RESOURCE_REQUESTS_CPU="$RESOURCE_REQUESTS_CPU" jq -M ".spec.template.spec.containers[0].resources.requests.cpu = env.RESOURCE_REQUESTS_CPU" <"$DEPLOYMENT_FILE" >"$TMP_FILE" 605 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 606 | fi 607 | 608 | if [[ -n "$RESOURCE_REQUESTS_MEMORY" ]]; then 609 | if [[ "$RESOURCE_REQUESTS_MEMORY" =~ ^[0-9]+$ ]]; then 610 | RESOURCE_REQUESTS_MEMORY="${RESOURCE_REQUESTS_MEMORY}Mi" 611 | fi 612 | RESOURCE_REQUESTS_MEMORY="$RESOURCE_REQUESTS_MEMORY" jq -M ".spec.template.spec.containers[0].resources.requests.memory = env.RESOURCE_REQUESTS_MEMORY" <"$DEPLOYMENT_FILE" >"$TMP_FILE" 613 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 614 | fi 615 | } 616 | 617 | fn-set-service-annotations() { 618 | declare APP="$1" DEPLOYMENT_FILE="$2" 619 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 620 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 621 | 622 | ANNOTATION_COUNT="$(fn-plugin-property-list-length "scheduler-kubernetes" "$APP" "service-annotations")" 623 | if [[ "$ANNOTATION_COUNT" == 0 ]]; then 624 | return 625 | fi 626 | 627 | jq -M --argjson data "{}" '.metadata.annotations += $data' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 628 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 629 | 630 | while IFS="" read -r p || [ -n "$p" ]; do 631 | local KEY="$(echo "$p" | cut -d' ' -f1)" 632 | local VALUE="$(echo "$p" | cut -d' ' -f2)" 633 | KEY="$KEY" VALUE="$VALUE" jq -M '.metadata.annotations[env.KEY] = env.VALUE' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 634 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 635 | done < <(fn-plugin-property-list-get "scheduler-kubernetes" "$APP" "service-annotations") 636 | } 637 | 638 | fn-strip-ports() { 639 | declare DEPLOYMENT_FILE="$1" 640 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 641 | 642 | jq -M 'del(.spec.template.spec.containers[0].ports)' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 643 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 644 | } 645 | 646 | fn-set-ingress-annotations() { 647 | declare APP_NAMESPACE="$1" INGRESS_FILE="$2" 648 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 649 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 650 | 651 | ANNOTATION_COUNT="$(fn-plugin-property-list-length "scheduler-kubernetes" "$APP_NAMESPACE" "ingress-annotations")" 652 | if [[ "$ANNOTATION_COUNT" == 0 ]]; then 653 | return 654 | fi 655 | 656 | jq -M --argjson data "{}" '.metadata.annotations += $data' <"$INGRESS_FILE" >"$TMP_FILE" 657 | mv "$TMP_FILE" "$INGRESS_FILE" 658 | 659 | while IFS="" read -r p || [ -n "$p" ]; do 660 | local KEY="$(echo "$p" | cut -d' ' -f1)" 661 | local VALUE="$(echo "$p" | cut -d' ' -f2)" 662 | KEY="$KEY" VALUE="$VALUE" jq -M '.metadata.annotations[env.KEY] = env.VALUE' <"$INGRESS_FILE" >"$TMP_FILE" 663 | mv "$TMP_FILE" "$INGRESS_FILE" 664 | done < <(fn-plugin-property-list-get "scheduler-kubernetes" "$APP_NAMESPACE" "ingress-annotations") 665 | } 666 | 667 | fn-scheduler-kubernetes-add-pvc() { 668 | declare desc="add persistent volume claim" 669 | declare NAME="$1" ACCESS_MODE="$2" STORAGE="$3" STORAGE_CLASS_NAME="$4" NAMESPACE="$5" 670 | 671 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 672 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 673 | KUBE_ARGS=() 674 | if [[ -n "$NAMESPACE" ]]; then 675 | KUBE_ARGS+=("--namespace=$NAMESPACE") 676 | fn-scheduler-kubernetes-ensure-namespace "$NAMESPACE" >/dev/null 677 | fi 678 | 679 | local PVC_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/pvc.json.sigil" 680 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 681 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 682 | 683 | if [[ "$STORAGE_CLASS_NAME" != "" ]]; then 684 | STORAGE_CLASS_NAME="$STORAGE_CLASS_NAME" jq -M '.spec.storageClassName = env.STORAGE_CLASS_NAME' <"$PVC_TEMPLATE" >"$TMP_FILE" 685 | else 686 | cp "$PVC_TEMPLATE" "$TMP_FILE" 687 | fi 688 | SIGIL_PARAMS=(NAME="$NAME" ACCESS_MODE="$ACCESS_MODE" STORAGE="$STORAGE") 689 | sigil -f "$TMP_FILE" "${SIGIL_PARAMS[@]}" | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" apply -f - | sed "s/^/ /" 690 | } 691 | 692 | cmd-scheduler-kubernetes-list-pvc() { 693 | declare desc="list persistent volume claims per namesapce" 694 | declare NAMESPACE="$1" 695 | 696 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 697 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 698 | KUBE_ARGS=() 699 | if [[ -n "$NAMESPACE" ]]; then 700 | KUBE_ARGS+=("--namespace=$NAMESPACE") 701 | fi 702 | 703 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" get pvc 704 | } 705 | 706 | fn-scheduler-kubernetes-remove-pvc() { 707 | declare desc="remove persistent volume claim from namesapce" 708 | declare NAME="$1" NAMESPACE="$2" 709 | 710 | export KUBECONFIG="${DOKKU_ROOT}/.kube/config" 711 | export KUBEDOG_KUBE_CONFIG="${DOKKU_ROOT}/.kube/config" 712 | KUBE_ARGS=() 713 | if [[ -n "$NAMESPACE" ]]; then 714 | KUBE_ARGS+=("--namespace=$NAMESPACE") 715 | fi 716 | 717 | "${DOKKU_LIB_ROOT}/data/scheduler-kubernetes/kubectl" "${KUBE_ARGS[@]}" delete pvc "$NAME" 718 | } 719 | 720 | fn-set-command-and-args() { 721 | declare desc="set the command and args" 722 | declare APP="$1" PROC_TYPE="$2" IMAGE_SOURCE_TYPE="$3" DEPLOYMENT_FILE="$4" 723 | 724 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 725 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 726 | 727 | DOKKU_HEROKUISH=false 728 | [[ "$IMAGE_SOURCE_TYPE" == "herokuish" ]] && DOKKU_HEROKUISH=true 729 | 730 | local command_with_args="$(fn-scheduler-kubernetes-extract-start-cmd "$APP" "$PROC_TYPE" "$DOKKU_HEROKUISH" 5000)" 731 | if [[ -z "$command_with_args" ]]; then 732 | dokku_log_fail "No $PROC_TYPE command detected for app" 733 | return 734 | fi 735 | 736 | COMMAND="" 737 | ARGS=() 738 | i=0 739 | for word in ${command_with_args[@]}; do 740 | [[ "$i" -eq 0 ]] && COMMAND="$word" 741 | [[ "$i" -ne 0 ]] && ARGS+=("$word") 742 | i=$((i + 1)) 743 | done 744 | 745 | COMMAND="$COMMAND" jq -M ".spec.template.spec.containers[0].command = [env.COMMAND]" <"$DEPLOYMENT_FILE" >"$TMP_FILE" 746 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 747 | jq -M ".spec.template.spec.containers[0].args = []" <"$DEPLOYMENT_FILE" >"$TMP_FILE" 748 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 749 | 750 | for ARG in "${ARGS[@]}"; do 751 | ARG="$ARG" jq -M ".spec.template.spec.containers[0].args += [env.ARG]" <"$DEPLOYMENT_FILE" >"$TMP_FILE" 752 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 753 | done 754 | } 755 | 756 | fn-scheduler-kubernetes-extract-start-cmd() { 757 | declare APP="$1" PROC_TYPE="$2" DOKKU_HEROKUISH="$3" PORT="$4" 758 | local DOKKU_DOCKERFILE_START_CMD DOKKU_PROCFILE_START_CMD START_CMD 759 | 760 | local START_CMD 761 | [[ "$DOKKU_HEROKUISH" == "true" ]] && START_CMD="/start $PROC_TYPE" 762 | DOKKU_START_CMD=$(config_get "$APP" DOKKU_START_CMD || true) 763 | [[ -n "$DOKKU_START_CMD" ]] && START_CMD="$DOKKU_START_CMD" 764 | 765 | if [[ "$DOKKU_HEROKUISH" != "false" ]]; then 766 | echo "$START_CMD" 767 | return 768 | fi 769 | 770 | DOKKU_DOCKERFILE_START_CMD=$(config_get "$APP" DOKKU_DOCKERFILE_START_CMD || true) 771 | DOKKU_PROCFILE_START_CMD=$(plugn trigger procfile-get-command "$APP" "$PROC_TYPE" "$PORT") 772 | START_CMD=${DOKKU_DOCKERFILE_START_CMD:-$DOKKU_PROCFILE_START_CMD} 773 | echo "$START_CMD" 774 | } 775 | 776 | fn-set-mount() { 777 | declare desc="mount volumes on path" 778 | declare APP="$1" DEPLOYMENT_FILE="$2" 779 | 780 | VOLUME_COUNT="$(fn-plugin-property-list-length "scheduler-kubernetes" "$APP" "volumes")" 781 | if [[ "$VOLUME_COUNT" == 0 ]]; then 782 | return 783 | fi 784 | local VOLUMES_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/volumes.json.sigil" 785 | local VOLUME_MOUNTS_TEMPLATE="$PLUGIN_AVAILABLE_PATH/scheduler-kubernetes/templates/volume-mounts.json.sigil" 786 | 787 | local TMP_FILE=$(mktemp "/tmp/${FUNCNAME[0]}.XXXX") 788 | trap 'rm -rf "$TMP_FILE" > /dev/null' RETURN INT TERM EXIT 789 | 790 | VOLUMES="$(fn-plugin-property-list-get "scheduler-kubernetes" "$APP" "volumes")" 791 | for VOLUME_ENTRY in ${VOLUMES[@]}; do 792 | local CLAIM_NAME=$(echo "$VOLUME_ENTRY" | cut -d':' -f1) 793 | local MOUNT_PATH=$(echo "$VOLUME_ENTRY" | cut -d':' -f2) 794 | 795 | local SIGIL_PARAMS=(VOLUME_NAME="$CLAIM_NAME" CLAIM_NAME="$CLAIM_NAME") 796 | jq --argjson volume "$(sigil -f "$VOLUMES_TEMPLATE" "${SIGIL_PARAMS[@]}")" '.spec.template.spec.volumes += [$volume]' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 797 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 798 | 799 | local SIGIL_PARAMS=(VOLUME_NAME="$CLAIM_NAME" MOUNT_PATH="$MOUNT_PATH") 800 | jq --argjson mount "$(sigil -f "$VOLUME_MOUNTS_TEMPLATE" "${SIGIL_PARAMS[@]}")" '.spec.template.spec.containers[0].volumeMounts += [$mount]' <"$DEPLOYMENT_FILE" >"$TMP_FILE" 801 | mv "$TMP_FILE" "$DEPLOYMENT_FILE" 802 | done 803 | } 804 | --------------------------------------------------------------------------------