pdt-cli 0.1.0__tar.gz → 0.1.2__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.
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/.gitignore +1 -0
- pdt_cli-0.1.0/README.md → pdt_cli-0.1.2/PKG-INFO +128 -7
- pdt_cli-0.1.0/PKG-INFO → pdt_cli-0.1.2/README.md +97 -25
- pdt_cli-0.1.2/ci/tests/test_claude_task.py +296 -0
- pdt_cli-0.1.2/ci/tests/test_close_stale_drafts.py +79 -0
- pdt_cli-0.1.2/ci/tests/test_rebase_task.py +333 -0
- pdt_cli-0.1.2/ci/tests/test_score_task.py +640 -0
- pdt_cli-0.1.2/pyproject.toml +65 -0
- pdt_cli-0.1.2/src/pdt/cli.py +299 -0
- pdt_cli-0.1.2/src/pdt/completion.py +159 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/config.py +233 -46
- pdt_cli-0.1.2/src/pdt/console.py +178 -0
- pdt_cli-0.1.2/src/pdt/deploy.py +152 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/deploy_aws.py +177 -84
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/deploy_aws_fargate.py +103 -44
- pdt_cli-0.1.2/src/pdt/deploy_azure.py +919 -0
- pdt_cli-0.1.2/src/pdt/deploy_azure_container_apps.py +676 -0
- pdt_cli-0.1.2/src/pdt/deploy_common.py +403 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/deploy_google_cloud.py +293 -121
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/deploy_windows.py +61 -19
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/hello-world/config.yml +3 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/impossible-travel-report/config.yml +2 -2
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/impossible-travel-report/run.py +4 -4
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/gcloud_sdk.py +18 -27
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/scaffold.py +80 -48
- pdt_cli-0.1.2/src/pdt/storage_cli.py +93 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/email_auth.py +32 -38
- pdt_cli-0.1.2/src/pdt/utils/env_secret.py +192 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/log.py +15 -1
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/send_email.py +16 -3
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/send_email_graph.py +20 -19
- pdt_cli-0.1.2/src/pdt/utils/storage.py +345 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/conftest.py +7 -0
- pdt_cli-0.1.2/tests/test_aws_inventory.py +38 -0
- pdt_cli-0.1.2/tests/test_aws_names.py +9 -0
- pdt_cli-0.1.2/tests/test_aws_not_found.py +19 -0
- pdt_cli-0.1.2/tests/test_aws_profile.py +16 -0
- pdt_cli-0.1.2/tests/test_aws_storage.py +42 -0
- pdt_cli-0.1.2/tests/test_azure_build.py +62 -0
- pdt_cli-0.1.2/tests/test_azure_deleted_vault.py +54 -0
- pdt_cli-0.1.2/tests/test_azure_environment.py +210 -0
- pdt_cli-0.1.2/tests/test_azure_environment_key.py +23 -0
- pdt_cli-0.1.2/tests/test_azure_group_ownership.py +21 -0
- pdt_cli-0.1.2/tests/test_azure_job_diagnostics.py +132 -0
- pdt_cli-0.1.2/tests/test_azure_names.py +24 -0
- pdt_cli-0.1.2/tests/test_azure_retries.py +113 -0
- pdt_cli-0.1.2/tests/test_azure_side_resources.py +39 -0
- pdt_cli-0.1.2/tests/test_azure_storage.py +80 -0
- pdt_cli-0.1.2/tests/test_azure_subscription_state.py +29 -0
- pdt_cli-0.1.2/tests/test_ci_azure.py +235 -0
- pdt_cli-0.1.2/tests/test_ci_google_cloud.py +93 -0
- pdt_cli-0.1.2/tests/test_cli.py +104 -0
- pdt_cli-0.1.2/tests/test_completion.py +185 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_config_merge.py +16 -1
- pdt_cli-0.1.2/tests/test_console.py +35 -0
- pdt_cli-0.1.2/tests/test_cost_estimate.py +11 -0
- pdt_cli-0.1.2/tests/test_deploy_state.py +52 -0
- pdt_cli-0.1.2/tests/test_dockerfile.py +79 -0
- pdt_cli-0.1.2/tests/test_env_secret.py +142 -0
- pdt_cli-0.1.2/tests/test_env_sources.py +227 -0
- pdt_cli-0.1.2/tests/test_fetch_json.py +111 -0
- pdt_cli-0.1.2/tests/test_gcloud_sdk.py +24 -0
- pdt_cli-0.1.2/tests/test_google_cloud_inventory.py +23 -0
- pdt_cli-0.1.2/tests/test_google_cloud_not_found.py +13 -0
- pdt_cli-0.1.2/tests/test_google_cloud_retries.py +41 -0
- pdt_cli-0.1.2/tests/test_google_cloud_service_account.py +24 -0
- pdt_cli-0.1.2/tests/test_google_cloud_storage.py +25 -0
- pdt_cli-0.1.2/tests/test_log_colour.py +30 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_project_discovery.py +7 -0
- pdt_cli-0.1.2/tests/test_run_log_links.py +51 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_scaffold.py +43 -3
- pdt_cli-0.1.2/tests/test_secrets.py +110 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_starter_app.py +44 -1
- pdt_cli-0.1.2/tests/test_storage.py +150 -0
- pdt_cli-0.1.2/tests/test_storage_cli.py +40 -0
- pdt_cli-0.1.2/tests/test_strict_validation.py +65 -0
- pdt_cli-0.1.2/tests/test_verify.py +277 -0
- pdt_cli-0.1.2/tests/test_windows_storage.py +50 -0
- pdt_cli-0.1.2/verify/README.md +92 -0
- pdt_cli-0.1.0/pyproject.toml +0 -41
- pdt_cli-0.1.0/src/pdt/cli.py +0 -177
- pdt_cli-0.1.0/src/pdt/deploy.py +0 -106
- pdt_cli-0.1.0/src/pdt/deploy_aws_lambda.py +0 -359
- pdt_cli-0.1.0/src/pdt/deploy_azure.py +0 -531
- pdt_cli-0.1.0/src/pdt/deploy_azure_container_apps.py +0 -328
- pdt_cli-0.1.0/src/pdt/deploy_azure_functions.py +0 -307
- pdt_cli-0.1.0/src/pdt/deploy_common.py +0 -94
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/__init__.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/hello-world/run.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/impossible-travel-report/env.template +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/monday-orphaned-account-report/config.yml +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/monday-orphaned-account-report/env.template +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/examples/monday-orphaned-account-report/run.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/__init__.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/entra.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/src/pdt/utils/web.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_aws_account.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_init_questions.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_save_platform_key.py +0 -0
- {pdt_cli-0.1.0 → pdt_cli-0.1.2}/tests/test_schedule.py +0 -0
|
@@ -1,3 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pdt-cli
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Easily deploy your process to AWS, Azure, Google Cloud, or Windows.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.12
|
|
7
|
+
Requires-Dist: argcomplete
|
|
8
|
+
Requires-Dist: backoff
|
|
9
|
+
Requires-Dist: google-auth
|
|
10
|
+
Requires-Dist: google-auth-oauthlib
|
|
11
|
+
Requires-Dist: msal
|
|
12
|
+
Requires-Dist: python-dotenv
|
|
13
|
+
Requires-Dist: pyyaml
|
|
14
|
+
Requires-Dist: rich
|
|
15
|
+
Requires-Dist: rich-argparse>=1.8.0
|
|
16
|
+
Requires-Dist: shellingham
|
|
17
|
+
Provides-Extra: apps
|
|
18
|
+
Requires-Dist: adlfs; extra == 'apps'
|
|
19
|
+
Requires-Dist: azure-identity; extra == 'apps'
|
|
20
|
+
Requires-Dist: azure-keyvault-secrets; extra == 'apps'
|
|
21
|
+
Requires-Dist: azure-storage-blob; extra == 'apps'
|
|
22
|
+
Requires-Dist: boto3; extra == 'apps'
|
|
23
|
+
Requires-Dist: cryptography; extra == 'apps'
|
|
24
|
+
Requires-Dist: fsspec; extra == 'apps'
|
|
25
|
+
Requires-Dist: gcsfs; extra == 'apps'
|
|
26
|
+
Requires-Dist: google-cloud-secret-manager; extra == 'apps'
|
|
27
|
+
Requires-Dist: google-cloud-storage; extra == 'apps'
|
|
28
|
+
Requires-Dist: pyjwt; extra == 'apps'
|
|
29
|
+
Requires-Dist: s3fs; extra == 'apps'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
1
32
|
# pdt
|
|
2
33
|
|
|
3
34
|
Run scheduled jobs — reports, integrations, automations — and deploy them to AWS, Azure, Google Cloud, or Windows Task Scheduler with one command.
|
|
@@ -10,6 +41,8 @@ uv tool install pdt-cli
|
|
|
10
41
|
|
|
11
42
|
That puts a `pdt` command on your PATH. To update it later, run `uv tool upgrade pdt-cli`.
|
|
12
43
|
|
|
44
|
+
The first interactive `pdt` command turns on tab completion without a prompt. It works in every new terminal: Tab finishes pdt commands, their options, your app names, and example names for `new --from`. It supports bash, zsh, fish, and PowerShell. `pdt completion [SHELL]` sets up a second shell, and `pdt completion --script bash` prints the script instead of writing it, if you keep your own dotfiles.
|
|
45
|
+
|
|
13
46
|
You can also clone this repository and run `./pdt` (or `.\pdt.bat` on Windows) instead. It installs `uv` for you if you do not have it. Both ways give you the same commands.
|
|
14
47
|
|
|
15
48
|
## Set up a project
|
|
@@ -20,7 +53,7 @@ A project is a folder holding `pdt.yml`. Each app is a folder inside it that con
|
|
|
20
53
|
pdt init my-jobs
|
|
21
54
|
```
|
|
22
55
|
|
|
23
|
-
`pdt init` asks where the project should live, which cloud you want, and which region. It warns you if you are about to create a project somewhere unwise, such as your home folder.
|
|
56
|
+
`pdt init` asks where the project should live, which cloud you want, and which region. It warns you if you are about to create a project somewhere unwise, such as your home folder. The folder name is optional: `pdt init` with no name uses the current folder, and `pdt init DIR` uses `DIR`, creating it if it is not there yet. Add `--yes` to take the defaults and answer nothing.
|
|
24
57
|
|
|
25
58
|
Region is the only setting it asks for. Your AWS account, Azure subscription, and Google Cloud project all come from your credentials the first time you deploy, and pdt writes the answer into `pdt.yml` so every later deploy checks against it.
|
|
26
59
|
|
|
@@ -84,17 +117,70 @@ pdt login my-report
|
|
|
84
117
|
| `pdt init [DIR]` | create a project here, or in DIR |
|
|
85
118
|
| `pdt examples` | list the example apps bundled with pdt |
|
|
86
119
|
| `pdt new APP --from EXAMPLE` | add an app to the project |
|
|
87
|
-
| `pdt list` | show every app with its schedule and provider |
|
|
120
|
+
| `pdt list` | show every app with its schedule and provider; `--names` prints only the enabled app names |
|
|
88
121
|
| `pdt validate` | check the config files and the required env vars |
|
|
89
122
|
| `pdt run APP` | run an app on this machine |
|
|
90
123
|
| `pdt deploy APP` | deploy an app to its configured platform |
|
|
91
124
|
| `pdt destroy APP` | remove everything deploy created |
|
|
125
|
+
| `pdt secrets APP` | show which `.env` values differ from the deployed app |
|
|
126
|
+
| `pdt secrets APP save` | send your `.env` values to the deployed app; the next run uses them |
|
|
127
|
+
| `pdt secrets APP get` | copy the deployed values into a `.env.<provider>` file |
|
|
128
|
+
| `pdt secrets APP set NAME` | put one value, read from stdin, into the deployed app's secrets |
|
|
92
129
|
| `pdt login APP` | sign in again to the app's cloud provider |
|
|
130
|
+
| `pdt storage APP ls|get|query|destroy` | look at, fetch, query, or delete the app's stored files |
|
|
93
131
|
| `pdt az ...` | run the Azure CLI that pdt installs |
|
|
94
132
|
| `pdt gcloud ...` | run the Google Cloud CLI that pdt installs |
|
|
133
|
+
| `pdt completion [SHELL]` | turn on tab completion for a shell |
|
|
134
|
+
|
|
135
|
+
Leave `APP` off `run`, `deploy`, `destroy`, `secrets`, or `login`, or mistype it, and pdt lists the apps in the project so you can pick one.
|
|
95
136
|
|
|
96
137
|
`pdt az` and `pdt gcloud` hand your arguments straight to the cloud tool, and install it first if it is missing. For example, `pdt az account list`.
|
|
97
138
|
|
|
139
|
+
## Keeping files between runs
|
|
140
|
+
|
|
141
|
+
Your app runs, writes some files, and stops. Then the computer it ran on is thrown away, and the files go with it. So pdt gives every app a folder in your cloud account that stays. Deploy the app, destroy it, deploy it again: the folder and everything in it is still there.
|
|
142
|
+
|
|
143
|
+
pdt creates one bucket per cloud account, named `pdt-data-` plus a short code, and gives each app its own folder inside it. When your app runs in the cloud, `PDT_STORAGE_URL` points at that folder. When you run `pdt run APP` on your own computer, it points at `.pdt/storage/APP/` inside your project instead. The app code is the same in both places.
|
|
144
|
+
|
|
145
|
+
Write and read files with `pdt.utils.storage`:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from pdt.utils import storage
|
|
149
|
+
|
|
150
|
+
store = storage.store()
|
|
151
|
+
with store.open("report.csv", "wb") as f:
|
|
152
|
+
f.write(b"name,count\n")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`store.fs()` gives you the full [fsspec](https://filesystem-spec.readthedocs.io/) filesystem, rooted at your app's folder. Output from one run goes under `store.run_folder()`, which names a new `runs/<time>-<id>/` folder each run.
|
|
156
|
+
|
|
157
|
+
Some apps need files from the last run: a Meltano bookmark, a database. Keep those in one local folder, and copy it down before the work and up after:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from pathlib import Path
|
|
161
|
+
from pdt.utils import storage
|
|
162
|
+
|
|
163
|
+
store = storage.store()
|
|
164
|
+
lease = store.pull("state/", Path(".pdt-state"))
|
|
165
|
+
# do the work; keep anything the next run needs inside .pdt-state/
|
|
166
|
+
store.push(Path(".pdt-state"), "state/", lease)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
`pull` locks the folder, so a second copy of your app cannot run at the same time and mix up the files. `push` checks that nobody else changed them, saves them, and unlocks. If a run crashes, the next one takes over the lock after 30 minutes.
|
|
170
|
+
|
|
171
|
+
From your own computer, `pdt storage APP ls`, `get`, and `query` read the files with your own cloud sign-in. `pdt storage APP destroy` is the only command that deletes them, and it asks first. An app that needs none of this sets `storage: false` in its `config.yml`.
|
|
172
|
+
|
|
173
|
+
An app that is not ready sets `enabled: false` in its `config.yml`. `pdt list` still shows it, and every other command acts as if the app is not there. `uv run run.py` in the app folder still runs it.
|
|
174
|
+
|
|
175
|
+
## Running pdt from CI
|
|
176
|
+
|
|
177
|
+
Every command reads its values from the environment it runs in. A build server sets them as CI variables, and no `.env` file is needed.
|
|
178
|
+
|
|
179
|
+
- A `.env` file is a convenience for a person working on their own machine. A value that is already in the environment wins over the same name in a `.env` file.
|
|
180
|
+
- `PDT_ENV_JSON` holds every value as one JSON object, for a CI system that keeps one secret instead of many. For example, `PDT_ENV_JSON={"PDT_TOKEN": "abc", "PDT_SMTP_USER": "reports@example.com"}`.
|
|
181
|
+
- pdt creates no `.env` file on a build server. It looks for the `CI` variable that build systems set, and for a missing terminal. Email authorization must therefore be done first: run `pdt run APP` once on a machine with a browser, then copy `PDT_SMTP_OAUTH_CACHE_B64` (or `PDT_GRAPH_MAIL_CACHE_B64` for Microsoft Graph) from your `.env` into the CI variables.
|
|
182
|
+
- `pdt deploy APP` and `pdt destroy APP` ask before they change anything. Add `--yes` so they proceed without asking.
|
|
183
|
+
|
|
98
184
|
## Choosing where jobs run
|
|
99
185
|
|
|
100
186
|
Set `platform:` in `pdt.yml` for every app, or in an app's own `config.yml` for one app. An app's own file wins.
|
|
@@ -107,12 +193,14 @@ platform:
|
|
|
107
193
|
subscription: 00000000-0000-0000-0000-000000000000
|
|
108
194
|
region: eastus
|
|
109
195
|
resource_group: pdt
|
|
110
|
-
#
|
|
111
|
-
|
|
196
|
+
# Optional. An environment you already own, as <resource-group>/<name>.
|
|
197
|
+
# environment: my-group/my-environment
|
|
112
198
|
```
|
|
113
199
|
|
|
114
200
|
`subscription` is optional. When it is missing or wrong, the deploy asks you to choose one.
|
|
115
201
|
|
|
202
|
+
Every pdt project in a subscription runs its jobs in one shared Container Apps environment per region, `pdt-<region>` in the resource group `pdt-shared`, because a subscription allows only a few environments. Destroying the last app that uses the environment removes it, and removes `pdt-shared` once it holds no environment. Set `environment` to use an environment you already have; pdt then never creates, changes, or deletes it.
|
|
203
|
+
|
|
116
204
|
### AWS
|
|
117
205
|
|
|
118
206
|
```yaml
|
|
@@ -121,10 +209,12 @@ platform:
|
|
|
121
209
|
region: us-east-1
|
|
122
210
|
# Written for you on the first deploy, from your credentials.
|
|
123
211
|
account: "123456789012"
|
|
124
|
-
#
|
|
125
|
-
|
|
212
|
+
# Optional. Which profile in ~/.aws to use. Written for you if pdt has to ask.
|
|
213
|
+
profile: work
|
|
126
214
|
```
|
|
127
215
|
|
|
216
|
+
`profile` is optional. When it is missing, pdt uses `AWS_PROFILE`, or the only profile on your computer. When there are several and none is chosen, or the one in the file is not on this computer, the deploy lists them and asks you to choose, then writes your answer here.
|
|
217
|
+
|
|
128
218
|
### Google Cloud
|
|
129
219
|
|
|
130
220
|
```yaml
|
|
@@ -136,6 +226,25 @@ platform:
|
|
|
136
226
|
|
|
137
227
|
`project` is optional. When it is missing or wrong, the deploy lists your projects and asks you to choose one, then writes your answer here.
|
|
138
228
|
|
|
229
|
+
### Bringing your own Dockerfile
|
|
230
|
+
|
|
231
|
+
Every cloud provider runs a job as a container: AWS on Fargate, Azure on Container Apps Jobs, Google Cloud on Cloud Run Jobs. The deploy builds an image for each app from a generated Dockerfile. It copies the app folder and `pdt.yml` into `/workspace`, installs the script-header dependencies of `run.py` with `uv sync --script`, and runs `run.py` as the entrypoint.
|
|
232
|
+
|
|
233
|
+
Put a `Dockerfile` in the app folder to build the image your own way, for example to add system packages or to install a heavy tool at build time instead of on every run. The build context is the same as the generated one: the app folder under its own name, next to `pdt.yml`. Start from the generated file:
|
|
234
|
+
|
|
235
|
+
```dockerfile
|
|
236
|
+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
237
|
+
COPY . /workspace
|
|
238
|
+
WORKDIR /workspace/my-app
|
|
239
|
+
ENV PDT_PROJECT=/workspace
|
|
240
|
+
RUN uv sync --script run.py
|
|
241
|
+
ENTRYPOINT ["uv", "run", "--script", "run.py"]
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
A `.dockerignore` in the app folder keeps files out of the image. Write its patterns relative to the app folder (`.meltano`, `output`, `*.csv`); pdt moves them to the root of the build context for you. `.env` and the other secret files never reach the context, with or without a `.dockerignore`.
|
|
245
|
+
|
|
246
|
+
The Windows provider never builds an image, so `pdt validate` reports a Dockerfile in an app that uses it.
|
|
247
|
+
|
|
139
248
|
### Windows Task Scheduler
|
|
140
249
|
|
|
141
250
|
```yaml
|
|
@@ -195,7 +304,7 @@ An app is a folder with a `run.py` that has a `main()` function. It declares its
|
|
|
195
304
|
```python
|
|
196
305
|
# /// script
|
|
197
306
|
# requires-python = ">=3.12"
|
|
198
|
-
# dependencies = ["pdt-cli[apps]==0.1.
|
|
307
|
+
# dependencies = ["pdt-cli[apps]==0.1.2"]
|
|
199
308
|
# ///
|
|
200
309
|
from pathlib import Path
|
|
201
310
|
|
|
@@ -305,3 +414,15 @@ Leave the two SES key variables empty to use the standard AWS credential chain (
|
|
|
305
414
|
Required environment variables:
|
|
306
415
|
|
|
307
416
|
- PDT_RESEND_API_KEY
|
|
417
|
+
|
|
418
|
+
## Logging
|
|
419
|
+
|
|
420
|
+
`pdt.utils.log` writes one log line per event:
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
from pdt.utils.log import die, log
|
|
424
|
+
|
|
425
|
+
log("info", "processed", rows=42)
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Levels are `debug`, `info`, `warning`, and `error`. Locally a line is human-readable text on stdout; with `LOG_FORMAT=json` it is one JSON object per line, which is also the default on Cloud Run.
|
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: pdt-cli
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Validate, run, and deploy small scheduled jobs to AWS, Azure, Google Cloud, or Windows.
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
Requires-Python: >=3.12
|
|
7
|
-
Requires-Dist: google-auth
|
|
8
|
-
Requires-Dist: google-auth-oauthlib
|
|
9
|
-
Requires-Dist: msal
|
|
10
|
-
Requires-Dist: python-dotenv
|
|
11
|
-
Requires-Dist: pyyaml
|
|
12
|
-
Provides-Extra: apps
|
|
13
|
-
Requires-Dist: boto3; extra == 'apps'
|
|
14
|
-
Requires-Dist: cryptography; extra == 'apps'
|
|
15
|
-
Requires-Dist: google-cloud-secret-manager; extra == 'apps'
|
|
16
|
-
Requires-Dist: pyjwt; extra == 'apps'
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
18
|
-
|
|
19
1
|
# pdt
|
|
20
2
|
|
|
21
3
|
Run scheduled jobs — reports, integrations, automations — and deploy them to AWS, Azure, Google Cloud, or Windows Task Scheduler with one command.
|
|
@@ -28,6 +10,8 @@ uv tool install pdt-cli
|
|
|
28
10
|
|
|
29
11
|
That puts a `pdt` command on your PATH. To update it later, run `uv tool upgrade pdt-cli`.
|
|
30
12
|
|
|
13
|
+
The first interactive `pdt` command turns on tab completion without a prompt. It works in every new terminal: Tab finishes pdt commands, their options, your app names, and example names for `new --from`. It supports bash, zsh, fish, and PowerShell. `pdt completion [SHELL]` sets up a second shell, and `pdt completion --script bash` prints the script instead of writing it, if you keep your own dotfiles.
|
|
14
|
+
|
|
31
15
|
You can also clone this repository and run `./pdt` (or `.\pdt.bat` on Windows) instead. It installs `uv` for you if you do not have it. Both ways give you the same commands.
|
|
32
16
|
|
|
33
17
|
## Set up a project
|
|
@@ -38,7 +22,7 @@ A project is a folder holding `pdt.yml`. Each app is a folder inside it that con
|
|
|
38
22
|
pdt init my-jobs
|
|
39
23
|
```
|
|
40
24
|
|
|
41
|
-
`pdt init` asks where the project should live, which cloud you want, and which region. It warns you if you are about to create a project somewhere unwise, such as your home folder.
|
|
25
|
+
`pdt init` asks where the project should live, which cloud you want, and which region. It warns you if you are about to create a project somewhere unwise, such as your home folder. The folder name is optional: `pdt init` with no name uses the current folder, and `pdt init DIR` uses `DIR`, creating it if it is not there yet. Add `--yes` to take the defaults and answer nothing.
|
|
42
26
|
|
|
43
27
|
Region is the only setting it asks for. Your AWS account, Azure subscription, and Google Cloud project all come from your credentials the first time you deploy, and pdt writes the answer into `pdt.yml` so every later deploy checks against it.
|
|
44
28
|
|
|
@@ -102,17 +86,70 @@ pdt login my-report
|
|
|
102
86
|
| `pdt init [DIR]` | create a project here, or in DIR |
|
|
103
87
|
| `pdt examples` | list the example apps bundled with pdt |
|
|
104
88
|
| `pdt new APP --from EXAMPLE` | add an app to the project |
|
|
105
|
-
| `pdt list` | show every app with its schedule and provider |
|
|
89
|
+
| `pdt list` | show every app with its schedule and provider; `--names` prints only the enabled app names |
|
|
106
90
|
| `pdt validate` | check the config files and the required env vars |
|
|
107
91
|
| `pdt run APP` | run an app on this machine |
|
|
108
92
|
| `pdt deploy APP` | deploy an app to its configured platform |
|
|
109
93
|
| `pdt destroy APP` | remove everything deploy created |
|
|
94
|
+
| `pdt secrets APP` | show which `.env` values differ from the deployed app |
|
|
95
|
+
| `pdt secrets APP save` | send your `.env` values to the deployed app; the next run uses them |
|
|
96
|
+
| `pdt secrets APP get` | copy the deployed values into a `.env.<provider>` file |
|
|
97
|
+
| `pdt secrets APP set NAME` | put one value, read from stdin, into the deployed app's secrets |
|
|
110
98
|
| `pdt login APP` | sign in again to the app's cloud provider |
|
|
99
|
+
| `pdt storage APP ls|get|query|destroy` | look at, fetch, query, or delete the app's stored files |
|
|
111
100
|
| `pdt az ...` | run the Azure CLI that pdt installs |
|
|
112
101
|
| `pdt gcloud ...` | run the Google Cloud CLI that pdt installs |
|
|
102
|
+
| `pdt completion [SHELL]` | turn on tab completion for a shell |
|
|
103
|
+
|
|
104
|
+
Leave `APP` off `run`, `deploy`, `destroy`, `secrets`, or `login`, or mistype it, and pdt lists the apps in the project so you can pick one.
|
|
113
105
|
|
|
114
106
|
`pdt az` and `pdt gcloud` hand your arguments straight to the cloud tool, and install it first if it is missing. For example, `pdt az account list`.
|
|
115
107
|
|
|
108
|
+
## Keeping files between runs
|
|
109
|
+
|
|
110
|
+
Your app runs, writes some files, and stops. Then the computer it ran on is thrown away, and the files go with it. So pdt gives every app a folder in your cloud account that stays. Deploy the app, destroy it, deploy it again: the folder and everything in it is still there.
|
|
111
|
+
|
|
112
|
+
pdt creates one bucket per cloud account, named `pdt-data-` plus a short code, and gives each app its own folder inside it. When your app runs in the cloud, `PDT_STORAGE_URL` points at that folder. When you run `pdt run APP` on your own computer, it points at `.pdt/storage/APP/` inside your project instead. The app code is the same in both places.
|
|
113
|
+
|
|
114
|
+
Write and read files with `pdt.utils.storage`:
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from pdt.utils import storage
|
|
118
|
+
|
|
119
|
+
store = storage.store()
|
|
120
|
+
with store.open("report.csv", "wb") as f:
|
|
121
|
+
f.write(b"name,count\n")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`store.fs()` gives you the full [fsspec](https://filesystem-spec.readthedocs.io/) filesystem, rooted at your app's folder. Output from one run goes under `store.run_folder()`, which names a new `runs/<time>-<id>/` folder each run.
|
|
125
|
+
|
|
126
|
+
Some apps need files from the last run: a Meltano bookmark, a database. Keep those in one local folder, and copy it down before the work and up after:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
from pathlib import Path
|
|
130
|
+
from pdt.utils import storage
|
|
131
|
+
|
|
132
|
+
store = storage.store()
|
|
133
|
+
lease = store.pull("state/", Path(".pdt-state"))
|
|
134
|
+
# do the work; keep anything the next run needs inside .pdt-state/
|
|
135
|
+
store.push(Path(".pdt-state"), "state/", lease)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`pull` locks the folder, so a second copy of your app cannot run at the same time and mix up the files. `push` checks that nobody else changed them, saves them, and unlocks. If a run crashes, the next one takes over the lock after 30 minutes.
|
|
139
|
+
|
|
140
|
+
From your own computer, `pdt storage APP ls`, `get`, and `query` read the files with your own cloud sign-in. `pdt storage APP destroy` is the only command that deletes them, and it asks first. An app that needs none of this sets `storage: false` in its `config.yml`.
|
|
141
|
+
|
|
142
|
+
An app that is not ready sets `enabled: false` in its `config.yml`. `pdt list` still shows it, and every other command acts as if the app is not there. `uv run run.py` in the app folder still runs it.
|
|
143
|
+
|
|
144
|
+
## Running pdt from CI
|
|
145
|
+
|
|
146
|
+
Every command reads its values from the environment it runs in. A build server sets them as CI variables, and no `.env` file is needed.
|
|
147
|
+
|
|
148
|
+
- A `.env` file is a convenience for a person working on their own machine. A value that is already in the environment wins over the same name in a `.env` file.
|
|
149
|
+
- `PDT_ENV_JSON` holds every value as one JSON object, for a CI system that keeps one secret instead of many. For example, `PDT_ENV_JSON={"PDT_TOKEN": "abc", "PDT_SMTP_USER": "reports@example.com"}`.
|
|
150
|
+
- pdt creates no `.env` file on a build server. It looks for the `CI` variable that build systems set, and for a missing terminal. Email authorization must therefore be done first: run `pdt run APP` once on a machine with a browser, then copy `PDT_SMTP_OAUTH_CACHE_B64` (or `PDT_GRAPH_MAIL_CACHE_B64` for Microsoft Graph) from your `.env` into the CI variables.
|
|
151
|
+
- `pdt deploy APP` and `pdt destroy APP` ask before they change anything. Add `--yes` so they proceed without asking.
|
|
152
|
+
|
|
116
153
|
## Choosing where jobs run
|
|
117
154
|
|
|
118
155
|
Set `platform:` in `pdt.yml` for every app, or in an app's own `config.yml` for one app. An app's own file wins.
|
|
@@ -125,12 +162,14 @@ platform:
|
|
|
125
162
|
subscription: 00000000-0000-0000-0000-000000000000
|
|
126
163
|
region: eastus
|
|
127
164
|
resource_group: pdt
|
|
128
|
-
#
|
|
129
|
-
|
|
165
|
+
# Optional. An environment you already own, as <resource-group>/<name>.
|
|
166
|
+
# environment: my-group/my-environment
|
|
130
167
|
```
|
|
131
168
|
|
|
132
169
|
`subscription` is optional. When it is missing or wrong, the deploy asks you to choose one.
|
|
133
170
|
|
|
171
|
+
Every pdt project in a subscription runs its jobs in one shared Container Apps environment per region, `pdt-<region>` in the resource group `pdt-shared`, because a subscription allows only a few environments. Destroying the last app that uses the environment removes it, and removes `pdt-shared` once it holds no environment. Set `environment` to use an environment you already have; pdt then never creates, changes, or deletes it.
|
|
172
|
+
|
|
134
173
|
### AWS
|
|
135
174
|
|
|
136
175
|
```yaml
|
|
@@ -139,10 +178,12 @@ platform:
|
|
|
139
178
|
region: us-east-1
|
|
140
179
|
# Written for you on the first deploy, from your credentials.
|
|
141
180
|
account: "123456789012"
|
|
142
|
-
#
|
|
143
|
-
|
|
181
|
+
# Optional. Which profile in ~/.aws to use. Written for you if pdt has to ask.
|
|
182
|
+
profile: work
|
|
144
183
|
```
|
|
145
184
|
|
|
185
|
+
`profile` is optional. When it is missing, pdt uses `AWS_PROFILE`, or the only profile on your computer. When there are several and none is chosen, or the one in the file is not on this computer, the deploy lists them and asks you to choose, then writes your answer here.
|
|
186
|
+
|
|
146
187
|
### Google Cloud
|
|
147
188
|
|
|
148
189
|
```yaml
|
|
@@ -154,6 +195,25 @@ platform:
|
|
|
154
195
|
|
|
155
196
|
`project` is optional. When it is missing or wrong, the deploy lists your projects and asks you to choose one, then writes your answer here.
|
|
156
197
|
|
|
198
|
+
### Bringing your own Dockerfile
|
|
199
|
+
|
|
200
|
+
Every cloud provider runs a job as a container: AWS on Fargate, Azure on Container Apps Jobs, Google Cloud on Cloud Run Jobs. The deploy builds an image for each app from a generated Dockerfile. It copies the app folder and `pdt.yml` into `/workspace`, installs the script-header dependencies of `run.py` with `uv sync --script`, and runs `run.py` as the entrypoint.
|
|
201
|
+
|
|
202
|
+
Put a `Dockerfile` in the app folder to build the image your own way, for example to add system packages or to install a heavy tool at build time instead of on every run. The build context is the same as the generated one: the app folder under its own name, next to `pdt.yml`. Start from the generated file:
|
|
203
|
+
|
|
204
|
+
```dockerfile
|
|
205
|
+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
206
|
+
COPY . /workspace
|
|
207
|
+
WORKDIR /workspace/my-app
|
|
208
|
+
ENV PDT_PROJECT=/workspace
|
|
209
|
+
RUN uv sync --script run.py
|
|
210
|
+
ENTRYPOINT ["uv", "run", "--script", "run.py"]
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
A `.dockerignore` in the app folder keeps files out of the image. Write its patterns relative to the app folder (`.meltano`, `output`, `*.csv`); pdt moves them to the root of the build context for you. `.env` and the other secret files never reach the context, with or without a `.dockerignore`.
|
|
214
|
+
|
|
215
|
+
The Windows provider never builds an image, so `pdt validate` reports a Dockerfile in an app that uses it.
|
|
216
|
+
|
|
157
217
|
### Windows Task Scheduler
|
|
158
218
|
|
|
159
219
|
```yaml
|
|
@@ -213,7 +273,7 @@ An app is a folder with a `run.py` that has a `main()` function. It declares its
|
|
|
213
273
|
```python
|
|
214
274
|
# /// script
|
|
215
275
|
# requires-python = ">=3.12"
|
|
216
|
-
# dependencies = ["pdt-cli[apps]==0.1.
|
|
276
|
+
# dependencies = ["pdt-cli[apps]==0.1.2"]
|
|
217
277
|
# ///
|
|
218
278
|
from pathlib import Path
|
|
219
279
|
|
|
@@ -323,3 +383,15 @@ Leave the two SES key variables empty to use the standard AWS credential chain (
|
|
|
323
383
|
Required environment variables:
|
|
324
384
|
|
|
325
385
|
- PDT_RESEND_API_KEY
|
|
386
|
+
|
|
387
|
+
## Logging
|
|
388
|
+
|
|
389
|
+
`pdt.utils.log` writes one log line per event:
|
|
390
|
+
|
|
391
|
+
```python
|
|
392
|
+
from pdt.utils.log import die, log
|
|
393
|
+
|
|
394
|
+
log("info", "processed", rows=42)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Levels are `debug`, `info`, `warning`, and `error`. Locally a line is human-readable text on stdout; with `LOG_FORMAT=json` it is one JSON object per line, which is also the default on Cloud Run.
|