pdt-cli 0.1.0__py3-none-any.whl
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/__init__.py +6 -0
- pdt/cli.py +177 -0
- pdt/config.py +394 -0
- pdt/deploy.py +106 -0
- pdt/deploy_aws.py +613 -0
- pdt/deploy_aws_fargate.py +449 -0
- pdt/deploy_aws_lambda.py +359 -0
- pdt/deploy_azure.py +531 -0
- pdt/deploy_azure_container_apps.py +328 -0
- pdt/deploy_azure_functions.py +307 -0
- pdt/deploy_common.py +94 -0
- pdt/deploy_google_cloud.py +756 -0
- pdt/deploy_windows.py +376 -0
- pdt/examples/hello-world/config.yml +20 -0
- pdt/examples/hello-world/run.py +41 -0
- pdt/examples/impossible-travel-report/config.yml +48 -0
- pdt/examples/impossible-travel-report/env.template +57 -0
- pdt/examples/impossible-travel-report/run.py +232 -0
- pdt/examples/monday-orphaned-account-report/config.yml +47 -0
- pdt/examples/monday-orphaned-account-report/env.template +57 -0
- pdt/examples/monday-orphaned-account-report/run.py +333 -0
- pdt/gcloud_sdk.py +170 -0
- pdt/scaffold.py +278 -0
- pdt/utils/__init__.py +0 -0
- pdt/utils/email_auth.py +406 -0
- pdt/utils/entra.py +114 -0
- pdt/utils/log.py +34 -0
- pdt/utils/send_email.py +467 -0
- pdt/utils/send_email_graph.py +266 -0
- pdt/utils/web.py +28 -0
- pdt_cli-0.1.0.dist-info/METADATA +325 -0
- pdt_cli-0.1.0.dist-info/RECORD +34 -0
- pdt_cli-0.1.0.dist-info/WHEEL +4 -0
- pdt_cli-0.1.0.dist-info/entry_points.txt +2 -0
pdt/deploy_common.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""Code shared by every provider deploy script.
|
|
2
|
+
|
|
3
|
+
Every env var the app declares goes into one json secret, mounted on the
|
|
4
|
+
job as PDT_ENV_JSON and expanded back into env vars by
|
|
5
|
+
pdt.config.load_env_json. A var that ends in _PATH is replaced by
|
|
6
|
+
<NAME>_B64 holding the base64 of the file it points to, because the
|
|
7
|
+
cloud job gets no files, only string secrets.
|
|
8
|
+
|
|
9
|
+
A build context holds the app directory and pdt.yml, nothing else. The
|
|
10
|
+
app's run.py declares pdt in its script header, so every deployment
|
|
11
|
+
installs the package from the index the same way a local run does.
|
|
12
|
+
PDT_PROJECT names the project directory, so no job depends on its cwd.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import base64
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import tempfile
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
from pdt import config
|
|
25
|
+
|
|
26
|
+
DOCKERFILE = """\
|
|
27
|
+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
|
28
|
+
COPY . /workspace
|
|
29
|
+
WORKDIR /workspace/{app}
|
|
30
|
+
ENV PDT_PROJECT=/workspace
|
|
31
|
+
RUN uv sync --script run.py
|
|
32
|
+
ENTRYPOINT ["uv", "run", "--script", "run.py"]
|
|
33
|
+
"""
|
|
34
|
+
BUILD_EXCLUDES = (
|
|
35
|
+
".env", ".env.*", ".secrets", ".git", ".venv", "__pycache__",
|
|
36
|
+
".DS_Store", ".gcloud", "*.json.key", "*-key.json",
|
|
37
|
+
"service-account*.json",
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def fail(message: str) -> None:
|
|
42
|
+
print(f"error: {message}")
|
|
43
|
+
raise SystemExit(1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def run_build(command: list[str]) -> None:
|
|
47
|
+
proc = subprocess.run(
|
|
48
|
+
command, capture_output=True, text=True, check=False)
|
|
49
|
+
if proc.returncode:
|
|
50
|
+
if proc.stdout.strip():
|
|
51
|
+
print(proc.stdout.strip())
|
|
52
|
+
if proc.stderr.strip():
|
|
53
|
+
print(proc.stderr.strip())
|
|
54
|
+
fail(f"{' '.join(command[:3])} failed")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def gather_secrets(app: dict) -> dict[str, str]:
|
|
58
|
+
spec = app["env"]
|
|
59
|
+
names = list(spec.get("required") or [])
|
|
60
|
+
for group in spec.get("one_of") or []:
|
|
61
|
+
names.extend(group)
|
|
62
|
+
names.extend(spec.get("optional") or [])
|
|
63
|
+
values = {}
|
|
64
|
+
for name in names:
|
|
65
|
+
value = os.environ.get(name, "").strip()
|
|
66
|
+
if value and name not in values:
|
|
67
|
+
values[name] = value
|
|
68
|
+
for name in [key for key in values if key.endswith("_PATH")]:
|
|
69
|
+
target = name[:-5] + "_B64"
|
|
70
|
+
path = Path(values[name]).expanduser()
|
|
71
|
+
if not path.is_absolute():
|
|
72
|
+
bases = [env_file.parent for env_file in config.find_env_files(app["dir"])]
|
|
73
|
+
bases += [app["dir"], config.find_project()]
|
|
74
|
+
path = next((base / path for base in bases if (base / path).is_file()), path)
|
|
75
|
+
if not path.is_file():
|
|
76
|
+
fail(f"{name} points to {path}, which does not exist")
|
|
77
|
+
values.setdefault(target, base64.b64encode(path.read_bytes()).decode("ascii"))
|
|
78
|
+
del values[name]
|
|
79
|
+
return values
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def stage_build_context(app: dict) -> Path:
|
|
83
|
+
# Stage a clean build context so .env and .secrets never reach the image.
|
|
84
|
+
stage = Path(tempfile.mkdtemp(prefix="pdt-build-"))
|
|
85
|
+
skip = shutil.ignore_patterns(*BUILD_EXCLUDES)
|
|
86
|
+
try:
|
|
87
|
+
shutil.copytree(app["dir"], stage / app["name"], ignore=skip)
|
|
88
|
+
project_file = config.find_project() / config.PROJECT_FILE
|
|
89
|
+
if project_file.is_file():
|
|
90
|
+
shutil.copy(project_file, stage / config.PROJECT_FILE)
|
|
91
|
+
return stage
|
|
92
|
+
except BaseException:
|
|
93
|
+
shutil.rmtree(stage, ignore_errors=True)
|
|
94
|
+
raise
|