v2.2.0 - #310
Merged
Merged
Conversation
…t isolation (#244) Out-of-process and parallel test suites share one long-lived `cloudemu serve` process and had no way to get a clean slate between tests. Add a control plane at /_cloudemu on the standalone server: - POST /_cloudemu/reset — rebuild every provider (and the shared Kubernetes data-plane) to empty state and swap it in atomically; in-flight requests finish against the old state, new requests see the fresh one. - GET /_cloudemu/health — liveness. - /_cloudemu/seed — reserved, returns 501 (needs the #250 fixture loader). New server/admin package (Backend hot-swap + Control routing) keeps the state lifecycle out of the wire handlers and the core server.Server — reset reuses the existing NewAWS/DriversFrom construction path, so no mock, handler, or driver code changes. Enabled by default; --admin=false to disable. Acceptance (out-of-process, real SDK): create an S3 bucket, POST reset, confirm ListBuckets is empty. Plus server/admin unit tests and docs.
feat: /_cloudemu/reset control plane for standalone-server test isolation (#244)
…, #244) Add a seed package that loads JSON fixtures (buckets, tables, secrets, instances) into a driver Target. Fixtures are provider-agnostic — they name resource kinds and Apply writes them through the driver interfaces every provider implements, so one file seeds AWS, Azure, or GCP. LoadFS reads go:embed-ed fixtures. Wire it into the standalone server: POST /_cloudemu/seed applies a fixture to the provider on that port (shares the reset mutex so seed and reset can't run against each other's half-built state). The seed endpoint is now real (was 501); a nil seeder (e.g. the Kubernetes port) still returns 501. Tests: seed package acceptance (embed fixtures, seed AWS drivers, read back via real S3 + DynamoDB SDK clients, plus secrets/instances via drivers); admin unit tests for the seed route; out-of-process serve test that POSTs a fixture and reads the seeded object back through the S3 SDK. Docs updated.
…oversize (#250 review) Review round-2 fixes: - Fixtures.Validate runs before any write, so an invalid fixture (e.g. a table with no partitionKey, whose items would silently collapse to one key) is rejected with nothing created — no half-seed, no silent data loss. - seedFor reads the provider Target under rebuildMu but runs seed.Apply outside it, so a large fixture (or --latency) no longer blocks resets for its whole duration. - The seed endpoint reads one byte past the 32 MiB cap and returns 413 instead of a silently-truncated body that fails JSON parsing. - Fixtures.ResourceCount reports actual resources (objects, items, instance count), not top-level entries. - Adds seed error-path tests (malformed JSON, nil driver, validation-before- write) and a ResourceCount test.
Add a multi-stage Dockerfile that builds cmd/cloudemu into a static binary on a
distroless nonroot base, a GHCR publish workflow that pushes semver + latest
tags on release tags, a docker-compose.yml example, and a .dockerignore.
The image runs 'serve --host 0.0.0.0' and exposes 4566/4568/4569/4570. Docs
gain a Docker section.
Acceptance (verified with a local build + run): docker run starts the server;
S3 CreateBucket/PutObject/GetObject over the mapped port round-trips
('hi from docker'), and POST /_cloudemu/reset clears it. Unblocks non-Go users
and the Testcontainers module (#248).
feat: publishable Docker image for the standalone server (#247)
A separate nested Go module (contrib/testcontainers) that runs the cloudemu standalone server in a container for out-of-process integration tests. Keeping it out of the core module means testcontainers-go and the Docker client it pulls in never touch every cloudemu user's go.sum. API: Run(ctx, opts...) → Container with AWSEndpoint/AzureEndpoint/GCPEndpoint/ KubernetesEndpoint, Reset (clean slate between tests), and Seed (loads a fixture via /_cloudemu/seed). WithImage overrides the default GHCR image. Acceptance (verified with Docker): Run builds+starts the container, drives S3 over the mapped endpoint, and Reset/Seed round-trip. A CI job compile-checks the module (build+vet); the container test needs Docker and runs on demand. Docs + module README added.
TestDDBItemJourney (and, latently, the S3 suite) intermittently failed CI with 'use of closed network connection' on GetItem/UpdateItem at HTTP 200: the aws-sdk-go-v2 client reuses keep-alive connections, and under the parallel load of 'go test ./...' the httptest server can close one between requests. Because these suites set NopRetryer (to observe error semantics on a single attempt), the transient wasn't retried and failed hard. Give both suites' clients an HTTP transport with DisableKeepAlives, so each request uses a fresh connection — no stale-connection reuse — while keeping the single-attempt error semantics. Only these two suites use NopRetryer.
test(aws): de-flake s3/dynamodb lifecycle suites (CI keep-alive flake)
The link used tree/master, but the module isn't on master yet and the repo's other doc links use development (the default branch). Match that.
feat: Testcontainers-Go module for cloudemu (#248)
…arameterVersionNotFound (#266) - GetParametersByPath and DescribeParameters now honor MaxResults/NextToken (paginated in the handler over the driver's stable name-sorted result set) instead of returning the entire set at once. Opaque offset token; malformed token -> ValidationException. - GetParameter on an existing parameter with a missing version/label now returns the distinct ParameterVersionNotFound (driver sentinel ErrVersionNotFound) rather than folding into ParameterNotFound. Deferred (documented in #266): ParameterFilters/tags, SecureString KMS, advanced parameter policies.
…es (#266) - QueryEntities: an OData $filter using any operator beyond eq/and (ne, gt, ge, lt, le, or, grouping) — or a value literal that splits through " and "/ " or " — now returns 400 InvalidInput instead of silently degrading to match-all (a data-correctness hazard where a narrowing query returned the whole table). - parseKeyPredicate: a single-entity key predicate must name BOTH PartitionKey and RowKey; a partial or malformed predicate returns 400 InvalidInput rather than a partial key that then reports the entity as not-found (404). Deferred (documented in #266): Queue enqueue PopReceipt (needs a ReceiptHandle on the shared messagequeue SendMessageOutput); the Queue/Blob shared-hostname collision (needs host-based routing, tied to #224).
…ound (#266) - Add Bucket.ListParts to the shared storage driver (implemented by S3, Blob, and GCS — all already buffer parts as map[int][]byte). The S3 wire handler's ListParts now enumerates the upload's parts ordered by part number with their ETag/Size, instead of always returning an empty list — resumable-upload tooling can read back what it has sent. Unknown upload -> NoSuchUpload (404). - UploadPart now rejects a partNumber outside 1..10000 with InvalidArgument, matching S3's limit. Deferred (documented in #266): object-level versionId history / real ListObjectVersions; non-GET ?uploads/?versions sub-resource 405.
#266) Review of #293 surfaced two issues: - Data race: ListParts iterated the multipart parts map with no lock while UploadPart writes it (the SDK uploader sends parts concurrently) — a pre-existing gap ListParts extended. Added a sync.Mutex to the multipart struct in all three storage providers (S3/Blob/GCS) and guarded parts access in UploadPart/ListParts/CompleteMultipartUpload. New -race concurrency test. - parseEqClause rejected legitimate $filter clauses with runs of whitespace ('Prop eq \'v\''); token-by-token cutting now tolerates extra spaces. Covered by a whitespace case in the table fidelity test.
…cycle suites TestDDBItemJourney (and the S3 suite) still flaked on CI: under parallel load httptest closes the TCP connection while the SDK reads a 200 response body -> 'use of closed network connection'. DisableKeepAlives didn't fix it because the standard retryer classifies that read error (net.OpError Op=read, non-temporary) as non-retryable, and NopRetryer turned the transient blip into a hard failure. Replace NopRetryer with a standard retryer whose retryables list contains ONLY a classifier for that connection error, so the transient transport failure is retried while API errors and the emulator's 500s are still observed on exactly one attempt (preserving negative-path assertions).
…#266) - SSM DeleteParameter/DeleteParameters strip a :version/:label selector like the read paths, so a selector addresses the base parameter (SSM has no per-version delete) instead of a literal name containing ':'. - S3 bucketOp/objectOp: a non-GET request with ?uploads, or non-GET with ?versions, or non-POST object ?uploads, now returns 405 instead of silently falling through to CreateBucket/DeleteBucket/PutObject (which ignored the sub-resource). Prevents e.g. 'PUT /{bucket}?uploads' from creating a bucket. Still deferred (feature-scale or cross-service risk; recommend separate PRs): SSM ParameterFilters/tags + parameter policies + SecureString KMS; S3 object versionId history / ListObjectVersions; Azure Queue enqueue PopReceipt (shared messagequeue SendMessageOutput change across SQS/ServiceBus/PubSub) and the Queue/Blob shared-hostname routing (needs host-based routing, #224).
Review note: the retry classifier matched only the error string. Also match the typed sentinel net.ErrClosed via errors.Is (robust to any wording change), keeping the string check as a fallback for a wrapper that breaks the Unwrap chain.
fix: fidelity gaps (#266) — SSM, Azure Tables, S3 (batches 1+2)
…kers (#266) Implements S3 object versioning in the AWS S3 provider + wire handler, replacing the flag-only stub that kept no history. - New optional driver.VersionedBucket interface (implemented by the S3 mock; Azure/GCS unaffected) plus VersionID/DeleteMarker on the shared ObjectInfo. - S3 mock keeps a per-key version chain: Enabled writes get a fresh versionId, Suspended writes reuse the 'null' version, unversioned keeps no history. Top-level delete on an Enabled bucket appends a delete marker (current object hidden); deleting a specific versionId permanently removes it and recomputes the current object. - Handler: tri-state Put/GetBucketVersioning (Enabled/Suspended/unset — fixes the Suspended-vs-never-configured gap), ?versionId on GET/HEAD/DELETE, x-amz-version-id / x-amz-delete-marker response headers, and a real ListObjectVersions (versions + delete markers, newest-first, IsLatest). Tests: rewrote the lifecycle versioning test to assert real history via the SDK (distinct version ids, version-addressable GET, delete markers, version delete); added driver-level tests for Suspended null-reuse and current recompute. Deferred (still in #266): Azure Blob / GCS object versioning (their SDK-compat surfaces don't expose S3-style versioning); versioning-aware lifecycle expiry.
…266) Review of #294 found: - CompleteMultipartUpload wrote the assembled object via a bare objects.Set, bypassing versioning — on an Enabled bucket the object got no version id and no history entry. Route it through storeObject like PutObject/CopyObject. - objectFromVersion aliased the stored version's metadata map into the current object (infoFromVersion already clones); clone it too for consistency. Tests: a completed multipart upload is now versioned (real version id + one history entry); GET of a delete-marker version id errors.
feat(s3): real object versioning (#266)
…296) * fix(serve): advertise the real Kubernetes data-plane endpoint `cloudemu serve` started the shared Kubernetes data plane and routed /k8s/{uid}/... to it, but the managed-Kubernetes control planes still advertised the Wave 1 sentinel: "endpoint": "https://EKS-DATAPLANE-NOT-IMPLEMENTED.cloudemu.local" so any tool that derives a kubeconfig from DescribeCluster / GetCluster — which is what real EKS/GKE/AKS clients do — got an address that does not resolve. The data plane was reachable the whole time; nothing told the control planes where it was. Two things were missing, and each alone is insufficient: 1. `APIServer.SetBaseURL` was never called, so `BaseURL()` returned "" and every provider's withK8sEndpoint took its early-return back to the sentinel. The listener binds c.host:c.k8sPort, so the base URL is derived from the same pair. 2. `Drivers.K8sAPI` is only the SERVER's path routing for /k8s/{uid}/... (see the comment on awsserver.Drivers.K8sAPI). The control-plane mocks hold their own reference and expose SetK8sAPI for it. Setting the drivers field without calling SetK8sAPI leaves m.k8sAPI nil, so no ClusterState is registered and no UID is recorded — the sentinel again. Applied to all three: cloud.EKS, cloud.GKE, cloud.AKS. Backward compatible: - No API change. SetBaseURL/SetK8sAPI already existed and are documented for exactly this ("providers read this back when rendering kubeconfigs"). - No flag change. Behaviour is unchanged when the data plane is disabled (-k8s-port ""), because k8sBackend stays nil and no BaseURL is set. - Library users are untouched: a caller that never calls SetBaseURL still gets the sentinel, which is what the existing fixtures assert. Verified: with the patch, `cloudemu serve` + `aws eks create-cluster` returns http://127.0.0.1:4570/k8s/<uid>, and POSTing a Namespace to that URL creates one. Suites for cmd/cloudemu, services/kubernetes and all three providers pass. * fix(eks): return a real self-signed CA, not an unparseable placeholder DescribeCluster advertised: -----BEGIN CERTIFICATE----- MIICloudemuStubCertificate -----END CERTIFICATE----- The code comment justified it as "SDK clients only base64-decode it for the kubeconfig, so a deterministic stub is enough". That holds for the raw SDK and breaks for anything that then builds a TLS config — which is what a kubeconfig is FOR. client-go calls AppendCertsFromPEM and fails with "unable to load root certificates: unable to parse bytes as PEM block" at kubernetes.NewForConfig, an error naming neither the cluster nor the CA. So any tool following the documented path to reach an EKS cluster — derive a kubeconfig from DescribeCluster — hit a wall as soon as it tried to use it. The emulator looked correct right up to the point of being used. Now generates a genuine self-signed CA certificate. Cached with sync.Once: generation is not free, and a stable CA across calls matches real EKS, where a cluster's CA does not change between DescribeCluster invocations. Generation failure falls back to an empty string rather than panicking. An empty CA makes client-go use the system roots, which is recoverable; a panic would take down an emulator whose entire purpose is keeping tests running. No API or behavioural contract change: the field is still a base64 PEM blob in the same place, it is now merely valid. No test pinned the placeholder value. Provider and cmd suites pass. * feat(kubernetes): serve API discovery, and reject protobuf with 415 The data plane implemented Namespace/Pod/Service/ConfigMap/Secret/Deployment and served them correctly — but only to callers that hit a resource path directly. Anything that NEGOTIATES first, which kubectl and helm both do, died at startup with: couldn't get current server API group list: the server could not find the requested resource so it never reached the resources that worked. Discovery is therefore not a nicety; it is what makes the existing data plane reachable by real tooling. Adds /api, /apis, /api/v1, /apis/apps/v1 and /version. The advertised set is derived from what ClusterState.ServeHTTP actually dispatches, so discovery can never promise a resource the emulator does not serve — a tool that trusts discovery and then 404s is worse than one that cannot start. Verbs are limited to what the handlers support, for the same reason. shortNames are included so `kubectl get ns` / `get deploy` resolve rather than reporting "the server doesn't have a resource type", which reads like the resource is missing rather than un-aliased. Serving discovery then exposed a second issue: kubectl negotiates PROTOBUF for built-in types once they are advertised, and the JSON decoder reported the frame's "k8s\x00" magic prefix as `invalid character 'k'` — a message that reads like a malformed request rather than an encoding the server does not speak. Writes failed while reads succeeded, which is a confusing place to leave a user. Protobuf bodies now return 415 with a clear message. 415 is the status the Kubernetes client libraries are built to handle; 400 made clients give up. Full protobuf decoding is a larger piece of work and is not attempted here. Verified against a live emulator: `kubectl get ns` and `kubectl get deployments -A` work, and `helm install` gets past discovery to chart rendering. Two follow-ups are now visible and were not before: /openapi/v2 (helm validates manifests against it unless --disable-openapi-validation) and the networking.k8s.io/policy API groups that common charts require. services/kubernetes, cmd/cloudemu and providers/aws/eks suites pass. * feat(kubernetes): PodDisruptionBudget (policy/v1) and an /openapi/v2 stub PDB is here because real charts render one as a matter of course — a chart producing a Deployment and a Service almost always produces a PDB beside them. Without the resource, `helm install` dies at object-building with "no matches for kind PodDisruptionBudget" BEFORE any of the workload kinds the emulator does support are reached. Supporting the workloads but not the PDB that ships with them means a realistic chart still cannot be installed. Full CRUD under /apis/policy/v1, mirroring the deployments handler, and advertised in discovery with the `pdb` shortName. Status reports only ObservedGeneration: there is no disruption controller here, so inventing eviction counts would assert semantics the emulator cannot honour. /openapi/v2 returns a minimal valid Swagger 2.0 envelope with no definitions. helm validates rendered manifests against it unless the caller passes --disable-openapi-validation, and its absence surfaces as "failed to download openapi" AFTER the chart renders — which reads like a chart fault rather than a missing server capability. Empty definitions means "nothing to contradict" (helm skips kinds it finds no schema for) rather than "everything invalid"; publishing hand-written partial schemas would be worse, since a subtly wrong schema rejects valid manifests and would have the emulator asserting API shapes it does not enforce. KNOWN INCOMPLETE, and the reason this is a stub: helm requests OpenAPI as PROTOBUF (application/com.github.proto-openapi.spec.v2+protobuf) and parses the response as such regardless of the Content-Type sent back, so a JSON body fails with "proto: cannot parse invalid wire-format data". Serving it properly needs the protobuf encoding, not another JSON shape. The endpoint is still an improvement — it is now the encoding that is missing rather than the route — but `helm install` does not yet complete against the emulator without --disable-openapi-validation. services/kubernetes and cmd/cloudemu suites pass. * feat(ec2): DescribeAvailabilityZones Provisioning a VPC is the first step of nearly every datastore plan, and choosing subnets requires knowing the region's zones. Without this action a datastore job fails at its very first step with "unknown action" — before any of the VPC/subnet/RDS behaviour the emulator does implement is reached, so the implemented surface was unreachable for that whole class of job. Reports three zones. Three is the meaningful number rather than an arbitrary one: it is the minimum real regions offer, and a subnet group spanning two AZs is a hard RDS requirement, so a region reporting fewer would make datastore provisioning untestable rather than merely approximate. Zones are derived from the requested region (us-east-1 -> us-east-1a/b/c) instead of a fixed table, so any region a caller uses behaves consistently rather than only the ones someone remembered to enumerate. The region is read from the SigV4 credential scope because the EC2 query API carries no region parameter — the signature is the only place the caller states which region it believes it is addressing. Verified against a real provisioner: a MySQL provisioning job that previously failed at "unknown action: DescribeAvailabilityZones" now advances past network AZ selection to the next unimplemented action (AllocateAddress), confirming the handler is reached and its response parsed. server/aws/ec2 suite passes. * feat(ec2): Elastic IP query-protocol handlers (Allocate/Release/Describe) The Networking driver already implemented AllocateAddress, ReleaseAddress, DescribeAddresses, AssociateAddress and DisassociateAddress — only the wire layer was missing, so callers got "unknown action" for behaviour that existed underneath. This is a wire gap, not a feature gap. It is the first hard stop in every VPC-with-private-subnets plan: a NAT gateway cannot be created without allocating an EIP first, so a datastore provisioning job fails at step 1 (create_network) before reaching any of the VPC/subnet/NAT behaviour the emulator does implement. ReleaseAddress reads AllocationId or PublicIp — only the former is meaningful after EC2-Classic's 2022 retirement, but SDK versions differ in which they send. Domain is reported as "vpc" unconditionally, matching real AWS post-Classic, so callers need not branch on a value that can no longer vary. Associate/Disassociate are deliberately NOT wired here: the provisioner does not call them (NAT gateways take an allocation id directly), and wiring actions no caller exercises would add untested surface. They remain available on the driver when a caller needs them. server/aws/ec2 suite passes. * feat(ec2): route-table association lifecycle and network delete actions A caller tearing down a VPC learns association ids only from DescribeRouteTables, so AssociateRouteTable on its own is not enough: the association has to survive the projection back out. driver.RouteTable grows an Associations field, joined in DescribeRouteTables from the association store, and the wire layer emits it as associationSet so the AWS SDK populates RouteTable.Associations[].RouteTableAssociationId. Wires the remaining network teardown actions, all of which the driver already implemented and only the HTTP layer was missing: DeleteRoute, DeleteRouteTable, DeleteInternetGateway, AssociateRouteTable, DisassociateRouteTable. RouteTableAssociation grows a Main flag: callers skip the main association during teardown and let it die with the VPC, so the distinction has to be visible. * feat(ec2): model elastic network interfaces and their teardown drain A VPC delete is refused while interfaces remain attached, so callers drain ENIs before deleting the network. Emulating that needs interfaces that actually exist: a NAT gateway now holds an ENI in its subnet for as long as it lives, and releases it on delete. DeleteNetworkInterface refuses an attached interface, which is what tells a draining caller its work is not finished rather than letting it report a clean teardown over a network it never drained. DescribeNetworkInterfaces honours vpc-id, subnet-id and status filters. An unrecognised filter narrows to nothing rather than matching everything — a caller draining by vpc-id must never be handed interfaces from another VPC. Interfaces are modelled in all three providers so the driver contract stays uniform. * feat(rds): DB subnet groups Managed databases are placed into a named set of subnets, and a caller provisioning one creates that group first — without it the provision stops at its first step. VpcId is derived from the member subnets rather than taken as input, matching real RDS. This is not cosmetic: a VPC teardown lists subnet groups and matches on VpcId to decide which are its own, so a blank value there leaks every group on every delete. The RDS mock resolves it through the networking mock, wired in the AWS composition root alongside SetMonitoring. A duplicate name reports DBSubnetGroupAlreadyExists. Callers re-running a provision treat that specific code as "already there, carry on", so folding it into a generic error would turn a safe re-run into a hard failure. Subnet groups are an AWS concept — Azure and GCP use vnet integration — so this is an optional capability discovered by type assertion rather than a new method on the RelationalDB interface. Drivers that do not model them answer InvalidAction, which is the truthful response for a cloud with no such resource, and no other provider had to change. * feat(elasticache): cache subnet groups Mirrors the RDS subnet-group work: VpcId is derived from the member subnets, because a VPC teardown lists cache subnet groups and deletes the ones whose VpcId matches the network going away. An empty value there strands every group. Needed even by callers that never provision a cache — a teardown sweeps RDS and ElastiCache subnet groups in the same step, so an unimplemented DeleteCacheSubnetGroup fails the whole step and leaves the VPC standing. Same optional-capability shape as RDS: discovered by type assertion, so no other cache driver had to change. * feat(elasticache): replication groups Redis provisioning with more than one node creates a replication group rather than a bare cache cluster, so without this the redis path stops at its create step. The primary endpoint is the point of the resource: callers read NodeGroups[0].PrimaryEndpoint.Address to build a connection string, and a group that reports success without one hands back an unusable cache. The node group is therefore always present, even for a single-node group. ReplicationGroupAlreadyExists and ReplicationGroupNotFoundFault are string- matched by callers to make a re-run idempotent and to treat an absent group as already deleted, so both codes are reproduced verbatim. * feat(iam): materialise AWS-managed policies on first reference AWS publishes hundreds of managed policies and every account already has all of them, so callers attach them — AmazonSSMManagedInstanceCore for an instance profile, AmazonEKSClusterPolicy for a cluster role — without a preceding CreatePolicy. The emulator required one, turning an ordinary AttachRolePolicy into NoSuchEntity and stopping any flow that grants a role SSM or EKS access. Any well-formed arn:aws:iam::aws:policy/ ARN is honoured rather than a seeded list, which would only move the failure to the first policy nobody thought to enumerate. Pathed ARNs keep their path. Customer-managed ARNs are untouched: attaching one that was never created is still NotFound, because there the error is a real caller bug worth surfacing. The stored document is an explicit empty-statement placeholder. The emulator does not evaluate policy documents, and inventing the real contents of these policies would be fiction presented as fact. * feat(ssm): resolve AWS-published AMI parameters on first read Callers resolve "current image for this distro" through the public /aws/service/.../ami-id parameters rather than pinning an id that goes stale every couple of weeks. AWS publishes them in every account, so requiring a PutParameter first made any instance launch fail with ParameterNotFound. The id is derived from the parameter name, so repeated reads are stable (a reconciling caller must not see the AMI change under it) and two distros never collide on one image. Deliberately narrow: only the ami-id family is synthesised. Other /aws/service/ parameters carry payloads that cannot be derived — the ECS-optimised family holds a JSON blob — and returning an invented value would be worse than reporting it absent, so those stay NotFound. Unset user parameters are likewise untouched. * feat(ssm): Run Command send and invocation polling Bootstrapping an instance goes through SendCommand plus a GetCommandInvocation poll, so without them a VM provision stops after the instances are already running — the most expensive place to fail. LIMITATION, stated plainly: nothing executes. An emulated instance has no guest operating system, so invocations report Success with empty output. This exercises a caller's send/poll orchestration — that it waits for a terminal status, reads the response code, handles failure — but it does NOT validate the script. A caller whose bootstrap script is wrong still sees success here. One send registers an invocation per target instance, because callers poll per instance and would hang on any that were dropped. Polling a command that was never sent is InvocationDoesNotExist rather than a fabricated success: that is a real caller bug and answering Success would bury it. * feat(elbv2): load balancer attributes and DescribeTags Enabling cross-zone load balancing is part of bringing an NLB up, so without ModifyLoadBalancerAttributes the load balancer step fails after the instances behind it are already running. LBAttributes grows an Extra map. AWS models attributes as open key/value pairs and adds new ones over time, so the typed struct silently dropped anything it had not been taught — cross-zone among them — and a caller reading back its own write would get a wrong answer. Unrecognised keys are now stored rather than echoed. Modify merges rather than replaces, matching AWS: a caller enabling cross-zone must not silently clear an idle timeout it set earlier. DescribeTags is added alongside because a sweep for orphaned infrastructure identifies its own load balancers by tag, and an empty answer reads as "not mine" — leaving the orphan standing. Both were wire-only gaps; the driver already stored attributes and tags. * fix(elbv2): describing a missing load balancer by ARN is LoadBalancerNotFound Real ELBv2 answers LoadBalancerNotFound when an explicitly named ARN does not exist. The emulator returned an empty list and no error, so a caller waiting for a delete to settle — which polls DescribeLoadBalancers with the ARN until it errors — kept polling to its timeout over a load balancer that was already gone, then reported the teardown as failed with everything downstream of it untouched. BEHAVIOUR CHANGE: an existing test asserted the empty-list answer. It encoded the emulator's behaviour rather than AWS's, and is updated here. An unfiltered describe is untouched and still returns whatever exists, including nothing. * feat(ec2): ModifyVpcAttribute Enabling DNS hostnames is part of standing a VPC up, and the emulator answered InvalidAction. Callers that discard the result never noticed; callers that check it could not proceed. A nil pointer means "unchanged". The real API accepts exactly one attribute per call, so treating an absent parameter as false would make a caller enabling DNS hostnames silently switch DNS support off. VPCs default to DNS support on and hostnames off, matching EC2. * fix: AWS fidelity corrections and vendor-neutral fixtures Three behaviours did not match the real services: DescribeNetworkInterfaces silently returned nothing for a filter it did not implement. Real EC2 answers InvalidParameterValue. Returning nothing is the worst of the options: a caller draining a VPC reads it as "nothing left to drain" and proceeds to a delete that then fails with DependencyViolation. Matching everything is equally bad — it hands back interfaces the caller never asked for and may delete. An explicit error cannot be mistaken for a result. network-interface-id and description are now supported alongside vpc-id, subnet-id and status. SendCommand reported the command as Success. Real SSM reports Pending — it has been accepted, not finished — and the caller learns the outcome from GetCommandInvocation. Reporting Success invites a caller to skip the poll it would need against the real service. DescribeTags resolved every ARN as a load balancer, so a target group ARN came back empty. Combined with the LoadBalancerNotFound fix in 3ec47d2 it became worse: a target group ARN reported LoadBalancerNotFound instead of its tags. Real DescribeTags takes both kinds in one call and does not care which is which, so ARNs are now resolved against both. Also documents a known gap: real EC2 gives every VPC a main route table whose association carries main=true. This emulator creates none, so no association is ever main. Self-consistent, but callers expecting a main association will not find one. Test fixtures renamed off a specific vendor's naming; no production code referenced it. * feat(ec2): main route tables EC2 creates a route table alongside every VPC, carrying the local route and an implicit association with main=true and no subnet. The emulator created none, so every VPC looked like it had one fewer route table than it does and the RouteTableAssociation.Main flag could never be true — a flag no producer ever set. It behaves as the real one does: it cannot be deleted on its own and its association cannot be disassociated, because both are implicit in the VPC rather than things a caller made. A caller sweeping a VPC's route tables has to skip it, and that refusal is how it learns to. Both disappear with the VPC, which is the only way they can be removed. Caller-created route tables are unaffected: still deletable, associations still disassociable, never main. * fix: replace permissive stand-ins with the real published sets Three places accepted more than the real services do. Each traded a false failure for a false success, which is the worse of the two: the emulator agreed with a caller that AWS would have rejected. IAM managed policies were materialised for any well-formed arn:aws:iam::aws:policy/ ARN. AWS publishes a finite set, so an ARN outside it is NoSuchEntity — and accepting anything meant a typo like AmazonEKSClusterPolicyy attached cleanly here and failed in production. Replaced with a catalogue of real policy names, pathed ones included; unknown names are rejected, which makes a missing entry a one-line fix rather than a silent divergence. SSM public parameters resolved any path ending in /ami-id. Restricted to the trees AWS actually publishes (amazon-linux, windows, canonical, debian, suse, marketplace, bottlerocket, eks-optimized). An invented distro now answers ParameterNotFound instead of handing back an image id that exists nowhere. SendCommand accepted any instance id. Real SSM answers InvalidInstanceId for a target that is not a managed instance, and that is the most common Run Command failure during bring-up — precisely the one worth surfacing early. Targets are now checked against the compute mock, wired in the AWS composition root. The AMI id itself stays synthesised: a real one changes as the publisher rebuilds the image, so there is no fixed value to copy. It is derived from the parameter name, so it is stable per parameter and distinct across distros. * fix(topology): resolve a subnet's route table by association, not by order findRouteTableForVPC returned whichever table was listed first for the VPC. That was only ever right while a VPC had exactly one; once main route tables existed alongside caller-created ones the arbitrary choice could land on a table whose routes do not govern the subnet, and every routing answer derived from it was wrong — TraceRoute reported a reachable destination as blocked. Resolution now follows the cloud: an explicit subnet association wins, and a subnet without one falls back to the VPC's main route table. Drivers that model no main table still get an answer rather than the subnet reading as unroutable. TestTraceRoute created a route table and never associated it with the subnet, then asserted traffic routed through it. That only passed because the emulator picked the sole table available; in the real cloud that subnet uses the main table and 8.8.8.8 is genuinely unreachable. The test now associates the table, which is what the scenario requires, and a new case pins the unassociated behaviour. * fix: report the AWS error code for the resource, not a generic one A duplicate DB subnet group answered ResourceAlreadyExistsFault and a duplicate replication group answered CacheClusterAlreadyExists. The messages read correctly, which is why string-matching callers were unaffected — but the SDK surfaces the CODE as a typed error, so a caller doing errors.As(&types.DBSubnetGroupAlreadyExistsFault{}) never matched and its idempotent re-run became a hard failure. RDS now distinguishes db subnet groups on both the not-found and already-exists paths. ElastiCache gained the same per-resource mapping, which it did not have at all: every error was reported as a cache cluster, so a missing replication group was indistinguishable from a missing cluster. Both are covered by tests that match the SDK's typed errors rather than grepping the message, since the message was never the part that was broken. * docs(services): document the new operations and optional capabilities Adds the network interface, VPC attribute and main-route-table behaviour to the networking reference, and the subnet-group, replication-group and load balancer attribute operations to their sections. The optional capabilities are marked as such and counted separately, because a driver without them is still complete — folding them into the core totals would imply every provider must implement an AWS-only concept. * feat(gcp): Cloud Routers A caller building a network with private subnets creates a router and then patches Cloud NAT onto it, so without routers the network step stops after the subnets exist and before anything on them can reach out. The whole step failed with methodNotAllowed, which named the method rather than the missing resource — the error sends an investigation in the wrong direction. Routers are held in the handler rather than the networking driver: the driver models the portable subset shared across clouds, and a router carrying embedded NAT blocks is specific to this provider's REST shape. The body is stored verbatim and echoed back, so a caller that patches a field this handler does not model still reads it. Patch replaces rather than merges, which is what makes a read agree with the write a caller just sent. * feat(gcp,azure): Service Networking connections and the subscriptions list Two endpoints a caller reaches before it can finish, both of which returned methodNotAllowed and stopped the work that followed. Service Networking backs private services access — a VPC reaching a Google-managed service over internal addresses. A caller sets a connection up while building a network and removes it while tearing one down, so its absence blocked teardown rather than just the feature. Connections are records here; nothing routes the peering they stand for. The Azure subscriptions collection is what a caller lists to verify a credential before connecting an account. The list is empty on purpose: this emulator has no tenant model, so it cannot say which subscriptions a credential reaches, and inventing some would fabricate an authorization boundary. A well-formed empty answer lets the caller complete the request and decide. * feat(gcp): global and regional addresses Private services access reserves an address range to carve out the block a managed service is peered into, and releases it when the network goes away. Without the resource the release fails, and every teardown step behind it is abandoned — the network, and the IAM the network depends on. Addresses are keyed by the scope they were reserved in, so a global address and a regional one sharing a name stay distinct. Held in the handler rather than the networking driver for the same reason as routers: a reserved range with a purpose and prefix length is this provider's shape, not the portable subset. With this a GCP cluster provisions AND tears down end to end against the emulator: all six create steps and all five delete steps. * feat(azure): resource groups Every Azure resource lives in a resource group, so a caller creates one before anything else and deletes it last. The API was unimplemented, so the first step of any Azure provisioning run failed and nothing behind it was reachable. A group reports provisioningState Succeeded immediately: it is usable the moment it exists, and a caller that polls would otherwise never see it leave a pending state. Deleting one that is already gone succeeds, because that is the caller's desired end state and a teardown retry must not fail on its second pass. The collection segment is matched case-insensitively — callers spell it both resourcegroups and resourceGroups — and a path continuing into /providers/... is a resource inside the group, left to the handler that owns it. With this an Azure cluster provisions and tears down end to end against the emulator. * fix: address review — enforce the ENI drain, and stop silent-empty describes Three things the review was right about. The ENI drain was described but never enforced. attachManagedENI and the test comments claimed a VPC delete is refused while an interface is attached, and DeleteVPC/DeleteSubnet deleted unconditionally — the emulator accepting what EC2 rejects, which is the class this branch set out to fix. Both now refuse with DependencyViolation while an attached interface remains, and the refusal is recoverable: detach, delete, and the delete succeeds. A test covers both the refusal and the recovery, because a refusal that cannot be cleared is just a wall. DescribeTargetGroups and DescribeNetworkInterfaces answered an empty list for a resource that does not exist — the same anti-pattern already fixed for load balancers. Both now report not-found for an explicitly named ARN or ID. Also: gofmt on three files (a CI blocker), stale "Phase 2" comments where the work has since landed, a nolint that named a case count now wrong, and a doubled error-code prefix in two elb messages — the code is added by the wire layer, so the driver naming it too produced "TargetGroupNotFound: TargetGroupNotFound: ...". * docs(services): document the handler-level resources and reconcile counts Five resources added on this branch have no driver behind them — routers, addresses, Service Networking connections, resource groups, the subscriptions list — so they had no place in the per-service tables and went undocumented. They are part of the emulated surface regardless, and a reader checking whether something is supported should not have to grep the server packages. A new section lists them with the reason each has no driver, alongside SSM Run Command and the materialised AWS-managed policies and published AMI parameters. Run Command gets its own table and repeats, in the place a reader will look, that nothing executes. Load Balancer moves to 21: the two attribute operations were always in the driver but unlisted, and the ELBv2 handler now exposes them. Handler-level resources are excluded from the totals — they have no driver operations to count — and that exclusion is stated rather than left to be inferred. * style: US spelling, and constants for values this branch duplicated The project lints for US spelling and this branch was written in British throughout — behaviour, recognise, materialise, catalogue, synthesised and friends across fourteen files. Mechanical, but it was a lint failure in every file the branch touched. Two goconst hits were caused by the new code rather than pre-existing: the replication-group path repeated the engine, node-type and status defaults the cache-cluster path already used. They are constants now, shared by both, so the two cannot drift apart. The duplicate-code warning on the two describe handlers is suppressed the way the codebase already suppresses it for per-resource describe pairs: the shape is duplicate by design, each reading its own collection. * fix: address the remaining review items PDB discovery advertised watch and patch, neither of which pdb.go implements — a reflector would open a watch that returns a list and never streams, and kubectl or helm would send a PATCH that 405s. Both failures land in the caller, far from the document that promised them. It now advertises only what it serves. DeleteCluster resolved the endpoint after deregistering the data plane, so the delete response described the cluster with the not-implemented sentinel rather than the endpoint it had been reachable on. Resolved before, since the response describes the cluster as it was. Two error codes said one thing and sent another. The unsupported-capability error named InvalidAction in its message while the generic mapping emitted InvalidParameterValue; SendCommand to a rejected target named InvalidInstanceId while surfacing the parameter-store ParameterNotFound. Callers match on the code, so both now emit what they claim. A malformed NumCacheClusters was coerced to a default instead of rejected, quietly building something other than what was asked for. Absent still means "unspecified"; present-but-unparseable is a ValidationException. NOT fixed — the k8s CA mismatch (#4). The data plane is HTTP while the cluster advertises a parseable CA, so a caller building a rest.Config from Endpoint plus CertificateAuthority alone fails the handshake. Clearing the CA was implemented and reverted: callers that build a kubeconfig need a parseable CA to construct one at all, and a full provisioning run broke at the first step that uses it. Resolving it properly means serving the data plane over TLS with this CA. The mismatch is now documented where the endpoint is set rather than left implicit. * refactor(networking): make interfaces and VPC attributes optional capabilities Both were added to the core Networking interface, which forced every provider to implement them. Azure and GCP model neither, so they carried byte-identical copies of the AWS implementation purely to satisfy the interface — three versions of the same logic to keep in sync, and two of them serving no caller. They are optional capabilities now, discovered by type assertion, matching the pattern already used for subnet groups, replication groups and Run Command. The duplicated Azure and GCP implementations are deleted. A driver that does not model them answers InvalidAction, which is the truthful response for a cloud with no such resource. ModifyVPCAttribute takes a VPCAttributeUpdate rather than positional pointers, so a new attribute can be added without changing every implementation and every call site. The service wrapper forwards both capabilities, since a caller holding a *Networking must see the same capability set as the driver beneath it — otherwise wrapping would hide them and every optional capability would look unimplemented. Compile-time assertions now cover the optional interfaces as well. Without them a signature drifting out of shape stops satisfying the interface silently and every call answers InvalidAction at runtime instead of failing the build. * feat(eks): make the advertised certificate authority certify the data plane DescribeCluster returned a self-signed CA whose private key was discarded the moment it was generated, so it could never sign anything. A caller building a rest.Config from Endpoint plus CertificateAuthority — the ordinary way to reach a cluster — presented that CA to a plain-HTTP listener and failed the handshake. Advertising a valid CA for a server it does not certify is worse than advertising none, because it reads as working. The CA key is retained now, and ServingTLSConfig mints a leaf signed by it for whoever serves the data plane. Served that way, the CA is true: a client validating the endpoint against it succeeds, with no skip-verify anywhere. Clearing the field instead was tried first and is worse — callers that build a kubeconfig need a parseable CA to construct one at all, and a full provisioning run broke at the first step that used it. The fix had to make the CA correct, not absent. * feat(discovery): surface interfaces and elastic IPs, and pin the PDB verbs The resources this branch added were invisible to Resource Explorer, Resource Graph and Cloud Asset. That is fine for a teardown driven by Describe against ids it already holds, but a discovery-driven sweep would walk past an elastic IP that is still costing money and an interface that is still blocking a VPC delete. Interfaces are gathered through the optional capability, so a driver that does not model them contributes nothing rather than failing the whole walk — a cloud with no interfaces has none to discover, which is not an error. Adds the tests the review found missing. Availability zones: that more than one is returned, since callers place subnets one per zone and a single-zone answer collapses a multi-AZ network into one that fails much later. Elastic IPs: the full reserve/read/release cycle, because an allocation outliving its teardown keeps accruing charges. PodDisruptionBudgets: that the advertised verbs match the implemented ones, so adding a verb to the advertisement without writing it fails here rather than inside whatever client trusted the document. * style(ec2): reuse the existing constants this branch duplicated The new interface code introduced a third "attached" and a second "true", the latter alongside a formTrue constant that already existed. Both now use the shared value. The dispatch functions are annotated the way the file already annotates its describe pairs: an action switch has this shape by design, and the duplication warning between two of them is not something to restructure away. * docs(services): file the provider-specific resources under what they are The section added earlier was organized around which layer holds a resource's state — "no driver" — and that is an implementation detail no reader of a resource reference is looking for. Someone asking whether Cloud Routers are supported looks under GCP networking, not under a section about driver absence. The "why no driver" column was design justification rather than reference material. It was also wrong in one row: SSM Run Command has a driver interface, so listing it as having none contradicted the code. It is documented now as the optional capability it is, next to the other two. Resources are grouped by provider and service, with what each supports. The materialized IAM policies and published AMI parameters are described as behavior of existing resources, because that is what they are — not resources in their own right. * fix(serve): serve the Kubernetes data plane over TLS, and surface CA failures ServingTLSConfig had no call sites. The certificate authority was made able to certify the data plane, and then the data plane carried on being served over plain HTTP — so the claim that the advertised CA certifies the endpoint was still false for anyone running `cloudemu serve`. A rest.Config built from Endpoint plus CertificateAuthority failed the handshake exactly as before. The listener now serves with a leaf signed by that CA and advertises https, so the two agree. Verified against the built binary: the presented certificate is CN=cloudemu-k8s issued by CN=cloudemu-eks-ca, and plain HTTP to the port is refused. initCA also swallowed generation failures. It ran under sync.Once and returned early on error, leaving the CA empty forever while stubCertificate handed that empty value to every cluster — the silent-placeholder failure this work exists to remove, reappearing one level down. It returns an error through sync.OnceValues now, and ServingTLSConfig propagates it. * fix: address the second review — a crash, a data-loss delete, and a race Three of these are defects in code this branch added. LBAttributes.Extra was shared, not copied. GetLBAttributes returned a struct copy whose map still aliased the stored one, and the attribute handler then wrote into it outside the lock while doing a read-modify-write. Two overlapping ModifyLoadBalancerAttributes on one load balancer crash the process with a concurrent map write. The map is copied on the way out and on the way in, so neither side can reach the other's state. A Service Networking delete with no network parameter erased every connection in the process, not just the caller's — the parameter defaulted to the match-everything wildcard. A delete now has to name its target. Interface fields were mutated without a lock. memstore copies the map on All() but the values are pointers, so a detach raced every concurrent read of the same interface. Guarded, and the suite passes under -race. Also: a subnet group whose member subnets do not resolve now leaves VpcId blank rather than silently deriving one, which is the teardown leak this branch set out to close; ReleaseAddress resolves a PublicIp back to its allocation id instead of passing it through as one, which always missed while the comment claimed both forms worked; the EKS-optimized AMI tree names its leaf image_id, so matching only ami-id left every EKS image lookup unresolved; and the new GCP handlers decode through the bounded reader their siblings use. * fix(discovery): surface interface-listing failures, and cover the new walkers walkNetworkInterfaces returned nil on any error, so a driver that models interfaces and then failed to list them produced a complete-looking inventory missing whatever could not be read — the opposite of what a sweep for orphans is for. Errors propagate now; a driver that does not implement the capability still contributes nothing, which is a different thing and stays silent. Adds the tests the re-review found missing: that a walk surfaces elastic IPs and interfaces at all, and that a failing listing surfaces rather than being swallowed. * fix(aws): complete the pointer-mutation race sweep, atomic LB attributes, delete-in-use The ENI race fix guarded one site. memstore hands out the stored pointer for every `Store[*T]`, so the same read-modify-write existed everywhere else that pattern is used — guarding ENIs alone left the class open. Swept the rest: VPC attributes, route creation/deletion, and elastic-IP association all mutate a shared pointer, and their describe paths read it. The mutex moves from a package global onto Mock, which also stops it being shared across emulator instances. Load-balancer attributes gain an atomic update. Modification is a read-modify-write, and doing it as Get-then-Put drops the lock in between, so two overlapping updates each read the same base and the second write discards the first. Exposed as an optional capability so other providers need no implementation, with the Get/Put path kept as the fallback. Subnet groups now refuse to delete while something is still placed in them (InvalidDBSubnetGroupStateFault / CacheSubnetGroupInUse), matching real AWS — a teardown that got a success here would strand a live instance in a group that no longer exists. Also: AssociateAddress/DisassociateAddress get the wire handlers the file header already claimed they had; IAM managed-policy materialization uses SetIfAbsent so two concurrent first-references cannot hand out mismatched ARNs; two bare body decodes get the cap their siblings use; and the dead findRouteTableForVPC wrapper is gone. Tests pin each fix rather than describe it — the race tests were confirmed to fail (10 race/lost-update reports) with the guards removed, and the EKS TLS test completes a real handshake trusting only the advertised CA, with a vacuity guard so an empty pool cannot make it pass.
…/AKS) (#297) * feat(discovery): surface Kubernetes clusters and node groups (EKS/GKE/AKS) Surface managed Kubernetes clusters and their node groups in resource discovery (Resource Explorer 2 / Azure Resource Graph / GCP Cloud Asset), so EKS cluster/nodegroup, GKE cluster/nodepool, and AKS managedcluster/ agentpool appear in the cross-service inventory like the other types. - resourcediscovery: a KubernetesClusters capability + provider-neutral DiscoveredCluster projection, a walkKubernetes walker, Cluster/NodeGroup types, and per-provider ARN/ID builders. The engine stays free of provider imports; each provider wires a thin adapter over its cluster mock. - inventory type maps: Azure managedclusters/agentpools, GCP container.googleapis.com/Cluster and /NodePool. - tests: walker unit tests plus end-to-end per-provider tests that create real EKS/GKE/AKS clusters + node groups and assert they surface; new map-table cases. - docs: surfaced resource-type table in services.md. * fix(discovery): address review on Kubernetes cluster ARNs and EKS listing - Thread the cluster's region and resource group into the k8s ARN/ID builders: GCP self-links now embed the cluster's own region and Azure IDs its real resource group, instead of the engine defaults (the region field and the ARN could previously disagree on GCP). - eksDiscovery now fails loud on DescribeCluster/ListNodegroups errors (previously a failed node-group listing silently dropped them from the inventory) and uses the EKS mock's own ARN verbatim rather than a rebuilt best-effort one. - Tests: exact GCP ARN region assertion, an Azure resource-group ARN assertion, and a portableToAzureType forward-direction test. * test(discovery): prove same-name K8s clusters in different scopes get distinct ARNs Follow-up review flagged a canonical-identifier collision risk: two clusters named the same in different scopes could share one ARN. The region/resource- group threading already prevents it; these tests pin it — GKE across two regions and AKS across two resource groups each yield two distinct ARNs, each embedding its own scope. * fix(discovery): skip EKS clusters that vanish mid-scan instead of aborting inventory A DeleteCluster racing between eksDiscovery's ListClusters and its per-cluster DescribeCluster/ListNodegroups reads makes those reads return NotFound. The previous fail-loud adapter turned that benign case into a walker error, which engine.List propagates — dropping every provider's resources from the whole ResourceDiscovery.List, not just the deleted cluster. Treat NotFound as "omit this cluster, continue"; propagate any other error unchanged. Also set DiscoveredCluster.Region from the cluster's verbatim ARN so the node-group ARN (built from Region) and Resource.Region stay consistent with the cluster ARN instead of falling back to the engine default. Back eksDiscovery with an eksClusters interface so the NotFound-skip and error-propagation paths are covered by a fake. * refactor(resourcegraph): map lookups for Azure type translation to stay under gocyclo gate Adding the two kubernetes/* cases tipped portableToAzureType and mapAzureType to gocyclo 11 (>10). Replace both switches with package-level map lookups — same behavior, complexity back to 1, and new type pairs no longer raise it. * chore(resourcegraph): annotate static type-map globals for gochecknoglobals The gocyclo->map refactor introduced two package-level lookup globals that tripped gochecknoglobals. Annotate both with the repo's convention (//nolint:gochecknoglobals // static lookup table), matching elbActions. * test(discovery): end-to-end SDK indexing tests for kubernetes resources Coverage stopped one layer short of the Databricks precedent (TestSDKResourceGraph_DatabricksIndexing) — the new kubernetes types were only exercised piecewise (type-translation units + engine-level List), never driven through the real query handler. Add handler-level round-trip tests via the live SDKs, one per discovery surface, asserting a cluster and its node group/agent pool are retrievable and that a type/service filter narrows to them: - Azure Resource Graph: TestSDKResourceGraph_KubernetesIndexing - GCP Cloud Asset: TestSDKCloudAsset_KubernetesIndexing - AWS Resource Explorer: TestSDKResourceExplorer2_KubernetesIndexing
* feat: full AWS Bedrock coverage (#214) Complete the Bedrock SDK-compat surface end-to-end so the real aws-sdk-go-v2 bedrock, bedrockruntime, bedrockagent, and bedrockagentruntime clients all work against the in-memory backend. Runtime (bedrock-runtime): - ConverseStream + InvokeModelWithResponseStream over vnd.amazon.eventstream - CountTokens, ApplyGuardrail - Async invoke: StartAsyncInvoke / GetAsyncInvoke / ListAsyncInvokes Control plane (bedrock): - Guardrail policy configs (topic/content/word/sensitive-info/contextual-grounding) and guardrail versions (CreateGuardrailVersion + version-addressed Get/Delete/List) - Model import jobs, model copy jobs, model evaluation jobs (+ Stop) - Inference profiles, prompt routers - Marketplace model endpoints (incl. Register/Deregister) - Foundation model agreements (Create/Delete/ListOffers/GetAvailability) - Automated reasoning policies (CRUD) - Resource tagging: TagResource / UntagResource / ListTagsForResource (also persists tags previously accepted-but-dropped on guardrail/provisioned create) Agents (new bedrock-agent / bedrock-agent-runtime services + SDK deps): - Agents, knowledge bases, data sources, flows, prompts (CRUD + lifecycle) - Runtime: InvokeAgent (eventstream), Retrieve, RetrieveAndGenerate Implemented across all layers (driver interface, in-memory provider, portable API, restJson1 SDK-compat handler) with unit tests and real-SDK roundtrip tests per subsystem. The two agent services register before the S3 catch-all, and the agent-runtime handler registers before the agent control plane and matches only POST so the shared /agents and /knowledgebases roots never collide. Verified: go build/vet/gofmt clean, golangci-lint clean, go test ./... passing, and a full-server end-to-end exercise with the real SDK clients over a socket. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bedrock): address PR review — concurrency, immutability, routing, fidelity Applies the review feedback on #214. Concurrency / immutability (data-race + aliasing): - Guardrail policies deep-copied on create/update and on version snapshot, so version snapshots are truly immutable and callers can't mutate stored state. - guardrailRecord guarded by a sync.RWMutex (draft/versions/nextVer); reads copy under the lock. go vet copylocks-clean. - Copy-on-write for in-place mutators that a new -race test surfaced: StopEvaluationJob, marketplace Update/Register, AR-policy Update. - copyMap on customization-job/custom-model read paths; copyBytes on Evaluation/Inference/PolicyDefinition []byte stores; copyRaw on bedrock-agent json.RawMessage configs. - New -race concurrency tests (guardrail + evaluation job). Routing: - bedrock-agent handler anchors /knowledgebases, /flows, /prompts via underPrefix so bucket paths like /flows-prod fall through to S3 (no shadowing); test added. Fidelity / consistency: - List endpoints (async invoke, import/copy/eval jobs, inference profiles, prompt routers, AR policies, marketplace) use SortedValues() for deterministic order. - Duplicate CreateMarketplaceModelEndpoint / CreatePromptRouter now return AlreadyExists instead of silently overwriting. - bedrock-agent-runtime decode uses errors.Is(io.EOF); dead *Preparing constants removed. Verified: go build/vet/gofmt clean, golangci-lint clean, go test ./... passing, go test -race on the bedrock packages clean, and an exhaustive 102-operation real-SDK end-to-end run against the full server passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bedrock): address re-review fidelity items + arch pattern gaps Follow-up to the second/third PR reviews on #214. Fidelity: - Evaluation jobs are now created InProgress (evaluation is long-running with no synchronous artifact, unlike import/copy), so StopEvaluationJob is meaningful; Stop rejects a non-InProgress job with FailedPrecondition, which the bedrock error mapper now surfaces as ConflictException (409), matching real AWS. - DeregisterMarketplaceModelEndpoint removes the Bedrock registration record, so a subsequent Get returns NotFound (the underlying SageMaker endpoint is unmodeled) — matching AWS instead of a success no-op. - ApplyGuardrail validates a requested numbered GuardrailVersion (NotFound if the version doesn't exist); "" / "DRAFT" resolve to the working draft. Aliasing/consistency: - GetInferenceProfile / GetPromptRouter / GetAutomatedReasoningPolicy (and their List) copy the Models slice / PolicyDefinition bytes out on read via clone helpers, so callers can't mutate stored state through a returned value. Pattern conformance: - Added the missing services/bedrockagent portable-layer test. - docs/services.md now lists bedrock-agent (+ bedrock-agent-runtime) and the operation-count table reflects the full surface. Tests updated for the corrected eval-job lifecycle and marketplace deregister semantics. Verified: go build/vet/gofmt/golangci-lint clean, go test ./... passing (174 pkgs), go test -race on the bedrock packages clean, and an exhaustive 107-operation real-SDK end-to-end run against the full server passing (including the changed behaviors). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bedrock): address in-depth review — cascade delete, copy-out, error taxonomy Follow-up to the fourth PR review on #214. - bedrock-agent cascade delete: DeleteKnowledgeBase now also removes its data sources and ingestion jobs; DeleteDataSource removes its ingestion jobs; DeleteAgent removes its aliases. No more orphaned children in the store. - Copy-out immutability on read: Get/List/Update/Create-return paths now clone json.RawMessage / []byte fields out (knowledge-base + data-source + flow + prompt configs; marketplace endpointConfig; evaluation-job evaluation/inference configs), so a caller mutating a returned value can't corrupt stored state — consistent with the guardrail/AR-policy paths. - Error taxonomy: writeErr in bedrock, bedrock-agent, and bedrock-agent-runtime now maps PermissionDenied -> 403 AccessDeniedException and ResourceExhausted -> 400 ServiceQuotaExceededException instead of falling through to 500. - CI: added a scoped `go test -race` step for the Bedrock packages so their concurrency tests actually exercise the race detector in CI. - Docs: recorded that bedrock-agent versioning/aliases-beyond-create, action groups, and collaborators are out of scope for this iteration, and that jobs complete synchronously / inference responses are deterministic simulations. Verified: go build/vet/gofmt/golangci-lint clean, go test ./... (204 pkgs) passing, and go test -race on the bedrock packages clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bedrock): address deep re-review — UTF-8 streaming bug, list determinism, aliasing Follow-up to the deep multi-agent re-review on #214. Correctness: - ConverseStream: chunkText split the completion on a byte offset, corrupting multi-byte UTF-8 responses (broken halves marshalled as U+FFFD). Now advances the split to a rune boundary. Added a multi-byte-UTF-8 streaming roundtrip test (café/日本語/emoji) asserting the reassembled text is valid UTF-8, U+FFFD-free, and equal to non-streaming Converse. Determinism: - ListModelCustomizationJobs, ListCustomModels, ListGuardrails (no-identifier), and ListProvisionedModelThroughputs now use SortedValues() instead of All(), matching the documented memstore contract and the sibling lists. Aliasing / immutability: - FoundationModel Get/List clone the modality/customization/inference slices out (the seed reuses shared backing slices, so a mutated return would corrupt all). - Model-invocation LoggingConfig is deep-copied on store and read (nested S3/CloudWatch pointers no longer shared with caller/store). Fidelity / consistency: - CreateInferenceProfile and CreateAutomatedReasoningPolicy reject duplicate names with AlreadyExists (consistent with prompt routers / provisioned throughput). - UpdateGuardrail rename now errors instead of silently clobbering an existing guardrail with the target name. - RegisterMarketplaceModelEndpoint upserts (registers an externally-created endpoint) instead of requiring the record to pre-exist. - CreateFoundationModelAgreement validates the modelId against the catalog. - CountTokens with neither union member returns ValidationException instead of 0. Docs/tests: documented the bedrock-agent bucket-name-shadowing tradeoff on the handler; added -race tests for the marketplace + AR-policy copy-on-write mutators. Verified: go build/vet/gofmt/golangci-lint clean, go test ./... (204 pkgs) passing, and go test -race on the bedrock/agent packages clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(rds): DB and DB cluster parameter groups Add parameter-group support to the RDS emulation as an optional ParameterGroups capability (mirroring the SubnetGroups pattern): create, describe, modify, delete, describe-parameters, reset, and copy — for both DB parameter groups and DB cluster parameter groups (14 actions). Only user-set parameters are modeled; the emulator does not fabricate the hundreds of engine defaults real AWS returns. Real AWS reuses the DBParameterGroup fault codes for the cluster variants, so error mapping is shared via a 'parameter group' message keyword. Covered by provider unit tests and aws-sdk-go-v2 SDK round-trip tests. * feat(rds): option groups Add option-group support as an optional OptionGroups capability: create, describe (with engine-name filter), modify (include/remove options), delete, copy, and describe-option-group-options (6 actions). DescribeOptionGroupOptions returns a small, representative per-engine catalog of well-known option names rather than fabricating AWS's exhaustive version-specific list. Covered by provider unit tests and SDK round-trip tests. * feat(rds): read replicas Add CreateDBInstanceReadReplica and PromoteReadReplica as an optional ReadReplicas capability. A replica inherits its source's engine, version and storage; the source tracks its replica identifiers and the replica records its source. Promotion detaches the replica (clears its source, removes it from the source's list). Instance XML now carries ReadReplicaSourceDBInstanceIdentifier and ReadReplicaDBInstanceIdentifiers. Covered by provider unit tests and SDK round-trip tests. * feat(rds): copy snapshot and point-in-time restore Add an optional AdvancedRestore capability: CopyDBSnapshot, CopyDBClusterSnapshot, RestoreDBInstanceToPointInTime, and RestoreDBClusterToPointInTime (4 actions). Copies clone the source snapshot's engine/version/storage under a new identifier; PITR clones the source instance/cluster's current spec into a new resource. The emulator has no historical timeline, so a point-in-time restore reflects the source as it is now; RestoreTime/UseLatestRestorableTime are accepted but not replayed. Covered by provider unit tests and SDK round-trip tests. * feat(rds): RDS Proxy Add RDS Proxy as an optional DBProxies capability: create, describe, modify, delete proxies; register/deregister targets; describe targets and target groups (8 actions). A proxy has a single implicit 'default' target group; targets may be RDS instances (RDS_INSTANCE) or clusters (TRACKED_CLUSTER), validated against existing resources on registration. Covered by provider unit tests and SDK round-trip tests. * feat(rds): event subscriptions, events, event categories Add an optional EventSubscriptions capability: create/describe/modify/delete event subscriptions, DescribeEvents, and DescribeEventCategories (6 actions). Enabled defaults to true on create (matching AWS). DescribeEventCategories returns AWS's published per-source-type categories. DescribeEvents returns an empty list by design: the emulator retains no event timeline, so there are truthfully no events for any window. Covered by provider unit tests and SDK round-trip tests. * feat(rds): Aurora custom endpoints, failover, and global clusters Add three optional Aurora capabilities (10 actions): - ClusterEndpoints: create/describe/modify/delete custom cluster endpoints. - ClusterFailover: FailoverDBCluster promotes the target member to writer (or rotates the first reader when no target is given). - GlobalClusters: create (optionally adopting a source cluster as writer), describe, modify (rename / engine version), delete, and remove-from. Covered by provider unit tests and SDK round-trip tests. * feat(rds): engine-version/orderable-option metadata and resource tagging Add two optional capabilities (5 actions): - Metadata: DescribeDBEngineVersions and DescribeOrderableDBInstanceOptions, backed by representative per-engine version and instance-class catalogs. - Tagging: AddTagsToResource, RemoveTagsFromResource, ListTagsForResource, addressed by resource ARN over the tag-bearing stores (instances, clusters, and instance/cluster snapshots). Covered by provider unit tests and SDK round-trip tests. * feat(discovery): surface RDS/Aurora in Resource Explorer (AWS) Issue #295 workstream A: RDS instances, Aurora clusters, and snapshots are emulated but were never enumerable via Resource Explorer. Add a RelationalDatabases discovery capability + neutral DiscoveredDatabase projection, a walkRelationalDB walker, and an rdsDiscovery adapter in the AWS provider (mirroring the Kubernetes eksDiscovery pattern, keeping services/ free of provider imports). Map rds<->relationaldb in the Resource Explorer 2 filter/handler so 'service:rds' narrows to these resources and their ResourceType/Service render correctly. Covered by an SDK indexing round-trip test. GCP Cloud SQL / Azure SQL discovery are deliberately out of scope here. * feat(rds): cost-tracker rates and enriched CloudWatch metrics Add relationaldb operation rates to the cost Tracker's rate catalog (instances and read replicas per instance-hour, RDS Proxy per hour; snapshots and Aurora cluster grouping free), consistent with how the other services populate the catalog. Enrich instance metric emission with FreeStorageSpace, ReadLatency, WriteLatency, and Network{Receive,Transmit}Throughput alongside the existing CPU/connections/memory/IOPS series; latency and throughput read zero when the instance is stopped. Covered by cost and metrics tests. * chore(rds): lint sweep + discovery cleanup Drop the write-only DiscoveredDatabase.Engine field; refactor the RDS error-code switches into ordered keyword->fault tables (keeps them under the gocyclo gate); wrap long signatures, fix cuddling/var-naming/receiver/goconst, and annotate the intentional per-resource duplication. Full build/vet/gofmt and the whole test suite pass; golangci-lint clean on the touched packages. * docs(rds): document new RDS capabilities and discovery surfacing Update services.md section 17 with the 11 new optional capability interfaces and their operations (parameter/option groups, read replicas, snapshot copy/PITR, RDS Proxy, event subscriptions, Aurora endpoints/failover/global clusters, metadata, tagging), refresh the totals, and note AWS RDS discovery through Resource Explorer 2 in section 19 (and features.md). * fix(rds): review — concurrency, delete guards, slice aliasing Address PR review (both HIGH + the MEDIUM aliasing set): HIGH - Concurrent-map-write panic on Tags/Parameters: copy-on-read in the Describe paths (instances/clusters/snapshots tags; parameter groups) and replace-on-write in the mutators (AddTags/RemoveTags build a fresh map; parameter Modify/Reset build a fresh map), mirroring ModifyInstance. Add a go test -race concurrency test (Describe + caller iteration vs tag/param writes). - DeleteInstance now refuses (FailedPrecondition) while the instance still has read replicas. MEDIUM - DeleteInstance strips a deleted replica from its source's target list; make removeString non-mutating so it never corrupts a slice a Describe handed out. - DeleteGlobalCluster refuses while members remain. - Fresh-slice rebuilds (no in-place [:0]/append into aliased backing arrays) in DeregisterDBProxyTargets, RegisterDBProxyTargets, RemoveFromGlobalCluster, ModifyOptionGroup. - DeleteDBSubnetGroup takes m.mu across the in-use scan + delete. - Preserve per-parameter ApplyMethod (store map[string]Parameter). LOW - Deterministic tag XML ordering; DescribeEventCategories/PITR return copied slices; PITR clears ClusterID; SubnetGroups conformance guard; subnet-group ARN via idgen. New tests: -race concurrency; delete-blocked-by-replica; delete-global-with- members; ApplyMethod round-trip. * fix(rds): review LOW — read-side slice copies + param/option in-use enforcement Addresses the remaining LOW review items: - Read-side slice aliasing (LOW #1): Describe paths now copy their slice/map fields so a returned value never aliases the store — DescribeDBProxies (Targets/Auth/subnets/SGs), DescribeGlobalClusters (Members), DescribeDBClusterEndpoints (Static/Excluded), DescribeEventSubscriptions (SourceIDs/EventCategories), DescribeOptionGroups (Options), and the parameter-group Parameters maps. Adds a generic cloneSlice helper; consistent with the tags copy-on-read. - Parameter/option-group in-use enforcement (LOW #2): instances now carry DBParameterGroupName/OptionGroupName and clusters DBClusterParameterGroupName (parsed from the wire). Delete{,Cluster}ParameterGroup and DeleteOptionGroup refuse an in-use group (FailedPrecondition) and refuse the reserved default.* / default:* names. Not changed: conformance guards for ClusterEndpoints/ClusterFailover/ GlobalClusters/Metadata/Tagging already exist (var _ blocks in aurora.go / metadata.go) — the review's #3 was a false alarm. Remaining LOW fidelity nits (proxy RoleArn/Auth validation, global secondary attach, metric backfill, etc.) stay as documented follow-ups. New tests: parameter/option-group delete guards (in-use + default), copy-on-read regression. build/vet/gofmt/full test + -race green; golangci-lint clean. * fix(rds): review — Modify can re-point/release param & option groups Addresses the re-review of the LOW-fix commit. MEDIUM — the new in-use enforcement could strand a group: a group attached at Create could only be released by deleting the instance/cluster. Add DBParameterGroupName/OptionGroupName (instance) and DBClusterParameterGroupName (cluster) to ModifyInstanceInput and wire the modify handlers, so re-pointing an instance/cluster to a new group releases the old one (which then deletes). LOW: - Finish read-side copies: DescribeInstances (VPCSecurityGroups, ReadReplicaTargets), DescribeClusters (Members, VPCSecurityGroups), and DescribeDBSubnetGroups (SubnetIDs) now return independent copies too, so the 'every Describe path copies its slice/map fields' contract actually holds. - Create{,Cluster}ParameterGroup / CreateOptionGroup reject the reserved default. / default: prefixes so a user can't self-inflict an undeletable group. - DescribeOptionGroups deep-copies Option.Settings (cloneSlice was shallow). Tests: Modify re-point/release for instance param, cluster param, and option groups; reserved-name rejection on create; copy-on-read across the list-all and named branches for proxies and instances.
…ility (#300) (#302) * feat(aws): ECS SDK-compat service (#159) + EC2 managed-resource visibility (#300) Two related AWS features (ECS Managed Instances are a producer of managed EC2 instances, so #300 composes with #159). ## ECS SDK-compat service (#159) New AWS ECS service so the real aws-sdk-go-v2/service/ecs client works against cloudemu over AWS JSON 1.1 (X-Amz-Target AmazonEC2ContainerServiceV20141113.*), across all four layers (driver → in-memory provider → portable API → SDK-compat server) and registered in the AWS server. All 19 operations from the ticket: - Clusters: CreateCluster, ListClusters, DescribeClusters, DeleteCluster - Task definitions: RegisterTaskDefinition (auto-incrementing revision per family), ListTaskDefinitions, DescribeTaskDefinition (family / family:revision / ARN; latest ACTIVE for a bare family), DeregisterTaskDefinition (→ INACTIVE) - Tasks: RunTask, StopTask, ListTasks, DescribeTasks - Services: CreateService (duplicate name → conflict), UpdateService, ListServices, DescribeServices, DeleteService - Container instances: ListContainerInstances, DescribeContainerInstances Resources complete synchronously (RunTask → RUNNING, CreateService reaches desiredCount). The batch Describe* ops and RunTask return partial success — unresolved ids appear in failures[]{arn,reason:"MISSING"} rather than erroring. DescribeClusters honors the include parameter (TAGS / SETTINGS gated), matching real ECS. Cluster defaults to "default" when omitted. ## EC2 managed-resource visibility (#300) Emulate AWS managed-resource visibility on DescribeInstances: - Instance model carries an Operator marker (Managed, Principal); instances can be seeded managed at launch (InstanceConfig.Managed/Principal) or via SetManaged. Managed instances carry aws:*-prefixed system tags (e.g. aws:ec2:managed-launch), which round-trip. - Account-level managed-resource-visibility setting (visible|hidden) via SetManagedResourceVisibility on the Go API. - DescribeInstances honors IncludeManagedResources. Behavior matrix (managed instance in state): hidden+no-flag → omitted; hidden+flag → included; visible → always included. Applies to list, filtered, and explicit-InstanceIds requests alike. Non-managed instances are always returned. Works on both the Go API and the query-protocol SDK server; the response Operator block (<operator><managed>/<principal>/<hiddenByDefault>) matches the ec2 SDK. The compute driver's DescribeInstances gained a trailing variadic options param (source-compatible for existing 3-arg callers); all implementers (GCP/Azure/ chaos/ssm) updated. ## Deps Add aws-sdk-go-v2/service/ecs v1.89.1; bump service/ec2 to v1.317.1 (adds the DescribeInstances IncludeManagedResources request parameter). ## Verified go build/vet/gofmt clean; golangci-lint introduces zero new findings; go test ./... passes (210 packages, 0 failures); go test -race clean on the ecs/ec2 packages; and exhaustive real-SDK end-to-end runs against the full server pass for both features (all 19 ECS ops + the full EC2 visibility matrix). docs/ services.md updated (new Container Orchestration service + EC2 managed-visibility section + counts). Closes #159 Closes #300 * fix(ecs): cap RunTask count at the AWS max of 10 CodeQL flagged a high-severity 'slice memory allocation with excessive size value' at providers/aws/ecs/tasks.go: the RunTask result slice was allocated with make([]Task, 0, count) using the caller-supplied count unbounded, so a large count could drive an excessive allocation. Reject count > 10 with a ValidationException before allocating (matching the AWS ECS RunTask limit), which bounds the allocation to a constant maximum. Added a regression test covering the max, over-max, and absurd-count cases. * feat(ecs): full ECS parity + address review (#159, #300) Addresses NitinKumar004's deep review on PR #302 across four waves. Wave 1 — correctness bugs (HIGH+MEDIUM): - Copy-on-write on every mutator + deep-copy slice/map fields on read (clone.go), killing the aliasing/data-race class; added -race concurrency tests. - DeleteCluster cascade guard (ClusterContainsServices/Tasks/ContainerInstances exceptions) backed by real cluster counters. - DeleteService force + desiredCount guard (InvalidParameterException without force). - Referential integrity: RunTask/CreateService/DeleteService/ListTasks validate the cluster (ClusterNotFoundException); CreateService validates the task-def. - RunTask on a missing task-def now errors (ClientException) instead of failures[]. - CreateService allows reusing an INACTIVE service name; cluster counters maintained; ListTaskDefinitions sorts by NUMERIC revision + honors sort ASC/DESC. - Typed exceptions mapped so SDK errors.As matches; Create* use SetIfAbsent (no TOCTOU). - EC2 #300: visibility setting RWMutex-guarded + validated; explicit-id describe of a hidden instance → InvalidInstanceID.NotFound; managed instances suppress instance-dimensioned CloudWatch metrics; resourcediscovery/topology/SSM include managed instances; CodeQL slice-alloc fixed (constant capacity + count<=10 guard). Wave 2 — EC2/Fargate placement + capacity engine: - Container instances carry CPU/memory capacity; EC2 RunTask/services are first-fit placed (reserve on run, release on stop); no capacity -> failures[]/PENDING. - launchType validated vs requiresCompatibilities; FARGATE requires awsvpc networkConfiguration + cpu/memory and synthesizes an ENI attachment + platformVersion. Wave 3 — service deployment model: - Real service->task convergence (launches DesiredCount tasks, running/pending counts), deployments[]/events[], DAEMON vs REPLICA, deploymentController/configuration, loadBalancers + serviceRegistries round-trip, full UpdateService surface. Wave 4 — task-def runtime fields + missing ops + EC2 compose: - Container/task-def runtime surface (portMappings, environment, secrets, healthCheck, logConfiguration, mountPoints, ulimits, resourceRequirements, volumes, ephemeralStorage, runtimePlatform, proxyConfiguration, ...) round-trips; register validation (essential defaults true, duplicate/empty names rejected). - Added ops: Tag/Untag/ListTagsForResource, PutAccountSetting(+Default)/List/Delete, RegisterContainerInstance/DeregisterContainerInstance/UpdateContainerInstancesState, UpdateCluster/UpdateClusterSettings/PutClusterCapacityProviders, Put/Delete/ListAttributes, ListTaskDefinitionFamilies, ExecuteCommand (37 ops total). - ECS now composes with #300: RegisterContainerInstance provisions a backing managed EC2 instance (Operator.Managed, principal ecs.amazonaws.com, aws:ec2:managed-launch) subject to managed-resource visibility. docs/services.md updated (§24 rewritten, counts). Verified: go build/vet/gofmt clean, golangci-lint 0 new findings, go test ./... (210 pkgs, 0 failures), go test -race clean on ecs/ec2, and 66 real-SDK end-to-end checks across ECS core/services + the EC2 visibility matrix + the ECS<->EC2 compose path. * fix(ecs): address re-review — Fargate combos, deployments, DAEMON, aliasing - RunTask/CreateService now validate the Fargate task cpu/memory pair against the supported configuration table (was presence-only), rejecting unsupported pairings with the AWS "No Fargate configuration exists" error. - UpdateService no longer accumulates deployments: the superseded (synchronously drained) deployment is dropped, leaving just the new PRIMARY. - CreateService rejects a caller-supplied desiredCount on a DAEMON service (AWS InvalidParameterException) instead of silently overwriting it. - Service create/update clone their LoadBalancers/ServiceRegistries/ capacity-provider/config reference fields, so the stored record never aliases the caller's input. - RegisterContainerInstance surfaces a launcher failure (ServerException) rather than silently synthesizing an id; force-deregistering an instance stops the tasks placed on it. - docs/services.md §24 discloses the accept-and-echo fields (capacityProviderStrategy, loadBalancers/serviceRegistries, deployment circuit-breaker) and clarifies that the task-def runtime surface is round-tripped, not reflected onto containers. Covered by tests for each fix. * fix(ecs): atomic tag/cluster RMW and bare wire error messages Completes the review findings not covered by 37cf9f5: - Finding 5 (atomic read-modify-write): TagResource/UntagResource and mutateCluster did Get -> clone -> Set, so two concurrent tag or cluster mutations on the same resource could lose one another's changes (last-writer-wins). Route them through memstore.Update (with SetIfAbsent seeding the tag entry) so the whole RMW runs under the store lock. - Wire error messages leaked the internal "<Code>: " prefix (e.g. "InvalidArgument: No Fargate configuration...") because the server used err.Error(). Add wireMessage() to unwrap *cerrors.Error and surface the bare Message, matching how real AWS SDK exceptions read. * fix(ecs): guard INACTIVE resources, close capacity race, drop test-only helper Addresses the re-review findings: - INACTIVE task definition still launched: resolveTaskDef accepts a deregistered family:revision, so RunTask/CreateService/UpdateService would run new tasks from it. Add resolveLaunchableTaskDef (requires ACTIVE) and route the three launch paths through it; describe/deregister/tag paths keep seeing INACTIVE defs. - INACTIVE (deleted) cluster still accepted work: clusterExists is presence-only, so RunTask/CreateService landed on a deleted-cluster tombstone. Add clusterActive (present AND ACTIVE) for the launch gates, and let CreateCluster reuse a deleted name (only an ACTIVE same-name cluster is a conflict) under a new clusterMu compare-and-set. - UpdateContainerInstancesState capacity lost-update: it mutated a clone and Set it back without placeMu, racing reserve/release/deregister. Hold placeMu for the whole read-modify-write. - SetManaged was a test-only helper in production EC2 code (zero non-test callers; the real path is LaunchManaged). Removed it and its dedicated test; managed-visibility coverage stays via TestManagedResourceVisibility. - ListServices returned INACTIVE service tombstones; filter to live (non-INACTIVE) services like real ECS. - UpdateService now rejects desiredCount under DAEMON (parity with CreateService). - Write-side input aliasing: RegisterTaskDefinition deep-clones the task def (nested ContainerDefinition slices + task-level reference fields) before storing; UpdateCluster clones the caller's Configuration raw JSON. Tests: new provider-level guards in nitin_review_test.go, plus server roundtrip assertions for the ClientException typed-error path, DescribeClusters include=[TAGS] gating, and PutAccountSettingDefault. --------- Co-authored-by: Gajendra Malviya <gajendra.malviya@zop.dev>
…xible Server, Cloud SQL) (#303) * feat(azure): full parity for Azure MySQL Flexible Server sub-resources Add databases, firewall rules and server configurations plus the server failover action to Azure Database for MySQL Flexible Server, bringing it to native parity with the ARM surface real armmysqlflexibleservers clients use. Introduce Databases, FirewallRules, Configurations and Failover as optional relationaldb driver capabilities (mirroring SubnetGroups), so the same interfaces are reusable by the other managed-SQL services. The mock stores each family per server and cascade-deletes children on server delete; the ARM handler routes databases/firewallRules/configurations, updateConfigurations (batch) and the failover action. Covered by real-SDK round-trip tests and mock-level error-path/cascade tests. * feat(azure): full parity for Azure PostgreSQL Flexible Server sub-resources Add databases, firewall rules and server configurations to Azure Database for PostgreSQL Flexible Server, reusing the Databases/FirewallRules/Configurations optional relationaldb capabilities introduced for MySQL Flex. Postgres Flex has no failover action and no batch-configuration endpoint, and its configuration resource accepts both PUT and PATCH; the handler and mock reflect that. The mock cascade-deletes children on server delete. Covered by real-SDK round-trip tests (the SDK has no ClientFactory, so each client is built from shared options) plus mock-level default/error/cascade tests. * feat(azure): full parity for Azure SQL server sub-resources Add firewall rules, virtual-network rules, elastic pools, failover groups and the Azure AD administrator to Azure SQL (Microsoft.Sql). Firewall rules reuse the shared FirewallRules capability; the other four are added as optional relationaldb capabilities (VNetRules, ElasticPools, FailoverGroups, AADAdmins) alongside the existing SubnetGroups pattern. Failover-group failover flips the local replication role between Primary and Secondary. The mock cascade-deletes all child resources on server delete and returns isolated copies of the slice-bearing failover-group state. Covered by real-SDK (armsql) round-trip tests across all five families plus mock-level error/cascade/aliasing tests. * feat(gcp): full parity for Cloud SQL databases, users, certs and instance ops Add databases (via the shared Databases capability), users and client SSL certs as instance child resources, plus the clone, failover, promote-replica and start/stop-replica instance actions to GCP Cloud SQL. Users and SSL certs are new optional relationaldb capabilities (Users, SslCerts); clone and replica promotion are Clonable and ReplicaPromotion; failover reuses the shared Failover capability. The REST path parser now recognizes the databases/users/sslCerts sub-collections, and the users route honors Cloud SQL's ?name= query quirk for delete and update. The mock cascade-deletes children on instance delete. Tiers and flags catalogs are intentionally out of scope (separate path shapes, static data). Covered by real sqladmin SDK round-trip tests plus mock-level clone/cascade/error tests. * feat(discovery): surface managed SQL servers in Resource Graph and Cloud Asset Add a RelationalDatabases discovery capability to the resource-discovery engine and a walkRelationalDB walker, mirroring the Kubernetes adapter pattern. Azure wires an adapter that projects Azure SQL logical servers plus MySQL/PostgreSQL Flexible Servers, and GCP projects Cloud SQL instances, so managed relational databases appear in cross-service inventory. Resource Graph maps the portable types to microsoft.sql/servers, microsoft.dbformysql/flexibleservers and microsoft.dbforpostgresql/flexibleservers; Cloud Asset maps Cloud SQL to sqladmin.googleapis.com/Instance. Both type-map switches become lookup tables to stay under the cyclomatic-complexity gate. Covered by walker and type-map tests. * feat(cost): add managed relational-database rates Add relationaldb:* entries to the cost rate catalog so provisioning a managed database server/instance (RDS, Azure SQL, Azure MySQL/PostgreSQL Flexible Server, Cloud SQL) is billed per instance-hour, clusters per cluster-hour, and restores reuse the instance-hour, while snapshots and lifecycle actions are free. The portable "relationaldb" service name means one catalog covers every cloud. Covered by a cost-tracker test. * docs: document full managed-SQL parity (sub-resources, discovery, cost) Document the native sub-resource capabilities added to Azure SQL, the Azure MySQL/PostgreSQL Flexible Servers and Cloud SQL — databases, users, firewall/ vnet rules, configurations, elastic pools, failover groups, AAD admins, SSL certs, clone/failover/replica actions — along with their discovery surfacing and cost rates. Refresh the relational-database operation totals and the discovery driver list. * feat(sql): Cloud SQL tiers/flags catalogs + elastic-pool metrics Close the remaining managed-SQL parity gaps: serve the Cloud SQL machine-tier catalog (GET /v1/projects/{p}/tiers) and the database-flag catalog (GET /v1/flags, which is project-less) as static reference data, and emit Microsoft.Sql/servers/elasticpools metrics (cpu/storage/workers percent) when an Azure SQL elastic pool is created. Also annotate the ModifyInstance/ModifyCluster methods across the four SQL mocks with the driver-interface hugeParam nolint now that the shared ModifyInstanceInput grew. Covered by SDK tiers/flags round-trip and an elastic-pool metric-emission test. * feat(azure): add SQL Managed Instance family to Azure SQL Add the Microsoft.Sql/managedInstances resource type and its managed databases as a ManagedInstances optional relationaldb capability: managed-instance CRUD + list + start/stop/failover actions, and managed-database CRUD + list. The handler now matches the managedInstances resource type alongside servers and routes both, and the mock cascade-deletes managed databases when their instance is removed. Covered by an armsql managed-instance/database SDK round-trip test (create → get → list → failover → cascade delete) and a mock-level lifecycle test. * docs: document managed instances, tiers/flags and elastic-pool metrics Update the relational-database section for the now-complete managed-SQL parity: add the ManagedInstances capability row, note Cloud SQL's tiers/flags reference catalogs and Azure SQL's Managed Instance family, and record the elastic-pool metric namespace. Refresh the optional-operation totals (25 capability interfaces, +109 relational / +118 grand-total optional). * fix(sql): clone ManagedInstance tags on read; deterministic list ordering Address review MEDIUM #1 and the list-ordering LOW: GetManagedInstance / ListManagedInstances now clone the stored Tags map so a caller mutating the returned map can't corrupt the store (the copy-on-read hole flagged as the concurrent-map panic class), and every List* mock method iterates memstore.SortedValues() instead of All() for deterministic SDK list ordering, matching the documented convention. Large-value list loops use index iteration to avoid per-element copies. * fix(azure): real update semantics + discoverable managed instances Address review MEDIUM #2/#3/#4 (Azure update paths) and #7 (MI discovery): - PUT on an existing Azure SQL server / database / managed instance now applies the request body (upsert) instead of returning the stale record. - PATCH is a genuine merge — elastic pools, failover groups and managed instances gain Update* capability methods that overlay only the fields the request supplied, so a partial PATCH no longer wipes unspecified fields; MI PATCH now decodes and applies its body instead of being a no-op. - Managed instances are projected into cross-service discovery (microsoft.sql/managedinstances) via sqlDiscovery + the Resource Graph type map, so a created MI appears in inventory. Covered by SDK PATCH-merge round-trip tests (elastic pool keeps its SKU when only maxSizeBytes is patched; MI keeps administratorLogin when only vCores is patched) and a managed-instance type-map test. * fix(azure): model Azure SQL database elastic-pool membership Address review MEDIUM #5. Databases now carry an ElasticPoolID (added to the shared InstanceConfig/Instance/ModifyInstanceInput and surfaced as the elasticPoolId ARM database property), so an SDK client placing a database into a pool round-trips instead of silently dropping it. DeleteElasticPool now returns a precondition error while the pool still contains databases, matching real Azure's 409. Covered by a membership/delete-guard test. * fix(gcp): real Cloud SQL read-replica and failover semantics Address review MEDIUM #6. A Cloud SQL insert with masterInstanceName now creates an actual read replica: the replica records its master (ReadReplicaSource) and the primary lists it (replicaNames), both surfaced in the instance body. Replica lifecycle is faithful — start/stopReplica require the target to be a replica and leave it RUNNABLE (no SUSPENDED state change); promoteReplica detaches it from the primary (and rejects a non-replica); failover is a primary-only operation and rejects a replica. Covered by SDK and mock replica-lifecycle tests. * fix(sql): review LOW items — determinism, validation, replica fidelity tests - Cloud SQL backup-run IDs are generated by the mock from the clock + a monotonic counter (was time.Now().UnixNano(), violating the Clock determinism rule and collision-prone); CloneInstance now clones the source's databases and resets replica linkage; UpdateUser doc corrected. - Firewall rules validate IPv4 start/end (Azure SQL + both Flexible Servers); Azure SQL Managed Instance requires subnetId, as real Azure does. - Added a real-SDK Resource Graph indexing test that drives sqlDiscovery end-to-end (logical server + managed instance appear and filter), and a -race concurrency test that mutates a returned managed-instance Tags map to pin the copy-on-read fix. * fix(sql): address second review — MI lifecycle, in-place restore, validation - Cloud SQL DeleteInstance now unlinks replica<->master before cascading children, so no dangling ReadReplicaSource/ReadReplicaTargets remain. - Managed Instance lifecycle gains a state guard (transitionManagedInstance): Start/Stop respect current state, Failover requires Ready; MI create emits representative metrics and clones returned Tags; adds CreateManagedInstance cost rate. - Cloud SQL RestoreBackup restores in place onto the existing instance via a new optional BackupRestorer capability (was create-new -> 409). - Failover-group failover requires a partner server; firewall rules validate Start <= End; Azure Flex SetConfiguration rejects unknown params and empty values; ARM parser captures the action verb so forced vs planned failover and unknown POST verbs are distinguished; Cloud SQL selfLink/targetLink use the served /v1 prefix. - Broaden new-package test coverage (sub-resource CRUD, MI lifecycle, Users update, backup get, error paths, server PATCH, raw MI start/stop). * fix(sql): address third review — output aliasing, config catalog, validation - Deep-copy Tags/slice fields on every Instance/Cluster/Snapshot return path in azuresql and cloudsql (Describe/Create/Modify/Restore/Clone), so a returned value never aliases the memstore — fixes a potential concurrent-map crash and matches the managedinstance/FailoverGroup discipline. - Azure Flex GetConfiguration/ListConfigurations return catalog defaults for a known-but-unset parameter (real Azure behavior) instead of NotFound/empty; the catalog now maps names to defaults. - MySQL Flex batch config update is atomic: all entries are validated before any is applied, via a new optional BatchConfigurations capability. - Azure SQL database create/modify validates the referenced elastic pool exists (symmetry with the pool-delete member guard). - Cloud SQL rejects child (database/user) names containing '/' that would orphan a row; get-paths add length guards on single-ID Describe lookups. - Flex firewall create validates start <= end (parity with Azure SQL). - Tests: aliasing, config defaults, batch atomicity, elastic-pool validation, child-name rejection, FG update/list, managed-database delete, and Azure wire-level error-mapping (404/400) across all three Azure SQL services.
…upport (#299) * feat(k8s): connect parity across EKS/AKS/GKE via a shared CA (Phase A) The data-plane serving cert and every provider's advertised CA must be the same authority or client-go's TLS handshake fails. Extract the CA into a new internal/k8spki package used by both the serving TLS config and all three control planes: - EKS: tls.go now delegates to k8spki (serve + EKS call sites unchanged). - GKE: advertise the real CA in masterAuth.clusterCaCertificate; drop the unparseable dummy blob that broke the handshake outright. - AKS: embed the real CA in the rendered kubeconfig and drop insecure-skip-tls-verify — parity with EKS/GKE. Tests: AKS data-plane test now serves with the k8spki cert and validates end-to-end (no skip-verify); new GKE real-TLS connect-parity test proves the advertised CA certifies the endpoint (create cluster -> validate CA -> client-go ConfigMap round-trip). RenderKubeconfig test updated to the real-CA behavior. First phase of the k8s runtime/parity work; registry refactor + reconcile engine + workload kinds follow on this branch. * feat(k8s): parse resource subresources in the router (Phase B foundation) Add Route.Subresource and parse the /{name}/{subresource} tail for both cluster-scoped (/api/v1/nodes/n/status) and namespaced (/apis/apps/v1/namespaces/ns/deployments/d/scale) shapes. ServeHTTP routes subresource requests to a dedicated dispatcher (stubbed to 404 until the reconcile phase wires /status and /scale) so a subresource path is never mis-parsed as a write against the parent object. Updated the parseRoute unit test to the new (correct) cluster-subresource semantics. * feat(k8s): generic resource registry + reconcile engine (Phases B+C) Turn the k8s data plane from a CRUD store into a minikube-like runtime. Registry (registry.go, registry_ops.go, registry_defs.go): a generic unstructured-backed store + one handler serving CRUD, list (label & field selectors), watch, patch, delete (ownerReference garbage collection), and the /status + /scale subresources for any registered kind. New kinds are a registration + optional reconcile hook. Registers apps/v1 ReplicaSet, StatefulSet, DaemonSet and core/v1 PersistentVolumeClaim; discovery is derived from the registry so it can't drift. Reconcile engine (reconcile.go), run synchronously on every write (no controller goroutines, so it stays deterministic): - Pods are driven Running with a synthetic Pod IP and ready containers. - Deployment materializes its Pods and reports real status; ReplicaSet and DaemonSet do likewise; StatefulSet creates stable-ordinal Pods (name-0..N-1) plus a Bound PVC per volumeClaimTemplate. - The endpoints controller fills a Service's Endpoints from the Running Pods matching its selector, and drains them when Pods are deleted/GC'd. - Deleting a controller cascades to its Pods; scaling (spec.replicas or the /scale subresource) adjusts the Pod count. Typed handlers: Deployment now reconciles + serves /scale and /status; direct Pod creates come up Running; Pod list honors label/field selectors; Service create populates endpoints. Tests: new client-go WorkloadRuntime E2E (Deployment+Service -> Running pods + endpoints -> scale to 4 -> StatefulSet with 3 stable pods + 3 Bound PVCs -> DaemonSet -> cascade teardown). Existing pod/cascade/provider tests updated to the new Running/materialized behavior. Deployment /scale + /status advertised in discovery. Deferred to later phases: the intermediate ReplicaSet object for Deployments (pods are owned by the Deployment directly); Job/CronJob, Ingress, RBAC, HPA, Node, Event, NetworkPolicy, EndpointSlice; strategic-merge / server-side-apply. * k8s: register batch/networking/rbac/storage/autoscaling/discovery + core supporting kinds; registry-driven discovery Adds registry entries for Job/CronJob, Ingress/IngressClass/NetworkPolicy, RBAC (Role/RoleBinding/ClusterRole/ClusterRoleBinding), StorageClass, HorizontalPodAutoscaler, EndpointSlice, and core PVC/PV/Node/Event/ ResourceQuota/LimitRange. Reconcile hooks drive Job pods to Succeeded, Ingress to a load-balancer IP, and PV to Available/Bound. serveDiscovery now derives the /apis group list and every /apis/<group>/<version> resource list from registeredResources() (seeded with the typed apps/policy groups) instead of a hardcoded switch, so new groups and their subresources are discoverable by kubectl and client-go without drifting from what the server serves. * test(k8s): e2e coverage for supporting kinds via client-go Drives Jobs (complete to Succeeded with materialized pods), Ingresses (get a load-balancer IP), PVCs (bind), StorageClass/RBAC/HPA/Node round-trips, and asserts the new API groups are discoverable — the negotiation kubectl and client-go do before any typed request. * k8s: rolling updates replace Pods on pod-template change; advertise endpoints - Controllers stamp a pod-template-hash label on Pods; reconcile treats a changed template hash as a rolling update and replaces stale-hash Pods (Deployment/ReplicaSet via syncScaledPods, StatefulSet via syncStablePods). Convergence is instant — no surge/unavailable pacing. - buildControllerPod now copies the template label map before stamping, so it can't mutate the controller's shared template. - Discovery advertises core/v1 endpoints (get/list/watch only, matching the read-only handler) so kubectl/client-go can resolve them. E2E: runtime test now exercises a rolling update (image change replaces all Pods, endpoints re-point); supporting-kinds test asserts endpoints discovery. * docs(k8s): document the minikube-like data plane and its non-goals Update services.md §18, sdk-server.md, and the package doc to reflect the reconcile engine (Running Pods, Endpoints, binding PVCs, completing Jobs), validated TLS via the shared CA, the full multi-group resource surface with /scale and /status subresources, rolling updates, and the deliberate emulation boundaries (no exec/logs/portforward, no scheduling, no quota/RBAC/policy enforcement, no HPA/CronJob actuation). * fix(k8s): merge-patch to /scale no longer silently scales to zero applyUnstructuredPatch decoded the merged JSON with plain json.Unmarshal into map[string]any, which turns whole-number JSON into float64. unstructured.NestedInt64 accepts only int64, so spec.replicas read back as 0 — a 'kubectl scale --replicas=N' (a merge-patch to the /scale subresource, or any merge-patch touching replicas) silently scaled the workload to zero. Decode via unstructured.Unstructured.UnmarshalJSON instead, which preserves integers as int64. This fixes every merge-patch path (object and /scale) at the root. Regression test drives a merge-patch scale-up and asserts both the returned Scale and the stored object carry replicas=4. * fix(k8s): address review findings across reconcile, GC, endpoints, PKI - Endpoints: only bump ResourceVersion / publish MODIFIED when the address set actually changes. resyncEndpointsForNamespaceLocked runs for every Service on any Pod change, so an unchanged Service was emitting a spurious watch event (with a climbing RV) on unrelated Pod churn. Regression test added. - Pod field selector: support spec.nodeName. Every materialized Pod is scheduled to the synthetic node, so 'kubectl get pods --field-selector spec.nodeName=...' (node-drain/kubelet tooling) previously returned an empty list. E2E covers it. - Garbage collection: walk the owned set breadth-first and collect UIDs before deleting, instead of mutating each store's map while ranging it and recursing. Pods owned by an intermediate controller (not just the root) are now reaped. - Scale subresource: only bump generation when spec.replicas actually changes, matching registryUpdate/registryPatch (no spurious generation != observed). - Job: ignore a non-positive spec.completions (default to 1) so a Job can't report Complete having run zero Pods. - StatefulSet PVCs: deep-copy the volumeClaimTemplate spec per ordinal instead of aliasing one map across every PVC. - PKI: give each serving leaf a random 128-bit serial (was fixed '2') and assert BasicConstraintsValid (cA=FALSE) so strict non-Go verifiers accept the leaf. - docs: correct the data-plane list to say it is unpaginated (limit/continue are not honored) and scope field-selector support accurately. * k8s: real-user kubectl parity — protobuf writes, OpenAPI v2/v3, strategic & JSON patch Driving a running cloudemu server with real kubectl (cluster created via the EKS/GKE/AKS SDK, then kubectl against the advertised endpoint) surfaced gaps the JSON-forcing client-go tests masked. kubectl now works end-to-end. - Protobuf request bodies: kubectl sends built-in kinds as protobuf on writes and does NOT retry as JSON on 415, so every 'kubectl create/apply/scale' write failed. Decode protobuf via the client-go scheme's recognizing deserializer (typed handlers decode in place; registry handlers convert to unstructured). Responses stay JSON — clients' Accept allows it. - OpenAPI: serve a v3 discovery root + per-group docs that carry each served GVK (with a permissive schema) so kubectl resolves the kind and stays on the JSON v3 path; and serve the legacy v2 doc as protobuf bytes (mime-safe application/octet-stream content type) for the fallback. Without this 'kubectl apply' died at 'failed to download openapi'. Served cluster-independently in APIServer.ServeHTTP so the prefix-less v3 serverRelativeURL follow-ups resolve. - Patch types: typed handlers now accept strategic-merge-patch (kubectl's default for set/edit/label — real strategic merge, so the container list merges by name) and JSONPatch (RFC 6902), in addition to merge-patch; registry handlers gain JSONPatch too. - Discovery: advertise kubectl short names (pvc, hpa, sts, ds, rs, ing, sc, …) for registry kinds so 'kubectl get pvc' resolves. Verified with real kubectl v1.36 against a standalone server: full lifecycle (apply → scale → rolling update → statefulset/PVCs → daemonset → job → cronjob → ingress → hpa → pv/pvc/storageclass → rbac → networkpolicy → node → all three patch types → cascade teardown) across EKS, GKE, and AKS connect paths. * docs(k8s): note full kubectl parity (protobuf, OpenAPI, all patch types) * k8s: drop len()+1 map capacity hint (clears CodeQL allocation-overflow alert) * k8s: filter watch streams by label/field selector (review blocker) Watch streams ignored labelSelector/fieldSelector: typed watches (watchPods, watchDeployments, …) filtered neither initial nor streamed events, and the registry watch filtered only the initial snapshot. A selective watch ('kubectl get pods -l app=x -w', or any informer/controller-runtime cache built with a selector) therefore received non-matching objects — polluting reflector caches and firing spurious reconciles, which the reconcile engine amplifies (one Deployment emits many Pod events). streamWatch now takes a keep(T) predicate applied to both the initial snapshot and every streamed event; each watch handler builds it from the request's selectors (parseListSelectors + metaFieldsMatch/podMatchesFields/matchesFields). Also extracts field-selector-name constants (fixes goconst) and indexes filterPods to avoid per-item Pod copies. * k8s: cap materialized pods, bootstrap Node, mirror EndpointSlices, resync endpoints on pod update/patch Correctness gaps from the PR review: - Unbounded replicas/completions: the reconciler runs synchronously under the cluster lock, so a huge spec value would allocate/hang the whole API. Clamp the materialized Pod count to maxReconciledPods (500) for Deployment, ReplicaSet/StatefulSet (via replicasOf), and Job. reconcileJob's top-up is also made O(n) instead of O(n²). - Synthetic Node: bootstrap cloudemu-node-0 (Ready, InternalIP) in newClusterState so 'kubectl get nodes' is non-empty and the node every Pod is scheduled onto actually exists. - EndpointSlices: mirror each Service's endpoints into a discovery.k8s.io EndpointSlice (labelled kubernetes.io/service-name) so EndpointSlice-mode consumers (kube-proxy, Gateway API) see the same backends as Endpoints. - Pod update/patch now resync endpoints (a label change matching a Service selector was invisible until unrelated churn) and re-drive a spec-only PUT back to Running so it isn't dropped out of the endpoint set. reconcileServiceEndpointsLocked is split into matchingEndpointAddressesLocked / writeEndpointsLocked / syncEndpointSliceLocked (also lowers its complexity). E2E asserts the synthetic Node and populated EndpointSlices; a new watch test asserts label-selector stream filtering. * k8s: dedup watch handlers + resolve golangci-lint to zero (review blocker) - All 8 typed watch handlers now share a generic serveWatch[T] helper (removes the dupl the near-identical subscribe/snapshot/stream blocks triggered, and addresses the reuse the review flagged). - golangci-lint (repo .golangci.yml, v2.11.4) is clean on the new non-test files: extracted goconst constants (api/apis path segments, status/scale subresources, group names), named crypto/mnd magic numbers in k8spki, gave parseListSelectors named results, indexed the container-status range, lowered ServeHTTP/serveRegistry complexity via dispatchResource / serveRegistryItem, fixed the govet shadow, and added reasoned //nolint for the legitimately-global lookup tables and hugeParam k8s structs. No behavior change; build, vet, tests, and -race remain green. * k8s: bump Deployment generation on spec change; unify /scale patch types - Typed Deployment now sets metadata.generation=1 on create and advances it only on a spec change (update/patch), matching apiserver semantics and the registry path so observedGeneration comparisons are meaningful. - deploymentScale PATCH routes through the shared applyPatchBytes dispatcher, so the typed /scale honors merge / strategic-merge / JSONPatch like the registry /scale (the two paths no longer diverge). - Tidy a stale AKS kubeconfig comment (Phase 3 -> the normal path). * docs(k8s): note watch selector filtering, synthetic node, EndpointSlice mirroring * k8s: address second-review findings (lint robustness, watch relist, docs) From thzgajendra's review of PR #299: - Lint (#1): replace the version-sensitive //nolint:prealloc directives with real preallocation (coreResources/appsResources/registryAPIResources, openapi kinds, endpoint-address slice), so golangci-lint is clean regardless of prealloc's version-dependent placement — no dangling directives. - Docs (#2): drop the 'pagination' claim from the k8s data-plane sentence in sdk-server.md; data-plane lists are unpaginated, matching services.md. - Watch load-shedding (#3): a slow watcher that overflows its buffer now receives a 410 Gone (ERROR) event and the stream ends, so client-go relists instead of running with a permanently-divergent cache. Regression test added. - GC comment (#4): correct the 'never mutate while ranging' wording — deleting the current key mid-range is legal; the BFS is what makes the cascade order-independent. - Provider package headers (#8): describe the now-wired data plane instead of 'out of scope / Wave 2'. Deferred as documented follow-ups (all Low): clamp/silent-signal, Job shrink reconcile, committed kubectl smoke test, provider CA-vs-sentinel misconfig window.
* feat(azure): full parity for Azure MySQL Flexible Server sub-resources
Add databases, firewall rules and server configurations plus the server
failover action to Azure Database for MySQL Flexible Server, bringing it to
native parity with the ARM surface real armmysqlflexibleservers clients use.
Introduce Databases, FirewallRules, Configurations and Failover as optional
relationaldb driver capabilities (mirroring SubnetGroups), so the same
interfaces are reusable by the other managed-SQL services. The mock stores each
family per server and cascade-deletes children on server delete; the ARM handler
routes databases/firewallRules/configurations, updateConfigurations (batch) and
the failover action. Covered by real-SDK round-trip tests and mock-level
error-path/cascade tests.
* feat(azure): full parity for Azure PostgreSQL Flexible Server sub-resources
Add databases, firewall rules and server configurations to Azure Database for
PostgreSQL Flexible Server, reusing the Databases/FirewallRules/Configurations
optional relationaldb capabilities introduced for MySQL Flex. Postgres Flex has
no failover action and no batch-configuration endpoint, and its configuration
resource accepts both PUT and PATCH; the handler and mock reflect that. The mock
cascade-deletes children on server delete. Covered by real-SDK round-trip tests
(the SDK has no ClientFactory, so each client is built from shared options) plus
mock-level default/error/cascade tests.
* feat(azure): full parity for Azure SQL server sub-resources
Add firewall rules, virtual-network rules, elastic pools, failover groups and
the Azure AD administrator to Azure SQL (Microsoft.Sql). Firewall rules reuse
the shared FirewallRules capability; the other four are added as optional
relationaldb capabilities (VNetRules, ElasticPools, FailoverGroups, AADAdmins)
alongside the existing SubnetGroups pattern. Failover-group failover flips the
local replication role between Primary and Secondary. The mock cascade-deletes
all child resources on server delete and returns isolated copies of the
slice-bearing failover-group state. Covered by real-SDK (armsql) round-trip
tests across all five families plus mock-level error/cascade/aliasing tests.
* feat(gcp): full parity for Cloud SQL databases, users, certs and instance ops
Add databases (via the shared Databases capability), users and client SSL certs
as instance child resources, plus the clone, failover, promote-replica and
start/stop-replica instance actions to GCP Cloud SQL. Users and SSL certs are
new optional relationaldb capabilities (Users, SslCerts); clone and replica
promotion are Clonable and ReplicaPromotion; failover reuses the shared Failover
capability. The REST path parser now recognizes the databases/users/sslCerts
sub-collections, and the users route honors Cloud SQL's ?name= query quirk for
delete and update. The mock cascade-deletes children on instance delete. Tiers
and flags catalogs are intentionally out of scope (separate path shapes, static
data). Covered by real sqladmin SDK round-trip tests plus mock-level
clone/cascade/error tests.
* feat(discovery): surface managed SQL servers in Resource Graph and Cloud Asset
Add a RelationalDatabases discovery capability to the resource-discovery engine
and a walkRelationalDB walker, mirroring the Kubernetes adapter pattern. Azure
wires an adapter that projects Azure SQL logical servers plus MySQL/PostgreSQL
Flexible Servers, and GCP projects Cloud SQL instances, so managed relational
databases appear in cross-service inventory. Resource Graph maps the portable
types to microsoft.sql/servers, microsoft.dbformysql/flexibleservers and
microsoft.dbforpostgresql/flexibleservers; Cloud Asset maps Cloud SQL to
sqladmin.googleapis.com/Instance. Both type-map switches become lookup tables to
stay under the cyclomatic-complexity gate. Covered by walker and type-map tests.
* feat(cost): add managed relational-database rates
Add relationaldb:* entries to the cost rate catalog so provisioning a managed
database server/instance (RDS, Azure SQL, Azure MySQL/PostgreSQL Flexible
Server, Cloud SQL) is billed per instance-hour, clusters per cluster-hour, and
restores reuse the instance-hour, while snapshots and lifecycle actions are
free. The portable "relationaldb" service name means one catalog covers every
cloud. Covered by a cost-tracker test.
* docs: document full managed-SQL parity (sub-resources, discovery, cost)
Document the native sub-resource capabilities added to Azure SQL, the Azure
MySQL/PostgreSQL Flexible Servers and Cloud SQL — databases, users, firewall/
vnet rules, configurations, elastic pools, failover groups, AAD admins, SSL
certs, clone/failover/replica actions — along with their discovery surfacing and
cost rates. Refresh the relational-database operation totals and the discovery
driver list.
* feat(sql): Cloud SQL tiers/flags catalogs + elastic-pool metrics
Close the remaining managed-SQL parity gaps: serve the Cloud SQL machine-tier
catalog (GET /v1/projects/{p}/tiers) and the database-flag catalog
(GET /v1/flags, which is project-less) as static reference data, and emit
Microsoft.Sql/servers/elasticpools metrics (cpu/storage/workers percent) when an
Azure SQL elastic pool is created. Also annotate the ModifyInstance/ModifyCluster
methods across the four SQL mocks with the driver-interface hugeParam nolint now
that the shared ModifyInstanceInput grew. Covered by SDK tiers/flags round-trip
and an elastic-pool metric-emission test.
* feat(azure): add SQL Managed Instance family to Azure SQL
Add the Microsoft.Sql/managedInstances resource type and its managed databases
as a ManagedInstances optional relationaldb capability: managed-instance CRUD +
list + start/stop/failover actions, and managed-database CRUD + list. The
handler now matches the managedInstances resource type alongside servers and
routes both, and the mock cascade-deletes managed databases when their instance
is removed. Covered by an armsql managed-instance/database SDK round-trip test
(create → get → list → failover → cascade delete) and a mock-level lifecycle
test.
* docs: document managed instances, tiers/flags and elastic-pool metrics
Update the relational-database section for the now-complete managed-SQL parity:
add the ManagedInstances capability row, note Cloud SQL's tiers/flags reference
catalogs and Azure SQL's Managed Instance family, and record the elastic-pool
metric namespace. Refresh the optional-operation totals (25 capability
interfaces, +109 relational / +118 grand-total optional).
* fix(sql): clone ManagedInstance tags on read; deterministic list ordering
Address review MEDIUM #1 and the list-ordering LOW: GetManagedInstance /
ListManagedInstances now clone the stored Tags map so a caller mutating the
returned map can't corrupt the store (the copy-on-read hole flagged as the
concurrent-map panic class), and every List* mock method iterates
memstore.SortedValues() instead of All() for deterministic SDK list ordering,
matching the documented convention. Large-value list loops use index iteration
to avoid per-element copies.
* fix(azure): real update semantics + discoverable managed instances
Address review MEDIUM #2/#3/#4 (Azure update paths) and #7 (MI discovery):
- PUT on an existing Azure SQL server / database / managed instance now applies
the request body (upsert) instead of returning the stale record.
- PATCH is a genuine merge — elastic pools, failover groups and managed
instances gain Update* capability methods that overlay only the fields the
request supplied, so a partial PATCH no longer wipes unspecified fields; MI
PATCH now decodes and applies its body instead of being a no-op.
- Managed instances are projected into cross-service discovery
(microsoft.sql/managedinstances) via sqlDiscovery + the Resource Graph type
map, so a created MI appears in inventory.
Covered by SDK PATCH-merge round-trip tests (elastic pool keeps its SKU when
only maxSizeBytes is patched; MI keeps administratorLogin when only vCores is
patched) and a managed-instance type-map test.
* fix(azure): model Azure SQL database elastic-pool membership
Address review MEDIUM #5. Databases now carry an ElasticPoolID (added to the
shared InstanceConfig/Instance/ModifyInstanceInput and surfaced as the
elasticPoolId ARM database property), so an SDK client placing a database into a
pool round-trips instead of silently dropping it. DeleteElasticPool now returns
a precondition error while the pool still contains databases, matching real
Azure's 409. Covered by a membership/delete-guard test.
* fix(gcp): real Cloud SQL read-replica and failover semantics
Address review MEDIUM #6. A Cloud SQL insert with masterInstanceName now creates
an actual read replica: the replica records its master (ReadReplicaSource) and
the primary lists it (replicaNames), both surfaced in the instance body. Replica
lifecycle is faithful — start/stopReplica require the target to be a replica and
leave it RUNNABLE (no SUSPENDED state change); promoteReplica detaches it from
the primary (and rejects a non-replica); failover is a primary-only operation
and rejects a replica. Covered by SDK and mock replica-lifecycle tests.
* fix(sql): review LOW items — determinism, validation, replica fidelity tests
- Cloud SQL backup-run IDs are generated by the mock from the clock + a
monotonic counter (was time.Now().UnixNano(), violating the Clock determinism
rule and collision-prone); CloneInstance now clones the source's databases and
resets replica linkage; UpdateUser doc corrected.
- Firewall rules validate IPv4 start/end (Azure SQL + both Flexible Servers);
Azure SQL Managed Instance requires subnetId, as real Azure does.
- Added a real-SDK Resource Graph indexing test that drives sqlDiscovery
end-to-end (logical server + managed instance appear and filter), and a
-race concurrency test that mutates a returned managed-instance Tags map to
pin the copy-on-read fix.
* fix(sql): address second review — MI lifecycle, in-place restore, validation
- Cloud SQL DeleteInstance now unlinks replica<->master before cascading
children, so no dangling ReadReplicaSource/ReadReplicaTargets remain.
- Managed Instance lifecycle gains a state guard (transitionManagedInstance):
Start/Stop respect current state, Failover requires Ready; MI create emits
representative metrics and clones returned Tags; adds CreateManagedInstance
cost rate.
- Cloud SQL RestoreBackup restores in place onto the existing instance via a
new optional BackupRestorer capability (was create-new -> 409).
- Failover-group failover requires a partner server; firewall rules validate
Start <= End; Azure Flex SetConfiguration rejects unknown params and empty
values; ARM parser captures the action verb so forced vs planned failover
and unknown POST verbs are distinguished; Cloud SQL selfLink/targetLink use
the served /v1 prefix.
- Broaden new-package test coverage (sub-resource CRUD, MI lifecycle, Users
update, backup get, error paths, server PATCH, raw MI start/stop).
* fix(sql): address third review — output aliasing, config catalog, validation
- Deep-copy Tags/slice fields on every Instance/Cluster/Snapshot return path in
azuresql and cloudsql (Describe/Create/Modify/Restore/Clone), so a returned
value never aliases the memstore — fixes a potential concurrent-map crash and
matches the managedinstance/FailoverGroup discipline.
- Azure Flex GetConfiguration/ListConfigurations return catalog defaults for a
known-but-unset parameter (real Azure behavior) instead of NotFound/empty; the
catalog now maps names to defaults.
- MySQL Flex batch config update is atomic: all entries are validated before any
is applied, via a new optional BatchConfigurations capability.
- Azure SQL database create/modify validates the referenced elastic pool exists
(symmetry with the pool-delete member guard).
- Cloud SQL rejects child (database/user) names containing '/' that would orphan
a row; get-paths add length guards on single-ID Describe lookups.
- Flex firewall create validates start <= end (parity with Azure SQL).
- Tests: aliasing, config defaults, batch atomicity, elastic-pool validation,
child-name rejection, FG update/list, managed-database delete, and Azure
wire-level error-mapping (404/400) across all three Azure SQL services.
* feat(gcp): add AlloyDB full-parity support
Adds GCP AlloyDB as a first-class managed database server, reusing the
relationaldb driver.
- Provider providers/gcp/alloydb: implements RelationalDB (clusters,
instances, cluster backups→ClusterSnapshot, restore) + Users + Databases,
plus a new optional rdsdriver.AlloyDB capability for AlloyDB-specific
behavior — rich cluster/instance create, cross-region secondary + promote,
instance failover/restart, continuous/automated-backup + maintenance config,
and *Info accessors. Cloud Monitoring metrics on instance create.
Copy-on-read (clone Tags/slices), '/'-name and instance-type validation,
cascade delete, and lifecycle state guards throughout.
- Server server/gcp/alloydb: alloydb.googleapis.com/v1 REST handler
(clusters[/instances|/users], backups, LRO operations, custom methods
:promote/:createsecondary/:restore/:failover/:restart) using the SDK types
for wire fidelity; SDK round-trip + wire-error (404/409/400) tests.
- Wiring: providers/gcp/gcp.go (field, monitoring, combined CloudSQL+AlloyDB
relational discovery adapter, TypeAlloyDBCluster); server/gcp Drivers gains
an opt-in AlloyDB field (left nil in DriversFrom — its paths collide with
GKE's, so the two are mutually exclusive on one server).
- Dedicated AlloyDB cost rates; docs/services.md updated (capabilities, GCP
column, operation counts).
* fix(gcp): address AlloyDB review comments
- One PRIMARY per cluster enforced (base + native create); SECONDARY instance
requires a SECONDARY cluster; READ_POOL requires nodeCount > 0.
- Detach dangling SECONDARY links when their PRIMARY cluster is deleted (the
replica-linkage class from #303).
- FailoverInstance requires a PRIMARY target; RestartInstance stays valid on
any type.
- UpdateUser is now reachable via Users.Patch (was 405).
- Custom methods (:promote / :failover / :restart) are POST-only and unknown
verbs 404 instead of misrouting to CRUD; GET on :promote no longer promotes.
- Deterministic list order: DescribeClusters/DescribeInstances use SortedValues.
- patchCluster/patchInstance handle the capability assertion; toWireUser returns
the full resource path; removed banner comments.
- Wiring: guard New against AlloyDB+GKE both enabled (they share paths) and add
DriversFromWithAlloyDB helper (enables AlloyDB in place of GKE).
- Tests for every guard + user PATCH + GET-:promote rejection + 400 wire error +
instance output aliasing.
* Add AWS MemoryDB full-parity support MemoryDB for Redis/Valkey is a durable, in-VPC cluster service. Unlike ElastiCache it is control-plane only (no Set/Get data plane), so it gets a dedicated driver (services/memorydb/driver) rather than reusing the cache driver, which mandates data-plane methods. - Driver: 33 core operations (clusters/shards/nodes, ACLs & users, parameter groups, subnet groups, snapshots, tags, engine-version & event catalogs) plus two type-asserted optional capabilities: MultiRegion (7) and ReservedNodes (3). - Provider (providers/aws/memorydb): in-memory Mock with shard/node/endpoint topology, reference validation, restore-from-snapshot, failover, CloudWatch metric emission, and clone-on-read on every return path. - Server (server/aws/memorydb): AWS JSON 1.1 handler on the "AmazonMemoryDB." target prefix, with typed faults (*NotFoundFault / *AlreadyExistsFault / InvalidParameterValueException). Verified with real aws-sdk-go-v2/memorydb round-trip tests plus wire-error assertions. - Wiring: registered in the AWS provider and server bundles; dedicated memorydb:* cost rate keys; docs/services.md section and counts updated. * Promote memorydb SDK to a direct dependency (go mod tidy) * Bound MemoryDB shard/replica counts before allocation Reject NumShards/ReplicasPerShard outside the MemoryDB service limits (500 shards, 5 replicas) with InvalidArgument before the mock allocates the shard/node topology, so an oversized create/update request cannot drive unbounded memory use. Fixes the CodeQL go/uncontrolled-allocation-size alert. * Clamp shard/replica counts inside buildShards for CodeQL The caller-side validateShardTopology guard isn't recognized across the function boundary by CodeQL's uncontrolled-allocation-size analysis. Add a defensive upper-bound clamp in buildShards itself, right before the slice allocations, so the bound dominates the make() in-function. Behavior is unchanged for valid input (callers still reject out-of-range counts first). * Drop tainted make() size hints in buildShards CodeQL's uncontrolled-allocation-size analysis does not treat the clamp reassignment as a sanitizer, so it kept flagging make([]T, 0, n) where n derives from caller input. Allocate the shard/node slices as nil and let append grow them; the clamped loop bounds keep growth bounded. No behavior change for valid input. * Address MemoryDB review: MRC linkage, fault nouns, restore fidelity, pagination, service updates Fixes from NitinKumar004's review plus the two larger parity items: Medium - Multi-region parent linkage: CreateCluster now validates the referenced MRC exists and registers the regional cluster into its Members; DeleteCluster unregisters it. The delete guard is now live — an MRC with attached regional clusters can no longer be deleted, and a dangling MultiRegionClusterName can no longer be created. - Reserved-node duplicate purchase now maps AlreadyExists to the ReservedNode noun (ReservedNodeAlreadyExistsFault, SDK-modeled) instead of the unmodeled ReservedNodesOfferingAlreadyExistsFault; NotFound keeps the Offering noun. Low - Restore preserves replica count (new ClusterConfiguration.ReplicasPerShard) plus SubnetGroupName/ParameterGroupName/TLSEnabled when the caller omits them. - UpdateCluster returns InvalidArgument (not NotFound) for a dangling ACL/parameter-group, consistent with CreateCluster. - FailoverShard checks the requested shard's node count, not shard[0]. - DeleteCluster rejects a finalSnapshotName that already exists. - CreateACL de-duplicates user names. - Multi-region parameter-group error noun corrected; ListTags returns tags in deterministic (sorted) order; docs signature typos fixed. Full parity additions - Pagination: every Describe* op honors MaxResults/NextToken via a server-side opaque base64 offset token over the deterministic result set; a bad token yields InvalidParameterValueException. - Service updates: new ServiceUpdates optional capability (DescribeServiceUpdates + BatchUpdateCluster with partial-success UnprocessedClusters), routed through the JSON 1.1 handler. Edge-case tests added across provider and SDK round-trip layers; docs and counts updated.
Amazon Keyspaces is a managed, Apache Cassandra–compatible wide-column service. It is control-plane only here (CQL data operations are out of scope), so it gets a dedicated driver rather than reusing the relational/cache drivers. - Driver (services/keyspaces/driver): 18 core operations across keyspaces, tables, user-defined types, and tags, plus an AutoScaling optional capability (GetTableAutoScalingSettings) discovered by type assertion. - Provider (providers/aws/keyspaces): in-memory Mock with keyspaces (single/ multi-region replication), tables (full schema, capacity, encryption, PITR, TTL, client-side timestamps, CDC, replicas, provisioned auto-scaling), restore-from-PITR, UDTs, and tags; schema and reference validation; clone-on- read on every return path; account-default system keyspaces. - Server (server/aws/keyspaces): AWS JSON 1.0 handler on the "KeyspacesService." target prefix, with typed faults (ResourceNotFound/Conflict/Validation). Because Keyspaces models members in lowerCamelCase and its deserializer is case-sensitive, responses are emitted with lower-camel keys. Server-side pagination (MaxResults/NextToken) over the deterministic result set. Verified with real aws-sdk-go-v2/service/keyspaces round-trip + wire-error tests. - Wiring: registered in the AWS provider and server bundles; dedicated keyspaces:* cost keys; docs/services.md section and counts updated.
… timestamp Fixes from NitinKumar004's review. Medium - UDT in-use delete guard is now live: CreateTable/RestoreTable register a table→UDT reference for every column type that mentions a UDT, DeleteTable unregisters it, so DeleteType blocks (FailedPrecondition) while a table depends on the type. - validateSchema now checks StaticColumns against AllColumns (and rejects empty/duplicate column names), matching the doc comment. - DeleteKeyspace guards user-defined types (not just tables) and refuses system keyspaces; GetType/DeleteType verify the parent keyspace exists, so a stale type can't be looked up after its keyspace is gone. - CreateKeyspace clones the caller's ReplicationRegions slice (clone-on-write), matching CreateTable. Low - Reject unknown ReplicationStrategy values; UpdateTable rejects duplicate added columns; RestoreTable validates RestoreTimestamp (optional, defaults to now, rejected if in the future) — decoded from the AWS-JSON epoch number the SDK sends, which can't unmarshal into a time.Time. - keyspaces:RestoreTable priced like CreateTable (0.01). - Response transform drops null values and the empty resultMetadata envelope, so omitted timestamps truly disappear rather than serializing as null. - Tags kept solely in the ARN-keyed map (no stale struct-level copy). - Expanded clone-on-read and edge-case tests (UDT guard, static-column validation, keyspace/type guards, region aliasing, dup column, future restore).
* Add AWS Keyspaces (Cassandra) full-parity support Amazon Keyspaces is a managed, Apache Cassandra–compatible wide-column service. It is control-plane only here (CQL data operations are out of scope), so it gets a dedicated driver rather than reusing the relational/cache drivers. - Driver (services/keyspaces/driver): 18 core operations across keyspaces, tables, user-defined types, and tags, plus an AutoScaling optional capability (GetTableAutoScalingSettings) discovered by type assertion. - Provider (providers/aws/keyspaces): in-memory Mock with keyspaces (single/ multi-region replication), tables (full schema, capacity, encryption, PITR, TTL, client-side timestamps, CDC, replicas, provisioned auto-scaling), restore-from-PITR, UDTs, and tags; schema and reference validation; clone-on- read on every return path; account-default system keyspaces. - Server (server/aws/keyspaces): AWS JSON 1.0 handler on the "KeyspacesService." target prefix, with typed faults (ResourceNotFound/Conflict/Validation). Because Keyspaces models members in lowerCamelCase and its deserializer is case-sensitive, responses are emitted with lower-camel keys. Server-side pagination (MaxResults/NextToken) over the deterministic result set. Verified with real aws-sdk-go-v2/service/keyspaces round-trip + wire-error tests. - Wiring: registered in the AWS provider and server bundles; dedicated keyspaces:* cost keys; docs/services.md section and counts updated. * Address Keyspaces review: UDT refs, schema/keyspace guards, aliasing, timestamp Fixes from NitinKumar004's review. Medium - UDT in-use delete guard is now live: CreateTable/RestoreTable register a table→UDT reference for every column type that mentions a UDT, DeleteTable unregisters it, so DeleteType blocks (FailedPrecondition) while a table depends on the type. - validateSchema now checks StaticColumns against AllColumns (and rejects empty/duplicate column names), matching the doc comment. - DeleteKeyspace guards user-defined types (not just tables) and refuses system keyspaces; GetType/DeleteType verify the parent keyspace exists, so a stale type can't be looked up after its keyspace is gone. - CreateKeyspace clones the caller's ReplicationRegions slice (clone-on-write), matching CreateTable. Low - Reject unknown ReplicationStrategy values; UpdateTable rejects duplicate added columns; RestoreTable validates RestoreTimestamp (optional, defaults to now, rejected if in the future) — decoded from the AWS-JSON epoch number the SDK sends, which can't unmarshal into a time.Time. - keyspaces:RestoreTable priced like CreateTable (0.01). - Response transform drops null values and the empty resultMetadata envelope, so omitted timestamps truly disappear rather than serializing as null. - Tags kept solely in the ARN-keyed map (no stale struct-level copy). - Expanded clone-on-read and edge-case tests (UDT guard, static-column validation, keyspace/type guards, region aliasing, dup column, future restore).
* Add Azure Managed Cassandra full-parity support Azure Managed Instance for Apache Cassandra (Microsoft.DocumentDB/ cassandraClusters) is a managed, Cassandra-compatible cluster service under Cosmos DB. Control-plane only here (CQL is out of scope), so it gets a dedicated driver. - Driver (services/managedcassandra/driver): 15 operations across clusters (create/get/list-by-rg/list-by-sub/update/delete/deallocate/start/ invokeCommand/status) and datacenters (create/get/list/update/delete). - Provider (providers/azure/managedcassandra): in-memory Mock with cluster + datacenter parent linkage (datacenter create validates the cluster; cluster delete cascade-deletes datacenters; deallocate/start propagate to datacenters), derived seed nodes, node-count bounds, and clone-on-read on every return path. - Server (server/azure/managedcassandra): ARM REST/JSON handler, path-routed via server/wire/azurearm. Long-running ops complete on the first response so the SDK's LRO pollers terminate: create/patch return the resource, delete → 204, deallocate/start → 202 + Azure-AsyncOperation, invokeCommand → 202 + Location returning the command output via an operationStatuses poll. Verified with real armcosmos CassandraClusters/CassandraDataCenters clients. - Wiring: registered in the Azure provider and server bundles; dedicated managedcassandra:* cost keys; docs/services.md section and counts updated. * docs: refresh service catalog across README and docs Bring the curated docs current with the shipped service set, including the recent database-server additions (MemoryDB, Keyspaces, Managed Cassandra): - README "What's supported": expand to the full domain matrix (wide-column Cassandra, in-memory/Redis, container orchestration/registry, event bus, notification, load balancer, DNS, logging, secrets, IAM, ML, AI search). - services.md master table: add In-memory Database and Wide-column rows. - sdk-server.md: add MemoryDB (JSON 1.1), Keyspaces (JSON 1.0), and Managed Cassandra (ARM) handler rows + protocol-detection entries. - architecture.md: broaden the server coverage summary and add the new provider/server package dirs to the layout trees. - getting-started.md: include the new services in the AWS example list. * Address Managed Cassandra review: inherit deallocated state on DC create Fixes from NitinKumar004's review. Medium - CreateOrUpdateDataCenter now inherits the parent cluster's Deallocated state, so a datacenter added to (or replaced in) a stopped cluster no longer comes back running — ClusterStatus stays internally consistent (all STOPPED / all NORMAL) instead of a mixed state. Low - CreateOrUpdateCluster preserves the service-computed GossipCertificates and PrometheusEndpoint across a re-PUT (like Deallocated), rather than dropping them. - managedcassandra cost keys renamed to the driver method names (CreateOrUpdateCluster / CreateOrUpdateDataCenter) so a recorder keyed on the real op name hits the rate. Tests - Provider: datacenter inherits the cluster's deallocated state (add-to-stopped + start-brings-back). - Server: typed *azcore.ResponseError assertions (404 get-missing, 400 dc-under-missing-cluster) and a BeginStart round-trip. * Managed Cassandra review: close remaining Low items - UpdateDataCenter treats a 0 node count as "unchanged", consistent with create (where 0 means "use the default") — removes the create-vs-PATCH node-count inconsistency. - Add a malformed-request-body → 400 server test (the previously-untested decode-error path).
Google Cloud Bigtable is a wide-column NoSQL database. Control-plane only here
(the data plane is out of scope), so it gets a dedicated driver.
- Driver (services/bigtable/driver): 44 operations across instances, clusters,
tables (with recursive column-family GC rules), app profiles, backups,
long-running operations, and per-resource IAM (get/set/testIamPermissions on
instances, tables, backups) plus cluster GetMemoryLayer.
- Provider (providers/gcp/bigtable): in-memory Mock keyed by full resource
names, with instance→cluster/table/app-profile parent linkage + cascade
delete, initial-cluster creation, recursive GC-rule handling,
soft-delete/undelete tables, backup create/copy/restore, IAM policy store,
bounded serve-node counts, and clone-on-read on every path.
- Server (server/gcp/bigtable): GCP REST/JSON handler under /v2, path-routed
with :verb custom-method support. Uses the bigtableadmin/v2 SDK types as the
wire format for exact fidelity; long-running RPCs return Operation{done:true}
with the resource inline and operations.get returns a done Operation, so SDK
LRO waits complete. Verified with real google.golang.org/api/bigtableadmin/v2
round-trip tests.
- Wiring: registered in the GCP provider and server bundles; dedicated
bigtable:* cost keys; docs (services.md, README, sdk-server.md) + counts.
- Clone cluster Autoscaling on write (clone-on-read masked a corruptible stored pointer) - Reject deleting an instance's last cluster (FailedPrecondition) - TestIamPermissions now intersects requested perms with the stored policy instead of echoing the input - Make CreateInstance atomic: validate all clusters (and reject duplicate names) before storing anything - Snapshot the source table's column families into Backup and rebuild them on RestoreTable/CopyBackup - Validate CreateBackup source table is in the same instance and not soft-deleted; require exactly one app-profile routing policy - Segment-anchor server Matches; emit Instance.CreateTime; reword cost comment - Fix docs op-count 44 -> 38 and Grand Total 1387 -> 1381 - Add provider tests: autoscaling aliasing, nested GC-rule deep clone, IAM deny, last-cluster delete, create-instance atomicity, restore keeps column families, app-profile routing, backup source-table validation
* Add GCP Bigtable Admin full-parity support
Google Cloud Bigtable is a wide-column NoSQL database. Control-plane only here
(the data plane is out of scope), so it gets a dedicated driver.
- Driver (services/bigtable/driver): 44 operations across instances, clusters,
tables (with recursive column-family GC rules), app profiles, backups,
long-running operations, and per-resource IAM (get/set/testIamPermissions on
instances, tables, backups) plus cluster GetMemoryLayer.
- Provider (providers/gcp/bigtable): in-memory Mock keyed by full resource
names, with instance→cluster/table/app-profile parent linkage + cascade
delete, initial-cluster creation, recursive GC-rule handling,
soft-delete/undelete tables, backup create/copy/restore, IAM policy store,
bounded serve-node counts, and clone-on-read on every path.
- Server (server/gcp/bigtable): GCP REST/JSON handler under /v2, path-routed
with :verb custom-method support. Uses the bigtableadmin/v2 SDK types as the
wire format for exact fidelity; long-running RPCs return Operation{done:true}
with the resource inline and operations.get returns a done Operation, so SDK
LRO waits complete. Verified with real google.golang.org/api/bigtableadmin/v2
round-trip tests.
- Wiring: registered in the GCP provider and server bundles; dedicated
bigtable:* cost keys; docs (services.md, README, sdk-server.md) + counts.
* Address GCP Bigtable review: provider correctness + docs op-count
- Clone cluster Autoscaling on write (clone-on-read masked a corruptible
stored pointer)
- Reject deleting an instance's last cluster (FailedPrecondition)
- TestIamPermissions now intersects requested perms with the stored policy
instead of echoing the input
- Make CreateInstance atomic: validate all clusters (and reject duplicate
names) before storing anything
- Snapshot the source table's column families into Backup and rebuild them
on RestoreTable/CopyBackup
- Validate CreateBackup source table is in the same instance and not
soft-deleted; require exactly one app-profile routing policy
- Segment-anchor server Matches; emit Instance.CreateTime; reword cost comment
- Fix docs op-count 44 -> 38 and Grand Total 1387 -> 1381
- Add provider tests: autoscaling aliasing, nested GC-rule deep clone,
IAM deny, last-cluster delete, create-instance atomicity, restore keeps
column families, app-profile routing, backup source-table validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
✨ Features
Managed database services
Nine managed database and data services now work against cloudemu with their real SDKs — including the native child resources a real workload provisions, surfaced in cross-service discovery and priced in the cost catalog:
AWS RDS & Bedrock
bedrock,bedrockruntime,bedrockagent, andbedrockagentruntime.AWS ECS
aws-sdk-go-v2/service/ecsclients work against cloudemu unchanged.Operator.ManagedandIncludeManagedResourcesonDescribeInstances.Kubernetes
kubectlworks end to end, not justclient-go.Standalone server tooling
/_cloudemu/seedendpoint./_cloudemu/resetwipes all state between tests for isolation.S3 object versioning
versionIdoperations, delete markers, and versioned multipart completion.🔧 Enhancements
Improvements that came out of running real infrastructure provisioners against cloudemu — closing behavior gaps so full provision-and-teardown flows work end to end.
SSM Parameter Store
GetParametersByPathandDescribeParametersnow paginate, so large parameter trees come back page by page, exactly as the real service returns them.ParameterVersionNotFounderror instead of a generic "not found".S3
ListPartsnow reports the parts you've actually uploaded to a multipart upload, and rejects part numbers outside the valid 1–10000 range.405 Method Not Allowed, matching real S3.Azure Table Storage
$filterqueries that use unsupported expressions or partial-key predicates are now rejected with a clear error, instead of quietly returning incorrect results.$filterexpressions with extra whitespace are now parsed correctly.Technical Details
Managed database services (#307, #305, #304, #303, #308, #309)
relationaldbdriver and its child resources (Users/Databases/…).Microsoft.DocumentDB/cassandraClustersARM surface (cluster + datacenter parent/child, deallocate/start LRO, invoke-command, status).bigtableadmin/v2SDK types as the wire format for exact fidelity; recursive GC rules, per-resource IAM, and cascade delete.RDS (#301) & Bedrock (#298)
ECS + EC2 managed visibility (#302, #159, #300)
aws-sdk-go-v2/service/ecs.Operator.Managed+IncludeManagedResourcesonDescribeInstances, hidden by default via an account setting (ECS Managed Instances are a primary producer of managed EC2 instances).Kubernetes runtime + discovery (#299, #297)
kubectlsupport end-to-end.Standalone-server tooling (#247, #248, #250, #244)
/_cloudemu/seedendpoint with validate-before-write and oversize413(feat: go:embed fixture-seeding loader for drivers #250)./_cloudemu/resetadmin control plane for test isolation (feat: admin/state HTTP API (reset, seed, snapshot, restore) on the SDK-compat server #244).S3 versioning & fidelity batches (#266 workstream, #296)
versionIdops, delete markers, versioned multipart completion.ListPartsbounds and sub-resource405s, Azure table$filtervalidation, multipart parts-map guarding.aws-sdk-go-v2round-trip test.