feat: add RDS to awsmodel coverage

This commit is contained in:
Julian Sommer
2026-08-06 12:00:02 +10:00
parent e3b5701f15
commit 456a60717b
8 changed files with 182 additions and 28 deletions
+52 -2
View File
@@ -155,6 +155,7 @@ Operator surface over the daemon's `bedrock.endpoint.*` subjects. The gateway do
| `describe-instance-types` | `--filters` (capacity only) | `--instance-types`, `--max-results`, `--next-token`, `--dry-run`, other filters | **DONE** |
| `modify-instance-attribute` | `--instance-id`, `--instance-type`, `--user-data`, `--disable-api-termination` | `--ebs-optimized`, `--source-dest-check`, `--instance-initiated-shutdown-behavior`, `--block-device-mappings`, `--groups`, `--ena-support`, `--sriov-net-support` | **DONE** |
| `get-console-output` | `--instance-id` | `--latest`, `--dry-run` | **DONE** |
| `get-password-data` | `--instance-id` | `--dry-run` | **DONE** |
| `describe-instance-attribute` | `--instance-id`, `--attribute` (instanceType, userData, disableApiTermination, instanceInitiatedShutdownBehavior, disableApiStop, ebsOptimized, enaSupport, sourceDestCheck, rootDeviceName, kernel, ramdisk) | `--dry-run` | **DONE** |
| `modify-instance-metadata-options` | `--instance-id`, `--http-put-response-hop-limit` (1–64), `--http-tokens` (`required`/`optional`), `--http-endpoint` (`enabled`), `--http-protocol-ipv6`/`--instance-metadata-tags` (`disabled`) — unmodelled values return `UnsupportedOperation` | `--dry-run` | **DONE** |
| `describe-instance-credit-specifications` | `--instance-ids` | `--filters`, `--max-results`, `--dry-run` | **DONE** (stub — always returns `standard`) |
@@ -338,8 +339,8 @@ KV CRUD only — no OVN/OVS integration. EIGWs are stored but have no effect on
| `delete-network-interface` | `--network-interface-id` | `--dry-run` | **DONE** |
| `describe-network-interfaces` | `--network-interface-ids`, `--filters` (subnet-id, vpc-id, attachment.instance-id) | `--max-results`, `--dry-run` | **DONE** |
| `modify-network-interface-attribute` | — | `--network-interface-id`, `--description`, `--groups` | **DONE** |
| `attach-network-interface` | — | `--network-interface-id`, `--instance-id`, `--device-index` | **NOT STARTED** (internal only) |
| `detach-network-interface` | — | `--attachment-id`, `--force` | **NOT STARTED** (internal only) |
| `attach-network-interface` | `--network-interface-id`, `--instance-id`, `--device-index` (all required) | `--dry-run`, `--network-card-index`, `--ena-srd-specification` | **DONE** — hot-plugged into a running instance |
| `detach-network-interface` | `--attachment-id` (required), `--force` | `--dry-run` | **DONE** |
| `assign-private-ip-addresses` | — | `--network-interface-id`, `--private-ip-addresses`, `--secondary-private-ip-address-count` | **NOT STARTED** |
| `unassign-private-ip-addresses` | — | `--network-interface-id`, `--private-ip-addresses` | **NOT STARTED** |
@@ -797,6 +798,8 @@ The default certificate cannot be added/removed via these calls — set it on th
| Command | Implemented Flags | Missing Flags | Status |
|---------|-------------------|---------------|--------|
| `describe-tags` | `--resource-arns` (loadbalancer, targetgroup, listener) | — | **DONE** |
| `add-tags` | `--resource-arns`, `--tags` | — | **DONE** |
| `remove-tags` | `--resource-arns`, `--tag-keys` | — | **DONE** |
### ELBv2 — Not Yet Implemented
@@ -832,6 +835,7 @@ Spinifex both stores externally-issued certificates (`import-certificate`) and i
| `import-certificate` | `--certificate`, `--private-key`, `--certificate-chain`, `--certificate-arn` (re-import) | `--tags` | **DONE** |
| `describe-certificate` | `--certificate-arn` | — | **DONE** |
| `list-certificates` | — | `--certificate-statuses`, `--includes`, `--max-items`, `--next-token` | **DONE** |
| `get-certificate` | `--certificate-arn` | — | **DONE** — returns the leaf certificate and its chain |
| `delete-certificate` | `--certificate-arn` | — | **DONE** |
| `request-certificate` | `--domain-name`, `--subject-alternative-names`, `--tags` | `--validation-method`, `--certificate-authority-arn`, `--options`, `--idempotency-token` | **PARTIAL** — `PRIVATE_CA` issues synchronously; `PROVIDER_API`/`MANUAL_TXT` are accepted and correctly shaped but stay `PENDING_VALIDATION` until a later issuance worker lands |
| `add-tags-to-certificate` / `list-tags-for-certificate` / `remove-tags-from-certificate` | `--certificate-arn`, `--tags`/`--tag-keys` | — | **DONE** |
@@ -968,6 +972,52 @@ IAM: `AmazonRDSFullAccess` and `AmazonRDSReadOnlyAccess` are available as manage
---
## ECR (Elastic Container Registry)
Repository metadata is served over the AWS API on the gateway endpoint; image data moves over the OCI Distribution `/v2/` endpoint on that same host, authenticated by the bearer token `get-login-password` mints.
| Command | Status |
|---------|--------|
| `create-repository`, `delete-repository`, `describe-repositories` | **DONE** |
| `describe-images`, `list-images`, `batch-get-image`, `batch-delete-image`, `put-image` | **DONE** |
| `put-image-tag-mutability` | **DONE** |
| `get-authorization-token` | **DONE** — mints the short-lived bearer token used by `docker login` |
| `get-repository-policy`, `set-repository-policy`, `delete-repository-policy` | **DONE** |
| `get-lifecycle-policy`, `put-lifecycle-policy`, `delete-lifecycle-policy`, `get-lifecycle-policy-preview`, `start-lifecycle-policy-preview` | **DONE** |
| `list-tags-for-resource` | **DONE** |
| `tag-resource`, `untag-resource` | **NOT STARTED** (registered stub) |
| `batch-check-layer-availability`, `initiate-layer-upload`, `upload-layer-part`, `complete-layer-upload`, `get-download-url-for-layer` | **NOT STARTED** (registered stub) — layer transfer is served by the OCI `/v2/` endpoint |
| `describe-registry`, `get-registry-policy`, `put-registry-policy`, `put-replication-configuration`, `replicate-image` | **NOT STARTED** (registered stub) — single-registry deployment, no cross-region replication |
| `list-repositories` | **NOT STARTED** (registered stub) — use `describe-repositories` |
| `start-image-scan`, `describe-image-scan-findings`, `put-image-scanning-configuration`, `get-image-scanning-configuration`, `get-registry-scanning-configuration`, `put-registry-scanning-configuration`, `batch-get-repository-scanning-configuration` | **NOT STARTED** (`OperationNotSupported`) — vulnerability scanning is not offered |
| Pull-through cache rules, repository creation templates, image replication status | **NOT STARTED** (`InvalidAction`) |
---
## ECS (Elastic Container Service)
Clusters, services and tasks run on EC2 container instances; there is no Fargate launch type.
| Command | Status |
|---------|--------|
| `create-cluster`, `delete-cluster`, `describe-clusters`, `list-clusters` | **DONE** |
| `create-service`, `update-service`, `delete-service`, `describe-services`, `list-services` | **DONE** |
| `register-task-definition`, `deregister-task-definition`, `describe-task-definition`, `list-task-definitions` | **DONE** |
| `run-task`, `start-task`, `stop-task`, `describe-tasks`, `list-tasks` | **DONE** |
| `register-container-instance`, `deregister-container-instance`, `describe-container-instances`, `list-container-instances`, `update-container-instances-state` | **DONE** |
| `create-capacity-provider`, `delete-capacity-provider`, `describe-capacity-providers`, `put-cluster-capacity-providers` | **DONE** |
| `submit-task-state-change` | **DONE** — reported by the in-guest agent |
| `tag-resource`, `untag-resource`, `list-tags-for-resource` | **DONE** |
| `update-cluster`, `list-account-settings`, `put-account-setting`, `list-services-by-namespace`, `list-task-definition-families` | **NOT STARTED** (registered stub) |
| Task sets (blue/green): `create-task-set`, `update-task-set`, `delete-task-set`, `describe-task-sets`, `update-service-primary-task-set` | **NOT STARTED** (`InvalidAction`) |
| `execute-command`, `get-task-protection`, `update-task-protection` | **NOT STARTED** (`InvalidAction`) |
| Attributes: `put-attributes`, `delete-attributes`, `list-attributes` | **NOT STARTED** (`InvalidAction`) |
| `update-capacity-provider`, `update-cluster-settings`, `update-container-agent` | **NOT STARTED** (`InvalidAction`) |
| `delete-account-setting`, `put-account-setting-default`, `delete-task-definitions` | **NOT STARTED** (`InvalidAction`) |
| `discover-poll-endpoint`, `submit-container-state-change`, `submit-attachment-state-changes` | **NOT STARTED** (`InvalidAction`) |
---
## CloudWatch (Basic Monitoring)
| Command | Implemented Flags | Missing Flags | Status |
+75
View File
@@ -12,6 +12,7 @@ Implemented means a modelled operation is registered to a real handler. Stub and
| ecs | 2014-11-13 | 56 | 39 | 31 | 5 | 0 | 20 | 3 |
| elasticloadbalancingv2 | 2015-12-01 | 46 | 37 | 33 | 0 | 0 | 13 | 4 |
| iam | 2010-05-08 | 159 | 75 | 75 | 0 | 0 | 84 | 0 |
| rds | 2014-10-31 | 162 | 40 | 24 | 0 | 12 | 126 | 4 |
| s3 | 2006-03-01 | 99 | — | — | — | — | — | — |
| sts | 2011-06-15 | 8 | 4 | 4 | 0 | 0 | 4 | 0 |
@@ -457,6 +458,80 @@ None.
None.
</details>
## rds
Implements **24 of 162** modelled operations (14.8%).
<details><summary>Implemented (24)</summary>
`AddTagsToResource`, `CreateDBInstance`, `CreateDBParameterGroup`, `CreateDBSnapshot`, `CreateDBSubnetGroup`, `DeleteDBInstance`, `DeleteDBParameterGroup`, `DeleteDBSnapshot`
`DeleteDBSubnetGroup`, `DescribeDBInstanceAutomatedBackups`, `DescribeDBInstances`, `DescribeDBParameterGroups`, `DescribeDBParameters`, `DescribeDBSnapshots`, `DescribeDBSubnetGroups`, `DescribeEvents`
`ListTagsForResource`, `ModifyDBInstance`, `ModifyDBParameterGroup`, `RebootDBInstance`, `RemoveTagsFromResource`, `RestoreDBInstanceFromDBSnapshot`, `StartDBInstance`, `StopDBInstance`
</details>
<details><summary>Missing from dispatch (126)</summary>
`AddRoleToDBCluster`, `AddRoleToDBInstance`, `AddSourceIdentifierToSubscription`, `ApplyPendingMaintenanceAction`, `AuthorizeDBSecurityGroupIngress`, `BacktrackDBCluster`, `CancelExportTask`, `CopyDBClusterParameterGroup`
`CopyDBClusterSnapshot`, `CopyDBParameterGroup`, `CopyDBSnapshot`, `CopyOptionGroup`, `CreateBlueGreenDeployment`, `CreateCustomDBEngineVersion`, `CreateDBClusterEndpoint`, `CreateDBClusterParameterGroup`
`CreateDBClusterSnapshot`, `CreateDBProxy`, `CreateDBProxyEndpoint`, `CreateDBSecurityGroup`, `CreateDBShardGroup`, `CreateEventSubscription`, `CreateGlobalCluster`, `CreateIntegration`
`CreateTenantDatabase`, `DeleteBlueGreenDeployment`, `DeleteCustomDBEngineVersion`, `DeleteDBClusterAutomatedBackup`, `DeleteDBClusterEndpoint`, `DeleteDBClusterParameterGroup`, `DeleteDBClusterSnapshot`, `DeleteDBInstanceAutomatedBackup`
`DeleteDBProxy`, `DeleteDBProxyEndpoint`, `DeleteDBSecurityGroup`, `DeleteDBShardGroup`, `DeleteEventSubscription`, `DeleteGlobalCluster`, `DeleteIntegration`, `DeleteTenantDatabase`
`DeregisterDBProxyTargets`, `DescribeAccountAttributes`, `DescribeBlueGreenDeployments`, `DescribeCertificates`, `DescribeDBClusterAutomatedBackups`, `DescribeDBClusterBacktracks`, `DescribeDBClusterEndpoints`, `DescribeDBClusterParameterGroups`
`DescribeDBClusterParameters`, `DescribeDBClusterSnapshotAttributes`, `DescribeDBClusterSnapshots`, `DescribeDBEngineVersions`, `DescribeDBLogFiles`, `DescribeDBProxies`, `DescribeDBProxyEndpoints`, `DescribeDBProxyTargetGroups`
`DescribeDBProxyTargets`, `DescribeDBRecommendations`, `DescribeDBSecurityGroups`, `DescribeDBShardGroups`, `DescribeDBSnapshotAttributes`, `DescribeDBSnapshotTenantDatabases`, `DescribeEngineDefaultClusterParameters`, `DescribeEngineDefaultParameters`
`DescribeEventCategories`, `DescribeEventSubscriptions`, `DescribeExportTasks`, `DescribeGlobalClusters`, `DescribeIntegrations`, `DescribeOptionGroupOptions`, `DescribeOrderableDBInstanceOptions`, `DescribePendingMaintenanceActions`
`DescribeReservedDBInstances`, `DescribeReservedDBInstancesOfferings`, `DescribeSourceRegions`, `DescribeTenantDatabases`, `DescribeValidDBInstanceModifications`, `DisableHttpEndpoint`, `DownloadDBLogFilePortion`, `EnableHttpEndpoint`
`FailoverGlobalCluster`, `ModifyActivityStream`, `ModifyCertificates`, `ModifyCurrentDBClusterCapacity`, `ModifyCustomDBEngineVersion`, `ModifyDBClusterEndpoint`, `ModifyDBClusterParameterGroup`, `ModifyDBClusterSnapshotAttribute`
`ModifyDBProxy`, `ModifyDBProxyEndpoint`, `ModifyDBProxyTargetGroup`, `ModifyDBRecommendation`, `ModifyDBShardGroup`, `ModifyDBSnapshot`, `ModifyDBSnapshotAttribute`, `ModifyDBSubnetGroup`
`ModifyEventSubscription`, `ModifyGlobalCluster`, `ModifyIntegration`, `ModifyTenantDatabase`, `PromoteReadReplicaDBCluster`, `PurchaseReservedDBInstancesOffering`, `RebootDBCluster`, `RebootDBShardGroup`
`RegisterDBProxyTargets`, `RemoveFromGlobalCluster`, `RemoveRoleFromDBCluster`, `RemoveRoleFromDBInstance`, `RemoveSourceIdentifierFromSubscription`, `ResetDBClusterParameterGroup`, `ResetDBParameterGroup`, `RestoreDBClusterFromS3`
`RestoreDBClusterFromSnapshot`, `RestoreDBClusterToPointInTime`, `RestoreDBInstanceFromS3`, `RevokeDBSecurityGroupIngress`, `StartActivityStream`, `StartDBCluster`, `StartDBInstanceAutomatedBackupsReplication`, `StartExportTask`
`StopActivityStream`, `StopDBCluster`, `StopDBInstanceAutomatedBackupsReplication`, `SwitchoverBlueGreenDeployment`, `SwitchoverGlobalCluster`, `SwitchoverReadReplica`
</details>
<details><summary>Registered stubs (0)</summary>
None.
</details>
<details><summary>Deliberately unsupported (12)</summary>
`CreateDBCluster`, `CreateDBInstanceReadReplica`, `CreateOptionGroup`, `DeleteDBCluster`, `DeleteOptionGroup`, `DescribeDBClusters`, `DescribeOptionGroups`, `FailoverDBCluster`
`ModifyDBCluster`, `ModifyOptionGroup`, `PromoteReadReplica`, `RestoreDBInstanceToPointInTime`
</details>
<details><summary>Registered outside the pinned model (4)</summary>
`GetDBBootstrapConfig`, `PollDBCommands`, `RegisterDBInstance`, `SubmitDBStateChange`
</details>
## s3
+1
View File
@@ -16,6 +16,7 @@ var modelFiles = map[Service]string{
ECS: "models/apis/ecs/2014-11-13/api-2.json",
ElasticLoadBalancingV2: "models/apis/elasticloadbalancingv2/2015-12-01/api-2.json",
IAM: "models/apis/iam/2010-05-08/api-2.json",
RDS: "models/apis/rds/2014-10-31/api-2.json",
S3: "models/apis/s3/2006-03-01/api-2.json",
STS: "models/apis/sts/2011-06-15/api-2.json",
}
+1
View File
@@ -29,6 +29,7 @@ const (
ECS Service = "ecs"
ElasticLoadBalancingV2 Service = "elasticloadbalancingv2"
IAM Service = "iam"
RDS Service = "rds"
S3 Service = "s3"
STS Service = "sts"
)
+2 -1
View File
@@ -8,7 +8,7 @@ import (
)
func TestServices(t *testing.T) {
want := []Service{ACM, EC2, ECR, ECS, ElasticLoadBalancingV2, IAM, S3, STS}
want := []Service{ACM, EC2, ECR, ECS, ElasticLoadBalancingV2, IAM, RDS, S3, STS}
if got := Services(); !reflect.DeepEqual(got, want) {
t.Fatalf("Services() = %v, want %v", got, want)
}
@@ -26,6 +26,7 @@ func TestLoadAllModels(t *testing.T) {
ECS: {"2014-11-13", 56, 400},
ElasticLoadBalancingV2: {"2015-12-01", 46, 357},
IAM: {"2010-05-08", 159, 497},
RDS: {"2014-10-31", 162, 745},
S3: {"2006-03-01", 99, 604},
STS: {"2011-06-15", 8, 76},
}
+5
View File
@@ -5,6 +5,7 @@ import (
gateway_ecrapi "github.com/mulgadc/spinifex/spinifex/gateway/ecrapi"
gateway_ecs "github.com/mulgadc/spinifex/spinifex/gateway/ecs"
gateway_rds "github.com/mulgadc/spinifex/spinifex/gateway/rds"
)
// ServiceOperationInventory describes the authoritative dispatch state for an
@@ -45,6 +46,10 @@ func AWSOperationInventory() map[string]ServiceOperationInventory {
"iam": {
Registered: mapKeys(iamActions),
},
"rds": {
Registered: gateway_rds.ActionNames(),
Unsupported: gateway_rds.UnsupportedActionNames(),
},
"sts": {
Registered: mapKeys(stsActions),
},
+4
View File
@@ -23,6 +23,10 @@ func TestAWSOperationInventoryClassifiesDispatchers(t *testing.T) {
require.NotContains(t, inventory["ecr"].Stubbed, "DescribeRepositories")
require.Contains(t, inventory["ecr"].Unsupported, "StartImageScan")
require.Contains(t, inventory["rds"].Registered, "CreateDBInstance")
require.NotContains(t, inventory["rds"].Unsupported, "CreateDBInstance")
require.Contains(t, inventory["rds"].Unsupported, "CreateDBCluster")
_, hasS3 := inventory["s3"]
require.False(t, hasS3, "S3 is delegated to Predastore, not dispatched here")
}
+42 -25
View File
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"log/slog"
"sort"
"github.com/mulgadc/spinifex/spinifex/awsec2query"
"github.com/mulgadc/spinifex/spinifex/awserrors"
@@ -56,19 +57,6 @@ func typed[In any](handler func(context.Context, *In, *nats.Conn, Caller) (any,
}
}
// Recognised but deliberately outside v1, so a client sees "not offered" rather
// than "you typo'd the action name".
func unsupported() Handler {
return rejectWith(awserrors.ErrorOperationNotSupported)
}
func rejectWith(code string) Handler {
return func(ctx context.Context, action string, _ map[string]string, _ *nats.Conn, _ Caller) ([]byte, error) {
slog.DebugContext(ctx, "RDS: action not available", "action", action, "code", code)
return nil, errors.New(code)
}
}
// One entry per action: what serves it, whether it is agent-only, and which
// resource its policy check evaluates against. Both authorization facts live on
// the table so a new action cannot be added without deciding either.
@@ -77,6 +65,9 @@ type actionDef struct {
// Agent-only: refused to every customer principal by class, before any
// policy is evaluated.
internal bool
// Recognised but deliberately outside v1, so a client sees "not offered"
// rather than "you typo'd the action name". Carries no handler.
unsupported bool
// nil evaluates against "*", which is right for creates and for describes
// that filter rather than address one resource.
scope *resourceScope
@@ -138,18 +129,18 @@ var actions = map[string]actionDef{
// Recognised but out of scope. Read replicas, Aurora clusters and option
// groups are not offered at all; point-in-time restore waits on WAL
// archiving.
"CreateDBInstanceReadReplica": {handler: unsupported()},
"PromoteReadReplica": {handler: unsupported()},
"CreateDBCluster": {handler: unsupported()},
"ModifyDBCluster": {handler: unsupported()},
"DeleteDBCluster": {handler: unsupported()},
"DescribeDBClusters": {handler: unsupported()},
"FailoverDBCluster": {handler: unsupported()},
"CreateOptionGroup": {handler: unsupported()},
"ModifyOptionGroup": {handler: unsupported()},
"DeleteOptionGroup": {handler: unsupported()},
"DescribeOptionGroups": {handler: unsupported()},
"RestoreDBInstanceToPointInTime": {handler: unsupported()},
"CreateDBInstanceReadReplica": {unsupported: true},
"PromoteReadReplica": {unsupported: true},
"CreateDBCluster": {unsupported: true},
"ModifyDBCluster": {unsupported: true},
"DeleteDBCluster": {unsupported: true},
"DescribeDBClusters": {unsupported: true},
"FailoverDBCluster": {unsupported: true},
"CreateOptionGroup": {unsupported: true},
"ModifyOptionGroup": {unsupported: true},
"DeleteOptionGroup": {unsupported: true},
"DescribeOptionGroups": {unsupported: true},
"RestoreDBInstanceToPointInTime": {unsupported: true},
}
// Checked before the IAM policy check, so an unknown action is rejected as
@@ -167,5 +158,31 @@ func Dispatch(ctx context.Context, action string, q map[string]string, nc *nats.
if !ok {
return nil, errors.New(awserrors.ErrorInvalidAction)
}
if def.unsupported {
slog.DebugContext(ctx, "RDS: action not available", "action", action)
return nil, errors.New(awserrors.ErrorOperationNotSupported)
}
return def.handler(ctx, action, q, nc, caller)
}
// ActionNames returns every action the gateway recognises, in stable order.
func ActionNames() []string {
return actionNames(func(actionDef) bool { return true })
}
// UnsupportedActionNames returns the recognised actions that deliberately
// report the feature as unavailable rather than serving it.
func UnsupportedActionNames() []string {
return actionNames(func(def actionDef) bool { return def.unsupported })
}
func actionNames(match func(actionDef) bool) []string {
names := make([]string, 0, len(actions))
for name, def := range actions {
if match(def) {
names = append(names, name)
}
}
sort.Strings(names)
return names
}