Manage Azure infrastructure as code via ARM
Management::ManagementGroup
is tenant-scoped, so a subscription-scoped service principal gets
403 AuthorizationFailed on the entire tree. Reads keep the opposite rule: there
the resource is known to exist, so a permission failure stays a failure.Authorization::RoleAssignment discovery no longer picks up assignments
inherited from a management group or the tenant root. Azure returns them
alongside the ones at and below the requested scope, and reading one needs
permission at that ancestor scope, so discovery failed on a resource the target
does not own.ErrorCode and ReadResult carries no message, so the
agent previously recorded only finished_with_error with no reason anywhere.Resources formae created in order to reach this subscription are no longer offered for import. The connect resource group and managed identity carry an ownership marker that discovery now excludes, so a reconcile can no longer take away formae's own access. The federated identity credential, which has no tags at all, is matched on the credential name, the formae issuer and a subject in formae's namespace together, so a credential a customer pointed at the same issuer stays visible. Role assignments are deliberately left visible: destroying one revokes a grant that re-running connect restores, without touching the trust itself.
100 new Azure resource types, taking the plugin to 292:
| Namespace | Types |
|---|---|
AZURE::ApiManagement |
28 |
AZURE::DataFactory |
17 |
AZURE::StreamAnalytics |
9 |
AZURE::Logic |
8 |
AZURE::Network (virtual network manager) |
8 |
AZURE::Automation |
7 |
AZURE::DataProtection |
6 |
AZURE::OperationalInsights |
6 |
AZURE::Storage |
6 |
AZURE::DesktopVirtualization |
5 |
Every type ships with a PKL schema, a provisioner, mock-based integration tests and conformance fixtures.
58 conformance fixtures in .github/conformance-matrix.txt, so nightly now
covers the new resources as well as CI. Each one passed CRUD and discovery
against a live subscription before being listed; none was added on the
strength of a local run.
TestNoFieldIsBothWriteOnlyAndRequired, guarding a combination that makes a
resource permanently undiscoverable. writeOnly says the provider never
returns the value and required makes core reject anything lacking it, so
discovery finds the resource and then throws it away — reported as a bare
[Discover] timeout with nothing pointing at the cause. Twenty fields had
both; verify-schema cannot see it and neither can any other gate.
make verify-fixtures, which renders every testdata/*.pkl. verify-schema,
test-unit, go vet, golangci-lint and pkl eval all evaluate the schema
without rendering a forma, so none of them catches a fault that only appears
when one is rendered.
25 new resource types, taking the plugin from 148 to 173.
Web::ServicePlan, Web::WebApp, Web::FunctionApp,
Web::WebAppSlot, Web::Certificate, Web::CustomHostnameBinding,
Web::StaticSite. The plugin had no PaaS compute coverage at all before this.DocumentDB::Sql{Database,Container,RoleDefinition,RoleAssignment},
DocumentDB::Mongo{Database,Collection}, DocumentDB::Cassandra{Keyspace,Table},
DocumentDB::Gremlin{Database,Graph}, DocumentDB::Table. The account type
already existed but none of its per-API child resources did, so it could not
express a usable database.Network::VirtualWan,
Network::VirtualHub, Network::VpnGateway, Network::VpnSite,
Network::VirtualNetworkGateway, Network::VirtualNetworkGatewayConnection,
Network::BastionHost. First hybrid-connectivity coverage in the plugin.Azure operation failures now log the provider's own message. All four failure
helpers in pkg/prov/wrap.go set ErrorCode and discarded err.Error(), so
every ARM error arrived as a bare code with no reason attached.
scripts/ci/clean-environment.sh purges soft-deleted API Management services
with az rest rather than az apim deletedservice purge --no-wait — a flag
that does not exist, so every purge had been failing silently and the
soft-deletes were exhausting the 20-service Consumption cap.
86 nested classes across the schema now extend formae.SubResource instead of
being plain classes. Schema extraction only walks nested classes that formally
extend SubResource, so a plain nested class's @azure.FieldHint annotations
never reached the schema and its nullable collections never got the
absent/explicit-null/explicit-empty handling a real sub-resource gets elsewhere.
Both now apply, which is a behavior change rather than a no-op:
intervalInSeconds) is tolerated as a provider default instead of reading
as drift on every sync.App::ContainerApp's
Secret.value, App::ManagedEnvironment's
LogAnalyticsConfiguration.sharedKey, Network::ApplicationGateway's
SSLCertificate.data and .password, Network::VirtualNetworkGateway's
VpnClientConfiguration.radiusServerSecret, and Network::VpnGateway's
VpnGatewayConnection.sharedKey.required validation now fires for 118 previously-unenforced nested fields,
and eval may reject a forma that omitted one of them.Network::VirtualHub delete no longer fails while the hub router is still
programming. ARM refuses DeleteVirtualHub while routingState is
Provisioning, which runs ~11 minutes past the point the create LRO reports
Succeeded; the delete now waits for the router instead of erroring.az group delete --no-wait || true and reported success
unconditionally, so a refused delete or a group created mid-sweep leaked silently.schema/provider-default-dispositions.json and its TestProviderDefaultDispositionsManifest
guard. The manifest required a recorded disposition for every
hasProviderDefault annotation, classified as pending, keep, co-owned or
referenced-output. In practice all 426 rows were pending and none carried a
pin, so it recorded no audit decision that the annotation itself did not already
state, while requiring every schema change to touch a second single-owner file.
The hasProviderDefault annotations are unchanged and continue to drive
provider-default tolerance in the conformance runner; only the ledger and its
test are gone.formae.SecretValue so their values are hashed at rest end-to-end (previously stored in cleartext on the read/actual-state path). Covers Azure::Compute::VirtualMachine and Azure::Compute::VirtualMachineScaleSet adminPassword, Azure::DBforPostgreSQL::FlexibleServer and Azure::Sql::Server administratorLoginPassword, Azure::KeyVault::Secret value, and Azure::KubernetesConfiguration::FluxConfiguration accessKey. Requires a formae agent on the matching release; minFormaeVersion is bumped to 0.88.0.Network::ApplicationGateway — Application Gateway v2 (L7 load balancer / HTTPS
ingress): gateway/frontend IP configurations, frontend ports, backend address
pools, backend HTTP settings, health probes, HTTP listeners, request routing
rules, SSL certificates (inline PFX or a Key Vault secret reference), an optional
user-assigned managed identity, and a firewallPolicyId to attach a WAF policy.Network::ApplicationGatewayWebApplicationFirewallPolicy — WAF policy
(policySettings, managed OWASP rule sets, custom rules).KeyVault::Certificate. Data-plane certificate lifecycle (vaultUri-based, like
KeyVault::Secret): import a BYO PFX/PEM (data + password, write-only) or
issue a self-signed cert via a minimal policy (issuerName / subject / keyType
/ validityMonths). The resolvable exposes id, secretId, and thumbprint, so
secretId can be wired into an Application Gateway or Front Door listener.Network::DnsZone and Network::DnsRecordSet — public DNS. One polymorphic
record-set resource covers A / CNAME / TXT via recordType.Microsoft.Cdn): Cdn::Profile,
Cdn::AFDEndpoint, Cdn::AFDOriginGroup, Cdn::AFDOrigin, Cdn::Route,
Cdn::AFDCustomDomain, and Cdn::Secret (BYO Key Vault TLS certificate).Microsoft.App): App::ManagedEnvironment and
App::ContainerApp (ingress, containers, scale; secrets are write-only).StatusMessage, so a retrying resource reports why it failed instead of a
bare error code.cdn-*) lane. cdn-route is excluded pending
a formae-core resolve-cache fix; certificate, cdn-afd-custom-domain,
cdn-secret, managed-environment, and container-app are excluded because
they need a real certificate/domain/data-plane role or are too slow to provision
in CI — all covered by mocked integration + marshaller round-trip tests and a
manual live gate.negationConditon default, Front Door provider-defaulted optional fields, Front
Door's canonical location ("Global"), and the Application Gateway
managed-identity type casing (userAssigned → UserAssigned) no longer
reconcile as phantom updates.CognitiveServices::Account, Compute::VirtualMachineExtension,
Dashboard::Grafana, Dashboard::GrafanaManagedPrivateEndpoint,
EventGrid::SystemTopic, EventHub::Namespace, Network::RouteTable, and
ServiceBus::Namespace.Compute::VirtualMachine now serializes SSH public keys on Read, and its OS /
configuration fields are marked create-only, so changing an immutable field
plans a replace instead of an update the provider would reject.KeyVault::Secret. Manage a secret inside an existing Key Vault, parented to a
KeyVault::Vault through its vaultUri. The secret value is write-only and
uses formae's opaque values, so it is masked in diffs and stored as a hash
rather than in cleartext: formae.value(x).opaque rotates the secret in place
when its value changes, while formae.value(x).opaque.setOnce seeds the secret
once and leaves it untouched on later edits to that value. Requires formae
0.86.2 or later, so an unchanged secret is not re-written when other fields on
the same resource change.Sql::Server, Sql::Database, Sql::FirewallRule, and
Sql::ServerAzureADAdministrator. Provision a logical SQL server together with
its databases, firewall rules, and Azure AD administrator from a single forma.ManagedCluster now exposes
oidcIssuerUrl and UserAssignedIdentity exposes tenantId through their
resolvables, and FederatedIdentityCredential.issuer accepts a resolvable.
Together these let you create an AKS cluster, a user-assigned identity, and the
federated credential linking them in one forma.Authorization::RoleAssignment,
Compute::VirtualMachine, ContainerService::ManagedCluster,
ContainerService::TrustedAccessRoleBinding,
KubernetesConfiguration::Extension, and
KubernetesConfiguration::FluxConfiguration are now marked create-only, so
changing them plans a replace instead of attempting an update the provider
would reject. Requires formae 0.86.0 or later.Network::LoadBalancer,
Network::PrivateEndpoint, Network::PrivateDnsZone,
Network::PrivateDnsZoneVirtualNetworkLink, Network::PrivateDnsZoneGroup,
Compute::Disk, Compute::VirtualMachineScaleSet, Storage::BlobContainer,
and ManagedIdentity::FederatedIdentityCredential. Enables provisioning
private-endpoint-fronted services (private DNS zones linked to VNets, app-side
private endpoints) and scaled compute backed by managed disks.AZURE:: instead of Azure::
(e.g. AZURE::Network::VirtualNetwork,
AZURE::ContainerService::ManagedCluster). Aligns with the casing used by the
AWS, GCP, and OCI plugins. CLI filters or queries that reference Azure resource
types by string need updating; resources already in inventory under the old
casing should be re-discovered after upgrade.MaintenanceConfiguration, Extension,
FluxConfiguration, and TrustedAccessRoleBinding can now be managed
alongside ManagedCluster. Use these for AKS maintenance windows, Kubernetes
extensions (Flux, Dapr, Azure ML), GitOps Flux v2 configuration, and granting
Azure services access to an AKS cluster.ManagedCluster now exposes kubeConfig and the cluster CA certificate
through its res resolvable. This lets you provision an AKS cluster and deploy
Kubernetes workloads onto it from the same forma without a manual kubeconfig
step, the same pattern as EKS auth via resolvables.