simulator-azure

command module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: AGPL-3.0 Imports: 66 Imported by: 0

README

simulator-azure

Local reimplementation of the Azure slice that sockerless touches. Not a mock — Container Apps job executions respect replicaTimeout for completion, Azure Functions invoke and produce real AppTraces entries, Kusto Query Language (KQL) queries parse and filter against real log data, and Azure Container Registry (ACR) stores real OCI manifests with chunked upload support.

Reference adaptor

The simulator exposes one HTTP endpoint (default :4568) that fronts all Azure services. Three external tools exercise that endpoint at Azure-API fidelity:

Adaptor Min version What it proves
Azure SDK for Go (armappcontainers, armappservice, armcontainerregistry, ...) v3+ Wire-level SDK compatibility — ARM REST shape, OData filters, async LRO polling (Azure-AsyncOperation / Location headers).
az CLI 2.60+ Endpoint-override fidelity (the sim accepts https://management.azure.com traffic when fronted with TLS).
Terraform azurerm provider v4+ Full plan → apply → destroy round-trip across azurerm_resource_group, azurerm_container_app_environment, azurerm_container_app_job, azurerm_linux_function_app, azurerm_log_analytics_workspace, azurerm_container_registry, azurerm_storage_account, azurerm_private_dns_zone, etc. macOS direct go test delegates to the shared Linux Docker test image so Terraform can honor SSL_CERT_FILE.

Anything any of these three tools does against the real Azure endpoint, it must do against this simulator. Gaps from that contract are real bugs (see BUGS.md).

The simulator is the upstream for the Azure Container Apps and Azure Functions backends during local development and CI.

The Storage data plane includes Blob container/blob CRUD, block staging, and Copy Blob via the public x-ms-copy-source REST operation. Copy Blob accepts simulator host-style and Azurite-style path-style source URLs, copies the stored source bytes, and returns Azure copy ID/status headers.

Validation

Test path What runs Last green
sdk-tests/ (31 tests) Real Azure SDK for Go clients against the sim. Per-op assertions on ARM response shape + error envelopes. 2026-05-13
cli-tests/ (17 tests) Real az CLI invoked via os/exec (using az rest for raw ARM calls). 2026-05-13
terraform-tests/ (TLS; Docker delegation on macOS) Real Terraform azurerm provider against the sim. 2026-05-28
make simulator-azure/test Leaf-Makefile unit + integration suite per docs/MAKEFILE_STANDARD.md. 2026-05-13

Wiring the adaptor

# 1. Build + start the sim (default :4568).
cd simulator-azure
go build -o simulator-azure .
SIM_LISTEN_ADDR=:4568 ./simulator-azure

With SIM_PERSIST=true, SIM_DATA_DIR is the persistence root: the SQLite control-plane store plus the Azure Files content root, which defaults to <SIM_DATA_DIR>/files (SIM_AZURE_FILES_DATA_DIR overrides) so share contents survive restarts alongside the metadata that describes them.

Docker or Podman is required when Container Apps or Azure Functions calls execute workloads. For API-only checks that do not invoke workload execution, SIM_RUNTIME=process starts the Azure simulator without initializing Docker/Podman. The /health response reports runtime and capabilities.workloadExecution; clients must require that capability before submitting work that needs a running container.

Service Bus raw AMQP/TLS is a second, optional listener because real Azure Service Bus exposes AMQP as a TCP/TLS transport in addition to HTTP/REST and WebSocket tunneling:

SIM_LISTEN_ADDR=:4568 \
SIM_SERVICEBUS_AMQP_LISTEN_ADDR=:5671 \
SIM_SERVICEBUS_AMQP_TLS_CERT=server-cert.pem \
SIM_SERVICEBUS_AMQP_TLS_KEY=server-key.pem \
./simulator-azure

Official azservicebus SDK callers can then keep a normal Service Bus connection string and point ClientOptions.CustomEndpoint at the raw AMQP listener. The listener also accepts the shared SIM_TLS_CERT / SIM_TLS_KEY values when the Service Bus-specific cert variables are unset. AMQP queue and topic receivers honor outstanding flow credit: messages sent after the receiver has issued credit are delivered without requiring another flow frame.

Local DNS for host-addressed data planes

Azure data planes are host-addressed. Blob Storage uses hosts like <account>.blob.core.windows.net, Service Bus uses <namespace>.servicebus.windows.net, and Event Grid topics/domains return *.eventgrid.azure.net publish endpoints. The simulator preserves that shape for local custom-cloud endpoints, so local clients need DNS that can resolve the simulator's host-style names.

The Azure simulator can serve those names directly:

SIM_LISTEN_ADDR=:4568 \
SIM_AZURE_DNS_LISTEN_ADDR=127.0.0.1:53 \
SIM_AZURE_DNS_ZONES=sockerless.azure.local,localhost \
SIM_AZURE_DNS_TARGET_IPV4=127.0.0.1 \
./simulator-azure

SIM_AZURE_DNS_LISTEN_ADDR enables the DNS listener. It serves DNS over UDP and TCP and fails startup if it cannot bind. SIM_AZURE_DNS_ZONES is a comma-separated list of local zones to answer. SIM_AZURE_DNS_TTL defaults to 60. SIM_AZURE_DNS_TARGET_IPV4 defaults to 127.0.0.1; SIM_AZURE_DNS_TARGET_IPV6 is optional and is only answered when set.

For macOS, configure a resolver for the local simulator zone after the DNS listener is running on port 53:

sudo mkdir -p /etc/resolver
printf 'nameserver 127.0.0.1\n' | sudo tee /etc/resolver/sockerless.azure.local

For systemd-resolved Linux hosts, route the simulator zone to the local DNS listener on the active link:

sudo resolvectl dns "$(ip route show default | awk '{print $5; exit}')" 127.0.0.1
sudo resolvectl domain "$(ip route show default | awk '{print $5; exit}')" '~sockerless.azure.local'

Then use the local custom-cloud host as the ARM endpoint:

az rest --method GET \
  --url "http://sockerless.azure.local:4568/subscriptions/00000000-0000-0000-0000-000000000001?api-version=2021-04-01"

ARM-returned data-plane endpoints such as http://myacct.blob.sockerless.azure.local:4568/, sb://myns.servicebus.sockerless.azure.local:4568/, and http://mytopic.eventgrid.sockerless.azure.local:4568/api/events keep the same host-addressed contract and resolve through the simulator DNS server.

Advertised data-plane endpoints

The ARM control plane can advertise data-plane hosts that are different from the ARM request host. This is for deployments where another local component fronts the data plane, such as a shim translating Azure-shaped Blob, Key Vault, Service Bus, or Event Grid calls to another backing cloud. The public ARM API shape does not change; only the normal Azure response fields point at the configured host.

Configure the projection with SIM_AZURE_ARM_EXTERNAL_DATA_PLANE_URLS_JSON:

SIM_AZURE_ARM_EXTERNAL_DATA_PLANE_URLS_JSON='{
  "storage": {
    "blob": "https://{account}.blob.shim.azure.local/",
    "file": "https://{account}.file.shim.azure.local/",
    "queue": "https://{account}.queue.shim.azure.local/",
    "table": "https://{account}.table.shim.azure.local/",
    "web": "https://{account}.web.shim.azure.local/",
    "dfs": "https://{account}.dfs.shim.azure.local/"
  },
  "keyVault": "https://{vault}.vault.shim.azure.local/",
  "serviceBus": "https://{namespace}.servicebus.shim.azure.local/",
  "eventGrid": "https://{topic}.eventgrid.shim.azure.local/api/events"
}' ./simulator-azure

