gen3-dataops-toolkit 2.1.2__tar.gz → 2.3.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-2.3.0/PKG-INFO +234 -0
- gen3_dataops_toolkit-2.3.0/README.md +199 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/pyproject.toml +2 -1
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/_internal/safety.py +0 -12
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/config_cmds.py +20 -2
- gen3_dataops_toolkit-2.3.0/src/g3dt/cli/delete_cmds.py +212 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/dict_cmds.py +46 -19
- gen3_dataops_toolkit-2.3.0/src/g3dt/cli/indexd_cmds.py +126 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/main.py +13 -0
- gen3_dataops_toolkit-2.3.0/src/g3dt/cli/metadata.py +133 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/synth.py +119 -8
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/config.py +106 -3
- gen3_dataops_toolkit-2.3.0/src/g3dt/indexd/file_access.py +274 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/indexd/indexd_registrar.py +95 -4
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/ingest/ingest.py +117 -33
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/delete/delete_metadata.sh +52 -18
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/dictionary/deploy_dd.sh +6 -3
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/dictionary/pull_dict.sh +5 -1
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/indexd/register_indexd.py +38 -0
- gen3_dataops_toolkit-2.3.0/src/g3dt/services/indexd/verify_file_access.py +126 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/synthetic_data/full_deploy_dd_and_synth.sh +7 -7
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/upload/metadata/upload_all_studies.sh +29 -8
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/upload/metadata/upload_metadata.py +34 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/upload/metadata_submitter.py +81 -8
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/utils/athena_utils.py +146 -25
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/utils/release_writer.py +32 -3
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/validate/validate.py +162 -50
- gen3_dataops_toolkit-2.1.2/PKG-INFO +0 -125
- gen3_dataops_toolkit-2.1.2/README.md +0 -91
- gen3_dataops_toolkit-2.1.2/src/g3dt/cli/delete_cmds.py +0 -101
- gen3_dataops_toolkit-2.1.2/src/g3dt/cli/indexd_cmds.py +0 -57
- gen3_dataops_toolkit-2.1.2/src/g3dt/cli/metadata.py +0 -76
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/__init__.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/__init__.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/_internal/__init__.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/_internal/dispatch.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/_internal/registry.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/_internal/resolve.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/_internal/runner.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/ec2_cmds.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/jobs.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/k8s.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/pipeline_cmds.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/cli/release_cmds.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/indexd/__init__.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/resolver.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/delete/delete_all_metadata_for_project.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/delete/delete_metadata_by_guid.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/dictionary/upload_dictionary.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/k8s_ops/argocd_restart_etl.sh +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/k8s_ops/argocd_restart_ms.sh +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/k8s_ops/argocd_restart_schema.sh +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/k8s_ops/login_to_pod.sh +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/k8s_ops/restart_etl_and_ms.sh +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/synthetic_data/delete_synth_metadata_sheepdog.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/synthetic_data/generate_synth_metadata.sh +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/services/synthetic_data/upload_synth_metadata_sheepdog.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/upload/__init__.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/upload/metadata_deleter.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/upload/upload_synthdata_s3.py +0 -0
- {gen3_dataops_toolkit-2.1.2 → gen3_dataops_toolkit-2.3.0}/src/g3dt/utils/dbt_utils.py +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gen3-dataops-toolkit
|
|
3
|
+
Version: 2.3.0
|
|
4
|
+
Summary: Gen3 DataOps toolkit (g3dt): operate SSM-published Gen3 data pipeline environments
|
|
5
|
+
License: Apache-2.0
|
|
6
|
+
Author: JoshuaHarris391
|
|
7
|
+
Author-email: harjo391@gmail.com
|
|
8
|
+
Requires-Python: >=3.9.5,<4.0.0
|
|
9
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
16
|
+
Requires-Dist: awswrangler (>=3.14.0,<4.0.0)
|
|
17
|
+
Requires-Dist: boto3
|
|
18
|
+
Requires-Dist: gen3 (>=4.27.4,<5.0.0)
|
|
19
|
+
Requires-Dist: gen3-metadata (>=1.4.0,<2.0.0)
|
|
20
|
+
Requires-Dist: gen3_validator (>=2.0.0,<3.0.0)
|
|
21
|
+
Requires-Dist: numpy (<2.0.0)
|
|
22
|
+
Requires-Dist: openpyxl (>=3.1.0)
|
|
23
|
+
Requires-Dist: pyarrow (>=14.0.0,<19.0.0)
|
|
24
|
+
Requires-Dist: pyjwt (>=2.10.1,<3.0.0)
|
|
25
|
+
Requires-Dist: python-dotenv
|
|
26
|
+
Requires-Dist: pytz (>=2025.2,<2026.0)
|
|
27
|
+
Requires-Dist: pyyaml (>=6.0.2,<7.0.0)
|
|
28
|
+
Requires-Dist: s3fs (==2025.10.0)
|
|
29
|
+
Requires-Dist: tenacity (>=8.2,<10.0)
|
|
30
|
+
Requires-Dist: typer (>=0.12)
|
|
31
|
+
Requires-Dist: tzlocal (>=5.3.1,<6.0.0)
|
|
32
|
+
Project-URL: Repository, https://github.com/AustralianBioCommons/gen3-dataops-toolkit
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# gen3-dataops-toolkit (`g3dt`)
|
|
36
|
+
|
|
37
|
+
Operate Gen3 AWS data-pipeline environments from one pip-installable CLI.
|
|
38
|
+
|
|
39
|
+
`g3dt` is the tooling half of the Gen3 DataOps platform: the
|
|
40
|
+
[gen3-aws-data-pipeline](https://github.com/AustralianBioCommons/gen3-aws-data-pipeline)
|
|
41
|
+
CDK app deploys a complete pipeline per project/environment and publishes every
|
|
42
|
+
resource name to AWS SSM Parameter Store; `g3dt` resolves those names at
|
|
43
|
+
runtime and gives operators one command surface for dictionary deploys,
|
|
44
|
+
metadata upload/delete, indexd registration, EC2 job dispatch, and Kubernetes
|
|
45
|
+
restarts. The dbt half of the platform lives in
|
|
46
|
+
[gen3-dbt-template](https://github.com/AustralianBioCommons/gen3-dbt-template).
|
|
47
|
+
|
|
48
|
+
**No AWS resource name is compiled into this package.** The same wheel
|
|
49
|
+
operates any project: it is targeted purely by `--env`, the project's SSM tree
|
|
50
|
+
(`/{project}/{env}/...`), and a tiny local bootstrap marker.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install gen3-dataops-toolkit
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Bootstrap (the only local configuration)
|
|
59
|
+
|
|
60
|
+
`g3dt` needs to know just the project and region — everything else comes from
|
|
61
|
+
SSM. Create `~/.g3dt/g3dt.yaml`:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
project: etl # your projectId
|
|
65
|
+
region: ap-southeast-2
|
|
66
|
+
default_env: test
|
|
67
|
+
profiles: # optional: AWS named profile per env
|
|
68
|
+
test: etl_test # (omit entirely on EC2/CodeBuild — ambient
|
|
69
|
+
staging: etl_staging # role credentials are used)
|
|
70
|
+
studies: # optional: the project's study registry;
|
|
71
|
+
mystudy_test: # alternatively upload it once per env to
|
|
72
|
+
project_id: MyStudy # s3://<metadata-bucket>/config/studies.yaml
|
|
73
|
+
program_id: program1
|
|
74
|
+
s3_metadata_path: s3://my-bucket/metadata/mystudy/
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Search order: `./g3dt.yaml` → `~/.g3dt/g3dt.yaml` → `/etc/g3dt/g3dt.yaml`
|
|
78
|
+
(the EC2 job box's copy, written by CDK user-data). Env vars override:
|
|
79
|
+
`G3DT_PROJECT`, `AWS_REGION`, `G3DT_DEFAULT_ENV`.
|
|
80
|
+
|
|
81
|
+
## Quick start
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
g3dt config envs # environments with a deployed SSM tree
|
|
85
|
+
g3dt config show --env test # every resolved name — the safety check
|
|
86
|
+
g3dt ec2 up --env test # start the env's job box (SSM-managed)
|
|
87
|
+
g3dt metadata upload --study mystudy --env test --on ec2
|
|
88
|
+
g3dt jobs logs <run-id> --follow # live logs; laptop can sleep, job keeps going
|
|
89
|
+
g3dt ec2 down --env test # or let the auto-stop alarm handle it
|
|
90
|
+
g3dt docs # the full operations overview
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## How configuration works
|
|
94
|
+
|
|
95
|
+
There are exactly two kinds of configuration:
|
|
96
|
+
|
|
97
|
+
- **INPUTS** — human-authored values, committed as
|
|
98
|
+
`config/<projectId>.<env>.json` in the CDK repo and read only by
|
|
99
|
+
`cdk deploy`. To change what an environment *declares*, edit that file and
|
|
100
|
+
redeploy — the value flows to SSM.
|
|
101
|
+
- **OUTPUTS** — every resource name the CDK creates plus the mirrored Gen3
|
|
102
|
+
app facts, published to SSM under `/{project}/{env}/...` on deploy. `g3dt`
|
|
103
|
+
reads these live (cached one round-trip per invocation) and never stores
|
|
104
|
+
them locally.
|
|
105
|
+
|
|
106
|
+
Because the CLI and the infrastructure read the same parameters, they cannot
|
|
107
|
+
disagree — and because each environment has its own tree (including its own
|
|
108
|
+
`ec2/instanceId`), running a job against the wrong environment's resources is
|
|
109
|
+
structurally impossible.
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
## CI isolation and the release contract
|
|
113
|
+
|
|
114
|
+
**Only the dbt template's `ci` target is prefixed.** `g3dt config dbt-env`
|
|
115
|
+
emits, alongside the real names, the CI-isolation variants the template's
|
|
116
|
+
`ci` target consumes: `G3DT_DB_RAW_SILVER_CI` / `G3DT_DB_RAW_GOLD_CI`
|
|
117
|
+
(`ci_` + the real database name) and `G3DT_S3_SILVER_DATA_DIR_CI` /
|
|
118
|
+
`G3DT_S3_GOLD_DATA_DIR_CI` (`dbt_ci/` under the same buckets). Commit-
|
|
119
|
+
triggered CI builds land there; every other target (default, local) and the
|
|
120
|
+
release build keep the real, unprefixed names — so CI can never advance the
|
|
121
|
+
warehouse's Iceberg snapshots that releases pin. The library enforces the
|
|
122
|
+
other half: `find_db_for_model` always skips `ci_`-prefixed databases, so
|
|
123
|
+
`g3dt release write` can never pin a release to a CI-build snapshot.
|
|
124
|
+
|
|
125
|
+
**Snapshot pinning.** `AthenaValidationWriter.construct_json` /
|
|
126
|
+
`AthenaGoldWriter.construct_json` honour a pre-set `snapshot_id` (reading the
|
|
127
|
+
table `FOR VERSION AS OF` that snapshot) and only fetch the latest snapshot
|
|
128
|
+
when unpinned — the contract the release-JSON export relies on for
|
|
129
|
+
reproducible releases.
|
|
130
|
+
|
|
131
|
+
**Concurrency.** `release_writer.run` processes models with a bounded thread
|
|
132
|
+
pool (`max_workers`, default 8) and fails at the end naming every failed
|
|
133
|
+
model (inserts are idempotent — re-run to fill the remainder). The S3
|
|
134
|
+
writers (`write_release_jsons_to_s3`, `write_validation_json_to_s3`) accept
|
|
135
|
+
`s3_client=` (pass one per worker thread) and `key_prefix=` (write a
|
|
136
|
+
verification tree without touching real artifacts).
|
|
137
|
+
|
|
138
|
+
**The validation gate.** `g3dt.validate.run_validation_gate(glue_database,
|
|
139
|
+
athena_s3_output, aws_region, workgroup)` queries the latest
|
|
140
|
+
`validation_id` in `full_validation_results` for REAL failures — the
|
|
141
|
+
known-noise patterns in `VALIDATION_GATE_IGNORED_ERRORS` and synthetic
|
|
142
|
+
studies are excluded. The validator Glue job fails when rows come back, so a
|
|
143
|
+
green validation Step Function means schema-clean data; the operator loop is
|
|
144
|
+
gate fails -> inspect the results table -> fix data -> re-run until green.
|
|
145
|
+
`validate_pipeline` also accepts pre-computed loop-invariants
|
|
146
|
+
(`schema=`/`resolver=`/`metadata_table=`) and `write_iceberg=False` so a
|
|
147
|
+
multi-study caller resolves the schema once, lists the validation prefix
|
|
148
|
+
once, and batches all studies into a single Iceberg INSERT.
|
|
149
|
+
|
|
150
|
+
### Where the data dictionary comes from
|
|
151
|
+
|
|
152
|
+
Composed from the env's inputs as
|
|
153
|
+
`{dictionary_base_url}/{schema_repo}/refs/tags/{dictionary_version}/{dictionary_path}`.
|
|
154
|
+
Only `schema_repo` and `dictionary_version` are required; `app/dictionary_base_url`
|
|
155
|
+
and `app/dictionary_path` are optional and default to raw GitHub and the schema
|
|
156
|
+
repo's conventional layout, so environments deployed before they existed keep
|
|
157
|
+
working. `g3dt config show --env <env>` prints the composed URL.
|
|
158
|
+
|
|
159
|
+
### Promoting a dictionary across environments
|
|
160
|
+
|
|
161
|
+
A dictionary version is *content*, not infrastructure: it changes far more often
|
|
162
|
+
than buckets or clusters do. Rather than a `cdk deploy` per environment per
|
|
163
|
+
version, `dict pull`, `dict upload` and `dict deploy` all accept `--version`:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
g3dt dict deploy --env test --version v1.1.7
|
|
167
|
+
g3dt dict deploy --env staging --version v1.1.7 # same tag, no cdk deploy
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
An override does not persist, so `config show` keeps reporting the declared
|
|
171
|
+
version until the CDK config catches up — `g3dt config diff --env <env>` reports
|
|
172
|
+
exactly that gap and exits 1, so it can gate CI.
|
|
173
|
+
|
|
174
|
+
Synthetic data is only schema-valid against the dictionary that generated it, so
|
|
175
|
+
`synth generate` records the dictionary version in each batch and `synth upload`
|
|
176
|
+
refuses a batch that doesn't match the version being uploaded (override with
|
|
177
|
+
`--allow-version-mismatch`).
|
|
178
|
+
|
|
179
|
+
## Verifying download access (check-download)
|
|
180
|
+
|
|
181
|
+
Registration alone does not prove a file can be downloaded. Two failure modes
|
|
182
|
+
are invisible until a user clicks the file in the portal: an Indexd record
|
|
183
|
+
with no storage URL (nothing to download, ever), and a record Fence refuses
|
|
184
|
+
to sign a URL for. `g3dt indexd check-download` walks the exact chain the
|
|
185
|
+
portal hits — Indexd record → storage URL → DRS object → access methods →
|
|
186
|
+
Fence signed URL — and reports PASS/FAIL per object, exiting non-zero if any
|
|
187
|
+
object fails so it can gate a deployment step.
|
|
188
|
+
|
|
189
|
+
Run it before a release, and after registering new files. The env selects the
|
|
190
|
+
API key secret and the key's JWT selects the commons, so there is no URL to
|
|
191
|
+
pass (and none to get wrong).
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
g3dt indexd check-download --env staging # sample the 25 newest
|
|
195
|
+
g3dt indexd check-download --env staging --limit 50
|
|
196
|
+
g3dt indexd check-download --env prod PREFIX/<uuid-1> PREFIX/<uuid-2>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
With no GUIDs, the newest objects for the env's commons are sampled from the
|
|
200
|
+
indexd registry (latest revision per baseid). The registry may live in a
|
|
201
|
+
different AWS account than the commons being checked; if the env's AWS
|
|
202
|
+
profile cannot reach it, pass GUIDs explicitly.
|
|
203
|
+
|
|
204
|
+
Reading a failure:
|
|
205
|
+
|
|
206
|
+
| Symptom | Meaning |
|
|
207
|
+
|---|---|
|
|
208
|
+
| `Indexd status: 404` | the object is not registered — a registration problem, not a download one |
|
|
209
|
+
| `urls: []` / no access methods | registered but with no storage location; it can never download |
|
|
210
|
+
| `Access endpoint … 401` | authorization: the API key's user lacks `read-storage` on the record's `authz` resource — an authz gap, not a broken key |
|
|
211
|
+
| `Access endpoint … 500` | Fence has the permission but failed to sign — a service-side fault |
|
|
212
|
+
|
|
213
|
+
On a 401, compare what the record requires
|
|
214
|
+
(`https://commons.example.org/index/<did>`, the `authz` field) with what the
|
|
215
|
+
key's user actually holds (`https://commons.example.org/user/user`):
|
|
216
|
+
downloads require `read-storage` on the record's authz resource, which a user
|
|
217
|
+
holding only `create` does not have.
|
|
218
|
+
|
|
219
|
+
## Development
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
poetry install
|
|
223
|
+
poetry run python3 -m pytest
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Provenance
|
|
227
|
+
|
|
228
|
+
This toolkit was ported (working tree only) from
|
|
229
|
+
[AustralianBioCommons/acdc-aws-etl-pipeline](https://github.com/AustralianBioCommons/acdc-aws-etl-pipeline),
|
|
230
|
+
the ACDC ETL monolith, as part of the Gen3 DataOps platform refactor (2026).
|
|
231
|
+
It starts at version **2.0.0**; versions ≤ 1.2.0 on PyPI are the legacy
|
|
232
|
+
`acdc_aws_etl_pipeline` package, which continues to operate the legacy ACDC
|
|
233
|
+
pipeline unchanged.
|
|
234
|
+
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# gen3-dataops-toolkit (`g3dt`)
|
|
2
|
+
|
|
3
|
+
Operate Gen3 AWS data-pipeline environments from one pip-installable CLI.
|
|
4
|
+
|
|
5
|
+
`g3dt` is the tooling half of the Gen3 DataOps platform: the
|
|
6
|
+
[gen3-aws-data-pipeline](https://github.com/AustralianBioCommons/gen3-aws-data-pipeline)
|
|
7
|
+
CDK app deploys a complete pipeline per project/environment and publishes every
|
|
8
|
+
resource name to AWS SSM Parameter Store; `g3dt` resolves those names at
|
|
9
|
+
runtime and gives operators one command surface for dictionary deploys,
|
|
10
|
+
metadata upload/delete, indexd registration, EC2 job dispatch, and Kubernetes
|
|
11
|
+
restarts. The dbt half of the platform lives in
|
|
12
|
+
[gen3-dbt-template](https://github.com/AustralianBioCommons/gen3-dbt-template).
|
|
13
|
+
|
|
14
|
+
**No AWS resource name is compiled into this package.** The same wheel
|
|
15
|
+
operates any project: it is targeted purely by `--env`, the project's SSM tree
|
|
16
|
+
(`/{project}/{env}/...`), and a tiny local bootstrap marker.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install gen3-dataops-toolkit
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Bootstrap (the only local configuration)
|
|
25
|
+
|
|
26
|
+
`g3dt` needs to know just the project and region — everything else comes from
|
|
27
|
+
SSM. Create `~/.g3dt/g3dt.yaml`:
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
project: etl # your projectId
|
|
31
|
+
region: ap-southeast-2
|
|
32
|
+
default_env: test
|
|
33
|
+
profiles: # optional: AWS named profile per env
|
|
34
|
+
test: etl_test # (omit entirely on EC2/CodeBuild — ambient
|
|
35
|
+
staging: etl_staging # role credentials are used)
|
|
36
|
+
studies: # optional: the project's study registry;
|
|
37
|
+
mystudy_test: # alternatively upload it once per env to
|
|
38
|
+
project_id: MyStudy # s3://<metadata-bucket>/config/studies.yaml
|
|
39
|
+
program_id: program1
|
|
40
|
+
s3_metadata_path: s3://my-bucket/metadata/mystudy/
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Search order: `./g3dt.yaml` → `~/.g3dt/g3dt.yaml` → `/etc/g3dt/g3dt.yaml`
|
|
44
|
+
(the EC2 job box's copy, written by CDK user-data). Env vars override:
|
|
45
|
+
`G3DT_PROJECT`, `AWS_REGION`, `G3DT_DEFAULT_ENV`.
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
g3dt config envs # environments with a deployed SSM tree
|
|
51
|
+
g3dt config show --env test # every resolved name — the safety check
|
|
52
|
+
g3dt ec2 up --env test # start the env's job box (SSM-managed)
|
|
53
|
+
g3dt metadata upload --study mystudy --env test --on ec2
|
|
54
|
+
g3dt jobs logs <run-id> --follow # live logs; laptop can sleep, job keeps going
|
|
55
|
+
g3dt ec2 down --env test # or let the auto-stop alarm handle it
|
|
56
|
+
g3dt docs # the full operations overview
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## How configuration works
|
|
60
|
+
|
|
61
|
+
There are exactly two kinds of configuration:
|
|
62
|
+
|
|
63
|
+
- **INPUTS** — human-authored values, committed as
|
|
64
|
+
`config/<projectId>.<env>.json` in the CDK repo and read only by
|
|
65
|
+
`cdk deploy`. To change what an environment *declares*, edit that file and
|
|
66
|
+
redeploy — the value flows to SSM.
|
|
67
|
+
- **OUTPUTS** — every resource name the CDK creates plus the mirrored Gen3
|
|
68
|
+
app facts, published to SSM under `/{project}/{env}/...` on deploy. `g3dt`
|
|
69
|
+
reads these live (cached one round-trip per invocation) and never stores
|
|
70
|
+
them locally.
|
|
71
|
+
|
|
72
|
+
Because the CLI and the infrastructure read the same parameters, they cannot
|
|
73
|
+
disagree — and because each environment has its own tree (including its own
|
|
74
|
+
`ec2/instanceId`), running a job against the wrong environment's resources is
|
|
75
|
+
structurally impossible.
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
## CI isolation and the release contract
|
|
79
|
+
|
|
80
|
+
**Only the dbt template's `ci` target is prefixed.** `g3dt config dbt-env`
|
|
81
|
+
emits, alongside the real names, the CI-isolation variants the template's
|
|
82
|
+
`ci` target consumes: `G3DT_DB_RAW_SILVER_CI` / `G3DT_DB_RAW_GOLD_CI`
|
|
83
|
+
(`ci_` + the real database name) and `G3DT_S3_SILVER_DATA_DIR_CI` /
|
|
84
|
+
`G3DT_S3_GOLD_DATA_DIR_CI` (`dbt_ci/` under the same buckets). Commit-
|
|
85
|
+
triggered CI builds land there; every other target (default, local) and the
|
|
86
|
+
release build keep the real, unprefixed names — so CI can never advance the
|
|
87
|
+
warehouse's Iceberg snapshots that releases pin. The library enforces the
|
|
88
|
+
other half: `find_db_for_model` always skips `ci_`-prefixed databases, so
|
|
89
|
+
`g3dt release write` can never pin a release to a CI-build snapshot.
|
|
90
|
+
|
|
91
|
+
**Snapshot pinning.** `AthenaValidationWriter.construct_json` /
|
|
92
|
+
`AthenaGoldWriter.construct_json` honour a pre-set `snapshot_id` (reading the
|
|
93
|
+
table `FOR VERSION AS OF` that snapshot) and only fetch the latest snapshot
|
|
94
|
+
when unpinned — the contract the release-JSON export relies on for
|
|
95
|
+
reproducible releases.
|
|
96
|
+
|
|
97
|
+
**Concurrency.** `release_writer.run` processes models with a bounded thread
|
|
98
|
+
pool (`max_workers`, default 8) and fails at the end naming every failed
|
|
99
|
+
model (inserts are idempotent — re-run to fill the remainder). The S3
|
|
100
|
+
writers (`write_release_jsons_to_s3`, `write_validation_json_to_s3`) accept
|
|
101
|
+
`s3_client=` (pass one per worker thread) and `key_prefix=` (write a
|
|
102
|
+
verification tree without touching real artifacts).
|
|
103
|
+
|
|
104
|
+
**The validation gate.** `g3dt.validate.run_validation_gate(glue_database,
|
|
105
|
+
athena_s3_output, aws_region, workgroup)` queries the latest
|
|
106
|
+
`validation_id` in `full_validation_results` for REAL failures — the
|
|
107
|
+
known-noise patterns in `VALIDATION_GATE_IGNORED_ERRORS` and synthetic
|
|
108
|
+
studies are excluded. The validator Glue job fails when rows come back, so a
|
|
109
|
+
green validation Step Function means schema-clean data; the operator loop is
|
|
110
|
+
gate fails -> inspect the results table -> fix data -> re-run until green.
|
|
111
|
+
`validate_pipeline` also accepts pre-computed loop-invariants
|
|
112
|
+
(`schema=`/`resolver=`/`metadata_table=`) and `write_iceberg=False` so a
|
|
113
|
+
multi-study caller resolves the schema once, lists the validation prefix
|
|
114
|
+
once, and batches all studies into a single Iceberg INSERT.
|
|
115
|
+
|
|
116
|
+
### Where the data dictionary comes from
|
|
117
|
+
|
|
118
|
+
Composed from the env's inputs as
|
|
119
|
+
`{dictionary_base_url}/{schema_repo}/refs/tags/{dictionary_version}/{dictionary_path}`.
|
|
120
|
+
Only `schema_repo` and `dictionary_version` are required; `app/dictionary_base_url`
|
|
121
|
+
and `app/dictionary_path` are optional and default to raw GitHub and the schema
|
|
122
|
+
repo's conventional layout, so environments deployed before they existed keep
|
|
123
|
+
working. `g3dt config show --env <env>` prints the composed URL.
|
|
124
|
+
|
|
125
|
+
### Promoting a dictionary across environments
|
|
126
|
+
|
|
127
|
+
A dictionary version is *content*, not infrastructure: it changes far more often
|
|
128
|
+
than buckets or clusters do. Rather than a `cdk deploy` per environment per
|
|
129
|
+
version, `dict pull`, `dict upload` and `dict deploy` all accept `--version`:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
g3dt dict deploy --env test --version v1.1.7
|
|
133
|
+
g3dt dict deploy --env staging --version v1.1.7 # same tag, no cdk deploy
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
An override does not persist, so `config show` keeps reporting the declared
|
|
137
|
+
version until the CDK config catches up — `g3dt config diff --env <env>` reports
|
|
138
|
+
exactly that gap and exits 1, so it can gate CI.
|
|
139
|
+
|
|
140
|
+
Synthetic data is only schema-valid against the dictionary that generated it, so
|
|
141
|
+
`synth generate` records the dictionary version in each batch and `synth upload`
|
|
142
|
+
refuses a batch that doesn't match the version being uploaded (override with
|
|
143
|
+
`--allow-version-mismatch`).
|
|
144
|
+
|
|
145
|
+
## Verifying download access (check-download)
|
|
146
|
+
|
|
147
|
+
Registration alone does not prove a file can be downloaded. Two failure modes
|
|
148
|
+
are invisible until a user clicks the file in the portal: an Indexd record
|
|
149
|
+
with no storage URL (nothing to download, ever), and a record Fence refuses
|
|
150
|
+
to sign a URL for. `g3dt indexd check-download` walks the exact chain the
|
|
151
|
+
portal hits — Indexd record → storage URL → DRS object → access methods →
|
|
152
|
+
Fence signed URL — and reports PASS/FAIL per object, exiting non-zero if any
|
|
153
|
+
object fails so it can gate a deployment step.
|
|
154
|
+
|
|
155
|
+
Run it before a release, and after registering new files. The env selects the
|
|
156
|
+
API key secret and the key's JWT selects the commons, so there is no URL to
|
|
157
|
+
pass (and none to get wrong).
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
g3dt indexd check-download --env staging # sample the 25 newest
|
|
161
|
+
g3dt indexd check-download --env staging --limit 50
|
|
162
|
+
g3dt indexd check-download --env prod PREFIX/<uuid-1> PREFIX/<uuid-2>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
With no GUIDs, the newest objects for the env's commons are sampled from the
|
|
166
|
+
indexd registry (latest revision per baseid). The registry may live in a
|
|
167
|
+
different AWS account than the commons being checked; if the env's AWS
|
|
168
|
+
profile cannot reach it, pass GUIDs explicitly.
|
|
169
|
+
|
|
170
|
+
Reading a failure:
|
|
171
|
+
|
|
172
|
+
| Symptom | Meaning |
|
|
173
|
+
|---|---|
|
|
174
|
+
| `Indexd status: 404` | the object is not registered — a registration problem, not a download one |
|
|
175
|
+
| `urls: []` / no access methods | registered but with no storage location; it can never download |
|
|
176
|
+
| `Access endpoint … 401` | authorization: the API key's user lacks `read-storage` on the record's `authz` resource — an authz gap, not a broken key |
|
|
177
|
+
| `Access endpoint … 500` | Fence has the permission but failed to sign — a service-side fault |
|
|
178
|
+
|
|
179
|
+
On a 401, compare what the record requires
|
|
180
|
+
(`https://commons.example.org/index/<did>`, the `authz` field) with what the
|
|
181
|
+
key's user actually holds (`https://commons.example.org/user/user`):
|
|
182
|
+
downloads require `read-storage` on the record's authz resource, which a user
|
|
183
|
+
holding only `create` does not have.
|
|
184
|
+
|
|
185
|
+
## Development
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
poetry install
|
|
189
|
+
poetry run python3 -m pytest
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Provenance
|
|
193
|
+
|
|
194
|
+
This toolkit was ported (working tree only) from
|
|
195
|
+
[AustralianBioCommons/acdc-aws-etl-pipeline](https://github.com/AustralianBioCommons/acdc-aws-etl-pipeline),
|
|
196
|
+
the ACDC ETL monolith, as part of the Gen3 DataOps platform refactor (2026).
|
|
197
|
+
It starts at version **2.0.0**; versions ≤ 1.2.0 on PyPI are the legacy
|
|
198
|
+
`acdc_aws_etl_pipeline` package, which continues to operate the legacy ACDC
|
|
199
|
+
pipeline unchanged.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "gen3-dataops-toolkit"
|
|
3
|
-
version = "2.
|
|
3
|
+
version = "2.3.0"
|
|
4
4
|
description = "Gen3 DataOps toolkit (g3dt): operate SSM-published Gen3 data pipeline environments"
|
|
5
5
|
authors = ["JoshuaHarris391 <harjo391@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -28,6 +28,7 @@ awswrangler = ">=3.14.0,<4.0.0"
|
|
|
28
28
|
pyarrow = ">=14.0.0,<19.0.0"
|
|
29
29
|
gen3-metadata = ">=1.4.0,<2.0.0"
|
|
30
30
|
typer = ">=0.12"
|
|
31
|
+
openpyxl = ">=3.1.0"
|
|
31
32
|
|
|
32
33
|
[tool.poetry.scripts]
|
|
33
34
|
g3dt = "g3dt.cli.main:main"
|
|
@@ -33,18 +33,6 @@ def require_test_env(env: str) -> None:
|
|
|
33
33
|
raise typer.Exit(2)
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def abort_if_prod(env: str) -> None:
|
|
37
|
-
"""Hard abort for bulk operations that must never touch production."""
|
|
38
|
-
if is_prod(env):
|
|
39
|
-
typer.secho(
|
|
40
|
-
f"Refusing bulk operation against a production environment "
|
|
41
|
-
f"('{env}').",
|
|
42
|
-
fg=typer.colors.RED,
|
|
43
|
-
err=True,
|
|
44
|
-
)
|
|
45
|
-
raise typer.Exit(2)
|
|
46
|
-
|
|
47
|
-
|
|
48
36
|
def confirm_destructive(action: str, target: str, env: str, assume_yes: bool) -> None:
|
|
49
37
|
"""Gate a destructive operation with an appropriate confirmation.
|
|
50
38
|
|
|
@@ -86,6 +86,10 @@ def show(
|
|
|
86
86
|
typer.echo(f" aws_profile : {e.aws_profile or '(ambient credentials)'}")
|
|
87
87
|
typer.echo(f" aws_secret_name : {e.aws_secret_name}")
|
|
88
88
|
typer.echo(f" dictionary_version : {e.dictionary_version}")
|
|
89
|
+
# The composed URL, not the three parts: "which dictionary will this env
|
|
90
|
+
# actually fetch?" now spans schema_repo plus two optional inputs, so showing
|
|
91
|
+
# the resolved result is what makes the answer checkable before a deploy.
|
|
92
|
+
typer.echo(f" dictionary_url : {config.dictionary_url(e)}")
|
|
89
93
|
typer.echo(f" schema_s3_uri : {e.schema_s3_uri}")
|
|
90
94
|
typer.echo(f" schema_repo : {e.schema_repo}")
|
|
91
95
|
typer.echo(f" domain : {e.domain}")
|
|
@@ -208,15 +212,24 @@ def dbt_env(
|
|
|
208
212
|
typer.secho(str(exc), fg=typer.colors.RED, err=True)
|
|
209
213
|
raise typer.Exit(1)
|
|
210
214
|
|
|
215
|
+
raw_silver_db = rc.get("glue/db/rawSilver")
|
|
216
|
+
raw_gold_db = rc.get("glue/db/rawGold")
|
|
211
217
|
values = {
|
|
212
218
|
"G3DT_REGION": rc.region,
|
|
213
219
|
"G3DT_ATHENA_WORKGROUP": rc.athena_workgroup,
|
|
214
220
|
"G3DT_ATHENA_OUTPUT": rc.athena_output_location,
|
|
215
221
|
"G3DT_DB_RAW_BRONZE": rc.get("glue/db/rawBronze"),
|
|
216
|
-
"G3DT_DB_RAW_SILVER":
|
|
217
|
-
"G3DT_DB_RAW_GOLD":
|
|
222
|
+
"G3DT_DB_RAW_SILVER": raw_silver_db,
|
|
223
|
+
"G3DT_DB_RAW_GOLD": raw_gold_db,
|
|
218
224
|
"G3DT_S3_SILVER_DATA_DIR": f"s3://{rc.get('buckets/rawSilver')}/dbt/",
|
|
219
225
|
"G3DT_S3_GOLD_DATA_DIR": f"s3://{rc.get('buckets/rawGold')}/dbt/",
|
|
226
|
+
# CI isolation: the dbt template's `ci` target builds into these
|
|
227
|
+
# instead — same grammar as the CDK's ci_ databases, same buckets
|
|
228
|
+
# under a dbt_ci/ prefix. Real names above are never prefixed.
|
|
229
|
+
"G3DT_DB_RAW_SILVER_CI": f"ci_{raw_silver_db}" if raw_silver_db else None,
|
|
230
|
+
"G3DT_DB_RAW_GOLD_CI": f"ci_{raw_gold_db}" if raw_gold_db else None,
|
|
231
|
+
"G3DT_S3_SILVER_DATA_DIR_CI": f"s3://{rc.get('buckets/rawSilver')}/dbt_ci/",
|
|
232
|
+
"G3DT_S3_GOLD_DATA_DIR_CI": f"s3://{rc.get('buckets/rawGold')}/dbt_ci/",
|
|
220
233
|
}
|
|
221
234
|
if profile:
|
|
222
235
|
# A named profile means a laptop run: select the dbt target that
|
|
@@ -239,6 +252,11 @@ def set_value(
|
|
|
239
252
|
settings — dictionary_version, domain, buckets, ... — are CDK INPUTS: edit
|
|
240
253
|
config/<project>.<env>.json in gen3-aws-data-pipeline and `cdk deploy`;
|
|
241
254
|
the values flow to SSM, which is what every consumer reads.
|
|
255
|
+
|
|
256
|
+
The one exception is per-invocation: `dict pull/upload/deploy --version`
|
|
257
|
+
fetches a different dictionary tag without a redeploy (for promoting one
|
|
258
|
+
dictionary across environments). That does not persist — `config diff`
|
|
259
|
+
reports the gap until the CDK config is updated to match.
|
|
242
260
|
"""
|
|
243
261
|
try:
|
|
244
262
|
old, new, path = config.set_marker_value(key, value)
|