Commit Graph

536 Commits

Author SHA1 Message Date
joshsiv-mulga ce359fcefb docs: add EKS add-on executor, bundle, v1 compatibility and release inventories 2026-10-09 08:37:42 +10:00
joshsiv-mulga 3a55e2ee19 docs: apply review decisions to EKS add-on lifecycle design 2026-10-09 07:48:54 +10:00
joshsiv-mulga 6cfb01b47d docs: draft EKS add-on lifecycle correction design for review 2026-10-08 23:12:30 +10:00
joshsiv-mulga e274814b46 docs: update EKS add-on inventory and remaining edges 2026-10-08 23:12:30 +10:00
joshsiv-mulga 18794e0173 docs: record EKS add-on wire contract extraction 2026-10-08 23:02:20 +10:00
joshsiv-mulga 692d929b4a refactor: extract EKS add-on guest wire contract
Move the add-on status subject, delivery phase, status report, staged
manifest and internal add-ons response into contracts/eks/v1 and use it
from the handler, gateway and gatewayfetch, replacing the agent's
duplicate. Wire bytes are unchanged, including the internal add-ons body
that the SDK renderer emits with Go field names; compatibility tests pin
the JSON, the guest printf form and the TSV.
2026-10-08 23:02:20 +10:00
joshsiv-mulga 3e1abc027e docs: record EKS add-on owner extraction 2026-10-08 22:54:39 +10:00
joshsiv-mulga d899630542 refactor: extract EKS add-on owner
Move the add-on catalogue, record and manifest keys, CAS, status
transitions and staging into domains/eks/addon behind an Owner that is
the sole writer. The handler keeps AWS validation, error mapping and
projection; the cluster reconciler delegates reports through the narrow
addonReports interface; node groups call EnsureGPUDevicePlugin. Stored
keys, JSON shape, wire types and lifecycle behaviour are unchanged.
2026-10-08 22:54:27 +10:00
joshsiv-mulga 15a05d6c2c docs: record EKS add-on PassRole check 2026-10-08 22:40:30 +10:00
joshsiv-mulga 675341b561 docs: record EKS add-on lifecycle contract 2026-10-08 22:33:28 +10:00
joshsiv-mulga d804f5ac30 docs: record RDS DB parameter group owner extraction 2026-10-08 21:51:43 +10:00
joshsiv-mulga d9c3f3ca6f refactor: extract RDS DB parameter group owner
Move the parameter group and override records, keys and listing into
domains/rds/parametergroup behind an Owner. Delete asks an owner-declared
Dependants interface; instance-side resolution reads through its own
parameterGroupValues interface. SetOverrides refuses an unknown group so
no override key is written without its group. Stored shape, keys, public
errors and propagation are unchanged.
2026-10-08 21:51:08 +10:00
joshsiv-mulga 5548a7039f docs: record RDS DB parameter group lifecycle contract 2026-10-08 20:43:59 +10:00
joshsiv-mulga 15d11aff08 docs: record RDS engine catalogue owner move 2026-10-08 17:33:30 +10:00
joshsiv-mulga 4b55247819 refactor: extract RDS engine catalogue into domains/rds/engine
Move engine identity and validation, the per-engine parameter catalogues,
effective-parameter resolution, db.* instance classes and engine/orderable
selection into an engine package. Behaviour, error codes and ordering are
unchanged.

The package imports no EC2 package: instance-class memory comes from an
immutable Sizing value built from an engine-declared InstanceTypes
interface, backed by the fixed instance-type table in handlers/rds.
Effective parameters leave as an engine-owned Setting, converted to
Parameter in handlers/rds. AWS output building, paging, apply-method
mapping, storage limits and the gateway runnable check stay in the
adapter path.

