Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
169 commits
Select commit Hold shift + click to select a range
025de7e
feat: opt-in ALB autocreation when pool capacity is exhausted
fedemaleh May 27, 2026
59821c3
fix: harden autocreate input validation and merge AWS polling call
fedemaleh May 27, 2026
56152e5
refactor: address PR review feedback on autocreate flow
fedemaleh Jun 8, 2026
b0b1cc1
docs: clarify get_alb_rule_count stdout contract in race-case branch
fedemaleh Jun 8, 2026
f5026d2
refactor: reuse $DOMAIN from build_context instead of recomputing
fedemaleh Jun 9, 2026
81b33a3
Merge remote-tracking branch 'origin/beta' into feature/clien-807-aut…
fedemaleh Jun 10, 2026
940276f
test: assert full log messages with emojis for autocreate networking …
fedemaleh Jun 10, 2026
33da501
feat: suggest enabling ALB_AUTOCREATE_ENABLED in capacity error hints
fedemaleh Jun 10, 2026
7f9435f
fix(autocreate): trigger on single-ALB setups and emit step-by-step d…
fedemaleh Jun 10, 2026
dff4e05
fix: render gomplate context as .json and emit heartbeat every 30s wh…
fedemaleh Jun 10, 2026
8a4c12f
fix: generate dummy ingress host via domain-generate so it matches th…
fedemaleh Jun 10, 2026
a9dbc6c
fix: stable 30s heartbeat cadence and trap-safe context cleanup under…
fedemaleh Jun 10, 2026
257a7bc
fix: gate wait_for_alb on route53, validate inputs, dedupe AWS call, …
fedemaleh Jun 10, 2026
a078c51
docs(changelog): shorten autocreate entry to one-liner client summary
fedemaleh Jun 10, 2026
47c98c2
feat(wait_for_alb): name the missing IAM permission in tag-failure warn
fedemaleh Jun 16, 2026
056f3f2
Merge pull request #187 from nullplatform/feature/clien-807-autocreat…
fedemaleh Jun 16, 2026
6ab8156
hotfix: change iteration
ignacioboud Feb 13, 2026
2353a52
fix(CLIEN-688): normalize aarch64 arch name for Linux ARM64 support
Mar 18, 2026
41ad813
build(CLIEN-688): add linux/arm64 binary and rebuild linux binaries
Mar 18, 2026
a47e4a3
feat: add scope configuration provider
ignacioboud Jan 15, 2026
f9f691d
fix: remove region and set provider as first choise
ignacioboud Jan 15, 2026
42b0c36
fix: add debug log
ignacioboud Jan 15, 2026
21c957e
fix: change to scope-configurations
ignacioboud Jan 15, 2026
2b02c15
chore: update readme
ignacioboud Jan 16, 2026
9861c22
feat: add azure vars
ignacioboud Jan 16, 2026
4c2b767
fix: remove logs
ignacioboud Jan 19, 2026
29fe275
fix: object_modifiers
ignacioboud Jan 19, 2026
d6143d6
fix: modifiers
ignacioboud Jan 19, 2026
f534940
feat: add two envs values
ignacioboud Jan 19, 2026
4ac1a06
Update scope definitions for azure-aro, azure, k8s and scheduled_task
fedemaleh Jan 23, 2026
d60ae20
Update selectors.provider to Agent
fedemaleh Jan 23, 2026
1c18fd8
Add testing framework infrastructure
fedemaleh Jan 26, 2026
1fbcfe1
Create certs for each test execution
fedemaleh Jan 26, 2026
d398117
chore: update change log
ignacioboud Jan 26, 2026
f32f6d5
fix: CPU
francocirulli95 Jan 30, 2026
448f8db
Add logging format and tests for k8s/backup module
fedemaleh Feb 4, 2026
e1232aa
Add logging format and tests for k8s/deployment module
fedemaleh Feb 6, 2026
45bf06c
Review changes
fedemaleh Feb 6, 2026
6c9700f
fix missing default
fedemaleh Feb 9, 2026
d34a0e2
Validate that period seconds is greater than timeout seconds
fedemaleh Feb 11, 2026
4a695a2
fix: remove make
ignacioboud Jan 26, 2026
b5e333f
Add logging format and tests for k8s/diagnose module
fedemaleh Feb 15, 2026
37d852f
Add logging format and tests for k8s/scope module
fedemaleh Mar 4, 2026
77ae8f3
Remove unnecessary set -eou pipefail
fedemaleh Mar 4, 2026
33b719b
Use logging level
fedemaleh Mar 5, 2026
2060955
Add logging logic to apply templates
fedemaleh Mar 5, 2026
fafdcc0
Add logging logic to template backups
fedemaleh Mar 5, 2026
e05597e
Improve create dns logging
fedemaleh Mar 5, 2026
c6ee0a0
feat: remove comment "normalize aarch64 to arm64 in log script"
violenti Mar 24, 2026
a498a3f
chore: add PR checks (BATS, ShellCheck, Go)
null-paorodrigues Mar 20, 2026
eeb6eb6
fix: add shellcheck --severity=error and shebang to assertions.sh
null-paorodrigues Mar 25, 2026
94ca16f
chore: use make test-unit in BATS step
null-paorodrigues Mar 25, 2026
5bd0a8b
chore: rename bats job to unit-tests
null-paorodrigues Mar 25, 2026
8620956
chore: migrate shellcheck to reusable workflow
null-paorodrigues Mar 26, 2026
817b99b
ci: re-trigger PR checks
null-paorodrigues Mar 27, 2026
b561aec
Add logging format and tests for k8s/deployment module
fedemaleh Mar 5, 2026
c62f4ff
Update values.yaml
fedemaleh Mar 11, 2026
8e27974
Improvements
fedemaleh Mar 12, 2026
3188133
feat: validate ALB capacity on scope creation
fedemaleh Apr 8, 2026
8f2b67f
docs: add ALB capacity validation to changelog with required AWS perm…
fedemaleh Apr 8, 2026
5109535
feat: validate ALB target group capacity on deployment
fedemaleh Apr 9, 2026
6291072
fix: update ALB capacity error message to reference c…
fedemaleh Apr 9, 2026
2793101
Add support for multiple ALBs
fedemaleh Apr 8, 2026
1e786d8
Simplify resolve_balancer and add container-orchestration provider
fedemaleh Apr 9, 2026
e388fd0
Fix changelog
fedemaleh Apr 9, 2026
2746951
Fix Route53 lookup for large zones
fedemaleh Apr 9, 2026
d5939df
Configure traffic-manager resources through config map
fedemaleh Apr 9, 2026
c2c8d2c
feat: ALB metrics publishing to CloudWatch and Datadog (#166)
pilundain Apr 10, 2026
f40039a
Update CHANGELOG.md
fedemaleh Apr 10, 2026
d4b4244
Skip alb validations when cloud is not alb
fedemaleh Apr 10, 2026
70aecb7
Skip alb validations when cloud is not alb
fedemaleh Apr 10, 2026
1e07bdd
Remove pipefail
fedemaleh Apr 11, 2026
c17dfc1
Fix blue-green switch-traffic failure when additional_ports added pos…
pilundain Apr 15, 2026
260a114
Add support for custom cron in scheduled_task job schema
fedemaleh Apr 15, 2026
f020dae
Release 1.11.0
fedemaleh Apr 16, 2026
ca86b13
Skip ALB capacity validations in scheduled_task workflows
javi-null Apr 17, 2026
b6cad79
Add --no-params flag to skip parameter fetching for actions that don'…
bjornaer Apr 16, 2026
f2110df
Add kubectl helper command
geisbruch Apr 16, 2026
556638a
Add kubectl logs
geisbruch Apr 16, 2026
bec1ca4
fix: support ELB hostnames for external-dns DNSEndpoint on AWS
Apr 30, 2026
2b1b9a8
fix(deployment): create DNSEndpoint for external_dns DNS type
May 4, 2026
4727a89
fix(wait_on_balancer): use DNSEndpoint observedGeneration instead of …
May 4, 2026
8983a1a
fix(manage_route): resolve ALB hostname via Ingress when gateway retu…
May 4, 2026
34fe66f
refactor(manage_route): try ALB Ingress first, fall back to gateway a…
May 4, 2026
e1985a3
revert(wait_on_balancer): restore nslookup check for external_dns to …
May 4, 2026
b8854c2
fix(wait_on_balancer): fix nslookup IP parsing to skip server address…
May 4, 2026
4507e93
fix(dns): add dns/zone-type label to DNSEndpoints to prevent cross-zo…
May 4, 2026
97f4198
fix(wait_on_balancer): use observedGeneration for private scopes inst…
May 4, 2026
8d067bc
refactor(wait_on_balancer): use observedGeneration for all external_d…
May 5, 2026
7545824
fix(dns): address PR review - remove DNS management from deployment v…
May 6, 2026
f45bb43
chore(changelog): add 1.12.0 entry for external-dns DNSEndpoint support
May 8, 2026
ab1dfc6
chore(changelog): condense 1.11.1 entry into single functional descri…
May 11, 2026
15df51e
chore(changelog): reframe 1.11.1 entry around user benefit
May 11, 2026
de96c6f
feat(k8s): configurable main_http_port and HTTP support for additiona…
fedemaleh May 5, 2026
675a36f
Additional port default is http
fedemaleh May 6, 2026
6ad5cf2
fix(k8s/templates): use root context for k8s_modifiers in HTTP additi…
fedemaleh May 6, 2026
feeb85a
fix(k8s/templates): remove HTTP sidecar; HTTP additional ports are ap…
fedemaleh May 6, 2026
e7a27b0
fix(k8s/templates): restore HTTP additional-port sidecar with UPSTREA…
fedemaleh May 6, 2026
28bbaf4
fix(k8s/templates): HTTP sidecar binds port+10000 so app can bind por…
fedemaleh May 8, 2026
257b73a
fix(k8s/verify_ingress): dedupe weights for multi-ingress listeners +…
fedemaleh May 8, 2026
0798a37
feat(k8s/templates): HTTP additional ports listen on dedicated HTTPS …
fedemaleh May 8, 2026
9384b41
Fix tests
fedemaleh May 8, 2026
1fe0a22
docs: document ALB listener lifecycle and 50-listener capacity limit
fedemaleh May 11, 2026
8aa7b2a
Review changes
fedemaleh May 11, 2026
e4928ed
Surface user-friendly failure reasons on deployment timeout (#184)
ignacioboud May 20, 2026
6d5d482
docs: add design spec for CLIEN-781 memory & CPU limits
fedemaleh May 21, 2026
2a07585
docs: add implementation plan for CLIEN-781 memory & CPU limits
fedemaleh May 21, 2026
40e4b84
feat: add cpu_millicores_limit and ram_memory_limit properties to k8s…
fedemaleh May 21, 2026
40ecfec
feat: rename Processor tab to Resources and surface CPU/RAM limit con…
fedemaleh May 21, 2026
96de4a5
feat: normalize cpu/ram limit capabilities to request value when unset
fedemaleh May 21, 2026
f8b7249
feat: render application container limits from normalized capability …
fedemaleh May 21, 2026
3ea944f
refactor: tighten normalize_capability_limits jq + bats here-string i…
fedemaleh May 21, 2026
9aba0a8
fix: mark cpu_millicores_limit and ram_memory_limit as required for U…
fedemaleh May 21, 2026
704f2b5
refactor: make cpu_millicores_limit a dropdown with 'Same as request'…
fedemaleh May 21, 2026
ac9240d
docs: align ram_memory_limit description with cpu_millicores_limit ph…
fedemaleh May 22, 2026
310f68e
chore: move design spec and plan to .claude (untracked working notes)
fedemaleh May 22, 2026
4303bc3
docs: add changelog entry for configurable CPU and memory limits
fedemaleh May 22, 2026
6fc6f44
Structured evidence + log embedding in k8s/diagnose (#181)
ignacioboud May 22, 2026
9ede1a0
refactor: drop ticket id and noise from normalize_capability_limits c…
fedemaleh May 22, 2026
d32248f
test: exercise normalize via full build_context instead of private fu…
fedemaleh May 22, 2026
08d7595
test: remove deployment template shape tests in favor of integration …
fedemaleh May 22, 2026
cd686fa
feat: clamp limit to request when below it as defense-in-depth
fedemaleh May 22, 2026
5d9f6c7
fix(k8s,scheduled_task): file-type parameter no longer leaks binary a…
fedemaleh May 27, 2026
34b17f7
docs(changelog): tighten file-parameter fix entry
fedemaleh May 27, 2026
b21bfba
fix(k8s,scheduled_task): isolate file binary in a dedicated Secret
fedemaleh May 27, 2026
2a50853
refactor(k8s,scheduled_task): derive file-param identifiers from .name
fedemaleh May 27, 2026
cd111a9
fix(k8s,scheduled_task): omit env: block when no file params
fedemaleh May 27, 2026
61410b8
Revert "fix(k8s,scheduled_task): omit env: block when no file params"
fedemaleh May 27, 2026
f7ca9fb
fix(k8s,scheduled_task): quote destination_path in YAML to escape flo…
fedemaleh May 27, 2026
4c0fd06
test(scheduled_task): add build_deployment render test for file params
fedemaleh May 27, 2026
230f082
Update CHANGELOG.md
fedemaleh Jun 8, 2026
cde1bc8
chore: gitignore local superpowers implementation plans
Jun 16, 2026
cdcde26
feat(k8s/assume-role): pure ARN resolver library with tests
Jun 16, 2026
ed75d3c
test(k8s/assume-role): direct tests for inner resolvers + teardown
Jun 16, 2026
167ec4c
feat(k8s/assume-role): sts:AssumeRole helper exporting temp creds
Jun 16, 2026
1de9ee6
fix(k8s/assume-role): fail on incomplete STS credentials
Jun 16, 2026
1fff70f
feat(k8s/assume-role): workflow step resolving and assuming the role
Jun 16, 2026
c099681
test(k8s/assume-role): strengthen step no-op + override coverage
Jun 16, 2026
a513df6
feat(k8s/assume-role): run assume-role step first in AWS workflows
Jun 16, 2026
f4051db
docs(k8s/assume-role): document role assumption and changelog
Jun 16, 2026
a903c3f
fix(k8s/assume-role): wire switch_traffic workflow + first-match test
Jun 16, 2026
f4dbbce
chore: stop tracking superpowers specs (local-only)
Jun 16, 2026
92e212f
feat(k8s/assume-role): resolve IAM role filtered by scope dimensions
Jun 17, 2026
c769c39
refactor(k8s/assume-role): resolve IAM role from dimension-resolved C…
Jun 17, 2026
918e249
refactor(k8s/assume-role): address review feedback
Jun 19, 2026
a5eeaee
fix: keep unit tests green after beta rebase
Jun 23, 2026
9330f82
Merge remote-tracking branch 'origin/beta' into feature/CLIET-842
Jun 23, 2026
6d53423
Merge pull request #191 from nullplatform/feature/CLIET-842
fedemaleh Jun 23, 2026
085c21e
fix(k8s): repair failing unit tests and guard assume_role_lib jq exit
fedemaleh Jun 23, 2026
7afd03b
feat(k8s/tofu): add IAM agent-permissions OpenTofu module
Jun 23, 2026
c129ebd
refactor(k8s/tofu): remove AVP policy from agent-permissions module
Jun 24, 2026
d93c510
refactor(k8s/tofu): move agent-permissions module to k8s/specs/tofu
Jun 24, 2026
bde6d91
Update locals.tf
davidf-null Jun 26, 2026
52e7a68
feat(k8s/tofu): allow additional roles in permissions trust policy
Jun 26, 2026
8e421b9
Merge pull request #193 from nullplatform/feat/opentofu
fedemaleh Jun 29, 2026
0733cab
refactor: move k8s IAM requirements to k8s/requirements/aws and renam…
agustincelentano Jul 1, 2026
860c1d8
fix(ci): run bats via portable script syntax and install gomplate
fedemaleh Jul 2, 2026
5ab0d3e
fix(diagnose): assert probe remediation in structured evidence not st…
fedemaleh Jul 2, 2026
d033be6
fix: write IAM inline policy temp file to OUTPUT_DIR to avoid concurr…
fedemaleh Jul 2, 2026
83063b8
Merge pull request #195 from nullplatform/fix/iam-role-concurrent-tmp…
fedemaleh Jul 2, 2026
17c907f
Merge pull request #192 from nullplatform/fix/beta-test-failures
fedemaleh Jul 3, 2026
e41f436
refactor: move requirements module under specs (k8s/specs/requirement…
agustincelentano Jul 3, 2026
e968028
Merge pull request #194 from nullplatform/feat/k8s-requirements-module
fedemaleh Jul 3, 2026
ebb3ec4
feat: add nodeSelector support for scheduled task scopes
fedemaleh Jul 7, 2026
d635728
Merge pull request #196 from nullplatform/feat/scheduled-task-nodesel…
fedemaleh Jul 9, 2026
0013fc7
Revert "Merge pull request #196 from nullplatform/feat/scheduled-task…
geisbruch Jul 9, 2026
2f4b4a5
Merge pull request #197 from nullplatform/hotfix/revert-pr-196
geisbruch Jul 9, 2026
36c2d69
feat: re-add scheduled task nodeSelector with correct indentation
fedemaleh Jul 10, 2026
3cdd965
Merge pull request #198 from nullplatform/feat/scheduled-task-nodesel…
geisbruch Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,12 @@ jobs:
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y bats jq

- name: Install gomplate
run: |
sudo curl -fsSL -o /usr/local/bin/gomplate \
https://github.com/hairyhenderson/gomplate/releases/download/v4.3.3/gomplate_linux-amd64
sudo chmod +x /usr/local/bin/gomplate
gomplate --version

- name: Run unit tests
run: make test-unit
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ np-agent-manifest.yaml

# Git worktrees
.worktrees/
DS_Store
.DS_Store
# Integration test runtime data
frontend/deployment/tests/integration/volume/

Expand All @@ -153,4 +153,7 @@ testing/docker/certs/
.claude/

# Visual Studio Code
.vscode/
.vscode/

# Superpowers specs and implementation plans (local-only)
docs/superpowers/
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
- Add support to get AWS credentials via assume role
- Add support to auto-create ALBs on scope create
- Fix race condition on IAM role creation when multiple scopes are created concurrently
- Add nodeSelector support for scheduled task scopes

## [1.12.0] - 2026-06-08
- Fix: do not inject file parameter as env vars
Expand Down
36 changes: 36 additions & 0 deletions k8s/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,42 @@ AWS IAM configuration for Kubernetes service accounts.
| **IAM_POLICIES** | List of IAM policies to attach to the role | `security.iam_policies` |
| **IAM_BOUNDARY_ARN** | ARN of the permissions boundary policy | `security.iam_boundary_arn` |

#### Assuming an IAM role for AWS operations

By default the scope's AWS CLI calls (IAM, ELBv2, Route53, S3, CloudWatch) use
the agent's own credentials. To run them under a dedicated IAM role per account,
configure the nullplatform **AWS IAM provider** (`aws-iam-configuration`) with an
`iam_role_arns.arns` entry whose `selector` is `containers`:

```hcl
attributes = {
iam_role_arns = {
arns = [
{ selector = "containers", arn = "arn:aws:iam::<account>:role/<role>" }
]
}
}
```

Resolution precedence (first non-empty wins):

1. `CONTAINERS_ASSUME_ROLE_ARN` environment variable (explicit override).
2. AWS IAM provider entry matching the selector (`CONTAINERS_ASSUME_ROLE_SELECTOR`, default `containers`).
3. `CONTAINERS_ASSUME_ROLE_ARN_DEFAULT` environment variable.
4. None configured → the agent's credentials are used (no role assumed).

The IAM provider is resolved **for the scope's dimensions** by the platform: it
is read from `CONTEXT.providers["identity-access-control"]`, which the engine
populates with the most-specific provider config whose `dimensions` are a subset
of the scope's dimensions (the empty-dimension config is the default). The
selector is then matched within that already-resolved config. For this to work,
`identity-access-control` must be listed under `provider_categories` in
`values.yaml`. This lets different dimensions map to different assumable roles
using the same matching the rest of the platform uses.

The target role's trust policy must allow the agent's role to call
`sts:AssumeRole`.

#### Vault

HashiCorp Vault configuration for secrets management.
Expand Down
2 changes: 1 addition & 1 deletion k8s/deployment/templates/deployment.yaml.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,4 @@ spec:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
progressDeadlineSeconds: 600
2 changes: 2 additions & 0 deletions k8s/deployment/tests/build_deployment.bats
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ _render_context() {
"capabilities": {
"cpu_millicores": 100,
"ram_memory": 128,
"cpu_millicores_limit": 200,
"ram_memory_limit": 256,
"additional_ports": [],
"scaling_type": "fixed",
"autoscaling": {
Expand Down
10 changes: 10 additions & 0 deletions k8s/deployment/workflows/delete.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ steps:
parameters:
level: string
message: string
- name: assume role
type: script
file: "$SERVICE_PATH/utils/assume_role_step"
output:
- name: AWS_ACCESS_KEY_ID
type: environment
- name: AWS_SECRET_ACCESS_KEY
type: environment
- name: AWS_SESSION_TOKEN
type: environment
- name: build context
type: script
file: "$SERVICE_PATH/deployment/build_context"
Expand Down
10 changes: 10 additions & 0 deletions k8s/deployment/workflows/finalize.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ steps:
parameters:
level: string
message: string
- name: assume role
type: script
file: "$SERVICE_PATH/utils/assume_role_step"
output:
- name: AWS_ACCESS_KEY_ID
type: environment
- name: AWS_SECRET_ACCESS_KEY
type: environment
- name: AWS_SESSION_TOKEN
type: environment
- name: build context
type: script
file: "$SERVICE_PATH/deployment/build_context"
Expand Down
10 changes: 10 additions & 0 deletions k8s/deployment/workflows/initial.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ steps:
parameters:
level: string
message: string
- name: assume role
type: script
file: "$SERVICE_PATH/utils/assume_role_step"
output:
- name: AWS_ACCESS_KEY_ID
type: environment
- name: AWS_SECRET_ACCESS_KEY
type: environment
- name: AWS_SESSION_TOKEN
type: environment
- name: build context
type: script
file: "$SERVICE_PATH/deployment/build_context"
Expand Down
10 changes: 10 additions & 0 deletions k8s/deployment/workflows/rollback.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ steps:
parameters:
level: string
message: string
- name: assume role
type: script
file: "$SERVICE_PATH/utils/assume_role_step"
output:
- name: AWS_ACCESS_KEY_ID
type: environment
- name: AWS_SECRET_ACCESS_KEY
type: environment
- name: AWS_SESSION_TOKEN
type: environment
- name: build context
type: script
file: "$SERVICE_PATH/deployment/build_context"
Expand Down
10 changes: 10 additions & 0 deletions k8s/deployment/workflows/switch_traffic.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ steps:
parameters:
level: string
message: string
- name: assume role
type: script
file: "$SERVICE_PATH/utils/assume_role_step"
output:
- name: AWS_ACCESS_KEY_ID
type: environment
- name: AWS_SECRET_ACCESS_KEY
type: environment
- name: AWS_SESSION_TOKEN
type: environment
- name: build context
type: script
file: "$SERVICE_PATH/deployment/build_context"
Expand Down
10 changes: 8 additions & 2 deletions k8s/diagnose/tests/scope/health_probe_endpoints.bats
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,10 @@ EOF
stripped=$(strip_ansi "$output")
assert_contains "$stripped" "Readiness Probe on HTTP://8080/health:"
assert_contains "$stripped" "HTTP 404 - Health check endpoint not found"
assert_contains "$stripped" "Update probe path or implement the endpoint in application"
# Remediation guidance lives in the structured evidence (suggested_actions),
# not in stdout - the check publishes it for the AI summarizer, not operators.
actions=$(jq -r '.evidence.suggested_actions[]' "$SCRIPT_OUTPUT_FILE")
assert_contains "$actions" "Update probe path or implement health endpoint in application"
}

@test "scope/health_probe_endpoints: updates status to failed on 404" {
Expand Down Expand Up @@ -261,7 +264,10 @@ EOF
stripped=$(strip_ansi "$output")
assert_contains "$stripped" "Readiness Probe on HTTP://8080/health:"
assert_contains "$stripped" "HTTP 500 - Application error"
assert_contains "$stripped" "Check application logs and fix internal errors or dependencies"
# Remediation guidance lives in the structured evidence (suggested_actions),
# not in stdout - the check publishes it for the AI summarizer, not operators.
actions=$(jq -r '.evidence.suggested_actions[]' "$SCRIPT_OUTPUT_FILE")
assert_contains "$actions" "Check application logs for internal errors"
}

@test "scope/health_probe_endpoints: updates status to warning on 500" {
Expand Down
76 changes: 76 additions & 0 deletions k8s/docs/autocreate-alb.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# ALB Autocreation

The k8s scope can provision new Application Load Balancers (ALBs) on demand when the declared pool of ALBs is exhausted. The behavior is opt-in and only triggers during scope creation; existing scopes are never moved to autocreated ALBs automatically.

## When the autocreate path runs

The flow only triggers when **all** of the following are true:

- `ALB_AUTOCREATE_ENABLED=true` in `values.yaml` or in the `container-orchestration` provider.
- `DNS_TYPE=route53` (autocreation requires the same load-balancing path used by Route53 scopes).
- Every candidate ALB in the pool (base + additional balancers declared in the provider) reports a rule count `>= ALB_MAX_CAPACITY`.
- The scope being created does not already have a Route53 record (a scope being recreated reuses its existing ALB and does not trigger autocreation).

If any candidate is below the threshold, the scope creation uses that candidate and the autocreate path is not taken.

## Configuration

| Key | Default | Description |
|---|---|---|
| `ALB_AUTOCREATE_ENABLED` | `false` | Master switch. When `false`, behavior is identical to previous releases. |
| `ALB_AUTOCREATE_NAME_PREFIX` | `nullplatform-auto-` | Prefix for autocreated ALB names. Final name format: `<prefix><public|private>-<6 hex chars>`. Must match `^[a-z0-9-]+$` and be ≤18 chars so the rendered name stays under the AWS 32-char ALB name limit. |
| `ALB_AUTOCREATE_TIMEOUT_SECONDS` | `300` | How long `wait_for_alb` polls AWS for the new ALB to reach `state=active` before failing the scope creation. The AWS Load Balancer Controller usually takes 2–4 minutes. Must be a positive integer. |

All three keys are also readable from `providers.container-orchestration.balancer.{autocreate_enabled, autocreate_name_prefix, autocreate_timeout_seconds}`.

## How it works

1. `resolve_balancer` evaluates the candidate pool — the base ALB plus the `additional_public_names` / `additional_private_names` list declared in the `container-orchestration` provider — and picks the least-loaded one.
2. If that candidate's rule count is at or above `ALB_MAX_CAPACITY` and `ALB_AUTOCREATE_ENABLED=true`, `resolve_balancer` sources `autocreate_alb`.
3. `autocreate_alb` generates a unique ALB name (`<prefix><public|private>-<6 hex>`) and **patches the container-orchestration provider via `np provider patch`** to append the new name to `additional_public_names` or `additional_private_names` (visibility-dependent). The provider is the authoritative registry of ALBs the platform uses.
4. `autocreate_alb` renders `scope/templates/ingress-dummy.yaml.tpl` into `$OUTPUT_DIR/ingress-dummy-<alb-name>.yaml`. The dummy Ingress carries `alb.ingress.kubernetes.io/group.name=<new-name>` and `alb.ingress.kubernetes.io/load-balancer-name=<new-name>`, which is what makes the AWS Load Balancer Controller materialize the ALB once the file is applied.
5. The workflow step `apply autocreated ingress` (in `k8s/scope/workflows/create.yaml`) applies whatever templates are in `$OUTPUT_DIR` via the standard `apply_templates` script. Its `post: wait for alb` runs `wait_for_alb`, which polls `aws elbv2 describe-load-balancers` every 10 seconds until the ALB reports `State.Code=active` (or `failed`/timeout, in which case the scope creation fails). An info-level heartbeat is emitted every ~30s so the operator can see progress.
6. Once active, `wait_for_alb` tags the ALB with `nullplatform:managed-by=autocreate`, `nullplatform:visibility=internet-facing|internal`, and `nullplatform:created-by-scope-id=<scope-id>`. **These tags are audit metadata only**, surfacing the lineage of which scope provisioned which ALB. Discovery does NOT depend on these tags.
7. The rest of the scope creation proceeds with `ALB_NAME` set to the new ALB.

## How concurrent scope creations behave

When scope A triggers autocreate, the provider is patched **before** the ALB is active. Scope B that starts during this window reads the provider list, sees the new ALB name, and treats it as a normal candidate. AWS will return `LoadBalancerNotFound` for the in-flight ALB during the few seconds before it shows up in the API; `resolve_balancer` interprets that error specifically as "0 rules" so the in-flight ALB wins least-loaded selection in scope B and no second autocreate fires. Scope B then waits on the same ALB via its own `wait_for_alb` step.

## Required permissions

In addition to the permissions already required for capacity validation, the autocreate path needs:

**Nullplatform API credentials.** The script calls `np provider list` and `np provider patch`, so the workflow environment must provide either `NP_TOKEN` or `NULLPLATFORM_API_KEY` with write access to the container-orchestration provider for the relevant NRN. Without these, the patch step fails with `❌ Failed to patch container-orchestration provider with new ALB`.

**AWS IAM (agent role).**

- `elasticloadbalancing:AddTags` — for the audit tags `wait_for_alb` applies once the ALB is active. Failure here is non-fatal (logged as a warning, the scope creation proceeds).

No new Kubernetes permissions are needed beyond those the agent already has for scope resources.

## Operational notes

- Scope creations that trigger autocreation are slower (typically 2–4 minutes extra). This is the expected behavior, not a regression. The platform logs `🔧 Best candidate ALB '...' is at or above capacity (X/Y); triggering autocreate` when it happens, followed by `⏳ Still waiting for ALB '...' to become active (provisioning, ~30s elapsed)` heartbeats while the controller provisions.
- The dummy Ingress (`nullplatform-autocreate-<alb-name>`) is created in the scope's namespace. It exposes no real traffic — the rule points to a fixed `404` response via the standard `alb.ingress.kubernetes.io/actions.response-404` annotation — and exists only to keep the ALB alive in the eyes of the AWS Load Balancer Controller. Deleting the dummy Ingress will cause the controller to delete the ALB.
- The ALB is registered in the nullplatform provider (not in the customer's IaC). Two consequences:
1. The provider becomes the source of truth for the ALB pool; subsequent scope creations read it directly.
2. The cloud's IaC (Terraform, OpenTofu, CloudFormation) is **not** updated automatically. If your IaC is the source of truth for ALB inventory, you should reconcile autocreated ALBs into it through your own process.

## Failure modes

| Failure | Outcome |
|---|---|
| `ALB_AUTOCREATE_NAME_PREFIX` invalid (bad charset or >18 chars) | Scope creation exits 1 with the validation error before any AWS or provider call. |
| `np provider list` cannot find a container-orchestration provider for the NRN | Scope creation exits 1 with `❌ No container-orchestration provider found for NRN '<nrn>'`. |
| `np provider patch` fails (no API token / no write access) | Scope creation exits 1 with `❌ Failed to patch container-orchestration provider with new ALB` + hint about `NP_TOKEN` / `NULLPLATFORM_API_KEY`. |
| `gomplate` render of the dummy Ingress fails | Scope creation exits 1 with `❌ Failed to render ingress-dummy template`. |
| ALB never reaches `active` within `ALB_AUTOCREATE_TIMEOUT_SECONDS` | Scope creation exits 1; check controller logs and AWS quota for ALBs in the region. |
| AWS reports the ALB state as `failed` | Scope creation exits 1 immediately. |
| `AddTags` call fails (no IAM permission) | Logged as `⚠️ Could not tag ALB '<name>' (audit only — provider registration already succeeded)`. The scope creation continues; the tags are documentation only. |

## What is out of scope

- Migration of existing scopes to autocreated ALBs. Use the `Recreate scope` action if needed.
- Automatic cleanup of unused autocreated ALBs (no scopes referencing them).
- Updating the cloud IaC (Terraform / OpenTofu / CloudFormation) with the new ALB.
19 changes: 10 additions & 9 deletions k8s/scope/build_context
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,13 @@ K8S_MODIFIERS=$(get_config_value \
)
K8S_MODIFIERS=$(echo "$K8S_MODIFIERS" | jq .)

OUTPUT_DIR="$SERVICE_PATH/output/$SCOPE_ID"
if [ -n "${NP_OUTPUT_DIR:-}" ]; then
OUTPUT_DIR="$NP_OUTPUT_DIR/output/$SCOPE_ID"
fi
mkdir -p "$OUTPUT_DIR"
export OUTPUT_DIR

source "$SCRIPT_DIR/networking/resolve_balancer"

NAMESPACE_SLUG=$(echo "$CONTEXT" | jq -r .namespace.slug)
Expand All @@ -224,18 +231,12 @@ CONTEXT=$(echo "$CONTEXT" | jq \
--arg gateway_name "$GATEWAY_NAME" \
--arg alb_name "$ALB_NAME" \
--arg component "$COMPONENT" \
--arg base_domain "$DOMAIN" \
--argjson modifiers "$K8S_MODIFIERS" \
'. + {ingress_visibility: $ingress_visibility, k8s_namespace: $k8s_namespace, gateway_name: $gateway_name, region: $region, k8s_modifiers: $modifiers, alb_name: $alb_name, component: $component}')
'. + {ingress_visibility: $ingress_visibility, k8s_namespace: $k8s_namespace, gateway_name: $gateway_name, region: $region, k8s_modifiers: $modifiers, alb_name: $alb_name, component: $component, base_domain: $base_domain}')

OUTPUT_DIR="$SERVICE_PATH/output/$SCOPE_ID"
if [ -n "${NP_OUTPUT_DIR:-}" ]; then
OUTPUT_DIR="$NP_OUTPUT_DIR/output/$SCOPE_ID"
fi

export OUTPUT_DIR
export CONTEXT
export REGION

mkdir -p "$OUTPUT_DIR"
export DOMAIN

log info "✅ Scope context built successfully"
2 changes: 1 addition & 1 deletion k8s/scope/iam/create_role
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ for ((i=0; i<$POLICIES_COUNT; i++)); do
POLICY_NAME="inline-policy-$((i+1))"
log debug "📝 Attaching inline policy: $POLICY_NAME"

TEMP_POLICY_FILE="/tmp/inline-policy-$i.json"
TEMP_POLICY_FILE="$OUTPUT_DIR/inline-policy-$i.json"
echo "$POLICY_VALUE" > "$TEMP_POLICY_FILE"

aws iam put-role-policy \
Expand Down
Loading
Loading