@mriekki/lean 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -67
- package/assets/components.json +33 -33
- package/assets/core/distribution.json +52 -51
- package/assets/deployment/distribution.json +34 -34
- package/assets/generated/ASSEMBLY.json +38 -33
- package/assets/generated/core/core/skills/core-sync/SKILL.md +78 -120
- package/assets/generated/core/core/skills/deploy/SKILL.md +74 -91
- package/assets/generated/core/core/skills/lean-update/SKILL.md +189 -0
- package/assets/generated/core/core/skills/upstream-fix/SKILL.md +91 -76
- package/assets/generated/deployment/distribution/lean-poc-deploy.py +8 -8
- package/assets/generated/deployment/src/lean_poc_deployment/__init__.py +3 -3
- package/assets/generated/deployment/src/lean_poc_deployment/__main__.py +3 -3
- package/assets/generated/deployment/src/lean_poc_deployment/app_cli.py +140 -140
- package/assets/generated/deployment/src/lean_poc_deployment/app_runtime.py +455 -455
- package/assets/generated/deployment/src/lean_poc_deployment/app_runtime_remote.py +415 -415
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform.py +208 -208
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform_assets.py +100 -100
- package/assets/generated/deployment/src/lean_poc_deployment/box_platform_remote.py +271 -271
- package/assets/generated/deployment/src/lean_poc_deployment/cli.py +437 -437
- package/assets/generated/deployment/src/lean_poc_deployment/doctor.py +121 -121
- package/assets/generated/deployment/src/lean_poc_deployment/entrypoint.py +25 -25
- package/assets/generated/deployment/src/lean_poc_deployment/errors.py +5 -5
- package/assets/generated/deployment/src/lean_poc_deployment/keys.py +171 -171
- package/assets/generated/deployment/src/lean_poc_deployment/profiles.py +204 -204
- package/assets/generated/deployment/src/lean_poc_deployment/route_registry.py +367 -367
- package/assets/generated/deployment/src/lean_poc_deployment/route_registry_remote.py +443 -443
- package/assets/generated/deployment/src/lean_poc_deployment/security.py +290 -290
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter.py +334 -334
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_adapter_remote.py +339 -339
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_cli.py +85 -85
- package/assets/generated/deployment/src/lean_poc_deployment/sqlite_import_recovery.py +437 -437
- package/assets/generated/deployment/src/lean_poc_deployment/ssh.py +118 -118
- package/assets/generated/deployment/src/lean_poc_deployment/teardown.py +286 -286
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_cli.py +120 -120
- package/assets/generated/deployment/src/lean_poc_deployment/teardown_remote.py +100 -100
- package/assets/generated/deployment/src/lean_poc_deployment/trust.py +278 -278
- package/bin/lean.mjs +9 -9
- package/package.json +33 -33
- package/src/cli.mjs +131 -131
- package/src/engine.mjs +243 -243
- package/src/package-data.mjs +18 -18
|
@@ -1,120 +1,120 @@
|
|
|
1
|
-
"""CLI for scoped destructive POC tear-down."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import argparse
|
|
6
|
-
import sys
|
|
7
|
-
|
|
8
|
-
from .errors import DeploymentKitError
|
|
9
|
-
from .teardown import (
|
|
10
|
-
PRODUCTION_DATA_DELETE_PHRASE,
|
|
11
|
-
SUPPORTED_SCOPES,
|
|
12
|
-
TEAR_DOWN_WRITE_PHRASE,
|
|
13
|
-
normalize_teardown_spec,
|
|
14
|
-
parse_environment_targets,
|
|
15
|
-
render_teardown_summary,
|
|
16
|
-
tear_down_poc,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
21
|
-
parser = argparse.ArgumentParser(
|
|
22
|
-
prog="lean-poc-deploy tear-down",
|
|
23
|
-
description=(
|
|
24
|
-
"Remove the selected Kit-managed POC environment(s) and/or exact-package local Docker state. "
|
|
25
|
-
"Remote tear-down deletes persistent application data for the selected environment."
|
|
26
|
-
),
|
|
27
|
-
)
|
|
28
|
-
parser.add_argument("scope", choices=SUPPORTED_SCOPES)
|
|
29
|
-
parser.add_argument("--owner-repo", required=True)
|
|
30
|
-
parser.add_argument("--app-slug", required=True)
|
|
31
|
-
parser.add_argument(
|
|
32
|
-
"--environment-target",
|
|
33
|
-
action="append",
|
|
34
|
-
default=[],
|
|
35
|
-
metavar="ENV=TARGET",
|
|
36
|
-
help="Explicit staging/production environment-to-target binding; repeat as needed by the selected scope.",
|
|
37
|
-
)
|
|
38
|
-
parser.add_argument(
|
|
39
|
-
"--local-package",
|
|
40
|
-
help="Exact local Docker package identity, e.g. ghcr.io/owner/package; required for local and all scopes.",
|
|
41
|
-
)
|
|
42
|
-
parser.add_argument(
|
|
43
|
-
"--authorization",
|
|
44
|
-
help=f"Required destructive phrase: {TEAR_DOWN_WRITE_PHRASE}",
|
|
45
|
-
)
|
|
46
|
-
parser.add_argument(
|
|
47
|
-
"--production-data-authorization",
|
|
48
|
-
help=(
|
|
49
|
-
"Additional phrase required for production/all because persistent Production data is deleted: "
|
|
50
|
-
f"{PRODUCTION_DATA_DELETE_PHRASE}"
|
|
51
|
-
),
|
|
52
|
-
)
|
|
53
|
-
return parser.parse_args(argv)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _preview(spec: dict[str, object]) -> dict[str, object]:
|
|
57
|
-
production = spec["scope"] in {"production", "all"}
|
|
58
|
-
return {
|
|
59
|
-
"schemaVersion": 1,
|
|
60
|
-
"ownerRepo": spec["ownerRepo"],
|
|
61
|
-
"appSlug": spec["appSlug"],
|
|
62
|
-
"scope": spec["scope"],
|
|
63
|
-
"environmentTargets": spec["environmentTargets"],
|
|
64
|
-
"localPackage": spec["localPackage"],
|
|
65
|
-
"destructive": True,
|
|
66
|
-
"deletesPersistentData": bool(spec["environmentTargets"]),
|
|
67
|
-
"deletesProductionData": production,
|
|
68
|
-
"preservesSharedPlatform": True,
|
|
69
|
-
"preservesUnrelatedPocs": True,
|
|
70
|
-
"deletesGhcrPackage": False,
|
|
71
|
-
"requiredAuthorization": TEAR_DOWN_WRITE_PHRASE,
|
|
72
|
-
"requiredProductionDataAuthorization": PRODUCTION_DATA_DELETE_PHRASE if production else None,
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
def main(argv: list[str] | None = None) -> int:
|
|
77
|
-
try:
|
|
78
|
-
args = parse_args(argv)
|
|
79
|
-
bindings = parse_environment_targets(args.environment_target)
|
|
80
|
-
spec = normalize_teardown_spec(
|
|
81
|
-
owner_repo=args.owner_repo,
|
|
82
|
-
app_slug=args.app_slug,
|
|
83
|
-
scope=args.scope,
|
|
84
|
-
environment_targets=bindings,
|
|
85
|
-
local_package=args.local_package,
|
|
86
|
-
)
|
|
87
|
-
preview = _preview(spec)
|
|
88
|
-
|
|
89
|
-
missing_general = args.authorization != TEAR_DOWN_WRITE_PHRASE
|
|
90
|
-
needs_production = args.scope in {"production", "all"}
|
|
91
|
-
missing_production = needs_production and (
|
|
92
|
-
args.production_data_authorization != PRODUCTION_DATA_DELETE_PHRASE
|
|
93
|
-
)
|
|
94
|
-
if missing_general or missing_production:
|
|
95
|
-
print("POC TEAR-DOWN PREVIEW")
|
|
96
|
-
print(render_teardown_summary(preview))
|
|
97
|
-
if missing_general:
|
|
98
|
-
print(
|
|
99
|
-
f"ERROR: destructive tear-down requires --authorization {TEAR_DOWN_WRITE_PHRASE!r}",
|
|
100
|
-
file=sys.stderr,
|
|
101
|
-
)
|
|
102
|
-
if missing_production:
|
|
103
|
-
print(
|
|
104
|
-
"ERROR: Production persistent-data deletion requires "
|
|
105
|
-
f"--production-data-authorization {PRODUCTION_DATA_DELETE_PHRASE!r}",
|
|
106
|
-
file=sys.stderr,
|
|
107
|
-
)
|
|
108
|
-
return 2
|
|
109
|
-
|
|
110
|
-
result = tear_down_poc(spec)
|
|
111
|
-
print("POC TEAR-DOWN COMPLETE")
|
|
112
|
-
print(render_teardown_summary(result))
|
|
113
|
-
return 0
|
|
114
|
-
except DeploymentKitError as exc:
|
|
115
|
-
print(f"ERROR: {exc}", file=sys.stderr)
|
|
116
|
-
return 2
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if __name__ == "__main__":
|
|
120
|
-
raise SystemExit(main())
|
|
1
|
+
"""CLI for scoped destructive POC tear-down."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from .errors import DeploymentKitError
|
|
9
|
+
from .teardown import (
|
|
10
|
+
PRODUCTION_DATA_DELETE_PHRASE,
|
|
11
|
+
SUPPORTED_SCOPES,
|
|
12
|
+
TEAR_DOWN_WRITE_PHRASE,
|
|
13
|
+
normalize_teardown_spec,
|
|
14
|
+
parse_environment_targets,
|
|
15
|
+
render_teardown_summary,
|
|
16
|
+
tear_down_poc,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
21
|
+
parser = argparse.ArgumentParser(
|
|
22
|
+
prog="lean-poc-deploy tear-down",
|
|
23
|
+
description=(
|
|
24
|
+
"Remove the selected Kit-managed POC environment(s) and/or exact-package local Docker state. "
|
|
25
|
+
"Remote tear-down deletes persistent application data for the selected environment."
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument("scope", choices=SUPPORTED_SCOPES)
|
|
29
|
+
parser.add_argument("--owner-repo", required=True)
|
|
30
|
+
parser.add_argument("--app-slug", required=True)
|
|
31
|
+
parser.add_argument(
|
|
32
|
+
"--environment-target",
|
|
33
|
+
action="append",
|
|
34
|
+
default=[],
|
|
35
|
+
metavar="ENV=TARGET",
|
|
36
|
+
help="Explicit staging/production environment-to-target binding; repeat as needed by the selected scope.",
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--local-package",
|
|
40
|
+
help="Exact local Docker package identity, e.g. ghcr.io/owner/package; required for local and all scopes.",
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--authorization",
|
|
44
|
+
help=f"Required destructive phrase: {TEAR_DOWN_WRITE_PHRASE}",
|
|
45
|
+
)
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--production-data-authorization",
|
|
48
|
+
help=(
|
|
49
|
+
"Additional phrase required for production/all because persistent Production data is deleted: "
|
|
50
|
+
f"{PRODUCTION_DATA_DELETE_PHRASE}"
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
return parser.parse_args(argv)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _preview(spec: dict[str, object]) -> dict[str, object]:
|
|
57
|
+
production = spec["scope"] in {"production", "all"}
|
|
58
|
+
return {
|
|
59
|
+
"schemaVersion": 1,
|
|
60
|
+
"ownerRepo": spec["ownerRepo"],
|
|
61
|
+
"appSlug": spec["appSlug"],
|
|
62
|
+
"scope": spec["scope"],
|
|
63
|
+
"environmentTargets": spec["environmentTargets"],
|
|
64
|
+
"localPackage": spec["localPackage"],
|
|
65
|
+
"destructive": True,
|
|
66
|
+
"deletesPersistentData": bool(spec["environmentTargets"]),
|
|
67
|
+
"deletesProductionData": production,
|
|
68
|
+
"preservesSharedPlatform": True,
|
|
69
|
+
"preservesUnrelatedPocs": True,
|
|
70
|
+
"deletesGhcrPackage": False,
|
|
71
|
+
"requiredAuthorization": TEAR_DOWN_WRITE_PHRASE,
|
|
72
|
+
"requiredProductionDataAuthorization": PRODUCTION_DATA_DELETE_PHRASE if production else None,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main(argv: list[str] | None = None) -> int:
|
|
77
|
+
try:
|
|
78
|
+
args = parse_args(argv)
|
|
79
|
+
bindings = parse_environment_targets(args.environment_target)
|
|
80
|
+
spec = normalize_teardown_spec(
|
|
81
|
+
owner_repo=args.owner_repo,
|
|
82
|
+
app_slug=args.app_slug,
|
|
83
|
+
scope=args.scope,
|
|
84
|
+
environment_targets=bindings,
|
|
85
|
+
local_package=args.local_package,
|
|
86
|
+
)
|
|
87
|
+
preview = _preview(spec)
|
|
88
|
+
|
|
89
|
+
missing_general = args.authorization != TEAR_DOWN_WRITE_PHRASE
|
|
90
|
+
needs_production = args.scope in {"production", "all"}
|
|
91
|
+
missing_production = needs_production and (
|
|
92
|
+
args.production_data_authorization != PRODUCTION_DATA_DELETE_PHRASE
|
|
93
|
+
)
|
|
94
|
+
if missing_general or missing_production:
|
|
95
|
+
print("POC TEAR-DOWN PREVIEW")
|
|
96
|
+
print(render_teardown_summary(preview))
|
|
97
|
+
if missing_general:
|
|
98
|
+
print(
|
|
99
|
+
f"ERROR: destructive tear-down requires --authorization {TEAR_DOWN_WRITE_PHRASE!r}",
|
|
100
|
+
file=sys.stderr,
|
|
101
|
+
)
|
|
102
|
+
if missing_production:
|
|
103
|
+
print(
|
|
104
|
+
"ERROR: Production persistent-data deletion requires "
|
|
105
|
+
f"--production-data-authorization {PRODUCTION_DATA_DELETE_PHRASE!r}",
|
|
106
|
+
file=sys.stderr,
|
|
107
|
+
)
|
|
108
|
+
return 2
|
|
109
|
+
|
|
110
|
+
result = tear_down_poc(spec)
|
|
111
|
+
print("POC TEAR-DOWN COMPLETE")
|
|
112
|
+
print(render_teardown_summary(result))
|
|
113
|
+
return 0
|
|
114
|
+
except DeploymentKitError as exc:
|
|
115
|
+
print(f"ERROR: {exc}", file=sys.stderr)
|
|
116
|
+
return 2
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
if __name__ == "__main__":
|
|
120
|
+
raise SystemExit(main())
|
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
"""Remote destructive tear-down for one Kit-managed POC environment."""
|
|
2
|
-
|
|
3
|
-
REMOTE_SCRIPT = r'''#!/usr/bin/env bash
|
|
4
|
-
set -Eeuo pipefail
|
|
5
|
-
|
|
6
|
-
APP_ROOT="/srv/lean-poc-apps"
|
|
7
|
-
OWNER="$1"
|
|
8
|
-
APP="$2"
|
|
9
|
-
ENVIRONMENT="$3"
|
|
10
|
-
|
|
11
|
-
valid_slug() { [[ "$1" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ ]]; }
|
|
12
|
-
valid_repo() { [[ "$1" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; }
|
|
13
|
-
meta_value() {
|
|
14
|
-
local path="$1" key="$2"
|
|
15
|
-
[[ -f "$path" && ! -L "$path" ]] || return 1
|
|
16
|
-
sed -n "s/^${key}=//p" "$path" | head -n1
|
|
17
|
-
}
|
|
18
|
-
assert_owned_marker() {
|
|
19
|
-
local path="$1"
|
|
20
|
-
[[ -f "$path" && ! -L "$path" ]] || return 1
|
|
21
|
-
[[ "$(meta_value "$path" OWNER_REPO || true)" == "$OWNER" && \
|
|
22
|
-
"$(meta_value "$path" APP_SLUG || true)" == "$APP" && \
|
|
23
|
-
"$(meta_value "$path" ENVIRONMENT || true)" == "$ENVIRONMENT" ]]
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
valid_repo "$OWNER" || { echo "Invalid owner repository." >&2; exit 20; }
|
|
27
|
-
valid_slug "$APP" || { echo "Invalid application slug." >&2; exit 20; }
|
|
28
|
-
valid_slug "$ENVIRONMENT" || { echo "Invalid environment slug." >&2; exit 20; }
|
|
29
|
-
|
|
30
|
-
ROOT="$APP_ROOT/$APP/$ENVIRONMENT"
|
|
31
|
-
STATE="$ROOT/deployment.env"
|
|
32
|
-
PERSISTENCE="$ROOT/persistence.env"
|
|
33
|
-
|
|
34
|
-
if [[ ! -e "$ROOT" ]]; then
|
|
35
|
-
printf 'TEAR_DOWN\tabsent\t%s\t%s\n' "$APP" "$ENVIRONMENT"
|
|
36
|
-
exit 0
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
[[ -d "$ROOT" && ! -L "$ROOT" ]] || {
|
|
40
|
-
echo "POC environment root is not a safe managed directory: $ROOT" >&2
|
|
41
|
-
exit 21
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
HAS_STATE=false
|
|
45
|
-
HAS_PERSISTENCE=false
|
|
46
|
-
[[ -f "$STATE" && ! -L "$STATE" ]] && HAS_STATE=true
|
|
47
|
-
[[ -f "$PERSISTENCE" && ! -L "$PERSISTENCE" ]] && HAS_PERSISTENCE=true
|
|
48
|
-
[[ "$HAS_STATE" == true || "$HAS_PERSISTENCE" == true ]] || {
|
|
49
|
-
echo "POC environment root has no managed ownership marker: $ROOT" >&2
|
|
50
|
-
exit 21
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if [[ "$HAS_STATE" == true ]] && ! assert_owned_marker "$STATE"; then
|
|
54
|
-
echo "POC deployment ownership mismatch for $ROOT" >&2
|
|
55
|
-
exit 22
|
|
56
|
-
fi
|
|
57
|
-
if [[ "$HAS_PERSISTENCE" == true ]] && ! assert_owned_marker "$PERSISTENCE"; then
|
|
58
|
-
echo "POC persistence ownership mismatch for $ROOT" >&2
|
|
59
|
-
exit 22
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
CONTAINER=""
|
|
63
|
-
if [[ "$HAS_STATE" == true ]]; then
|
|
64
|
-
CURRENT_RELEASE="$(meta_value "$STATE" CURRENT_RELEASE || true)"
|
|
65
|
-
[[ -n "$CURRENT_RELEASE" && "$CURRENT_RELEASE" != */* && "$CURRENT_RELEASE" != *..* ]] || {
|
|
66
|
-
echo "Managed deployment state has an invalid current release." >&2
|
|
67
|
-
exit 23
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
RELEASE_FILE="$ROOT/releases/$CURRENT_RELEASE/release.env"
|
|
71
|
-
[[ -f "$RELEASE_FILE" && ! -L "$RELEASE_FILE" ]] || {
|
|
72
|
-
echo "Managed current release metadata is missing: $RELEASE_FILE" >&2
|
|
73
|
-
exit 23
|
|
74
|
-
}
|
|
75
|
-
assert_owned_marker "$RELEASE_FILE" || {
|
|
76
|
-
echo "Managed release ownership does not match deployment state." >&2
|
|
77
|
-
exit 23
|
|
78
|
-
}
|
|
79
|
-
CONTAINER="$(meta_value "$RELEASE_FILE" CONTAINER_NAME || true)"
|
|
80
|
-
[[ "$CONTAINER" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$ ]] || {
|
|
81
|
-
echo "Managed release contains an invalid container name." >&2
|
|
82
|
-
exit 23
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if docker inspect "$CONTAINER" >/dev/null 2>&1; then
|
|
86
|
-
docker rm -f "$CONTAINER" >/dev/null
|
|
87
|
-
fi
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
# The verified environment root is owned by this POC/environment. Tear-down
|
|
91
|
-
# removes releases, evidence, persistent data, and backups together.
|
|
92
|
-
rm -rf --one-file-system -- "$ROOT"
|
|
93
|
-
|
|
94
|
-
APP_DIR="$APP_ROOT/$APP"
|
|
95
|
-
if [[ -d "$APP_DIR" ]] && ! find "$APP_DIR" -mindepth 1 -print -quit | grep -q .; then
|
|
96
|
-
rmdir "$APP_DIR"
|
|
97
|
-
fi
|
|
98
|
-
|
|
99
|
-
printf 'TEAR_DOWN\tremoved\t%s\t%s\t%s\n' "$APP" "$ENVIRONMENT" "$CONTAINER"
|
|
100
|
-
'''
|
|
1
|
+
"""Remote destructive tear-down for one Kit-managed POC environment."""
|
|
2
|
+
|
|
3
|
+
REMOTE_SCRIPT = r'''#!/usr/bin/env bash
|
|
4
|
+
set -Eeuo pipefail
|
|
5
|
+
|
|
6
|
+
APP_ROOT="/srv/lean-poc-apps"
|
|
7
|
+
OWNER="$1"
|
|
8
|
+
APP="$2"
|
|
9
|
+
ENVIRONMENT="$3"
|
|
10
|
+
|
|
11
|
+
valid_slug() { [[ "$1" =~ ^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$ ]]; }
|
|
12
|
+
valid_repo() { [[ "$1" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; }
|
|
13
|
+
meta_value() {
|
|
14
|
+
local path="$1" key="$2"
|
|
15
|
+
[[ -f "$path" && ! -L "$path" ]] || return 1
|
|
16
|
+
sed -n "s/^${key}=//p" "$path" | head -n1
|
|
17
|
+
}
|
|
18
|
+
assert_owned_marker() {
|
|
19
|
+
local path="$1"
|
|
20
|
+
[[ -f "$path" && ! -L "$path" ]] || return 1
|
|
21
|
+
[[ "$(meta_value "$path" OWNER_REPO || true)" == "$OWNER" && \
|
|
22
|
+
"$(meta_value "$path" APP_SLUG || true)" == "$APP" && \
|
|
23
|
+
"$(meta_value "$path" ENVIRONMENT || true)" == "$ENVIRONMENT" ]]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
valid_repo "$OWNER" || { echo "Invalid owner repository." >&2; exit 20; }
|
|
27
|
+
valid_slug "$APP" || { echo "Invalid application slug." >&2; exit 20; }
|
|
28
|
+
valid_slug "$ENVIRONMENT" || { echo "Invalid environment slug." >&2; exit 20; }
|
|
29
|
+
|
|
30
|
+
ROOT="$APP_ROOT/$APP/$ENVIRONMENT"
|
|
31
|
+
STATE="$ROOT/deployment.env"
|
|
32
|
+
PERSISTENCE="$ROOT/persistence.env"
|
|
33
|
+
|
|
34
|
+
if [[ ! -e "$ROOT" ]]; then
|
|
35
|
+
printf 'TEAR_DOWN\tabsent\t%s\t%s\n' "$APP" "$ENVIRONMENT"
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
[[ -d "$ROOT" && ! -L "$ROOT" ]] || {
|
|
40
|
+
echo "POC environment root is not a safe managed directory: $ROOT" >&2
|
|
41
|
+
exit 21
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
HAS_STATE=false
|
|
45
|
+
HAS_PERSISTENCE=false
|
|
46
|
+
[[ -f "$STATE" && ! -L "$STATE" ]] && HAS_STATE=true
|
|
47
|
+
[[ -f "$PERSISTENCE" && ! -L "$PERSISTENCE" ]] && HAS_PERSISTENCE=true
|
|
48
|
+
[[ "$HAS_STATE" == true || "$HAS_PERSISTENCE" == true ]] || {
|
|
49
|
+
echo "POC environment root has no managed ownership marker: $ROOT" >&2
|
|
50
|
+
exit 21
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if [[ "$HAS_STATE" == true ]] && ! assert_owned_marker "$STATE"; then
|
|
54
|
+
echo "POC deployment ownership mismatch for $ROOT" >&2
|
|
55
|
+
exit 22
|
|
56
|
+
fi
|
|
57
|
+
if [[ "$HAS_PERSISTENCE" == true ]] && ! assert_owned_marker "$PERSISTENCE"; then
|
|
58
|
+
echo "POC persistence ownership mismatch for $ROOT" >&2
|
|
59
|
+
exit 22
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
CONTAINER=""
|
|
63
|
+
if [[ "$HAS_STATE" == true ]]; then
|
|
64
|
+
CURRENT_RELEASE="$(meta_value "$STATE" CURRENT_RELEASE || true)"
|
|
65
|
+
[[ -n "$CURRENT_RELEASE" && "$CURRENT_RELEASE" != */* && "$CURRENT_RELEASE" != *..* ]] || {
|
|
66
|
+
echo "Managed deployment state has an invalid current release." >&2
|
|
67
|
+
exit 23
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
RELEASE_FILE="$ROOT/releases/$CURRENT_RELEASE/release.env"
|
|
71
|
+
[[ -f "$RELEASE_FILE" && ! -L "$RELEASE_FILE" ]] || {
|
|
72
|
+
echo "Managed current release metadata is missing: $RELEASE_FILE" >&2
|
|
73
|
+
exit 23
|
|
74
|
+
}
|
|
75
|
+
assert_owned_marker "$RELEASE_FILE" || {
|
|
76
|
+
echo "Managed release ownership does not match deployment state." >&2
|
|
77
|
+
exit 23
|
|
78
|
+
}
|
|
79
|
+
CONTAINER="$(meta_value "$RELEASE_FILE" CONTAINER_NAME || true)"
|
|
80
|
+
[[ "$CONTAINER" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$ ]] || {
|
|
81
|
+
echo "Managed release contains an invalid container name." >&2
|
|
82
|
+
exit 23
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if docker inspect "$CONTAINER" >/dev/null 2>&1; then
|
|
86
|
+
docker rm -f "$CONTAINER" >/dev/null
|
|
87
|
+
fi
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# The verified environment root is owned by this POC/environment. Tear-down
|
|
91
|
+
# removes releases, evidence, persistent data, and backups together.
|
|
92
|
+
rm -rf --one-file-system -- "$ROOT"
|
|
93
|
+
|
|
94
|
+
APP_DIR="$APP_ROOT/$APP"
|
|
95
|
+
if [[ -d "$APP_DIR" ]] && ! find "$APP_DIR" -mindepth 1 -print -quit | grep -q .; then
|
|
96
|
+
rmdir "$APP_DIR"
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
printf 'TEAR_DOWN\tremoved\t%s\t%s\t%s\n' "$APP" "$ENVIRONMENT" "$CONTAINER"
|
|
100
|
+
'''
|