The RDS agent declares its own EngineCatalog interface and types and
receives an engine-backed implementation from cmd/rds-agent, so agent
code does not import the catalogue. Cross-engine tests use the two real
engines instead of a registered fake.
2026-10-08 17:33:11 +10:00
joshsiv-mulga 292803bb1c docs: record RDS engine catalogue boundary and evidence 2026-10-08 14:43:46 +10:00
joshsiv-mulga 2b877b01a0 docs: record EKS access entry owner extraction 2026-10-08 12:34:24 +10:00
joshsiv-mulga 68ee6f620c refactor: extract EKS access entry owner into domains/eks/access
Move the access entry record, associated policy and scope types, keys,
PrincipalARNHash, the policy catalogue and group mapping into a resource
owner package. Stored JSON field names and KV keys are unchanged.

Owner is the only writer: Create, SeedCreatorAdmin, Update, Associate,
Disassociate and Delete carry the existing logic verbatim, and the put,
compare-and-set and delete primitives are private. The handler keeps AWS
validation, the cluster existence check, error mapping and projection.
Token review still reads records through the exported Get, and the cluster
prefix purge still sweeps entry keys.

List handlers/eks and the new package under spinifex_daemon so EKS-only
changes select the EKS suites.
2026-10-08 12:33:46 +10:00
joshsiv-mulga 2dec7b0de7 docs: record EKS access entry lifecycle contract 2026-10-08 11:53:52 +10:00
joshsiv-mulga 37bb781c4c docs: record ECS task definition owner extraction 2026-10-08 11:08:48 +10:00
joshsiv-mulga 544b0c6a01 refactor: extract ECS task definition owner into domains/ecs/taskdefinition
Move the task definition record, container types, keys, reference parsing,
ARN helpers and the register/deregister/resolve/list logic into a resource
owner package. Stored JSON field names and KV keys are unchanged, and
revision allocation keeps its existing non-atomic read-then-write.

The owner declares its own PortMapping and SystemControl persisted types so
it no longer depends on handlers/ecs/bus; the handler translates them onto
the assign payload. Task and service launches reach task definitions only
through a launch-side taskDefinitionResolver. AWS conversion, the guest
projection and tags stay in handlers/ecs, and gateway authz only switches to
the moved reference/ARN helpers.

List handlers/ecs and the new package under spinifex_daemon so ECS-only
changes select e2e-ecs.
2026-10-08 11:07:54 +10:00
joshsiv-mulga 194deebf7b docs: record ECS task definition lifecycle contract 2026-10-08 10:36:28 +10:00
joshsiv-mulga c3b1aec23c docs: correct RDS subnet group tagging debt and conformance scope 2026-10-08 10:27:47 +10:00
joshsiv-mulga 11fab67247 docs: record RDS DB subnet group owner extraction 2026-10-08 10:03:17 +10:00
joshsiv-mulga 17dc29251d refactor: extract the RDS DB subnet group lifecycle owner
domains/rds/subnetgroup owns the db-subnet-groups/{name} record, member
subnet validation and the delete in-use guard. handlers/rds keeps AWS
validation, projection and paging and binds two consumer-owned
capabilities: the owner's Dependants (instances naming the group) and the
instance side's subnetGroupPlacement. Stored shape and behaviour are
unchanged.
2026-10-08 10:03:01 +10:00
joshsiv-mulga 56384f9046 docs: record RDS DB subnet group lifecycle contract 2026-10-08 09:46:12 +10:00
joshsiv-mulga 9e77181f21 Merge origin/dev into refactor/spinifex-package-boundaries 2026-10-08 09:28:43 +10:00
joshsiv-mulga 8b6840adb0 docs: record EKS control-plane secrets persisted in the cluster record 2026-10-08 08:51:51 +10:00
joshsiv-mulga bc1d3fea51 docs: record ECS, EKS and RDS source inventory and first-resource ranking 2026-10-08 08:44:51 +10:00
joshsiv-mulga 6c50349bd8 docs: record ACM registration 2026-10-08 00:42:00 +10:00
joshsiv-mulga 229b0dc9c3 docs: record ingress service selection move 2026-10-07 23:43:43 +10:00
joshsiv-mulga d70a5a43f7 docs: record error envelope extraction 2026-10-07 23:27:21 +10:00
joshsiv-mulga 88c7397f45 docs: record ingress pipeline deferral 2026-10-07 23:02:45 +10:00
joshsiv-mulga e6622c51d2 docs: record ECR control-plane registration 2026-10-07 22:55:20 +10:00
joshsiv-mulga 6c48ab6b78 refactor: register the ECR control plane from its domain
domains/ecr/awsapi now registers the "ecr" AWS JSON 1.1 surface with the
ingress dispatch registry: action resolution, resource scope, authorisation
through the invocation, the composed capabilities and the success writer all
live with the domain. The awsgw role is the only serving composition point;
the gateway no longer imports the ECR API package and ECR leaves the legacy
service table, JSON error table and dispatch switch. The OCI /v2 route is
unchanged.

