SDK-Compatible Server
Point real aws-sdk-go-v2, azure-sdk-for-go, and Google Cloud SDKs at cloudemu without changing your application code
Point a real aws-sdk-go-v2, azure-sdk-for-go, or cloud.google.com/go client at cloudemu and run production code unchanged. The HTTP servers speak each provider's actual wire protocol; the custom endpoint is the only change.
one server, three wire protocols
Nothing to mock. No Docker. No accounts. The same SDK calls you'd run against real cloud APIs hit a local httptest.NewServer and get back SDK-decodable responses.
NOTE
Want a long-lived, out-of-process server that any language's SDK can point at? See Standalone Server.
Why#
cloudemu's Portable API works well for new code you write for testing. But most real apps already use the official cloud SDKs directly. Rewriting those call sites just to test against an emulator is friction. The SDK-compat server removes that friction — change the endpoint, done.
Quick start (AWS)#
package main
import (
"context"
"net/http/httptest"
"github.com/aws/aws-sdk-go-v2/aws"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/stackshy/cloudemu/v2"
awsserver "github.com/stackshy/cloudemu/v2/server/aws"
)
func main() {
ctx := context.Background()
cloud := cloudemu.NewAWS()
srv := awsserver.New(awsserver.Drivers{
S3: cloud.S3,
DynamoDB: cloud.DynamoDB,
EC2: cloud.EC2,
VPC: cloud.VPC,
Lambda: cloud.Lambda,
SQS: cloud.SQS,
CloudWatch: cloud.CloudWatch,
})
ts := httptest.NewServer(srv)
defer ts.Close()
cfg, _ := awsconfig.LoadDefaultConfig(ctx,
awsconfig.WithRegion("us-east-1"),
awsconfig.WithCredentialsProvider(
credentials.NewStaticCredentialsProvider("test", "test", ""),
),
)
// Use the REAL aws-sdk-go-v2 client — only the endpoint changes.
client := s3.NewFromConfig(cfg, func(o *s3.Options) {
o.BaseEndpoint = aws.String(ts.URL)
o.UsePathStyle = true
})
client.CreateBucket(ctx, &s3.CreateBucketInput{Bucket: aws.String("my-bucket")})
// ... full SDK API works
}Region and credentials can be any dummy values — the server doesn't validate signatures.
Quick start (Azure)#
import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5"
"github.com/stackshy/cloudemu/v2"
azureserver "github.com/stackshy/cloudemu/v2/server/azure"
)
cp := cloudemu.NewAzure()
srv := azureserver.New(azureserver.Drivers{
VirtualMachines: cp.VirtualMachines,
BlobStorage: cp.BlobStorage,
CosmosDB: cp.CosmosDB,
Network: cp.VNet,
Monitor: cp.Monitor,
Functions: cp.Functions,
ServiceBus: cp.ServiceBus,
})
// Azure SDK refuses bearer tokens over plain HTTP — use TLS.
ts := httptest.NewTLSServer(srv)
opts := &arm.ClientOptions{
ClientOptions: azcore.ClientOptions{
Cloud: cloud.Configuration{
Services: map[cloud.ServiceName]cloud.ServiceConfiguration{
cloud.ResourceManager: {
Endpoint: ts.URL,
Audience: "https://management.azure.com",
},
},
},
Transport: ts.Client(),
},
}
client, _ := armcompute.NewVirtualMachinesClient("sub-1", fakeCred{}, opts)Quick start (GCP)#
import (
gcpcompute "cloud.google.com/go/compute/apiv1"
"github.com/stackshy/cloudemu/v2"
gcpserver "github.com/stackshy/cloudemu/v2/server/gcp"
"google.golang.org/api/option"
)
cp := cloudemu.NewGCP()
srv := gcpserver.New(gcpserver.Drivers{
Compute: cp.GCE,
Storage: cp.GCS,
Firestore: cp.Firestore,
Networking: cp.VPC,
Monitoring: cp.CloudMonitoring,
CloudFunctions: cp.CloudFunctions,
PubSub: cp.PubSub,
})
ts := httptest.NewServer(srv)
opts := []option.ClientOption{
option.WithEndpoint(ts.URL),
option.WithoutAuthentication(),
option.WithHTTPClient(ts.Client()),
}
client, _ := gcpcompute.NewInstancesRESTClient(ctx, opts...)Currently supported#
AWS handlers#
| Service | Operations |
|---|---|
| S3 | CreateBucket, DeleteBucket, ListBuckets, PutObject, GetObject, HeadObject, DeleteObject, ListObjectsV2 (prefix, delimiter, common prefixes, continuation token), CopyObject |
| DynamoDB | CreateTable, DeleteTable, DescribeTable, ListTables, PutItem, GetItem, DeleteItem, UpdateItem (SET/REMOVE), Query, Scan (with FilterExpression), BatchWriteItem, BatchGetItem, TransactWriteItems |
| EC2 | RunInstances, DescribeInstances (filters: instance-id, instance-type, instance-state-name, tag:*), Start/Stop/Reboot/TerminateInstances, ModifyInstanceAttribute |
| EC2 — VPC + Networking | VPCs, Subnets, Security Groups + ingress/egress rules, Internet Gateways, Route Tables + Routes, NAT Gateways, VPC Peering, Flow Logs, Network ACLs |
| EC2 — EBS + Key Pairs | Volumes (Create/Delete/Describe/Attach/Detach), Key Pairs |
| EC2 — Snapshots + AMIs + Spot + Launch Templates | Snapshots, Images, Spot instance requests, Launch Templates |
| Auto Scaling | CreateAutoScalingGroup, Update/Delete/Describe, SetDesiredCapacity, scaling policies |
| Lambda (REST + JSON) | CreateFunction, GetFunction, ListFunctions, DeleteFunction, Invoke (sync) |
| SQS (JSON-RPC AwsJson1_0) | CreateQueue, GetQueueUrl, ListQueues, DeleteQueue, SendMessage, ReceiveMessage, DeleteMessage |
| CloudWatch (Smithy rpc-v2-cbor) | PutMetricData, GetMetricStatistics, ListMetrics, PutMetricAlarm, DescribeAlarms, DeleteAlarms |
| RDS | CreateDBInstance, DescribeDBInstances, Modify/Delete/Start/Stop/RebootDBInstance; DB clusters (Create/Describe/Modify/Delete/Start/Stop); DB + cluster snapshots + restore (Aurora, Neptune, DocumentDB engines) |
| Redshift | CreateCluster, DescribeClusters, Modify/Delete/RebootCluster; cluster snapshots (Create/Describe/Delete) + RestoreFromClusterSnapshot |
| EKS | Clusters (Create/Describe/List/UpdateConfig/UpdateVersion/Delete); managed node groups; Fargate profiles; addons. Issues kubeconfigs pointing at the shared Kubernetes data plane |
| IAM | Users, groups, roles, managed policies (+ versions) & attach/detach, access keys, instance profiles |
| ECR | CreateRepository, DescribeRepositories, DeleteRepository; PutImage, ListImages, DescribeImages, BatchDeleteImage |
| Bedrock (+ bedrock-runtime) | ListFoundationModels, GetFoundationModel; custom models, guardrails, provisioned throughput, invocation logging; runtime InvokeModel, Converse (deterministic echo responses) |
| SageMaker (+ sagemaker-runtime) | Models, endpoint configs, endpoints, inference components; training/tuning/transform/AutoML jobs; registry, Studio, notebooks, Feature Store, pipelines; runtime InvokeEndpoint / InvokeEndpointAsync |
| Resource Explorer 2 | CreateView, DeleteView, ListViews, GetView, Search, ListResources, ListIndexes, GetIndex (service: / tag.k:v / region: filters) |
| Resource Groups Tagging API | GetResources, GetTagKeys, GetTagValues, TagResources, UntagResources |
| Route 53 (DNS) | Hosted zones (Create/Get/List/Delete); resource record sets (ChangeResourceRecordSets, ListResourceRecordSets); health checks |
| ELBv2 (Load Balancer) | Load balancers, target groups, listeners, rules; RegisterTargets/DeregisterTargets, DescribeTargetHealth; Modify/DescribeLoadBalancerAttributes |
| ElastiCache (Cache) | Cache clusters + replication groups + subnet groups (Create/Describe/Modify/Delete) |
| Secrets Manager | CreateSecret, GetSecretValue, PutSecretValue, DescribeSecret, ListSecrets, ListSecretVersionIds, DeleteSecret |
| CloudWatch Logs | Log groups + log streams (Create/Delete/Describe); PutLogEvents, GetLogEvents, FilterLogEvents; metric filters |
| SNS (Notification) | CreateTopic, DeleteTopic, ListTopics, Subscribe, Unsubscribe, ListSubscriptions, Publish |
| EventBridge (Event Bus) | Event buses (Create/Delete/List); PutRule, DeleteRule, Enable/DisableRule, ListRules; PutTargets, RemoveTargets, ListTargets; PutEvents |
| ECS | Clusters, task definitions, services, tasks (register/run/list/describe/update/delete) |
| SSM (Parameter Store) | PutParameter, GetParameter(s), GetParametersByPath, DescribeParameters, DeleteParameter(s), label/history |
| STS | GetCallerIdentity, AssumeRole, GetSessionToken (identity reported by the emulator) |
| Keyspaces (Cassandra) | Keyspaces + tables (Create/Get/List/Update/Delete) — AWS JSON 1.0 |
| MemoryDB (Redis/Valkey) | Clusters (Create/Describe/Update/Delete, FailoverShard), ACLs + users, subnet/parameter groups — AWS JSON 1.1 |
| Network Firewall | Firewalls, firewall policies, rule groups (Create/Describe/Update/Delete) |
Azure handlers#
All speak ARM JSON over HTTPS unless noted.
| Service | ARM provider / operations |
|---|---|
| Virtual Machines | Microsoft.Compute/virtualMachines — CreateOrUpdate, Get, List, Delete, start, powerOff, restart |
| Disks / Snapshots / Images / SSH Public Keys | Microsoft.Compute/{disks,snapshots,images,sshPublicKeys} — full CRUD |
| Blob Storage (data plane) | Containers + Blobs: Create/Delete/List, PutBlob, GetBlob, DeleteBlob, CopyBlob |
| Cosmos DB (data plane) | Databases, Containers, Documents — full CRUD with x-ms-documentdb-* headers |
| Virtual Network | Microsoft.Network/virtualNetworks — CRUD + subnets |
| Azure Monitor | microsoft.insights/metricAlerts and metric data ingest/read |
| Functions | Microsoft.Web/sites (Function Apps): CreateOrUpdate, Get, List, Delete + non-ARM /api/{name} invoke |
| Service Bus | Microsoft.ServiceBus/namespaces[/queues] ARM CRUD + raw-HTTP REST data plane (POST /{ns}/{queue}/messages, DELETE /messages/head) |
| SQL Database | Microsoft.Sql/servers + .../databases — PUT/GET/PATCH/DELETE/LIST |
| PostgreSQL / MySQL Flexible Server | Microsoft.DBforPostgreSQL/flexibleServers, Microsoft.DBforMySQL/flexibleServers — CRUD + start/stop/restart |
| AKS | Microsoft.ContainerService/managedClusters — CreateOrUpdate/Get/List/UpdateTags/Delete/RotateCertificates; agent pools; maintenance configs. Issues kubeconfigs for the shared Kubernetes data plane |
| IAM | Microsoft.Authorization — role assignments + role definitions |
| ACR | Microsoft.ContainerRegistry/registries — registries + repositories + tags |
| Databricks (ARM + data plane) | Microsoft.Databricks/workspaces control plane + workspace REST data plane (clusters, jobs, runs, instance pools, policies, libraries, permissions, DBFS, repos, secrets, Unity Catalog, SQL warehouses, …) |
| Resource Graph | POST /providers/Microsoft.ResourceGraph/resources — KQL subset over the Resources table (where type ==, in~, location, tags[…], limit/take) |
| Azure DNS | Microsoft.Network/dnsZones + record sets — CreateOrUpdate/Get/List/Delete |
| Load Balancer | Microsoft.Network/loadBalancers — CreateOrUpdate/Get/List/Delete + rules, probes, backend pools |
| Azure Cache for Redis | Microsoft.Cache/redis — CreateOrUpdate/Get/List/Delete + keys |
| Key Vault (Secrets) | Microsoft.KeyVault/vaults control plane + secrets data plane (Set/Get/List/Delete versions) |
| Log Analytics | Microsoft.OperationalInsights/workspaces + data-plane ingest/query |
| Notification Hubs | Microsoft.NotificationHubs/namespaces[/notificationHubs] — CRUD + send |
| Event Grid | Microsoft.EventGrid/topics + event subscriptions; event publish |
| Azure AI | Microsoft.CognitiveServices/accounts (AI Foundry / AI Studio) — accounts + deployments |
| Azure AI Search | Microsoft.Search/searchServices — CreateOrUpdate/Get/List/Delete |
| Table Storage (data plane) | Tables + entities — Create/Query/Insert/Merge/Delete |
| Cosmos DB for PostgreSQL | Microsoft.DBforPostgreSQL/serverGroupsv2 (Citus) — CRUD |
| Managed Cassandra | Microsoft.DocumentDB/cassandraClusters[/dataCenters] — CRUD |
GCP handlers#
All speak REST + JSON.
| Service | Operations |
|---|---|
| Compute Engine | Instances + Disks + Snapshots + Images: insert/get/list/delete with LRO envelopes |
| Networks | VPCs, Subnetworks, Firewalls, Routes |
| Cloud Storage (GCS) | Buckets + Objects: create/get/list/delete, upload, download, copy |
| Firestore | Documents + Collections via :commit, :batchGet, :runQuery |
| Cloud Monitoring | Time-series ingest/read, alert policies |
| Cloud Functions v1 | Create (LRO), Get, List, Delete (LRO), :call (sync invoke) |
| Pub/Sub | Topics + Subscriptions lifecycle, :publish, :pull, :acknowledge |
| Cloud SQL | Instances insert/get/list/update/delete, restart, restoreBackup; backup runs; operations (MySQL/PostgreSQL/SQL Server) |
| GKE | Clusters (Create/Get/List/Update/Delete) + config setters; node pools (Create/Get/List/Update/Delete, SetSize/Autoscaling/Management, Rollback); operations. Issues kubeconfigs for the shared Kubernetes data plane |
| IAM | Service accounts + roles (getIamPolicy / setIamPolicy bindings) |
| Artifact Registry | Repositories + Docker images |
| Vertex AI | Models, endpoints (Deploy/Undeploy), datasets, jobs, pipelines, Feature Store, Vector Search; runtime GenerateContent (Gemini), CountTokens, Predict, RawPredict |
| Cloud Asset Inventory | searchAllResources, searchAllIamPolicies, exportAssets, batchGetAssetsHistory, assets.list, feeds CRUD |
| Cloud DNS | Managed zones + resource record sets — create/get/list/delete + changes |
| Cloud Load Balancing | Backend services, URL maps, target proxies, forwarding rules, health checks |
| Memorystore (Cache) | Redis instances — create (LRO)/get/list/update/delete |
| Secret Manager | Secrets + versions — create/get/list/delete, :access, :addVersion |
| Cloud Logging | Log entries (entries:write, entries:list), log buckets/sinks |
| FCM (Notification) | projects/*/messages:send — send to token / topic / condition |
| Eventarc (Event Bus) | Triggers + channels — create (LRO)/get/list/delete |
| AlloyDB | Clusters + instances — create (LRO)/get/list/delete |
| Bigtable | Instances, clusters, tables — admin create/get/list/delete |
Any operation not in these tables returns 501 Not Implemented or the provider's native error code (UnknownOperation, NotImplemented, NOT_FOUND).
Kubernetes data plane (shared across providers)#
EKS, AKS, and GKE each issue kubeconfigs that point at a single shared in-memory Kubernetes API server (kubernetes.NewAPIServer(), passed as K8sAPI to each provider's Drivers). Real client-go / kubectl drive it end-to-end: Namespace, Pod, Service, ConfigMap, Secret, ServiceAccount, Deployment support Create/Get/List/Update/Delete/Patch (JSON-merge) + Watch streaming; Endpoints are read-only. API groups core/v1 and apps/v1. See the Kubernetes service page for what's intentionally out of scope (no scheduler, controllers, RBAC, or PV/PVC).
How it works#
The server is a tiny core plus a plugin-per-service model. Each service is a self-contained package under server/.
server/
├── server.go # core: Handler interface + dispatcher (~80 LOC)
├── wire/
│ ├── wire.go # shared XML/JSON helpers
│ ├── awsquery/ # AWS query-protocol decoder + XML envelope
│ ├── azurearm/ # ARM URL parser + JSON helpers + error envelope
│ └── gcprest/ # GCP REST URL parser + Operation LRO helpers
├── aws/
│ ├── aws.go # awsserver.New(Drivers{...})
│ ├── s3/ ec2/ dynamodb/ lambda/ sqs/ cloudwatch/ rds/ redshift/
│ ├── eks/ iam/ ecr/ ecs/ ssm/ sts/ bedrock/ sagemaker/
│ ├── bedrockagent/ bedrockagentruntime/ keyspaces/ memorydb/ networkfirewall/
│ ├── route53/ elbv2/ elasticache/ secretsmanager/ cloudwatchlogs/ sns/ eventbridge/
│ └── resourceexplorer2/ resourcegroupstaggingapi/
├── azure/
│ ├── azure.go # azureserver.New(Drivers{...})
│ ├── virtualmachines/ disks/ snapshots/ images/ sshpublickeys/
│ ├── blobstorage/ cosmosdb/ cosmosaccount/ vnet/ monitor/ functions/ servicebus/ queue/
│ ├── sql/ postgresflex/ mysqlflex/ cosmospostgresql/ managedcassandra/ aks/ iam/ acr/
│ ├── dns/ loadbalancer/ cache/ keyvault/ loganalytics/ notificationhubs/ eventgrid/
│ ├── ai/ search/ tablestorage/ storageaccount/ subscriptions/ resourcegroups/
│ ├── databricks/ # ARM + workspace data plane (dbfs, repos, uc, …)
│ └── resourcegraph/
└── gcp/
├── gcp.go # gcpserver.New(Drivers{...})
├── compute/ vpc/ gcs/ firestore/ monitoring/ lro/ servicenetworking/
├── cloudfunctions/ pubsub/ cloudsql/ gke/ iam/ alloydb/ bigtable/
├── clouddns/ loadbalancer/ memorystore/ secretmanager/ cloudlogging/ fcm/ eventarc/
└── artifactregistry/ vertexai/ cloudasset/Each handler implements a two-method interface:
type Handler interface {
Matches(r *http.Request) bool // detect by header/path/form
ServeHTTP(w http.ResponseWriter, r *http.Request)
}server.Server iterates registered handlers and dispatches to the first that claims the request. Adding a new service is one new package + one Register call. The core never changes.
Protocol detection#
Each handler uses a different signal so dispatch is unambiguous within a provider:
| Handler | How it's detected |
|---|---|
| AWS DynamoDB | X-Amz-Target: DynamoDB_20120810.* header |
| AWS SQS | X-Amz-Target: AmazonSQS.* header |
| AWS Lambda | URL prefix /2015-03-31/functions |
| AWS EC2 | Action=… in URL query or Content-Type: application/x-www-form-urlencoded POST |
| AWS CloudWatch | Smithy-Protocol: rpc-v2-cbor header |
| AWS S3 | Fallback (everything else REST-shaped) |
| Azure (all ARM) | URL begins with /subscriptions/{sub} and matches Microsoft.<Provider>/<Type> |
| Azure Cosmos | URL begins with /dbs/ (data plane, non-ARM) |
| Azure Functions invoke | URL begins with /api/ (non-ARM data plane) |
| Azure Service Bus data plane | Non-ARM URL ending in /messages or /messages/head |
| Azure Blob | Fallback (everything else non-ARM REST-shaped) |
| GCP Compute / Networks | URL prefix /compute/v1/ |
| GCP Cloud Functions | /v1/projects/.../locations/.../functions[/...] |
| GCP Pub/Sub | /v1/projects/.../topics[/...] or /v1/projects/.../subscriptions[/...] |
| GCP Firestore | /v1/projects/.../databases/.../documents[/...] |
| GCP Cloud Monitoring | /v3/projects/.../ |
| GCP GCS | Fallback (/storage/v1/ and /{bucket}/{object} direct-media) |
The newer handlers follow the same per-provider scheme: AWS RDS/Redshift use the query protocol (Action=…), Bedrock/SageMaker/Resource Explorer dispatch on X-Amz-Target or their REST path prefix; Azure SQL/Postgres/MySQL/AKS/ACR/Resource Graph match their Microsoft.<Provider>/<Type> ARM path, and Databricks data-plane calls match the /api/2.x/ prefix; GCP Cloud SQL, GKE, Vertex AI, and Cloud Asset match their REST path prefixes.
Registration order matters when handlers share a path prefix — the provider factories register more-specific handlers ahead of catch-alls (S3, Blob, GCS, Firestore) so first-match-wins resolves correctly.
Coverage status#
| Provider | Domains shipped |
|---|---|
| AWS | Storage, Compute (full VPC stack), Database (+ Keyspaces/MemoryDB), Relational DB (RDS/Aurora/Redshift), Serverless, Kubernetes (EKS + data plane), Container Orchestration (ECS), Message Queue, Monitoring, Logging, DNS, Load Balancer, Cache, Secrets, Notification, Event Bus, IAM/STS, Parameter Store (SSM), Container Registry, Network Firewall, Resource Discovery, Bedrock, SageMaker |
| Azure | Storage (+ Table Storage), Compute (+ Disks/Snapshots/Images/SSHKeys), Database (+ Managed Cassandra), Relational DB (SQL + Postgres/MySQL Flex + Cosmos-for-PostgreSQL), Serverless, Kubernetes (AKS + data plane), Message Queue (ARM + REST data plane), Networking, Monitoring, Logging, DNS, Load Balancer, Cache, Secrets (Key Vault), Notification, Event Bus, IAM, Container Registry, Resource Discovery, Azure AI, Azure AI Search, Databricks |
| GCP | Storage, Compute (+ Disks/Snapshots/Images), Database (+ Bigtable), Relational DB (Cloud SQL + AlloyDB), Serverless, Kubernetes (GKE + data plane), Message Queue, Networking, Monitoring, Logging, DNS, Load Balancer, Cache, Secrets, Notification, Event Bus, IAM, Container Registry, Resource Discovery, Vertex AI |
Every core service domain now ships an SDK-compat handler across all three providers — including DNS, Load Balancer, Cache, Secrets, Logging, Notification, and Event Bus, which were the last cross-cloud domains to land. New services continue to drop in as self-contained handler packages without touching the core.
Writing your own handler#
If you need a service we don't cover yet, implement the server.Handler interface in your own package and register it:
type MyHandler struct{ /* driver */ }
func (*MyHandler) Matches(r *http.Request) bool {
// your detection logic
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// your logic
}
srv := server.New()
srv.Register(&MyHandler{...})The Handler interface is the only contract — no registration is needed in core cloudemu. If the handler is generally useful, a PR to add it under server/<provider>/<service> is welcome.
Limitations#
- No signature validation. cloudemu is a local development tool, not a security boundary. Requests are accepted regardless of AWS SigV4 / Azure AAD / GCP OAuth signatures.
- No AMQP for Azure Service Bus. The modern
azservicebusSDK uses AMQP exclusively for data plane. ARM control plane is fully supported viaarmservicebus; tests that need send/receive can use the raw-HTTP REST data plane. - GCS direct-media downloads assume path-style URLs.
- DynamoDB / Cosmos / Firestore filters and queries support common patterns but are not full DSL parsers.
- Pagination tokens are honored where present in the SDK contract; some list operations short-circuit to a single page.
When a client hits an unsupported operation, the server responds with the provider's native error code so failures are easy to diagnose.