Supported template variables are {name}, {account}, {vault}, {namespace}, {topic}, {service}, {scheme}, {host}, {hostname}, and {port}. Storage Account ARM responses fill properties.primaryEndpoints, Key Vault fills properties.vaultUri, Service Bus and Event Hubs fill serviceBusEndpoint plus listKeys connection strings, Event Grid fills topic and domain publish endpoints, and /metadata/endpoints emits matching storage and Key Vault suffixes for Azure clients that validate custom-cloud metadata.

# 2. Point Azure clients at it.
# For az CLI: use az rest with explicit URL.
az rest --method GET --url "http://localhost:4568/subscriptions/00000000-0000-0000-0000-000000000001?api-version=2021-04-01"

# For the Go SDK:
import "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud"

cfg := cloud.Configuration{
    ActiveDirectoryAuthorityHost: "http://localhost:4568",
    Services: map[cloud.ServiceName]cloud.ServiceConfiguration{
        cloud.ResourceManager: {Endpoint: "http://localhost:4568", Audience: "http://localhost:4568"},
    },
}

For Terraform, use TLS + Docker (see terraform-tests/ Makefile):

provider "azurerm" {
  features {}
  environment      = "custom"
  metadata_host    = "localhost:4568"
  client_id        = "00000000-0000-0000-0000-000000000001"
  tenant_id        = "00000000-0000-0000-0000-000000000001"
  subscription_id  = "00000000-0000-0000-0000-000000000001"
  skip_provider_registration = true
}

