From 456a60717bf4d77e372c4fbc0b956de378cbc496 Mon Sep 17 00:00:00 2001
From: Julian Sommer <115516918+juliansommer@users.noreply.github.com>
Date: Thu, 6 Aug 2026 12:00:02 +1000
Subject: [PATCH] feat: add RDS to awsmodel coverage
---
docs/COMMANDS.md | 54 +++++++++++++++++++-
docs/aws-model-operation-coverage.md | 75 ++++++++++++++++++++++++++++
internal/awsmodel/embed.go | 1 +
internal/awsmodel/model.go | 1 +
internal/awsmodel/model_test.go | 3 +-
spinifex/gateway/operations.go | 5 ++
spinifex/gateway/operations_test.go | 4 ++
spinifex/gateway/rds/handler.go | 67 +++++++++++++++----------
8 files changed, 182 insertions(+), 28 deletions(-)
diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md
index 6c363cd78..6ad5985f3 100644
--- a/docs/COMMANDS.md
+++ b/docs/COMMANDS.md
@@ -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 |
diff --git a/docs/aws-model-operation-coverage.md b/docs/aws-model-operation-coverage.md
index 8e1e60b16..d59381bf8 100644
--- a/docs/aws-model-operation-coverage.md
+++ b/docs/aws-model-operation-coverage.md
@@ -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.
+
+
+## rds
+
+Implements **24 of 162** modelled operations (14.8%).
+
+Implemented (24)
+
+`AddTagsToResource`, `CreateDBInstance`, `CreateDBParameterGroup`, `CreateDBSnapshot`, `CreateDBSubnetGroup`, `DeleteDBInstance`, `DeleteDBParameterGroup`, `DeleteDBSnapshot`
+
+`DeleteDBSubnetGroup`, `DescribeDBInstanceAutomatedBackups`, `DescribeDBInstances`, `DescribeDBParameterGroups`, `DescribeDBParameters`, `DescribeDBSnapshots`, `DescribeDBSubnetGroups`, `DescribeEvents`
+
+`ListTagsForResource`, `ModifyDBInstance`, `ModifyDBParameterGroup`, `RebootDBInstance`, `RemoveTagsFromResource`, `RestoreDBInstanceFromDBSnapshot`, `StartDBInstance`, `StopDBInstance`
+
+
+
+
+Missing from dispatch (126)
+
+`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`
+
+
+
+
+Registered stubs (0)
+
+None.
+
+
+
+Deliberately unsupported (12)
+
+`CreateDBCluster`, `CreateDBInstanceReadReplica`, `CreateOptionGroup`, `DeleteDBCluster`, `DeleteOptionGroup`, `DescribeDBClusters`, `DescribeOptionGroups`, `FailoverDBCluster`
+
+`ModifyDBCluster`, `ModifyOptionGroup`, `PromoteReadReplica`, `RestoreDBInstanceToPointInTime`
+
+
+
+
+Registered outside the pinned model (4)
+
+`GetDBBootstrapConfig`, `PollDBCommands`, `RegisterDBInstance`, `SubmitDBStateChange`
+
+
## s3
diff --git a/internal/awsmodel/embed.go b/internal/awsmodel/embed.go
index 0d21ef315..de8fdf341 100644
--- a/internal/awsmodel/embed.go
+++ b/internal/awsmodel/embed.go
@@ -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",
}
diff --git a/internal/awsmodel/model.go b/internal/awsmodel/model.go
index 01ba0e1cf..14a6933ab 100644
--- a/internal/awsmodel/model.go
+++ b/internal/awsmodel/model.go
@@ -29,6 +29,7 @@ const (
ECS Service = "ecs"
ElasticLoadBalancingV2 Service = "elasticloadbalancingv2"
IAM Service = "iam"
+ RDS Service = "rds"
S3 Service = "s3"
STS Service = "sts"
)
diff --git a/internal/awsmodel/model_test.go b/internal/awsmodel/model_test.go
index f3580c2cd..7c2854794 100644
--- a/internal/awsmodel/model_test.go
+++ b/internal/awsmodel/model_test.go
@@ -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},
}
diff --git a/spinifex/gateway/operations.go b/spinifex/gateway/operations.go
index fa4d31b34..ccdcfbc32 100644
--- a/spinifex/gateway/operations.go
+++ b/spinifex/gateway/operations.go
@@ -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),
},
diff --git a/spinifex/gateway/operations_test.go b/spinifex/gateway/operations_test.go
index 11621b84e..91969fc9e 100644
--- a/spinifex/gateway/operations_test.go
+++ b/spinifex/gateway/operations_test.go
@@ -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")
}
diff --git a/spinifex/gateway/rds/handler.go b/spinifex/gateway/rds/handler.go
index d1fec09da..dbdd8a984 100644
--- a/spinifex/gateway/rds/handler.go
+++ b/spinifex/gateway/rds/handler.go
@@ -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
+}