In-memory stand-ins for cloud APIs that fail on cue, for testing code that must survive partial failure.
Code that creates cloud resources has to cope with calls that fail after they took effect: an insert that succeeded but whose response was lost, an operation that ended in an error, a retry that finds the resource already there. Those paths are where leaked resources and stuck records come from, and they are hard to reach against a real cloud. A squall server answers the same API calls the official client libraries make, keeps the resources in memory, and fails exactly the calls you choose, or a seeded random share of them, so a failure replays from its seed.
| Package | What it does |
|---|---|
fault |
Decides which calls fail and how: rejected, applied with the response lost, applied with an error, an operation that ends in an error, or slow. Scripted rules and a seeded random rate. |
access |
Grants a set of permissions (Google Cloud IAM permissions, AWS IAM actions or Azure RBAC actions) and refuses calls that need one it lacks, with the error the cloud answers, including those a request's fields add and Azure's linked checks. Reports which permissions were needed but missing and which were granted but never used. |
aws/cur |
AWS Cost and Usage Reports: report definitions (put with the API's field rules, a per-account limit and a check that the S3 bucket's policy allows report delivery, describe with pages, delete). |
aws/ec2 |
Amazon EC2: instances (run with client tokens, describe with filters and pages, start, stop, terminate, change type or which attached volumes terminating deletes), images (describe, register from snapshots, deregister), instance types, EBS volumes (create, describe, attach, detach, delete), EBS snapshots (create, copy across regions, describe with filters and pages, delete), placement groups, tags, Elastic IP addresses (allocate, describe, release, associate with an interface or instance, disassociate), network interfaces (create with client tokens, describe, delete), and VPC networking: VPCs and their DNS attributes, subnets, availability zones, internet gateways (create, attach, detach, delete), NAT gateways on Elastic IP addresses, route tables with their routes and subnet associations, DHCP options, security groups (create, describe groups and rules with filters and pages, authorize and revoke ingress and egress rules, delete) with each VPC's default group, and VPC attachments to transit gateways the test sets up, refusing deletes with dependencies as EC2 does. |
aws/route53 |
Amazon Route 53: private hosted zones (create with caller references, get, list by name with pages, delete), their VPC associations, tags, and record sets (change in atomic batches with CREATE, UPSERT and DELETE, list with pages), and changes (get). |
aws/s3 |
Amazon S3: general purpose buckets (create, head, list with pages and filters, delete), their tags, versioning, policy and CORS rules, and objects (put, delete one or many, list with pages, prefixes and delimiters, list versions and delete markers). |
azure/arm |
Azure Resource Manager: resource groups (get, create or update, list with tag filters, list the resources in one, delete with everything in them that nothing outside uses), virtual machines (create or update, get, list, instance view, deallocate, start, delete) and their extensions (create or update, get, list, delete), Uniform scale sets (create or update, update, get, list, delete, delete instances) and their VMs, managed disks (VM OS disks included) and snapshots, with cross-region incremental snapshot copies whose completionPercent climbs to 100, Azure Compute Gallery galleries, image definitions and image versions with their replication status, virtual networks and their subnets, NAT gateways, public load balancers with backend pools and outbound rules, network security groups, network interfaces and public IP addresses, the IP configurations of scale set VMs, private DNS zones, virtual network links and A record sets, resource provider registration, storage accounts with their blob containers (soft-deleted ones listed, legal holds and immutability policies that block deletes), file shares with usage stats, queues and tables, the Blob service's data plane (containers, and block blobs put, listed with versions, snapshots, soft-deleted and uncommitted blobs, and deleted) with RBAC data actions and account network rules, Cost Management exports at a subscription's scope, Log Analytics workspaces (create or update, recover, get, list, delete with soft deletion) and the default workspace Application Insights makes for a component that names none, Cognitive Services deployments with partner model attestations, and long-running operations to poll. |
azure/graph |
Microsoft Graph: the organization (list), a directory's domains (list) and users (create with Microsoft Entra's name and password rules, get by ID or user principal name, delete, permanently delete once deleted), with application permissions checked as Graph documents them. |
gcp/bigquery |
BigQuery: datasets (insert with the API's ID and label rules, get, list with pages, patch friendly name, description and labels with etag checks, delete refusing a dataset that holds tables unless deleteContents), optionally refused in a project where a gcp/serviceusage server has not enabled the API. |
gcp/compute |
Compute Engine: instances (bulkInsert, insert, get, list, aggregatedList, delete, stop, start, setLabels, setMachineType, addAccessConfig, attachDisk, detachDisk), disks (get, list, aggregatedList, insert, delete), snapshots (insert, get, list, delete), zones, regional addresses (get, list, insert, delete, setLabels), resource policies, instance templates, routes, custom mode VPC networks with their default route, subnetworks with secondary ranges, routers with Cloud NAT (insert, get, patch, delete, list, aggregatedList), firewalls, global addresses including ranges for private services access, and zone, region and global operations (get, wait). |
gcp/lustre |
Managed Lustre: instances (create with the API's capacity, tier, ID, filesystem, root squash and maintenance rules, get, list with filters on name, state, filesystem, network, description and labels, patch labels, description, gkeSupportEnabled and capacity, delete) and their long-running operations (get, list, delete, cancel). |
gcp/resourcemanager |
Cloud Resource Manager: projects (get by ID or number, getIamPolicy, setIamPolicy with etag checks, testIamPermissions answered from the server's access policy), seeded by the test. |
gcp/servicenetworking |
Service Networking: private service connections (create, list by network, patch with force, deleteConnection), checked against a gcp/compute server's networks and allocated ranges and peering the network there, and their long-running operations (get). |
gcp/serviceusage |
Service Usage: services (enable, disable, get, list by state) in projects seeded by the test and named by number or ID, and their long-running operations (get). |
gcp/storage |
Cloud Storage: buckets (insert, get, list, patch labels, versioning and CORS, delete) and objects (multipart and media uploads, list with versions, delete by generation). |
Coverage grows with use: each stand-in implements the methods someone needed, answers anything else with 404, and documents how faithful it is. Contributions of more methods and services are welcome.
faults := fault.New().Add(fault.Rule{
Method: "compute.addresses.insert",
Nth: 1,
Fault: fault.LoseResponse(), // takes effect, but the caller never hears back
})
s := compute.New(compute.WithFaults(faults))
srv := httptest.NewServer(s)
defer srv.Close()
client, _ := gcompute.NewAddressesRESTClient(ctx,
option.WithEndpoint(srv.URL), option.WithoutAuthentication())
// Run the code under test with client, then check what it left behind:
for _, a := range s.Addresses() {
fmt.Println(a.Name, a.Address, a.Labels)
}For a property test, inject random faults and check that every seed converges:
for seed := range uint64(100) {
faults := fault.New(fault.Seed(seed)).Random(0.3,
fault.Unavailable(), fault.LoseResponse(), fault.FailAfter(500))
// ... run the code under test against compute.New(compute.WithFaults(faults))
// and on failure report the seed and faults.Injected().
}To check that a list of required permissions is exact, grant only it and see what the code under test needed:
policy := access.Allow(requiredPermissions...)
s := compute.New(compute.WithPolicy(policy))
// ... run the code under test against s
for _, d := range policy.Denials() {
t.Errorf("%s on %s needs %v", d.Method, d.Resource, d.Missing)
}
if unused := policy.Unused(); len(unused) > 0 {
t.Logf("granted but never used: %v", unused)
}Each stand-in follows the real API's requests, response shapes and error codes for the methods it covers, and is tested through the official Go client library. Where it simplifies, the package documentation says so. For Compute Engine:
- Changes to instances, disks and snapshots finish before the call that makes
them returns, so the SDK's
Operation.Waitreturns at once.WithSettleAfter(n)keeps them in progress, with their operationRUNNING, fornmore reads of the resource or its operation, or until the test callsSettle: a new instance isPROVISIONINGand thenSTAGING, a stopping or deleted oneSTOPPING, a starting oneSTAGING, a new snapshotCREATINGand a deleted oneDELETING. Anoperations.waitfinishes the operation it waits for. A change to an instance that has another in progress finishes that one first. Every other change finishes at once. - A call that repeats an earlier call's
requestIdin the same project, for the same method, gets that call's operation back without taking effect again, as the real API does for retries. The server keeps the last 10,000 operations. - Images are registered by the test with
PutImage, and an image family names the newest image in it. Snapshots are taken of aREADYdisk withsnapshots.insert, or registered withPutSnapshot; they record the disk's size, not its data, and a disk from one that is notREADYfails withresourceNotReady. Machine and disk types are any well-formed name in the call's zone. Zones come from a fixed set per region, whichWithZoneschanges. - Networks are custom mode only, and each gets the default route to
0.0.0.0/0that goes with it. Subnetworks, routers, firewalls andVPC_PEERINGglobal addresses must name a network that exists (404 otherwise), and subnetwork ranges must not overlap within the network or with its allocated ranges. Deleting a network or subnetwork that something still uses, a peering included, fails with 400resourceInUseByAnotherResource. A router patch replaces itsnats, as a JSON merge patch does. AVPC_PEERINGrange with no address gets the first free block of its size in 10.0.0.0/8. - Instances, routes and regional addresses may name networks and subnetworks the stand-in does not hold, and service accounts and accelerators are not checked. Fields the stand-in only records keep the JSON the caller sent, and fields it does not know are rejected with 400. Instance group managers are not supported: their aggregated list is always empty.
- A
bulkInsertskips each instance whose name, or a disk it would create, already exists, and creates the rest when they number at leastminCount; its operation succeeds, withcreatedVmCountandfailedToCreateVmCountcounting the two, and lists no warning for what it skipped. With fewer thanminCount, its operation fails withVM_MIN_COUNT_NOT_REACHEDand aRESOURCE_ALREADY_EXISTSerror per conflict, and nothing is created. It takes no fixed IP addresses or disk names, and ignoreslocationPolicy. - List filters support
field = valueandfield != valuecomparisons joined by AND, OR and parentheses (OR binds tighter than AND), and the regular expressionsfield eq reandfield ne re, which must match the whole field. Lists are ordered by name. Anything else, such as a wildcard ororderBy=creationTimestamp desc, is rejected with 400 rather than ignored. AnaggregatedListlists only the scopes that have items. - IP addresses come from 100.64.0.0/10 (external) and 10.0.0.0/8 (internal),
never from routable ranges. An external address is unique across every
project and region; an internal one is unique within its region, which stands
in for its subnetwork. An instance's ephemeral external address is released
when it stops; a reserved one it takes is
IN_USEuntil it goes, and stays with it until then if the address is deleted. - A fault rule's
Resourcefor an insert is the resource being created, so a rule can target the insert of one name; for abulkInsert, the first instance it names.
For Cloud Resource Manager:
- Projects are seeded with
WithProjectorPutProjectand are alwaysACTIVE. A project the server does not hold answers 403PERMISSION_DENIED, as the API does for one that does not exist. - A
setIamPolicywith a stale etag answers 409ABORTED. Conditional bindings need policy version 3. Audit configs are rejected with 400. testIamPermissionsanswers from the server'saccess.Policy, not the project's own policy, so a test can check code that tests its own permissions before acting.
For BigQuery:
- Point the client at
srv.URL + "/bigquery/v2/". - Datasets hold a friendly name, a description, labels and a location, which
defaults to
US. Other dataset fields are rejected with 400, and projects are not checked. - Tables are not served;
AddTableputs one in a dataset, so a delete withoutdeleteContentsis refused with 400resourceInUse. WithServiceUsagerefuses calls in a project where agcp/serviceusageserver has not enabledbigquery.googleapis.com, with 403accessNotConfigured.
For Service Usage:
- Projects are seeded with
WithProjectand start with no service enabled. A project the server does not hold, or a service outside its catalog (extended withWithService), answers 403PERMISSION_DENIED, as the API does for one that does not exist. - Changes finish before the call returns. Enabling an enabled service
answers with the done operation
operations/noop.DONE_OPERATION; disabling a disabled one is refused withFAILED_PRECONDITION. Dependencies between services are not modeled.
For Service Networking:
WithComputechecks a connection's network and reserved ranges against agcp/computeserver and adds theservicenetworking-googleapis-compeering to the network, so the network cannot be deleted untildeleteConnection.WithProjectNumberlets a network be named by project number or ID; connections name it by number, as the API does.- Changes finish before the call returns. A second create on a network, or
one naming a range that is not a
VPC_PEERINGaddress on it, ends its operation inFAILED_PRECONDITION, the first with the API's "Please use UpdateConnection." message. A patch that drops a range needsforce=true. Producer services are not modeled, so ranges are never in use by them.
For Managed Lustre:
- Creates, patches and deletes finish before the call that makes them returns,
so the SDK's
Waitreturns at once.WithSettleAfter(n)keeps the instanceCREATINGorDELETING(a patched one keeps its state, since the API deprecatesUPDATING), with its operation not done, fornmore reads of it or its operation, or until the test callsSettle. The v1 API has noFAILEDstate: a failed create ends its operation in an error and leaves no instance. - Instance IDs, filesystem names, networks, labels and root squash options are
checked as the API documents them, and
capacityGibagainst its throughput tier's minimum, maximum and step, the step growing above the tier's threshold. Locations are not checked, and networks, KMS keys and quotas are not looked up. A mount point's address comes from 10.0.0.0/8. - List filters follow AIP-160 for
name,state,filesystem,network,descriptionand labels (labels.KEY = value,labels.KEY:*,labels:KEY), with AND, OR, NOT and parentheses; anything else is rejected with 400. A patch changes labels, description, gkeSupportEnabled or capacityGib, named byupdateMask, by*, or without a mask by the fields the body holds; output-only fields and unchanged ones are ignored, as AIP-203 directs. The API also changes throughput, access rules, maintenance windows, placement policy and target version, which the stand-in rejects with 400. Names match exactly, in lowerCamelCase or snake_case, and options read back the same whether their enums were sent by name or number. Error messages are the stand-in's own. - Fault method names are the API's method IDs, such as
lustre.projects.locations.instances.create, and a rule'sResourceis the path below/v1/, for a create the instance it makes.
For Amazon EC2:
- Signatures are not checked. The region comes from the SigV4 credential scope, and each region keeps its own resources.
- Key pair names are not validated, and there is no default VPC:
RunInstancesneeds a subnet or a network interface. A subnet ID the stand-in does not hold is accepted there and byCreateNetworkInterface, with any security group IDs, as before VPCs existed; in a subnet created withCreateSubnet, interfaces and instances take the subnet's VPC, zone and an address from its CIDR block, and security groups of that VPC: the ones named, which must exist there, or the VPC's default group. Images are registered by the test withPutImage, or withRegisterImagefrom snapshots the stand-in holds; instance types come from a small built-in set orPutInstanceType. - Snapshots record the volume's size and encryption, not its data, and every
one is the caller's own:
DescribeSnapshotswith an owner other thanselflists none.DeleteSnapshotfails withInvalidSnapshot.InUsewhile a registered image maps the snapshot. CopySnapshot, called in the destination region, copies acompletedsnapshot fromSourceRegioninto a new snapshot there that startspendingand completes like a new snapshot. A source that region does not hold fails withInvalidSnapshot.NotFound; apendingsource fails withIncorrectState(inferred: AWS documents copying only completed snapshots). The copy takes the request's description and tags, not the source's tags, reports the arbitrary volume IDvol-ffffffff, and is independent of its source: deleting either leaves the other (inferred from copies being full copies with their own ID). It is encrypted if its source is orEncryptedis set; KMS keys are accepted and not modeled, and Outpost, Local Zone and time-based copies are rejected as unknown parameters.Encrypted=false, and aKmsKeyIdwithoutEncrypted=true, are accepted, though the API reference allows neither.ModifyInstanceAttributechanges the instance type of a stopped instance, or, withBlockDeviceMapping, theDeleteOnTerminationof volumes attached to the instance in any state,truewhen a mapping leaves it out;DescribeVolumes,DescribeInstancesand termination follow the new value (sourced: the API reference forModifyInstanceAttributeandBlockDeviceMapping.N). A device with no volume of the instance attached, or aVolumeIdother than the one there, fails withInvalidBlockDeviceMappingand changes nothing, and both attributes in one call fail withInvalidParameterCombination(inferred: the reference says only one attribute can change at a time and names no codes).RunInstancesrecords metadata, private DNS name and CPU options, and the primary interface's type (interfaceorefa; with an existing interface, only that interface's type) and network card index (only 0, the same launch as leaving it out);DescribeInstancesreturns them, with metadata options pending until the instance first settles. EFA and AMD SEV-SNP are not checked against the instance type. CPU options take up to the type's default cores and threads per core, whichDescribeInstanceTypesreports, and any core count from 1 up, where EC2 accepts only the counts it lists for each type.- Instances, volumes and snapshots move through EC2's states when describes
see them. By default the first
DescribeInstances,DescribeVolumesorDescribeSnapshotsafter a change shows the state it settles in, so the SDK's waiters return at once;WithSettleAfterholds them in transition for more describes, or until the test callsSettle. Describes are consistent at once, not eventually. - Terminated instances stay visible, as in EC2, until the test calls
ForgetInstance. Termination deletes the interface and volumes marked to delete on termination and releases the rest. - A
CreateNetworkInterface,RunInstancesorCreateVolumethat repeats aClientTokenwith the same parameters gets what the first call created, as the real API does for retries; with other parameters it fails withIdempotentParameterMismatch. - Filters take EC2's
*and?wildcards. Filter names and parameters the stand-in does not support are rejected with 400 rather than ignored. - Public IP addresses come from 100.64.0.0/10 and private ones from 10.0.0.0/8, or from the subnet's CIDR block for subnets the stand-in holds. There is no address quota unless the test sets one.
- VPCs and subnets take IPv4 CIDR blocks from /16 to /28; IPv6, secondary
CIDR blocks and IPAM pools are not supported. Each VPC gets a main route
table with the local route, a
defaultsecurity group, and the region's default DHCP options set. A region has the zonesWithAvailabilityZonessets, or the region witha,bandcappended, with zone IDs such asuse1-az1. - Security groups belong to a VPC. The
defaultgroup allows all traffic from its members and all outbound traffic, is deleted with its VPC and cannot be deleted on its own (CannotDelete); a new group starts with the allow-all outbound rule. Rules take IPv4 and IPv6 CIDR blocks and groups in the same VPC as peers, one rule with ansgr-ID per peer. Duplicates fail withInvalidPermission.Duplicateand revoking a missing rule withInvalidPermission.NotFound, as in a nondefault VPC. Prefix lists, the flatCidrIpparameters and rule tags are not supported. Group names stand in for IDs only inDescribeSecurityGroups, where they match groups of that name in any VPC. - Deletes fail with
DependencyViolationwhile something depends on the resource, as in EC2: a VPC with subnets, an attached internet gateway, extra route tables or security groups other than its default one; a security group an interface or instance uses or another group's rule refers to; a subnet with network interfaces (a NAT gateway's included until it is deleted) or a transit gateway attachment; an attached internet gateway; a main or associated route table; DHCP options in use. Detaching an internet gateway while the VPC has public addresses mapped fails the same way. - NAT gateways and transit gateway attachments move from
pendingtoavailableand fromdeletingtodeletedas describes see them, like instances, and stay visible once deleted. A NAT gateway on an Elastic IP address that is already associated, or in a VPC with no internet gateway, endsfailedwith EC2's failure code. Routes whose target is gone readblackhole. Transit gateways themselves are set up by the test withPutTransitGateway; the stand-in has no API to create them. - EC2 has no long-running operations, so a failed operation fails the call
before it takes effect. A fault's reason is the EC2 error code it answers
with, and the generic ones become
InternalError,UnavailableandRequestLimitExceeded. A fault rule'sResourceis the region and ID, such asus-east-1/eipalloc-…, and for a create, the resource being created (the first instance, forRunInstances). A rejected call has no effect, so a rejected describe does not move an instance along.
For Amazon S3:
- Requests are path-style, which the SDK uses for an IP endpoint such as
httptest's. Signatures are not checked; the region comes from the SigV4
credential scope. A call on a bucket in another region fails with 301
PermanentRedirectandx-amz-bucket-region, as S3 answers it. - There is one account.
PutForeignBucketholds a name for another one, so creating it fails withBucketAlreadyExistsand calls on it withAccessDenied(403 forHeadBucket). Creating an owned bucket again succeeds in us-east-1 and fails withBucketAlreadyOwnedByYouelsewhere. ListBucketswith any parameter pages (10,000 buckets by default) and reports each bucket's region, as S3 does; with none it lists every bucket without regions.ListObjectsV2lists each key whose newest version is not a delete marker, in UTF-8 binary order, and its continuation tokens are positions.ListObjectVersionsorders by key and then newest first, and its markers are positions, so the next page still follows one whose versions were just deleted.DeleteBucketfails withBucketNotEmptywhile any version or delete marker remains.- Objects keep their size and ETag, not their data. Checksums are not
verified, and multipart uploads, copies, object tags and lifecycle are not
supported. Policies and CORS rules are recorded, not enforced. S3 documents
in-progress multipart uploads as blocking
DeleteBucketonly for directory buckets. - A fault rule's
Resourceis the bucket name, orbucket/keyfor an object call. A lostHeadBucketbreaks off inside its status line, since a sent header would read as an answer.
For Amazon Route 53:
- Only private hosted zones are supported:
CreateHostedZoneneeds a VPC. VPCs are any ID and region unlessWithVPCLookupsays which exist, so a test can back it with another stand-in. Signatures are not checked. - A reused
CallerReferencefails withHostedZoneAlreadyExists, even after its zone is deleted. A zone name already associated with the same VPC fails withConflictingDomainExists, on create and onAssociateVPCWithHostedZone; removing a zone's last VPC fails withLastVPCAssociation. - Each zone starts with its SOA and NS record sets, which cannot be deleted,
and
DeleteHostedZonefails withHostedZoneNotEmptywhile any other remains. Changes areINSYNCat once. - A change batch applies in order and atomically. A failed one answers
InvalidChangeBatchwith a message per failed change: creating a set that exists, deleting one that is missing or whose TTL and values differ, a name outside the zone, a bad A or AAAA value, or a CNAME that shares its name. Record sets are simple or alias ones: set identifiers, routing policies and health checks are rejected withInvalidInput. - Names are lowercased with a trailing dot, and other characters are
\oooescapes, so*lists as\052. Lists order by name with its labels reversed, then by zone ID or record type. - A fault rule's
Resourceis the bare hosted zone ID, or the zone's name forCreateHostedZone.
For Cloud Storage:
- Point the client at
srv.URL + "/storage/v1/", or setSTORAGE_EMULATOR_HOST. There is one caller, which reaches every project;PutForeignBucketholds a name it cannot reach, so inserting it fails with 409conflictand calls on it with 403forbidden. A repeated insert of the caller's own bucket fails with 409, as the client's retry of a create that took effect does. - Bucket names and labels follow the API's rules; locations are upper-cased but not checked. Fields the server does not model, such as lifecycle rules or retention, are rejected with 400. A patch with a field set to null clears it, and a label set to null is deleted.
- Objects keep their size, MD5 and CRC32C, which an upload's checksums must match. With versioning on, overwrites and deletes keep the old generation as noncurrent, and a bucket with any generation left cannot be deleted. Page tokens are positions, so a listing survives deletes between pages. Resumable uploads, reads, IAM policies and HMAC keys are not supported.
- A fault rule's
Resourceis the path below/storage/v1/, such asb/my-bucket/o/my-object, and for an insert the bucket or object it makes.
For Azure Resource Manager:
- Long-running operations finish within the call that starts them, so the
SDK's pollers are done at once.
WithSettleAfter(n)answers with anAzure-AsyncOperationURL instead (and aLocationURL for actions and deletes, the only one for a resource group deletion), keeping the resourceCreating,UpdatingorDeletingfornmore reads of it or its operation, or until the test callsSettle. Polls carry aretry-after-msof 10. A fault inFailOperationmode ends the operation in its error without the call taking effect. - PUT and DELETE are idempotent, as in Azure: a retried create answers 200 with
the resource, and deleting what is not there answers 204. A change to a
resource with another in progress is answered as its provider answers it:
network resources fail it with 429
RetryableErrorand virtual network links with 409Conflict, counting the call as a read of that change, and a VM or scale set change preempts the other, which endsCanceledwithOperationPreempted, or fails with 409OperationNotAllowedif that one is a deletion. A change in a resource group being deleted fails withResourceGroupBeingDeleted. - Deleting a resource group deletes what nothing outside it uses. When
something outside still uses a resource in it, such as a VM in another group
attached to its network interface, the rest is deleted and the operation
fails with 409
ResourceGroupDeletionBlocked, leaving the group with the resources in use. Each resource left is a detail whose message is the error deleting it alone answers, such asNicInUse, as Azure reports it; the status, and that the rest is deleted first, are inferred. - Listing a group's resources lists the types ARM tracks, those with a
location, nested ones such as VM extensions and virtual network links
included, ordered by ID; subnets, record sets, containers and other proxy
types are left out. Each entry shows the fields a GET of the resource shows
among
id,type,location,tags,extendedLocation,kind,plan,sku,identityandmanagedBy, and with$expanditsprovisioningState. It takes a tag$filter, and with atagValueclause leaves out each resource's tags, as Azure does, and an$expandofcreatedTimeandprovisioningState. Other filters (on name, location orresourceType, or withsubstringoforstartswith) andchangedTimefail with 400. - Listing resource groups or a group's resources with
$topreturns at most that many, with nonextLink; Azure may page them instead. - Resource names, subscription IDs, locations and tags are not validated, and
names match without regard to case. Fields it does not know are rejected
with
InvalidRequestContent; values it does not model, such as a Flexible scale set or a VM withnetworkInterfaceConfigurations, withBadRequest. - A subnet's address prefixes must lie in its virtual network's address space
(
NetcfgSubnetRangeOutsideVnet), not overlap another subnet's (NetcfgSubnetRangesOverlap) and be no smaller than a /29; a virtual network PUT replaces its subnets, and a subnet, virtual network or NAT gateway in use cannot be deleted. Interfaces in a subnet the server holds take addresses from its prefix. A subnet the server does not hold is recorded, not checked: its private addresses come from 10.0.0.0/8, unique within the subnet ID, and public ones from 100.64.0.0/10, assigned when created;WithPrivateRangeandWithPublicRangechange the ranges. Once a range is used up, a change that needs an address fails withSubnetIsFullorPublicIPCountLimitReached. - A public load balancer's outbound rule gives SNAT ports in multiples of 8
(
SpecifiedAllocatedOutboundPortsForOutboundRuleIsNotAMultipleOfEight), and the write that would ask more of its frontends than 64,000 ports per public IP address, shared among the primary IP configurations in its backend pool (each scale set VM counts), fails withSpecifiedAllocatedOutboundPortsForOutboundRuleExceedsTotalNumberOfAvailablePorts: the load balancer's, or the scale set or interface write that grows the pool. A load balancer whose pool a scale set uses cannot be deleted (LoadBalancerInUseByVirtualMachineScaleSet). Load balancing rules, probes and inbound NAT rules are not modeled. - A disk or snapshot change in progress answers 202 with the resource, showing
Updatingeven while it is created, and polling URLs underlocations/{location}/DiskOperations, the Location URL addingmonitor=true; once done, the status carries the resource asproperties.output. That a delete is polled the same way is inferred. - A VM made from an image makes its OS disk a served disk,
Attachedand managed by the VM,Reservedwhile the VM is deallocated. Deleting the VM deletes the disk when itsdeleteOptionisDeleteand detaches it otherwise. Its default SKU and size,Standard_LRSand 30 GB, are inferred. An attached disk cannot be deleted (409OperationNotAllowed). Data disks and scale set VM disks are not modeled. - A VM extension create answers 201 with only an
Azure-AsyncOperationURL underMicrosoft.Compute/locations/{location}/operations, showingCreating, thenSucceeded, and a delete 202 with aLocationURL as well, the same path addingmonitor=true, as Azure answers; that an update showsUpdatingis inferred.protectedSettingsare recorded, asVMExtensionsshows, but not returned, as Azure leaves them out. The handler does not run, so an extension never endsFailed, and its instance view is not served. A write under a missing VM fails withParentResourceNotFoundand the message Microsoft documents; its 404 status is inferred, as are 404ResourceNotFoundfor a get or list under a missing VM or a get of a missing extension, and 204 for deleting either. Deleting the VM or its group deletes its extensions, as Microsoft documents deleting a parent deleting its children. - A copy from a disk or snapshot that is missing or in another location fails
with 404
NotFound. An in-regionCopysnapshot shows nocompletionPercent. An incrementalCopyStartcopy, in any location, succeeds while its background copy goes on:completionPercentclimbs from 0 to 100 inWithCopyStepssteps (2 by default: 50, then 100), one per get of the snapshot (unlessWithSettleAfteris negative) or perSettle, a pace the stand-in sets. Using it as a source before then fails with 409Conflict"Source incremental snapshot {name} copy is still in progress", as Azure answers a disk; for a snapshot or image version it is inferred. One copy of a disk's snapshots runs at a time (409Conflict, message inferred). Deleting the source mid-copy succeeds and the copy showscopyCompletionErrorCopySourceNotFound; its message is inferred. Copy order and the ZRS default for incremental snapshots are not modeled. - Galleries, image definitions and image versions are polled under
locations/{location}/capsOperations. A version isCreating, thenSucceeded;$expand=ReplicationStatusshows each target region's state. A version from a copy still in progress is accepted and endsFailed, its operation and replication status giving the copy'sConflict, which is inferred. A missing source fails with 400InvalidParameter, the code inferred. Deleting a gallery or image definition that holds others fails with 409CannotDeleteResource, the code inferred. Replication, sharing and soft deletion are not modeled. - A snapshot of a disk needs
Microsoft.Compute/disks/beginGetAccess/actionon the disk, or fails withLinkedAuthorizationFailed; linked checks on other disk, snapshot and gallery sources are not modeled. - Scale set VMs are numbered from 0 without reuse; raising
sku.capacitycreates them with their interfaces and addresses, and lowering it deletes the newest. A PUT to an existing scale set that leaves outskukeeps the capacity. One that gives askuwithout a capacity fails, as Azure answers one that changes the model, unless it repeats the create of a scale set made without a capacity with nothing changed since: then it succeeds and changes nothing, so a retry after a lost response converges, which is inferred from ARM PUTs being idempotent. A new scale set needs askuwith a name. One created without a capacity starts with no VMs, as a Flexible one does in Azure; for Uniform, both are inferred. - Resource SKUs list none, so availability checks see no restrictions.
- Every resource provider namespace is
RegisteredunlessWithUnregisteredProvidersnames it; Azure registers only some by default, such asMicrosoft.ResourcesandMicrosoft.CostManagementbut notMicrosoft.CostManagementExports, as Microsoft documents. A call to aNotRegisterednamespace fails with 409MissingSubscriptionRegistration, naming it as the detail's target, as an Azure CLI recording shows. Registering answers 200 with the providerRegisteringand no operation to poll, as Azure answers; it becomesRegisteredafter the readsWithSettleAftersets (gets of the provider, and export writes it turns away) or onSettle, and at once by default. A namespace being registered takes calls, as Microsoft documents that callers need not wait for it.ProviderStateandProviderRegistrationsshow the states and how many register calls each got. Registering aRegisterednamespace leaves itRegistered, which is inferred. A provider lists no resource types and unregistering is not served. - A Cost Management export create answers 201 and an update 200, each with a
new eTag, as Azure CLI recordings show. An update needs the current eTag in
the body: without one it fails with 400 "Etag value required for update
operation of report: {name}.Please get the latest to avoid overwrite.", as
Azure answers; with a stale one, 412, which is inferred, as is its message.
Cost Management errors carry the HTTP status as their code, such as
404, and end in a request ID, as Azure answers. A missing export answers 404 "No export found for {name}, Subscription: {subscription}.", as Azure answers a get; that a delete of one answers the same is inferred (the SDK accepts only 200 from a delete). An export write whileMicrosoft.CostManagementExportsisRegisteringfails with 503, as Microsoft documents; the message is inferred. One while it isNotRegisteredis not refused, since no source shows how Azure answers it. The destination storage account and container are recorded, not checked. The answer leaves out the destination'stype, shows a nullrootFolderPathwhen none was given, and writes recurrence dates in RFC 3339 UTC, as Azure does. Exports live at a subscription's scope, so deleting a resource group, even the destination's, leaves them. Runs, run history,$expandand other scopes are not served.Exportsshows each export's definition, destination, schedule and eTag. - A Log Analytics workspace create answers 201 and an update 200, polled
through an
Azure-AsyncOperationURL underMicrosoft.OperationalInsights/locations/{location}/operationstatuses, and a delete 200, or 202 with aLocationURL underworkspaces/{name}/operationresultsas well, as Azure CLI recordings (test_monitor_log_analytics_workspace_default) and the REST API'sWorkspacesDeleteexample show; deleting a missing workspace answers 204, as Microsoft documents. A workspace shows acustomerId, and the PerGB2018 SKU, 30 days of retention, public network access and thefeaturesandworkspaceCappinga recording shows unless set: 30 days is the default Microsoft documents and PerGB2018 its pay-as-you-go tier, which as the default when a create names no SKU is inferred. Deleting a workspace, or its group, keeps it as deleted for 14 days, as Microsoft documents, unless the delete passesforce=true;DeletedLogAnalyticsWorkspaceslists them. A forced delete of a workspace that is already deleted answers 204 and leaves it deleted, as Microsoft documents that it must be recovered first. A create with the deleted workspace's group, name and location recovers it, keeping itscustomerId, showingRestoringwhile in progress (201, as a recording ofaz monitor log-analytics workspace recovershows), and keeping the properties the create leaves out, as Microsoft documents that recovery brings back its configuration. A create in another location while the name is held fails with 409Conflict; the code and message are inferred. Polling a delete'soperationresultsURL needsMicrosoft.OperationalInsights/workspaces/operations/read, which is inferred, as Azure defines nooperationresultsaction for workspaces. Tables, data sources, deleted workspace listing and shared keys are not served. - An Application Insights component records and echoes
properties.WorkspaceResourceIdas sent, and showsIngestionModeLogAnalyticswhen linked unless set, as the REST API'sComponentsCreateexample shows. The workspace it names is not checked. A component created without one gets a workspace made for it, as Microsoft Learn documents ("a new Log Analytics resource is created automatically"); it isDefaultWorkspace-{subscription}-{code}in the groupDefaultResourceGroup-{code}, both in the component's location, where{code}is the location'slaRegionCodein the Application Insights portal's region mapping, the names the Azure CLI uses for the same default (appservice/_create_util.py). Making them in Application Insights rather than the client, their location, and using the location's name as the code for one the mapping gives none are inferred. Later components reuse them, an update that leaves out the link keeps it (inferred), and they outlive the component's group, asResourcesandResourceGroupsshow. A deleted default workspace is recovered by the next component that needs it, as a create recovers one (inferred). No RBAC action is checked on them. - A Cognitive Services deployment records and echoes
properties.modelProviderData(industry,organizationName,countryCode), which clients send for partner models such as Anthropic's on newer api-versions than the SDK's, as the Azure-Samples Claude starter kit and an azure-rest-api-specs issue (#43610) show; the published specs do not describe it. Other members fail withInvalidRequestContent. Values are not checked, and a partner model deployed without it is not failed, though those sources report Azure failing it withAnthropicOrganizationCreationFailed.Deploymentsshows it. - The Blob service is
srv.Blob(), anhttp.Handlerof its own: serve it withhttptestand make the azblob client with{its URL}/{account}, as for the storage emulator, any token credential andInsecureAllowCredentialWithHTTP; a host name such as{account}.blob.core.windows.net(or{account}.privatelink.blob...) names the account too. Container names are lower case, 3 to 63 letters, digits and hyphens, through ARM as through the Blob service, or$rootand$web(not$logs); a blob in the root container is addressed with$rootin the path (with no/in its blob names), and implicit root addressing answers 400. It shares the ARM server's accounts, containers, blob service properties, faults and policy, so deleting a container or account through ARM deletes its blobs. A missing account answers 404ResourceNotFound, where Azure's host name would not resolve. Blobs keep their size, MD5 and properties, not data (Get Blob is not served). Versioning, blob and container soft delete, snapshots and uncommitted blocks follow the blob service properties (versions written while versioning was on are kept after it is turned off, and a snapshot of a versioned blob makes a version); permanent delete, undelete, leases, tags, tiers and page or append blobs are not supported. Calls need the RBAC data actions Microsoft lists (403AuthorizationPermissionMismatch), which a grant of*covers here though a role's*does not in Azure;networkAclsthat deny by default, or disabled public network access, answer 403AuthorizationFailureunless an IP rule allows the caller. - Version-level immutability policies are not modeled: a non-empty default
policy on an account's version-level settings, or an immutability policy
on a version-level immutable container, answers 400, as do changing a
locked container policy, legal holds on a version-level immutable
container and
allowProtectedAppendWritesAll; deleting or extending a policy is not served (404). A container's policy can be read back (404 when it has none, inferred, as Azure's spec lists only 200; prefer the container'shasImmutabilityPolicy). Locking takes the policy's ETag, not*, and locking a locked policy changes nothing. A container takes 1 to 10 legal hold tags, which are lower-cased so case variants count once. - Container legal holds block overwrites, snapshots and deletes of their
blobs, and deleting the container itself, even empty, until the hold is
cleared (inferred: the container-level WORM table says container deletion
fails while a legal hold is in effect, its Deletion section that the
container must be empty). An immutability policy blocks deletes for its
days, and overwrites and snapshots for as long as it exists; a container
under one can be deleted through ARM once empty. Deleting an account with
a container under a locked policy or a legal hold fails with 409
AccountProtectedFromDeletion, as Azure answers. A legal hold protects the account even on an empty container, as Microsoft's data protection overview says, though the container-level WORM table adds "at least one blob" and its note says unlocked policies "and/or legal hold" don't provide delete protection (failing closed is inferred). Uncommitted blocks alone are not a blob, here or inBlobs(). A blocked group delete leaves the account whole, shares, queues and tables included. Soft-deleted containers do not protect their account (inferred from the soft delete page: "Container soft delete doesn't protect against the deletion of a storage account"), while soft-deleted blobs count as data for holds, policies and version-level immutability (inferred). - A container with version-level immutability can be deleted only through
ARM once empty (
ContainerImmutableStorageWithVersioningEnabledotherwise, with the message Azure shows on Microsoft Q&A 5656033 and 1300488; using it for the Blob service is inferred). An account with it enabled cannot be deleted while any container exists, and one without it cannot while it has such a container; an account with it and no container can be deleted (inferred, as the version-level WORM page says both "it can't be deleted until it's empty" and that deletion fails "if it's enabled for the account", and the setting cannot be turned off). Account version-level immutability is set only at creation, kept across updates and implies versioning; a container takes it only with versioning on, and versioning cannot then be turned off ("Conflicting feature 'VersionLevelWorm' is enabled. Please disable it and retry.", as on Q&A 5656033; that a soft-deleted container's counts is inferred). Blob-level immutability policies and legal holds are not supported (400), and version-level immutability with a hierarchical namespace, on the account or a container, answers 400. Retention days run against the server's clock, so a test of expiry should make the server witharm.WithClockand move the clock rather than wait. - Fault method names are the Azure RBAC action, such as
Microsoft.Compute/virtualMachines/write, and a rule'sResourceis the resource ID in the call's path. Blob service faults name the operation, such asPutBloborListBlobs, and{account}/{container}/{blob}; calls the Blob service refuses as not served never match a fault.
For Microsoft Graph:
- Permanently deleting a deleted user (
DELETE /directory/deletedItems/{id}) answers 204 and moves it fromDeletedUserstoPurgedUsers, as Microsoft documents. It needsUser.DeleteRestore.All, the only application permission Graph lists for a user;Directory.ReadWrite.Allis documented as not allowing user deletion, and without it the call fails with the stand-in's usual 403Authorization_RequestDenied. An ID that is not a deleted user's fails with 404Request_ResourceNotFound, the status and code a public report records for the call; the message is inferred. GET /organizationlists one organization, as Graph documents, in the shape of its example:WithOrganizationsets its ID (the tenant ID), display name,countryLetterCodeanddefaultUsageLocation, which default to00000000-0000-0000-0000-000000000001, Squall, US and US, andverifiedDomainsare the verified domainsWithDomainssets. It needsOrganization.Read.All,Directory.Read.All,Organization.ReadWrite.AllorDirectory.ReadWrite.All, as Graph documents. Each verified domain's capabilities and type are inferred.- The initial domain, which domains and the organization's verified domains
mark with
isInitial, is the directory'sonmicrosoft.comdomain, whichever domain is the default, as Microsoft Entra documents; that it is the first such domainWithDomainssets is inferred.
Go 1.27 or later. The module requires nothing beyond the standard library; the
tests that use the official client libraries live in the separate sdktest
module.
See CONTRIBUTING.md. Security issues: SECURITY.md.