Operation coverage reads ECR's declared inventory, which the registration
uses verbatim; coverage.json is byte-identical.
2026-10-07 22:55:00 +10:00
joshsiv-mulga f1cd7c5fe8 docs: record ingress dispatch registry consumption 2026-10-07 22:35:33 +10:00
joshsiv-mulga 4ad0b7e8fc docs: record ingress registration phases 0 and 1 2026-10-07 22:09:54 +10:00
joshsiv-mulga 70ac74a21c docs: record AWS ingress helper moves and tooling conformance 2026-10-07 18:12:29 +10:00
joshsiv-mulga 7303059fc8 docs: record EC2 instance action adapter moves 2026-10-07 14:47:51 +10:00
joshsiv-mulga 534c5b49dd refactor: move EC2 instance action adapters to domains/ec2/awsapi 2026-10-07 14:47:51 +10:00
joshsiv-mulga cf76c609cf docs: record EC2 storage and tag action adapter moves 2026-10-07 14:36:36 +10:00
joshsiv-mulga 8c94a4e613 docs: record EC2 VPC networking action adapter moves 2026-10-07 14:31:27 +10:00
joshsiv-mulga 21bb9c9545 docs: record EC2 standalone action adapter moves 2026-10-07 14:23:27 +10:00
joshsiv-mulga 84293aecd7 docs: record spx command tree move 2026-10-07 13:56:18 +10:00
Ben Duncan f99c861810 docs: render the OCI guide's console screenshot (#1186) 2026-10-07 14:27:10 +11:00
joshsiv-mulga 606770ed7f docs: record installer move 2026-10-07 13:14:45 +10:00
joshsiv-mulga db8df67f23 docs: record RDS guest agent move 2026-10-07 12:47:23 +10:00
joshsiv-mulga a2238a0f83 docs: record ECS agent move 2026-10-07 12:27:11 +10:00
joshsiv-mulga 50753e9831 docs: record ECS agent subpackage move 2026-10-07 12:16:10 +10:00
Ben Duncan 8409799cbf fix: default the OCI helper to the DEFAULT profile (#1174)
* fix: default the OCI helper to the DEFAULT profile

oci_env.py shipped our own internal Oracle compartment profile as the default for
every reader, with a second internal name as a fallback.
A fresh tenancy's config holds only `[DEFAULT]`, so the shipped default was a name
nobody else can have, and the fallback chain could land a deployment in whichever
tenancy a different profile happened to point at.

The profile is now `DEFAULT`, overridable by `OCI_CLI_PROFILE` or `--profile`, and a
named profile the config does not hold is an error rather than a fallback.
`DEFAULT` needs its own existence test, because configparser treats it as the
fallback for every other section and `has_section` denies it exists even when
`oci setup config` wrote nothing else.

`eip-move-test.sh` and the config table in the README carried the same name.
CI reads its credential from the environment rather than from a profile, so nothing
there referenced it.

* docs: look the OCI compartment OCID up by name

The guide asked the reader to paste `ocid1.compartment.oc1..xxx` three times, and
the `oci` CLI it already requires can resolve the name instead.

The lookup needs its own check. `oci iam compartment list --query ... | [0]` exits 0
and prints nothing when the name matches nothing, so an unset `$CID` would otherwise
reach Terraform and fail with an OCI error naming neither the compartment nor the
cause.

Two compartments in one tenancy can share a name, which the listing command shows
and the lookup cannot resolve, so the paste form stays as the answer for that case.
Records why the tenancy root is the wrong value: the step 2.1 policy is scoped to
whatever `$CID` holds.

* Revert "docs: look the OCI compartment OCID up by name"

This reverts commit 5443149cce.

* Reapply "docs: look the OCI compartment OCID up by name"

This reverts commit 1535d94c8a.

* fix: stop validate-topology.sh failing on macOS bash

`tf_principal_var` is empty on every run that does not pass `--instance-principal`,
and bash before 4.4 treats `"${arr[@]}"` on an empty array as an unbound variable
under `set -u`.
macOS ships bash 3.2, so the documented deployment command aborted at the tf_vars
assembly with `tf_principal_var[@]: unbound variable` before building anything.

Assigning the array was not enough, which is why the earlier `HOSTS=()` fix did not
cover this: the error is in the expansion, not the reference.
Both sites now expand only behind a length test, which is valid on every version.

`capture_journals` had the same shape for `HOSTS`, reachable when a run fails before
the apply.

Reproduced and fixed against bash 3.2.57 in a container, which emits the identical
message, and the script now runs to the terraform call on 3.2 for vm-single,
vm-single-principal and `--instance-principal`, including the `--keep` cleanup path
with no hosts.

* docs: record the bash 3.x requirement in the OCI scripts

These three are the scripts the OCI guide tells an operator to run, and macOS ships
bash 3.2, so a bash 4 construct in any of them breaks a deployment rather than a
test.
The constraint has now been rediscovered twice, through declare -A and mapfile, and
then through an empty array expanded under set -u.

Names the three traps at the top of each file so the next change does not have to
find them by failing on someone's Mac.

* fix: stream the workbook suite instead of hiding it in a file

The nine workbooks ran with stdout redirected to `workbooks.log`, so a run wedged
inside one printed nothing until the whole suite finished.
A CI job then sat silent for hours with no way to tell which workbook it was in, or
whether it was working at all.

The output is now teed, so the file the per-workbook table is parsed from is
unchanged while the run is visible live.
Each of the driver's own RUN/PASS/FAIL lines is stamped with elapsed seconds and the
workbook name, which is what identifies the slow one without reading the log.

`pipefail` carries the ssh exit status through the pipe, so a failed suite still
takes the branch that records the table and captures the journals.

* fix: grant the OCI policy the subnet verb AssignPrivateIP requires

An OCI node with a pool-mode external interface formed, reported healthy, and
then refused every launch wanting a public address with a 404
NotAuthorizedOrNotFound from AssignPrivateIP.

AssignPrivateIP calls CreatePrivateIp, which Oracle documents as needing
PRIVATE_IP_CREATE, PRIVATE_IP_ASSIGN, VNIC_ASSIGN and SUBNET_ATTACH, and
DeletePrivateIp as needing SUBNET_DETACH.
The policy granted use vnics, manage private-ips and manage public-ips, and no
subnet permission at all, so SUBNET_ATTACH was never held.
Only the subnets use verb carries it.

Measured on a live node as the instance principal: GetVnic and ListPrivateIps
succeed, GetSubnet and CreatePrivateIp both 404.
Authentication, the dynamic group's matching rule and the compartment were all
correct, so the credential was authentic and unauthorised.
The documented operator policy for the API-key route carried the same three
statements, so neither credential route could allocate.

A node now proves its own credential at pool start by making one read in each
permission class, and logs the missing verb if one fails.
The topology gate waited for "resolved the external VNIC", which comes from the
metadata service and needs no IAM policy, so an unauthorised node passed the
gate and failed an hour later inside a workbook as capacity.
It now waits for the authorisation line and fails immediately on a refusal.

Reads rather than a real assign-and-release round trip: a round trip also fails
on a full subnet, and reporting that as an authorisation fault sends an operator
to IAM for a capacity problem.

* docs: say what a principal run costs in a tenancy with no dynamic group

No principal topology can pass there, and the only previous hint was one clause
about OCI_INSTANCE_PRINCIPAL needing the group to exist.

* test: cover the subnet read and the authorisation verdict

Diff coverage was 39.1% against a 60% gate. Three of the four gaps were real.

GetSubnet on the SDK-backed client had no test, so the request shape was
unverified: addressing the collection rather than one subnet by OCID would
answer from a different permission and report a credential as working when it is
not. Also pins that a sparse body is not an error and that the unauthorised 404
arrives as ErrNotFound, which is what stops the check retrying a verdict that
will never change.

The fake's GetSubnet was exercised only from the ocinet tests, and coverage is
recorded per package under test, so it read as zero-covered.

reportAuthorisation now has a test asserting the exact log text, because
validate-topology.sh greps for it and a rename would quietly turn the gate into
one that never fires.

The six lines still uncovered are inside FromPoolConfig, which needs a live
JetStream to reach. Diff coverage 87.0%.

* fix: refuse a run whose tfvars file contradicts the compartment it was given

Terraform ranks *.auto.tfvars above TF_VAR_*, so a leftover file wins silently
over an operator targeting a different tenancy by environment, and the guide
tells every operator to write one.

Hit for real: a stale terraform.auto.tfvars naming a different tenancy's
compartment and a bare-metal shape overrode TF_VAR_compartment_ocid, and the
plan was for 11 resources in a compartment nobody had chosen. Only a dry run
caught it.

The run now stops and names both values rather than building.

* fix: collect orphaned OCI public IPs and name the limit that was hit

A reserved public IP attached to nothing is on no VNIC, so the reconcile
that exists to find leaked addresses could not see the ones that matter.
It also ran only at startup, while an address is leaked on release and on
teardown. Twenty accumulated in one compartment and exhausted the
tenancy's limit of 50, after which every launch needing a public address
aborted.

Reconcile now lists reserved public IPs in the compartment and collects
those that carry our prefix, are attached to nothing, are named by no
binding and are older than a grace period. All four are needed: an
allocated but unassociated Elastic IP is detached too and is the
customer's to keep, and a young detached address is what another node's
CreatePublicIP looks like until it attaches. A pool with no binding
record is not swept at all, since that is indistinguishable from a store
that cannot be read.

The pass now repeats on a ticker rather than running once at startup.

Both OCI ceilings also resolved to InsufficientAddressCapacity, which
tells an operator the region is out of addresses when the fix is to
release addresses they already hold. AWS separates them, so a tenancy out
of reserved public IPs is now AddressLimitExceeded and a full VNIC is
PrivateIpAddressLimitExceeded.

* feat: run the OCI principal e2e in a tenancy that can hold its dynamic group

vm-single-principal needs the tenancy's dynamic group, and creating one is a
tenancy-root write that a shared test tenancy does not grant us. The topology
could never pass there however correct the code was, so it now takes its own
credential from OCI_PRINCIPAL_*, all six or none. A partial set is refused
before the build rather than at the apply, because it would build one tenancy's
instances against another's compartment. With none set the topology is recorded
SKIPPED instead of spending a provision to fail.

The sweep takes the same credential, or a leftover principal deployment is
destroyed against a tenancy that never held it and reported clean.

Two defects found while proving it:

--dry-run reported FAIL on every topology with a tree source, because the dry
run skips the build and then refuses the tarball it would have produced. The
flag operators are told to run first could not pass.

The dry run truncated its plan to the last 30 lines, which is the outputs block.
That dropped the resource count and every compartment and shape line, so the
one check a dry run exists for could not be made from its output. It now writes
the whole plan and prints the summary.

Removing a deployment is documented as leaving public IPs behind. Spinifex
allocates them on the running node, so they are in no Terraform state and
destroy cannot see them; twenty accumulated and exhausted a tenancy's limit,
which then refused every launch in every compartment.

* fix: canonicalise the Opc-Next-Page header in the pagination test

CI's linter rejects a non-canonical header name and the local preflight does
not, so this passed here and failed there.
2026-10-06 20:56:16 -05:00