@tdk-landscape/tdk-cli-core 1.3.49 → 1.3.50
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.
- package/.claude/skills/layer-autoresearch/SKILL.md +48 -0
- package/.claude/skills/tdk-troubleshoot/SKILL.md +222 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +40 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +18 -0
- package/.github/dependabot.yml +33 -0
- package/.github/pull_request_template.md +5 -0
- package/.github/workflows/ci.yml +187 -0
- package/.github/workflows/release-binaries.yml +19 -9
- package/.github/zizmor.yml +9 -0
- package/CODE_OF_CONDUCT.md +9 -0
- package/CONTRIBUTING.md +42 -0
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/SECURITY.md +17 -0
- package/benchmarks/results/container-scale-2026-09-25T20-54-49-736Z.json +45 -0
- package/benchmarks/results/container-scale-2026-09-25T21-01-20-540Z.json +150 -0
- package/benchmarks/results/container-scale-2026-09-25T21-15-01-441Z.json +73 -0
- package/benchmarks/results/container-scale-2026-09-25T22-09-01-630Z.json +77 -0
- package/benchmarks/results/container-scale-2026-09-25T22-18-28-371Z.json +202 -0
- package/cli/README.md +25 -0
- package/cli/biome.json +1 -1
- package/cli/dist/cli.d.ts +3 -0
- package/cli/dist/cli.d.ts.map +1 -0
- package/cli/dist/cli.js +64 -0
- package/cli/dist/cli.js.map +1 -0
- package/cli/dist/commands/completion.d.ts +3 -0
- package/cli/dist/commands/completion.d.ts.map +1 -0
- package/cli/dist/commands/completion.js +294 -0
- package/cli/dist/commands/completion.js.map +1 -0
- package/cli/dist/commands/config.d.ts +3 -0
- package/cli/dist/commands/config.d.ts.map +1 -0
- package/cli/dist/commands/config.js +231 -0
- package/cli/dist/commands/config.js.map +1 -0
- package/cli/dist/commands/doctor.d.ts +19 -0
- package/cli/dist/commands/doctor.d.ts.map +1 -0
- package/cli/dist/commands/doctor.js +745 -0
- package/cli/dist/commands/doctor.js.map +1 -0
- package/cli/dist/commands/down.d.ts +3 -0
- package/cli/dist/commands/down.d.ts.map +1 -0
- package/cli/dist/commands/down.js +32 -0
- package/cli/dist/commands/down.js.map +1 -0
- package/cli/dist/commands/help.d.ts +2 -0
- package/cli/dist/commands/help.d.ts.map +1 -0
- package/cli/dist/commands/help.js +138 -0
- package/cli/dist/commands/help.js.map +1 -0
- package/cli/dist/commands/networks.d.ts +3 -0
- package/cli/dist/commands/networks.d.ts.map +1 -0
- package/cli/dist/commands/networks.js +260 -0
- package/cli/dist/commands/networks.js.map +1 -0
- package/cli/dist/commands/project.d.ts +3 -0
- package/cli/dist/commands/project.d.ts.map +1 -0
- package/cli/dist/commands/project.js +370 -0
- package/cli/dist/commands/project.js.map +1 -0
- package/cli/dist/commands/projects.d.ts +3 -0
- package/cli/dist/commands/projects.d.ts.map +1 -0
- package/cli/dist/commands/projects.js +71 -0
- package/cli/dist/commands/projects.js.map +1 -0
- package/cli/dist/commands/resource.d.ts +69 -0
- package/cli/dist/commands/resource.d.ts.map +1 -0
- package/cli/dist/commands/resource.js +579 -0
- package/cli/dist/commands/resource.js.map +1 -0
- package/cli/dist/commands/resources.d.ts +3 -0
- package/cli/dist/commands/resources.d.ts.map +1 -0
- package/cli/dist/commands/resources.js +73 -0
- package/cli/dist/commands/resources.js.map +1 -0
- package/cli/dist/commands/stack.d.ts +3 -0
- package/cli/dist/commands/stack.d.ts.map +1 -0
- package/cli/dist/commands/stack.js +97 -0
- package/cli/dist/commands/stack.js.map +1 -0
- package/cli/dist/commands/stacks.d.ts +3 -0
- package/cli/dist/commands/stacks.d.ts.map +1 -0
- package/cli/dist/commands/stacks.js +43 -0
- package/cli/dist/commands/stacks.js.map +1 -0
- package/cli/dist/commands/status.d.ts +3 -0
- package/cli/dist/commands/status.d.ts.map +1 -0
- package/cli/dist/commands/status.js +71 -0
- package/cli/dist/commands/status.js.map +1 -0
- package/cli/dist/commands/ui.d.ts +3 -0
- package/cli/dist/commands/ui.d.ts.map +1 -0
- package/cli/dist/commands/ui.js +445 -0
- package/cli/dist/commands/ui.js.map +1 -0
- package/cli/dist/commands/up.d.ts +3 -0
- package/cli/dist/commands/up.d.ts.map +1 -0
- package/cli/dist/commands/up.js +124 -0
- package/cli/dist/commands/up.js.map +1 -0
- package/cli/dist/commands/upgrade.d.ts +11 -0
- package/cli/dist/commands/upgrade.d.ts.map +1 -0
- package/cli/dist/commands/upgrade.js +496 -0
- package/cli/dist/commands/upgrade.js.map +1 -0
- package/cli/dist/commands/version.d.ts +3 -0
- package/cli/dist/commands/version.d.ts.map +1 -0
- package/cli/dist/commands/version.js +9 -0
- package/cli/dist/commands/version.js.map +1 -0
- package/cli/dist/components/Accessible.d.ts +8 -0
- package/cli/dist/components/Accessible.d.ts.map +1 -0
- package/cli/dist/components/Accessible.js +10 -0
- package/cli/dist/components/Accessible.js.map +1 -0
- package/cli/dist/components/BaseTooltip.d.ts +4 -0
- package/cli/dist/components/BaseTooltip.d.ts.map +1 -0
- package/cli/dist/components/BaseTooltip.js +28 -0
- package/cli/dist/components/BaseTooltip.js.map +1 -0
- package/cli/dist/components/DetailPanel.d.ts +4 -0
- package/cli/dist/components/DetailPanel.d.ts.map +1 -0
- package/cli/dist/components/DetailPanel.js +18 -0
- package/cli/dist/components/DetailPanel.js.map +1 -0
- package/cli/dist/components/FileTree.d.ts +4 -0
- package/cli/dist/components/FileTree.d.ts.map +1 -0
- package/cli/dist/components/FileTree.js +47 -0
- package/cli/dist/components/FileTree.js.map +1 -0
- package/cli/dist/components/ResourceSelectInput.d.ts +4 -0
- package/cli/dist/components/ResourceSelectInput.d.ts.map +1 -0
- package/cli/dist/components/ResourceSelectInput.js +7 -0
- package/cli/dist/components/ResourceSelectInput.js.map +1 -0
- package/cli/dist/components/ResourceTable.d.ts +4 -0
- package/cli/dist/components/ResourceTable.d.ts.map +1 -0
- package/cli/dist/components/ResourceTable.js +18 -0
- package/cli/dist/components/ResourceTable.js.map +1 -0
- package/cli/dist/components/TabBar.d.ts +4 -0
- package/cli/dist/components/TabBar.d.ts.map +1 -0
- package/cli/dist/components/TabBar.js +17 -0
- package/cli/dist/components/TabBar.js.map +1 -0
- package/cli/dist/components/Tooltip.d.ts +12 -0
- package/cli/dist/components/Tooltip.d.ts.map +1 -0
- package/cli/dist/components/Tooltip.js +12 -0
- package/cli/dist/components/Tooltip.js.map +1 -0
- package/cli/dist/components/index.d.ts +8 -0
- package/cli/dist/components/index.d.ts.map +1 -0
- package/cli/dist/components/index.js +8 -0
- package/cli/dist/components/index.js.map +1 -0
- package/cli/dist/config/platform-standards.d.ts +159 -0
- package/cli/dist/config/platform-standards.d.ts.map +1 -0
- package/cli/dist/config/platform-standards.js +181 -0
- package/cli/dist/config/platform-standards.js.map +1 -0
- package/cli/dist/generator/extension-fetch.d.ts +25 -0
- package/cli/dist/generator/extension-fetch.d.ts.map +1 -0
- package/cli/dist/generator/extension-fetch.js +259 -0
- package/cli/dist/generator/extension-fetch.js.map +1 -0
- package/cli/dist/generator/template-engine.d.ts +62 -0
- package/cli/dist/generator/template-engine.d.ts.map +1 -0
- package/cli/dist/generator/template-engine.js +623 -0
- package/cli/dist/generator/template-engine.js.map +1 -0
- package/cli/dist/index.d.ts +11 -0
- package/cli/dist/index.d.ts.map +1 -0
- package/cli/dist/index.js +11 -0
- package/cli/dist/index.js.map +1 -0
- package/cli/dist/types/index.d.ts +257 -0
- package/cli/dist/types/index.d.ts.map +1 -0
- package/cli/dist/types/index.js +17 -0
- package/cli/dist/types/index.js.map +1 -0
- package/cli/dist/utils/cache.d.ts +19 -0
- package/cli/dist/utils/cache.d.ts.map +1 -0
- package/cli/dist/utils/cache.js +64 -0
- package/cli/dist/utils/cache.js.map +1 -0
- package/cli/dist/utils/command-helpers.d.ts +17 -0
- package/cli/dist/utils/command-helpers.d.ts.map +1 -0
- package/cli/dist/utils/command-helpers.js +41 -0
- package/cli/dist/utils/command-helpers.js.map +1 -0
- package/cli/dist/utils/constants.d.ts +53 -0
- package/cli/dist/utils/constants.d.ts.map +1 -0
- package/cli/dist/utils/constants.js +115 -0
- package/cli/dist/utils/constants.js.map +1 -0
- package/cli/dist/utils/discovery-context.d.ts +4 -0
- package/cli/dist/utils/discovery-context.d.ts.map +1 -0
- package/cli/dist/utils/discovery-context.js +39 -0
- package/cli/dist/utils/discovery-context.js.map +1 -0
- package/cli/dist/utils/docker.d.ts +4 -0
- package/cli/dist/utils/docker.d.ts.map +1 -0
- package/cli/dist/utils/docker.js +19 -0
- package/cli/dist/utils/docker.js.map +1 -0
- package/cli/dist/utils/doctor-runtime.d.ts +81 -0
- package/cli/dist/utils/doctor-runtime.d.ts.map +1 -0
- package/cli/dist/utils/doctor-runtime.js +498 -0
- package/cli/dist/utils/doctor-runtime.js.map +1 -0
- package/cli/dist/utils/env-validator.d.ts +15 -0
- package/cli/dist/utils/env-validator.d.ts.map +1 -0
- package/cli/dist/utils/env-validator.js +109 -0
- package/cli/dist/utils/env-validator.js.map +1 -0
- package/cli/dist/utils/errors.d.ts +31 -0
- package/cli/dist/utils/errors.d.ts.map +1 -0
- package/cli/dist/utils/errors.js +140 -0
- package/cli/dist/utils/errors.js.map +1 -0
- package/cli/dist/utils/file-helpers.d.ts +14 -0
- package/cli/dist/utils/file-helpers.d.ts.map +1 -0
- package/cli/dist/utils/file-helpers.js +45 -0
- package/cli/dist/utils/file-helpers.js.map +1 -0
- package/cli/dist/utils/formatting.d.ts +30 -0
- package/cli/dist/utils/formatting.d.ts.map +1 -0
- package/cli/dist/utils/formatting.js +164 -0
- package/cli/dist/utils/formatting.js.map +1 -0
- package/cli/dist/utils/paths.d.ts +3 -0
- package/cli/dist/utils/paths.d.ts.map +1 -0
- package/cli/dist/utils/paths.js +41 -0
- package/cli/dist/utils/paths.js.map +1 -0
- package/cli/dist/utils/port-assignment.d.ts +6 -0
- package/cli/dist/utils/port-assignment.d.ts.map +1 -0
- package/cli/dist/utils/port-assignment.js +81 -0
- package/cli/dist/utils/port-assignment.js.map +1 -0
- package/cli/dist/utils/project-features.d.ts +23 -0
- package/cli/dist/utils/project-features.d.ts.map +1 -0
- package/cli/dist/utils/project-features.js +98 -0
- package/cli/dist/utils/project-features.js.map +1 -0
- package/cli/dist/utils/project-templates.d.ts +7 -0
- package/cli/dist/utils/project-templates.d.ts.map +1 -0
- package/cli/dist/utils/project-templates.js +24 -0
- package/cli/dist/utils/project-templates.js.map +1 -0
- package/cli/dist/utils/prompt.d.ts +28 -0
- package/cli/dist/utils/prompt.d.ts.map +1 -0
- package/cli/dist/utils/prompt.js +79 -0
- package/cli/dist/utils/prompt.js.map +1 -0
- package/cli/dist/utils/resource-features.d.ts +29 -0
- package/cli/dist/utils/resource-features.d.ts.map +1 -0
- package/cli/dist/utils/resource-features.js +175 -0
- package/cli/dist/utils/resource-features.js.map +1 -0
- package/cli/dist/utils/service-urls.d.ts +30 -0
- package/cli/dist/utils/service-urls.d.ts.map +1 -0
- package/cli/dist/utils/service-urls.js +93 -0
- package/cli/dist/utils/service-urls.js.map +1 -0
- package/cli/dist/utils/services.d.ts +18 -0
- package/cli/dist/utils/services.d.ts.map +1 -0
- package/cli/dist/utils/services.js +292 -0
- package/cli/dist/utils/services.js.map +1 -0
- package/cli/dist/utils/stack-features.d.ts +22 -0
- package/cli/dist/utils/stack-features.d.ts.map +1 -0
- package/cli/dist/utils/stack-features.js +13 -0
- package/cli/dist/utils/stack-features.js.map +1 -0
- package/cli/dist/utils/tar.d.ts +7 -0
- package/cli/dist/utils/tar.d.ts.map +1 -0
- package/cli/dist/utils/tar.js +21 -0
- package/cli/dist/utils/tar.js.map +1 -0
- package/cli/dist/utils/tilt.d.ts +20 -0
- package/cli/dist/utils/tilt.d.ts.map +1 -0
- package/cli/dist/utils/tilt.js +95 -0
- package/cli/dist/utils/tilt.js.map +1 -0
- package/cli/dist/utils/validation.d.ts +16 -0
- package/cli/dist/utils/validation.d.ts.map +1 -0
- package/cli/dist/utils/validation.js +58 -0
- package/cli/dist/utils/validation.js.map +1 -0
- package/cli/package.json +11 -12
- package/cli/src/commands/__tests__/always-enabled-infra.test.ts +2 -8
- package/cli/src/commands/__tests__/doctor.test.ts +181 -8
- package/cli/src/commands/__tests__/phases-model.test.ts +2 -4
- package/cli/src/commands/__tests__/project-resource-features-e2e.test.ts +4 -24
- package/cli/src/commands/__tests__/saas-starter-integration.test.ts +8 -10
- package/cli/src/commands/__tests__/stack-features-e2e.test.ts +10 -4
- package/cli/src/commands/__tests__/upgrade.test.ts +23 -13
- package/cli/src/commands/doctor.ts +136 -0
- package/cli/src/commands/help.ts +13 -3
- package/cli/src/commands/project.ts +11 -14
- package/cli/src/commands/projects.ts +2 -2
- package/cli/src/commands/up.ts +13 -4
- package/cli/src/commands/upgrade.ts +11 -4
- package/cli/src/config/platform-standards.test.ts +6 -6
- package/cli/src/generator/__tests__/extension-fetch.test.ts +36 -14
- package/cli/src/generator/extension-fetch.ts +12 -5
- package/cli/src/generator/template-engine.ts +19 -21
- package/cli/src/types/index.ts +2 -0
- package/cli/src/utils/__tests__/tilt.test.ts +29 -0
- package/cli/src/utils/constants.ts +4 -0
- package/cli/src/utils/doctor-runtime.ts +67 -27
- package/cli/src/utils/env-validator.ts +2 -2
- package/cli/src/utils/project-templates.ts +2 -1
- package/cli/src/utils/prompt.ts +2 -4
- package/cli/src/utils/resource-features.ts +10 -4
- package/cli/src/utils/services.ts +2 -1
- package/cli/src/utils/tilt.ts +20 -0
- package/engine/assets/docker/wake-gateway/Dockerfile +26 -0
- package/engine/assets/docker/wake-gateway/decide.test.ts +87 -0
- package/engine/assets/docker/wake-gateway/decide.ts +75 -0
- package/engine/assets/docker/wake-gateway/server.ts +220 -0
- package/engine/topologies/platform/docker/build/golden_image_dockerfile.star +13 -23
- package/engine/topologies/platform/docker/compose/compose.star +7 -14
- package/engine/topologies/platform/docker/compose/compose_helpers.star +1 -1
- package/engine/topologies/platform/docker/compose/traefik_standalone.star +44 -6
- package/engine/topologies/platform/docker/config/healthcheck.star +56 -0
- package/engine/topologies/platform/docker/constants.star +10 -0
- package/engine/topologies/platform/docker/layers/l1_base_layers.star +1 -1
- package/engine/topologies/platform/docker/layers/l4_runtime_layers.star +55 -20
- package/engine/topologies/platform/docker/networking/traefik_static_routes.star +107 -0
- package/engine/topologies/tilt/generators/typescript/backend_tsconfig.star +2 -2
- package/engine/topologies/tilt/generators/typescript/frontend_tsconfig.star +1 -1
- package/engine/topologies/tilt/manifest/schema.star +6 -0
- package/engine/topologies/tilt/manifest/validator.star +16 -0
- package/engine/topologies/tilt/resources/orchestrator/apply_compose_resource_registration.star +107 -8
- package/engine/topologies/tilt/resources/orchestrator/builders/typescript.star +12 -6
- package/engine/topologies/tilt/resources/orchestrator/generators/manifest_resource.star +2 -1
- package/knip.json +1 -1
- package/openspec/changes/optimize-runtime-docker-layers/.openspec.yaml +3 -0
- package/openspec/changes/optimize-runtime-docker-layers/design.md +81 -0
- package/openspec/changes/optimize-runtime-docker-layers/proposal.md +44 -0
- package/openspec/changes/optimize-runtime-docker-layers/specs/runtime-container-footprint/spec.md +79 -0
- package/openspec/changes/optimize-runtime-docker-layers/tasks.md +36 -0
- package/openspec/changes/prioritized-cold-start/.openspec.yaml +4 -0
- package/openspec/changes/prioritized-cold-start/design.md +113 -0
- package/openspec/changes/prioritized-cold-start/proposal.md +40 -0
- package/openspec/changes/prioritized-cold-start/specs/on-demand-resource-startup/spec.md +92 -0
- package/openspec/changes/prioritized-cold-start/tasks.md +46 -0
- package/openspec/config.yaml +1 -0
- package/package.json +35 -5
- package/scripts/autoresearch/layer-eval.sh +65 -0
- package/scripts/benchmark/README.md +25 -0
- package/scripts/benchmark/container-scale.ts +503 -0
- package/scripts/benchmark/pre-release-gate.sh +9 -0
- package/scripts/measure-idle-footprint.sh +58 -0
- package/tests/tilt-engine/test_runtime_container_footprint.py +162 -0
- package/tests/tilt-engine/test_sablier_defer_start.py +196 -0
- package/tests/tilt-engine/test_standalone_traefik.py +38 -0
- package/tests/tilt-engine/test_traefik_static_wake_route.py +119 -0
- package/bun.lock +0 -538
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wake gateway: fronts every `sablier.deferStart` resource's Traefik static
|
|
3
|
+
* route (see openspec/changes/prioritized-cold-start). On the first request
|
|
4
|
+
* for a resource with no running container, it triggers the resource (and
|
|
5
|
+
* its cold dependencies) via `tilt trigger`, and/or Sablier's own start for
|
|
6
|
+
* an already-created container, then holds the request until the resource
|
|
7
|
+
* answers its healthcheck or the configured timeout elapses.
|
|
8
|
+
*
|
|
9
|
+
* Config for each request comes from headers a Traefik `headers` middleware
|
|
10
|
+
* injects (set by the file-provider entry generated in
|
|
11
|
+
* traefik_static_routes.star) rather than the path, so the original request
|
|
12
|
+
* path/query reach the real backend unchanged once it's ready:
|
|
13
|
+
* X-Wake-Resource Tilt/Compose resource name (for `tilt trigger`,
|
|
14
|
+
* and the proxy target -- Compose's embedded DNS
|
|
15
|
+
* resolves a service by this name too).
|
|
16
|
+
* X-Wake-Container The actual Docker container name (for `docker
|
|
17
|
+
* inspect` state/health -- NOT the same string as
|
|
18
|
+
* the resource name; Compose derives it as
|
|
19
|
+
* <project>-<service>-<replica>, confirmed empirically
|
|
20
|
+
* against a live TDK container, e.g.
|
|
21
|
+
* "analytics-autogenerated-data-governance-api-1"
|
|
22
|
+
* for the "data-governance-api" resource).
|
|
23
|
+
* X-Wake-Deps Comma-separated "resourceName=containerName" pairs
|
|
24
|
+
* for required dependencies (task 1.5: Tilt never
|
|
25
|
+
* cascades a trigger to these on its own).
|
|
26
|
+
* X-Wake-Timeout-Ms, X-Wake-Proxy-Port, X-Wake-Session-Duration
|
|
27
|
+
*/
|
|
28
|
+
import { decideWakeAction, evaluateReadiness, type ContainerState } from "./decide";
|
|
29
|
+
|
|
30
|
+
const TILT_HOST = process.env.TILT_HOST || "host.docker.internal";
|
|
31
|
+
const TILT_PORT = process.env.TILT_PORT || "10350";
|
|
32
|
+
const SABLIER_URL = process.env.SABLIER_URL || "http://sablier:10000";
|
|
33
|
+
const POLL_INTERVAL_MS = 1000;
|
|
34
|
+
const DEFAULT_TIMEOUT_MS = 60_000;
|
|
35
|
+
|
|
36
|
+
async function run(cmd: string[]): Promise<{ code: number; stdout: string; stderr: string }> {
|
|
37
|
+
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
38
|
+
const [stdout, stderr, code] = await Promise.all([
|
|
39
|
+
new Response(proc.stdout).text(),
|
|
40
|
+
new Response(proc.stderr).text(),
|
|
41
|
+
proc.exited,
|
|
42
|
+
]);
|
|
43
|
+
return { code, stdout, stderr };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function containerState(name: string): Promise<ContainerState> {
|
|
47
|
+
const { code, stdout } = await run([
|
|
48
|
+
"docker", "inspect", "--format", "{{.State.Status}}", name,
|
|
49
|
+
]);
|
|
50
|
+
if (code !== 0) return "absent";
|
|
51
|
+
const status = stdout.trim();
|
|
52
|
+
if (status === "running") return "running";
|
|
53
|
+
if (status === "created") return "created";
|
|
54
|
+
return "exited";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function isHealthy(name: string): Promise<boolean> {
|
|
58
|
+
const { code, stdout } = await run([
|
|
59
|
+
"docker", "inspect",
|
|
60
|
+
"--format", "{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}",
|
|
61
|
+
name,
|
|
62
|
+
]);
|
|
63
|
+
if (code !== 0) return false;
|
|
64
|
+
const status = stdout.trim();
|
|
65
|
+
return status === "healthy" || status === "running";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function tiltTrigger(name: string): Promise<void> {
|
|
69
|
+
await run(["tilt", "trigger", name, "--host", TILT_HOST, "--port", TILT_PORT]);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Sablier's real blocking-strategy endpoint (GET /api/strategies/blocking,
|
|
74
|
+
* per https://sablierapp.dev/openapi.json): starts the named instance and
|
|
75
|
+
* holds until it's ready or `timeoutMs` elapses, returning that outcome via
|
|
76
|
+
* the X-Sablier-Session-Status header. Only valid for an instance Sablier
|
|
77
|
+
* already knows about (a container that exists with sablier.enable=true) --
|
|
78
|
+
* see decide.ts's useSablierStart.
|
|
79
|
+
*/
|
|
80
|
+
async function sablierBlockingWait(resource: string, sessionDuration: string, timeoutMs: number): Promise<boolean> {
|
|
81
|
+
const params = new URLSearchParams({
|
|
82
|
+
names: resource,
|
|
83
|
+
session_duration: sessionDuration,
|
|
84
|
+
timeout: `${Math.max(1, Math.round(timeoutMs / 1000))}s`,
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetch(`${SABLIER_URL}/api/strategies/blocking?${params}`);
|
|
88
|
+
return res.headers.get("X-Sablier-Session-Status") === "ready";
|
|
89
|
+
} catch {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface NamedDep {
|
|
95
|
+
resourceName: string;
|
|
96
|
+
containerName: string;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function parseDeps(raw: string): NamedDep[] {
|
|
100
|
+
return raw
|
|
101
|
+
.split(",")
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.map((pair) => {
|
|
104
|
+
const [resourceName, containerName] = pair.split("=");
|
|
105
|
+
return { resourceName, containerName: containerName || resourceName };
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function parseRequest(req: Request) {
|
|
110
|
+
const resource = req.headers.get("X-Wake-Resource") || "";
|
|
111
|
+
const container = req.headers.get("X-Wake-Container") || resource;
|
|
112
|
+
const deps = parseDeps(req.headers.get("X-Wake-Deps") || "");
|
|
113
|
+
const timeoutMs = Number(req.headers.get("X-Wake-Timeout-Ms")) || DEFAULT_TIMEOUT_MS;
|
|
114
|
+
const proxyPort = req.headers.get("X-Wake-Proxy-Port") || "80";
|
|
115
|
+
const sessionDuration = req.headers.get("X-Wake-Session-Duration") || "10m";
|
|
116
|
+
return { resource, container, deps, timeoutMs, proxyPort, sessionDuration };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function handleWake(req: Request): Promise<Response> {
|
|
120
|
+
const { resource, container, deps, timeoutMs, proxyPort, sessionDuration } = parseRequest(req);
|
|
121
|
+
if (!resource) {
|
|
122
|
+
return new Response("wake gateway: missing X-Wake-Resource header (set by the generated Traefik static route)", { status: 400 });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const resourceState = await containerState(container);
|
|
126
|
+
const dependencies = await Promise.all(
|
|
127
|
+
deps.map(async (dep) => ({ name: dep.resourceName, state: await containerState(dep.containerName) })),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const action = decideWakeAction({ resourceContainerName: resource, resourceState, dependencies });
|
|
131
|
+
const deadline = Date.now() + timeoutMs;
|
|
132
|
+
|
|
133
|
+
// tilt trigger runs for everything cold (task 1.5: Tilt never cascades this
|
|
134
|
+
// on its own); Sablier's blocking call, when applicable, both starts AND
|
|
135
|
+
// waits for the resource itself in one round trip.
|
|
136
|
+
const [, resourceReadyViaSablier] = await Promise.all([
|
|
137
|
+
Promise.all(action.tiltTriggerNames.map(tiltTrigger)),
|
|
138
|
+
action.useSablierStart
|
|
139
|
+
? sablierBlockingWait(resource, sessionDuration, timeoutMs)
|
|
140
|
+
: Promise.resolve(false),
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
const requiredNames = [resource, ...deps.map((d) => d.resourceName)];
|
|
144
|
+
const containerByName: Record<string, string> = { [resource]: container };
|
|
145
|
+
for (const dep of deps) containerByName[dep.resourceName] = dep.containerName;
|
|
146
|
+
|
|
147
|
+
while (Date.now() < deadline) {
|
|
148
|
+
const healthy: Record<string, boolean> = {};
|
|
149
|
+
// Sablier's blocking call already confirmed the resource itself; re-check
|
|
150
|
+
// it too (cheap) so a false result from Sablier still gets a fair retry.
|
|
151
|
+
healthy[resource] = resourceReadyViaSablier || (await isHealthy(container));
|
|
152
|
+
for (const dep of deps) {
|
|
153
|
+
healthy[dep.resourceName] = await isHealthy(dep.containerName);
|
|
154
|
+
}
|
|
155
|
+
const outcome = evaluateReadiness(requiredNames, healthy);
|
|
156
|
+
if (outcome.ready) {
|
|
157
|
+
return proxyThrough(req, resource, proxyPort);
|
|
158
|
+
}
|
|
159
|
+
await Bun.sleep(POLL_INTERVAL_MS);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return new Response(
|
|
163
|
+
`wake gateway: timed out after ${timeoutMs}ms waiting for ${resource} and its dependencies to become healthy`,
|
|
164
|
+
{ status: 504 },
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Headers that must not be forwarded verbatim to the upstream fetch: Host
|
|
169
|
+
// would otherwise carry the gateway's own address, and the X-Wake-* control
|
|
170
|
+
// headers are gateway-internal, not part of the real app request.
|
|
171
|
+
const STRIPPED_REQUEST_HEADERS = new Set([
|
|
172
|
+
"host",
|
|
173
|
+
"x-wake-resource",
|
|
174
|
+
"x-wake-container",
|
|
175
|
+
"x-wake-deps",
|
|
176
|
+
"x-wake-timeout-ms",
|
|
177
|
+
"x-wake-proxy-port",
|
|
178
|
+
"x-wake-session-duration",
|
|
179
|
+
]);
|
|
180
|
+
|
|
181
|
+
async function proxyThrough(req: Request, resource: string, port: string): Promise<Response> {
|
|
182
|
+
// The original path/query reach the gateway untouched (config travels via
|
|
183
|
+
// X-Wake-* headers, not the path), so they're forwarded as-is.
|
|
184
|
+
const target = new URL(req.url);
|
|
185
|
+
target.protocol = "http:";
|
|
186
|
+
// Set hostname/port separately, not via `.host` -- confirmed empirically
|
|
187
|
+
// that `target.host = "name:port"` silently keeps the URL's original port
|
|
188
|
+
// when one is already present instead of replacing it.
|
|
189
|
+
target.hostname = resource;
|
|
190
|
+
target.port = port;
|
|
191
|
+
|
|
192
|
+
const headers = new Headers();
|
|
193
|
+
for (const [key, value] of req.headers) {
|
|
194
|
+
if (!STRIPPED_REQUEST_HEADERS.has(key.toLowerCase())) headers.set(key, value);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const upstream = await fetch(target.toString(), {
|
|
198
|
+
method: req.method,
|
|
199
|
+
headers,
|
|
200
|
+
body: req.method === "GET" || req.method === "HEAD" ? undefined : await req.arrayBuffer(),
|
|
201
|
+
});
|
|
202
|
+
return new Response(upstream.body, { status: upstream.status, headers: upstream.headers });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
Bun.serve({
|
|
206
|
+
port: Number(process.env.PORT) || 9999,
|
|
207
|
+
// Bun's default idleTimeout (10s) is shorter than a real wake can take
|
|
208
|
+
// (tilt trigger + healthcheck polling) -- confirmed empirically: a request
|
|
209
|
+
// was killed mid-wait with "request timed out after 10 seconds" before this
|
|
210
|
+
// was raised. 255s is Bun's own max for this setting.
|
|
211
|
+
idleTimeout: 255,
|
|
212
|
+
fetch(req) {
|
|
213
|
+
return handleWake(req).catch((err) => {
|
|
214
|
+
console.error("wake gateway error:", err);
|
|
215
|
+
return new Response(`wake gateway error: ${String(err)}`, { status: 500 });
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
console.log(`wake gateway listening on :${Number(process.env.PORT) || 9999}`);
|
|
@@ -2,16 +2,8 @@
|
|
|
2
2
|
# 🏗️ TILT SDK - GOLDEN IMAGE DOCKERFILE GENERATOR
|
|
3
3
|
# =============================================================================
|
|
4
4
|
|
|
5
|
-
# === INLINED CONSTANTS for pure extension loading ===
|
|
6
|
-
get_docker_healthcheck_config = lambda: {"path": "/health", "interval_seconds": 10, "timeout_seconds": 5, "start_period_seconds": 30, "retries": 3, "frontend_start_period_seconds": 30}
|
|
7
|
-
# === END INLINED CONSTANTS ===
|
|
8
|
-
|
|
9
|
-
|
|
10
5
|
load('../../../tilt/common/utils.star', 'Utils')
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
# Load centralized healthcheck configuration
|
|
14
|
-
_docker_health = get_docker_healthcheck_config()
|
|
6
|
+
load('../config/healthcheck.star', 'DOCKER_HEALTHCHECK', 'dockerfile_healthcheck_flags')
|
|
15
7
|
|
|
16
8
|
|
|
17
9
|
# Load project name for dynamic naming
|
|
@@ -76,7 +68,6 @@ RUN apk add --no-cache \
|
|
|
76
68
|
ca-certificates \
|
|
77
69
|
tzdata \
|
|
78
70
|
curl \
|
|
79
|
-
file \
|
|
80
71
|
bash \
|
|
81
72
|
openssl
|
|
82
73
|
|
|
@@ -180,9 +171,11 @@ LABEL layer="l4-backend-bun" \
|
|
|
180
171
|
maintainer="{prefix}"
|
|
181
172
|
|
|
182
173
|
ENV NODE_ENV=production
|
|
174
|
+
# Smaller heap growth and more frequent GC: idle services dominate a 100-service landscape.
|
|
175
|
+
ENV BUN_OPTIONS=--smol
|
|
183
176
|
|
|
184
|
-
#
|
|
185
|
-
|
|
177
|
+
# Service-specific tooling (e.g. hugo) is installed per service by the L4 runtime
|
|
178
|
+
# layer when the manifest lists it in featuresEnabled - not here for every backend.
|
|
186
179
|
|
|
187
180
|
# ⚠️ Infisical CLI is NOT installed in golden L4 images
|
|
188
181
|
# Services use TypeScript SDK (fetch API) or read from env vars - no CLI needed
|
|
@@ -192,7 +185,7 @@ RUN apk add --no-cache hugo
|
|
|
192
185
|
USER bun
|
|
193
186
|
|
|
194
187
|
# Backend-specific healthcheck (checks Bun runtime)
|
|
195
|
-
HEALTHCHECK
|
|
188
|
+
HEALTHCHECK {hc_flags} \
|
|
196
189
|
CMD bun --version || exit 1
|
|
197
190
|
|
|
198
191
|
WORKDIR /app
|
|
@@ -221,7 +214,7 @@ RUN addgroup -S app && adduser -S app -G app
|
|
|
221
214
|
USER app
|
|
222
215
|
|
|
223
216
|
# Backend-specific healthcheck (checks Node.js runtime)
|
|
224
|
-
HEALTHCHECK
|
|
217
|
+
HEALTHCHECK {hc_flags} \
|
|
225
218
|
CMD node --version || exit 1
|
|
226
219
|
|
|
227
220
|
WORKDIR /app
|
|
@@ -246,7 +239,7 @@ ENV NODE_ENV=production
|
|
|
246
239
|
RUN apk add --no-cache curl
|
|
247
240
|
|
|
248
241
|
# Frontend-specific healthcheck (checks Nginx)
|
|
249
|
-
HEALTHCHECK
|
|
242
|
+
HEALTHCHECK {hc_flags_frontend} \
|
|
250
243
|
CMD curl -f http://localhost/ || exit 1
|
|
251
244
|
|
|
252
245
|
WORKDIR /usr/share/nginx/html
|
|
@@ -276,16 +269,13 @@ RUN mkdir -p /cache/bun && chown -R bun:bun /cache/bun
|
|
|
276
269
|
|
|
277
270
|
USER bun
|
|
278
271
|
|
|
279
|
-
#
|
|
280
|
-
|
|
281
|
-
|
|
272
|
+
# Migrators are one-shot jobs (migrate.sh runs to completion), so no healthcheck.
|
|
273
|
+
# The per-service l4_migrator_runtime stage sets HEALTHCHECK NONE as well.
|
|
274
|
+
HEALTHCHECK NONE
|
|
282
275
|
|
|
283
276
|
WORKDIR /app
|
|
284
277
|
""".format(
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
start_period=_docker_health["start_period_seconds"],
|
|
288
|
-
retries=_docker_health["retries"],
|
|
289
|
-
frontend_start_period=_docker_health["frontend_start_period_seconds"],
|
|
278
|
+
hc_flags=dockerfile_healthcheck_flags(),
|
|
279
|
+
hc_flags_frontend=dockerfile_healthcheck_flags(DOCKER_HEALTHCHECK["frontend_start_period_seconds"]),
|
|
290
280
|
prefix=_GOLDEN_PREFIX,
|
|
291
281
|
)
|
|
@@ -23,6 +23,7 @@ load("../secrets/infisical.star", "get_infisical_environment_vars")
|
|
|
23
23
|
load("../auth/auth.star", "AuthConfig")
|
|
24
24
|
|
|
25
25
|
load("../constants.star", "PlatformDockerConstants")
|
|
26
|
+
load("../config/healthcheck.star", "DOCKER_HEALTHCHECK", "compose_healthcheck_timing")
|
|
26
27
|
|
|
27
28
|
# Load project name for dynamic host naming
|
|
28
29
|
# Use TDK_PROJECT_ROOT env var set by Tilt, fallback to current directory
|
|
@@ -125,6 +126,7 @@ def generate_frontend_compose(resource_path, resource_name, res, manifest=None):
|
|
|
125
126
|
|
|
126
127
|
return """ {res_name}:
|
|
127
128
|
image: {image_name}
|
|
129
|
+
init: true
|
|
128
130
|
{build_config}
|
|
129
131
|
<<: *frontend-memory-limit
|
|
130
132
|
env_file:
|
|
@@ -138,12 +140,7 @@ def generate_frontend_compose(resource_path, resource_name, res, manifest=None):
|
|
|
138
140
|
- {network_backend}
|
|
139
141
|
healthcheck:
|
|
140
142
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:{port}"]
|
|
141
|
-
|
|
142
|
-
timeout: 5s
|
|
143
|
-
retries: 3
|
|
144
|
-
# STORY 4 FIX: 30s grace period to prevent 504 errors on startup
|
|
145
|
-
start_period: 30s
|
|
146
|
-
""".format(
|
|
143
|
+
{healthcheck_timing}""".format(
|
|
147
144
|
res_name=res_name,
|
|
148
145
|
image_name=image_name,
|
|
149
146
|
build_config=build_config,
|
|
@@ -152,6 +149,7 @@ def generate_frontend_compose(resource_path, resource_name, res, manifest=None):
|
|
|
152
149
|
port=port,
|
|
153
150
|
network_traefik_public=PlatformDockerConstants.NETWORK_TRAEFIK_PUBLIC,
|
|
154
151
|
network_backend=PlatformDockerConstants.NETWORK_BACKEND,
|
|
152
|
+
healthcheck_timing=compose_healthcheck_timing(DOCKER_HEALTHCHECK["frontend_start_period_seconds"]),
|
|
155
153
|
)
|
|
156
154
|
|
|
157
155
|
|
|
@@ -261,13 +259,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
261
259
|
)
|
|
262
260
|
healthcheck_section = """ healthcheck:
|
|
263
261
|
test: ["CMD", "curl", "-f", "--max-time", "5", "http://localhost:{internal_port}{health_path}"]
|
|
264
|
-
|
|
265
|
-
timeout: 5s
|
|
266
|
-
retries: 6
|
|
267
|
-
# STORY 4 FIX: 30s grace period to prevent 504 errors during startup
|
|
268
|
-
# Services get 30s to initialize before health checks begin
|
|
269
|
-
start_period: 30s
|
|
270
|
-
""".format(internal_port=internal_port, health_path=health_path)
|
|
262
|
+
""".format(internal_port=internal_port, health_path=health_path) + compose_healthcheck_timing()
|
|
271
263
|
infisical_env = get_infisical_environment_vars(as_array=True, resource_name=res_name)
|
|
272
264
|
|
|
273
265
|
# Build auth environment variables section using centralized auth utilities
|
|
@@ -275,6 +267,7 @@ def _generate_single_backend_entry(resource_path, resource_name, res, manifest,
|
|
|
275
267
|
return """
|
|
276
268
|
{resource_entry_name}:
|
|
277
269
|
image: {image_name}
|
|
270
|
+
init: true
|
|
278
271
|
restart: unless-stopped
|
|
279
272
|
{build_config}
|
|
280
273
|
{ports_section}
|
|
@@ -619,7 +612,7 @@ services:
|
|
|
619
612
|
{migrator_name}:
|
|
620
613
|
image: {image_name}
|
|
621
614
|
build:
|
|
622
|
-
context:
|
|
615
|
+
context: ../../../.
|
|
623
616
|
dockerfile: {dockerfile_ref}
|
|
624
617
|
target: migrator
|
|
625
618
|
environment:
|
|
@@ -40,7 +40,7 @@ def compose_build_config(resource_path, res_name, dockerfile=OUTPUT.AUTOGENERATE
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
base_config = """ build:
|
|
43
|
-
context:
|
|
43
|
+
context: ../../../.
|
|
44
44
|
dockerfile: {full_dockerfile_path}""".format(
|
|
45
45
|
full_dockerfile_path=full_dockerfile_path
|
|
46
46
|
)
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
# skip Traefik entirely and api.{project}.localhost never served.
|
|
7
7
|
|
|
8
8
|
load("../constants.star", "PlatformDockerConstants")
|
|
9
|
+
load("../config/healthcheck.star", "compose_healthcheck_timing")
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def generate_standalone_traefik_compose():
|
|
@@ -24,6 +25,7 @@ services:
|
|
|
24
25
|
restart: unless-stopped
|
|
25
26
|
depends_on:
|
|
26
27
|
- sablier
|
|
28
|
+
- wake-gateway
|
|
27
29
|
command:
|
|
28
30
|
- "--api.insecure=true"
|
|
29
31
|
- "--ping=true"
|
|
@@ -31,6 +33,13 @@ services:
|
|
|
31
33
|
- "--providers.docker.exposedbydefault=false"
|
|
32
34
|
- "--providers.docker.watch=true"
|
|
33
35
|
- "--providers.docker.network={network}"
|
|
36
|
+
# Static routes for sablier.deferStart resources (openspec/changes/
|
|
37
|
+
# prioritized-cold-start): a never-created resource has no container
|
|
38
|
+
# and thus no Docker labels for the provider above to discover, so its
|
|
39
|
+
# route is defined here instead, pointed at wake-gateway. Harmless when
|
|
40
|
+
# no deferStart resource exists (empty directory, no routes emitted).
|
|
41
|
+
- "--providers.file.directory=/etc/traefik/dynamic"
|
|
42
|
+
- "--providers.file.watch=true"
|
|
34
43
|
- "--entrypoints.web.address=:80"
|
|
35
44
|
- "--entrypoints.websecure.address=:443"
|
|
36
45
|
- "--log.level=INFO"
|
|
@@ -44,15 +53,12 @@ services:
|
|
|
44
53
|
- "8080:8080"
|
|
45
54
|
volumes:
|
|
46
55
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
|
56
|
+
- ./{dynamic_dir_name}:/etc/traefik/dynamic:ro
|
|
47
57
|
networks:
|
|
48
58
|
- traefik-public
|
|
49
59
|
healthcheck:
|
|
50
60
|
test: ["CMD", "traefik", "healthcheck", "--ping"]
|
|
51
|
-
|
|
52
|
-
timeout: 5s
|
|
53
|
-
retries: 6
|
|
54
|
-
start_period: 10s
|
|
55
|
-
|
|
61
|
+
{healthcheck_timing}
|
|
56
62
|
sablier:
|
|
57
63
|
image: sablierapp/sablier:1.18.0
|
|
58
64
|
container_name: {name}_sablier
|
|
@@ -66,8 +72,40 @@ services:
|
|
|
66
72
|
networks:
|
|
67
73
|
- traefik-public
|
|
68
74
|
|
|
75
|
+
# Wake gateway (openspec/changes/prioritized-cold-start): fronts every
|
|
76
|
+
# deferStart resource's static route above. Triggers a cold resource (and
|
|
77
|
+
# its dependencies, which Tilt never cascades to on its own -- see design.md
|
|
78
|
+
# D3/task 1.5) via `tilt trigger`, or Sablier's own start when a container
|
|
79
|
+
# already exists, then holds the request until healthy. Needs the same
|
|
80
|
+
# Tilt session token Sablier/Traefik don't: confirmed empirically that
|
|
81
|
+
# `tilt trigger` from inside a container is otherwise rejected with
|
|
82
|
+
# "(403): invalid session token" even when the Tilt HTTP port is reachable.
|
|
83
|
+
wake-gateway:
|
|
84
|
+
build: ./{vendored_gateway_dir}
|
|
85
|
+
container_name: {gateway_container}
|
|
86
|
+
restart: unless-stopped
|
|
87
|
+
environment:
|
|
88
|
+
- TILT_HOST=host.docker.internal
|
|
89
|
+
- TILT_PORT=10350
|
|
90
|
+
- SABLIER_URL=http://sablier:10000
|
|
91
|
+
- PORT={gateway_port}
|
|
92
|
+
volumes:
|
|
93
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
94
|
+
- {tilt_dev_dir}:/root/.tilt-dev:ro
|
|
95
|
+
networks:
|
|
96
|
+
- traefik-public
|
|
97
|
+
|
|
69
98
|
networks:
|
|
70
99
|
traefik-public:
|
|
71
100
|
name: {network}
|
|
72
101
|
external: true
|
|
73
|
-
""".format(
|
|
102
|
+
""".format(
|
|
103
|
+
name=name,
|
|
104
|
+
network=network,
|
|
105
|
+
healthcheck_timing=compose_healthcheck_timing(10),
|
|
106
|
+
dynamic_dir_name=PlatformDockerConstants.TRAEFIK_DYNAMIC_DIR_REL.split("/")[-1],
|
|
107
|
+
vendored_gateway_dir="tdk-cli-ext/engine/assets/docker/wake-gateway",
|
|
108
|
+
gateway_container=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_CONTAINER,
|
|
109
|
+
gateway_port=PlatformDockerConstants.TRAEFIK_WAKE_GATEWAY_PORT,
|
|
110
|
+
tilt_dev_dir=os.environ.get("HOME", "") + "/.tilt-dev",
|
|
111
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# =============================================================================
|
|
2
|
+
# Docker healthcheck timing - single source for golden images, L4 runtimes and
|
|
3
|
+
# generated compose files.
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# Steady-state probes are infrequent (each probe is a `docker exec`, which adds
|
|
6
|
+
# up across 100+ containers), while `start_interval` keeps first-healthy time
|
|
7
|
+
# fast during the start period. `start_interval` needs Docker Engine 25+ and
|
|
8
|
+
# Compose 2.20.2+ (checked by `tdk doctor`).
|
|
9
|
+
#
|
|
10
|
+
# Override per run with environment variables, e.g.:
|
|
11
|
+
# TDK_HEALTHCHECK_INTERVAL_SECONDS=15 tdk up
|
|
12
|
+
# =============================================================================
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _env_int(name, default):
|
|
16
|
+
raw = os.environ.get(name, '')
|
|
17
|
+
if raw and raw.isdigit() and int(raw) > 0:
|
|
18
|
+
return int(raw)
|
|
19
|
+
return default
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
DOCKER_HEALTHCHECK = {
|
|
23
|
+
"path": "/health",
|
|
24
|
+
"interval_seconds": _env_int("TDK_HEALTHCHECK_INTERVAL_SECONDS", 30),
|
|
25
|
+
"start_interval_seconds": _env_int("TDK_HEALTHCHECK_START_INTERVAL_SECONDS", 2),
|
|
26
|
+
"timeout_seconds": _env_int("TDK_HEALTHCHECK_TIMEOUT_SECONDS", 5),
|
|
27
|
+
"start_period_seconds": _env_int("TDK_HEALTHCHECK_START_PERIOD_SECONDS", 30),
|
|
28
|
+
"frontend_start_period_seconds": _env_int("TDK_HEALTHCHECK_START_PERIOD_SECONDS", 30),
|
|
29
|
+
"retries": _env_int("TDK_HEALTHCHECK_RETRIES", 3),
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def dockerfile_healthcheck_flags(start_period_seconds = None):
|
|
34
|
+
"""Flags for a Dockerfile `HEALTHCHECK` instruction."""
|
|
35
|
+
h = DOCKER_HEALTHCHECK
|
|
36
|
+
start_period = start_period_seconds if start_period_seconds != None else h["start_period_seconds"]
|
|
37
|
+
return "--interval={}s --start-interval={}s --timeout={}s --start-period={}s --retries={}".format(
|
|
38
|
+
h["interval_seconds"],
|
|
39
|
+
h["start_interval_seconds"],
|
|
40
|
+
h["timeout_seconds"],
|
|
41
|
+
start_period,
|
|
42
|
+
h["retries"],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def compose_healthcheck_timing(start_period_seconds = None, indent = " "):
|
|
47
|
+
"""Timing keys for a compose `healthcheck:` block (everything except `test:`)."""
|
|
48
|
+
h = DOCKER_HEALTHCHECK
|
|
49
|
+
start_period = start_period_seconds if start_period_seconds != None else h["start_period_seconds"]
|
|
50
|
+
return (
|
|
51
|
+
indent + "interval: {}s\n".format(h["interval_seconds"])
|
|
52
|
+
+ indent + "timeout: {}s\n".format(h["timeout_seconds"])
|
|
53
|
+
+ indent + "retries: {}\n".format(h["retries"])
|
|
54
|
+
+ indent + "start_period: {}s\n".format(start_period)
|
|
55
|
+
+ indent + "start_interval: {}s\n".format(h["start_interval_seconds"])
|
|
56
|
+
)
|
|
@@ -62,6 +62,13 @@ NETWORK_DATABASE = PROJECT_NAME + "_database"
|
|
|
62
62
|
NETWORK_INFISICAL = PROJECT_NAME + "_infisical-network"
|
|
63
63
|
NETWORK_PROXY = PROJECT_NAME + "_proxy"
|
|
64
64
|
|
|
65
|
+
# Traefik file-provider dynamic config: one generated YAML per `sablier.deferStart`
|
|
66
|
+
# resource's static route (see openspec/changes/prioritized-cold-start), mounted
|
|
67
|
+
# read-only into the standalone Traefik container at /etc/traefik/dynamic.
|
|
68
|
+
TRAEFIK_DYNAMIC_DIR_REL = ".tdk/.tdk-out/traefik-dynamic"
|
|
69
|
+
TRAEFIK_WAKE_GATEWAY_CONTAINER = PROJECT_NAME_HYPHEN + "-wake-gateway"
|
|
70
|
+
TRAEFIK_WAKE_GATEWAY_PORT = 9999
|
|
71
|
+
|
|
65
72
|
# NATS constants
|
|
66
73
|
NATS_HOST = PROJECT_NAME + "_nats"
|
|
67
74
|
NATS_PORT = "4222"
|
|
@@ -146,6 +153,9 @@ PlatformDockerConstants = struct(
|
|
|
146
153
|
NETWORK_DATABASE = NETWORK_DATABASE,
|
|
147
154
|
NETWORK_INFISICAL = NETWORK_INFISICAL,
|
|
148
155
|
NETWORK_PROXY = NETWORK_PROXY,
|
|
156
|
+
TRAEFIK_DYNAMIC_DIR_REL = TRAEFIK_DYNAMIC_DIR_REL,
|
|
157
|
+
TRAEFIK_WAKE_GATEWAY_CONTAINER = TRAEFIK_WAKE_GATEWAY_CONTAINER,
|
|
158
|
+
TRAEFIK_WAKE_GATEWAY_PORT = TRAEFIK_WAKE_GATEWAY_PORT,
|
|
149
159
|
NATS_HOST = NATS_HOST,
|
|
150
160
|
NATS_PORT = NATS_PORT,
|
|
151
161
|
NATS_URL = NATS_URL,
|
|
@@ -61,7 +61,7 @@ def L1_generate_os_base(base_image = None, maintainer = None, use_golden = True)
|
|
|
61
61
|
"# ---- L1: os_base (" + base_image + ") ----\n"
|
|
62
62
|
+ "FROM " + base_image + " AS l1_os_base\n"
|
|
63
63
|
+ "LABEL maintainer=\"" + maintainer + "\"\n"
|
|
64
|
-
+ "RUN apk add --no-cache ca-certificates tzdata curl
|
|
64
|
+
+ "RUN apk add --no-cache ca-certificates tzdata curl bash openssl \\\n"
|
|
65
65
|
+ " && addgroup -S app && adduser -S app -G app\n"
|
|
66
66
|
+ "WORKDIR /app\n"
|
|
67
67
|
)
|