From 650be935033a71a50e5da965a045385d9c392e6f Mon Sep 17 00:00:00 2001 From: poprhythm Date: Sun, 1 Mar 2026 01:02:49 +0000 Subject: [PATCH] Add get-env/set-env commands to portainer.sh; fix redeploy env var wipe - get-env: display env vars for a named stack - set-env: merge KEY=VALUE pairs into a stack's env vars and redeploy (uses git/redeploy endpoint with pullImage:false for git-linked stacks) - redeploy: now preserves existing env vars by including them in the git/redeploy payload (previously wiped them when env was omitted) --- portainer.sh | 154 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 145 insertions(+), 9 deletions(-) diff --git a/portainer.sh b/portainer.sh index 448eb75..c06edea 100755 --- a/portainer.sh +++ b/portainer.sh @@ -4,6 +4,8 @@ # ./portainer.sh list # ./portainer.sh redeploy # ./portainer.sh deploy +# ./portainer.sh get-env +# ./portainer.sh set-env KEY=VALUE [KEY=VALUE ...] set -euo pipefail @@ -54,6 +56,19 @@ print(s['Id']) " } +get_stack_json_by_name() { + local name="$1" + api_get "stacks?filters=%7B%22EndpointID%22%3A${ENDPOINT_ID}%7D" \ + | python3 -c " +import json, sys +stacks = json.load(sys.stdin) +matches = [s for s in stacks if s['Name'] == '$name'] +if not matches: + sys.exit(1) +print(json.dumps(matches[0])) +" +} + # -------------------------------------------------------------------------- # Commands # -------------------------------------------------------------------------- @@ -82,27 +97,37 @@ cmd_redeploy() { fi echo "Looking up stack '$name'..." - local stack_id - if ! stack_id=$(get_stack_by_name "$name"); then + local stack_json + if ! stack_json=$(get_stack_json_by_name "$name"); then echo "Error: stack '$name' not found" >&2 exit 1 fi + local stack_id + stack_id=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['Id'])" "$stack_json") echo "Found stack ID: $stack_id" + # Preserve existing env vars — git/redeploy clears them if env is omitted + local payload + payload=$(python3 -c " +import json, sys +stack = json.loads(sys.argv[1]) +env = stack.get('Env') or [] +print(json.dumps({'pullImage': True, 'prune': False, 'env': env})) +" "$stack_json") + echo "Redeploying..." local response - response=$(api_put "stacks/${stack_id}/git/redeploy?endpointId=${ENDPOINT_ID}" \ - '{"pullImage": true, "prune": false}') + response=$(api_put "stacks/${stack_id}/git/redeploy?endpointId=${ENDPOINT_ID}" "$payload") python3 -c " import json, sys -d = json.load(sys.stdin) +d = json.loads(sys.argv[1]) if 'message' in d and 'Id' not in d: print('Error:', d['message']) sys.exit(1) hash = d.get('GitConfig', {}).get('ConfigHash', 'unknown')[:10] print(f'Done. ConfigHash: {hash}') -" <<< "$response" +" "$response" } cmd_deploy() { @@ -142,6 +167,113 @@ print(f\"Done. Stack ID: {d['Id']}, Name: {d['Name']}\") " <<< "$response" } +cmd_get_env() { + local name="${1:-}" + if [[ -z "$name" ]]; then + echo "Usage: $0 get-env " >&2 + exit 1 + fi + + echo "Looking up stack '$name'..." + local stack_json + if ! stack_json=$(get_stack_json_by_name "$name"); then + echo "Error: stack '$name' not found" >&2 + exit 1 + fi + + python3 -c " +import json, sys +s = json.loads(sys.argv[1]) +env = s.get('Env') or [] +if not env: + print('(no env vars set)') +else: + for e in sorted(env, key=lambda x: x['name']): + print(f\"{e['name']}={e['value']}\") +" "$stack_json" +} + +cmd_set_env() { + local name="${1:-}" + shift || true + if [[ -z "$name" || $# -eq 0 ]]; then + echo "Usage: $0 set-env KEY=VALUE [KEY=VALUE ...]" >&2 + echo " Example: $0 set-env authelia SECRET_KEY=abc123 OTHER_KEY=xyz" >&2 + exit 1 + fi + + echo "Looking up stack '$name'..." + local stack_json + if ! stack_json=$(get_stack_json_by_name "$name"); then + echo "Error: stack '$name' not found" >&2 + exit 1 + fi + + local stack_id + stack_id=$(python3 -c "import json,sys; print(json.loads(sys.argv[1])['Id'])" "$stack_json") + echo "Found stack ID: $stack_id" + + local kvs=("$@") + local payload + payload=$(python3 -c " +import json, sys + +stack = json.loads(sys.argv[1]) +new_kvs = sys.argv[2:] + +# Merge env vars: preserve existing, override/add new +env_dict = {e['name']: e['value'] for e in (stack.get('Env') or [])} +for kv in new_kvs: + eq = kv.index('=') + env_dict[kv[:eq]] = kv[eq+1:] +env_list = [{'name': k, 'value': v} for k, v in env_dict.items()] + +git = stack.get('GitConfig') or {} +if git: + # Use git/redeploy endpoint (pullImage:false = redeploy with existing images only) + p = {'pullImage': False, 'prune': False, 'env': env_list} +else: + print('Error: string-based stacks are not supported; update env vars via Portainer UI.', file=sys.stderr) + sys.exit(2) + +print(json.dumps(p)) +" "$stack_json" "${kvs[@]}") + + echo "Updating env vars (redeploys with existing images)..." + local endpoint + local stack_json_check + stack_json_check=$(python3 -c " +import json, sys +stack = json.loads(sys.argv[1]) +git = stack.get('GitConfig') or {} +print('git' if git else 'string') +" "$stack_json") + + local response + if [[ "$stack_json_check" == "git" ]]; then + response=$(api_put "stacks/${stack_id}/git/redeploy?endpointId=${ENDPOINT_ID}" "$payload") + else + echo "Error: string-based stacks are not supported; update env vars via Portainer UI." >&2 + exit 2 + fi + + python3 -c " +import json, sys +try: + d = json.loads(sys.argv[1]) +except Exception: + print('Failed to parse response:', sys.argv[1][:300]) + sys.exit(1) +if 'message' in d and 'Id' not in d: + print('Error:', d['message']) + if 'details' in d: + print('Details:', d['details']) + sys.exit(1) +updated = len(d.get('Env') or []) +print(f'Done. Stack has {updated} env var(s) set.') +" "$response" +} + # -------------------------------------------------------------------------- # Dispatch # -------------------------------------------------------------------------- @@ -151,13 +283,17 @@ case "$command" in list) cmd_list ;; redeploy) cmd_redeploy "${2:-}" ;; deploy) cmd_deploy "${2:-}" "${3:-}" ;; + get-env) cmd_get_env "${2:-}" ;; + set-env) cmd_set_env "${2:-}" "${@:3}" ;; *) echo "Usage: $0 [args]" echo "" echo "Commands:" - echo " list List all stacks" - echo " redeploy Pull latest git commit and redeploy" - echo " deploy Create new git-linked stack" + echo " list List all stacks" + echo " redeploy Pull latest git commit and redeploy" + echo " deploy Create new git-linked stack" + echo " get-env Show env vars for a stack" + echo " set-env KEY=VAL [...] Set env vars (redeploys without new image pull)" exit 1 ;; esac