@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,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: upstream-fix
|
|
3
|
-
description: Route reusable bugs discovered in a consumer to the canonical shared repository, allow bounded upstream implementation, and return adoption
|
|
3
|
+
description: Route reusable bugs discovered in a consumer to the canonical shared repository, allow bounded upstream implementation, and return adoption through the consumer's actual provenance/update boundary.
|
|
4
4
|
argument-hint: "[optional problem summary]"
|
|
5
5
|
user-invocable: true
|
|
6
6
|
disable-model-invocation: true
|
|
@@ -13,32 +13,26 @@ disable-model-invocation: true
|
|
|
13
13
|
The governing rule is:
|
|
14
14
|
|
|
15
15
|
```text
|
|
16
|
-
discover in
|
|
17
|
-
→ diagnose/prove in
|
|
18
|
-
→ fix in
|
|
16
|
+
discover in consumer
|
|
17
|
+
→ diagnose/prove in consumer
|
|
18
|
+
→ fix in owning canonical repository
|
|
19
19
|
→ deliver there
|
|
20
|
-
→
|
|
21
|
-
→ verify again in
|
|
20
|
+
→ adopt through the consumer's recorded distribution boundary
|
|
21
|
+
→ verify again in consumer
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
A temporary edit to a shared-managed file inside
|
|
24
|
+
A temporary edit to a shared-managed file inside a consumer may be useful as diagnosis/proof, but it is never the canonical delivered fix.
|
|
25
25
|
|
|
26
26
|
## 1. Invocation authorization
|
|
27
27
|
|
|
28
28
|
Invoking `/upstream-fix` authorizes:
|
|
29
29
|
|
|
30
30
|
- read-only ownership/provenance inspection in the consumer;
|
|
31
|
-
- classification
|
|
31
|
+
- classification to the smallest canonical owner;
|
|
32
32
|
- inspection of already-available local canonical repository candidates;
|
|
33
|
-
- when ownership and the
|
|
33
|
+
- when ownership and the bounded patch are clear and a canonical checkout is safe, routine creation/use of one short-lived canonical work branch and application of only that bounded fix plus focused tests/docs.
|
|
34
34
|
|
|
35
|
-
Invocation does **not** authorize
|
|
36
|
-
|
|
37
|
-
- commit, push, PR creation, merge, release publication, or deployment;
|
|
38
|
-
- rewriting/discarding consumer work;
|
|
39
|
-
- changing more than one canonical repository in the same bounded fix;
|
|
40
|
-
- broad filesystem scans or cloning shared repositories merely to satisfy the helper;
|
|
41
|
-
- treating a temporary consumer patch as accepted shared implementation.
|
|
35
|
+
Invocation does **not** authorize commit, push, PR creation, merge, release publication, package publication, deployment, rewriting/discarding consumer work, or changing multiple canonical repositories in one bounded fix.
|
|
42
36
|
|
|
43
37
|
Normal review and `/deliver` occur later while focused on the owning canonical repository.
|
|
44
38
|
|
|
@@ -46,20 +40,16 @@ Normal review and `/deliver` occur later while focused on the owning canonical r
|
|
|
46
40
|
|
|
47
41
|
From the consumer root inspect, when present:
|
|
48
42
|
|
|
43
|
+
- `LEAN_BASELINE.json`;
|
|
49
44
|
- `CORE_BASELINE.json`;
|
|
50
45
|
- `DEPLOYMENT_BASELINE.json`;
|
|
51
46
|
- `.github/lean-workspace.json`;
|
|
52
47
|
- current Git branch/status/diff;
|
|
53
48
|
- the files involved in the reported problem or temporary proof.
|
|
54
49
|
|
|
55
|
-
Run repository-local managed-baseline checks when available
|
|
56
|
-
|
|
57
|
-
```text
|
|
58
|
-
python tools/lean-core/check-core-baseline.py --root .
|
|
59
|
-
python tools/lean-deployment/check-deployment-baseline.py
|
|
60
|
-
```
|
|
50
|
+
Run repository-local managed-baseline checks when available.
|
|
61
51
|
|
|
62
|
-
Do not assume every generated-looking file is shared-owned. Use baseline managed-path records as the primary ownership evidence.
|
|
52
|
+
Do not assume every generated-looking file is shared-owned. Use baseline managed-path records as the primary ownership evidence, and use `LEAN_BASELINE.json` to determine whether component baselines are managed together by an umbrella package.
|
|
63
53
|
|
|
64
54
|
## 3. Classify the owner
|
|
65
55
|
|
|
@@ -67,7 +57,7 @@ Use the smallest owning layer that would need the same fix for another consumer.
|
|
|
67
57
|
|
|
68
58
|
### Consumer-owned
|
|
69
59
|
|
|
70
|
-
|
|
60
|
+
Application domain logic, UI behavior, app-specific configuration, product tests, and consumer architecture normally remain consumer-owned.
|
|
71
61
|
|
|
72
62
|
Result:
|
|
73
63
|
|
|
@@ -76,13 +66,11 @@ CONSUMER FIX
|
|
|
76
66
|
Owner: current repository
|
|
77
67
|
```
|
|
78
68
|
|
|
79
|
-
|
|
69
|
+
Return to the consumer's normal `/start-work` / implementation / review / `/deliver` flow.
|
|
80
70
|
|
|
81
71
|
### Core-owned
|
|
82
72
|
|
|
83
|
-
Evidence includes a
|
|
84
|
-
|
|
85
|
-
Result:
|
|
73
|
+
Evidence includes a path managed by `CORE_BASELINE.json` or behavior defined by reusable Core workflow assets.
|
|
86
74
|
|
|
87
75
|
```text
|
|
88
76
|
CORE BUG
|
|
@@ -91,26 +79,33 @@ Owner: <repository recorded by CORE_BASELINE.json>
|
|
|
91
79
|
|
|
92
80
|
### Deployment-Kit-owned
|
|
93
81
|
|
|
94
|
-
Evidence includes a
|
|
95
|
-
|
|
96
|
-
Result:
|
|
82
|
+
Evidence includes a path managed by `DEPLOYMENT_BASELINE.json` or reusable deployment mechanics owned by that recorded Kit.
|
|
97
83
|
|
|
98
84
|
```text
|
|
99
85
|
DEPLOYMENT BUG
|
|
100
86
|
Owner: <repository recorded by DEPLOYMENT_BASELINE.json>
|
|
101
87
|
```
|
|
102
88
|
|
|
89
|
+
### Package assembly / Lean Platform-owned
|
|
90
|
+
|
|
91
|
+
When canonical component source is correct but the published package bytes, component pins, package engine, or umbrella baseline behavior is wrong:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
LEAN PLATFORM BUG
|
|
95
|
+
Owner: package/platform repository supported by evidence
|
|
96
|
+
```
|
|
97
|
+
|
|
103
98
|
### Generator/control-plane owned
|
|
104
99
|
|
|
105
|
-
If
|
|
100
|
+
If the package payload is correct but generation/publication/orchestration materializes it incorrectly, classify Bootstrap/control-plane ownership from evidence rather than patching a generated consumer.
|
|
106
101
|
|
|
107
|
-
If ownership is genuinely ambiguous, stop at `UPSTREAM OWNER UNCLEAR
|
|
102
|
+
If ownership is genuinely ambiguous, stop at `UPSTREAM OWNER UNCLEAR`. Do not write to multiple repositories speculatively.
|
|
108
103
|
|
|
109
104
|
## 4. Temporary consumer proof
|
|
110
105
|
|
|
111
|
-
A temporary consumer edit to a shared-managed path is allowed for diagnosis when it
|
|
106
|
+
A temporary consumer edit to a shared-managed path is allowed for diagnosis when it proves the fix in real application context.
|
|
112
107
|
|
|
113
|
-
|
|
108
|
+
Report it explicitly:
|
|
114
109
|
|
|
115
110
|
```text
|
|
116
111
|
TEMPORARY SHARED-MANAGED PROOF
|
|
@@ -119,48 +114,38 @@ Canonical owner: <repo>
|
|
|
119
114
|
Do not deliver this consumer copy.
|
|
120
115
|
```
|
|
121
116
|
|
|
122
|
-
Do not silently
|
|
117
|
+
Do not silently discard the proof and do not stage/commit it as ordinary consumer implementation.
|
|
123
118
|
|
|
124
|
-
## 5. Resolve a canonical
|
|
119
|
+
## 5. Resolve a safe canonical checkout
|
|
125
120
|
|
|
126
|
-
For the classified owner
|
|
121
|
+
For the classified owner consider, in order:
|
|
127
122
|
|
|
128
|
-
1. explicit path supplied by the human;
|
|
129
|
-
2. an already-open workspace folder
|
|
123
|
+
1. an explicit path supplied by the human;
|
|
124
|
+
2. an already-open workspace folder with the expected Git remote;
|
|
130
125
|
3. a normal sibling checkout derived from the canonical repository name.
|
|
131
126
|
|
|
132
|
-
Do not broadly scan the filesystem
|
|
133
|
-
|
|
134
|
-
A candidate is safe for bounded upstream implementation only when:
|
|
127
|
+
Do not broadly scan the filesystem and do not clone automatically merely to continue.
|
|
135
128
|
|
|
136
|
-
|
|
137
|
-
- Git state can be inspected;
|
|
138
|
-
- no merge/rebase/cherry-pick is in progress;
|
|
139
|
-
- unrelated local work is absent;
|
|
140
|
-
- branch state does not require reset/rebase/force/stash/discard operations.
|
|
141
|
-
|
|
142
|
-
If no safe local canonical checkout exists, report the exact canonical repository and the proven consumer evidence. Do not clone automatically just to continue.
|
|
129
|
+
A candidate is safe only when it is the expected repository, has no merge/rebase/cherry-pick in progress, has no unrelated local work, and does not require stash/reset/rebase/force/discard operations.
|
|
143
130
|
|
|
144
131
|
## 6. Port the bounded fix upstream
|
|
145
132
|
|
|
146
|
-
When
|
|
147
|
-
|
|
148
|
-
1. inspect the canonical implementation and tests before editing;
|
|
149
|
-
2. create/use a repository-appropriate short-lived branch tied to the upstream issue/work item when needed;
|
|
150
|
-
3. reproduce the root cause against canonical code where practical;
|
|
151
|
-
4. apply the reusable fix in the canonical source, not by copying consumer-specific names/configuration;
|
|
152
|
-
5. add/update focused canonical tests that would have caught the defect;
|
|
153
|
-
6. update canonical docs only when the contract changed;
|
|
154
|
-
7. run relevant local checks when available;
|
|
155
|
-
8. show the canonical meaningful diff and observed validation.
|
|
133
|
+
When a safe canonical checkout exists and the equivalent reusable fix is clear:
|
|
156
134
|
|
|
157
|
-
|
|
135
|
+
1. inspect canonical implementation/tests before editing;
|
|
136
|
+
2. create/use one repository-appropriate short-lived branch;
|
|
137
|
+
3. reproduce the root cause canonically where practical;
|
|
138
|
+
4. apply the reusable fix without consumer-specific names/configuration;
|
|
139
|
+
5. add/update focused tests that would have caught the defect;
|
|
140
|
+
6. update canonical docs when the contract changed;
|
|
141
|
+
7. run relevant local checks;
|
|
142
|
+
8. show the meaningful canonical diff and observed validation.
|
|
158
143
|
|
|
159
|
-
If the consumer proof cannot be generalized safely, stop and reclassify
|
|
144
|
+
If the consumer proof cannot be generalized safely, stop and reclassify rather than forcing it upstream.
|
|
160
145
|
|
|
161
146
|
## 7. Handoff to canonical delivery
|
|
162
147
|
|
|
163
|
-
|
|
148
|
+
When the upstream implementation is ready:
|
|
164
149
|
|
|
165
150
|
```text
|
|
166
151
|
UPSTREAM FIX READY
|
|
@@ -172,23 +157,53 @@ Validation: <observed checks>
|
|
|
172
157
|
Next: focus the canonical repository and run /deliver
|
|
173
158
|
```
|
|
174
159
|
|
|
175
|
-
`/upstream-fix` does not commit/push/open/merge the canonical PR itself.
|
|
160
|
+
`/upstream-fix` does not commit/push/open/merge the canonical PR itself.
|
|
161
|
+
|
|
162
|
+
## 8. Adopt the accepted fix through the right boundary
|
|
163
|
+
|
|
164
|
+
After the canonical PR is merged and required default-branch CI is green, re-read the consumer provenance before choosing adoption.
|
|
165
|
+
|
|
166
|
+
### Package-managed consumer (`LEAN_BASELINE.json` present)
|
|
167
|
+
|
|
168
|
+
Do **not** direct-sync Core or Deployment Kit into the consumer.
|
|
169
|
+
|
|
170
|
+
The accepted component fix must first become part of an accepted published Lean package:
|
|
171
|
+
|
|
172
|
+
```text
|
|
173
|
+
canonical Core/Deployment fix merged
|
|
174
|
+
→ Lean Platform pins the exact accepted component commit
|
|
175
|
+
→ Lean Platform PR + CI
|
|
176
|
+
→ explicit package publication gate
|
|
177
|
+
→ published exact @mriekki/lean version
|
|
178
|
+
→ consumer /lean-update <version>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Until that package exists, report:
|
|
182
|
+
|
|
183
|
+
```text
|
|
184
|
+
PACKAGE RELEASE REQUIRED
|
|
185
|
+
Consumer adoption is blocked on a Lean package that contains the accepted upstream commit.
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Do not silently patch `LEAN_BASELINE.json`, `CORE_BASELINE.json`, or `DEPLOYMENT_BASELINE.json` to pretend the package contains the new component.
|
|
189
|
+
|
|
190
|
+
### Component-only Core consumer (`LEAN_BASELINE.json` absent)
|
|
191
|
+
|
|
192
|
+
A Core-owned fix may return through `/core-sync` using the exact accepted Core commit/distribution.
|
|
176
193
|
|
|
177
|
-
|
|
194
|
+
### Component-only Deployment consumer (`LEAN_BASELINE.json` absent)
|
|
178
195
|
|
|
179
|
-
|
|
196
|
+
A Deployment-owned fix may return through the canonical Deployment Kit compatibility sync and refresh `DEPLOYMENT_BASELINE.json`.
|
|
180
197
|
|
|
181
|
-
|
|
182
|
-
- Deployment-Kit-owned fixes return through the canonical Deployment Kit consumer-sync capability and refresh `DEPLOYMENT_BASELINE.json`;
|
|
183
|
-
- generator/control-plane fixes are validated by regenerating or using that platform's canonical update path, not by manually editing generated managed files.
|
|
198
|
+
### Generator/control-plane fix
|
|
184
199
|
|
|
185
|
-
|
|
200
|
+
Validate through that platform's canonical regeneration/update path, never by manually editing generated managed files.
|
|
186
201
|
|
|
187
|
-
|
|
202
|
+
Then rerun the original reproduction and baseline checks in the consumer.
|
|
188
203
|
|
|
189
204
|
## 9. Consumer delivery guard
|
|
190
205
|
|
|
191
|
-
If `/deliver` is invoked in a consumer with unexplained Core
|
|
206
|
+
If `/deliver` is invoked in a consumer with unexplained Core-, Deployment-, or Lean-package-managed drift, stop before staging/commit/push. Route reusable managed-file changes through `/upstream-fix` and then through the appropriate accepted update boundary.
|
|
192
207
|
|
|
193
208
|
Consumer-owned changes may be delivered normally when managed baselines are clean.
|
|
194
209
|
|
|
@@ -197,10 +212,10 @@ Consumer-owned changes may be delivered normally when managed baselines are clea
|
|
|
197
212
|
`/upstream-fix` never:
|
|
198
213
|
|
|
199
214
|
- merges changes;
|
|
200
|
-
- publishes releases/tags;
|
|
215
|
+
- publishes releases/tags/packages;
|
|
201
216
|
- deploys production;
|
|
202
|
-
- commits one
|
|
217
|
+
- commits one bounded fix across multiple repositories;
|
|
203
218
|
- auto-stashes, resets, rebases, force-pushes, or discards work;
|
|
204
|
-
- turns a maintainer multi-root workspace into generated repository content;
|
|
205
219
|
- requires ordinary consumer users to clone shared repositories;
|
|
206
|
-
- treats a temporary
|
|
220
|
+
- treats a temporary managed consumer edit as a permanent fork;
|
|
221
|
+
- bypasses `LEAN_BASELINE.json` with component-only sync.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
from __future__ import annotations
|
|
3
|
-
|
|
4
|
-
from lean_poc_deployment.entrypoint import main
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if __name__ == "__main__":
|
|
8
|
-
raise SystemExit(main())
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from lean_poc_deployment.entrypoint import main
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
if __name__ == "__main__":
|
|
8
|
+
raise SystemExit(main())
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
"""Lean POC Deployment Kit."""
|
|
2
|
-
|
|
3
|
-
__version__ = "0.1.0.dev0"
|
|
1
|
+
"""Lean POC Deployment Kit."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0.dev0"
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
from .entrypoint import main
|
|
2
|
-
|
|
3
|
-
raise SystemExit(main())
|
|
1
|
+
from .entrypoint import main
|
|
2
|
+
|
|
3
|
+
raise SystemExit(main())
|
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
"""CLI for generic immutable application runtime deployment."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import argparse
|
|
6
|
-
import json
|
|
7
|
-
import sys
|
|
8
|
-
from typing import Any
|
|
9
|
-
|
|
10
|
-
from .app_runtime import (
|
|
11
|
-
APP_DEPLOY_WRITE_PHRASE,
|
|
12
|
-
PRODUCTION_APP_DEPLOY_WRITE_PHRASE,
|
|
13
|
-
app_inventory,
|
|
14
|
-
apply_app_deploy,
|
|
15
|
-
deploy_write_phrase,
|
|
16
|
-
normalize_app_spec,
|
|
17
|
-
plan_app_deploy,
|
|
18
|
-
)
|
|
19
|
-
from .errors import DeploymentKitError
|
|
20
|
-
from .sqlite_adapter import assert_app_spec_matches_persistence
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _print(value: Any, *, as_json: bool) -> None:
|
|
24
|
-
if as_json:
|
|
25
|
-
print(json.dumps(value, indent=2, sort_keys=True))
|
|
26
|
-
return
|
|
27
|
-
if isinstance(value, dict):
|
|
28
|
-
for key, item in value.items():
|
|
29
|
-
if isinstance(item, (dict, list)):
|
|
30
|
-
print(f"{key}: {json.dumps(item, sort_keys=True)}")
|
|
31
|
-
else:
|
|
32
|
-
print(f"{key}: {item}")
|
|
33
|
-
return
|
|
34
|
-
print(value)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _parse_env(items: list[str]) -> list[tuple[str, str]]:
|
|
38
|
-
result: list[tuple[str, str]] = []
|
|
39
|
-
for item in items:
|
|
40
|
-
if "=" not in item:
|
|
41
|
-
raise DeploymentKitError("--env entries must use NAME=value form.")
|
|
42
|
-
key, value = item.split("=", 1)
|
|
43
|
-
result.append((key, value))
|
|
44
|
-
return result
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def _confirm(args: argparse.Namespace, phrase: str, action: str) -> None:
|
|
48
|
-
if args.yes:
|
|
49
|
-
return
|
|
50
|
-
entered = input(f"Type {phrase} to {action}: ").strip()
|
|
51
|
-
if entered != phrase:
|
|
52
|
-
raise DeploymentKitError(f"{action.capitalize()} was not authorized.")
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
def build_parser() -> argparse.ArgumentParser:
|
|
56
|
-
parser = argparse.ArgumentParser(prog="lean-poc-deploy app")
|
|
57
|
-
sub = parser.add_subparsers(dest="command", required=True)
|
|
58
|
-
|
|
59
|
-
inventory = sub.add_parser(
|
|
60
|
-
"inventory",
|
|
61
|
-
help="Read Kit-managed application runtime ownership/readiness without writes",
|
|
62
|
-
)
|
|
63
|
-
inventory.add_argument("name")
|
|
64
|
-
inventory.add_argument("--json", action="store_true")
|
|
65
|
-
|
|
66
|
-
deploy = sub.add_parser(
|
|
67
|
-
"deploy",
|
|
68
|
-
help="Deploy one immutable application runtime without modifying routes",
|
|
69
|
-
)
|
|
70
|
-
deploy.add_argument("name")
|
|
71
|
-
deploy.add_argument("--owner-repo", required=True)
|
|
72
|
-
deploy.add_argument("--environment", required=True)
|
|
73
|
-
deploy.add_argument("--app-slug", required=True)
|
|
74
|
-
deploy.add_argument("--container-name", required=True)
|
|
75
|
-
deploy.add_argument("--image", required=True)
|
|
76
|
-
deploy.add_argument("--runtime-port", type=int, required=True)
|
|
77
|
-
deploy.add_argument("--health-path", required=True)
|
|
78
|
-
deploy.add_argument("--run-as", required=True)
|
|
79
|
-
deploy.add_argument(
|
|
80
|
-
"--persistence-mode",
|
|
81
|
-
choices=("none", "directory"),
|
|
82
|
-
required=True,
|
|
83
|
-
)
|
|
84
|
-
deploy.add_argument("--data-container-path")
|
|
85
|
-
deploy.add_argument(
|
|
86
|
-
"--env",
|
|
87
|
-
action="append",
|
|
88
|
-
default=[],
|
|
89
|
-
help="Non-secret runtime environment value in NAME=value form; repeat as needed",
|
|
90
|
-
)
|
|
91
|
-
deploy.add_argument("--json", action="store_true")
|
|
92
|
-
deploy.add_argument("--yes", action="store_true")
|
|
93
|
-
return parser
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
def main(argv: list[str] | None = None) -> int:
|
|
97
|
-
args = build_parser().parse_args(argv)
|
|
98
|
-
try:
|
|
99
|
-
if args.command == "inventory":
|
|
100
|
-
_print(app_inventory(args.name), as_json=args.json)
|
|
101
|
-
return 0
|
|
102
|
-
|
|
103
|
-
spec = normalize_app_spec(
|
|
104
|
-
owner_repo=args.owner_repo,
|
|
105
|
-
environment=args.environment,
|
|
106
|
-
app_slug=args.app_slug,
|
|
107
|
-
container_name=args.container_name,
|
|
108
|
-
image=args.image,
|
|
109
|
-
runtime_port=args.runtime_port,
|
|
110
|
-
health_path=args.health_path,
|
|
111
|
-
run_as=args.run_as,
|
|
112
|
-
persistence_mode=args.persistence_mode,
|
|
113
|
-
data_container_path=args.data_container_path,
|
|
114
|
-
environment_variables=_parse_env(args.env),
|
|
115
|
-
)
|
|
116
|
-
assert_app_spec_matches_persistence(args.name, spec)
|
|
117
|
-
plan = plan_app_deploy(args.name, spec)
|
|
118
|
-
_print(
|
|
119
|
-
{key: value for key, value in plan.items() if key != "spec"},
|
|
120
|
-
as_json=False,
|
|
121
|
-
)
|
|
122
|
-
phrase = deploy_write_phrase(plan)
|
|
123
|
-
_confirm(
|
|
124
|
-
args,
|
|
125
|
-
phrase,
|
|
126
|
-
f"deploy {plan['appSlug']} ({plan['environment']}) on target {args.name}",
|
|
127
|
-
)
|
|
128
|
-
_print(apply_app_deploy(plan), as_json=args.json)
|
|
129
|
-
return 0
|
|
130
|
-
except DeploymentKitError as error:
|
|
131
|
-
print(f"ERROR: {error}", file=sys.stderr)
|
|
132
|
-
return 2
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
__all__ = [
|
|
136
|
-
"APP_DEPLOY_WRITE_PHRASE",
|
|
137
|
-
"PRODUCTION_APP_DEPLOY_WRITE_PHRASE",
|
|
138
|
-
"build_parser",
|
|
139
|
-
"main",
|
|
140
|
-
]
|
|
1
|
+
"""CLI for generic immutable application runtime deployment."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from .app_runtime import (
|
|
11
|
+
APP_DEPLOY_WRITE_PHRASE,
|
|
12
|
+
PRODUCTION_APP_DEPLOY_WRITE_PHRASE,
|
|
13
|
+
app_inventory,
|
|
14
|
+
apply_app_deploy,
|
|
15
|
+
deploy_write_phrase,
|
|
16
|
+
normalize_app_spec,
|
|
17
|
+
plan_app_deploy,
|
|
18
|
+
)
|
|
19
|
+
from .errors import DeploymentKitError
|
|
20
|
+
from .sqlite_adapter import assert_app_spec_matches_persistence
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _print(value: Any, *, as_json: bool) -> None:
|
|
24
|
+
if as_json:
|
|
25
|
+
print(json.dumps(value, indent=2, sort_keys=True))
|
|
26
|
+
return
|
|
27
|
+
if isinstance(value, dict):
|
|
28
|
+
for key, item in value.items():
|
|
29
|
+
if isinstance(item, (dict, list)):
|
|
30
|
+
print(f"{key}: {json.dumps(item, sort_keys=True)}")
|
|
31
|
+
else:
|
|
32
|
+
print(f"{key}: {item}")
|
|
33
|
+
return
|
|
34
|
+
print(value)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _parse_env(items: list[str]) -> list[tuple[str, str]]:
|
|
38
|
+
result: list[tuple[str, str]] = []
|
|
39
|
+
for item in items:
|
|
40
|
+
if "=" not in item:
|
|
41
|
+
raise DeploymentKitError("--env entries must use NAME=value form.")
|
|
42
|
+
key, value = item.split("=", 1)
|
|
43
|
+
result.append((key, value))
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _confirm(args: argparse.Namespace, phrase: str, action: str) -> None:
|
|
48
|
+
if args.yes:
|
|
49
|
+
return
|
|
50
|
+
entered = input(f"Type {phrase} to {action}: ").strip()
|
|
51
|
+
if entered != phrase:
|
|
52
|
+
raise DeploymentKitError(f"{action.capitalize()} was not authorized.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
56
|
+
parser = argparse.ArgumentParser(prog="lean-poc-deploy app")
|
|
57
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
58
|
+
|
|
59
|
+
inventory = sub.add_parser(
|
|
60
|
+
"inventory",
|
|
61
|
+
help="Read Kit-managed application runtime ownership/readiness without writes",
|
|
62
|
+
)
|
|
63
|
+
inventory.add_argument("name")
|
|
64
|
+
inventory.add_argument("--json", action="store_true")
|
|
65
|
+
|
|
66
|
+
deploy = sub.add_parser(
|
|
67
|
+
"deploy",
|
|
68
|
+
help="Deploy one immutable application runtime without modifying routes",
|
|
69
|
+
)
|
|
70
|
+
deploy.add_argument("name")
|
|
71
|
+
deploy.add_argument("--owner-repo", required=True)
|
|
72
|
+
deploy.add_argument("--environment", required=True)
|
|
73
|
+
deploy.add_argument("--app-slug", required=True)
|
|
74
|
+
deploy.add_argument("--container-name", required=True)
|
|
75
|
+
deploy.add_argument("--image", required=True)
|
|
76
|
+
deploy.add_argument("--runtime-port", type=int, required=True)
|
|
77
|
+
deploy.add_argument("--health-path", required=True)
|
|
78
|
+
deploy.add_argument("--run-as", required=True)
|
|
79
|
+
deploy.add_argument(
|
|
80
|
+
"--persistence-mode",
|
|
81
|
+
choices=("none", "directory"),
|
|
82
|
+
required=True,
|
|
83
|
+
)
|
|
84
|
+
deploy.add_argument("--data-container-path")
|
|
85
|
+
deploy.add_argument(
|
|
86
|
+
"--env",
|
|
87
|
+
action="append",
|
|
88
|
+
default=[],
|
|
89
|
+
help="Non-secret runtime environment value in NAME=value form; repeat as needed",
|
|
90
|
+
)
|
|
91
|
+
deploy.add_argument("--json", action="store_true")
|
|
92
|
+
deploy.add_argument("--yes", action="store_true")
|
|
93
|
+
return parser
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def main(argv: list[str] | None = None) -> int:
|
|
97
|
+
args = build_parser().parse_args(argv)
|
|
98
|
+
try:
|
|
99
|
+
if args.command == "inventory":
|
|
100
|
+
_print(app_inventory(args.name), as_json=args.json)
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
spec = normalize_app_spec(
|
|
104
|
+
owner_repo=args.owner_repo,
|
|
105
|
+
environment=args.environment,
|
|
106
|
+
app_slug=args.app_slug,
|
|
107
|
+
container_name=args.container_name,
|
|
108
|
+
image=args.image,
|
|
109
|
+
runtime_port=args.runtime_port,
|
|
110
|
+
health_path=args.health_path,
|
|
111
|
+
run_as=args.run_as,
|
|
112
|
+
persistence_mode=args.persistence_mode,
|
|
113
|
+
data_container_path=args.data_container_path,
|
|
114
|
+
environment_variables=_parse_env(args.env),
|
|
115
|
+
)
|
|
116
|
+
assert_app_spec_matches_persistence(args.name, spec)
|
|
117
|
+
plan = plan_app_deploy(args.name, spec)
|
|
118
|
+
_print(
|
|
119
|
+
{key: value for key, value in plan.items() if key != "spec"},
|
|
120
|
+
as_json=False,
|
|
121
|
+
)
|
|
122
|
+
phrase = deploy_write_phrase(plan)
|
|
123
|
+
_confirm(
|
|
124
|
+
args,
|
|
125
|
+
phrase,
|
|
126
|
+
f"deploy {plan['appSlug']} ({plan['environment']}) on target {args.name}",
|
|
127
|
+
)
|
|
128
|
+
_print(apply_app_deploy(plan), as_json=args.json)
|
|
129
|
+
return 0
|
|
130
|
+
except DeploymentKitError as error:
|
|
131
|
+
print(f"ERROR: {error}", file=sys.stderr)
|
|
132
|
+
return 2
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
__all__ = [
|
|
136
|
+
"APP_DEPLOY_WRITE_PHRASE",
|
|
137
|
+
"PRODUCTION_APP_DEPLOY_WRITE_PHRASE",
|
|
138
|
+
"build_parser",
|
|
139
|
+
"main",
|
|
140
|
+
]
|