gen3-dataops-toolkit 4.2.0__tar.gz → 5.0.0__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-4.2.0 → gen3_dataops_toolkit-5.0.0}/PKG-INFO +62 -4
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/README.md +61 -3
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/pyproject.toml +1 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/config_cmds.py +52 -4
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/delete_cmds.py +50 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/dict_cmds.py +108 -6
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/indexd_cmds.py +3 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/jobs.py +24 -4
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/k8s.py +68 -10
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/main.py +71 -12
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/metadata.py +105 -8
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/pipeline_cmds.py +20 -4
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/study_cmds.py +131 -23
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/synth.py +47 -9
- gen3_dataops_toolkit-5.0.0/src/g3dt/import_order.py +265 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/delete/delete_all_metadata_for_project.py +41 -19
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/delete/delete_metadata.sh +18 -3
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/delete/delete_metadata_by_guid.py +40 -19
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/delete/delete_synth_metadata_by_version.py +40 -19
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/dictionary/deploy_dd.sh +7 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/k8s_ops/argocd_restart_etl.sh +1 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/k8s_ops/argocd_restart_ms.sh +1 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/k8s_ops/argocd_restart_schema.sh +1 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/k8s_ops/restart_etl_and_ms.sh +13 -3
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/synthetic_data/full_deploy_dd_and_synth.sh +9 -2
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/upload/metadata/upload_all_studies.sh +8 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/upload/metadata/upload_metadata.py +55 -1
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/utils/athena_utils.py +0 -9
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/_internal/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/_internal/aws_quiet.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/_internal/dispatch.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/_internal/helptext.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/_internal/registry.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/_internal/resolve.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/_internal/runner.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/_internal/safety.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/ec2_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/cli/release_cmds.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/config.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/contexts.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/indexd/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/indexd/file_access.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/indexd/indexd_registrar.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/ingest/ingest.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/resolver.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/dictionary/pull_dict.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/dictionary/upload_dictionary.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/indexd/register_indexd.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/indexd/verify_file_access.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/k8s_ops/login_to_pod.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/synthetic_data/delete_synth_metadata_sheepdog.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/synthetic_data/generate_synth_metadata.sh +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/services/synthetic_data/upload_synth_metadata_sheepdog.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/studies.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/upload/__init__.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/upload/metadata_deleter.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/upload/metadata_submitter.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/upload/upload_synthdata_s3.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/utils/dbt_utils.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/src/g3dt/utils/release_writer.py +0 -0
- {gen3_dataops_toolkit-4.2.0 → gen3_dataops_toolkit-5.0.0}/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
|
+
Version: 5.0.0
|
|
4
4
|
Summary: Gen3 DataOps toolkit (g3dt): operate SSM-published Gen3 data pipeline environments
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Author: JoshuaHarris391
|
|
@@ -37,7 +37,7 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
Operate Gen3 AWS data-pipeline environments from one pip-installable CLI.
|
|
38
38
|
|
|
39
39
|
`g3dt` is the tooling half of the Gen3 DataOps platform: the
|
|
40
|
-
[
|
|
40
|
+
[aws-gen3-pipeline](https://github.com/AustralianBioCommons/aws-gen3-pipeline)
|
|
41
41
|
CDK app deploys a complete pipeline per project/environment and publishes every
|
|
42
42
|
resource name to AWS SSM Parameter Store; `g3dt` resolves those names at
|
|
43
43
|
runtime and gives operators one command surface for dictionary deploys,
|
|
@@ -53,9 +53,15 @@ operates any project: it is targeted by a **context** — a named
|
|
|
53
53
|
## Install
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
|
-
|
|
56
|
+
pipx install gen3-dataops-toolkit # a CLI in its own venv (recommended)
|
|
57
|
+
pip install gen3-dataops-toolkit # or into an environment you manage
|
|
57
58
|
```
|
|
58
59
|
|
|
60
|
+
Synthetic-data generation needs the simulator too: `g3dt synth
|
|
61
|
+
install-simulator` installs it into whichever environment owns `g3dt`
|
|
62
|
+
(for pipx that is `pipx inject gen3-dataops-toolkit gen3-metadata-simulator`).
|
|
63
|
+
`g3dt --version` reports the installed toolkit version.
|
|
64
|
+
|
|
59
65
|
## Bootstrap: discover, then use
|
|
60
66
|
|
|
61
67
|
```bash
|
|
@@ -111,7 +117,7 @@ CodeBuild's `g3dt config dbt-env --env $ENV` contract is unchanged.)
|
|
|
111
117
|
There are exactly two kinds of configuration:
|
|
112
118
|
|
|
113
119
|
- **INPUTS** — human-authored values, committed as
|
|
114
|
-
`config/<
|
|
120
|
+
`config/<project>.<env>.json` in the deployment wrapper repo and read only by
|
|
115
121
|
`cdk deploy`. To change what an environment *declares*, edit that file and
|
|
116
122
|
redeploy — the value flows to SSM.
|
|
117
123
|
- **OUTPUTS** — every resource name the CDK creates plus the mirrored Gen3
|
|
@@ -169,6 +175,35 @@ gate fails -> inspect the results table -> fix data -> re-run until green.
|
|
|
169
175
|
multi-study caller resolves the schema once, lists the validation prefix
|
|
170
176
|
once, and batches all studies into a single Iceberg INSERT.
|
|
171
177
|
|
|
178
|
+
### The study registry: what an upload sends, and from where
|
|
179
|
+
|
|
180
|
+
Each study the pipeline can upload is one JSON record in the env's SSM tree
|
|
181
|
+
(`/{project}/{env}/studies/<name>`) holding its Gen3 `project_id`,
|
|
182
|
+
`program_id` and `s3_metadata_path` — the `s3://` prefix of that study's
|
|
183
|
+
release JSONs (`DataImportOrder.txt` plus one `<node>.json` per node, exactly
|
|
184
|
+
as the release export writes them). The environment lives in the path, so
|
|
185
|
+
staging and prod records can never cross-resolve. `g3dt config` shows the
|
|
186
|
+
registry but never edits deployed settings; the registry is the one thing the
|
|
187
|
+
toolkit itself writes, through `g3dt study` (design: `docs/design/studies.md`):
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
g3dt study list # bare names (config studies is an alias)
|
|
191
|
+
g3dt study show cdah # record + liveness check of its S3 path
|
|
192
|
+
g3dt study add cdah --project-id CAUGHT-CAD --program-id program1 \
|
|
193
|
+
--path s3://my-gold/release_jsons/v2.0.0/cdah/
|
|
194
|
+
g3dt study set cdah --path s3://my-gold/release_jsons/v2.1.0/cdah/ --dry-run
|
|
195
|
+
g3dt study set cdah --path s3://my-gold/release_jsons/v2.1.0/cdah/ # config study-set is an alias
|
|
196
|
+
g3dt study repoint --release v2.1.0 # every study at once (--latest: newest tag)
|
|
197
|
+
g3dt study remove cdah # registry entry only; data untouched
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`set` and `repoint` check a new path in S3 before writing anything (the same
|
|
201
|
+
`DataImportOrder.txt` + node-JSON checks `metadata upload` makes), so a typo
|
|
202
|
+
cannot point an upload at nothing; `set --no-verify` records a path whose
|
|
203
|
+
release is not exported yet. Every write on a production context requires
|
|
204
|
+
typing the context name. To upload one release without moving the registry,
|
|
205
|
+
use `g3dt metadata upload --release <tag>` (5.0.0).
|
|
206
|
+
|
|
172
207
|
### Where the data dictionary comes from
|
|
173
208
|
|
|
174
209
|
Composed from the env's inputs as
|
|
@@ -194,6 +229,20 @@ version until the CDK config catches up —
|
|
|
194
229
|
`g3dt config diff --env <env> --file <wrapper>/config/<project>.<env>.json`
|
|
195
230
|
reports exactly that gap and exits 1, so it can gate CI.
|
|
196
231
|
|
|
232
|
+
### Deleting metadata: where the node order comes from
|
|
233
|
+
|
|
234
|
+
`delete metadata` walks nodes children-before-parents. The order is resolved
|
|
235
|
+
from the first available of: an explicit `--import-order <path|s3://…>`
|
|
236
|
+
(failures are fatal — an explicit source is never silently skipped), the
|
|
237
|
+
registered study's release bucket (`DataImportOrder.txt` next to the release's
|
|
238
|
+
node JSONs), a `DataImportOrder.txt` in the current directory, or a
|
|
239
|
+
topological sort derived from the dictionary itself — the `--dict-version`
|
|
240
|
+
tag's bundle when given (downloaded to `~/.g3dt/schemas` if needed), else the
|
|
241
|
+
env's deployed dictionary. Deriving matters when deleting data submitted
|
|
242
|
+
under an older dictionary whose node layout differs from today's: pass
|
|
243
|
+
`--dict-version <old-tag>`. With `--on ec2`, `--import-order` must be an
|
|
244
|
+
`s3://` URI (a laptop path does not exist on the box).
|
|
245
|
+
|
|
197
246
|
Synthetic data is only schema-valid against the dictionary that generated it, so
|
|
198
247
|
`synth generate` records the dictionary version in each batch and `synth upload`
|
|
199
248
|
refuses a batch that doesn't match the version being uploaded (override with
|
|
@@ -274,6 +323,15 @@ one run, and environments deployed without the block keep the classic Gen3 set
|
|
|
274
323
|
a manually redeployed frontend) simply omits it from the list in its wrapper
|
|
275
324
|
config. `g3dt config show --env <env>` prints the resolved values.
|
|
276
325
|
|
|
326
|
+
**Syncing ArgoCD is opt-in.** None of these flows runs `argocd app sync`
|
|
327
|
+
unless you pass `--sync` (available on `k8s restart-schema`, `k8s restart-etl`,
|
|
328
|
+
`k8s restart-ms`, `dict deploy` and `synth deploy`). Add it only when the
|
|
329
|
+
commons app is behind the merged revision; a sync that fails on unrelated
|
|
330
|
+
drift (for example a Job whose template changed, which Kubernetes refuses to
|
|
331
|
+
patch) aborts the whole command before anything restarts. Before 5.0.0
|
|
332
|
+
`restart-ms` and `synth deploy` always synced first, and `restart-ms` ignored
|
|
333
|
+
`--restart-services`; both are fixed.
|
|
334
|
+
|
|
277
335
|
## Verifying download access (check-download)
|
|
278
336
|
|
|
279
337
|
Registration alone does not prove a file can be downloaded. Two failure modes
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Operate Gen3 AWS data-pipeline environments from one pip-installable CLI.
|
|
4
4
|
|
|
5
5
|
`g3dt` is the tooling half of the Gen3 DataOps platform: the
|
|
6
|
-
[
|
|
6
|
+
[aws-gen3-pipeline](https://github.com/AustralianBioCommons/aws-gen3-pipeline)
|
|
7
7
|
CDK app deploys a complete pipeline per project/environment and publishes every
|
|
8
8
|
resource name to AWS SSM Parameter Store; `g3dt` resolves those names at
|
|
9
9
|
runtime and gives operators one command surface for dictionary deploys,
|
|
@@ -19,9 +19,15 @@ operates any project: it is targeted by a **context** — a named
|
|
|
19
19
|
## Install
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
22
|
+
pipx install gen3-dataops-toolkit # a CLI in its own venv (recommended)
|
|
23
|
+
pip install gen3-dataops-toolkit # or into an environment you manage
|
|
23
24
|
```
|
|
24
25
|
|
|
26
|
+
Synthetic-data generation needs the simulator too: `g3dt synth
|
|
27
|
+
install-simulator` installs it into whichever environment owns `g3dt`
|
|
28
|
+
(for pipx that is `pipx inject gen3-dataops-toolkit gen3-metadata-simulator`).
|
|
29
|
+
`g3dt --version` reports the installed toolkit version.
|
|
30
|
+
|
|
25
31
|
## Bootstrap: discover, then use
|
|
26
32
|
|
|
27
33
|
```bash
|
|
@@ -77,7 +83,7 @@ CodeBuild's `g3dt config dbt-env --env $ENV` contract is unchanged.)
|
|
|
77
83
|
There are exactly two kinds of configuration:
|
|
78
84
|
|
|
79
85
|
- **INPUTS** — human-authored values, committed as
|
|
80
|
-
`config/<
|
|
86
|
+
`config/<project>.<env>.json` in the deployment wrapper repo and read only by
|
|
81
87
|
`cdk deploy`. To change what an environment *declares*, edit that file and
|
|
82
88
|
redeploy — the value flows to SSM.
|
|
83
89
|
- **OUTPUTS** — every resource name the CDK creates plus the mirrored Gen3
|
|
@@ -135,6 +141,35 @@ gate fails -> inspect the results table -> fix data -> re-run until green.
|
|
|
135
141
|
multi-study caller resolves the schema once, lists the validation prefix
|
|
136
142
|
once, and batches all studies into a single Iceberg INSERT.
|
|
137
143
|
|
|
144
|
+
### The study registry: what an upload sends, and from where
|
|
145
|
+
|
|
146
|
+
Each study the pipeline can upload is one JSON record in the env's SSM tree
|
|
147
|
+
(`/{project}/{env}/studies/<name>`) holding its Gen3 `project_id`,
|
|
148
|
+
`program_id` and `s3_metadata_path` — the `s3://` prefix of that study's
|
|
149
|
+
release JSONs (`DataImportOrder.txt` plus one `<node>.json` per node, exactly
|
|
150
|
+
as the release export writes them). The environment lives in the path, so
|
|
151
|
+
staging and prod records can never cross-resolve. `g3dt config` shows the
|
|
152
|
+
registry but never edits deployed settings; the registry is the one thing the
|
|
153
|
+
toolkit itself writes, through `g3dt study` (design: `docs/design/studies.md`):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
g3dt study list # bare names (config studies is an alias)
|
|
157
|
+
g3dt study show cdah # record + liveness check of its S3 path
|
|
158
|
+
g3dt study add cdah --project-id CAUGHT-CAD --program-id program1 \
|
|
159
|
+
--path s3://my-gold/release_jsons/v2.0.0/cdah/
|
|
160
|
+
g3dt study set cdah --path s3://my-gold/release_jsons/v2.1.0/cdah/ --dry-run
|
|
161
|
+
g3dt study set cdah --path s3://my-gold/release_jsons/v2.1.0/cdah/ # config study-set is an alias
|
|
162
|
+
g3dt study repoint --release v2.1.0 # every study at once (--latest: newest tag)
|
|
163
|
+
g3dt study remove cdah # registry entry only; data untouched
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`set` and `repoint` check a new path in S3 before writing anything (the same
|
|
167
|
+
`DataImportOrder.txt` + node-JSON checks `metadata upload` makes), so a typo
|
|
168
|
+
cannot point an upload at nothing; `set --no-verify` records a path whose
|
|
169
|
+
release is not exported yet. Every write on a production context requires
|
|
170
|
+
typing the context name. To upload one release without moving the registry,
|
|
171
|
+
use `g3dt metadata upload --release <tag>` (5.0.0).
|
|
172
|
+
|
|
138
173
|
### Where the data dictionary comes from
|
|
139
174
|
|
|
140
175
|
Composed from the env's inputs as
|
|
@@ -160,6 +195,20 @@ version until the CDK config catches up —
|
|
|
160
195
|
`g3dt config diff --env <env> --file <wrapper>/config/<project>.<env>.json`
|
|
161
196
|
reports exactly that gap and exits 1, so it can gate CI.
|
|
162
197
|
|
|
198
|
+
### Deleting metadata: where the node order comes from
|
|
199
|
+
|
|
200
|
+
`delete metadata` walks nodes children-before-parents. The order is resolved
|
|
201
|
+
from the first available of: an explicit `--import-order <path|s3://…>`
|
|
202
|
+
(failures are fatal — an explicit source is never silently skipped), the
|
|
203
|
+
registered study's release bucket (`DataImportOrder.txt` next to the release's
|
|
204
|
+
node JSONs), a `DataImportOrder.txt` in the current directory, or a
|
|
205
|
+
topological sort derived from the dictionary itself — the `--dict-version`
|
|
206
|
+
tag's bundle when given (downloaded to `~/.g3dt/schemas` if needed), else the
|
|
207
|
+
env's deployed dictionary. Deriving matters when deleting data submitted
|
|
208
|
+
under an older dictionary whose node layout differs from today's: pass
|
|
209
|
+
`--dict-version <old-tag>`. With `--on ec2`, `--import-order` must be an
|
|
210
|
+
`s3://` URI (a laptop path does not exist on the box).
|
|
211
|
+
|
|
163
212
|
Synthetic data is only schema-valid against the dictionary that generated it, so
|
|
164
213
|
`synth generate` records the dictionary version in each batch and `synth upload`
|
|
165
214
|
refuses a batch that doesn't match the version being uploaded (override with
|
|
@@ -240,6 +289,15 @@ one run, and environments deployed without the block keep the classic Gen3 set
|
|
|
240
289
|
a manually redeployed frontend) simply omits it from the list in its wrapper
|
|
241
290
|
config. `g3dt config show --env <env>` prints the resolved values.
|
|
242
291
|
|
|
292
|
+
**Syncing ArgoCD is opt-in.** None of these flows runs `argocd app sync`
|
|
293
|
+
unless you pass `--sync` (available on `k8s restart-schema`, `k8s restart-etl`,
|
|
294
|
+
`k8s restart-ms`, `dict deploy` and `synth deploy`). Add it only when the
|
|
295
|
+
commons app is behind the merged revision; a sync that fails on unrelated
|
|
296
|
+
drift (for example a Job whose template changed, which Kubernetes refuses to
|
|
297
|
+
patch) aborts the whole command before anything restarts. Before 5.0.0
|
|
298
|
+
`restart-ms` and `synth deploy` always synced first, and `restart-ms` ignored
|
|
299
|
+
`--restart-services`; both are fixed.
|
|
300
|
+
|
|
243
301
|
## Verifying download access (check-download)
|
|
244
302
|
|
|
245
303
|
Registration alone does not prove a file can be downloaded. Two failure modes
|
|
@@ -24,6 +24,7 @@ from typing import List, Optional
|
|
|
24
24
|
import typer
|
|
25
25
|
|
|
26
26
|
from g3dt import config, contexts
|
|
27
|
+
from g3dt.cli import study_cmds
|
|
27
28
|
from g3dt.cli._internal import resolve
|
|
28
29
|
from g3dt.cli._internal.resolve import env_of, study_of
|
|
29
30
|
from g3dt.cli._internal.helptext import ENV_OPT
|
|
@@ -35,8 +36,11 @@ app = typer.Typer(
|
|
|
35
36
|
"stored in the local g3dt.yaml marker. Every command acts on the "
|
|
36
37
|
"current context — switch with 'g3dt config use <name>', one-shot "
|
|
37
38
|
"with --ctx. Discovery scans one SSO profile's AWS account at a "
|
|
38
|
-
"time ('g3dt config discover <profile>').
|
|
39
|
-
"
|
|
39
|
+
"time ('g3dt config discover <profile>'). Deployed settings are "
|
|
40
|
+
"read-only here (they are CDK inputs); study records are edited "
|
|
41
|
+
"with 'g3dt config study-set' / 'g3dt study set' and moved to a "
|
|
42
|
+
"release with 'g3dt study repoint'. Run 'g3dt docs' for the full "
|
|
43
|
+
"model.",
|
|
40
44
|
)
|
|
41
45
|
|
|
42
46
|
|
|
@@ -530,12 +534,51 @@ def envs() -> None:
|
|
|
530
534
|
def studies(
|
|
531
535
|
env: Optional[str] = typer.Option(None, "--env", "-e", help=ENV_OPT),
|
|
532
536
|
) -> None:
|
|
533
|
-
"""List the env's registered studies (alias of `g3dt study list`).
|
|
534
|
-
from g3dt.cli import study_cmds
|
|
537
|
+
"""List the env's registered studies (alias of `g3dt study list`).
|
|
535
538
|
|
|
539
|
+
Inspect one with `g3dt config show --study <name>`; change its S3 path
|
|
540
|
+
or Gen3 ids with `g3dt config study-set <name> ...`.
|
|
541
|
+
"""
|
|
536
542
|
study_cmds.list_impl(env)
|
|
537
543
|
|
|
538
544
|
|
|
545
|
+
@app.command("study-set")
|
|
546
|
+
def study_set(
|
|
547
|
+
name: str = typer.Argument(..., help="Study name (see `g3dt config studies`)."),
|
|
548
|
+
env: Optional[str] = typer.Option(None, "--env", "-e", help=ENV_OPT),
|
|
549
|
+
project_id: Optional[str] = typer.Option(
|
|
550
|
+
None, "--project-id", help=study_cmds.SET_PROJECT_ID_HELP
|
|
551
|
+
),
|
|
552
|
+
program_id: Optional[str] = typer.Option(
|
|
553
|
+
None, "--program-id", help=study_cmds.SET_PROGRAM_ID_HELP
|
|
554
|
+
),
|
|
555
|
+
path: Optional[str] = typer.Option(None, "--path", help=study_cmds.SET_PATH_HELP),
|
|
556
|
+
dry_run: bool = typer.Option(
|
|
557
|
+
False, "--dry-run", "-d", help=study_cmds.SET_DRY_RUN_HELP
|
|
558
|
+
),
|
|
559
|
+
no_verify: bool = typer.Option(
|
|
560
|
+
False, "--no-verify", help=study_cmds.SET_NO_VERIFY_HELP
|
|
561
|
+
),
|
|
562
|
+
) -> None:
|
|
563
|
+
"""Update a study's S3 release location or Gen3 ids (alias of `g3dt study set`).
|
|
564
|
+
|
|
565
|
+
Only the fields you pass change. A new --path is checked in S3 first
|
|
566
|
+
(DataImportOrder.txt plus node JSONs) so an upload can never be pointed
|
|
567
|
+
at nothing. To move every study to a new release at once use
|
|
568
|
+
`g3dt study repoint`.
|
|
569
|
+
|
|
570
|
+
Examples:
|
|
571
|
+
g3dt config study-set cdah --path s3://my-gold/release_jsons/v2.1.0/cdah/ --dry-run
|
|
572
|
+
g3dt config study-set cdah --path s3://my-gold/release_jsons/v2.1.0/cdah/
|
|
573
|
+
|
|
574
|
+
Targeting production requires typing the context/env name to confirm.
|
|
575
|
+
"""
|
|
576
|
+
study_cmds.set_impl(
|
|
577
|
+
name, env, project_id, program_id, path,
|
|
578
|
+
dry_run=dry_run, verify=not no_verify,
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
|
|
539
582
|
@app.command()
|
|
540
583
|
def show(
|
|
541
584
|
env: Optional[str] = typer.Option(None, "--env", "-e", help=ENV_OPT),
|
|
@@ -586,6 +629,11 @@ def show(
|
|
|
586
629
|
typer.echo(f" project_id : {s.project_id}")
|
|
587
630
|
typer.echo(f" program_id : {s.program_id}")
|
|
588
631
|
typer.echo(f" s3_metadata_path : {s.s3_metadata_path}")
|
|
632
|
+
typer.secho(
|
|
633
|
+
f" change with : g3dt config study-set {s.key} --path s3://... "
|
|
634
|
+
f"| g3dt study repoint --release <tag>",
|
|
635
|
+
fg=typer.colors.BRIGHT_BLACK,
|
|
636
|
+
)
|
|
589
637
|
if full:
|
|
590
638
|
rc = resolve.rc_of(env)
|
|
591
639
|
typer.secho(
|
|
@@ -14,6 +14,12 @@ synthetic projects are never registered), bare names default to version
|
|
|
14
14
|
``data_version`` property via GraphQL rather than Athena receipts (synthetic
|
|
15
15
|
uploads write none).
|
|
16
16
|
|
|
17
|
+
The node deletion order comes from, in order: an explicit ``--import-order``
|
|
18
|
+
(path or s3:// URI; failures are fatal, never silently skipped), the
|
|
19
|
+
registered study's release bucket, a ``DataImportOrder.txt`` in the current
|
|
20
|
+
directory, or a topological sort derived from the dictionary itself — the
|
|
21
|
+
``--dict-version`` bundle when given, else the env's deployed dictionary.
|
|
22
|
+
|
|
17
23
|
Every command confirms before acting. Production always requires typing the
|
|
18
24
|
target id, even with ``--yes``. Deleting ALL versions always prompts, even with
|
|
19
25
|
``--yes``. Confirmation happens locally before any EC2 dispatch (SSM has no
|
|
@@ -186,6 +192,21 @@ def metadata(
|
|
|
186
192
|
help="Gen3 program for --synthetic (default: program1). "
|
|
187
193
|
"Invalid without --synthetic.",
|
|
188
194
|
),
|
|
195
|
+
import_order: Optional[str] = typer.Option(
|
|
196
|
+
None,
|
|
197
|
+
"--import-order",
|
|
198
|
+
help="Path or s3:// URI of DataImportOrder.txt. Default: auto — the "
|
|
199
|
+
"study's release bucket (registered studies), then "
|
|
200
|
+
"./DataImportOrder.txt, then derived from the dictionary. "
|
|
201
|
+
"With --on ec2 only s3:// URIs are accepted.",
|
|
202
|
+
),
|
|
203
|
+
dict_version: Optional[str] = typer.Option(
|
|
204
|
+
None,
|
|
205
|
+
"--dict-version",
|
|
206
|
+
help="Dictionary git tag to derive the node order from (verbatim, "
|
|
207
|
+
"e.g. v1.3.0). Default: the env's deployed dictionary. Only "
|
|
208
|
+
"used when the order is derived.",
|
|
209
|
+
),
|
|
189
210
|
yes: bool = typer.Option(
|
|
190
211
|
False, "--yes", "-y", help="Skip the non-prod prompt (specific-version only)."
|
|
191
212
|
),
|
|
@@ -212,6 +233,26 @@ def metadata(
|
|
|
212
233
|
err=True,
|
|
213
234
|
)
|
|
214
235
|
raise typer.Exit(2)
|
|
236
|
+
if import_order and dict_version:
|
|
237
|
+
typer.secho(
|
|
238
|
+
"--import-order names the exact file; --dict-version derives one "
|
|
239
|
+
"— pass only one.",
|
|
240
|
+
fg=typer.colors.RED,
|
|
241
|
+
err=True,
|
|
242
|
+
)
|
|
243
|
+
raise typer.Exit(2)
|
|
244
|
+
# A laptop path forwarded to the EC2 box would resolve against the box's
|
|
245
|
+
# filesystem — at best a crash after confirmation, at worst a same-named
|
|
246
|
+
# DIFFERENT file ordering the delete. s3:// URIs (and --dict-version)
|
|
247
|
+
# resolve identically anywhere, so only those may travel.
|
|
248
|
+
if on == Target.ec2 and import_order and not import_order.startswith("s3://"):
|
|
249
|
+
typer.secho(
|
|
250
|
+
"--import-order with --on ec2 must be an s3:// URI (a local path "
|
|
251
|
+
"does not exist on the box). Upload the file, or run locally.",
|
|
252
|
+
fg=typer.colors.RED,
|
|
253
|
+
err=True,
|
|
254
|
+
)
|
|
255
|
+
raise typer.Exit(2)
|
|
215
256
|
if version is None:
|
|
216
257
|
fallback = None
|
|
217
258
|
elif synthetic:
|
|
@@ -266,6 +307,10 @@ def metadata(
|
|
|
266
307
|
# optional on the wire, but an explicit value keeps the contract
|
|
267
308
|
# visible in logs and SSM command history.
|
|
268
309
|
a += ["--synthetic", "--program-id", program_id or "program1"]
|
|
310
|
+
if import_order:
|
|
311
|
+
a += ["--import-order", import_order]
|
|
312
|
+
if dict_version:
|
|
313
|
+
a += ["--dict-version", dict_version]
|
|
269
314
|
return a
|
|
270
315
|
|
|
271
316
|
def remote_cli(env_name):
|
|
@@ -284,6 +329,11 @@ def metadata(
|
|
|
284
329
|
a.append("--synthetic")
|
|
285
330
|
if program_id is not None:
|
|
286
331
|
a += ["--program-id", program_id]
|
|
332
|
+
if import_order:
|
|
333
|
+
# Guaranteed s3:// by the pre-dispatch gate above.
|
|
334
|
+
a += ["--import-order", import_order]
|
|
335
|
+
if dict_version:
|
|
336
|
+
a += ["--dict-version", dict_version]
|
|
287
337
|
return a
|
|
288
338
|
|
|
289
339
|
dispatch.run_or_dispatch(
|
|
@@ -14,12 +14,21 @@ from typing import Optional
|
|
|
14
14
|
|
|
15
15
|
import typer
|
|
16
16
|
|
|
17
|
-
from g3dt.config import
|
|
17
|
+
from g3dt.config import (
|
|
18
|
+
ConfigError,
|
|
19
|
+
dictionary_filename,
|
|
20
|
+
dictionary_url,
|
|
21
|
+
normalize_s3_location,
|
|
22
|
+
script_env,
|
|
23
|
+
)
|
|
18
24
|
from g3dt.cli._internal import resolve, runner, safety
|
|
19
25
|
from g3dt.cli._internal.resolve import env_of
|
|
20
26
|
from g3dt.cli._internal.helptext import ENV_OPT
|
|
21
27
|
|
|
22
|
-
app = typer.Typer(
|
|
28
|
+
app = typer.Typer(
|
|
29
|
+
no_args_is_help=True,
|
|
30
|
+
help="Data dictionary operations: pull, upload, deploy, status (local).",
|
|
31
|
+
)
|
|
23
32
|
|
|
24
33
|
SCHEMA_DIR = Path("~/.g3dt/schemas").expanduser()
|
|
25
34
|
|
|
@@ -78,6 +87,82 @@ def pull(
|
|
|
78
87
|
)
|
|
79
88
|
|
|
80
89
|
|
|
90
|
+
@app.command()
|
|
91
|
+
def status(
|
|
92
|
+
env: Optional[str] = typer.Option(None, "--env", "-e", help=ENV_OPT),
|
|
93
|
+
strict: bool = typer.Option(
|
|
94
|
+
False, "--strict",
|
|
95
|
+
help="Exit 1 when the deployed version differs from the declared one.",
|
|
96
|
+
),
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Show which dictionary version is deployed, next to the declared one.
|
|
99
|
+
|
|
100
|
+
Declared is the env's `dictionary_version` (a CDK input — what
|
|
101
|
+
`g3dt config show` reports). Deployed is the `version` stamped on the S3
|
|
102
|
+
object at `schema_s3_uri` by `dict upload`, i.e. what the commons'
|
|
103
|
+
services actually read on their next restart. The two drift after
|
|
104
|
+
`dict deploy --version <tag>` until the CDK config is updated and
|
|
105
|
+
redeployed (`g3dt config diff` shows that half). Read-only.
|
|
106
|
+
|
|
107
|
+
Examples:
|
|
108
|
+
g3dt dict status --env staging
|
|
109
|
+
g3dt dict status --env staging --strict # drift gate for scripts
|
|
110
|
+
"""
|
|
111
|
+
from botocore.exceptions import ClientError
|
|
112
|
+
|
|
113
|
+
env = resolve.active_env(env)
|
|
114
|
+
e = env_of(env)
|
|
115
|
+
_, session = resolve.rc_session_of(env)
|
|
116
|
+
try:
|
|
117
|
+
location = normalize_s3_location(e.schema_s3_uri, param="schema_s3_uri")
|
|
118
|
+
except ConfigError as exc:
|
|
119
|
+
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
120
|
+
raise typer.Exit(1)
|
|
121
|
+
if "/" not in location:
|
|
122
|
+
typer.secho(
|
|
123
|
+
f"schema_s3_uri '{e.schema_s3_uri}' has no object key — expected "
|
|
124
|
+
f"<bucket>/<key>.", fg=typer.colors.RED, err=True,
|
|
125
|
+
)
|
|
126
|
+
raise typer.Exit(1)
|
|
127
|
+
bucket, key = location.split("/", 1)
|
|
128
|
+
deployed, uploaded = None, None
|
|
129
|
+
try:
|
|
130
|
+
head = session.client("s3").head_object(Bucket=bucket, Key=key)
|
|
131
|
+
deployed = head.get("Metadata", {}).get("version")
|
|
132
|
+
uploaded = head.get("LastModified")
|
|
133
|
+
except ClientError as exc:
|
|
134
|
+
code = exc.response.get("Error", {}).get("Code", "")
|
|
135
|
+
if code not in ("404", "NoSuchKey", "NotFound"):
|
|
136
|
+
typer.secho(
|
|
137
|
+
f"Cannot read s3://{location} ({code or exc}).",
|
|
138
|
+
fg=typer.colors.RED, err=True,
|
|
139
|
+
)
|
|
140
|
+
raise typer.Exit(1)
|
|
141
|
+
|
|
142
|
+
declared = e.dictionary_version
|
|
143
|
+
typer.echo(f"declared : {declared} (SSM app/dictionary_version)")
|
|
144
|
+
if uploaded is None:
|
|
145
|
+
typer.echo(f"deployed : (nothing at s3://{location} — run `g3dt dict deploy`)")
|
|
146
|
+
state = "MISSING"
|
|
147
|
+
else:
|
|
148
|
+
stamp = deployed or "(no version stamp on the object)"
|
|
149
|
+
when = uploaded.strftime("%Y-%m-%d %H:%M %Z") if hasattr(uploaded, "strftime") else uploaded
|
|
150
|
+
typer.echo(f"deployed : {stamp} (s3://{location}, uploaded {when})")
|
|
151
|
+
state = "in sync" if deployed == declared else "DRIFT"
|
|
152
|
+
if state == "in sync":
|
|
153
|
+
typer.secho("status : in sync", fg=typer.colors.GREEN)
|
|
154
|
+
return
|
|
155
|
+
typer.secho(
|
|
156
|
+
f"status : {state} — the services will read {deployed or 'nothing'} "
|
|
157
|
+
f"while the CDK config declares {declared}. Deploy with "
|
|
158
|
+
f"`g3dt dict deploy`, or update dictionaryVersion in the wrapper "
|
|
159
|
+
f"config and redeploy.",
|
|
160
|
+
fg=typer.colors.YELLOW,
|
|
161
|
+
)
|
|
162
|
+
if strict:
|
|
163
|
+
raise typer.Exit(1)
|
|
164
|
+
|
|
165
|
+
|
|
81
166
|
@app.command()
|
|
82
167
|
def upload(
|
|
83
168
|
env: Optional[str] = typer.Option(None, "--env", "-e", help=ENV_OPT),
|
|
@@ -88,6 +173,13 @@ def upload(
|
|
|
88
173
|
) -> None:
|
|
89
174
|
"""Upload the (already pulled) dictionary JSON to the env's S3 location.
|
|
90
175
|
|
|
176
|
+
Stamps the object's S3 metadata `version` from the JSON's _settings, which
|
|
177
|
+
is what `g3dt dict status` reads back.
|
|
178
|
+
|
|
179
|
+
Examples:
|
|
180
|
+
g3dt dict upload --env staging
|
|
181
|
+
g3dt dict upload --env staging --version v1.1.7
|
|
182
|
+
|
|
91
183
|
Targeting production requires typing the context/env name to confirm.
|
|
92
184
|
"""
|
|
93
185
|
env = resolve.active_env(env)
|
|
@@ -116,17 +208,25 @@ def deploy(
|
|
|
116
208
|
restart_services: Optional[str] = typer.Option(
|
|
117
209
|
None, "--restart-services",
|
|
118
210
|
help="Comma-separated deployment names restarted after the upload, in "
|
|
119
|
-
"order; default: the env's SSM app/restart_services
|
|
211
|
+
"order; default: the env's SSM app/restart_services (the CDK "
|
|
212
|
+
"config's k8s.schemaRestartServices).",
|
|
213
|
+
),
|
|
214
|
+
sync: bool = typer.Option(
|
|
215
|
+
False, "--sync",
|
|
216
|
+
help="Run 'argocd app sync' on the commons app before the restart "
|
|
217
|
+
"(off by default; add it when the app is behind the merged revision).",
|
|
120
218
|
),
|
|
121
219
|
) -> None:
|
|
122
220
|
"""Pull + upload the dictionary and restart Gen3 schema microservices.
|
|
123
221
|
|
|
124
222
|
Wraps services/dictionary/deploy_dd.sh. Requires an interactive ArgoCD SSO
|
|
125
|
-
login, so it runs locally only.
|
|
223
|
+
login, so it runs locally only. The restart never syncs the ArgoCD app
|
|
224
|
+
unless --sync is given.
|
|
126
225
|
|
|
127
226
|
The version defaults to the env's `dictionary_version`, a CDK INPUT: edit
|
|
128
|
-
config/<project>.<env>.json in
|
|
129
|
-
|
|
227
|
+
config/<project>.<env>.json in your deployment wrapper (the repo that pins
|
|
228
|
+
the aws-gen3-pipeline template) and `cdk deploy` to change what an env
|
|
229
|
+
declares. Pass --version to deploy a different tag now
|
|
130
230
|
without that round trip — which is how one dictionary gets promoted across
|
|
131
231
|
environments. `g3dt config diff` reports the resulting drift until the CDK
|
|
132
232
|
config catches up.
|
|
@@ -146,6 +246,8 @@ def deploy(
|
|
|
146
246
|
env_vars = script_env(e, _version(e, version))
|
|
147
247
|
if restart_services:
|
|
148
248
|
env_vars["G3DT_RESTART_SERVICES"] = restart_services
|
|
249
|
+
if sync:
|
|
250
|
+
env_vars["G3DT_SYNC"] = "1"
|
|
149
251
|
runner.run(
|
|
150
252
|
runner.bash_script("services/dictionary/deploy_dd.sh", env),
|
|
151
253
|
env=env_vars,
|
|
@@ -35,7 +35,9 @@ def register(
|
|
|
35
35
|
env: Optional[str] = typer.Option(None, "--env", "-e", help=ENV_OPT),
|
|
36
36
|
dry_run: bool = typer.Option(
|
|
37
37
|
False, "--dry-run", "-d",
|
|
38
|
-
help="Scan
|
|
38
|
+
help="Scan the prefixes and record what was found in the warehouse's "
|
|
39
|
+
"file_metadata table, but register NOTHING with indexd. Note this "
|
|
40
|
+
"still writes that table (unlike --dry-run elsewhere in g3dt)."
|
|
39
41
|
),
|
|
40
42
|
force: bool = typer.Option(
|
|
41
43
|
False, "--force",
|
|
@@ -7,7 +7,15 @@ import typer
|
|
|
7
7
|
|
|
8
8
|
from g3dt.cli._internal import dispatch, registry, resolve
|
|
9
9
|
|
|
10
|
-
app = typer.Typer(
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
no_args_is_help=True,
|
|
12
|
+
help="Track EC2-dispatched jobs.\n\n"
|
|
13
|
+
"Every `--on ec2` dispatch (metadata upload / upload-all, delete "
|
|
14
|
+
"metadata, indexd register) prints a run id and records it in this "
|
|
15
|
+
"laptop's ~/.g3dt/runs.json; these commands read that file and the "
|
|
16
|
+
"run's SSM Run Command status / CloudWatch logs. No --env: the run id "
|
|
17
|
+
"already names its environment.",
|
|
18
|
+
)
|
|
11
19
|
|
|
12
20
|
|
|
13
21
|
def _status_color(state: str) -> str:
|
|
@@ -79,9 +87,21 @@ def stop(run_id: str = typer.Argument(..., help="Run id from dispatch.")) -> Non
|
|
|
79
87
|
|
|
80
88
|
@app.command()
|
|
81
89
|
def logs(
|
|
82
|
-
run_id: str = typer.Argument(
|
|
83
|
-
|
|
90
|
+
run_id: str = typer.Argument(
|
|
91
|
+
..., help="Run id printed by the --on ec2 dispatch (see `g3dt jobs list`)."
|
|
92
|
+
),
|
|
93
|
+
follow: bool = typer.Option(
|
|
94
|
+
False, "--follow", "-f",
|
|
95
|
+
help="Stream new output as it arrives, until the run ends.",
|
|
96
|
+
),
|
|
84
97
|
) -> None:
|
|
85
|
-
"""Print (and optionally follow) the CloudWatch logs for a dispatched run.
|
|
98
|
+
"""Print (and optionally follow) the CloudWatch logs for a dispatched run.
|
|
99
|
+
|
|
100
|
+
Empty output in the first minute is normal: the log stream is created
|
|
101
|
+
when the box starts writing.
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
g3dt jobs logs 20260908T093000-metadata-upload --follow
|
|
105
|
+
"""
|
|
86
106
|
resolve.announce_context()
|
|
87
107
|
dispatch.logs(run_id, follow=follow)
|