update - #4
Merged
Merged
Conversation
enhance metrics part for resources
6 tasks
aryanmehrotra
added a commit
to aryanmehrotra/cloudemu
that referenced
this pull request
Jul 27, 2026
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 (stackshy#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.
thzgajendra
pushed a commit
that referenced
this pull request
Jul 28, 2026
…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.
Merged
4 tasks
thzgajendra
added a commit
to thzgajendra/cloudemu
that referenced
this pull request
Jul 29, 2026
Address review MEDIUM stackshy#2/stackshy#3/stackshy#4 (Azure update paths) and stackshy#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.
thzgajendra
added a commit
that referenced
this pull request
Jul 30, 2026
…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.
NitinKumar004
added a commit
that referenced
this pull request
Jul 30, 2026
…ocs) 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.
NitinKumar004
added a commit
that referenced
this pull request
Jul 30, 2026
…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.
thzgajendra
added a commit
that referenced
this pull request
Jul 31, 2026
* 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.
4 tasks
This was referenced Aug 5, 2026
Merged
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.
No description provided.