Services

Authentication & Metadata
Service Endpoints
OAuth2 Token endpoint (/{tenantId}/oauth2/v2.0/token), OpenID discovery, JWKS
Metadata /metadata/endpoints — cloud metadata (ARM endpoint, suffixes)
Subscription Get subscription, list providers

OAuth2 access tokens are RS256-signed JWTs. The simulator publishes the matching RSA public key at /{tenantId}/discovery/v2.0/keys, so clients that follow Azure AD's JWKS validation flow can verify simulator-issued bearer tokens without shared secrets.

Compute & Containers
Service Endpoints
Container App Environments CRUD for managed environments
Container App Jobs CRUD, Start execution, Stop execution, List/Get executions
Azure Functions (Sites) CRUD for function apps, List functions, Invoke (/api/function)
App Service Plans CRUD (serverFarms)
ACR Registry CRUD, Name availability, OCI Distribution (/v2/ manifests + blobs + chunked upload)
Infrastructure
Service Endpoints
Resource Groups CRUD, List resources, HEAD existence check
Virtual Networks / Subnets / NSGs CRUD (subnets with delegations + NSG references; NSGs with security rules)
Managed Identity User-assigned identity CRUD
Authorization Role definitions (list with OData filter), Role assignments at any scope
Storage & Data
Service Endpoints
Storage Accounts CRUD, List keys
File Shares CRUD under storage accounts
Storage Data-Plane Host-based routing ({account}.blob.localhost:{port}) for blob/file service properties and ACLs
Cosmos DB for NoSQL ARM databaseAccounts, SQL databases, containers, throughput settings, listKeys/listConnectionStrings; SQL data-plane database/container/document CRUD and query

Storage account listKeys returns Azure-shaped 512-bit base64 SharedKeys that are deterministic per resource ID and key name, so downstream SharedKey verifiers can validate requests signed with the simulator-emitted account key.

Monitoring
Service Endpoints
Log Analytics Workspaces CRUD, Shared keys
Log Ingestion POST entries via data collection rules
Log Query KQL query execution (simple where/take parsing)
Application Insights Component CRUD, Billing features, Query
DNS
Service Endpoints
Private DNS Zones Microsoft.Network/privateDnsZones zone CRUD, list-by-resource-group, and auto-created SOA record
Public DNS Zones Microsoft.Network/dnsZones zone CRUD, list-by-resource-group, and record-set CRUD
A Records CRUD under zones
Virtual Network Links CRUD and list-by-zone

Special handling

