refactor: use one safe deployment path
Always stop, back up, migrate and check readiness instead of maintaining image/config hash detection and selective updates. Keep file locking, key preservation and interrupted recovery. Repeated and frontend-only deployments may cause brief API downtime.
This commit was merged in pull request #3.
This commit is contained in:
+11
-22
@@ -63,7 +63,7 @@ printf 'FRONTEND_IMAGE=wrong:9.9.9\nBACKEND_IMAGE=wrong:9.9.9\n' > "$sandbox/rep
|
||||
|
||||
cat > "$sandbox/bin/docker" <<'PY'
|
||||
#!/usr/bin/env python3
|
||||
import hashlib, json, os, pathlib, sys
|
||||
import json, os, pathlib, sys
|
||||
base = pathlib.Path(__file__).resolve().parent.parent
|
||||
case = pathlib.Path((base / 'case').read_text())
|
||||
args = sys.argv[1:]
|
||||
@@ -73,22 +73,17 @@ state = json.loads(db.read_text()) if db.exists() else {}
|
||||
with (case / 'calls').open('a') as log:
|
||||
log.write(json.dumps(args) + '\n')
|
||||
def save(): db.write_text(json.dumps(state))
|
||||
def image_id(ref): return 'sha256:' + hashlib.sha256(ref.encode()).hexdigest()
|
||||
if args[0] == 'context': print('unix:///test/docker.sock')
|
||||
elif args[0] == 'info': pass
|
||||
elif args[0] == 'ps':
|
||||
service = next(a.split('=')[-1] for a in args if 'com.docker.compose.service=' in a)
|
||||
if service in state: print(service)
|
||||
elif args[0] == 'image': print(image_id(args[-1]))
|
||||
elif args[0] == 'inspect':
|
||||
fmt, name = args[2], args[3]
|
||||
c = state[name]
|
||||
if '.Mounts' in fmt: print(str(case / 'data'))
|
||||
elif '.Config.Env' in fmt: print('\n'.join(k + '=' + c['env'][k] for k in ('JWT_SECRET','TOKEN_ENCRYPTION_KEY')))
|
||||
elif '.Config.Image' in fmt: print(c['image'])
|
||||
elif '.Image' in fmt: print(image_id(c['image']))
|
||||
elif '.Config.Labels' in fmt: print(c['hash'])
|
||||
elif '.State.Health' in fmt: print(('true' if c['running'] else 'false') + ' ' + c['health'])
|
||||
elif '.State.Running' in fmt: print('true' if c['running'] else 'false')
|
||||
else: raise AssertionError(fmt)
|
||||
elif args[0] == 'compose':
|
||||
@@ -103,13 +98,8 @@ elif args[0] == 'compose':
|
||||
k,v = line.split('=',1); env[k] = v
|
||||
cmd = args[args.index('-f')+2:]
|
||||
def ref(service): return env['FRONTEND_IMAGE' if service == 'frontend' else 'BACKEND_IMAGE']
|
||||
def service_hash(service):
|
||||
config = {'image': ref(service)}
|
||||
if service != 'frontend': config.update({k:v for k,v in env.items() if k not in ('FRONTEND_IMAGE','FRONTEND_PORT')})
|
||||
return hashlib.sha256(json.dumps(config,sort_keys=True).encode()).hexdigest()
|
||||
if cmd[0] == 'config':
|
||||
if '--environment' in cmd: print('\n'.join(k+'='+v for k,v in env.items()))
|
||||
elif '--hash' in cmd: print(cmd[-1] + ' ' + service_hash(cmd[-1]))
|
||||
elif cmd[0] == 'pull':
|
||||
if fault == 'pull': sys.exit(1)
|
||||
elif cmd[0] == 'run':
|
||||
@@ -125,8 +115,7 @@ elif args[0] == 'compose':
|
||||
if fault == 'migration': sys.exit(1)
|
||||
with (case/'data/wealthledger.db').open('a') as f: f.write(' migrated')
|
||||
else:
|
||||
state[name] = dict(image=ref(name), hash=service_hash(name), env=env, running=True,
|
||||
health='unhealthy' if fault == name else 'healthy')
|
||||
state[name] = dict(image=ref(name), env=env, running=True)
|
||||
save()
|
||||
if fault == name: sys.exit(1)
|
||||
else: raise AssertionError(cmd)
|
||||
@@ -180,25 +169,25 @@ first_backup=$(find "$case_dir/backups" -name wealthledger.db -print)
|
||||
contains "$case_dir/.deploy/current" 'backend=registry.test/backend:1.0.0'
|
||||
: > "$case_dir/calls"
|
||||
ok v1.0.0
|
||||
unchanged_runtime
|
||||
[[ $(count_backups) == 1 ]]
|
||||
contains "$case_dir/calls" '"stop", "backend"'
|
||||
[[ $(count_backups) == 2 ]]
|
||||
: > "$case_dir/calls"
|
||||
ok v1.0.1
|
||||
! grep -Eq '"(stop|up)".*"(backend|migration)"' "$case_dir/calls"
|
||||
[[ $(count_backups) == 1 ]]
|
||||
contains "$case_dir/calls" '"stop", "backend"'
|
||||
[[ $(count_backups) == 3 ]]
|
||||
: > "$case_dir/calls"
|
||||
ok v1.1.0
|
||||
! grep -Eq '"(stop|up)".*"frontend"' "$case_dir/calls"
|
||||
[[ $(count_backups) == 2 ]]
|
||||
contains "$case_dir/calls" '"frontend"'
|
||||
[[ $(count_backups) == 4 ]]
|
||||
printf '\nLOG_LEVEL=debug\n' >> "$case_dir/production.env"
|
||||
ok v1.1.0
|
||||
[[ $(count_backups) == 3 ]]
|
||||
[[ $(count_backups) == 5 ]]
|
||||
printf '\nJWT_SECRET=changed-secret\n' >> "$case_dir/production.env"
|
||||
: > "$case_dir/calls"
|
||||
fails v1.1.0
|
||||
unchanged_runtime
|
||||
! grep -q 'changed-secret\|test-secret-that' "$case_dir/output"
|
||||
echo 'PASS adoption, backup companions, no-op, selective update, config drift, secret preservation'
|
||||
echo 'PASS adoption, backup companions, complete repeated updates and secret preservation'
|
||||
|
||||
new_case init
|
||||
rm "$case_dir/data/"*
|
||||
@@ -227,7 +216,7 @@ for fault in pull preflight copy migration backend frontend; do
|
||||
copy) ! grep -Eq '"up".*"migration"' "$case_dir/calls"; contains "$case_dir/output" 'Backup failed' ;;
|
||||
migration) ! grep -Eq '"up".*"backend"' "$case_dir/calls" ;;
|
||||
backend) contains "$case_dir/calls" '"stop", "backend"' ;;
|
||||
frontend) ! grep -Eq '"(stop|up)".*"(backend|migration)"' "$case_dir/calls" ;;
|
||||
frontend) python3 -c 'import json,sys; assert json.load(open(sys.argv[1]))["backend"]["running"]' "$case_dir/docker.json" ;;
|
||||
esac
|
||||
if [[ $fault != pull && $fault != preflight ]]; then
|
||||
[[ -f $case_dir/.deploy/pending ]]
|
||||
|
||||
Reference in New Issue
Block a user