mirror of
https://github.com/mulgadc/spinifex.git
synced 2026-10-26 05:59:41 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d2d81b9cb | |||
| c4feca87ed | |||
| 2df87981f2 | |||
| 7fc79d95bd | |||
| 0c440a6717 | |||
| 00e38f9e76 |
@@ -10,28 +10,28 @@ type allowance struct {
|
||||
// docs/PACKAGE_BOUNDARY_MIGRATION.md. It only shrinks: delete an entry as soon
|
||||
// as its use is gone, and never add one to make a new consumer pass.
|
||||
var allowlist = []allowance{
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "daemon.LocalStatePath", Inv: "INV-01"},
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "daemon.ReadLocalState", Inv: "INV-01"},
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "daemon.MarshalLocalState", Inv: "INV-01"},
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "daemon.WriteLocalStateBytes", Inv: "INV-01"},
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "JetStreamManager.InitKVBucket", Inv: "INV-02"},
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "JetStreamManager.InitTerminatedInstanceBucket", Inv: "INV-02"},
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "JetStreamManager.WriteNodeMarkerBestEffort", Inv: "INV-03"},
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "JetStreamManager.WriteRunningSet", Inv: "INV-03"},
|
||||
{File: "spinifex/daemon/instance_record_repair.go", Symbol: "JetStreamManager.WriteRunningSet", Inv: "INV-03"},
|
||||
{File: "spinifex/daemon/daemon.go", Symbol: "JetStreamManager.ListTerminatedInstances", Inv: "INV-04"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "daemon.LocalStatePath", Inv: "INV-01"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "daemon.ReadLocalState", Inv: "INV-01"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "daemon.MarshalLocalState", Inv: "INV-01"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "daemon.WriteLocalStateBytes", Inv: "INV-01"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "JetStreamManager.InitKVBucket", Inv: "INV-02"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "JetStreamManager.InitTerminatedInstanceBucket", Inv: "INV-02"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "JetStreamManager.WriteNodeMarkerBestEffort", Inv: "INV-03"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "JetStreamManager.WriteRunningSet", Inv: "INV-03"},
|
||||
{File: "spinifex/runtime/daemon/instance_record_repair.go", Symbol: "JetStreamManager.WriteRunningSet", Inv: "INV-03"},
|
||||
{File: "spinifex/runtime/daemon/daemon.go", Symbol: "JetStreamManager.ListTerminatedInstances", Inv: "INV-04"},
|
||||
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.LoadState", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.WriteStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.LoadStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.DeleteStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.UpdateStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.ClaimStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.ListStoppedInstances", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.WriteTerminatedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.UpdateTerminatedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.ListTerminatedInstances", Inv: "INV-05"},
|
||||
{File: "spinifex/daemon/vm_adapters.go", Symbol: "JetStreamManager.DeleteTerminatedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.LoadState", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.WriteStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.LoadStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.DeleteStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.UpdateStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.ClaimStoppedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.ListStoppedInstances", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.WriteTerminatedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.UpdateTerminatedInstance", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.ListTerminatedInstances", Inv: "INV-05"},
|
||||
{File: "spinifex/runtime/daemon/vm_adapters.go", Symbol: "JetStreamManager.DeleteTerminatedInstance", Inv: "INV-05"},
|
||||
|
||||
{File: "spinifex/runtime/compute/vm/migrate.go", Symbol: "JetStreamManager.WriteStoppedInstance", Inv: "INV-06"},
|
||||
{File: "spinifex/runtime/compute/vm/migrate.go", Symbol: "JetStreamManager.WriteTerminatedInstance", Inv: "INV-06"},
|
||||
@@ -50,23 +50,23 @@ var allowlist = []allowance{
|
||||
{File: "spinifex/domains/ec2/instance/service_impl.go", Symbol: "JetStreamManager.WriteTerminatedInstance", Inv: "INV-07"},
|
||||
{File: "spinifex/domains/ec2/instance/service_impl.go", Symbol: "JetStreamManager.DeleteStoppedInstance", Inv: "INV-07"},
|
||||
|
||||
{File: "spinifex/daemon/daemon_handlers_image.go", Symbol: "JetStreamManager.LoadStoppedInstance", Inv: "INV-08"},
|
||||
{File: "spinifex/daemon/daemon_handlers_image.go", Symbol: "JetStreamManager.UpdateStoppedInstance", Inv: "INV-08"},
|
||||
{File: "spinifex/runtime/daemon/daemon_handlers_image.go", Symbol: "JetStreamManager.LoadStoppedInstance", Inv: "INV-08"},
|
||||
{File: "spinifex/runtime/daemon/daemon_handlers_image.go", Symbol: "JetStreamManager.UpdateStoppedInstance", Inv: "INV-08"},
|
||||
|
||||
{File: "spinifex/daemon/instance_recovery.go", Symbol: "vm.InstanceRecord", Inv: "INV-10"},
|
||||
{File: "spinifex/daemon/instance_recovery.go", Symbol: "JetStreamManager.ListInstanceRecords", Inv: "INV-10"},
|
||||
{File: "spinifex/daemon/instance_recovery.go", Symbol: "JetStreamManager.ClaimRecoverableInstance", Inv: "INV-10"},
|
||||
{File: "spinifex/daemon/instance_recovery.go", Symbol: "JetStreamManager.ReleaseRecoveredInstance", Inv: "INV-10"},
|
||||
{File: "spinifex/daemon/instance_recovery.go", Symbol: "JetStreamManager.AbandonRecovery", Inv: "INV-10"},
|
||||
{File: "spinifex/daemon/instance_recovery.go", Symbol: "JetStreamManager.LoadInstanceRecord", Inv: "INV-10"},
|
||||
{File: "spinifex/runtime/daemon/instance_recovery.go", Symbol: "vm.InstanceRecord", Inv: "INV-10"},
|
||||
{File: "spinifex/runtime/daemon/instance_recovery.go", Symbol: "JetStreamManager.ListInstanceRecords", Inv: "INV-10"},
|
||||
{File: "spinifex/runtime/daemon/instance_recovery.go", Symbol: "JetStreamManager.ClaimRecoverableInstance", Inv: "INV-10"},
|
||||
{File: "spinifex/runtime/daemon/instance_recovery.go", Symbol: "JetStreamManager.ReleaseRecoveredInstance", Inv: "INV-10"},
|
||||
{File: "spinifex/runtime/daemon/instance_recovery.go", Symbol: "JetStreamManager.AbandonRecovery", Inv: "INV-10"},
|
||||
{File: "spinifex/runtime/daemon/instance_recovery.go", Symbol: "JetStreamManager.LoadInstanceRecord", Inv: "INV-10"},
|
||||
|
||||
{File: "spinifex/daemon/dns_reconcile.go", Symbol: "daemon.InstanceRecordPrefix", Inv: "INV-11"},
|
||||
{File: "spinifex/daemon/dns_reconcile.go", Symbol: "daemon.InstanceStateBucket", Inv: "INV-11"},
|
||||
{File: "spinifex/daemon/dns_reconcile.go", Symbol: "JetStreamManager.ListInstanceRecords", Inv: "INV-11"},
|
||||
{File: "spinifex/runtime/daemon/dns_reconcile.go", Symbol: "daemon.InstanceRecordPrefix", Inv: "INV-11"},
|
||||
{File: "spinifex/runtime/daemon/dns_reconcile.go", Symbol: "daemon.InstanceStateBucket", Inv: "INV-11"},
|
||||
{File: "spinifex/runtime/daemon/dns_reconcile.go", Symbol: "JetStreamManager.ListInstanceRecords", Inv: "INV-11"},
|
||||
|
||||
{File: "spinifex/daemon/eni_orphan_reaper.go", Symbol: "vm.InstanceRecord", Inv: "INV-12"},
|
||||
{File: "spinifex/daemon/eni_orphan_reaper.go", Symbol: "JetStreamManager.ListInstanceRecords", Inv: "INV-12"},
|
||||
{File: "spinifex/daemon/eni_orphan_reaper.go", Symbol: "JetStreamManager.ListTerminatedInstanceRecords", Inv: "INV-12"},
|
||||
{File: "spinifex/runtime/daemon/eni_orphan_reaper.go", Symbol: "vm.InstanceRecord", Inv: "INV-12"},
|
||||
{File: "spinifex/runtime/daemon/eni_orphan_reaper.go", Symbol: "JetStreamManager.ListInstanceRecords", Inv: "INV-12"},
|
||||
{File: "spinifex/runtime/daemon/eni_orphan_reaper.go", Symbol: "JetStreamManager.ListTerminatedInstanceRecords", Inv: "INV-12"},
|
||||
|
||||
{File: "spinifex/domains/ec2/instance/state/imds_readers.go", Symbol: "daemon.LocalState", Inv: "INV-13"},
|
||||
{File: "spinifex/domains/ec2/instance/state/imds_readers.go", Symbol: "daemon.LocalStatePath", Inv: "INV-13"},
|
||||
|
||||
@@ -95,7 +95,7 @@ const faultSource = `package %s
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
spxd "github.com/mulgadc/spinifex/spinifex/daemon"
|
||||
spxd "github.com/mulgadc/spinifex/spinifex/runtime/daemon"
|
||||
"github.com/mulgadc/spinifex/spinifex/runtime/compute/vm"
|
||||
)
|
||||
|
||||
@@ -176,11 +176,11 @@ func TestADR0007_S6_RatchetDetectsFaults(t *testing.T) {
|
||||
// must see those too.
|
||||
func TestADR0007_S6_RatchetSeesBareIdentifiersInOwnerPackages(t *testing.T) {
|
||||
src := map[string]string{
|
||||
"spinifex/daemon/new_consumer.go": "package daemon\n\nfunc f() string { return InstanceStateBucket + LocalStatePath(\"\") }\n",
|
||||
"spinifex/runtime/daemon/new_consumer.go": "package daemon\n\nfunc f() string { return InstanceStateBucket + LocalStatePath(\"\") }\n",
|
||||
"spinifex/runtime/compute/vm/new_reader.go": "package vm\n\nfunc g(r *InstanceRecord) *VM { return VMFromRecord(r) }\n",
|
||||
}
|
||||
want := map[string][]string{
|
||||
"spinifex/daemon/new_consumer.go": {"daemon.InstanceStateBucket", "daemon.LocalStatePath"},
|
||||
"spinifex/runtime/daemon/new_consumer.go": {"daemon.InstanceStateBucket", "daemon.LocalStatePath"},
|
||||
"spinifex/runtime/compute/vm/new_reader.go": {"vm.InstanceRecord", "vm.VMFromRecord"},
|
||||
}
|
||||
for rel, code := range src {
|
||||
|
||||
@@ -20,9 +20,9 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
daemonPath = "github.com/mulgadc/spinifex/spinifex/daemon"
|
||||
daemonPath = "github.com/mulgadc/spinifex/spinifex/runtime/daemon"
|
||||
vmPath = "github.com/mulgadc/spinifex/spinifex/runtime/compute/vm"
|
||||
daemonDir = "spinifex/daemon"
|
||||
daemonDir = "spinifex/runtime/daemon"
|
||||
vmDir = "spinifex/runtime/compute/vm"
|
||||
)
|
||||
|
||||
@@ -69,12 +69,12 @@ var literals = set(`"spinifex-instance-state"`, `"spinifex-terminated-instances"
|
||||
// ownerFiles define the symbols above. They are the implementation being
|
||||
// migrated, not consumers of it, and are listed so a deleted one is noticed.
|
||||
var ownerFiles = []string{
|
||||
"spinifex/daemon/instance_membership.go",
|
||||
"spinifex/daemon/instance_records.go",
|
||||
"spinifex/daemon/instance_records_migrate.go",
|
||||
"spinifex/daemon/instance_running_set.go",
|
||||
"spinifex/daemon/jetstream.go",
|
||||
"spinifex/daemon/local_state.go",
|
||||
"spinifex/runtime/daemon/instance_membership.go",
|
||||
"spinifex/runtime/daemon/instance_records.go",
|
||||
"spinifex/runtime/daemon/instance_records_migrate.go",
|
||||
"spinifex/runtime/daemon/instance_running_set.go",
|
||||
"spinifex/runtime/daemon/jetstream.go",
|
||||
"spinifex/runtime/daemon/local_state.go",
|
||||
"spinifex/runtime/compute/vm/record.go",
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ var allowlist = []allowance{
|
||||
{From: "accountteardown", To: "domains/ec2/awsapi/volume", Debt: "accountteardown (dependency)", Reason: "EC2 reapers call the awsapi action adapters; needs domain-owned cleanup capabilities"},
|
||||
{From: "accountteardown", To: "domains/ec2/awsapi/vpc", Debt: "accountteardown (dependency)", Reason: "EC2 reapers call the awsapi action adapters; needs domain-owned cleanup capabilities"},
|
||||
|
||||
{From: "agents/ecs/agent", To: "handlers/ecs", Debt: "agents/ecs/agent", Reason: "assignment/state wire types from the ECS implementation; needs a guest/controller contract"},
|
||||
{From: "agents/ecs/agent", To: "domains/ecs", Debt: "agents/ecs/agent", Reason: "assignment/state wire types from the ECS implementation; needs a guest/controller contract"},
|
||||
|
||||
{From: "agents/eks/tokenwebhook", To: "domains/eks/cluster", Debt: "agents/eks/tokenwebhook", Reason: "WebhookTokenReviewResult from the EKS implementation; needs an EKS contract or projection"},
|
||||
|
||||
@@ -32,7 +32,7 @@ var allowlist = []allowance{
|
||||
{From: "cmd/aws-model-coverage", To: "domains/acm/awsapi", Debt: "cmd/aws-model-coverage", Reason: "reads declared ECR/ACM inventories; named exception, must not construct a registration"},
|
||||
{From: "cmd/aws-model-coverage", To: "domains/ecr/awsapi", Debt: "cmd/aws-model-coverage", Reason: "reads declared ECR/ACM inventories; named exception, must not construct a registration"},
|
||||
|
||||
{From: "daemon", To: "domains/ec2/awsapi/instance", Debt: "Instance adapter consumers outside EC2", Reason: "daemon/eks_cp_control.go and rds_deps.go call the instance adapter; needs an EC2 capability"},
|
||||
{From: "runtime/daemon", To: "domains/ec2/awsapi/instance", Debt: "Instance adapter consumers outside EC2", Reason: "runtime/daemon/eks_cp_control.go and rds_deps.go call the instance adapter; needs an EC2 capability"},
|
||||
|
||||
{From: "domains/acm", To: "domains/iam", Debt: "ACM: the IAM AES-GCM helper import (ACM relocation residual)", Reason: "IAM AES-GCM helpers; needs a shared crypto boundary"},
|
||||
|
||||
@@ -66,7 +66,7 @@ var allowlist = []allowance{
|
||||
{From: "domains/ec2/instance", To: "domains/network/topology", Debt: "EC2 → network implementation", Reason: "EC2 reads network implementation; clears with the EC2-owned projection"},
|
||||
{From: "domains/ec2/instance", To: "runtime/compute/gpu", Debt: "domains/ec2/instance", Reason: "VM/GPU runtime state read directly; needs a domain-owned projection"},
|
||||
{From: "domains/ec2/instance", To: "runtime/compute/vm", Debt: "domains/ec2/instance", Reason: "VM/GPU runtime state read directly; needs a domain-owned projection"},
|
||||
{From: "domains/ec2/instance/state", To: "daemon", Debt: "domains/ec2/instance/state (dependency, IMDS readers)", Reason: "wraps the daemon's local-file reader and JetStreamManager bucket open unchanged for vpcd's IMDS lookup"},
|
||||
{From: "domains/ec2/instance/state", To: "runtime/daemon", Debt: "domains/ec2/instance/state (dependency, IMDS readers)", Reason: "wraps the daemon's local-file reader and JetStreamManager bucket open unchanged for vpcd's IMDS lookup"},
|
||||
{From: "domains/ec2/instance/state", To: "runtime/compute/vm", Debt: "domains/ec2/instance/state (dependency)", Reason: "the vpcd IMDS local reader returns vm.VM"},
|
||||
|
||||
{From: "domains/ec2/volume", To: "runtime/compute/vm", Debt: "Domain → runtime", Reason: "VM runtime state read directly; needs a domain-owned projection"},
|
||||
@@ -89,8 +89,9 @@ var allowlist = []allowance{
|
||||
{From: "domains/network/reconcile", To: "runtime/compute/vm", Debt: "domains/network", Reason: "domain-to-runtime import"},
|
||||
|
||||
{From: "domains/ochre", To: "domains/ec2/systeminstance", Debt: "Cross-domain", Reason: "consumes another domain's implementation rather than a capability"},
|
||||
{From: "domains/ochre", To: "domains/iam", Debt: "IAM implementation consumers in STS, Ochre and ELBv2", Reason: "secret encrypt/decrypt helpers; needs a shared crypto boundary"},
|
||||
{From: "domains/ochre", To: "domains/network/systemvpc", Debt: "Cross-domain", Reason: "consumes another domain's implementation rather than a capability"},
|
||||
{From: "domains/ochre", To: "gateway/bedrock", Debt: "gateway/bedrock", Reason: "Ochre domain core still lives in gateway/bedrock; needs the recorded split"},
|
||||
{From: "domains/ochre", To: "ingress/aws/bodyscope", Debt: "domains/ochre (action adapters)", Reason: "Bedrock action adapters still live in the Ochre package; they move to domains/ochre/awsapi"},
|
||||
{From: "domains/ochre", To: "runtime/compute/gpu", Debt: "Domain → runtime", Reason: "GPU runtime state read directly; needs a domain-owned projection"},
|
||||
|
||||
{From: "domains/ochre/vector", To: "domains/iam", Debt: "Cross-domain", Reason: "consumes another domain's implementation rather than a capability"},
|
||||
@@ -116,8 +117,6 @@ var allowlist = []allowance{
|
||||
{From: "gateway", To: "domains/ec2/awsapi/vpc", Debt: "gateway/ec2.go (registration)", Reason: "EC2 dispatch map stays in gateway until the ingress registration seam"},
|
||||
{From: "gateway", To: "domains/ec2/awsapi/zone", Debt: "gateway/ec2.go (registration)", Reason: "EC2 dispatch map stays in gateway until the ingress registration seam"},
|
||||
|
||||
{From: "gateway/bedrock", To: "domains/iam", Debt: "IAM implementation consumers in STS, Ochre and ELBv2", Reason: "secret encrypt/decrypt helpers; needs a shared crypto boundary"},
|
||||
|
||||
{From: "domains/rds/awsapi", To: "domains/ec2/awsapi/instance", Debt: "Instance adapter consumers outside EC2", Reason: "enginecatalog.go calls the instance adapter; needs an EC2 capability"},
|
||||
|
||||
{From: "domains/sts/awsapi", To: "domains/iam", Debt: "IAM implementation consumers in STS, Ochre and ELBv2", Reason: "GetCallerIdentity takes IAMService; needs an IAM capability"},
|
||||
@@ -126,11 +125,11 @@ var allowlist = []allowance{
|
||||
{From: "gateway", To: "domains/iam/awsapi", Debt: "gateway (service registration)", Reason: "IAM, STS, EKS and RDS dispatch stays in gateway until the ingress registration seam"},
|
||||
{From: "gateway", To: "domains/rds/awsapi", Debt: "gateway (service registration)", Reason: "IAM, STS, EKS and RDS dispatch stays in gateway until the ingress registration seam"},
|
||||
{From: "gateway", To: "domains/sts/awsapi", Debt: "gateway (service registration)", Reason: "IAM, STS, EKS and RDS dispatch stays in gateway until the ingress registration seam"},
|
||||
{From: "handlers/ecs", To: "domains/ec2/ebs/metadata", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "handlers/ecs", To: "domains/ec2/eip", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "handlers/ecs", To: "domains/ec2/instancetypes", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "handlers/ecs", To: "handlers/elbv2", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "handlers/ecs", To: "domains/iam", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "domains/ecs", To: "domains/ec2/ebs/metadata", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "domains/ecs", To: "domains/ec2/eip", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "domains/ecs", To: "domains/ec2/instancetypes", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "domains/ecs", To: "domains/elbv2", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
{From: "domains/ecs", To: "domains/iam", Debt: "close-out: handlers/ecs (ADR-0004 blocked-dependency list; inventory-ecs boundary imports)", Reason: "legacy ECS calls another domain's implementation as an internal API"},
|
||||
|
||||
{From: "domains/eks/cluster", To: "domains/dns", Debt: "close-out: handlers/eks (ADR-0004 blocked-dependency list; inventory-eks boundary imports)", Reason: "legacy EKS calls another domain's or runtime implementation directly"},
|
||||
{From: "domains/eks/cluster", To: "domains/ec2/ebs/metadata", Debt: "close-out: handlers/eks (ADR-0004 blocked-dependency list; inventory-eks boundary imports)", Reason: "legacy EKS calls another domain's or runtime implementation directly"},
|
||||
@@ -141,13 +140,13 @@ var allowlist = []allowance{
|
||||
{From: "domains/eks/cluster", To: "domains/iam", Debt: "close-out: handlers/eks (ADR-0004 blocked-dependency list; inventory-eks boundary imports)", Reason: "legacy EKS calls another domain's or runtime implementation directly"},
|
||||
{From: "domains/eks/cluster", To: "runtime/compute/vm", Debt: "close-out: handlers/eks (ADR-0004 blocked-dependency list; inventory-eks boundary imports)", Reason: "legacy EKS calls another domain's or runtime implementation directly"},
|
||||
|
||||
{From: "handlers/elbv2", To: "domains/acm", Debt: "handlers/elbv2 (cross-domain)", Reason: "legacy ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "handlers/elbv2", To: "domains/dns", Debt: "handlers/elbv2 (cross-domain)", Reason: "legacy ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "handlers/elbv2", To: "domains/ec2/systeminstance", Debt: "handlers/elbv2 (cross-domain)", Reason: "legacy ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "handlers/elbv2", To: "domains/ec2/vpc", Debt: "handlers/elbv2 (cross-domain)", Reason: "legacy ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "handlers/elbv2", To: "domains/network/topology", Debt: "handlers/elbv2 (cross-domain)", Reason: "legacy ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "handlers/elbv2", To: "domains/iam", Debt: "IAM implementation consumers in STS, Ochre and ELBv2", Reason: "system-instance role ensurer; needs an IAM capability"},
|
||||
{From: "handlers/elbv2", To: "lbagent", Debt: "handlers/elbv2/{agent_types,haproxy,health_checker,nginx}.go import lbagent", Reason: "health-report and cert/PID-path vocabulary; needs a guest/controller contract"},
|
||||
{From: "domains/elbv2", To: "domains/acm", Debt: "domains/elbv2 (cross-domain)", Reason: "ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "domains/elbv2", To: "domains/dns", Debt: "domains/elbv2 (cross-domain)", Reason: "ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "domains/elbv2", To: "domains/ec2/systeminstance", Debt: "domains/elbv2 (cross-domain)", Reason: "ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "domains/elbv2", To: "domains/ec2/vpc", Debt: "domains/elbv2 (cross-domain)", Reason: "ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "domains/elbv2", To: "domains/network/topology", Debt: "domains/elbv2 (cross-domain)", Reason: "ELBv2 cross-domain import; clears when ELBv2 is extracted behind consumer-owned capabilities"},
|
||||
{From: "domains/elbv2", To: "domains/iam", Debt: "IAM implementation consumers in STS, Ochre and ELBv2", Reason: "system-instance role ensurer; needs an IAM capability"},
|
||||
{From: "domains/elbv2", To: "agents/elbv2", Debt: "domains/elbv2/{agent_types,haproxy,health_checker,nginx}.go import lbagent", Reason: "health-report and cert/PID-path vocabulary; needs a guest/controller contract"},
|
||||
|
||||
{From: "domains/rds/instance", To: "domains/dns", Debt: "close-out: handlers/rds (ADR-0004 blocked-dependency list; inventory-rds boundary imports)", Reason: "legacy RDS calls another domain's or runtime implementation directly"},
|
||||
{From: "domains/rds/instance", To: "domains/ec2/instance", Debt: "close-out: handlers/rds (ADR-0004 blocked-dependency list; inventory-rds boundary imports)", Reason: "legacy RDS calls another domain's or runtime implementation directly"},
|
||||
@@ -160,7 +159,5 @@ var allowlist = []allowance{
|
||||
{From: "domains/sts", To: "domains/iam", Debt: "IAM implementation consumers in STS, Ochre and ELBv2", Reason: "STS reads IAM service, trust-policy and OIDC-provider state; needs an IAM capability"},
|
||||
|
||||
{From: "operator/cli", To: "accountteardown", Debt: "operator/cli (dependency)", Reason: "command tree imports implementations; needs public capabilities"},
|
||||
{From: "operator/cli", To: "daemon", Debt: "operator/cli (dependency)", Reason: "command tree imports implementations; needs public capabilities"},
|
||||
{From: "operator/cli", To: "gateway", Debt: "operator/cli (dependency)", Reason: "command tree imports implementations; needs public capabilities"},
|
||||
{From: "operator/cli", To: "gateway/bedrock", Debt: "operator/cli (dependency)", Reason: "command tree imports implementations; needs public capabilities"},
|
||||
}
|
||||
|
||||
@@ -191,3 +191,25 @@ func TestADR0001_LayeringCheckerDetectsFaults(t *testing.T) {
|
||||
t.Errorf("allow-listed cross-domain edge %s was not detected by R3, so the allowlist entry would read as stale", allowedEdge)
|
||||
}
|
||||
}
|
||||
|
||||
// TestADR0001_InterimDaemonDirectionCoversRuntimeDaemon: the daemon left its
|
||||
// legacy root, so gateway importing runtime/daemon must still be rejected, and
|
||||
// the former handlers packages are held to it by R5 at their domain homes.
|
||||
func TestADR0001_InterimDaemonDirectionCoversRuntimeDaemon(t *testing.T) {
|
||||
daemon, _ := classify("spinifex/runtime/daemon")
|
||||
checks := []struct{ rule, from string }{
|
||||
{"Interim_DaemonGatewayHandlersDirection", "spinifex/gateway"},
|
||||
{"R5_DomainImportsNoRuntimeOrOperator", "spinifex/domains/ecs"},
|
||||
}
|
||||
for _, c := range checks {
|
||||
r := ruleByID(c.rule)
|
||||
f, _ := classify(c.from)
|
||||
if !r.subject(f, c.from) || !r.forbids(f, daemon, c.from) {
|
||||
t.Errorf("%s -> runtime/daemon is not rejected by %s", c.from, c.rule)
|
||||
}
|
||||
}
|
||||
other, _ := classify("spinifex/runtime/host/process")
|
||||
if gw, _ := classify("spinifex/gateway"); ruleByID("Interim_DaemonGatewayHandlersDirection").forbids(gw, other, "spinifex/gateway") {
|
||||
t.Error("gateway -> runtime/host/process is rejected, but it is not the daemon")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ type class struct {
|
||||
legacy string // legacy root name when layer is layerLegacy
|
||||
domain string // owning domain for domain-shaped and agent packages
|
||||
kind domainKind
|
||||
daemon bool // runtime/daemon, the interim daemon -> gateway -> handlers root
|
||||
}
|
||||
|
||||
func (c class) domainShaped() bool { return c.kind != notDomain }
|
||||
@@ -59,8 +60,8 @@ func (c class) isLegacy(roots ...string) bool {
|
||||
|
||||
// legacyRoots are pre-ADR-0001 application roots still standing.
|
||||
var legacyRoots = map[string]bool{
|
||||
"handlers": true, "gateway": true, "daemon": true,
|
||||
"accountteardown": true, "lbagent": true,
|
||||
"gateway": true,
|
||||
"accountteardown": true,
|
||||
}
|
||||
|
||||
// gatewayDomains names the domain each gateway/<service> adapter belongs to;
|
||||
@@ -70,12 +71,8 @@ var gatewayDomains = map[string]string{
|
||||
"rds": "rds", "sts": "sts",
|
||||
}
|
||||
|
||||
// runtimeLegacy and operatorLegacy are the legacy roots whose ADR-0001 target
|
||||
// is runtime/ and operator/ respectively.
|
||||
var (
|
||||
runtimeLegacy = []string{"daemon"}
|
||||
operatorLegacy = []string{"accountteardown"}
|
||||
)
|
||||
// operatorLegacy are the legacy roots whose ADR-0001 target is operator/.
|
||||
var operatorLegacy = []string{"accountteardown"}
|
||||
|
||||
// classify maps a module-relative package directory to its layer. ok is false
|
||||
// for a package that has no deliberate classification.
|
||||
@@ -112,7 +109,7 @@ func classifyApp(app []string) (class, bool) {
|
||||
case "ingress":
|
||||
return class{layer: layerIngress}, true
|
||||
case "runtime":
|
||||
return class{layer: layerRuntime}, true
|
||||
return class{layer: layerRuntime, daemon: len(app) >= 2 && app[1] == "daemon"}, true
|
||||
case "operator":
|
||||
return class{layer: layerOperator}, true
|
||||
case "providers":
|
||||
|
||||
@@ -11,20 +11,15 @@ import (
|
||||
// completed move whose line must be deleted.
|
||||
var frozenLegacyPackages = []string{
|
||||
"accountteardown",
|
||||
"daemon",
|
||||
"gateway",
|
||||
"gateway/bedrock",
|
||||
"gateway/ecs",
|
||||
"gateway/elbv2",
|
||||
"gateway/spx",
|
||||
"handlers/ecs",
|
||||
"handlers/elbv2",
|
||||
"lbagent",
|
||||
}
|
||||
|
||||
// retiredLegacyRoots were legacy roots whose every package has moved to its
|
||||
// ADR-0001 target; recreating one would reopen a closed migration row.
|
||||
var retiredLegacyRoots = []string{"admin", "services", "utils", "vpcd"}
|
||||
var retiredLegacyRoots = []string{"admin", "daemon", "handlers", "lbagent", "services", "utils", "vpcd"}
|
||||
|
||||
// TestADR0001_LegacyRootsFrozen: the current-to-target map gives every legacy
|
||||
// root a destination, so none may gain a package and no retired root may return.
|
||||
|
||||
@@ -73,7 +73,7 @@ var rules = []rule{
|
||||
subject: func(f class, _ string) bool { return f.domainShaped() },
|
||||
forbids: func(f, t class, _ string) bool {
|
||||
return f.domainShaped() && (isLayer(t, layerRuntime, layerOperator) ||
|
||||
t.isLegacy(runtimeLegacy...) || t.isLegacy(operatorLegacy...))
|
||||
t.isLegacy(operatorLegacy...))
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -121,7 +121,7 @@ var rules = []rule{
|
||||
return false
|
||||
}
|
||||
switch {
|
||||
case t.layer == layerAgents || t.isLegacy("lbagent"):
|
||||
case t.layer == layerAgents:
|
||||
return true
|
||||
case t.layer == layerIngress:
|
||||
return f.kind != domainAWSAPI && f.kind != legacyAdapter
|
||||
@@ -137,9 +137,9 @@ var rules = []rule{
|
||||
id: "Agents_ImportNoDomainOrLegacyImplementation",
|
||||
clause: "area table: agents/<domain> own in-guest and service-role behaviour, not tenant API authority or another domain's private state",
|
||||
hint: "move the shared wire vocabulary behind a guest/controller contract under contracts/",
|
||||
subject: func(f class, _ string) bool { return f.layer == layerAgents || f.isLegacy("lbagent") },
|
||||
subject: func(f class, _ string) bool { return f.layer == layerAgents },
|
||||
forbids: func(f, t class, _ string) bool {
|
||||
return (f.layer == layerAgents || f.isLegacy("lbagent")) && (t.domainShaped() || t.isLegacy())
|
||||
return f.layer == layerAgents && (t.domainShaped() || t.isLegacy())
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -160,9 +160,9 @@ var rules = []rule{
|
||||
forbids: func(f, t class, _ string) bool {
|
||||
switch {
|
||||
case f.isLegacy("handlers"):
|
||||
return t.isLegacy("gateway", "daemon")
|
||||
return t.isLegacy("gateway") || t.daemon
|
||||
case f.isLegacy("gateway"):
|
||||
return t.isLegacy("daemon")
|
||||
return t.daemon
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"syscall"
|
||||
|
||||
"github.com/mulgadc/bluebottle/pkg/otelsetup"
|
||||
"github.com/mulgadc/spinifex/spinifex/lbagent"
|
||||
lbagent "github.com/mulgadc/spinifex/spinifex/agents/elbv2"
|
||||
|
||||
_ "github.com/mulgadc/bluebottle/pkg/fipsboot"
|
||||
)
|
||||
|
||||
@@ -17,7 +17,7 @@ func TestVolumeAbandonSubject_BothForms(t *testing.T) {
|
||||
assert.Equal(t, "ebs.abandon", viperblocklegacyv1.VolumeAbandonSubject(""))
|
||||
}
|
||||
|
||||
// TestVolumeAbandonRequest_JSON pins the request daemon/vm_adapters.go sends
|
||||
// TestVolumeAbandonRequest_JSON pins the request runtime/daemon/vm_adapters.go sends
|
||||
// on the abandon route.
|
||||
func TestVolumeAbandonRequest_JSON(t *testing.T) {
|
||||
golden := `{"volume":"vol-1","reason":"instance owned by another node"}`
|
||||
|
||||
+36
-53
@@ -50,14 +50,12 @@ Request routing (`spinifex/gateway/gateway.go`):
|
||||
1. **Authentication**: SigV4 middleware validates AWS credentials and resolves the account ID
|
||||
2. **Throttling**: Per-account+action token bucket rejects bursts post-auth
|
||||
3. **Service Detection**: Extracts service name from the Authorization header
|
||||
4. **Action Dispatch**: Routes to service-specific handler. A service registered through the ingress dispatch registry (`ingress/aws/dispatch`, wired by `runtime/roles/awsgw`) is dispatched by its registration; currently that is ECR's control plane, registered from `domains/ecr/awsapi`. Every other service uses the legacy switch:
|
||||
4. **Action Dispatch**: Routes to service-specific handler. A service registered through the ingress dispatch registry (`ingress/aws/dispatch`, wired by `runtime/roles/awsgw`) is dispatched by its registration; currently those are the ECR and ACM control planes, registered from `domains/ecr/awsapi` and `domains/acm/awsapi`. Every other service uses the legacy switch:
|
||||
|
||||
```go
|
||||
switch svc {
|
||||
case "ec2":
|
||||
err = gw.EC2_Request(w, r)
|
||||
case "account":
|
||||
err = gw.Account_Request(w, r)
|
||||
case "iam":
|
||||
err = gw.IAM_Request(w, r)
|
||||
case "sts":
|
||||
@@ -66,10 +64,13 @@ case "elasticloadbalancing":
|
||||
err = gw.ELBv2_Request(w, r)
|
||||
case "eks":
|
||||
err = gw.EKS_Request(w, r)
|
||||
case "bedrock":
|
||||
err = gw.Bedrock_Request(w, r)
|
||||
// ... bedrock-runtime, bedrock-agent, bedrock-agent-runtime
|
||||
case "ecs":
|
||||
err = gw.ECS_Request(w, r)
|
||||
case "acm":
|
||||
err = gw.ACM_Request(w, r)
|
||||
case "rds":
|
||||
err = gw.RDS_Request(w, r)
|
||||
case "tagging":
|
||||
err = gw.Tagging_Request(w, r)
|
||||
case "spinifex":
|
||||
@@ -79,14 +80,15 @@ case "spinifex":
|
||||
|
||||
### 3. EC2 Handler
|
||||
|
||||
The EC2 handler (`spinifex/gateway/ec2.go`) parses the `Action` parameter and delegates to specific handlers via a generic `ec2Handler` wrapper. The account ID resolved by SigV4 auth is threaded into every handler so requests are scoped per IAM principal:
|
||||
The EC2 handler (`spinifex/gateway/ec2.go`) parses the `Action` parameter and delegates to specific handlers via the generic `ec2Handler` and `ec2HandlerWithReq` wrappers. The account ID resolved by SigV4 auth is threaded into every handler so requests are scoped per IAM principal:
|
||||
|
||||
```go
|
||||
"RunInstances": ec2Handler(func(input *ec2.RunInstancesInput, gw *GatewayConfig, accountID string) (any, error) {
|
||||
return ec2instanceapi.RunInstances(input, gw.NATSConn, accountID)
|
||||
"RunInstances": ec2HandlerWithReq(func(ctx context.Context, input *ec2.RunInstancesInput, gw *GatewayConfig, accountID string, r *http.Request) (any, error) {
|
||||
// ... PassRole and launch-quota checks, then the vCPU quota charge
|
||||
return ec2instanceapi.RunInstances(ctx, input, gw.NATSConn, gw.IAMService, accountID, passRoleCheck, launchQuotaCheck, gw.ExpectedNodes)
|
||||
}),
|
||||
"DescribeInstances": ec2Handler(func(input *ec2.DescribeInstancesInput, gw *GatewayConfig, accountID string) (any, error) {
|
||||
return ec2instanceapi.DescribeInstances(input, gw.NATSConn, gw.DiscoverActiveNodes(), accountID)
|
||||
"DescribeInstances": ec2Handler(func(ctx context.Context, input *ec2.DescribeInstancesInput, gw *GatewayConfig, accountID string) (any, error) {
|
||||
return ec2instanceapi.DescribeInstancesChecked(ctx, input, gw.NATSConn, 0, gw.NodeIDs, accountID)
|
||||
}),
|
||||
// ... + volumes, snapshots, VPCs, subnets, route tables, IGWs, NAT gateways,
|
||||
// security groups, network interfaces, elastic IPs, placement groups, key pairs,
|
||||
@@ -98,13 +100,13 @@ The EC2 handler (`spinifex/gateway/ec2.go`) parses the `Action` parameter and de
|
||||
The gateway communicates with daemons via NATS request/response. Most calls go through `natsmsg.NATSRequest`, which marshals the input, attaches the account ID as a NATS header, and unmarshals the typed response:
|
||||
|
||||
```go
|
||||
func (s *NATSInstanceService) RunInstances(input *ec2.RunInstancesInput, accountID string) (*ec2.Reservation, error) {
|
||||
func (s *NATSInstanceService) RunInstances(ctx context.Context, input *ec2.RunInstancesInput, accountID string) (*ec2.Reservation, error) {
|
||||
topic := fmt.Sprintf("ec2.RunInstances.%s", aws.StringValue(input.InstanceType))
|
||||
return natsmsg.NATSRequest[ec2.Reservation](s.natsConn, topic, input, 5*time.Minute, accountID)
|
||||
return natsmsg.NATSRequest[ec2.Reservation](ctx, s.natsConn, topic, input, 5*time.Minute, accountID)
|
||||
}
|
||||
```
|
||||
|
||||
`RunInstances` uses a per-instance-type subject so NATS only delivers the request to a node with spare capacity for that type — no application-level reject-and-retry.
|
||||
The gateway's `RunInstances` places instances itself (`domains/ec2/awsapi/instance/placement.go`): it gathers capacity from every node on `spinifex.node.status`, spreads the launch across eligible nodes, and sends each node its share on the node-targeted subject `ec2.RunInstances.{type}.{nodeId}`, rolling back on partial failure.
|
||||
|
||||
### Cross-Process Contract Ownership
|
||||
|
||||
@@ -124,14 +126,14 @@ refactor. It needs a versioned successor or an explicit compatibility plan.
|
||||
|
||||
### 5. Daemon Processing
|
||||
|
||||
Daemons (`spinifex/daemon/daemon.go`) subscribe to NATS topics and handle requests. A table-driven `subscribeAll()` registers the static EC2/ELBv2 surface at startup:
|
||||
Daemons (`spinifex/runtime/daemon/daemon.go`) subscribe to NATS topics and handle requests. A table-driven `subscribeAll()` registers the static EC2/ELBv2 surface at startup:
|
||||
|
||||
```go
|
||||
// Queue group → load-balanced (one daemon handles each request)
|
||||
{"ec2.CreateKeyPair", d.handleEC2CreateKeyPair, "spinifex-workers"},
|
||||
{"ec2.CreateKeyPair", handleNATSRequest(d.node, d.keyService.CreateKeyPair), "spinifex-workers"},
|
||||
|
||||
// No queue group → fan-out (every daemon responds)
|
||||
{"ec2.DescribeInstanceTypes", d.handleEC2DescribeInstanceTypes, ""},
|
||||
{"ec2.DescribeInstanceTypes", handleNATSRequest(d.node, d.instanceService.DescribeInstanceTypes), ""},
|
||||
```
|
||||
|
||||
`ec2.RunInstances` is dynamic: `ResourceManager` subscribes to `ec2.RunInstances.{type}` only while the node has capacity for that type, and unsubscribes when full. The node-targeted subject `ec2.RunInstances.{type}.{nodeId}` stays subscribed even at full capacity — the launch-time `allocate()` enforces limits there. Per-instance commands flow over `ec2.cmd.{instanceID}`, subscribed only by the owning node.
|
||||
@@ -144,20 +146,17 @@ When a daemon handles `RunInstances`:
|
||||
|
||||
1. **Resource Check**: Validates CPU/memory availability via `ResourceManager`
|
||||
2. **Volume Generation**: Creates boot and EFI volumes via Viperblock
|
||||
3. **Volume Mount**: Sends `ebs.mount` request to Viperblock, receives NBD URI
|
||||
3. **Volume Mount**: Sends a mount request to the colocated Viperblock worker on `ebs.{node}.mount`, receives NBD URI
|
||||
4. **QEMU Launch**: Builds and executes QEMU command with NBD-backed disks
|
||||
5. **QMP Monitoring**: Establishes QEMU Machine Protocol connection for VM management
|
||||
6. **Command subscription**: Subscribes to `ec2.cmd.{instanceID}` for subsequent commands on this VM
|
||||
|
||||
```go
|
||||
// Volume mounting via NATS
|
||||
msg, err := d.natsConn.Request("ebs.mount", ebsMountRequest, 10*time.Second)
|
||||
// Volume mounting via NATS (runtime/daemon/vm_adapters.go)
|
||||
reply, err := ebsRequestWithTrace(ctx, a.nc, instance.AccountID, viperblocklegacyv1.MountSubject(a.node), ebsMountRequest, 30*time.Second)
|
||||
|
||||
// QEMU launch with NBD storage
|
||||
cmd := exec.Command("qemu-system-x86_64",
|
||||
"-drive", fmt.Sprintf("file=nbd:%s,format=raw", nbdURI),
|
||||
// ... additional QEMU args
|
||||
)
|
||||
// QEMU launch with NBD-backed drives in args (runtime/compute/vm/vm.go)
|
||||
cmd := exec.Command(qemuArchitecture, args...) // qemu-system-x86_64 or qemu-system-aarch64
|
||||
```
|
||||
|
||||
## Key Components
|
||||
@@ -199,33 +198,17 @@ type ResourceManager struct {
|
||||
|
||||
### Multi-Node Aggregation
|
||||
|
||||
For operations that need data from all nodes (like `DescribeInstances`), the gateway uses inbox-based fan-out (`spinifex/domains/ec2/awsapi/instance/DescribeInstances.go`):
|
||||
For operations that need data from all nodes (like `DescribeInstances`), the gateway uses the scatter-gather helper `natsmsg.Gather` (`spinifex/foundation/messaging/nats/nats.go`), called from `spinifex/domains/ec2/awsapi/instance/DescribeInstances.go`:
|
||||
|
||||
```go
|
||||
func DescribeInstances(...) {
|
||||
// Create unique inbox for collecting responses
|
||||
inbox := nats.NewInbox()
|
||||
sub, _ := natsConn.SubscribeSync(inbox)
|
||||
|
||||
// Publish to all nodes (no queue group = all daemons respond).
|
||||
// Account ID is propagated as a NATS header so each daemon scopes its response.
|
||||
pubMsg := nats.NewMsg("ec2.DescribeInstances")
|
||||
pubMsg.Reply = inbox
|
||||
pubMsg.Data = jsonData
|
||||
pubMsg.Header.Set(natsmsg.AccountIDHeader, accountID)
|
||||
_ = natsConn.PublishMsg(pubMsg)
|
||||
|
||||
// Collect responses, returning early once expectedNodes have replied
|
||||
// or the 3s timeout is hit.
|
||||
for {
|
||||
msg, err := sub.NextMsg(3 * time.Second)
|
||||
allReservations = append(allReservations, nodeOutput.Reservations...)
|
||||
}
|
||||
|
||||
return allReservations
|
||||
}
|
||||
// Publish to every node (no queue group = all daemons respond) and collect
|
||||
// the replies. The account ID travels as a NATS header so each daemon scopes
|
||||
// its response.
|
||||
frames, sum, err := natsmsg.Gather(ctx, natsConn, "ec2.DescribeInstances", jsonData, gatherOpts)
|
||||
```
|
||||
|
||||
Given the configured node IDs, `Gather` checks each reply's `X-Node-ID` header against them, and `DescribeInstancesChecked` fails closed rather than returning a partial listing when a node is missing, duplicated or unidentified.
|
||||
|
||||
## Storage Integration
|
||||
|
||||
### Viperblock (EBS)
|
||||
@@ -240,6 +223,7 @@ type EBSRequest struct {
|
||||
Boot bool // Boot volume flag
|
||||
EFI bool // EFI boot volume
|
||||
NBDURI string // NBD URI returned from mount
|
||||
// ... + DeviceName, DeleteOnTermination, hotplug port and host block-device fields
|
||||
}
|
||||
```
|
||||
|
||||
@@ -298,6 +282,7 @@ type Config struct {
|
||||
Predastore PredastoreConfig // S3 endpoint + service credentials
|
||||
Viperblock ViperblockConfig
|
||||
VPCD VPCDConfig // OVN/OVS endpoints
|
||||
// ... + EBS, Northstar, RDS, ACM, Bedrock, OchreVector
|
||||
}
|
||||
```
|
||||
|
||||
@@ -320,15 +305,13 @@ spinifex/
|
||||
│ ├── ingress/ # AWS protocol ingress: query/REST parsing, dispatch registry, rate limiting
|
||||
│ ├── runtime/
|
||||
│ │ ├── roles/ # Long-running service roles (awsgw, spinifex, vpcd, nats, predastore, ...)
|
||||
│ │ ├── daemon/ # Per-node daemon and its NATS subscription table
|
||||
│ │ └── compute/ # VM lifecycle, QMP, NBD, GPU
|
||||
│ ├── bootstrap/ # Cluster and node configuration structures, preflight checks
|
||||
│ ├── operator/ # spx CLI commands, admin use cases, installer, upgrade
|
||||
│ ├── agents/ # In-guest agent libraries (ecs, eks, rds)
|
||||
│ ├── agents/ # In-guest agent libraries (ecs, eks, elbv2, rds)
|
||||
│ ├── gateway/ # Legacy root: HTTP route assembly, SigV4 auth, legacy per-service dispatch
|
||||
│ ├── handlers/ # Legacy root: ecs, eks, elbv2, iam, rds, sts service implementations
|
||||
│ ├── daemon/ # Legacy root: per-node daemon and its NATS subscription table
|
||||
│ ├── accountteardown/ # Legacy root: account deletion cleanup
|
||||
│ └── lbagent/ # Legacy root: load-balancer agent
|
||||
│ └── accountteardown/ # Legacy root: account deletion cleanup
|
||||
├── internal/ # In-guest SigV4 clients, AWS Smithy models and test kits
|
||||
├── architecture/ # Executable architecture rules (package layering)
|
||||
├── build/ # systemd units, node helper scripts, microVM build
|
||||
@@ -338,7 +321,7 @@ spinifex/
|
||||
└── e2e/ # Live-cluster suites (see docs/testing/e2e-coverage.md)
|
||||
```
|
||||
|
||||
The five legacy roots predate ADR-0001 and are frozen: they may shrink but not gain packages.
|
||||
The two remaining legacy roots predate ADR-0001 and are frozen: they may shrink but not gain packages.
|
||||
[`PACKAGE_BOUNDARY_MIGRATION.md`](PACKAGE_BOUNDARY_MIGRATION.md) records what remains in each and what blocks its move.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -345,6 +345,11 @@ while the branch is in flight; they do not describe the umbrella repository's
|
||||
| `spinifex#1251` | `spinifex/handlers/rds` | `spinifex/domains/rds/instance` (`instance`, imported as `rdsinstance`) | A mechanical relocation of the remaining RDS instance resource to its ADR-0001 resource package; APIs, NATS subjects, KV buckets and behaviour are unchanged and only the package name changed from `handlers_rds`. The frozen-legacy entry is removed and the allowlist entries naming `handlers/rds` now name `domains/rds/instance`. |
|
||||
| `spinifex#1264` | `spinifex/handlers/eks` | `spinifex/domains/eks/cluster` (`cluster`, imported as `ekscluster`) | A mechanical relocation of the remaining EKS implementation, nodegroups included; APIs, NATS subjects, KV buckets and behaviour are unchanged and only the package name changed from `handlers_eks`. The frozen-legacy entry is removed, the allowlist entries naming `handlers/eks` now name `domains/eks/cluster`, and the `operator/cli` entry is removed because an operator importing a domain package no longer breaks the legacy rule. Nodegroup service extraction is deferred. |
|
||||
| `spinifex#1265` | `spinifex/gateway/{iam,sts,eks,rds}` | `spinifex/domains/{iam,sts,eks,rds}/awsapi` (`awsapi`, imported as `iamawsapi`, `stsawsapi`, `eksawsapi`, `rdsawsapi`) | A mechanical relocation of the IAM, STS, EKS and RDS AWS action adapters to their ADR-0001 domain homes; APIs, NATS subjects, KV buckets and behaviour are unchanged. The root `gateway` dispatch still imports each package, recorded as `gateway` (service registration) debt. `gateway/bedrock` stays: ADR-0001 moves only its action adapters, and the package also holds the Ochre domain core, stores and inference engine. |
|
||||
| `spinifex#1266` | `spinifex/gateway/bedrock` | `spinifex/domains/ochre` (merged into package `ochre`) | A mechanical whole-package relocation: the Ochre domain core, stores, inference engine and Bedrock action layer join the existing `domains/ochre` package with their package-private code and tests intact. No identifier, API, NATS subject, KV bucket, metric, error string or behaviour changes; `domains/ochre` no longer imports a legacy package. Separating the action adapters into `domains/ochre/awsapi` is deferred, recorded as `domains/ochre` (action adapters) debt. |
|
||||
| `spinifex#1267` | `spinifex/daemon` | `spinifex/runtime/daemon` | A mechanical whole-package relocation under the 2026-10-26 ADR-0001 amendment: the per-node daemon keeps package name `daemon`, its files, tests and current dependencies. No identifier, API, NATS subject, KV bucket, metric, error string or behaviour changes. The interim daemon → gateway → handlers guardrail now matches `runtime/daemon`. Q-28's ownership and deployment refactor is deferred, recorded as `runtime/daemon` (Q-28 ownership and deployment) debt. |
|
||||
| `spinifex#1268` | `spinifex/lbagent` | `spinifex/agents/elbv2` (package `elbv2`) | A mechanical whole-package relocation under the 2026-10-26 ADR-0001 amendment: the load-balancer agent keeps its files, tests and current dependencies, and importers alias it `lbagent` so call sites are unchanged. No identifier, API, NATS subject, file path on the guest, metric, error string or behaviour changes; `cmd/lb-agent` stays the thin main. Q-30's data-plane boundary is deferred, recorded as `agents/elbv2` (Q-30 data-plane boundary) debt. |
|
||||
| `spinifex#1269` | `spinifex/handlers/elbv2` | `spinifex/domains/elbv2` (package `elbv2`) | A mechanical whole-package relocation under the 2026-10-26 ADR-0001 amendment: the ELBv2 control plane keeps its files, tests and current dependencies, and importers alias it `elbv2domain`. No identifier, API, NATS subject, KV bucket, metric, error string or behaviour changes. Its allowlist entries are now sourced from `domains/elbv2`. Q-30's data-plane boundary stays deferred, recorded as `agents/elbv2` (Q-30 data-plane boundary) debt. |
|
||||
| `spinifex#1271` | `spinifex/handlers/ecs` | `spinifex/domains/ecs` (package `ecs`) | A mechanical whole-package relocation: ADR-0001's `handlers/ecs` row targets `domains/ecs`, the files are already organised by resource, and resource packages are introduced only where callers need a compiler boundary. The ECS control plane keeps its files, tests and current dependencies alongside the already-extracted `domains/ecs/taskdefinition`, and importers alias it `ecsdomain`. No identifier, API, NATS subject, KV bucket, metric, error string or behaviour changes. Its allowlist entries are now sourced from `domains/ecs`. `handlers` has no package left and is a retired legacy root; R5 holds `domains/ecs` to the interim rule's ban on importing the daemon. |
|
||||
The EC2 contract row is intentionally different from the directory moves: it creates
|
||||
a compatibility boundary. `ec2.cmd.*` retains its deployed one-token NATS
|
||||
subscription and permission shape, while callers use
|
||||
@@ -418,14 +423,15 @@ These residuals were recorded by individual slices and must be resolved once the
|
||||
- The public `/keys` endpoint (root `gateway`, `oidc_discovery.go`) serves malformed stored JWKS bytes with HTTP 200. Removal condition: validate before serving, or record it as accepted.
|
||||
- The public discovery and `/keys` endpoints return 404 for KV read errors, including timeouts; only a bucket-open failure returns 500. Removal condition: distinguish dependency failure from not-found.
|
||||
- The root `gateway` package imports `domains/eks/cluster` directly (`gateway/oidc_discovery.go`: `AccountBucketName`, `GetClusterMeta`). The layering rules don't see it because the root gateway has no domain classification, so it has no allowlist entry. Removal condition: the public OIDC discovery and JWKS endpoints consume an EKS-owned resolver injected at composition, the same pattern as STS, leaving the root gateway with no `domains/eks/cluster` import.
|
||||
- `handlers/elbv2/{agent_types,haproxy,health_checker,nginx}.go` import `lbagent` (`HealthReport`, `ServerStatus`, `CertDir`, `NginxPIDPath`, `NginxCertDir`), a domain reading an agent's implementation package rather than a declared wire contract. Removal condition: the health-report and cert/PID-path vocabulary moves behind a guest/controller contract the domain and the agent both consume.
|
||||
- `domains/ec2/instance/state/imds_readers.go` (dependency, IMDS readers) imports `daemon` for `LocalState`, `ReadLocalState`, `LocalStatePath`, `NewJetStreamManager` and `InstanceStateBucket`. The local-file reader and the instance-record loader's `JetStreamManager` open, which `vpcd/imds_instance_state.go` held directly, moved here unchanged and are now injected into `vpcd` through `guestmetadata.LocalStateReader` and `guestmetadata.RecordLoader`. The readers already live in `domains/ec2/instance/state`. Removal condition: the legacy dependencies they call are extracted from `daemon` — `LocalStatePath`, `ReadLocalState` and `LocalState`, the instance bucket constants, and `JetStreamManager` with its bucket initialisation (`InitKVBucket`) — leaving `domains/ec2/instance/state` with no `daemon` import.
|
||||
- `domains/elbv2/{agent_types,haproxy,health_checker,nginx}.go` import `agents/elbv2` (`HealthReport`, `ServerStatus`, `CertDir`, `NginxPIDPath`, `NginxCertDir`), a domain reading an agent's implementation package rather than a declared wire contract. Removal condition: the health-report and cert/PID-path vocabulary moves behind a guest/controller contract the domain and the agent both consume.
|
||||
- `agents/elbv2` (Q-30 data-plane boundary): the agent moved whole, keeping its current dependencies and the `domains/elbv2` import above. Q-30's data-plane boundary and the common agent layout remain deferred by ADR-0001 and are not paid by the move.
|
||||
- `domains/ec2/instance/state/imds_readers.go` (dependency, IMDS readers) imports `runtime/daemon` for `LocalState`, `ReadLocalState`, `LocalStatePath`, `NewJetStreamManager` and `InstanceStateBucket`. The local-file reader and the instance-record loader's `JetStreamManager` open, which `vpcd/imds_instance_state.go` held directly, moved here unchanged and are now injected into `vpcd` through `guestmetadata.LocalStateReader` and `guestmetadata.RecordLoader`. The readers already live in `domains/ec2/instance/state`. Removal condition: the legacy dependencies they call are extracted from `daemon` — `LocalStatePath`, `ReadLocalState` and `LocalState`, the instance bucket constants, and `JetStreamManager` with its bucket initialisation (`InitKVBucket`) — leaving `domains/ec2/instance/state` with no `daemon` import.
|
||||
- Advisory: 27 production files across `domains/{acm,dns,ec2/*,network/host,network/ovn,ochre}` import `bootstrap/config` directly rather than through typed constructor inputs, which ADR-0001's `config` row expects. `bootstrap/preflight/preflight.go:15` imports `runtime/host/command` and `services.go:13` imports `operator/host/systemd`; both are expected bootstrap-to-runtime/operator edges for startup validation and are not domain-rule violations.
|
||||
- Operator bootstrap credential generation: `operator/cli/admin.go` mints the bootstrap admin and Northstar access keys and secrets directly with `awscredentials` and writes them into config, rather than asking an IAM bootstrap capability to issue them. `awscredentials` only formats the values. Removal condition: a real IAM bootstrap capability issues these credentials and `operator/cli` calls it.
|
||||
- `domains/dns`: imports `runtime/compute/vm` (domain-to-runtime), `domains/network/reconcile` for the reconcile lock and `bootstrap/config` for node configuration.
|
||||
- ACM: the IAM AES-GCM helper import (ACM relocation residual).
|
||||
- `runtime/roles/awsgw`: the quota reconcile loop belongs to `domains/admission/quota`, and the EKS token-verify responder belongs with STS or EKS identity.
|
||||
- `runtime/roles/awsgw` → `handlers/elbv2` (inherited from `gateway/tagging`): this is a new physical import in AWSGW that inherits the former `gateway/tagging` dependency, because the `elbv2Lister` adapter builds its client with `handlers_elbv2.NewNATSELBv2Service` and calls the `ELBv2Service` interface. It is permitted temporarily because AWSGW is the composition root, which may wire legacy packages, so the layering test needs no allowlist row. Removal condition: it must clear when ELBv2 exposes a domain-owned boundary (its NATS client or contract under its domain owner) and the adapter consumes that instead.
|
||||
- `runtime/roles/awsgw` → `domains/elbv2` (inherited from `gateway/tagging`): this is a new physical import in AWSGW that inherits the former `gateway/tagging` dependency, because the `elbv2Lister` adapter builds its client with `elbv2domain.NewNATSELBv2Service` and calls the `ELBv2Service` interface. AWSGW is the composition root, so the layering test needs no allowlist row. Removal condition: it must clear when ELBv2 exposes a domain-owned boundary (its NATS client or contract under its domain owner) and the adapter consumes that instead.
|
||||
- `runtime/roles/vpcd` (placement): the role still mixes process/role supervision with network-controller responsibilities. `lease_reaper.go` (orphaned external-pool DHCP lease sweep), `ovn_controller_watchdog.go` (OVN controller liveness and restart), `host_eip_loop.go` (host-EIP reconcile loop) and `lease_datapath_reconcile.go` (lease datapath reconcile) are periodic network-control loops, not role wiring; `vpcd.go` itself also carries bridge-mode detection and verification, chassis-list parsing, external-CIDR resolution, DHCP manager startup and lease/IP-change handling, and gateway/host-EIP allocator and neighbour flush/prime construction alongside `Service.Start`. The future placement question is whether these belong in `domains/network`. Removal condition: an accepted decision on network-controller placement, and extraction of those loops and helpers behind the role.
|
||||
- ECR control-plane registration: `awsapi.Deps.NATS` is a raw `*nats.Conn`, not a narrow capability. Every NATS-backed `Handler` passes it to `domains/ecr.NewNATSMetaStore`, which calls `natsmsg.NATSRequest`, so narrowing it means changing the exported `Handler` type, every handler and the meta store; it must not be broadened further meanwhile.
|
||||
- ACM control-plane registration: `domains/acm/awsapi.Deps.NATS` (`registration.go`) is likewise a raw `*nats.Conn` carried by every `Handler`, for the same reason; it must not be broadened further meanwhile.
|
||||
@@ -437,17 +443,17 @@ These residuals were recorded by individual slices and must be resolved once the
|
||||
- `foundation/messaging/nats/nats_trace.go` still names its OpenTelemetry tracer `github.com/mulgadc/spinifex/spinifex/utils`. It is an emitted instrumentation-scope name that dashboards may filter on, so renaming it is a telemetry change, not a path fix.
|
||||
- `runtime/compute/cache.New`: two construction-time panics carry a lint exemption pending a constructor that returns an error.
|
||||
- `domains/ec2/systeminstance`: managed-service parent metadata is only the `ManagedBy` tag value.
|
||||
- `gateway/bedrock`: holds the Ochre domain core (model catalogue, provisioned throughput, guardrail, access, credential, usage, price, logging and weights stores), the inference engine (provider, vLLM, Anthropic, embeddings, reranker, admission) and the Bedrock action layer in one package with internal cycles. `domains/ochre` imports it, which is the ADR step 7 Ochre gateway import. Splitting it needs a plan: domain core and stores to `domains/ochre/<resource>`, inference to an Ochre-owned package, and only the action layer to `domains/ochre/awsapi`. The top-level `gateway/bedrock*.go` HTTP handlers additionally wait for the ingress registration seam.
|
||||
- `domains/ochre` (action adapters): since `spinifex#1266` the Bedrock action layer (handlers, action authorization, event-stream framing and the CRUD, catalogue and Converse/Invoke entry points) shares package `ochre` with the domain core, stores and inference engine, and imports `ingress/aws/bodyscope`. Moving it to `domains/ochre/awsapi` is not mechanical: the adapters reach package-private store internals (the guardrail and provisioned-throughput store handles and get/put/update methods), and adapter tests override the package-private model catalogue. It needs core capabilities first, then the move; the top-level `gateway/bedrock*.go` HTTP handlers additionally wait for the ingress registration seam.
|
||||
- `domains/network`: `reconcile/intent.go` builds desired network state by reading EC2's KV buckets and decoding EC2's private VPC, subnet, ENI, route-table, gateway, EIP, NAT and security-group records, and `reconcile/drift.go` watches those buckets. ADR-0001 names this pair explicitly: EC2 and network communicate through a capability/projection or versioned contract, never by one being the other's KV reader. Step 7 fix: an EC2-owned `domains/ec2/projection` exposing network intent with list and watch (it may stay KV-backed, so no data moves); a narrow intent interface declared by `reconcile`; wiring at the composition root; and the duplicated `PurposeEIP`/`PurposeIGWLRP` constants and mirrored `SGRule`/`SGEvent` folded into the projection or `contracts/ec2/v1`. The drift-watch coverage test and reconcile scenario tests are the evidence the projection must keep. `domains/network` also imports `runtime/compute/vm` and `runtime/host/command` (domain-to-runtime).
|
||||
- `domains/ec2/instance`: imports `runtime/compute/vm` and `runtime/compute/gpu` (domain-to-runtime) and `domains/dns` (cross-domain implementation import).
|
||||
- `domains/ec2/instance/state` (dependency): the vpcd IMDS local reader imports `runtime/compute/vm` to return `vm.VM`. Removal condition: that type moves into a domain-owned package that `runtime/compute/vm` consumes (runtime to domain is allowed).
|
||||
- `agents/eks/tokenwebhook`: imports `domains/eks/cluster` for `WebhookTokenReviewResult`, an agent depending on a domain implementation. Temporary: the eventual EKS contract or projection for the token-review reply needs its own design, and no contract is invented to complete the move.
|
||||
- `agents/ecs/agent`: takes its wire types from `contracts/ecs/v1`, and still imports `handlers/ecs` for the poll-assignment and GPU-report inputs and outputs and the instance-type and availability-zone attribute names. Temporary agent-to-domain debt; no contract is created during the relocation.
|
||||
- `agents/ecs/agent`: takes its wire types from `contracts/ecs/v1`, and still imports `domains/ecs` for the poll-assignment and GPU-report inputs and outputs and the instance-type and availability-zone attribute names. Temporary agent-to-domain debt; no contract is created during the relocation.
|
||||
- `agents/rds/agent` (wire contract): imports `domains/rds/instance` for the command, reply, parameter, health and bootstrap types exchanged with the control plane. Temporary agent-to-domain debt; no contract is created during the relocation. The earlier `LookupEngine` dependency on the RDS domain's engine registry has since cleared: `controlPlaneRulesFrom` and both engine constructors now take the agent-declared `EngineCatalog` (see the narrowed entry below), with zero production uses of `LookupEngine` remaining in `agents/rds/agent`.
|
||||
- `operator/installer/firstboot` (security): `Config.InstallCallback` is rendered into `spinifex-firstboot.sh`, which is world-readable (0755). The field is currently unused (`fireInstallCallback` reads `SPINIFEX_INSTALL_CALLBACK` from the environment instead), but any future wiring must not place secrets or tokenised callback URLs in the generated script; sensitive callback credentials should reach first boot through a protected channel such as a 0600 file or a credential store.
|
||||
- `operator/cli` (known security risk): `spx admin join` contacts the formation server with `InsecureSkipVerify`, sends the join token as a bearer credential and receives the cluster CA certificate and key, master key, Viperblock key and shared credentials, so an on-path attacker during formation can capture the token and the cluster root secrets or substitute a CA. This predates the move; lint surfaced it when the tree left `cmd/`. It is accepted for now and carries a line-level gosec annotation stating it is not a false positive. Removal condition: authenticate formation TLS with a pinned key or certificate delivered with the join material, or an out-of-band CA, with tests proving a mismatched server certificate fails before credentials are sent.
|
||||
- `domains/eks/cluster` (potential security risk): `ClusterMeta.ControlPlaneTemplate` (`domains/eks/cluster/service_impl.go`) clears the template's credentials but keeps `OIDCPrivateKeyPEM` and the k3s `JoinToken`, so both are persisted in the `eks-account-{acct}` cluster record, which the gateway also reads. Whether that record is encrypted at rest is unconfirmed; if it is not, a reader of the bucket can forge service-account tokens for the cluster's OIDC issuer and join nodes to it. This predates the refactor and is not fixed in it. Removal condition: the key and token are held by a dedicated secret store (a future KMS or Secrets Manager capability is the likely owner) and the cluster record keeps only a reference, with a test proving the persisted record contains neither secret.
|
||||
- `operator/cli` (dependency): the command tree still imports domain and service implementations directly (`handlers/{iam,eks}`, `domains/admission/quota`, `gateway`, `gateway/bedrock`, `daemon`, `vpcd`, `accountteardown`, the Viperblock adapter and runtime roles). The ADR's `cmd/spinifex` current-to-target map row requires command packages to reach domains through public capabilities; splitting supported use cases into `operator/*` packages is later work.
|
||||
- `operator/cli` (dependency): the command tree still imports domain and service implementations directly (`handlers/{iam,eks}`, `domains/admission/quota`, `gateway`, `domains/ochre`, `vpcd`, `accountteardown`, the Viperblock adapter and runtime roles). The ADR's `cmd/spinifex` current-to-target map row requires command packages to reach domains through public capabilities; splitting supported use cases into `operator/*` packages is later work.
|
||||
- `providers/ebs/viperblock` → `runtime/compute/nbd` and `runtime/host/process` (dependency, moved unchanged from `services/viperblockd`): the adapter formats NBD endpoints and manages nbdkit processes through runtime packages. ADR-0001 does not list providers → runtime as forbidden and the layering test does not enforce it (same category as `providers/ebs/conformance` → `runtime/compute/nbd`), but a provider should not depend on node runtime. Clears when NBD endpoint naming has a provider-facing home (a candidate for `contracts/ebs/v1`) and process supervision is injected by the role. The adapter also imports `providers/objectstore`, a provider-to-provider edge that is allowed.
|
||||
- `gateway/ec2.go` (registration): the EC2 dispatch map stays a `GatewayConfig` method in `gateway` and imports every `domains/ec2/awsapi/*` package; `gateway/ec2_idempotency.go` also imports `domains/ec2/awsapi/idem` (`KVBucket`, `TTL`, `TokenAndParams`) for the dispatch-time idempotency wrapper. ADR-0001 makes the domain own its action registration; moving the map waits on the generic ingress registration seam and must not be done as a package move.
|
||||
- `gateway` (service registration): the root `gateway` dispatch for IAM, STS, EKS and RDS imports `domains/{iam,sts,eks,rds}/awsapi`, which only AWSGW may wire. It clears when each service registers through the ADR-0002 ingress registration seam; as with EC2, moving the dispatch must not be done as a package move.
|
||||
@@ -456,22 +462,23 @@ These residuals were recorded by individual slices and must be resolved once the
|
||||
- `domains/admission/quota` (dependency, moved from `handlers/quota` unchanged): `live.go` imports `gateway/elbv2` and `domains/rds/instance` for its live ELBv2 and RDS counters; `records.go` imports `runtime/compute/vm` for the instance-record vCPU sweep; `live.go`, `vcpu.go` and `storage.go` call `domains/ec2/awsapi/{vpc,eip,volume}` describe adapters to count VPCs, subnets, addresses and volumes, as they did under `gateway/ec2`. The ADR's `handlers/quota` current-to-target map row replaces these with reader capabilities once their final locations exist.
|
||||
- `domains/ec2/awsapi/instance` (dependency): imports `domains/iam` (`IAMService` in `DescribeInstances.go`, `RunInstances.go` and `IamInstanceProfileAssociation.go`; `InstanceProfile` in the latter two), `runtime/compute/cache` (`NodeState`, `NodeUnknown`, `NodeLive`) and `runtime/compute/vm` (`VM`), both in `status_synthesis.go`. The IAM edge needs an IAM capability; the runtime edges are domain-to-runtime and need the instance record and node liveness exposed through an EC2-owned projection.
|
||||
- `domains/ec2/awsapi/spotinstance` (dependency): imports `domains/iam.IAMService` and `domains/admission/quota.Service` in `RequestSpotInstances.go`.
|
||||
- Instance adapter consumers outside EC2: `domains/rds/awsapi/enginecatalog.go`, `daemon/eks_cp_control.go`, `daemon/rds_deps.go`, `domains/admission/quota`'s `vcpu.go` and `gateway/iam.go` (`CountInstanceProfileAssociations`) call `domains/ec2/awsapi/instance` directly; each needs an EC2 capability rather than the AWS action adapter.
|
||||
- Instance adapter consumers outside EC2: `domains/rds/awsapi/enginecatalog.go`, `runtime/daemon/eks_cp_control.go`, `runtime/daemon/rds_deps.go`, `domains/admission/quota`'s `vcpu.go` and `gateway/iam.go` (`CountInstanceProfileAssociations`) call `domains/ec2/awsapi/instance` directly; each needs an EC2 capability rather than the AWS action adapter.
|
||||
- `runtime/daemon` (Q-28 ownership and deployment): the daemon moved whole, keeping its current ownership, deployment and `daemon → gateway → handlers` dependencies. Q-28's write-side consolidation and any daemon decomposition or split remain deferred by ADR-0001 and are not paid by the move.
|
||||
|
||||
Enforcement: the owner decided the layering test is blocking now, with a ratchet. Its allowlist mirrors this debt list exactly, each edge carrying its owner and removal condition; any new unrecorded forbidden edge fails local preflight and CI, and an allowlisted edge that no longer exists must be removed from the list.
|
||||
- `domains/rds/instance` → `domains/rds/subnetgroup` (legacy edges): `instanceReferences` binds `Dependants` by scanning instance records, and `tags.go` writes the group record through its generic tag registry. The first clears when the RDS instance gains its own owner and answers `Dependants` from it. The second clears when tagging invokes a subnet-group-owned tag/untag capability rather than writing the record; moving the tagging adapter to `domains/rds/awsapi` alone does not clear it.
|
||||
- `domains/rds/instance` → `domains/rds/parametergroup` (legacy edges): `parameterGroupReferences` binds `Dependants` by scanning instance records; this clears when the RDS instance gains its own owner and answers `Dependants` from it. `tags.go` writes the meta record through its generic tag registry; this clears when tagging invokes a parameter-group-owned tag mutation capability, and moving the tagging adapter to `domains/rds/awsapi` alone does not clear it. The Modify handler still calls `propagateParameterGroup` after the owner's write; propagation is instance-side realization and stays legacy until it is designed as asynchronous realization (group generation, instance staleness, retry). `parameterGroupValues` returns the persisted `Record` and overrides to instance code; when the instance owner is extracted, replace them with a consumer-owned resolved-parameters projection.
|
||||
- `handlers/ecs` → `domains/ecs/taskdefinition` (legacy edges): `tags.go` reads and writes the revision record through its generic tag registry; this clears when ECS tagging invokes an owner-provided tag mutation capability, and moving `tags.go` to `domains/ecs/awsapi` alone does not clear it. `taskDefinitionResolver` returns the persisted `Record` to task and service launch code; when the task and service owners are extracted, replace the record crossing that boundary with a consumer-owned resolved task-definition (launch-spec) projection. Moving packages has not made the record private.
|
||||
- `domains/ecs/taskdefinition` (deliberate duplication, not debt): private `getJSON`, `putJSON` and `keysWithPrefix` copy the raw-KV helpers in `handlers/ecs` so the extraction keeps ECS's get/put/list error and recovery semantics. Revisit only if a later, tested state abstraction proves it preserves those semantics; do not migrate ECS to `kvstore` merely to remove them.
|
||||
- `domains/ecs` → `domains/ecs/taskdefinition` (owner bypass): `tags.go` reads and writes the revision record through its generic tag registry; this clears when ECS tagging invokes an owner-provided tag mutation capability, and moving `tags.go` to `domains/ecs/awsapi` alone does not clear it. `taskDefinitionResolver` returns the persisted `Record` to task and service launch code; when the task and service owners are extracted, replace the record crossing that boundary with a consumer-owned resolved task-definition (launch-spec) projection. Moving packages has not made the record private.
|
||||
- `domains/ecs/taskdefinition` (deliberate duplication, not debt): private `getJSON`, `putJSON` and `keysWithPrefix` copy the raw-KV helpers in `domains/ecs` so the extraction keeps ECS's get/put/list error and recovery semantics. Revisit only if a later, tested state abstraction proves it preserves those semantics; do not migrate ECS to `kvstore` merely to remove them.
|
||||
- `domains/eks/cluster` → `domains/eks/access` (legacy edges): `ResolveTokenReview` opens the EKS bucket in the gateway process and reads records through the exported `access.Get`; this clears when token review asks the owner across a contract rather than opening the bucket itself. `DeleteClusterPrefix` erases access-entry keys as part of its blanket cluster sweep; this clears when cluster delete invokes an owner-provided removal of the cluster's entries. The owner returns the persisted `Record` to the AWS adapter and token review; replace it with consumer-owned projections when those consumers are extracted.
|
||||
- `domains/eks/cluster` → `domains/eks/addon` (legacy edges): `DeleteClusterPrefix` erases add-on records and manifests in its blanket cluster sweep; this clears when cluster delete invokes an add-on-owned teardown capability. `stageGPUDeviceAddon` is a temporary node-group-to-add-on orchestration edge: best-effort, errors logged and swallowed, never undone on node-group delete; it clears when the node-group owner is extracted and the dependency is designed. `ClusterReconciler` hosts the add-on status subscription; it clears when add-on delivery observation has its own host under the lifecycle correction. Cluster existence is checked in the handler, not through an owner-declared capability; it moves into the owner when cluster admission is defined (today status is ignored). Guest wire types (`AddonStatusReport`, `AddonDeliveryPhase`, `AddonStatusSubject`, `StagedAddonManifest`, `InternalAddonsResponse`) are owned by `contracts/eks/v1`, which `domains/eks/cluster`, `gateway/eks` and `agents/eks/gatewayfetch` import directly.
|
||||
- `domains/eks/cluster` and `domains/eks/awsapi` → `domains/eks/addon` (further edges): the AWS adapter reads the persisted `Record` through `addon.Get`/`addon.List`; this clears when the owner returns a view and the add-on adapter moves to `domains/eks/awsapi`. The guest route reaches the owner through the `handlers_eks` NATS RPC (`ListStagedAddonManifests`); this clears with per-resource commands behind EKS registration. The add-on ARN is derived in both the owner and `domains/eks/awsapi/authz.go`; this clears when authorization obtains the ARN from the owner, which an incarnation suffix would require. The owner's `Manifest`/`Report` beside the `contracts/eks/v1` types is deliberate duplication, not debt.
|
||||
- `agents/rds/agent` (dependency, narrowed): engine metadata now arrives through the agent-declared `EngineCatalog`, but the agent still imports `domains/rds/instance` for the command protocol, wire and record types (`Command*`, `Parameter`, `EngineHealth*`, `BootstrapMode*`) and API output types. This clears when those move behind a guest/controller wire contract.
|
||||
- `domains/rds/awsapi` → `domains/rds/instance` (`InstanceSizing`): the gateway obtains its `Sizing` through the RDS handler package's adapter rather than its own composition root. This clears when the gateway's RDS composition moves to `runtime/roles/awsgw` and builds `Sizing` there.
|
||||
- `domains/admission/quota` → `domains/ec2/instancetypes`: `reconcile.go`, `records.go` and `vcpu.go` read `DefaultVCPUs` to size instances without a recorded vCPU count. This clears when quota receives vCPU sizing through a consumer-owned capability or an EC2 projection.
|
||||
- IAM implementation consumers in STS, Ochre and ELBv2: `domains/sts` imports `domains/iam` across five files (`IAMService`, `SessionRevoker`, `EncryptSecret`/`DecryptSecret`, `AccessKeyStatusActive`, `ValidateTrustPolicyDocument`, `PolicyEffectAllow`/`PolicyEffectDeny`, `IAMAccountBucketName`, `OIDCProviderARN`, `OIDCProviderKey`), not only `oidc_jwks.go`; `domains/sts/awsapi/GetCallerIdentity.go` takes `IAMService`; `gateway/bedrock/credentials.go` uses `EncryptSecret`/`DecryptSecret`; and `handlers/elbv2/{lb_role,service_impl}.go` use `EnsureSystemInstanceProfile` and `SystemInstanceRoleEnsurer`. Each clears when its consumer receives a narrow IAM capability, or a shared crypto boundary for the secret helpers, rather than importing the IAM implementation.
|
||||
- `handlers/elbv2` (cross-domain): `service_impl.go` imports `domains/acm` (`Store`, `NewStore`), `domains/dns` (`ELBChanges`, `ELBName`, `Change`, `ActionUpsert`, `ResolveBaseDomain`), `domains/ec2/vpc` (`VPCServiceImpl`) and `domains/network/topology` (`Port`), and `launcher.go` re-exports `domains/ec2/systeminstance` launch types as aliases. These clear when ELBv2 is extracted and each edge becomes a consumer-owned capability, which ADR-0001 sequences after the ELBv2 data-plane boundary is settled.
|
||||
- `docs/service-interfaces.yaml` (ECS coverage gap, closed): the traced `ecs.bus.*` family and the ECS AWS-API action subjects are both listed. The action subjects are declared as `ecs.*`, the same wildcard convention as `rds.*`: `handlers/ecs/service_nats.go` publishes them from the gateway and `daemon/daemon.go` subscribes to them. Declaring `ecs.bus.*` alone had made `ecs` a managed prefix, so `manifest-lint` failed CI on all 34 undeclared action subjects until this was added.
|
||||
- IAM implementation consumers in STS, Ochre and ELBv2: `domains/sts` imports `domains/iam` across five files (`IAMService`, `SessionRevoker`, `EncryptSecret`/`DecryptSecret`, `AccessKeyStatusActive`, `ValidateTrustPolicyDocument`, `PolicyEffectAllow`/`PolicyEffectDeny`, `IAMAccountBucketName`, `OIDCProviderARN`, `OIDCProviderKey`), not only `oidc_jwks.go`; `domains/sts/awsapi/GetCallerIdentity.go` takes `IAMService`; `domains/ochre/credentials.go` uses `EncryptSecret`/`DecryptSecret`; and `domains/elbv2/{lb_role,service_impl}.go` use `EnsureSystemInstanceProfile` and `SystemInstanceRoleEnsurer`. Each clears when its consumer receives a narrow IAM capability, or a shared crypto boundary for the secret helpers, rather than importing the IAM implementation.
|
||||
- `domains/elbv2` (cross-domain): `service_impl.go` imports `domains/acm` (`Store`, `NewStore`), `domains/dns` (`ELBChanges`, `ELBName`, `Change`, `ActionUpsert`, `ResolveBaseDomain`), `domains/ec2/vpc` (`VPCServiceImpl`) and `domains/network/topology` (`Port`), and `launcher.go` re-exports `domains/ec2/systeminstance` launch types as aliases. These clear when each edge becomes a consumer-owned capability.
|
||||
- `docs/service-interfaces.yaml` (ECS coverage gap, closed): the traced `ecs.bus.*` family and the ECS AWS-API action subjects are both listed. The action subjects are declared as `ecs.*`, the same wildcard convention as `rds.*`: `domains/ecs/service_nats.go` publishes them from the gateway and `runtime/daemon/daemon.go` subscribes to them. Declaring `ecs.bus.*` alone had made `ecs` a managed prefix, so `manifest-lint` failed CI on all 34 undeclared action subjects until this was added.
|
||||
- `contracts/ecs/v1` (dead routes): four of five bus subjects have no producer. The scheduler leader still subscribes to `instance-register`, `instance-heartbeat` and `task-state`, which can never fire, and `assign` has neither producer nor consumer; only `service-reconcile` is live. Removal condition: an owner decision to retire these routes (a successor contract under the compatibility rules) or to adopt them in the agent.
|
||||
|
||||
The layering test enforcing these rules (`architecture/layering`, a module-root repository governance test, not Spinifex product code; run by `make test`, `test-cover` and `test-race`) is blocking, with a ratchet: the debt recorded in this section is narrowly allow-listed by owner and removal condition, and any new, unrecorded forbidden edge fails local preflight and CI. The allow-list mirrors the edges in this section that break an import rule; same-domain legacy edges, placement residuals and security notes are not import-rule violations and have no allow-list entry.
|
||||
@@ -656,40 +663,40 @@ series above; "needs decision" is open.
|
||||
|
||||
| Legacy path | What remains | What blocks it |
|
||||
|---|---|---|
|
||||
| `handlers/iam` | — | Done. The implementation moved to `domains/iam` (package `iam`) in `spinifex#1249` with no API, NATS subject, storage or behaviour change. ADR-0001's row says a shared IAM contract is not inferred from current KV readers, and the move adds none. The cross-domain readers (the EC2 instance and spot-instance AWS adapters, `domains/sts`, `domains/sts/awsapi`, `gateway/bedrock`, the ELBv2 system-instance role path and ECR/Ochre signing-key crypto) still import the implementation and each still needs an IAM capability; that is recorded dependency-rule debt, with its allowlist entries now targeting `domains/iam`, not a prerequisite to relocating IAM's implementation. |
|
||||
| `handlers/iam` | — | Done. The implementation moved to `domains/iam` (package `iam`) in `spinifex#1249` with no API, NATS subject, storage or behaviour change. ADR-0001's row says a shared IAM contract is not inferred from current KV readers, and the move adds none. The cross-domain readers (the EC2 instance and spot-instance AWS adapters, `domains/sts`, `domains/sts/awsapi`, `domains/ochre`, the ELBv2 system-instance role path and ECR/Ochre signing-key crypto) still import the implementation and each still needs an IAM capability; that is recorded dependency-rule debt, with its allowlist entries now targeting `domains/iam`, not a prerequisite to relocating IAM's implementation. |
|
||||
| `handlers/sts` | — | Done. The implementation moved to `domains/sts` (package `sts`) in `spinifex#1250` with no API, NATS subject, storage or behaviour change. ADR-0001's condition, cutting the EKS dependency, was already met through the injected `ClusterJWKSResolver`. `domains/sts` still imports `domains/iam` directly; per-consumer IAM capabilities remain dependency-rule debt, with its allowlist entry now sourced from `domains/sts`. |
|
||||
| `handlers/ecs` | Task, service and capacity resources (`taskdefinition` already extracted to `domains/ecs/taskdefinition`). | Deferred: ADR-0004 blocked-dependency list — `iam:PassRole` in the gateway adapter and EC2/ELBv2/EIP adapters called as internal APIs. |
|
||||
| `handlers/ecs` | — | Done. The ECS control plane moved to `domains/ecs` (package `ecs`) with no API, NATS subject, storage or behaviour change, alongside the already-extracted `domains/ecs/taskdefinition`. The ADR-0004 blocked-dependency list (`iam:PassRole` in the gateway adapter and EC2/ELBv2/EIP adapters called as internal APIs) remains dependency-rule debt, with its allowlist entries now sourced from `domains/ecs`. |
|
||||
| `handlers/eks` | — | Done for the relocation: the remaining EKS implementation moved to `domains/eks/cluster` (package `cluster`) in `spinifex#1264` with no API, NATS subject, storage or behaviour change, alongside the already-extracted `access` and `addon` packages. Deferred follow-on: nodegroup service extraction. The nodegroup operations are methods on the shared `EKSServiceImpl` and use its private state, so moving them to their own resource package needs a nodegroup owner and interfaces, which is decomposition work, not a move. The ADR-0004 blocked-dependency imports (IAM role/instance-profile creation, Bluebottle `auth`, the NATS CA, and EKS identity: token review, OIDC) remain dependency-rule debt, with their allowlist entries now sourced from `domains/eks/cluster`. |
|
||||
| `handlers/rds` | — | Done. The remaining RDS instance resource moved to `domains/rds/instance` (package `instance`) in `spinifex#1251` with no API, NATS subject, storage or behaviour change, fulfilling ADR-0001's organise-by-resource condition alongside the already-extracted subnet group, parameter group and engine catalogue. The ADR-0004 blocked-dependency imports (quota admission, the duplicated expected-node fan-out, the IAM ensurer and the Bluebottle master key) remain dependency-rule debt, with their allowlist entries now sourced from `domains/rds/instance`. |
|
||||
| `handlers/elbv2` | Everything; not yet touched. | Deferred: ADR-0001 sequences this after the ELBv2 data-plane boundary is settled. Needs decision: what evidence establishes that boundary is settled. |
|
||||
| `handlers/elbv2` | — | Done. The implementation moved to `domains/elbv2` (package `elbv2`) with no API, NATS subject, storage or behaviour change. Q-30's data-plane boundary remains deferred debt; the amended ADR-0001 row makes it not a relocation prerequisite. Its cross-domain, IAM and `agents/elbv2` imports remain dependency-rule debt, with their allowlist entries now sourced from `domains/elbv2`. |
|
||||
| `handlers/iam/mock` | — | Done. Moved with the implementation to `domains/iam/mock`. |
|
||||
| `gateway` (root) | Generic HTTP route assembly, SigV4/registry authentication, the policy gate, request-audit middleware, request-context keys, and the legacy per-service dispatch switch for every service not yet registered. | Deferred: the ADR-0002 phases above (auth/IAM and request-context boundary design) gate moving the audit, context-key and legacy-dispatch mechanics. |
|
||||
| `gateway/bedrock` | The Ochre domain core, its stores and the inference engine, together with the Bedrock action layer, in one package with internal cycles. | Deferred: needs the split plan recorded above — domain core and stores to `domains/ochre/<resource>`, inference to an Ochre-owned package, and only the action layer to `domains/ochre/awsapi`. |
|
||||
| `gateway/bedrock` | — | Done. The whole package moved into `domains/ochre` in `spinifex#1266` with its package-private core and tests intact and no behaviour change. Separating the action adapters into `domains/ochre/awsapi` remains deferred, recorded as `domains/ochre` (action adapters) debt above. |
|
||||
| `gateway/{iam,sts,eks,rds}` | — | Done. The action adapters moved to `domains/{iam,sts,eks,rds}/awsapi` in `spinifex#1265` with no API, NATS subject, storage or behaviour change. ADR-0001 does not make ADR-0002 registration a prerequisite for relocating a domain's adapters, as the EC2 adapter moves already showed; the root `gateway` dispatch that still imports them is recorded `gateway` (service registration) debt. |
|
||||
| `gateway/{ecs,elbv2}` | Each service's AWS action adapters and dispatch, not yet registered through ADR-0002. | Deferred: each follows its `handlers/<svc>`, because a new-path `awsapi` cannot import a legacy handler. |
|
||||
| `gateway/ecs` | The ECS AWS action adapters and dispatch, not yet registered through ADR-0002. | Not moved. Its handler is now `domains/ecs`, so the follows-its-handler blocker no longer applies. |
|
||||
| `gateway/elbv2` | The ELBv2 AWS action adapters and dispatch, not yet registered through ADR-0002. | Not moved. Its handler is now `domains/elbv2`, so the follows-its-handler blocker no longer applies. |
|
||||
| `gateway/spx` | `promote_image.go` and `spx.go` dispatch (`storage.go` already consumes `contracts/operator/v1`). | Needs decision: which package owns operator-plane action dispatch registration — an `ingress/aws`-style seam for the operator plane, or `gateway` stays its home. |
|
||||
| `gateway/tagging` | — | Done. `domains/tagging` owns the read-only cross-resource `GetResources` aggregation and its API semantics (`0d830e901`, `aedd0b2c4`); AWSGW composes the owner listers, the EC2 tag index and ELBv2 (`f3545e3ea`); the root `gateway` keeps only the legacy dispatch in `gateway/tagging.go`. The package is removed, and its two allowlist rows and frozen-legacy entry are gone. Owner tag mutation capabilities remain future direction, so the RDS and ECS tag-writing edges above stay open. |
|
||||
| `services/viperblockd` | — | Done. The adapter is `providers/ebs/viperblock` (with the read-only `dirty` reader), the shared vocabulary is `contracts/viperblockd/legacy/v1`, and the role is `runtime/roles/viperblockd`. `services/` is empty. |
|
||||
| `utils` | — | Done and deleted. `ec2_subject.go` went to `contracts/ec2/v1`, the vpcd protocol to `contracts/network/v1` with the typed client in `domains/network/projection`, and the master-key loader to `domains/ec2/ebs/encryption`. |
|
||||
| `vpcd` | — | Done. The IMDS local-file reader and record-space opener moved to `domains/ec2/instance/state` and are injected through `guestmetadata.LocalStateReader`/`RecordLoader` (recorded above), so `vpcd` no longer imports `daemon` directly; its inter-process boundary now exists (`contracts/network/v1`, with `domains/network/subscribers` as the consumer and `domains/network/projection` as the producer client). The package moved unchanged to `runtime/roles/vpcd` (recorded below), and the legacy directory is removed. |
|
||||
| `daemon` | Reserved as `runtime/daemon`'s home only; not split or moved. | Deferred: Q-28's write-side consolidation is a prerequisite ADR-0001 names explicitly. |
|
||||
| `daemon` | `runtime/daemon` (whole-package move, above) | Done. Q-28's ownership and deployment refactor remains deferred debt; the amended ADR-0001 row makes it not a relocation prerequisite. |
|
||||
| `admin` | — | Done and deleted. The operator use cases moved to `operator/admin`, image promotion to `domains/ec2/image`, the bootstrap account identifiers to `foundation/aws/identifiers`, and the credential-format generators to `foundation/aws/credentials` (recorded above). Operator bootstrap's direct credential generation stays recorded as debt. |
|
||||
| `accountteardown` | The whole package, a frozen legacy root. Its direct cleanup calls into `domains/ec2/awsapi/*` action adapters, other domain and handler services, the object-store provider, raw NATS subjects and a raw KV bucket stay as recorded debt. | Assessed for relocation and not moved. ADR-0001's `accountteardown` row requires conversion to domain-owned cleanup capabilities before moving, and ADR-0008 does not authorise a mechanical rename. It remains a legacy root until its direct cleanup calls are converted to domain-owned capabilities; its destination after that conversion is `operator/accountlifecycle`. |
|
||||
| `lbagent` | The whole package; `handlers/elbv2` imports it as a domain-to-agent implementation edge (recorded above). | Deferred: Q-30 reserves `agents/elbv2` as its destination but forbids moving it as an isolated rename ahead of the common-agent design. |
|
||||
| `lbagent` | `agents/elbv2` (whole-package move, above) | Done. Q-30's data-plane boundary remains deferred debt; the amended ADR-0001 row makes it not a relocation prerequisite. The `domains/elbv2` import stays recorded debt. |
|
||||
| `migrate` | Nothing at the old path. | Split, not an exact ADR row: ADR-0001 maps `migrate` to `foundation/state/migrate`, but the package held two responsibilities. The KV migration engine and its tests are in `foundation/state/migrate` (`8cd9cff75`); the config-file and object-store upgrade workflow moved to `operator/upgrade` (`919d5bfa6`) by owner decision. Needs an ADR-0001 map clarification before this row can be claimed as target conformance. |
|
||||
|
||||
### Remaining legacy roots by blocker class
|
||||
|
||||
Five legacy roots remain: `accountteardown`, `daemon`, `gateway`, `handlers` and `lbagent`.
|
||||
Two legacy roots remain: `accountteardown` and `gateway`.
|
||||
The rows above give each path's blocker; this summary groups them so a deferral is not mistaken for unfinished mechanical work.
|
||||
ADR-0001 completion is structural, so none of these is cleared by remediating service lifecycle behaviour.
|
||||
|
||||
| Class | Paths | Status |
|
||||
|---|---|---|
|
||||
| Explicitly deferred by ADR-0001 | `daemon` (Q-28), `lbagent` (Q-30) | Closed for this refactor; the ADR names the prerequisite. |
|
||||
| No missing boundary (candidate moves) | `handlers/ecs/bus` → `contracts/ecs/v1` (`a130f6b8e`); `admin/images_promote.go` → `domains/ec2/image` (`263c58167`); `admin` bootstrap account identifiers → `foundation/aws/identifiers` (`3fdaa54d9`); `admin` credential-format generators → `foundation/aws/credentials` `0368e861a`, retiring `admin` | Done. |
|
||||
| One narrow missing boundary | Done: `handlers/sts` EKS JWKS lookup → `handlers/eks` `ClusterJWKSResolver` (`254d898a3`); `gateway/tagging` → `domains/tagging` with AWSGW-composed listers (`0d830e901`, `f3545e3ea`, `aedd0b2c4`). Open: `gateway/spx` (owner of operator-plane dispatch registration) | Each needs one decision or one capability, then a mechanical move. |
|
||||
| Broader boundary or follows another package | `handlers/{ecs,eks,rds}` (ADR-0004 blocked-dependency lists); `handlers/elbv2` (data-plane boundary evidence); `accountteardown` (domain cleanup capabilities); `gateway` root (ADR-0002 auth and request-context phase); `gateway/{ecs,elbv2}` (each follows its `handlers/<svc>`, because a new-path `awsapi` cannot import a legacy handler); `gateway/bedrock` (the recorded Ochre split plan) | Recorded debt with removal conditions above. |
|
||||
| Broader boundary or follows another package | `accountteardown` (domain cleanup capabilities); `gateway` root (ADR-0002 auth and request-context phase) | Recorded debt with removal conditions above. |
|
||||
|
||||
Per-consumer IAM capabilities are the widest unblocker: they gate STS's IAM reads, Ochre's IAM reads, the ELBv2 role path and the ECS, EKS and RDS ensurers.
|
||||
|
||||
|
||||
@@ -50,13 +50,13 @@ services:
|
||||
# Translates VPC / EBS / S3 operations into vpc.* / ebs.* / s3.* and
|
||||
# forwards to vpcd / viperblockd / predastore.
|
||||
#
|
||||
# Source layout: spinifex/runtime/roles/spinifex (entry), spinifex/daemon
|
||||
# Source layout: spinifex/runtime/roles/spinifex (entry), spinifex/runtime/daemon
|
||||
# (subscribe registration), spinifex/domains/<domain> and the legacy
|
||||
# spinifex/handlers/{ecs,eks,elbv2,iam,rds} packages (per-subject handler
|
||||
# bodies). Selector treats these paths as belonging to this service via
|
||||
# the `additional_paths` field.
|
||||
additional_paths:
|
||||
- spinifex/daemon
|
||||
- spinifex/runtime/daemon
|
||||
- spinifex/domains/ec2/account
|
||||
- spinifex/domains/ec2/eigw
|
||||
- spinifex/domains/ec2/eip
|
||||
@@ -74,10 +74,10 @@ services:
|
||||
- spinifex/domains/ec2/volume
|
||||
- spinifex/domains/ec2/volumestate
|
||||
- spinifex/domains/ec2/vpc
|
||||
- spinifex/handlers/elbv2
|
||||
- spinifex/domains/elbv2
|
||||
- spinifex/domains/acm
|
||||
- spinifex/domains/iam
|
||||
- spinifex/handlers/ecs
|
||||
- spinifex/domains/ecs
|
||||
- spinifex/domains/rds/instance
|
||||
- spinifex/domains/rds/subnetgroup
|
||||
- spinifex/domains/rds/parametergroup
|
||||
@@ -136,7 +136,7 @@ services:
|
||||
- "spinifex.cluster.shutdown.infra"
|
||||
# Operator-plane Predastore topology contract (contracts/operator/v1).
|
||||
- "spinifex.storage.config"
|
||||
# ELBv2 control plane (handlers/elbv2/service_nats.go topic map).
|
||||
# ELBv2 control plane (domains/elbv2/service_nats.go topic map).
|
||||
- "elbv2.CreateLoadBalancer"
|
||||
- "elbv2.DeleteLoadBalancer"
|
||||
- "elbv2.DescribeLoadBalancers"
|
||||
@@ -163,7 +163,7 @@ services:
|
||||
# per-subject list would be a hand-mirror of that file with nothing
|
||||
# checking it.
|
||||
- "rds.*"
|
||||
# ECS control plane (handlers/ecs/service_nats.go, registered in
|
||||
# ECS control plane (domains/ecs/service_nats.go, registered in
|
||||
# daemon.go). One wildcard, as for RDS: every action subject is a literal
|
||||
# in those two files, so a per-subject list would only mirror them.
|
||||
- "ecs.*"
|
||||
@@ -403,7 +403,7 @@ suites:
|
||||
e2e-lb:
|
||||
path: tests/e2e/lb
|
||||
covers: [awsgw, spinifex_daemon, vpcd, viperblockd, predastore]
|
||||
# Exercises full ELBv2 control plane (handlers/elbv2) + data plane
|
||||
# Exercises full ELBv2 control plane (domains/elbv2) + data plane
|
||||
# (LB agent on the system VM). ELBv2 is part of spinifex_daemon in
|
||||
# this manifest; revisit if it splits into a standalone service.
|
||||
|
||||
@@ -436,7 +436,7 @@ suites:
|
||||
path: tests/e2e/bedrock
|
||||
covers: [awsgw]
|
||||
# Ochre (AWS-Bedrock) control plane: static catalog list/get served
|
||||
# straight from awsgw (gateway/bedrock.go, gateway/bedrock/) — no NATS
|
||||
# straight from awsgw (gateway/bedrock.go, domains/ochre/) — no NATS
|
||||
# hop to spinifex_daemon. Converse (Anthropic-direct / self-host vLLM)
|
||||
# subtests are gated on backend availability and skip by default.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ecs"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
// leaves an INACTIVE record that still lists, so a stage waiting for services
|
||||
// to disappear would never drain.
|
||||
func ECSReapers(nc *nats.Conn) []Reaper {
|
||||
return []Reaper{&ecsClusterReaper{svc: handlers_ecs.NewNATSECSService(nc)}}
|
||||
return []Reaper{&ecsClusterReaper{svc: ecsdomain.NewNATSECSService(nc)}}
|
||||
}
|
||||
|
||||
// ecsClusterReaper removes the account's ECS clusters.
|
||||
@@ -27,7 +27,7 @@ func ECSReapers(nc *nats.Conn) []Reaper {
|
||||
// so the instance reaper takes them — but only after this one has stopped the
|
||||
// tasks riding on them, which is why this is registered first.
|
||||
type ecsClusterReaper struct {
|
||||
svc handlers_ecs.ECSService
|
||||
svc ecsdomain.ECSService
|
||||
}
|
||||
|
||||
func (r *ecsClusterReaper) Kind() string { return "ecs-cluster" }
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ecs"
|
||||
"github.com/mulgadc/spinifex/internal/testkit"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -94,7 +94,7 @@ func (c *fakeECS) request(t *testing.T, subject string, into any) {
|
||||
}
|
||||
|
||||
func newECSReaper(nc *nats.Conn) *ecsClusterReaper {
|
||||
return &ecsClusterReaper{svc: handlers_ecs.NewNATSECSService(nc)}
|
||||
return &ecsClusterReaper{svc: ecsdomain.NewNATSECSService(nc)}
|
||||
}
|
||||
|
||||
// The listing is keyed by cluster name, which is the identity ECS takes back
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/elbv2"
|
||||
handlers_elbv2 "github.com/mulgadc/spinifex/spinifex/handlers/elbv2"
|
||||
elbv2domain "github.com/mulgadc/spinifex/spinifex/domains/elbv2"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// groups, so it has to go before either can be deleted. Target groups follow
|
||||
// their balancers because a group still referenced by a listener refuses.
|
||||
func ELBv2Reapers(nc *nats.Conn) []Reaper {
|
||||
svc := handlers_elbv2.NewNATSELBv2Service(nc)
|
||||
svc := elbv2domain.NewNATSELBv2Service(nc)
|
||||
return []Reaper{
|
||||
&loadBalancerReaper{svc: svc},
|
||||
&targetGroupReaper{svc: svc},
|
||||
@@ -23,7 +23,7 @@ func ELBv2Reapers(nc *nats.Conn) []Reaper {
|
||||
}
|
||||
|
||||
type loadBalancerReaper struct {
|
||||
svc handlers_elbv2.ELBv2Service
|
||||
svc elbv2domain.ELBv2Service
|
||||
}
|
||||
|
||||
func (r *loadBalancerReaper) Kind() string { return "load-balancer" }
|
||||
@@ -58,7 +58,7 @@ func (r *loadBalancerReaper) Delete(ctx context.Context, accountID string, resou
|
||||
}
|
||||
|
||||
type targetGroupReaper struct {
|
||||
svc handlers_elbv2.ELBv2Service
|
||||
svc elbv2domain.ELBv2Service
|
||||
}
|
||||
|
||||
func (r *targetGroupReaper) Kind() string { return "target-group" }
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/elbv2"
|
||||
handlers_elbv2 "github.com/mulgadc/spinifex/spinifex/handlers/elbv2"
|
||||
elbv2domain "github.com/mulgadc/spinifex/spinifex/domains/elbv2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
// The embedded interface is nil, so a reaper reaching for anything beyond the
|
||||
// four methods below panics rather than passing.
|
||||
type fakeELBv2 struct {
|
||||
handlers_elbv2.ELBv2Service
|
||||
elbv2domain.ELBv2Service
|
||||
|
||||
loadBalancers []*elbv2.LoadBalancer
|
||||
targetGroups []*elbv2.TargetGroup
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
ecsv1 "github.com/mulgadc/spinifex/contracts/ecs/v1"
|
||||
"github.com/mulgadc/spinifex/internal/ecsgw"
|
||||
"github.com/mulgadc/spinifex/spinifex/agents/ecs/credentials"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
)
|
||||
|
||||
// controlPlane is the agent's channel to the AWS gateway. It replaces the direct
|
||||
@@ -31,7 +31,7 @@ type controlPlane interface {
|
||||
PollAssignments(cluster, instance string, ackAssigns, ackStops []string) ([]ecsv1.Assign, []ecsv1.StopDirective, error)
|
||||
// ReportTaskGPU reports the local ledger's pinned device UUIDs for a task's
|
||||
// containers, so the control plane can populate DescribeTasks gpuIds.
|
||||
ReportTaskGPU(cluster, task string, containers []handlers_ecs.ContainerGPUReport) error
|
||||
ReportTaskGPU(cluster, task string, containers []ecsdomain.ContainerGPUReport) error
|
||||
}
|
||||
|
||||
// gatewayControlPlane implements controlPlane over the SigV4 ECS gateway client.
|
||||
@@ -125,7 +125,7 @@ func (g *gatewayControlPlane) SubmitTaskState(st ecsv1.TaskState) error {
|
||||
}
|
||||
|
||||
func (g *gatewayControlPlane) PollAssignments(cluster, instance string, ackAssigns, ackStops []string) ([]ecsv1.Assign, []ecsv1.StopDirective, error) {
|
||||
in := &handlers_ecs.PollAssignmentsInput{
|
||||
in := &ecsdomain.PollAssignmentsInput{
|
||||
Cluster: cluster,
|
||||
ContainerInstance: instance,
|
||||
AckTaskIDs: ackAssigns,
|
||||
@@ -139,15 +139,15 @@ func (g *gatewayControlPlane) PollAssignments(cluster, instance string, ackAssig
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("poll: %w", err)
|
||||
}
|
||||
var out handlers_ecs.PollAssignmentsOutput
|
||||
var out ecsdomain.PollAssignmentsOutput
|
||||
if err := json.Unmarshal(resp, &out); err != nil {
|
||||
return nil, nil, fmt.Errorf("decode poll: %w", err)
|
||||
}
|
||||
return out.Assignments, out.Stops, nil
|
||||
}
|
||||
|
||||
func (g *gatewayControlPlane) ReportTaskGPU(cluster, task string, containers []handlers_ecs.ContainerGPUReport) error {
|
||||
in := &handlers_ecs.ReportTaskGPUInput{Cluster: cluster, Task: task, Containers: containers}
|
||||
func (g *gatewayControlPlane) ReportTaskGPU(cluster, task string, containers []ecsdomain.ContainerGPUReport) error {
|
||||
in := &ecsdomain.ReportTaskGPUInput{Cluster: cluster, Task: task, Containers: containers}
|
||||
body, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal report-task-gpu: %w", err)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/ecs"
|
||||
"github.com/mulgadc/spinifex/spinifex/agents/ecs/credentials"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
)
|
||||
|
||||
// fixedCredsProvider is a static credentials.CredentialsProvider for tests
|
||||
@@ -101,7 +101,7 @@ func TestGatewayControlPlane_Register_NoGPUOmitsResource(t *testing.T) {
|
||||
}
|
||||
|
||||
// ReportTaskGPU posts the per-container device UUIDs under the ReportTaskGPU
|
||||
// action, decodable as the handlers_ecs internal shape.
|
||||
// action, decodable as the domains/ecs internal shape.
|
||||
func TestGatewayControlPlane_ReportTaskGPU(t *testing.T) {
|
||||
var gotTarget string
|
||||
var body []byte
|
||||
@@ -113,7 +113,7 @@ func TestGatewayControlPlane_ReportTaskGPU(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
cp := newTestGatewayControlPlane(t, srv)
|
||||
err := cp.ReportTaskGPU("web", "t-1", []handlers_ecs.ContainerGPUReport{
|
||||
err := cp.ReportTaskGPU("web", "t-1", []ecsdomain.ContainerGPUReport{
|
||||
{Name: "trainer", GPUIDs: []string{"GPU-aaa"}},
|
||||
})
|
||||
if err != nil {
|
||||
@@ -123,7 +123,7 @@ func TestGatewayControlPlane_ReportTaskGPU(t *testing.T) {
|
||||
t.Fatal("no X-Amz-Target header sent")
|
||||
}
|
||||
|
||||
var in handlers_ecs.ReportTaskGPUInput
|
||||
var in ecsdomain.ReportTaskGPUInput
|
||||
if err := json.Unmarshal(body, &in); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ecs"
|
||||
ecsv1 "github.com/mulgadc/spinifex/contracts/ecs/v1"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
)
|
||||
|
||||
// identity is the container instance's stable identity, assembled at boot from
|
||||
@@ -34,8 +34,8 @@ func instanceAttributes(id identity) []*ecs.Attribute {
|
||||
}
|
||||
attrs = append(attrs, &ecs.Attribute{Name: aws.String(name), Value: aws.String(value)})
|
||||
}
|
||||
add(handlers_ecs.AttrInstanceType, id.InstanceType)
|
||||
add(handlers_ecs.AttrAvailabilityZone, id.AZ)
|
||||
add(ecsdomain.AttrInstanceType, id.InstanceType)
|
||||
add(ecsdomain.AttrAvailabilityZone, id.AZ)
|
||||
return attrs
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
ecsv1 "github.com/mulgadc/spinifex/contracts/ecs/v1"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
)
|
||||
|
||||
// fakeCP is a test double for the gateway control-plane. It records registers,
|
||||
@@ -37,7 +37,7 @@ type fakeCP struct {
|
||||
type gpuReport struct {
|
||||
cluster string
|
||||
task string
|
||||
containers []handlers_ecs.ContainerGPUReport
|
||||
containers []ecsdomain.ContainerGPUReport
|
||||
}
|
||||
|
||||
var _ controlPlane = (*fakeCP)(nil)
|
||||
@@ -93,7 +93,7 @@ func (f *fakeCP) PollAssignments(_, _ string, ackAssigns, ackStops []string) ([]
|
||||
return out, stops, nil
|
||||
}
|
||||
|
||||
func (f *fakeCP) ReportTaskGPU(cluster, task string, containers []handlers_ecs.ContainerGPUReport) error {
|
||||
func (f *fakeCP) ReportTaskGPU(cluster, task string, containers []ecsdomain.ContainerGPUReport) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.gpuReports = append(f.gpuReports, gpuReport{cluster: cluster, task: task, containers: containers})
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
ecsv1 "github.com/mulgadc/spinifex/contracts/ecs/v1"
|
||||
ctrruntime "github.com/mulgadc/spinifex/spinifex/agents/ecs/runtime"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
)
|
||||
|
||||
// defaultStopTimeout is applied when a container carries no explicit
|
||||
@@ -413,10 +413,10 @@ func (a *Agent) reportTaskState(as *ecsv1.Assign, status, reason string, contain
|
||||
// container in the report that has them, so DescribeTasks can surface gpuIds.
|
||||
// A no-op when nothing was pinned (the common non-GPU-task case).
|
||||
func (a *Agent) reportTaskGPUs(taskID string, containers []ecsv1.ContainerStatus) {
|
||||
reports := make([]handlers_ecs.ContainerGPUReport, 0, len(containers))
|
||||
reports := make([]ecsdomain.ContainerGPUReport, 0, len(containers))
|
||||
for _, c := range containers {
|
||||
if len(c.GPUIDs) > 0 {
|
||||
reports = append(reports, handlers_ecs.ContainerGPUReport{Name: c.Name, GPUIDs: c.GPUIDs})
|
||||
reports = append(reports, ecsdomain.ContainerGPUReport{Name: c.Name, GPUIDs: c.GPUIDs})
|
||||
}
|
||||
}
|
||||
if len(reports) == 0 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Package lbagent implements the LB config agent that runs inside load balancer VMs.
|
||||
// Package elbv2 implements the LB config agent that runs inside load balancer VMs.
|
||||
// It polls the AWS gateway for config updates and reports health via heartbeats.
|
||||
// All communication uses SigV4-signed HTTP requests to the gateway.
|
||||
package lbagent
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -57,7 +57,7 @@ const (
|
||||
registrationGrace = 60 * time.Second
|
||||
)
|
||||
|
||||
// Data-plane engine names (duplicated from handlers/elbv2 to avoid an import cycle).
|
||||
// Data-plane engine names (duplicated from domains/elbv2 to avoid an import cycle).
|
||||
const (
|
||||
EngineHAProxy = "haproxy"
|
||||
EngineNginx = "nginx"
|
||||
@@ -1,4 +1,4 @@
|
||||
package lbagent
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"os"
|
||||
@@ -1,4 +1,4 @@
|
||||
package lbagent
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -1,4 +1,4 @@
|
||||
package lbagent
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@@ -1,4 +1,4 @@
|
||||
package lbagent
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package lbagent
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
@@ -1,4 +1,4 @@
|
||||
package lbagent
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"net"
|
||||
@@ -166,7 +166,7 @@ func WithRenewalClock(now func() time.Time) WorkerOption {
|
||||
|
||||
// NewWorker builds a renewal worker over svc. holderID identifies this node
|
||||
// in the per-certificate lease (the daemon passes its node ID, mirroring
|
||||
// handlers_ecs.NewScheduler's holder identity).
|
||||
// ecs.NewScheduler's holder identity).
|
||||
func NewWorker(svc *ACMServiceImpl, holderID string, opts ...WorkerOption) *Worker {
|
||||
w := &Worker{
|
||||
svc: svc,
|
||||
|
||||
@@ -82,7 +82,7 @@ type ACMServiceImpl struct {
|
||||
// SAN) by deriveValidationMode. Wired by the daemon post-construction
|
||||
// (mirroring CertMaterialUpdated below) to domains/dns.HostsZone. A func
|
||||
// field stands in for a direct import of domains/dns, the same pattern
|
||||
// CertMaterialUpdated uses for handlers/elbv2, so this package and its
|
||||
// CertMaterialUpdated uses for domains/elbv2, so this package and its
|
||||
// tests stay decoupled from domains/dns's S3/northstar-config
|
||||
// dependencies. Nil means "northstar hosts nothing" — the safe default for
|
||||
// tests and any deployment that never wires it, so an unwired service can
|
||||
@@ -91,7 +91,7 @@ type ACMServiceImpl struct {
|
||||
|
||||
// CertMaterialUpdated, when set, is invoked after new certificate material
|
||||
// is written under an existing ARN so the caller can re-render every load
|
||||
// balancer that references it (see handlers/elbv2.UpdateStoredConfigForCert).
|
||||
// balancer that references it (see domains/elbv2.UpdateStoredConfigForCert).
|
||||
// Wired by the daemon post-construction to avoid an elbv2 -> acm import
|
||||
// cycle; nil-safe — skipped in tests and whenever ELBv2 isn't wired up.
|
||||
// Fan-out errors are logged, never propagated: a rendering problem on one
|
||||
|
||||
@@ -106,7 +106,7 @@ type CertRecord struct {
|
||||
ImportedAt time.Time `json:"imported_at"`
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
// InUseBy is the set of load balancer ARNs whose listeners currently
|
||||
// reference this certificate. Maintained by handlers/elbv2 as listeners are
|
||||
// reference this certificate. Maintained by domains/elbv2 as listeners are
|
||||
// created, modified and deleted; also surfaces as the public
|
||||
// CertificateDetail.InUseBy field.
|
||||
InUseBy []string `json:"in_use_by,omitempty"`
|
||||
@@ -443,7 +443,7 @@ const maxInUseByCASAttempts = 50
|
||||
// No-op if the certificate does not exist or already lists resourceArn.
|
||||
//
|
||||
// InUseBy is the sole mechanism by which a re-imported certificate reaches
|
||||
// the data plane (see UpdateStoredConfigForCert in handlers/elbv2), so a lost
|
||||
// the data plane (see UpdateStoredConfigForCert in domains/elbv2), so a lost
|
||||
// update here is not a cosmetic race: it silently drops a load balancer from
|
||||
// fan-out, and that load balancer's certificate expires in HAProxy while ACM
|
||||
// still reports it as renewed. Two listeners on different load balancers can
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
)
|
||||
|
||||
// BedrockUsageReader resolves accountID's current-period Bedrock token usage
|
||||
// (input+output, across every model), the counter gateway_bedrock's usage
|
||||
// (input+output, across every model), the counter ochre's usage
|
||||
// consumer maintains from the invocation stream. The
|
||||
// interface is defined here rather than importing gateway_bedrock so the two
|
||||
// packages stay decoupled; gateway_bedrock.UsageStore satisfies it
|
||||
// interface is defined here rather than importing domains/ochre so the two
|
||||
// packages stay decoupled; ochre.UsageStore satisfies it
|
||||
// structurally, and awsgw.go wires the two together at startup.
|
||||
type BedrockUsageReader interface {
|
||||
TokensThisPeriod(ctx context.Context, accountID string) (int64, error)
|
||||
|
||||
@@ -2918,7 +2918,7 @@ func (s *InstanceServiceImpl) deleteInstanceVolumes(ctx context.Context, instanc
|
||||
}
|
||||
// Terminate implies detach: DeleteVolumeOnTerminate clears the stale
|
||||
// AttachedInstance left over from Stop (Unmount deliberately never
|
||||
// clears a Boot volume, daemon/vm_adapters.go) before deleting, so
|
||||
// clears a Boot volume, runtime/daemon/vm_adapters.go) before deleting, so
|
||||
// DeleteVolume's in-use guard does not reject an otherwise-legitimate
|
||||
// delete. The resulting error is surfaced into lastErr, not swallowed.
|
||||
if err := s.volumeDeleter.DeleteVolumeOnTerminate(ctx, ebsRequest.Name, instance.AccountID); err != nil {
|
||||
|
||||
@@ -2034,7 +2034,7 @@ func TestTerminateStoppedInstance_StampsTeardownVolumesFailedOnDeleteError(t *te
|
||||
|
||||
// TestTerminateStoppedInstance_NonDoTBootVolumeDetachedNotDeleted locks the
|
||||
// second half of the terminate-implies-detach fix: a DeleteOnTermination=false
|
||||
// Boot volume is never cleared by Stop's Unmount (daemon/vm_adapters.go), so
|
||||
// Boot volume is never cleared by Stop's Unmount (runtime/daemon/vm_adapters.go), so
|
||||
// without this it would strand attached to the now-gone instance forever.
|
||||
// Terminate must still detach it (AWS semantics: it survives as available,
|
||||
// it just isn't deleted).
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"github.com/mulgadc/spinifex/spinifex/daemon"
|
||||
"github.com/mulgadc/spinifex/spinifex/runtime/compute/vm"
|
||||
"github.com/mulgadc/spinifex/spinifex/runtime/daemon"
|
||||
)
|
||||
|
||||
// LocalVMReaderForTest is the local instance state reader's consumer-facing
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mulgadc/spinifex/spinifex/daemon"
|
||||
"github.com/mulgadc/spinifex/spinifex/domains/ec2/guestmetadata"
|
||||
"github.com/mulgadc/spinifex/spinifex/foundation/state/kvstore"
|
||||
"github.com/mulgadc/spinifex/spinifex/runtime/compute/vm"
|
||||
"github.com/mulgadc/spinifex/spinifex/runtime/daemon"
|
||||
"github.com/nats-io/nats.go"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mulgadc/spinifex/spinifex/daemon"
|
||||
"github.com/mulgadc/spinifex/spinifex/domains/ec2/instance/state"
|
||||
"github.com/mulgadc/spinifex/spinifex/runtime/compute/vm"
|
||||
"github.com/mulgadc/spinifex/spinifex/runtime/daemon"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -382,7 +382,7 @@ func volumeAttachment(ctx context.Context, store objectstore.ObjectStore, bucket
|
||||
// volume-to-node resolution is needed here.
|
||||
//
|
||||
// An attachment record is not proof of a live writer: stop deliberately leaves
|
||||
// a boot volume attached (daemon/vm_adapters.go's volumeMounterAdapter.Unmount)
|
||||
// a boot volume attached (runtime/daemon/vm_adapters.go's volumeMounterAdapter.Unmount)
|
||||
// while tearing down both the NBD plugin and this subscription. The two
|
||||
// not-running signals below are therefore the stopped-instance path, not a
|
||||
// failure — treating them as one would make every stopped instance's root
|
||||
|
||||
@@ -130,7 +130,7 @@ func TestDeleteVolume_CrossAccountDocumentIsStillNotFound(t *testing.T) {
|
||||
// UpdateVolumeState succeeds, but the document goes unreadable before
|
||||
// DeleteVolume's own read of the same key. That read failure must come back
|
||||
// as a non-NotFound error, which is exactly the signal
|
||||
// daemon/vm_adapters.go's DeleteVolumes and instance/service_impl.go's
|
||||
// runtime/daemon/vm_adapters.go's DeleteVolumes and instance/service_impl.go's
|
||||
// deleteInstanceVolumes use to tell a real failure from an already-gone
|
||||
// volume (!awserrors.IsNotFound(err) && !objectstore.IsNoSuchKeyError(err)).
|
||||
func TestDeleteVolumeOnTerminate_DeleteReadFailureIsNotSuccess(t *testing.T) {
|
||||
|
||||
@@ -1742,7 +1742,7 @@ func TestDeleteVolume_EmptyStateUnattachedDeletable(t *testing.T) {
|
||||
// TestDeleteVolumeOnTerminate_ClearsAttachmentThenDeletes locks the
|
||||
// A DeleteOnTermination root volume left attached to a
|
||||
// stopped instance (Stop's Unmount deliberately never clears a Boot volume's
|
||||
// AttachedInstance, daemon/vm_adapters.go) hits DeleteVolume's in-use guard
|
||||
// AttachedInstance, runtime/daemon/vm_adapters.go) hits DeleteVolume's in-use guard
|
||||
// directly — see TestDeleteVolume_VolumeAttachedButAvailable above.
|
||||
// DeleteVolumeOnTerminate must clear the stale attachment first (terminate
|
||||
// implies detach) so the terminate delete actually succeeds instead of
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"github.com/mulgadc/spinifex/spinifex/domains/ecs/taskdefinition"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"github.com/mulgadc/spinifex/spinifex/domains/ecs/taskdefinition"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// the package before a task reaches the API surface.
|
||||
//
|
||||
//test:in-package — placeTask and InstanceRecord's slot arithmetic are
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import "context"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+29
-29
@@ -1,24 +1,24 @@
|
||||
package handlers_ecs_test
|
||||
package ecs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ecs"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// listInstanceARNs lists a cluster's container instances at one status, or at
|
||||
// every status when the filter is empty.
|
||||
func listInstanceARNs(t *testing.T, svc *handlers_ecs.Service, cluster, status string) []string {
|
||||
func listInstanceARNs(t *testing.T, svc *ecsdomain.Service, cluster, status string) []string {
|
||||
t.Helper()
|
||||
in := &ecs.ListContainerInstancesInput{Cluster: aws.String(cluster)}
|
||||
if status != "" {
|
||||
in.Status = aws.String(status)
|
||||
}
|
||||
out, err := svc.ListContainerInstances(t.Context(), in, handlers_ecs.TestAccountID)
|
||||
out, err := svc.ListContainerInstances(t.Context(), in, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
return aws.StringValueSlice(out.ContainerInstanceArns)
|
||||
}
|
||||
@@ -27,24 +27,24 @@ func listInstanceARNs(t *testing.T, svc *handlers_ecs.Service, cluster, status s
|
||||
// find one that can take work, and a cluster carrying a drained node reads as
|
||||
// if every node were live.
|
||||
func TestListContainerInstances_StatusSelectsOnlyThatStatus(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
_, err := svc.CreateCluster(t.Context(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, handlers_ecs.TestAccountID)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
_, err := svc.CreateCluster(t.Context(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
handlers_ecs.RegisterInstance(t, svc, "web", "i-live", 1024, 2048)
|
||||
handlers_ecs.RegisterInstance(t, svc, "web", "i-drained", 1024, 2048)
|
||||
ecsdomain.RegisterInstance(t, svc, "web", "i-live", 1024, 2048)
|
||||
ecsdomain.RegisterInstance(t, svc, "web", "i-drained", 1024, 2048)
|
||||
|
||||
kv, err := svc.Bucket(t.Context(), handlers_ecs.TestAccountID)
|
||||
kv, err := svc.Bucket(t.Context(), ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
drained, err := svc.UpsertInstance(t.Context(), kv, handlers_ecs.TestAccountID, "web", "i-drained", func(r *handlers_ecs.InstanceRecord) {
|
||||
r.Status = handlers_ecs.InstanceStatusDraining
|
||||
drained, err := svc.UpsertInstance(t.Context(), kv, ecsdomain.TestAccountID, "web", "i-drained", func(r *ecsdomain.InstanceRecord) {
|
||||
r.Status = ecsdomain.InstanceStatusDraining
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
active := listInstanceARNs(t, svc, "web", handlers_ecs.InstanceStatusActive)
|
||||
active := listInstanceARNs(t, svc, "web", ecsdomain.InstanceStatusActive)
|
||||
assert.Len(t, active, 1)
|
||||
assert.NotContains(t, active, drained.ARN)
|
||||
|
||||
assert.Equal(t, []string{drained.ARN}, listInstanceARNs(t, svc, "web", handlers_ecs.InstanceStatusDraining))
|
||||
assert.Equal(t, []string{drained.ARN}, listInstanceARNs(t, svc, "web", ecsdomain.InstanceStatusDraining))
|
||||
assert.Len(t, listInstanceARNs(t, svc, "web", ""), 2)
|
||||
}
|
||||
|
||||
@@ -52,10 +52,10 @@ func TestListContainerInstances_StatusSelectsOnlyThatStatus(t *testing.T) {
|
||||
// so asking for one is a valid question with an empty answer rather than an
|
||||
// error.
|
||||
func TestListContainerInstances_TransitionalStatusMatchesNothing(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
_, err := svc.CreateCluster(t.Context(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, handlers_ecs.TestAccountID)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
_, err := svc.CreateCluster(t.Context(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
handlers_ecs.RegisterInstance(t, svc, "web", "i-live", 1024, 2048)
|
||||
ecsdomain.RegisterInstance(t, svc, "web", "i-live", 1024, 2048)
|
||||
|
||||
for _, status := range []string{"REGISTERING", "DEREGISTERING", "REGISTRATION_FAILED"} {
|
||||
assert.Empty(t, listInstanceARNs(t, svc, "web", status), status)
|
||||
@@ -63,42 +63,42 @@ func TestListContainerInstances_TransitionalStatusMatchesNothing(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListContainerInstances_UnknownStatusIsRefused(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
_, err := svc.CreateCluster(t.Context(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, handlers_ecs.TestAccountID)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
_, err := svc.CreateCluster(t.Context(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = svc.ListContainerInstances(t.Context(), &ecs.ListContainerInstancesInput{
|
||||
Cluster: aws.String("web"),
|
||||
Status: aws.String("INACTIVE"),
|
||||
}, handlers_ecs.TestAccountID)
|
||||
}, ecsdomain.TestAccountID)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// A filtered list and a describe of what it returned have to agree, since the
|
||||
// status the filter selects on is the one describe reports.
|
||||
func TestListContainerInstances_FilterAgreesWithDescribe(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
_, err := svc.CreateCluster(t.Context(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, handlers_ecs.TestAccountID)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
_, err := svc.CreateCluster(t.Context(), &ecs.CreateClusterInput{ClusterName: aws.String("web")}, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
handlers_ecs.RegisterInstance(t, svc, "web", "i-live", 1024, 2048)
|
||||
handlers_ecs.RegisterInstance(t, svc, "web", "i-drained", 1024, 2048)
|
||||
ecsdomain.RegisterInstance(t, svc, "web", "i-live", 1024, 2048)
|
||||
ecsdomain.RegisterInstance(t, svc, "web", "i-drained", 1024, 2048)
|
||||
|
||||
kv, err := svc.Bucket(t.Context(), handlers_ecs.TestAccountID)
|
||||
kv, err := svc.Bucket(t.Context(), ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
_, err = svc.UpsertInstance(t.Context(), kv, handlers_ecs.TestAccountID, "web", "i-drained", func(r *handlers_ecs.InstanceRecord) {
|
||||
r.Status = handlers_ecs.InstanceStatusDraining
|
||||
_, err = svc.UpsertInstance(t.Context(), kv, ecsdomain.TestAccountID, "web", "i-drained", func(r *ecsdomain.InstanceRecord) {
|
||||
r.Status = ecsdomain.InstanceStatusDraining
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
arns := listInstanceARNs(t, svc, "web", handlers_ecs.InstanceStatusActive)
|
||||
arns := listInstanceARNs(t, svc, "web", ecsdomain.InstanceStatusActive)
|
||||
require.NotEmpty(t, arns)
|
||||
out, err := svc.DescribeContainerInstances(t.Context(), &ecs.DescribeContainerInstancesInput{
|
||||
Cluster: aws.String("web"),
|
||||
ContainerInstances: aws.StringSlice(arns),
|
||||
}, handlers_ecs.TestAccountID)
|
||||
}, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, out.ContainerInstances, len(arns))
|
||||
for _, ci := range out.ContainerInstances {
|
||||
assert.Equal(t, handlers_ecs.InstanceStatusActive, aws.StringValue(ci.Status))
|
||||
assert.Equal(t, ecsdomain.InstanceStatusActive, aws.StringValue(ci.Status))
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
//test:in-package — these decode persisted records straight into the unexported
|
||||
// record types and call their unexported toAWS projection, which is the whole
|
||||
@@ -2,7 +2,7 @@
|
||||
//convergeIfDue) and the trigger is an unexported channel, so an external test
|
||||
//package can reach neither.
|
||||
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -2,7 +2,7 @@
|
||||
// unexported, and the pass is only reachable through the scheduler's own state.
|
||||
//
|
||||
//test:in-package — the converge pass has no exported entry point
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,7 +1,7 @@
|
||||
// Package handlers_ecs is the ECS control plane: the ECSService contract, its
|
||||
// Package ecs is the ECS control plane: the ECSService contract, its
|
||||
// daemon-side JetStream KV implementation and NATS client, and the leader-elected
|
||||
// scheduler that converges services and drains silent container instances.
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -16,8 +16,8 @@ import (
|
||||
"github.com/aws/aws-sdk-go/service/ecs"
|
||||
"github.com/aws/aws-sdk-go/service/elbv2"
|
||||
ec2eip "github.com/mulgadc/spinifex/spinifex/domains/ec2/eip"
|
||||
elbv2domain "github.com/mulgadc/spinifex/spinifex/domains/elbv2"
|
||||
"github.com/mulgadc/spinifex/spinifex/foundation/aws/errors"
|
||||
handlers_elbv2 "github.com/mulgadc/spinifex/spinifex/handlers/elbv2"
|
||||
"github.com/nats-io/nats.go"
|
||||
"github.com/nats-io/nats.go/jetstream"
|
||||
)
|
||||
@@ -43,7 +43,7 @@ func newNATSTargetRegistrar(nc *nats.Conn) *natsTargetRegistrar {
|
||||
}
|
||||
|
||||
func (r *natsTargetRegistrar) Register(ctx context.Context, accountID, tgARN, ip string, port int) error {
|
||||
_, err := handlers_elbv2.NewNATSELBv2Service(r.nc).RegisterTargets(ctx, &elbv2.RegisterTargetsInput{
|
||||
_, err := elbv2domain.NewNATSELBv2Service(r.nc).RegisterTargets(ctx, &elbv2.RegisterTargetsInput{
|
||||
TargetGroupArn: aws.String(tgARN),
|
||||
Targets: []*elbv2.TargetDescription{{Id: aws.String(ip), Port: aws.Int64(int64(port))}},
|
||||
}, accountID)
|
||||
@@ -51,7 +51,7 @@ func (r *natsTargetRegistrar) Register(ctx context.Context, accountID, tgARN, ip
|
||||
}
|
||||
|
||||
func (r *natsTargetRegistrar) Deregister(ctx context.Context, accountID, tgARN, ip string, port int) error {
|
||||
_, err := handlers_elbv2.NewNATSELBv2Service(r.nc).DeregisterTargets(ctx, &elbv2.DeregisterTargetsInput{
|
||||
_, err := elbv2domain.NewNATSELBv2Service(r.nc).DeregisterTargets(ctx, &elbv2.DeregisterTargetsInput{
|
||||
TargetGroupArn: aws.String(tgARN),
|
||||
Targets: []*elbv2.TargetDescription{{Id: aws.String(ip), Port: aws.Int64(int64(port))}},
|
||||
}, accountID)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+52
-52
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs_test
|
||||
package ecs_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/ecs"
|
||||
ecsv1 "github.com/mulgadc/spinifex/contracts/ecs/v1"
|
||||
handlers_ecs "github.com/mulgadc/spinifex/spinifex/handlers/ecs"
|
||||
ecsdomain "github.com/mulgadc/spinifex/spinifex/domains/ecs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -14,49 +14,49 @@ import (
|
||||
// seedTasks writes task records straight to KV. The statuses under test are
|
||||
// reported by the agent, so driving them through RunTask would require a live
|
||||
// agent to reach anything other than PENDING.
|
||||
func seedTasks(t *testing.T, svc *handlers_ecs.Service, cluster string, recs ...*handlers_ecs.TaskRecord) {
|
||||
func seedTasks(t *testing.T, svc *ecsdomain.Service, cluster string, recs ...*ecsdomain.TaskRecord) {
|
||||
t.Helper()
|
||||
kv, err := svc.Bucket(t.Context(), handlers_ecs.TestAccountID)
|
||||
kv, err := svc.Bucket(t.Context(), ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
for _, r := range recs {
|
||||
r.Cluster = cluster
|
||||
r.ARN = handlers_ecs.TaskARN(svc.Region(), handlers_ecs.TestAccountID, cluster, r.TaskID)
|
||||
require.NoError(t, handlers_ecs.PutJSON(t.Context(), kv, handlers_ecs.TaskKey(cluster, r.TaskID), r))
|
||||
r.ARN = ecsdomain.TaskARN(svc.Region(), ecsdomain.TestAccountID, cluster, r.TaskID)
|
||||
require.NoError(t, ecsdomain.PutJSON(t.Context(), kv, ecsdomain.TaskKey(cluster, r.TaskID), r))
|
||||
}
|
||||
}
|
||||
|
||||
func listTaskIDs(t *testing.T, svc *handlers_ecs.Service, input *ecs.ListTasksInput) []string {
|
||||
func listTaskIDs(t *testing.T, svc *ecsdomain.Service, input *ecs.ListTasksInput) []string {
|
||||
t.Helper()
|
||||
out, err := svc.ListTasks(t.Context(), input, handlers_ecs.TestAccountID)
|
||||
out, err := svc.ListTasks(t.Context(), input, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
ids := make([]string, 0, len(out.TaskArns))
|
||||
for _, a := range out.TaskArns {
|
||||
ids = append(ids, handlers_ecs.ContainerInstanceShortID(aws.StringValue(a)))
|
||||
ids = append(ids, ecsdomain.ContainerInstanceShortID(aws.StringValue(a)))
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func TestListTasks_DesiredStatusFilters(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
seedTasks(t, svc, "web",
|
||||
&handlers_ecs.TaskRecord{TaskID: "run-1", DesiredStatus: handlers_ecs.TaskStatusRunning, LastStatus: handlers_ecs.TaskStatusRunning},
|
||||
&handlers_ecs.TaskRecord{TaskID: "stop-1", DesiredStatus: handlers_ecs.TaskStatusStopped, LastStatus: handlers_ecs.TaskStatusStopped},
|
||||
&handlers_ecs.TaskRecord{TaskID: "stop-2", DesiredStatus: handlers_ecs.TaskStatusStopped, LastStatus: handlers_ecs.TaskStatusStopped},
|
||||
&ecsdomain.TaskRecord{TaskID: "run-1", DesiredStatus: ecsdomain.TaskStatusRunning, LastStatus: ecsdomain.TaskStatusRunning},
|
||||
&ecsdomain.TaskRecord{TaskID: "stop-1", DesiredStatus: ecsdomain.TaskStatusStopped, LastStatus: ecsdomain.TaskStatusStopped},
|
||||
&ecsdomain.TaskRecord{TaskID: "stop-2", DesiredStatus: ecsdomain.TaskStatusStopped, LastStatus: ecsdomain.TaskStatusStopped},
|
||||
)
|
||||
|
||||
assert.ElementsMatch(t, []string{"run-1"},
|
||||
listTaskIDs(t, svc, &ecs.ListTasksInput{Cluster: aws.String("web"), DesiredStatus: aws.String(handlers_ecs.TaskStatusRunning)}))
|
||||
listTaskIDs(t, svc, &ecs.ListTasksInput{Cluster: aws.String("web"), DesiredStatus: aws.String(ecsdomain.TaskStatusRunning)}))
|
||||
assert.ElementsMatch(t, []string{"stop-1", "stop-2"},
|
||||
listTaskIDs(t, svc, &ecs.ListTasksInput{Cluster: aws.String("web"), DesiredStatus: aws.String(handlers_ecs.TaskStatusStopped)}))
|
||||
listTaskIDs(t, svc, &ecs.ListTasksInput{Cluster: aws.String("web"), DesiredStatus: aws.String(ecsdomain.TaskStatusStopped)}))
|
||||
}
|
||||
|
||||
// The default is the case that broke a caller: asking for nothing used to hand
|
||||
// back every task the cluster had ever run.
|
||||
func TestListTasks_OmittedDesiredStatusMeansRunning(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
seedTasks(t, svc, "web",
|
||||
&handlers_ecs.TaskRecord{TaskID: "run-1", DesiredStatus: handlers_ecs.TaskStatusRunning, LastStatus: handlers_ecs.TaskStatusRunning},
|
||||
&handlers_ecs.TaskRecord{TaskID: "stop-1", DesiredStatus: handlers_ecs.TaskStatusStopped, LastStatus: handlers_ecs.TaskStatusStopped},
|
||||
&ecsdomain.TaskRecord{TaskID: "run-1", DesiredStatus: ecsdomain.TaskStatusRunning, LastStatus: ecsdomain.TaskStatusRunning},
|
||||
&ecsdomain.TaskRecord{TaskID: "stop-1", DesiredStatus: ecsdomain.TaskStatusStopped, LastStatus: ecsdomain.TaskStatusStopped},
|
||||
)
|
||||
|
||||
assert.ElementsMatch(t, []string{"run-1"},
|
||||
@@ -64,15 +64,15 @@ func TestListTasks_OmittedDesiredStatusMeansRunning(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListTasks_FamilyStartedByServiceAndInstanceFilter(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
seedTasks(t, svc, "web",
|
||||
&handlers_ecs.TaskRecord{
|
||||
TaskID: "a", DesiredStatus: handlers_ecs.TaskStatusRunning, LastStatus: handlers_ecs.TaskStatusRunning,
|
||||
TaskDefFamily: "api", StartedBy: "ci", Group: handlers_ecs.ServiceTaskGroup("web"), ContainerInstanceID: "i-1",
|
||||
&ecsdomain.TaskRecord{
|
||||
TaskID: "a", DesiredStatus: ecsdomain.TaskStatusRunning, LastStatus: ecsdomain.TaskStatusRunning,
|
||||
TaskDefFamily: "api", StartedBy: "ci", Group: ecsdomain.ServiceTaskGroup("web"), ContainerInstanceID: "i-1",
|
||||
},
|
||||
&handlers_ecs.TaskRecord{
|
||||
TaskID: "b", DesiredStatus: handlers_ecs.TaskStatusRunning, LastStatus: handlers_ecs.TaskStatusRunning,
|
||||
TaskDefFamily: "worker", StartedBy: "human", Group: handlers_ecs.ServiceTaskGroup("batch"), ContainerInstanceID: "i-2",
|
||||
&ecsdomain.TaskRecord{
|
||||
TaskID: "b", DesiredStatus: ecsdomain.TaskStatusRunning, LastStatus: ecsdomain.TaskStatusRunning,
|
||||
TaskDefFamily: "worker", StartedBy: "human", Group: ecsdomain.ServiceTaskGroup("batch"), ContainerInstanceID: "i-2",
|
||||
},
|
||||
)
|
||||
|
||||
@@ -89,14 +89,14 @@ func TestListTasks_FamilyStartedByServiceAndInstanceFilter(t *testing.T) {
|
||||
|
||||
// Filters combine, so a task has to satisfy all of them rather than any.
|
||||
func TestListTasks_FiltersCombine(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
seedTasks(t, svc, "web",
|
||||
&handlers_ecs.TaskRecord{
|
||||
TaskID: "a", DesiredStatus: handlers_ecs.TaskStatusRunning, LastStatus: handlers_ecs.TaskStatusRunning,
|
||||
&ecsdomain.TaskRecord{
|
||||
TaskID: "a", DesiredStatus: ecsdomain.TaskStatusRunning, LastStatus: ecsdomain.TaskStatusRunning,
|
||||
TaskDefFamily: "api", ContainerInstanceID: "i-1",
|
||||
},
|
||||
&handlers_ecs.TaskRecord{
|
||||
TaskID: "b", DesiredStatus: handlers_ecs.TaskStatusRunning, LastStatus: handlers_ecs.TaskStatusRunning,
|
||||
&ecsdomain.TaskRecord{
|
||||
TaskID: "b", DesiredStatus: ecsdomain.TaskStatusRunning, LastStatus: ecsdomain.TaskStatusRunning,
|
||||
TaskDefFamily: "api", ContainerInstanceID: "i-2",
|
||||
},
|
||||
)
|
||||
@@ -109,49 +109,49 @@ func TestListTasks_FiltersCombine(t *testing.T) {
|
||||
// A task nobody asked to stop still has to leave the RUNNING filter when it
|
||||
// exits, or a caller polling for live tasks never stops seeing it.
|
||||
func TestRecordTaskState_SelfExitMovesDesiredStatusToStopped(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
seedTasks(t, svc, "web",
|
||||
&handlers_ecs.TaskRecord{TaskID: "t-1", DesiredStatus: handlers_ecs.TaskStatusRunning, LastStatus: handlers_ecs.TaskStatusRunning},
|
||||
&ecsdomain.TaskRecord{TaskID: "t-1", DesiredStatus: ecsdomain.TaskStatusRunning, LastStatus: ecsdomain.TaskStatusRunning},
|
||||
)
|
||||
|
||||
require.NoError(t, svc.RecordTaskState(t.Context(), &ecsv1.TaskState{
|
||||
AccountID: handlers_ecs.TestAccountID, ClusterName: "web", TaskID: "t-1",
|
||||
LastStatus: handlers_ecs.TaskStatusStopped, Reason: "container exited (1)",
|
||||
AccountID: ecsdomain.TestAccountID, ClusterName: "web", TaskID: "t-1",
|
||||
LastStatus: ecsdomain.TaskStatusStopped, Reason: "container exited (1)",
|
||||
}))
|
||||
|
||||
kv, err := svc.Bucket(t.Context(), handlers_ecs.TestAccountID)
|
||||
kv, err := svc.Bucket(t.Context(), ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
var rec handlers_ecs.TaskRecord
|
||||
found, err := handlers_ecs.GetJSON(t.Context(), kv, handlers_ecs.TaskKey("web", "t-1"), &rec)
|
||||
var rec ecsdomain.TaskRecord
|
||||
found, err := ecsdomain.GetJSON(t.Context(), kv, ecsdomain.TaskKey("web", "t-1"), &rec)
|
||||
require.NoError(t, err)
|
||||
require.True(t, found)
|
||||
assert.Equal(t, handlers_ecs.TaskStatusStopped, rec.DesiredStatus)
|
||||
assert.Equal(t, ecsdomain.TaskStatusStopped, rec.DesiredStatus)
|
||||
|
||||
assert.Empty(t, listTaskIDs(t, svc, &ecs.ListTasksInput{Cluster: aws.String("web")}))
|
||||
assert.ElementsMatch(t, []string{"t-1"}, listTaskIDs(t, svc, &ecs.ListTasksInput{
|
||||
Cluster: aws.String("web"), DesiredStatus: aws.String(handlers_ecs.TaskStatusStopped),
|
||||
Cluster: aws.String("web"), DesiredStatus: aws.String(ecsdomain.TaskStatusStopped),
|
||||
}))
|
||||
}
|
||||
|
||||
func TestDescribeContainerInstances_CountsComeFromTaskRecords(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
_, err := svc.RegisterContainerInstance(t.Context(), &ecs.RegisterContainerInstanceInput{
|
||||
Cluster: aws.String("web"),
|
||||
InstanceIdentityDocument: aws.String("i-1"),
|
||||
}, handlers_ecs.TestAccountID)
|
||||
}, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
|
||||
seedTasks(t, svc, "web",
|
||||
&handlers_ecs.TaskRecord{TaskID: "r1", LastStatus: handlers_ecs.TaskStatusRunning, ContainerInstanceID: "i-1"},
|
||||
&handlers_ecs.TaskRecord{TaskID: "p1", LastStatus: handlers_ecs.TaskStatusPending, ContainerInstanceID: "i-1"},
|
||||
&handlers_ecs.TaskRecord{TaskID: "s1", LastStatus: handlers_ecs.TaskStatusStopped, ContainerInstanceID: "i-1"},
|
||||
&handlers_ecs.TaskRecord{TaskID: "s2", LastStatus: handlers_ecs.TaskStatusStopped, ContainerInstanceID: "i-1"},
|
||||
&handlers_ecs.TaskRecord{TaskID: "r2", LastStatus: handlers_ecs.TaskStatusRunning, ContainerInstanceID: "i-2"},
|
||||
&ecsdomain.TaskRecord{TaskID: "r1", LastStatus: ecsdomain.TaskStatusRunning, ContainerInstanceID: "i-1"},
|
||||
&ecsdomain.TaskRecord{TaskID: "p1", LastStatus: ecsdomain.TaskStatusPending, ContainerInstanceID: "i-1"},
|
||||
&ecsdomain.TaskRecord{TaskID: "s1", LastStatus: ecsdomain.TaskStatusStopped, ContainerInstanceID: "i-1"},
|
||||
&ecsdomain.TaskRecord{TaskID: "s2", LastStatus: ecsdomain.TaskStatusStopped, ContainerInstanceID: "i-1"},
|
||||
&ecsdomain.TaskRecord{TaskID: "r2", LastStatus: ecsdomain.TaskStatusRunning, ContainerInstanceID: "i-2"},
|
||||
)
|
||||
|
||||
out, err := svc.DescribeContainerInstances(t.Context(), &ecs.DescribeContainerInstancesInput{
|
||||
Cluster: aws.String("web"), ContainerInstances: []*string{aws.String("i-1")},
|
||||
}, handlers_ecs.TestAccountID)
|
||||
}, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, out.ContainerInstances, 1)
|
||||
assert.Equal(t, int64(1), aws.Int64Value(out.ContainerInstances[0].RunningTasksCount))
|
||||
@@ -162,21 +162,21 @@ func TestDescribeContainerInstances_CountsComeFromTaskRecords(t *testing.T) {
|
||||
// names a task that stopped, so a count taken from it reports a task nobody is
|
||||
// running.
|
||||
func TestDescribeContainerInstances_StalePlacedTasksDoNotInflateCount(t *testing.T) {
|
||||
svc, _ := handlers_ecs.NewTestService(t)
|
||||
kv, err := svc.Bucket(t.Context(), handlers_ecs.TestAccountID)
|
||||
svc, _ := ecsdomain.NewTestService(t)
|
||||
kv, err := svc.Bucket(t.Context(), ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
_, err = svc.UpsertInstance(t.Context(), kv, handlers_ecs.TestAccountID, "web", "i-1", func(r *handlers_ecs.InstanceRecord) {
|
||||
_, err = svc.UpsertInstance(t.Context(), kv, ecsdomain.TestAccountID, "web", "i-1", func(r *ecsdomain.InstanceRecord) {
|
||||
r.PlacedTasks = []string{"gone-1", "gone-2", "r1"}
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
seedTasks(t, svc, "web",
|
||||
&handlers_ecs.TaskRecord{TaskID: "r1", LastStatus: handlers_ecs.TaskStatusRunning, ContainerInstanceID: "i-1"},
|
||||
&ecsdomain.TaskRecord{TaskID: "r1", LastStatus: ecsdomain.TaskStatusRunning, ContainerInstanceID: "i-1"},
|
||||
)
|
||||
|
||||
out, err := svc.DescribeContainerInstances(t.Context(), &ecs.DescribeContainerInstancesInput{
|
||||
Cluster: aws.String("web"), ContainerInstances: []*string{aws.String("i-1")},
|
||||
}, handlers_ecs.TestAccountID)
|
||||
}, ecsdomain.TestAccountID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, out.ContainerInstances, 1)
|
||||
assert.Equal(t, int64(1), aws.Int64Value(out.ContainerInstances[0].RunningTasksCount))
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_ecs
|
||||
package ecs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,8 +1,8 @@
|
||||
package handlers_elbv2
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/mulgadc/spinifex/spinifex/lbagent"
|
||||
lbagent "github.com/mulgadc/spinifex/spinifex/agents/elbv2"
|
||||
)
|
||||
|
||||
// LBAgentHeartbeatInput is sent by the LB agent on each heartbeat tick,
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
package handlers_elbv2
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/mulgadc/spinifex/spinifex/lbagent"
|
||||
lbagent "github.com/mulgadc/spinifex/spinifex/agents/elbv2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_elbv2
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_elbv2
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_elbv2
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package handlers_elbv2
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
@@ -1,4 +1,4 @@
|
||||
package handlers_elbv2
|
||||
package elbv2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/mulgadc/spinifex/spinifex/lbagent"
|
||||
lbagent "github.com/mulgadc/spinifex/spinifex/agents/elbv2"
|
||||
)
|
||||
|
||||
// All interpolated fields are regex-validated at the input boundary.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user