These are the load-bearing wire-quirks the sim implements to satisfy the real adaptors:

  • Double-slash cleanupCleanPathMiddleware strips leading // from paths (the azurerm provider appends a trailing slash to the ARM endpoint).
  • Case-insensitive pathsAzurePathNormalizationMiddleware normalises known segments (e.g., /resourcegroups//resourceGroups/).
  • Auth outside mux — OAuth2 token endpoints are handled as outer middleware to avoid conflicts with ACR's /v2/ catch-all.
  • TLS for Terraform — Azure Terraform tests use self-signed certs because the azurestack provider hardcodes https://. On macOS, direct go test delegates into Linux Docker so the providers validate the generated CA through SSL_CERT_FILE.
  • Host-addressed data-plane DNS — Data-plane requests are matched by Host header ({account}.{service}.<zone>). The optional simulator DNS listener serves the local zones over UDP/TCP so real clients can use returned Azure-shaped hosts directly.
  • Sync creates return 200go-azure-sdk treats 200 as immediate completion for BeginCreate LRO; the sim returns 200 instead of 201 for synchronous creates.

Building

cd simulator-azure && go build -o simulator-azure .

Sample

End-to-end via az rest:

$ SIM_LISTEN_ADDR=:4568 ./simulator-azure &

$ az rest --method PUT \
    --url "http://localhost:4568/subscriptions/00000000-0000-0000-0000-000000000001/resourceGroups/my-rg?api-version=2021-04-01" \
    --body '{"location":"eastus"}'
{"name":"my-rg","location":"eastus","properties":{"provisioningState":"Succeeded"}}

$ az rest --method PUT \
    --url "http://localhost:4568/subscriptions/.../resourceGroups/my-rg/providers/Microsoft.App/jobs/my-job?api-version=2023-05-01" \
    --body '{"location":"eastus","properties":{...,"template":{"containers":[{"image":"alpine","command":["echo","hello-from-aca"]}]}}}'

$ az rest --method POST --url ".../jobs/my-job/start?api-version=2023-05-01" --body '{}'

$ az rest --method POST --url "http://localhost:4568/v1/workspaces/default/query" \
    --body '{"query":"ContainerAppConsoleLogs_CL | where ContainerGroupName_s == \"my-job\""}'
{"tables":[{"rows":[["...","my-job","hello-from-aca","stdout"]]}]}

Project structure

azure/
├── main.go                 Entry point, middleware setup, service registration
├── auth.go                 OAuth2 tokens, OpenID discovery, path cleanup (102 lines)
├── authorization.go        Role definitions + assignments (263 lines)
├── metadata.go             Cloud metadata endpoint (69 lines)
├── subscription.go         Subscription + providers (65 lines)
├── resourcegroups.go       Resource group CRUD (103 lines)
├── network.go              VNets, subnets, NSGs (336 lines)
├── managedidentity.go      User-assigned identities (102 lines)
├── containerappsenv.go     Container App Environments (139 lines)
├── containerapps.go        Container App Jobs + executions (479 lines)
├── appserviceplan.go       App Service Plans (132 lines)
├── functions.go            Function Apps + invoke (312 lines)
├── acr.go                  Container Registry + OCI Distribution (491 lines)
├── files.go                Storage accounts, file shares, data-plane (481 lines)
├── monitor.go              Log Analytics, log ingestion, KQL query (348 lines)
├── insights.go             Application Insights (169 lines)
├── dns.go                  Private DNS zones, A records, VNet links (406 lines)
├── shared/                 Shared simulator framework
├── sdk-tests/              SDK integration tests (31 tests)
├── cli-tests/              CLI integration tests (17 tests)
└── terraform-tests/        Terraform apply/destroy tests (TLS; Docker delegation on macOS)

Testing

# SDK tests (Azure SDK for Go against the running sim)
cd sdk-tests && go test -v ./...

# CLI tests (az CLI shell-outs)
cd cli-tests && go test -v ./...

# Terraform tests (TLS; macOS delegates to Docker)
cd terraform-tests && go test -v ./...

Execution model

Container Apps job executions honor the replicaTimeout configuration (in seconds). When a command is provided, the simulator executes it as a real process and streams output to Log Analytics. When a replica timeout is configured and no command is present, the execution auto-completes with Succeeded status after that duration. When no timeout and no command are set, the execution stays running until explicitly stopped. Azure Functions invocations are synchronous and inject AppTraces entries queryable via KQL.

Known issues

Active Azure simulator bugs live in BUGS.md. The Terraform macOS TLS harness issue was fixed by delegating direct macOS test runs into the shared Linux Docker test image.

What's out of scope

  • Full KQL parser: only where + take + limit + simple == / >= predicates are supported.
  • gRPC for Application Insights ingestion: REST only.
  • Multi-region replication / availability zones.
  • Real authentication: tokens are accepted but not cryptographically verified.
  • Cost / billing surfaces.
  • Azure AD identity flows beyond OAuth2 token issuance (/.well-known/openid-configuration + JWKS are provided so SDK validators pass; user / group / app management is not modelled).

Extended examples

Container Apps Jobs
# Create job
az rest --method PUT \
  --url "http://localhost:4568/subscriptions/.../resourceGroups/my-rg/providers/Microsoft.App/jobs/my-job?api-version=2023-05-01" \
  --body '{
    "location": "eastus",
    "properties": {
      "configuration": {"replicaTimeout": 30, "triggerType": "Manual", "manualTriggerConfig": {"parallelism":1,"replicaCompletionCount":1}},
      "template": {"containers": [{"name":"app","image":"alpine:latest","command":["echo","hello-from-aca"]}]}
    }
  }'

# Start execution
az rest --method POST --url ".../jobs/my-job/start?api-version=2023-05-01" --body '{}'

# Query Log Analytics for the execution output
az rest --method POST --url "http://localhost:4568/v1/workspaces/default/query" \
  --body '{"query": "ContainerAppConsoleLogs_CL | where ContainerGroupName_s == \"my-job\""}'
