Add stack-deploy.sh + envparse.py: safe env loading via Python for all stacks
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
+2
-2
@@ -168,14 +168,14 @@ steps:
|
|||||||
- |
|
- |
|
||||||
YAML_FILES=$(echo "${CI_COMMIT_CHANGED_FILES}" | tr ',' '\n' | grep '\.yaml$' || true)
|
YAML_FILES=$(echo "${CI_COMMIT_CHANGED_FILES}" | tr ',' '\n' | grep '\.yaml$' || true)
|
||||||
[ -z "$YAML_FILES" ] && echo "No yaml files changed" && exit 0
|
[ -z "$YAML_FILES" ] && echo "No yaml files changed" && exit 0
|
||||||
rsync -av -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" deploy/safe-deploy.sh root@192.168.4.32:/tmp/sd.sh
|
rsync -av -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" deploy/ root@192.168.4.32:/volume1/docker/compose-files/deploy/
|
||||||
for f in $YAML_FILES; do
|
for f in $YAML_FILES; do
|
||||||
[ -f "$f" ] || continue
|
[ -f "$f" ] || continue
|
||||||
STACK=$(basename "$f" .yaml)
|
STACK=$(basename "$f" .yaml)
|
||||||
rsync -av -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" \
|
rsync -av -e "ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa" \
|
||||||
"$f" root@192.168.4.32:/volume1/docker/compose-files/
|
"$f" root@192.168.4.32:/volume1/docker/compose-files/
|
||||||
ssh -o StrictHostKeyChecking=no root@192.168.4.32 \
|
ssh -o StrictHostKeyChecking=no root@192.168.4.32 \
|
||||||
"python3 /tmp/sd.sh ${STACK}" \
|
"bash /volume1/docker/compose-files/deploy/stack-deploy.sh $STACK" \
|
||||||
&& echo " OK $STACK" || { echo " FAIL $STACK"; exit 1; }
|
&& echo " OK $STACK" || { echo " FAIL $STACK"; exit 1; }
|
||||||
done
|
done
|
||||||
secrets: [ ssh_key ]
|
secrets: [ ssh_key ]
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import sys, re
|
||||||
|
|
||||||
|
def parse_env(path):
|
||||||
|
pairs = []
|
||||||
|
with open(path) as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#') or '=' not in line:
|
||||||
|
continue
|
||||||
|
k, _, v = line.partition('=')
|
||||||
|
k = k.strip(); v = v.strip()
|
||||||
|
if (v.startswith("'") and v.endswith("'")) or \
|
||||||
|
(v.startswith('"') and v.endswith('"')):
|
||||||
|
v = v[1:-1]
|
||||||
|
pairs.append((k, v))
|
||||||
|
return pairs
|
||||||
|
|
||||||
|
mode = sys.argv[1]
|
||||||
|
if mode == 'export':
|
||||||
|
for k, v in parse_env(sys.argv[2]):
|
||||||
|
print('export {}={}'.format(k, repr(v)))
|
||||||
|
elif mode == 'vars':
|
||||||
|
print(' '.join('$' + k for k, v in parse_env(sys.argv[2])))
|
||||||
|
elif mode == 'strip':
|
||||||
|
t = sys.stdin.read()
|
||||||
|
t = re.sub(r'[ \t]*env_file:[ \t]*\n([ \t]+-[^\n]*\n)+', '', t)
|
||||||
|
sys.stdout.write(t)
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# safe-deploy.sh — Safe Docker Stack deployer with .env support
|
|
||||||
# Usage: python3 deploy/safe-deploy.sh <stack-name> [compose-dir]
|
|
||||||
# Parses .env safely — no shell interpretation of special chars
|
|
||||||
import subprocess, sys, os
|
|
||||||
|
|
||||||
def parse_env_file(path):
|
|
||||||
env = {}
|
|
||||||
with open(path) as f:
|
|
||||||
for line in f:
|
|
||||||
line = line.rstrip('\n')
|
|
||||||
if not line.strip() or line.strip().startswith('#'): continue
|
|
||||||
if '=' not in line: continue
|
|
||||||
key, _, value = line.partition('=')
|
|
||||||
key = key.strip(); value = value.strip()
|
|
||||||
if len(value) >= 2:
|
|
||||||
if (value[0]=="'" and value[-1]=="'") or (value[0]=='"' and value[-1]=='"'):
|
|
||||||
value = value[1:-1]
|
|
||||||
env[key] = value
|
|
||||||
return env
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 2: print("Usage: safe-deploy.sh <stack> [dir]"); sys.exit(1)
|
|
||||||
stack = sys.argv[1]
|
|
||||||
d = sys.argv[2] if len(sys.argv)>2 else "/volume1/docker/compose-files"
|
|
||||||
yp = os.path.join(d, stack+".yaml")
|
|
||||||
ep = os.path.join(d, stack+".env")
|
|
||||||
if not os.path.exists(yp): print(f"ERROR: {yp} not found"); sys.exit(1)
|
|
||||||
env = dict(os.environ)
|
|
||||||
if os.path.exists(ep): parsed=parse_env_file(ep); print(f" Loaded {len(parsed)} vars"); env.update(parsed)
|
|
||||||
else: print(f" No {stack}.env - deploying without overlay")
|
|
||||||
cmd=["docker","stack","deploy","-c",yp,stack]
|
|
||||||
print(f" Running: {chr(32).join(cmd)}")
|
|
||||||
sys.exit(subprocess.run(cmd,env=env,cwd=d).returncode)
|
|
||||||
|
|
||||||
if __name__=="__main__": main()
|
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# stack-deploy.sh - Safe Docker Swarm stack deployer
|
||||||
|
# Handles special chars in .env (||, backticks, %, *) via Python
|
||||||
|
set -euo pipefail
|
||||||
|
STACK="${1:?Usage: stack-deploy.sh <stack-name>}"
|
||||||
|
DIR="/volume1/docker/compose-files"
|
||||||
|
YAML="$DIR/${STACK}.yaml"
|
||||||
|
ENVFILE="$DIR/${STACK}.env"
|
||||||
|
PY="/volume1/docker/compose-files/deploy/envparse.py"
|
||||||
|
[ -f "$YAML" ] || { echo "ERROR: $YAML not found"; exit 1; }
|
||||||
|
echo "==> Deploying stack: $STACK"
|
||||||
|
if [ -f "$ENVFILE" ]; then
|
||||||
|
echo " Loading: $ENVFILE"
|
||||||
|
eval "$(python3 $PY export "$ENVFILE")"
|
||||||
|
VARS="$(python3 $PY vars "$ENVFILE")"
|
||||||
|
echo " Vars: $VARS"
|
||||||
|
envsubst "$VARS" < "$YAML" \
|
||||||
|
| python3 $PY strip \
|
||||||
|
| docker stack deploy -c - "$STACK"
|
||||||
|
else
|
||||||
|
echo " No env file"
|
||||||
|
docker stack deploy -c "$YAML" "$STACK"
|
||||||
|
fi
|
||||||
|
echo "==> Done: $STACK"
|
||||||
Reference in New Issue
Block a user