diff --git a/nullplatform/agent/README.md b/nullplatform/agent/README.md
index 5c4845db..ed903fe7 100644
--- a/nullplatform/agent/README.md
+++ b/nullplatform/agent/README.md
@@ -169,7 +169,7 @@ resource "example_resource" "this" {
| [service\_template](#input\_service\_template) | Path, inside the worker image, of the Service template the k8s scope renders. Empty (default) uses the template worker\_ingress selects; set it only to point at a custom template. | `string` | `""` | no |
| [tags\_selectors](#input\_tags\_selectors) | Map of tags used to select and filter channels and agents | `map(string)` | n/a | yes |
| [use\_account\_slug](#input\_use\_account\_slug) | Flag to determine whether to use the account slug in resource naming | `string` | `""` | no |
-| [worker](#input\_worker) | Extra worker-orchestration config, merged on top of the module's own computed
worker block: backend ("kubernetes" by default), allowedRegistries
(["public.ecr.aws/nullplatform/*"] by default, so the platform's own scope
images keep working), and a patch for the worker container (2Gi memory
limit, the deploy/DNS env vars below, and a serviceAccountName that always
mirrors service\_account\_name). allowedRegistries and patches set here are
concatenated with (not replacing) the module defaults — add your own
registries or an extra patch rather than having to repeat the defaults;
set backend here to override it outright. Anything else — security, idleTTL
(reap idle workers), the legacy defaults/rules/pins — passes through as-is.
See the nullplatform-agent chart values (>= 2.37.0) for the full shape.
null = nothing extra.
Example:
worker = {
allowedRegistries = ["123456789012.dkr.ecr.us-east-1.amazonaws.com/your-org/*"]
patches = [{ target = { package = "my-pkg" }, merge = { spec = { serviceAccountName = "np-agent-sa" } } }]
idleTTL = "30m"
} | `any` | `null` | no |
+| [worker](#input\_worker) | Extra worker-orchestration config, merged on top of the module's own computed
worker block: backend ("kubernetes" by default), allowedRegistries
(["public.ecr.aws/nullplatform/*"] by default, so the platform's own scope
images keep working), idleTTL ("30m" by default, so worker Deployments
left behind by an old/removed package revision get reaped instead of
accumulating forever), and a patch for the worker container (2Gi memory
limit, the deploy/DNS env vars below, and a serviceAccountName that always
mirrors service\_account\_name). allowedRegistries and patches set here are
concatenated with (not replacing) the module defaults — add your own
registries or an extra patch rather than having to repeat the defaults;
set backend or idleTTL here to override them outright (e.g. idleTTL = ""
to disable the reaper, matching this module's pre-idleTTL-default
behavior). Anything else — security, the legacy defaults/rules/pins —
passes through as-is. See the nullplatform-agent chart values (>= 2.37.0)
for the full shape. null = nothing extra beyond the defaults above.
Example:
worker = {
allowedRegistries = ["123456789012.dkr.ecr.us-east-1.amazonaws.com/your-org/*"]
patches = [{ target = { package = "my-pkg" }, merge = { spec = { serviceAccountName = "np-agent-sa" } } }]
idleTTL = "1h"
} | `any` | `null` | no |
| [worker\_ingress](#input\_worker\_ingress) | Ingress stack the containers worker deploys scopes with: "alb" keeps the k8s scope's own templates (AWS Load Balancer Controller Ingress), "istio" points it at the Gateway API templates baked in the scopes/containers image. service\_template, initial\_ingress\_path and blue\_green\_ingress\_path override the derived paths when set. | `string` | `"alb"` | no |
| [worker\_k8s\_packages](#input\_worker\_k8s\_packages) | Package slugs whose worker runs the k8s scope and must receive its env vars (DNS\_TYPE, K8S\_NAMESPACE, template paths, CLUSTER\_NAME, extra\_envs). Add every package that runs the k8s scope code, e.g. ["containers", "scheduled-task"]. | `list(string)` |
[| no | | [worker\_memory\_limit](#input\_worker\_memory\_limit) | Memory limit for a worker-orchestrated package's pod (packages in var.worker\_orchestrated\_packages). The chart's own default is small enough to OOM mid-tofu-apply for packages that run real IaC tooling. | `string` | `"2Gi"` | no | diff --git a/nullplatform/agent/locals.tf b/nullplatform/agent/locals.tf index ccd82ba0..cc89297e 100644 --- a/nullplatform/agent/locals.tf +++ b/nullplatform/agent/locals.tf @@ -172,6 +172,13 @@ locals { backend = "kubernetes" allowedRegistries = ["public.ecr.aws/nullplatform/*"] patches = concat(local.worker_common_patches, local.worker_k8s_env_patches) + # Reap worker-orchestrated pods (and their Deployments) after 30m with no + # activity. Previously unset (NP_WORKER_IDLE_TTL empty), which disables + # the reaper entirely — stale workers from old package revisions or + # removed packages accumulate forever instead of being cleaned up. + # Override per-install via var.worker.idleTTL (see its docs for the + # shape) if a longer/shorter window is needed. + idleTTL = "30m" } worker_final = merge( diff --git a/nullplatform/agent/tests/agent_values.tftest.hcl b/nullplatform/agent/tests/agent_values.tftest.hcl index fe19d350..56800a43 100644 --- a/nullplatform/agent/tests/agent_values.tftest.hcl +++ b/nullplatform/agent/tests/agent_values.tftest.hcl @@ -234,6 +234,32 @@ run "worker_defaults" { condition = strcontains(helm_release.agent.values[0], "\"serviceAccountName\": \"nullplatform-agent\"") error_message = "the worker's serviceAccountName must default to service_account_name's default (nullplatform-agent)" } + + assert { + condition = strcontains(helm_release.agent.values[0], "\"idleTTL\": \"30m\"") + error_message = "idleTTL must default to 30m so worker Deployments from an old/removed package revision get reaped instead of accumulating forever" + } +} + +# idleTTL is a plain override (like backend), not additive (like +# allowedRegistries/patches) — var.worker's value must win outright, +# including the empty string, which disables the reaper. +run "worker_idle_ttl_override_wins_outright" { + command = plan + + variables { + worker = { + idleTTL = "" + } + } + + assert { + condition = ( + strcontains(helm_release.agent.values[0], "\"idleTTL\": \"\"") && + !strcontains(helm_release.agent.values[0], "\"idleTTL\": \"30m\"") + ) + error_message = "var.worker.idleTTL must override the module default outright, including disabling it with an empty string" + } } # var.worker stays available as an extra/override layer on top of the computed diff --git a/nullplatform/agent/variables.tf b/nullplatform/agent/variables.tf index 5aa95410..cbbf7a04 100644 --- a/nullplatform/agent/variables.tf +++ b/nullplatform/agent/variables.tf @@ -140,21 +140,24 @@ variable "worker" { Extra worker-orchestration config, merged on top of the module's own computed worker block: backend ("kubernetes" by default), allowedRegistries (["public.ecr.aws/nullplatform/*"] by default, so the platform's own scope - images keep working), and a patch for the worker container (2Gi memory + images keep working), idleTTL ("30m" by default, so worker Deployments + left behind by an old/removed package revision get reaped instead of + accumulating forever), and a patch for the worker container (2Gi memory limit, the deploy/DNS env vars below, and a serviceAccountName that always mirrors service_account_name). allowedRegistries and patches set here are concatenated with (not replacing) the module defaults — add your own registries or an extra patch rather than having to repeat the defaults; - set backend here to override it outright. Anything else — security, idleTTL - (reap idle workers), the legacy defaults/rules/pins — passes through as-is. - See the nullplatform-agent chart values (>= 2.37.0) for the full shape. - null = nothing extra. + set backend or idleTTL here to override them outright (e.g. idleTTL = "" + to disable the reaper, matching this module's pre-idleTTL-default + behavior). Anything else — security, the legacy defaults/rules/pins — + passes through as-is. See the nullplatform-agent chart values (>= 2.37.0) + for the full shape. null = nothing extra beyond the defaults above. Example: worker = { allowedRegistries = ["123456789012.dkr.ecr.us-east-1.amazonaws.com/your-org/*"] patches = [{ target = { package = "my-pkg" }, merge = { spec = { serviceAccountName = "np-agent-sa" } } }] - idleTTL = "30m" + idleTTL = "1h" } EOT type = any
"containers"
]