Azure Functions
# Create App Service Plan (Consumption tier)
az rest --method PUT \
  --url "http://localhost:4568/subscriptions/.../resourceGroups/my-rg/providers/Microsoft.Web/serverfarms/my-plan?api-version=2022-09-01" \
  --body '{"location":"eastus","sku":{"name":"Y1","tier":"Dynamic"}}'

# Create Function App with normal container configuration
az rest --method PUT \
  --url "http://localhost:4568/subscriptions/.../resourceGroups/my-rg/providers/Microsoft.Web/sites/my-func-app?api-version=2022-09-01" \
  --body '{
    "location": "eastus", "kind": "functionapp",
    "properties": {
      "serverFarmId": ".../serverfarms/my-plan",
      "siteConfig": {
        "linuxFxVersion": "DOCKER|myregistry.azurecr.io/my-function:latest",
        "appSettings": [
          {"name": "FUNCTIONS_WORKER_RUNTIME", "value": "custom"}
        ]
      }
    }
  }'

# Invoke the configured container/function endpoint
az rest --method POST --url "http://localhost:4568/api/function" --body '{}'

# Query AppTraces
az rest --method POST --url "http://localhost:4568/v1/workspaces/default/query" \
  --body '{"query": "AppTraces | where AppRoleName == \"my-func-app\""}'
Log Analytics
# Create workspace
az rest --method PUT \
  --url ".../providers/Microsoft.OperationalInsights/workspaces/my-workspace?api-version=2022-10-01" \
  --body '{"location":"eastus","properties":{"retentionInDays":30}}'

# Ingest entries via data collection rule
az rest --method POST \
  --url "http://localhost:4568/dataCollectionRules/dcr-1/streams/Custom-Logs" \
  --body '[{"TimeGenerated":"2025-01-01T00:00:00Z","ContainerGroupName_s":"my-job","Log_s":"running","Stream_s":"stdout"}]'

# KQL query (supports where, take/limit, datetime filters)
az rest --method POST --url "http://localhost:4568/v1/workspaces/default/query" \
  --body '{"query": "ContainerAppConsoleLogs_CL | where ContainerGroupName_s == \"my-job\" | take 100"}'
ACR (Container Registry)
az rest --method PUT \
  --url ".../providers/Microsoft.ContainerRegistry/registries/myregistry?api-version=2023-01-01-preview" \
  --body '{"location":"eastus","sku":{"name":"Basic"},"properties":{"adminUserEnabled":false}}'

# OCI Distribution endpoints under /v2/:
#   GET  /v2/                                    → version check
#   POST /v2/{repo}/blobs/uploads/               → initiate blob upload
#   PATCH /v2/{repo}/blobs/uploads/{uuid}        → chunked upload
#   PUT  /v2/{repo}/blobs/uploads/{uuid}?digest= → finalize blob
#   PUT  /v2/{repo}/manifests/{tag}              → push manifest
#   GET  /v2/{repo}/manifests/{ref}              → pull manifest
Storage
az rest --method PUT \
  --url ".../providers/Microsoft.Storage/storageAccounts/mystorageacct?api-version=2023-05-01" \
  --body '{"location":"eastus","kind":"StorageV2","sku":{"name":"Standard_LRS"}}'

az rest --method POST \
  --url ".../providers/Microsoft.Storage/storageAccounts/mystorageacct/listKeys?api-version=2023-05-01"

See also: backends/aca/README.md, backends/azure-functions/README.md, specs/CLOUD_RESOURCE_MAPPING.md § Azure.

Documentation

Overview

Command simulator-azure runs the Azure service simulator.

It simulates the subset of Azure APIs used by the Sockerless ACA and Azure Functions backends: Container Apps Jobs, Azure Monitor, Azure Files, ACR, Private DNS, Azure Functions, and Application Insights.

Configure with environment variables:

SIM_LISTEN_ADDR  — listen address (default ":4568")
SIM_TLS_CERT     — TLS certificate file (optional)
SIM_TLS_KEY      — TLS key file (optional)
SIM_RUNTIME      — "docker" by default; "process" starts API-only mode for runs that do not execute workloads
SIM_SERVICEBUS_AMQP_LISTEN_ADDR — raw Service Bus AMQP/TLS listen address (optional)
SIM_LOG_LEVEL    — log level: trace, debug, info, warn, error (default "info")

SDK configuration:

Use custom cloud.Configuration with ARM endpoint http://localhost:4568

Source Files

Directories

Path Synopsis
Package simulator provides a shared framework for building cloud service simulators.
Package simulator provides a shared framework for building cloud service simulators.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL