gen3-dataops-toolkit 3.7.4__tar.gz → 3.8.1__tar.gz
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.
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/PKG-INFO +37 -27
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/README.md +36 -26
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/pyproject.toml +1 -1
- gen3_dataops_toolkit-3.8.1/src/g3dt/cli/_internal/resolve.py +85 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/_internal/runner.py +14 -2
- gen3_dataops_toolkit-3.8.1/src/g3dt/cli/_internal/safety.py +130 -0
- gen3_dataops_toolkit-3.8.1/src/g3dt/cli/config_cmds.py +722 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/delete_cmds.py +3 -2
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/dict_cmds.py +7 -4
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/ec2_cmds.py +7 -3
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/indexd_cmds.py +5 -3
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/jobs.py +5 -1
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/k8s.py +7 -4
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/main.py +39 -6
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/metadata.py +5 -3
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/pipeline_cmds.py +5 -2
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/release_cmds.py +3 -1
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/synth.py +66 -5
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/config.py +178 -16
- gen3_dataops_toolkit-3.8.1/src/g3dt/contexts.py +313 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/upload/metadata/upload_all_studies.sh +5 -1
- gen3_dataops_toolkit-3.7.4/src/g3dt/cli/_internal/resolve.py +0 -22
- gen3_dataops_toolkit-3.7.4/src/g3dt/cli/_internal/safety.py +0 -98
- gen3_dataops_toolkit-3.7.4/src/g3dt/cli/config_cmds.py +0 -325
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/__init__.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/__init__.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/_internal/__init__.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/_internal/dispatch.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/cli/_internal/registry.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/indexd/__init__.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/indexd/file_access.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/indexd/indexd_registrar.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/ingest/ingest.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/resolver.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/delete/delete_all_metadata_for_project.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/delete/delete_metadata.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/delete/delete_metadata_by_guid.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/dictionary/deploy_dd.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/dictionary/pull_dict.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/dictionary/upload_dictionary.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/indexd/register_indexd.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/indexd/verify_file_access.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/k8s_ops/argocd_restart_etl.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/k8s_ops/argocd_restart_ms.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/k8s_ops/argocd_restart_schema.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/k8s_ops/login_to_pod.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/k8s_ops/restart_etl_and_ms.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/synthetic_data/delete_synth_metadata_sheepdog.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/synthetic_data/full_deploy_dd_and_synth.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/synthetic_data/generate_synth_metadata.sh +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/synthetic_data/upload_synth_metadata_sheepdog.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/services/upload/metadata/upload_metadata.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/upload/__init__.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/upload/metadata_deleter.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/upload/metadata_submitter.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/upload/upload_synthdata_s3.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/utils/athena_utils.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/utils/dbt_utils.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/utils/release_writer.py +0 -0
- {gen3_dataops_toolkit-3.7.4 → gen3_dataops_toolkit-3.8.1}/src/g3dt/validate/validate.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gen3-dataops-toolkit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.8.1
|
|
4
4
|
Summary: Gen3 DataOps toolkit (g3dt): operate SSM-published Gen3 data pipeline environments
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: JoshuaHarris391
|
|
@@ -46,8 +46,9 @@ restarts. The dbt half of the platform lives in
|
|
|
46
46
|
[gen3-dbt-template](https://github.com/AustralianBioCommons/gen3-dbt-template).
|
|
47
47
|
|
|
48
48
|
**No AWS resource name is compiled into this package.** The same wheel
|
|
49
|
-
operates any project: it is targeted
|
|
50
|
-
(
|
|
49
|
+
operates any project: it is targeted by a **context** — a named
|
|
50
|
+
(project, env, profile, region) tuple — and the project's SSM tree
|
|
51
|
+
(`/{project}/{env}/...`).
|
|
51
52
|
|
|
52
53
|
## Install
|
|
53
54
|
|
|
@@ -55,41 +56,50 @@ operates any project: it is targeted purely by `--env`, the project's SSM tree
|
|
|
55
56
|
pip install gen3-dataops-toolkit
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
## Bootstrap
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
program_id: program1
|
|
74
|
-
s3_metadata_path: s3://my-bucket/metadata/mystudy/
|
|
59
|
+
## Bootstrap: discover, then use
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
g3dt config discover <aws-profile> --add # logs the profile in if needed,
|
|
63
|
+
# lists every deployed env its
|
|
64
|
+
# account holds, registers them
|
|
65
|
+
g3dt config contexts # list them (current marked *)
|
|
66
|
+
g3dt config use myproj/test # point g3dt at one
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
That writes `~/.g3dt/g3dt.yaml` for you. **Every command prints the active
|
|
70
|
+
context first** (on stderr) — read that line before anything else:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
ctx myproj/test → project=myproj env=test profile=myproj_test region=ap-southeast-2
|
|
75
74
|
```
|
|
76
75
|
|
|
76
|
+
Production contexts are marked `[PROD]`, switching to one asks for
|
|
77
|
+
confirmation, and destructive actions on them require typing the context
|
|
78
|
+
name — `--yes` never bypasses that.
|
|
79
|
+
|
|
80
|
+
The marker can also be written by hand (design doc:
|
|
81
|
+
`docs/design/contexts.md`); the study registry lives either in a top-level
|
|
82
|
+
`studies:` block or per env at `s3://<metadata-bucket>/config/studies.yaml`.
|
|
77
83
|
Search order: `./g3dt.yaml` → `~/.g3dt/g3dt.yaml` → `/etc/g3dt/g3dt.yaml`
|
|
78
|
-
(the EC2 job box's copy, written by CDK user-data).
|
|
79
|
-
`
|
|
84
|
+
(the EC2 job box's copy, written by CDK user-data). Legacy markers
|
|
85
|
+
(`project`/`default_env`/`profiles:` keys) keep working unchanged, as do the
|
|
86
|
+
env-var overrides `G3DT_PROJECT`, `AWS_REGION`, `G3DT_DEFAULT_ENV` — that is
|
|
87
|
+
the file-less CodeBuild/EC2 path.
|
|
80
88
|
|
|
81
89
|
## Quick start
|
|
82
90
|
|
|
83
91
|
```bash
|
|
84
|
-
g3dt config
|
|
85
|
-
g3dt
|
|
86
|
-
g3dt
|
|
87
|
-
g3dt metadata upload --study mystudy --env test --on ec2
|
|
92
|
+
g3dt config show # every resolved name — the safety check
|
|
93
|
+
g3dt ec2 up # start the context's job box (SSM-managed)
|
|
94
|
+
g3dt metadata upload --study mystudy --on ec2
|
|
88
95
|
g3dt jobs logs <run-id> --follow # live logs; laptop can sleep, job keeps going
|
|
89
|
-
g3dt ec2 down
|
|
96
|
+
g3dt ec2 down # or let the auto-stop alarm handle it
|
|
90
97
|
g3dt docs # the full operations overview
|
|
91
98
|
```
|
|
92
99
|
|
|
100
|
+
(`--env <name>` still works everywhere and selects the matching context;
|
|
101
|
+
CodeBuild's `g3dt config dbt-env --env $ENV` contract is unchanged.)
|
|
102
|
+
|
|
93
103
|
## How configuration works
|
|
94
104
|
|
|
95
105
|
There are exactly two kinds of configuration:
|
|
@@ -12,8 +12,9 @@ restarts. The dbt half of the platform lives in
|
|
|
12
12
|
[gen3-dbt-template](https://github.com/AustralianBioCommons/gen3-dbt-template).
|
|
13
13
|
|
|
14
14
|
**No AWS resource name is compiled into this package.** The same wheel
|
|
15
|
-
operates any project: it is targeted
|
|
16
|
-
(
|
|
15
|
+
operates any project: it is targeted by a **context** — a named
|
|
16
|
+
(project, env, profile, region) tuple — and the project's SSM tree
|
|
17
|
+
(`/{project}/{env}/...`).
|
|
17
18
|
|
|
18
19
|
## Install
|
|
19
20
|
|
|
@@ -21,41 +22,50 @@ operates any project: it is targeted purely by `--env`, the project's SSM tree
|
|
|
21
22
|
pip install gen3-dataops-toolkit
|
|
22
23
|
```
|
|
23
24
|
|
|
24
|
-
## Bootstrap
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
program_id: program1
|
|
40
|
-
s3_metadata_path: s3://my-bucket/metadata/mystudy/
|
|
25
|
+
## Bootstrap: discover, then use
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
g3dt config discover <aws-profile> --add # logs the profile in if needed,
|
|
29
|
+
# lists every deployed env its
|
|
30
|
+
# account holds, registers them
|
|
31
|
+
g3dt config contexts # list them (current marked *)
|
|
32
|
+
g3dt config use myproj/test # point g3dt at one
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
That writes `~/.g3dt/g3dt.yaml` for you. **Every command prints the active
|
|
36
|
+
context first** (on stderr) — read that line before anything else:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
ctx myproj/test → project=myproj env=test profile=myproj_test region=ap-southeast-2
|
|
41
40
|
```
|
|
42
41
|
|
|
42
|
+
Production contexts are marked `[PROD]`, switching to one asks for
|
|
43
|
+
confirmation, and destructive actions on them require typing the context
|
|
44
|
+
name — `--yes` never bypasses that.
|
|
45
|
+
|
|
46
|
+
The marker can also be written by hand (design doc:
|
|
47
|
+
`docs/design/contexts.md`); the study registry lives either in a top-level
|
|
48
|
+
`studies:` block or per env at `s3://<metadata-bucket>/config/studies.yaml`.
|
|
43
49
|
Search order: `./g3dt.yaml` → `~/.g3dt/g3dt.yaml` → `/etc/g3dt/g3dt.yaml`
|
|
44
|
-
(the EC2 job box's copy, written by CDK user-data).
|
|
45
|
-
`
|
|
50
|
+
(the EC2 job box's copy, written by CDK user-data). Legacy markers
|
|
51
|
+
(`project`/`default_env`/`profiles:` keys) keep working unchanged, as do the
|
|
52
|
+
env-var overrides `G3DT_PROJECT`, `AWS_REGION`, `G3DT_DEFAULT_ENV` — that is
|
|
53
|
+
the file-less CodeBuild/EC2 path.
|
|
46
54
|
|
|
47
55
|
## Quick start
|
|
48
56
|
|
|
49
57
|
```bash
|
|
50
|
-
g3dt config
|
|
51
|
-
g3dt
|
|
52
|
-
g3dt
|
|
53
|
-
g3dt metadata upload --study mystudy --env test --on ec2
|
|
58
|
+
g3dt config show # every resolved name — the safety check
|
|
59
|
+
g3dt ec2 up # start the context's job box (SSM-managed)
|
|
60
|
+
g3dt metadata upload --study mystudy --on ec2
|
|
54
61
|
g3dt jobs logs <run-id> --follow # live logs; laptop can sleep, job keeps going
|
|
55
|
-
g3dt ec2 down
|
|
62
|
+
g3dt ec2 down # or let the auto-stop alarm handle it
|
|
56
63
|
g3dt docs # the full operations overview
|
|
57
64
|
```
|
|
58
65
|
|
|
66
|
+
(`--env <name>` still works everywhere and selects the matching context;
|
|
67
|
+
CodeBuild's `g3dt config dbt-env --env $ENV` contract is unchanged.)
|
|
68
|
+
|
|
59
69
|
## How configuration works
|
|
60
70
|
|
|
61
71
|
There are exactly two kinds of configuration:
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Thin wrappers that turn config resolution errors into clean CLI exits.
|
|
2
|
+
|
|
3
|
+
3.8.0 additions: :func:`active_env` / :func:`announce_context` implement the
|
|
4
|
+
universal context banner (design doc docs/design/contexts.md section 6), and
|
|
5
|
+
:func:`rc_of` is the single resolved-config idiom that replaces the hand-rolled
|
|
6
|
+
``load_marker``/``require_project``/``env_base``/``aws_profile_for`` block that
|
|
7
|
+
used to be copy-pasted across command modules (one copy of which forgot the
|
|
8
|
+
``_ec2`` → ambient rule — the class of bug this unification removes).
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import typer
|
|
15
|
+
|
|
16
|
+
from g3dt import config, contexts
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def env_of(env: str) -> config.EnvConfig:
|
|
20
|
+
try:
|
|
21
|
+
return config.resolve_env(env)
|
|
22
|
+
except config.ConfigError as exc:
|
|
23
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
24
|
+
raise typer.Exit(1)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def study_of(study: str, env: str) -> config.StudyConfig:
|
|
28
|
+
try:
|
|
29
|
+
return config.resolve_study(study, env)
|
|
30
|
+
except config.ConfigError as exc:
|
|
31
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
32
|
+
raise typer.Exit(1)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def active_env(env: Optional[str]) -> str:
|
|
36
|
+
"""Resolve the acting context, print the banner, return the effective env.
|
|
37
|
+
|
|
38
|
+
Every command that takes ``--env`` calls this first. The returned string
|
|
39
|
+
keeps a caller-supplied ``_ec2`` suffix intact (dispatch/auth mechanics
|
|
40
|
+
downstream depend on it, and the remote wire form never changes).
|
|
41
|
+
"""
|
|
42
|
+
try:
|
|
43
|
+
ctx, effective = contexts.resolve_context(
|
|
44
|
+
ctx_name=contexts.override(), env=env
|
|
45
|
+
)
|
|
46
|
+
except config.ConfigError as exc:
|
|
47
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
48
|
+
raise typer.Exit(1)
|
|
49
|
+
contexts.set_active(ctx)
|
|
50
|
+
contexts.print_banner(ctx, effective)
|
|
51
|
+
return effective
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def announce_context() -> None:
|
|
55
|
+
"""Banner-only entry point for commands that take no ``--env``.
|
|
56
|
+
|
|
57
|
+
Never fails: with nothing configured it prints the "(none configured)"
|
|
58
|
+
banner and returns — `jobs list`, `version` etc. must keep working on a
|
|
59
|
+
bare machine.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
ctx, effective = contexts.resolve_context(required=False)
|
|
63
|
+
except config.ConfigError:
|
|
64
|
+
ctx, effective = None, None
|
|
65
|
+
contexts.set_active(ctx)
|
|
66
|
+
contexts.print_banner(ctx, effective)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def rc_of(env: str):
|
|
70
|
+
"""Resolved SSM config for ``env`` with the correct credential rule.
|
|
71
|
+
|
|
72
|
+
``_ec2``-suffixed envs always use the ambient chain (never a laptop
|
|
73
|
+
profile) — the rule three call sites applied and two forgot.
|
|
74
|
+
"""
|
|
75
|
+
from g3dt import resolver
|
|
76
|
+
|
|
77
|
+
marker = config.load_marker()
|
|
78
|
+
project = config.require_project(marker)
|
|
79
|
+
base = config.env_base(env)
|
|
80
|
+
profile = None if env.endswith("_ec2") else config.aws_profile_for(env, marker)
|
|
81
|
+
try:
|
|
82
|
+
return resolver.resolve(project, base, profile=profile)
|
|
83
|
+
except config.ConfigError as exc:
|
|
84
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
85
|
+
raise typer.Exit(1)
|
|
@@ -38,8 +38,20 @@ def python_script(relpath: str, *args: str) -> List[str]:
|
|
|
38
38
|
|
|
39
39
|
|
|
40
40
|
def bash_script(relpath: str, *args: str) -> List[str]:
|
|
41
|
-
"""Build an argv that runs a packaged shell script via bash.
|
|
42
|
-
|
|
41
|
+
"""Build an argv that runs a packaged shell script via bash.
|
|
42
|
+
|
|
43
|
+
The CLI's own interpreter is passed through as ``G3DT_PYTHON`` (via an
|
|
44
|
+
``env`` prefix, so no process-global mutation) because wrapped scripts
|
|
45
|
+
that shell back into packaged Python services must use the interpreter
|
|
46
|
+
that owns this g3dt installation. A bare ``python3`` is NOT that on
|
|
47
|
+
Amazon Linux 2023, where the system default is 3.9 but g3dt is installed
|
|
48
|
+
under 3.11 — observed live on acdc/staging as ``ModuleNotFoundError:
|
|
49
|
+
No module named 'g3dt'`` from every study in an upload-all run.
|
|
50
|
+
"""
|
|
51
|
+
return [
|
|
52
|
+
"env", f"G3DT_PYTHON={sys.executable}",
|
|
53
|
+
"bash", str(package_path(relpath)), *[str(a) for a in args],
|
|
54
|
+
]
|
|
43
55
|
|
|
44
56
|
|
|
45
57
|
def run(
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Confirmation and environment guards for destructive / production operations.
|
|
2
|
+
|
|
3
|
+
These mirror (and strengthen) the guards already baked into the shell scripts:
|
|
4
|
+
the test-only ``synth deploy`` guard, the prod aborts in the bulk scripts, and
|
|
5
|
+
the optional delete confirmation prompts.
|
|
6
|
+
|
|
7
|
+
3.8.0: the guards are context-aware. Production is any env/study key whose
|
|
8
|
+
name contains ``prod`` (unchanged), OR an active context classified production
|
|
9
|
+
(``production: true`` flag, or 'prod' in its name — see
|
|
10
|
+
``contexts.is_production``). When a *named* context from the marker is active,
|
|
11
|
+
the typed confirmation token is the **context name** (e.g. ``acdc/prod``);
|
|
12
|
+
legacy and synthetic contexts keep the historical env-name/target tokens, so
|
|
13
|
+
older muscle memory and the pinned safety tests are unaffected. ``--yes``
|
|
14
|
+
never bypasses a production prompt, and confirmation always happens locally,
|
|
15
|
+
before any EC2 dispatch.
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import typer
|
|
22
|
+
|
|
23
|
+
from g3dt.config import env_base
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def is_prod(env: str) -> bool:
|
|
27
|
+
"""True if the environment name refers to production."""
|
|
28
|
+
return "prod" in env.lower()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _active_prod_context():
|
|
32
|
+
"""The active context when it is production-classified, else ``None``."""
|
|
33
|
+
from g3dt import contexts
|
|
34
|
+
|
|
35
|
+
ctx = contexts.active()
|
|
36
|
+
if ctx is not None and contexts.is_production(ctx):
|
|
37
|
+
return ctx
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _named(ctx) -> bool:
|
|
42
|
+
"""True for a context the operator configured by name (marker source)."""
|
|
43
|
+
return ctx is not None and ctx.source == "marker"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _typed_gate(header: str, token: str) -> None:
|
|
47
|
+
typer.secho(header, fg=typer.colors.RED, bold=True)
|
|
48
|
+
# default="" so an empty entry (just pressing Enter) returns immediately
|
|
49
|
+
# and aborts, instead of click re-prompting forever.
|
|
50
|
+
typed = typer.prompt(
|
|
51
|
+
f"Type '{token}' to confirm", default="", show_default=False
|
|
52
|
+
)
|
|
53
|
+
if typed.strip() != token:
|
|
54
|
+
typer.secho(
|
|
55
|
+
"Confirmation did not match. Aborting.",
|
|
56
|
+
fg=typer.colors.RED,
|
|
57
|
+
err=True,
|
|
58
|
+
)
|
|
59
|
+
raise typer.Exit(1)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def require_test_env(env: str) -> None:
|
|
63
|
+
"""Abort unless ``env`` is the test environment (``test`` or ``test_ec2``).
|
|
64
|
+
|
|
65
|
+
A hard guard for any command that must never run outside test. (The ``synth``
|
|
66
|
+
commands no longer use this — they allow any env and gate prod with
|
|
67
|
+
:func:`confirm_prod_strict` instead.)
|
|
68
|
+
"""
|
|
69
|
+
if env_base(env) != "test":
|
|
70
|
+
typer.secho(
|
|
71
|
+
f"Refusing: this command is only allowed for the 'test' "
|
|
72
|
+
f"environment (got '{env}').",
|
|
73
|
+
fg=typer.colors.RED,
|
|
74
|
+
err=True,
|
|
75
|
+
)
|
|
76
|
+
raise typer.Exit(2)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def confirm_destructive(action: str, target: str, env: str, assume_yes: bool) -> None:
|
|
80
|
+
"""Gate a destructive operation with an appropriate confirmation.
|
|
81
|
+
|
|
82
|
+
* Production (env name, study key, or the active context's
|
|
83
|
+
classification): ALWAYS require a typed confirmation, even with
|
|
84
|
+
``--yes`` (so automation can never silently delete prod data). The
|
|
85
|
+
token is the active context's name when one is configured, else the
|
|
86
|
+
``target``.
|
|
87
|
+
* Non-production: a simple y/N prompt, skippable with ``--yes``.
|
|
88
|
+
|
|
89
|
+
Confirmation always happens locally, before any EC2 dispatch (SSM has no
|
|
90
|
+
TTY), after which the remote job is invoked with ``--yes``.
|
|
91
|
+
"""
|
|
92
|
+
ctx = _active_prod_context()
|
|
93
|
+
if is_prod(env) or ctx is not None:
|
|
94
|
+
token = ctx.name if _named(ctx) else target
|
|
95
|
+
_typed_gate(
|
|
96
|
+
f"PRODUCTION {action} targeting '{target}' (env={env}"
|
|
97
|
+
+ (f", ctx={ctx.name}" if ctx is not None else "")
|
|
98
|
+
+ ").",
|
|
99
|
+
token,
|
|
100
|
+
)
|
|
101
|
+
return
|
|
102
|
+
|
|
103
|
+
if assume_yes:
|
|
104
|
+
return
|
|
105
|
+
if not typer.confirm(f"{action} targeting '{target}' (env={env}). Proceed?"):
|
|
106
|
+
typer.secho("Aborted.", fg=typer.colors.YELLOW)
|
|
107
|
+
raise typer.Exit(1)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def confirm_prod_strict(action: str, env: str) -> None:
|
|
111
|
+
"""Warn and require a typed confirmation before any action on production.
|
|
112
|
+
|
|
113
|
+
Production is any env whose name contains ``prod`` (see :func:`is_prod`)
|
|
114
|
+
or an active production-classified context. Non-production returns
|
|
115
|
+
immediately (no prompt). The confirmation cannot be bypassed, so
|
|
116
|
+
automation can never silently act on prod. The token is the active
|
|
117
|
+
context's name when one is configured, else the env name.
|
|
118
|
+
|
|
119
|
+
Used by the ``synth`` commands, which may target any configured environment.
|
|
120
|
+
"""
|
|
121
|
+
ctx = _active_prod_context()
|
|
122
|
+
if not is_prod(env) and ctx is None:
|
|
123
|
+
return
|
|
124
|
+
token = ctx.name if _named(ctx) else env
|
|
125
|
+
_typed_gate(
|
|
126
|
+
f"PRODUCTION {action} targeting env '{env}'"
|
|
127
|
+
+ (f" (ctx={ctx.name})" if ctx is not None else "")
|
|
128
|
+
+ ".",
|
|
129
|
+
token,
|
|
130
|
+
)
|