Migrating Azure Terraform to Frostmoln
To run a conversion, see the migration guide.
This matrix lists how the converter handles each supported resource type and attribute.
azurerm_dns_a_record → frostmoln_dns_record — partially supported
A records map to frostmoln_dns_record; the record name becomes zone-relative and the IP list becomes records.
The zone is referenced by name on Azure — a direct azurerm_dns_zone reference is rewritten to the converted zone id, otherwise set zone_id manually. Alias records (target_resource_id) have no equivalent.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| records | Mapped to records | |
| ttl | Mapped to ttl | |
| target_resource_id | Manual action required | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_aaaa_record → frostmoln_dns_record — partially supported
AAAA records map to frostmoln_dns_record; the record name becomes zone-relative and the IPv6 list becomes records.
The zone is referenced by name on Azure — a direct azurerm_dns_zone reference is rewritten to the converted zone id, otherwise set zone_id manually. Alias records (target_resource_id) have no equivalent.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| records | Mapped to records | |
| ttl | Mapped to ttl | |
| target_resource_id | Manual action required | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_caa_record → frostmoln_dns_record — partially supported
CAA records BLOCK the conversion: the structured record blocks cannot be aggregated into the required records set, and a plausible placeholder would be dangerous.
frostmoln_dns_record takes records as rdata strings ("<flags> <tag> <value>"). The Azure record blocks have no direct equivalent, and inventing a value could block legitimate certificate issuance — so the whole record is left unconverted for you to recreate by hand.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| record | Manual action required | |
| ttl | Mapped to ttl | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_cname_record → frostmoln_dns_record — partially supported
CNAME records map to frostmoln_dns_record; the single record target becomes a one-element records set.
The zone is referenced by name on Azure — a direct azurerm_dns_zone reference is rewritten to the converted zone id, otherwise set zone_id manually. Alias records (target_resource_id) have no equivalent.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| record | Converted to records | The single CNAME target becomes a one-element records set. |
| ttl | Mapped to ttl | |
| target_resource_id | Manual action required | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_mx_record → frostmoln_dns_record — partially supported
MX records map to frostmoln_dns_record; literal record blocks are aggregated into rdata strings, and anything computed blocks the conversion.
frostmoln_dns_record takes records as rdata strings ("<preference> <exchange>", e.g. "10 mail.example.com."). Record blocks whose preference and exchange are literals are aggregated for you and flagged for review — verify them, because wrong values misroute mail. A record block with a computed value leaves the whole record unconverted. Azure allows an omitted name for the apex; set "@" if needed.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| record | Converted to records | Literal record blocks are aggregated into "<preference> <exchange>" rdata strings and flagged for review; a computed value blocks the whole record. |
| ttl | Mapped to ttl | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_ns_record → frostmoln_dns_record — partially supported
NS records map to frostmoln_dns_record; the record name becomes zone-relative and the name-server list becomes records.
The zone is referenced by name on Azure — a direct azurerm_dns_zone reference is rewritten to the converted zone id, otherwise set zone_id manually.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| records | Mapped to records | |
| ttl | Mapped to ttl | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_ptr_record → frostmoln_dns_record — partially supported
PTR records map to frostmoln_dns_record; the record name becomes zone-relative and the target list becomes records.
The zone is referenced by name on Azure — a direct azurerm_dns_zone reference is rewritten to the converted zone id, otherwise set zone_id manually.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| records | Mapped to records | |
| ttl | Mapped to ttl | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_srv_record → frostmoln_dns_record — partially supported
SRV records BLOCK the conversion: the structured record blocks cannot be aggregated into the required records set, and a plausible placeholder would be dangerous.
frostmoln_dns_record takes records as rdata strings ("<priority> <weight> <port> <target>"). The Azure record blocks have no direct equivalent, and inventing a value could misdirect service discovery — so the whole record is left unconverted for you to recreate by hand.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| record | Manual action required | |
| ttl | Mapped to ttl | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_txt_record → frostmoln_dns_record — partially supported
TXT records map to frostmoln_dns_record; literal record blocks are aggregated into the records set, and anything computed blocks the conversion.
frostmoln_dns_record takes records as a set of TXT values. Record blocks whose values are literals are aggregated for you and flagged for review — verify them, because SPF/DKIM/verification records break silently if a value is wrong. A record block with a computed value leaves the whole record unconverted: inventing a value there would be dangerous.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Azure record names are already relative to the zone, which is exactly what Frostmoln takes: they pass through unchanged (lowercased). A name that repeats the zone suffix is flagged for review. |
| zone_name | Converted to zone_id | A direct azurerm_dns_zone reference is rewritten to the converted frostmoln_dns_zone id. |
| record | Converted to records | Literal record blocks are aggregated into the records set and flagged for review; a computed value blocks the whole record. |
| ttl | Mapped to ttl | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
typeautomatically. the record type is fixed by the source resource type.
azurerm_dns_zone → frostmoln_dns_zone — partially supported
Public DNS zones map to frostmoln_dns_zone; the zone name is normalized to a lowercase FQDN and a required SOA email is synthesized as a placeholder.
Zone names are normalized to lowercase fully qualified domains with a trailing dot. The required SOA admin email cannot come from Azure — an FM-TODO placeholder is emitted. SOA record tuning is platform-managed.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Converted | Zone names are normalized to a lowercase FQDN with a trailing dot, e.g. "example.com." |
| soa_record | Not carried over | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Converted |
- The converter adds
emailautomatically. the SOA admin email is required; replace the placeholder with a real contact address.
azurerm_key_vault_secret → frostmoln_secret — partially supported
Key Vault secrets map to frostmoln_secret; the secret value and content type carry over.
The secret is no longer scoped to a vault — access is granted from the principal side, with a frostmoln_iam_policy attached by frostmoln_iam_policy_attachment to the api_key, workload_identity, or group that needs it. Expiry and activation dates have no equivalent; version history is platform-managed.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| value | Mapped to secret_value | |
| content_type | Mapped to content_type | |
| key_vault_id | Not needed on Frostmoln | |
| expiration_date | Not carried over | |
| not_before_date | Not carried over | |
| tags | Converted |
azurerm_kubernetes_cluster → frostmoln_kubernetes_cluster — partially supported
AKS clusters map to frostmoln_kubernetes_cluster; the default_node_pool becomes the required initial_node_pool (its vm_size reported as a size, with flavor_id left for you to set), and managed identity / RBAC / network profiles are dropped.
The API endpoint's exposure does NOT carry over — it is not a per-cluster setting on the platform (see private_cluster_enabled). Public WORKLOAD traffic has no cluster-level equivalent either: a managed cluster is NOT given an ingress load balancer, so there is no attribute for an AKS public entrypoint to convert into and none is emitted. Expose a workload the standard Kubernetes way instead — a Service of type=LoadBalancer inside the cluster, which the platform provisions a load balancer for. That is an in-cluster step (kubectl/Helm), not Terraform, so this conversion cannot do it for you. And it is only half a replacement today: such a Service can be given an INTERNAL address only, because a per-Service PUBLIC address is not built yet, so a publicly reachable workload entrypoint is an open GAP. Do not assume the converted configuration exposes anything — carry the source's public entrypoint forward on your migration checklist and plan it by hand. frostmoln_kubernetes_cluster requires a VPC and subnet — vpc_id and subnet_id are synthesized as placeholders (set them to the converted network; AKS takes its subnet from default_node_pool.vnet_subnet_id). Managed identities, service principals, Azure AD RBAC, add-on profiles, and network plugins have no equivalent and are dropped. Workload identity IS supported on managed Kubernetes — it is switched on for the tenant rather than per cluster, so workload_identity_enabled carries nothing over. The tenant must hold the workload-identity entitlement BEFORE the cluster is created; it cannot be enabled on an existing one. Each azurerm_federated_identity_credential has to be rewritten by hand as a frostmoln_workload_identity_binding, because the source block carries neither the cluster nor the permissions the binding needs; grant the binding with a frostmoln_iam_policy attached by frostmoln_iam_policy_attachment (its flat scopes list is optional and coarser). Verify the Kubernetes version is offered on the platform.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| kubernetes_version | Mapped to version | Verify the version is offered on the platform (fm kubernetes version list). |
| default_node_pool | Converted to initial_node_pool | node_count and name carry over; autoscaling/zones/taints are dropped. vm_size is parsed for its vCPUs and RAM and reported; flavor_id is left as an FM-TODO to set from fm kubernetes flavor list. If vm_size comes from a variable, the pool still converts and the size is simply not reported. |
| dns_prefix | Not carried over | |
| dns_prefix_private_cluster | Not carried over | |
| identity | Manual action required | |
| service_principal | Manual action required | |
| role_based_access_control_enabled | Not needed on Frostmoln | |
| azure_active_directory_role_based_access_control | Manual action required | |
| network_profile | Not carried over | |
| private_cluster_enabled | Not carried over | The Kubernetes API endpoint's exposure is not configurable on the platform, so there is nothing to set here. A private AKS cluster carries over faithfully — the managed control plane's API endpoint is private by default and is reached from inside your VPC. A PUBLIC AKS cluster does NOT: plan for kubectl access from inside the VPC, or through the platform's authenticated access path. If what you actually exposed publicly was your WORKLOAD traffic, that has no setting on the converted cluster either: a managed cluster is not given an ingress load balancer, and a workload is exposed with a Kubernetes Service of type=LoadBalancer inside the cluster. Only INTERNAL addresses are available for such a Service today, so a PUBLIC workload entrypoint is an open GAP — plan it as manual work rather than expecting the converted configuration to provide one. |
| oidc_issuer_enabled | Not carried over | |
| workload_identity_enabled | Not carried over | Workload identity is available on managed Kubernetes, but it is switched on for your tenant rather than per cluster, so there is nothing to set here. Your tenant must hold the workload-identity entitlement BEFORE the cluster is created — it cannot be turned on for a cluster that already exists. Bind each Kubernetes service account with frostmoln_workload_identity_binding, granted by a frostmoln_iam_policy attached with frostmoln_iam_policy_attachment (its flat scopes list is optional and coarser). |
| azure_policy_enabled | Not carried over | |
| oms_agent | Not carried over | |
| key_vault_secrets_provider | Manual action required | |
| sku_tier | Not carried over | |
| resource_group_name | Not needed on Frostmoln | |
| node_resource_group | Not needed on Frostmoln | |
| location | Not needed on Frostmoln | |
| tags | Not carried over |
- The converter adds
initial_node_poolautomatically. frostmoln_kubernetes_cluster requires an initial node pool; set flavor_id to a platform flavor (seefm kubernetes flavor list). - The converter adds
versionautomatically. frostmoln_kubernetes_cluster requires a version; set it to an offered version (fm kubernetes version list). - The converter adds
vpc_idautomatically. frostmoln_kubernetes_cluster requires a VPC; set vpc_id to the converted frostmoln_vpc id. - The converter adds
subnet_idautomatically. frostmoln_kubernetes_cluster requires a subnet; set subnet_id to the converted frostmoln_subnet id (AKS takes it from default_node_pool.vnet_subnet_id).
azurerm_kubernetes_cluster_node_pool → frostmoln_kubernetes_node_pool — partially supported
AKS node pools map to frostmoln_kubernetes_node_pool; the vm_size's size is reported and flavor_id is left for you to set, and node_count carries over.
A node pool draws flavor_id from the Kubernetes node-flavor catalog, which is separate from the compute one — so the converter reports the source vm_size's vCPUs and RAM and leaves flavor_id as an FM-TODO for you to set from fm kubernetes flavor list. Autoscaling (min/max count), availability zones, node labels, node taints, and per-pool subnets have no equivalent and are dropped or blocked. The pool name must be a lowercase DNS label of at most 18 characters.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| kubernetes_cluster_id | Mapped to cluster_id | |
| vm_size | Converted to flavor_id | Parsed for its vCPUs and RAM and reported; flavor_id is left as an FM-TODO to set from fm kubernetes flavor list. |
| node_count | Mapped to node_count | |
| auto_scaling_enabled (also matches enable_auto_scaling) | Manual action required | |
| min_count | Manual action required | |
| max_count | Manual action required | |
| vnet_subnet_id | Not carried over | |
| zones | Not carried over | |
| node_labels | Not carried over | |
| node_taints | Manual action required | |
| mode | Not carried over | |
| os_type | Not carried over | |
| os_disk_size_gb | Not carried over | |
| os_disk_type | Not carried over | |
| orchestrator_version | Not carried over | |
| priority | Manual action required | |
| eviction_policy | Manual action required |
- The converter adds
flavor_idautomatically. frostmoln_kubernetes_node_pool requires a flavor; set flavor_id to a platform flavor (seefm kubernetes flavor list).
azurerm_linux_virtual_machine → frostmoln_instance — partially supported
Linux VMs map to frostmoln_instance: the VM size is capability-matched to a flavor, the source image becomes a frostmoln_image lookup, the admin SSH key becomes a frostmoln_ssh_key, and the referenced network interface is absorbed (its subnet becomes subnet_id and its public IP becomes a frostmoln_public_ip).
size is matched against the platform flavor catalog and emitted as a data "frostmoln_flavor" lookup — offline a reviewed placeholder is emitted instead. The source image becomes a data "frostmoln_image" lookup whose name needs review (fm compute image list). subnet_id is contributed by the absorbed azurerm_network_interface — a VM whose interface could not be merged has no subnet and must be wired manually. Network security groups are NOT associated automatically (Azure attaches them to the interface or subnet, not the VM); set security_groups to the converted frostmoln_security_group ids. os_disk sizing is flavor/image-determined and dropped. custom_data is base64 on Azure and is decoded into user_data. Managed identities have no equivalent.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| size | Converted to flavor_id | Capability-matched against the platform flavor catalog and emitted as a frostmoln_flavor data source lookup by name. |
| source_image_id | Converted to image_id | A marketplace/shared image id becomes a frostmoln_image data source with a reviewed placeholder name. |
| source_image_reference | Converted to image_id | publisher/offer/sku are matched against the platform image catalog when possible, otherwise a reviewed placeholder is emitted. |
| admin_ssh_key | Converted to ssh_key_names | The public key becomes a generated frostmoln_ssh_key, and ssh_key_names references it. |
| custom_data | Converted to user_data | Azure custom_data is base64-encoded; it is decoded into plain-text user_data. |
| user_data | Manual action required | |
| network_interface_ids | Not needed on Frostmoln | |
| location | Converted | The platform picks the zone; set zone to a zone id to pin one. |
| zone | Not carried over | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Converted | |
| admin_username | Not carried over | |
| admin_password | Not carried over | |
| disable_password_authentication | Not needed on Frostmoln | |
| computer_name | Not carried over | |
| os_disk | Not carried over | |
| identity | Manual action required | |
| boot_diagnostics | Not carried over | |
| availability_set_id | Manual action required | |
| proximity_placement_group_id | Manual action required | |
| priority | Manual action required | |
| eviction_policy | Manual action required | |
| patch_mode | Not carried over | |
| secret | Manual action required | |
| additional_capabilities | Not carried over | |
| plan | Not carried over |
- The converter adds
nameautomatically. frostmoln_instance requires a name; it is taken from the Name tag or the resource address. - The converter adds
flavor_idautomatically. frostmoln_instance requires a flavor; set flavor_id to a platform flavor (seefm compute flavor list). - The converter adds
image_idautomatically. frostmoln_instance requires an image; set image_id to a platform image (seefm compute image list).
azurerm_managed_disk → frostmoln_volume — partially supported
Managed disks map to frostmoln_volume; the storage account type is dropped (the platform's volume tier applies) and IOPS/throughput are set by the tier.
Volume tiers are platform-defined — pick a volume_type from the volume tiers catalog (frostmoln_volume_tiers data source) after the conversion. A disk created by copying a snapshot keeps source_resource_id as snapshot_id (verify it references a snapshot). A required volume name is synthesized from the Name tag or the resource address.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| disk_size_gb | Mapped to size_gb | |
| storage_account_type | Not carried over | |
| source_resource_id | Converted to snapshot_id | |
| create_option | Not needed on Frostmoln | |
| os_type | Not carried over | |
| zone | Converted | The platform picks the zone; set zone to a zone id to pin one. |
| disk_iops_read_write | Not carried over | |
| disk_mbps_read_write | Not carried over | |
| disk_iops_read_only | Not carried over | |
| disk_mbps_read_only | Not carried over | |
| tier | Not carried over | |
| source_uri | Manual action required | |
| image_reference_id | Manual action required | |
| disk_encryption_set_id | Manual action required | |
| encryption_settings | Manual action required | |
| network_access_policy | Not carried over | |
| public_network_access_enabled | Not carried over | |
| max_shares | Manual action required | |
| resource_group_name | Not needed on Frostmoln | |
| location | Not needed on Frostmoln | |
| tags | Converted |
- The converter adds
nameautomatically. frostmoln_volume requires a name; it is taken from the Name tag or the resource address.
azurerm_mysql_flexible_server → frostmoln_mysql_instance — partially supported
MySQL Flexible Servers map to frostmoln_mysql_instance; the SKU's size is reported and flavor_id is left for you to set, the storage block's size_gb becomes storage_gb, and a high_availability block becomes ha_enabled.
A managed database draws flavor_id from the managed-database flavor catalog, which is separate from the compute one — so the converter reports the source SKU's vCPUs and RAM and leaves flavor_id as an FM-TODO for you to set from fm database flavor list. Administrator credentials are platform-issued and dropped. frostmoln_mysql_instance requires a VPC and subnet — vpc_id is synthesized as a placeholder and subnet_id comes from delegated_subnet_id when present. Backup retention is platform-managed. Verify the engine version is offered on the platform.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| version | Mapped to version | Verify the version is offered on the platform (fm database mysql version list). |
| sku_name | Converted to flavor_id | The VM-size component of the SKU is parsed for its vCPUs and RAM and reported; flavor_id is left as an FM-TODO to set from fm database flavor list. |
| storage | Converted to storage_gb | The storage block's size_gb becomes storage_gb; IOPS and auto-grow are platform-managed. |
| high_availability | Converted to ha_enabled | The presence of a high_availability block becomes ha_enabled = true. |
| delegated_subnet_id | Mapped to subnet_id | |
| administrator_login | Not carried over | |
| administrator_password | Not carried over | |
| backup_retention_days | Not carried over | |
| geo_redundant_backup_enabled | Not carried over | |
| zone | Not carried over | |
| maintenance_window | Not carried over | |
| private_dns_zone_id | Not carried over | |
| customer_managed_key | Manual action required | |
| resource_group_name | Not needed on Frostmoln | |
| location | Not needed on Frostmoln |
- The converter adds
versionautomatically. frostmoln_mysql_instance requires a version; set it to an offered version (fm database mysql version list). - The converter adds
flavor_idautomatically. frostmoln_mysql_instance requires a flavor; set flavor_id to a platform flavor (seefm database flavor list). - The converter adds
storage_gbautomatically. frostmoln_mysql_instance requires storage_gb; set it to the desired size in GiB. - The converter adds
vpc_idautomatically. frostmoln_mysql_instance requires a VPC; Azure servers have no VPC concept — set vpc_id to the converted frostmoln_vpc id. - The converter adds
subnet_idautomatically. frostmoln_mysql_instance requires a subnet; set subnet_id to the converted frostmoln_subnet id (delegated_subnet_id maps it when present).
azurerm_network_interface → frostmoln_instance — partially supported
Network interfaces are absorbed into the virtual machine that references them: the interface disappears, its subnet becomes the instance's subnet_id, and a referenced public IP becomes a frostmoln_public_ip bound to the instance.
Only an interface referenced by exactly one azurerm_linux_virtual_machine or azurerm_windows_virtual_machine (via network_interface_ids) and carrying a single ip_configuration with a subnet_id can be merged. A standalone interface has no equivalent — attach the instance via subnet_id. Static private IPs, multiple ip_configuration blocks, accelerated networking, and DNS servers have no equivalent. Generating the ip_configuration with a dynamic block also refuses the merge: the converter cannot read which subnet it selects, how many configurations it declares, or which public IPs they bind. An interface using count or for_each cannot be merged either. Every refusal marks the INSTANCE with a blocker for the subnet_id it never received, because frostmoln_instance.subnet_id is optional and an instance emitted without one would otherwise apply onto whichever network the platform picks.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Not needed on Frostmoln | |
| ip_configuration | Converted | subnet_id becomes the instance's subnet_id; a referenced public_ip_address_id binds the converted frostmoln_public_ip to the instance; a static private_ip_address is dropped (the platform assigns one). |
| location | Not needed on Frostmoln | |
| resource_group_name | Not needed on Frostmoln | |
| dns_servers | Manual action required | |
| accelerated_networking_enabled (also matches enable_accelerated_networking) | Not carried over | |
| ip_forwarding_enabled (also matches enable_ip_forwarding) | Manual action required | |
| tags | Not carried over |
azurerm_network_security_group → frostmoln_security_group — partially supported
Network security groups map to frostmoln_security_group; inline security_rule blocks are split into one frostmoln_security_group_rule each.
The platform security model is ALLOW-ONLY — a Deny rule has no equivalent and BLOCKS the conversion (silently dropping it would open the traffic it denies). A rule that cannot be expressed as a single allow rule (multiple address prefixes or port ranges, a restricted self side, a non-wildcard source port) also blocks — a frostmoln security-group rule filters the DESTINATION port only. Azure NSGs and Frostmoln security groups are both stateful, so such a rule was never return-traffic plumbing: it is a real source-port filter with no equivalent. Rewrite it against the destination port the traffic reaches (the source-port numbers are NOT reusable as destination ports), or keep the restriction at another layer. Inbound maps to ingress and Outbound to egress; the source (inbound) or destination (outbound) address prefix becomes remote_cidr; */Internet becomes 0.0.0.0/0. Rule priorities are dropped (the platform does not order rules). Security groups have no VPC association on Azure, so vpc_id is left unset. EGRESS DEFAULTS MATCH THE SOURCE - DO NOT "FIX" THEM: every Frostmoln security group is created with allow-all egress (Neutron adds one IPv4 and one IPv6 allow-any egress rule to every new group, and Frostmoln removes neither), and converted egress rules ADD to those defaults rather than replacing them, but an Azure NSG allows all outbound by default (AllowVnetOutBound and AllowInternetOutBound, which cannot be removed) and narrows it only with a Deny rule, which this converter always blocks - a Deny rule never becomes an output rule. So for an NSG whose outbound rules are all plain Allow, allow-all egress MATCHES the posture the source had and the additive platform default widens nothing. An NSG that DID declare an outbound Deny is reported as WIDENED instead: that rule blocked, so the restriction it expressed is absent from the output. Watch for a standalone azurerm_network_security_rule too - it blocks as its own resource, and an outbound Deny there also leaves the converted group wider than the source. Do NOT delete the two default egress rules unless you are deliberately tightening beyond the source: the group's egress would then be exactly the egress rules converted from this NSG, and where it declared none that denies ALL outbound traffic. ADDRESS FAMILY: a frostmoln_security_group_rule has no ether_type field, so every rule the platform creates is IPv4 and no converted rule can be IPv6. An inline security_rule whose address prefix is an IPv6 address or CIDR therefore BLOCKS - the rule is left unconverted rather than emitted as one that is rejected when it is applied - and the group is missing exactly that rule. Do NOT translate an IPv6 range to an IPv4 one, it selects different hosts. The wildcard prefixes are not equivalent to each other here: * covers both families, so 0.0.0.0/0 is strictly narrower, but Internet means the space OUTSIDE the virtual network, so converting it NARROWS on address family and WIDENS on address range at the same time - 0.0.0.0/0 includes the VPC's own ranges that the Internet tag excluded. Both are kept with a review finding rather than blocked, because no conversion can express the IPv6 half and a hand-rewrite lands on the same value; check the review finding on each.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| location | Not needed on Frostmoln | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Converted |
Inline security_rule blocks are split into frostmoln_security_group_rule resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| access | Converted | |
| direction | Converted | |
| protocol | Converted | |
| source_address_prefix | Converted to remote_cidr | |
| destination_address_prefix | Converted to remote_cidr | |
| destination_port_range | Converted to port_range_min | |
| destination_port_range | Converted to port_range_max | |
| description | Mapped to description | |
| source_address_prefixes | Manual action required | |
| destination_address_prefixes | Manual action required | |
| destination_port_ranges | Manual action required | |
| source_port_ranges | Manual action required | |
| source_port_range | Not needed on Frostmoln | |
| priority | Not carried over | |
| name | Not carried over |
- The converter adds
nameautomatically. frostmoln_security_group requires a name; it is taken from the Name tag or the resource address.
azurerm_network_security_rule → frostmoln_security_group_rule — partially supported
Standalone security rules map to frostmoln_security_group_rule; Deny rules and unexpressible shapes block the conversion (the platform is allow-only).
A Deny rule has no equivalent and BLOCKS the whole rule — silently converting it would open the traffic it denies. Inbound maps to ingress and Outbound to egress; the source (inbound) or destination (outbound) address prefix becomes remote_cidr; */Internet becomes 0.0.0.0/0. Multiple address prefixes or port ranges, a restricted self side, and non-wildcard source ports also block — a frostmoln security-group rule filters the DESTINATION port only. Azure NSGs and Frostmoln security groups are both stateful, so such a rule was never return-traffic plumbing: it is a real source-port filter with no equivalent. Rewrite it against the destination port the traffic reaches (the source-port numbers are NOT reusable as destination ports), or keep the restriction at another layer. Rule priority is dropped. The wildcard destination port * becomes the full 1-65535 range. ADDRESS FAMILY: a frostmoln_security_group_rule has no ether_type field, so every rule the platform creates is IPv4 and no converted rule can be IPv6. An address prefix that is an IPv6 address or CIDR therefore BLOCKS THE WHOLE RULE rather than being emitted as a rule that is rejected when it is applied - do NOT translate an IPv6 range to an IPv4 one, it selects different hosts. The two wildcard prefixes are NOT equivalent: * covers both families and every address, so 0.0.0.0/0 is strictly narrower, but Internet means the space OUTSIDE the virtual network, so converting it NARROWS on address family and WIDENS on address range at the same time - 0.0.0.0/0 includes the VPC's own ranges the Internet tag excluded. Both are kept rather than blocked, because no conversion can express the IPv6 half and a hand-rewrite lands on the same value; each carries its own review finding saying which happened.
| Attribute | Outcome | Notes |
|---|---|---|
| access | Converted | |
| direction | Converted | |
| protocol | Converted | |
| source_address_prefix | Converted to remote_cidr | |
| destination_address_prefix | Converted to remote_cidr | |
| destination_port_range | Converted to port_range_min | |
| network_security_group_name | Converted to security_group_id | A direct azurerm_network_security_group reference is rewritten to the converted frostmoln_security_group id; otherwise set security_group_id manually. |
| source_address_prefixes | Manual action required | |
| destination_address_prefixes | Manual action required | |
| destination_port_ranges | Manual action required | |
| source_port_ranges | Manual action required | |
| source_port_range | Not needed on Frostmoln | |
| priority | Not carried over | |
| description | Mapped to description | |
| resource_group_name | Not needed on Frostmoln |
- The converter adds
port_range_maxautomatically. The upper bound of the destination port range (equal to port_range_min for a single port).
azurerm_postgresql_flexible_server → frostmoln_postgres_instance — partially supported
PostgreSQL Flexible Servers map to frostmoln_postgres_instance; the SKU's size is reported and flavor_id is left for you to set, storage_mb becomes storage_gb, and a high_availability block becomes ha_enabled.
A managed database draws flavor_id from the managed-database flavor catalog, which is separate from the compute one — so the converter reports the source SKU's vCPUs and RAM and leaves flavor_id as an FM-TODO for you to set from fm database flavor list. Administrator credentials are platform-issued and dropped. frostmoln_postgres_instance requires a VPC and subnet — vpc_id is synthesized as a placeholder (Azure servers have no VPC concept) and subnet_id comes from delegated_subnet_id when present. Azure's 7-35 day backup retention differs from the platform's 35-90 day range, so backup_retention_days is dropped — set it manually. Verify the engine version is offered on the platform.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| version | Mapped to version | Verify the version is offered on the platform (fm database postgres version list). |
| sku_name | Converted to flavor_id | The VM-size component of the SKU is parsed for its vCPUs and RAM and reported; flavor_id is left as an FM-TODO to set from fm database flavor list. |
| storage_mb | Converted to storage_gb | Azure sizes storage in MiB; storage_gb is storage_mb / 1024. |
| high_availability | Converted to ha_enabled | The presence of a high_availability block becomes ha_enabled = true. |
| delegated_subnet_id | Mapped to subnet_id | |
| administrator_login | Not carried over | |
| administrator_password | Not carried over | |
| backup_retention_days | Not carried over | |
| geo_redundant_backup_enabled | Not carried over | |
| storage_tier | Not carried over | |
| auto_grow_enabled | Not carried over | |
| zone | Not carried over | |
| maintenance_window | Not carried over | |
| authentication | Manual action required | |
| private_dns_zone_id | Not carried over | |
| customer_managed_key | Manual action required | |
| resource_group_name | Not needed on Frostmoln | |
| location | Not needed on Frostmoln |
- The converter adds
versionautomatically. frostmoln_postgres_instance requires a version; set it to an offered version (fm database postgres version list). - The converter adds
flavor_idautomatically. frostmoln_postgres_instance requires a flavor; set flavor_id to a platform flavor (seefm database flavor list). - The converter adds
storage_gbautomatically. frostmoln_postgres_instance requires storage_gb; set it to the desired size in GiB. - The converter adds
vpc_idautomatically. frostmoln_postgres_instance requires a VPC; Azure servers have no VPC concept — set vpc_id to the converted frostmoln_vpc id. - The converter adds
subnet_idautomatically. frostmoln_postgres_instance requires a subnet; set subnet_id to the converted frostmoln_subnet id (delegated_subnet_id maps it when present).
azurerm_public_ip → frostmoln_public_ip — partially supported
Public IPs map to frostmoln_public_ip; the SKU and allocation method are platform-managed and a specific address cannot be requested.
When a network interface references this public IP, the absorbing virtual machine binds it via instance_id. A DNS label (domain_name_label) has no equivalent — create a frostmoln_dns_record instead. Static allocation and SKU tiers are platform-managed.
| Attribute | Outcome | Notes |
|---|---|---|
| allocation_method | Not needed on Frostmoln | |
| sku | Not needed on Frostmoln | |
| sku_tier | Not needed on Frostmoln | |
| location | Not needed on Frostmoln | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Converted | |
| domain_name_label | Manual action required | |
| ip_version | Manual action required | |
| zones | Not carried over | |
| public_ip_prefix_id | Manual action required | |
| idle_timeout_in_minutes | Not carried over | |
| reverse_fqdn | Manual action required | |
| ip_tags | Not carried over |
azurerm_redis_cache → frostmoln_redis_instance — partially supported
Redis caches map to frostmoln_redis_instance; the Azure tier model (sku/capacity/family) does not map to a platform flavor, so flavor_id is a placeholder to set from fm cache flavor list.
Azure sizes Redis by SKU tier + capacity rather than vCPU/RAM, so flavor_id, vpc_id, and subnet_id are synthesized as placeholders. The redis_configuration block is dropped — set eviction_policy (from maxmemory_policy) and persistence_mode (rdb/aof) on the instance manually. Verify the engine version is offered on the platform. The managed cache serves the cache protocol in PLAINTEXT on port 6379 at its private address in your VPC and has no TLS listener, whatever the source configuration asked for — the AUTH password therefore also crosses the wire in cleartext, as do the keys and values themselves; and with no TLS there is no integrity protection either, so anything on the network path inside your VPC can read and modify the traffic. Azure defaults caches to TLS-only, so this differs from the source even when the configuration says nothing about it.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| redis_version | Mapped to version | Verify the version is offered on the platform (fm cache redis version list). |
| subnet_id | Mapped to subnet_id | Only Premium caches attach to a subnet; verify it references the converted frostmoln_subnet. |
| capacity | Not carried over | |
| family | Not carried over | |
| sku_name | Not carried over | |
| redis_configuration | Not carried over | |
| non_ssl_port_enabled (also matches enable_non_ssl_port) | Manual action required | |
| minimum_tls_version | Manual action required | |
| patch_schedule | Not carried over | |
| zones | Not carried over | |
| private_static_ip_address | Manual action required | |
| public_network_access_enabled | Not carried over | |
| resource_group_name | Not needed on Frostmoln | |
| location | Not needed on Frostmoln |
- The converter adds
versionautomatically. frostmoln_redis_instance requires a version; set it to an offered version (fm cache redis version list). - The converter adds
flavor_idautomatically. frostmoln_redis_instance requires a flavor; the Azure tier does not map — set flavor_id (seefm cache flavor list). - The converter adds
vpc_idautomatically. frostmoln_redis_instance requires a VPC; set vpc_id to the converted frostmoln_vpc id. - The converter adds
subnet_idautomatically. frostmoln_redis_instance requires a subnet; set subnet_id to the converted frostmoln_subnet id.
azurerm_snapshot → frostmoln_snapshot — partially supported
Disk snapshots map to frostmoln_snapshot; the source managed disk becomes volume_id.
Only a snapshot copied from a managed disk (create_option Copy, source_resource_id) converts cleanly — a blob-import snapshot has no equivalent. A required snapshot name is synthesized from the Name tag or the resource address.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| source_resource_id | Converted to volume_id | |
| create_option | Not needed on Frostmoln | |
| source_uri | Manual action required | |
| storage_account_id | Manual action required | |
| disk_size_gb | Not carried over | |
| incremental_enabled | Not carried over | |
| encryption_settings | Manual action required | |
| disk_encryption_set_id (retired by the provider) | Manual action required | |
| resource_group_name | Not needed on Frostmoln | |
| location | Not needed on Frostmoln | |
| tags | Converted |
- The converter adds
nameautomatically. frostmoln_snapshot requires a name; it is taken from the Name tag or the resource address. - The converter adds
volume_idautomatically. frostmoln_snapshot requires a source volume; set volume_id to the converted frostmoln_volume id.
azurerm_ssh_public_key → frostmoln_ssh_key — supported
SSH public keys map directly to frostmoln_ssh_key.
The key name becomes the resource name; tags are not supported on SSH keys.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| public_key | Mapped to public_key | |
| resource_group_name | Not needed on Frostmoln | |
| location | Not needed on Frostmoln | |
| tags | Not carried over |
azurerm_storage_container → frostmoln_bucket — partially supported
Storage containers map to frostmoln_bucket; the container name becomes the bucket name.
Bucket names are globally unique on the platform — if the container name collides, prefix it with the storage account name. Buckets are private and stay private: a public container_access_type has no equivalent. The bucket API does carry an acl field and will accept and echo back public-read, but it is inert - it is kept as bucket metadata and never applied to the storage backend - and there is no bucket-policy API at all. Every issued S3 credential is additionally denied s3:PutBucketAcl and s3:PutBucketPolicy, so anonymous read cannot be re-created by any route. Re-grant access with a frostmoln_s3_credential scoped to the bucket (allowed_buckets, optionally ip_whitelist, and allowed_actions - which takes a fixed catalogue of S3 actions, so leave it out to grant them all). For a browser, hand out a presigned URL instead of a public object, remembering it is issued through the API or portal rather than Terraform and lasts at most an hour. Object metadata and immutability policies are dropped.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | The container name becomes the bucket name; prefix it with the storage account name if it collides globally. |
| storage_account_id (also matches storage_account_name) | Not carried over | |
| container_access_type | Manual action required | |
| metadata | Not carried over | |
| default_encryption_scope | Not needed on Frostmoln |
data.azurerm_subnet → frostmoln_subnet — partially supported
The azurerm_subnet data source maps to the frostmoln_subnet data source, which selects a subnet by id or name (optionally narrowed by vpc_id); resource-group scoping has no equivalent.
frostmoln_subnet looks up a subnet by id or name. virtual_network_name narrows the lookup by VPC — a direct azurerm_virtual_network reference is rewritten to the converted VPC id, otherwise set vpc_id manually.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| virtual_network_name | Converted to vpc_id | A direct azurerm_virtual_network reference is rewritten to the converted frostmoln_vpc id. |
| resource_group_name | Manual action required |
azurerm_subnet → frostmoln_subnet — partially supported
Subnets map to frostmoln_subnet; the first address prefix becomes the subnet CIDR and the parent virtual network becomes vpc_id.
frostmoln_subnet takes a single CIDR — a subnet with more than one address_prefixes keeps only the first and flags the rest for review. virtual_network_name references the network by name; a direct azurerm_virtual_network reference is rewritten to the converted VPC id, otherwise set vpc_id manually. Service endpoints, delegations, and private-endpoint policies are dropped (the platform manages them).
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| address_prefixes | Converted to cidr | The first address prefix becomes cidr; additional ranges are flagged for review. |
| virtual_network_name | Converted to vpc_id | A direct azurerm_virtual_network reference is rewritten to the converted frostmoln_vpc id; otherwise set vpc_id manually. |
| resource_group_name | Not needed on Frostmoln | |
| service_endpoint (also matches service_endpoints) | Not carried over | |
| service_endpoint_policy_ids | Not carried over | |
| delegation | Not carried over | |
| private_endpoint_network_policies (also matches private_endpoint_network_policies_enabled) | Not carried over | |
| private_link_service_network_policies_enabled | Not carried over | |
| default_outbound_access_enabled | Not carried over |
- The converter adds
nameautomatically. frostmoln_subnet requires a name; it is taken from the Name tag or the resource address.
azurerm_virtual_machine_data_disk_attachment → frostmoln_volume_attachment — supported
Data disk attachments map to frostmoln_volume_attachment; the managed disk becomes volume_id and the VM becomes instance_id.
The LUN, caching mode, and write-accelerator flag are platform-managed and dropped. The device path is assigned by the platform.
| Attribute | Outcome | Notes |
|---|---|---|
| managed_disk_id | Mapped to volume_id | |
| virtual_machine_id | Mapped to instance_id | |
| lun | Not carried over | |
| caching | Not carried over | |
| write_accelerator_enabled | Not carried over | |
| create_option | Not needed on Frostmoln |
data.azurerm_virtual_network → frostmoln_vpc — partially supported
The azurerm_virtual_network data source maps to the frostmoln_vpc data source, which selects a VPC by id or name; resource-group scoping has no equivalent.
frostmoln_vpc looks up a VPC by id or name. The address_space export is a list on Azure but a single cidr on the platform — references to address_space break; use cidr.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| resource_group_name | Manual action required |
azurerm_virtual_network → frostmoln_vpc — partially supported
Virtual networks map to frostmoln_vpc; the first address space becomes the VPC CIDR.
frostmoln_vpc takes a single CIDR — a virtual network with more than one address_space keeps only the first and flags the rest for review (recreate additional ranges as separate resources). DNS servers, DDoS plans, and peerings have no equivalent at the VPC level. The converted VPC has NO outbound internet access on its own — Azure's default outbound access has no counterpart: outbound is an explicit frostmoln_gateway (one per VPC, egressing from an address the platform draws for it; name a Public IP of your own with public_ip_id where a partner allow-lists your source address, and that address is billed as a Public IP), and without it the VPC also has no DNS resolution and no managed-service connectivity, because those are reached over routes that exist only while the egress path does.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| address_space | Converted to cidr | The first address space becomes cidr; additional ranges are flagged for review. |
| location | Not needed on Frostmoln | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Converted | |
| dns_servers | Manual action required | |
| ddos_protection_plan | Manual action required | |
| subnet | Manual action required | |
| bgp_community | Manual action required | |
| flow_timeout_in_minutes | Not carried over | |
| edge_zone | Manual action required |
- The converter adds
nameautomatically. frostmoln_vpc requires a name; it is taken from the Name tag or the resource address.
azurerm_windows_virtual_machine → frostmoln_instance — partially supported
Windows VMs map to frostmoln_instance: the VM size is capability-matched to a flavor, the source image becomes a frostmoln_image lookup, and the referenced network interface is absorbed (its subnet becomes subnet_id and its public IP becomes a frostmoln_public_ip).
Conversion depends on an equivalent Windows image being available in the platform catalog (fm compute image list). The admin password is platform-issued and dropped. subnet_id is contributed by the absorbed azurerm_network_interface. Network security groups are NOT associated automatically; set security_groups to the converted frostmoln_security_group ids. os_disk sizing is flavor/image-determined and dropped.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| size | Converted to flavor_id | Capability-matched against the platform flavor catalog and emitted as a frostmoln_flavor data source lookup by name. |
| source_image_id | Converted to image_id | A marketplace/shared image id becomes a frostmoln_image data source with a reviewed placeholder name. |
| source_image_reference | Converted to image_id | publisher/offer/sku are matched against the platform image catalog when possible, otherwise a reviewed placeholder is emitted. |
| custom_data | Converted to user_data | Azure custom_data is base64-encoded; it is decoded into plain-text user_data. |
| network_interface_ids | Not needed on Frostmoln | |
| location | Converted | The platform picks the zone; set zone to a zone id to pin one. |
| zone | Not carried over | |
| resource_group_name | Not needed on Frostmoln | |
| tags | Converted | |
| admin_username | Not carried over | |
| admin_password | Not carried over | |
| computer_name | Not carried over | |
| os_disk | Not carried over | |
| identity | Manual action required | |
| boot_diagnostics | Not carried over | |
| availability_set_id | Manual action required | |
| proximity_placement_group_id | Manual action required | |
| priority | Manual action required | |
| eviction_policy | Manual action required | |
| patch_mode | Not carried over | |
| secret | Manual action required | |
| winrm_listener | Not carried over | |
| additional_capabilities | Not carried over | |
| plan | Not carried over |
- The converter adds
nameautomatically. frostmoln_instance requires a name; it is taken from the Name tag or the resource address. - The converter adds
flavor_idautomatically. frostmoln_instance requires a flavor; set flavor_id to a platform flavor (seefm compute flavor list). - The converter adds
image_idautomatically. frostmoln_instance requires an image; set image_id to a platform image (seefm compute image list).
Resources without an equivalent
| Resource | What happens |
|---|---|
| azurerm_application_insights | Application Insights has no equivalent; there is no managed APM service to provision. |
| data.azurerm_client_config | There is no caller-identity data source; the tenant the provider operates in is provider configuration (the frostmoln provider's tenant_id, or FROSTMOLN_TENANT_ID), so replace references with that value. |
| azurerm_container_app | Container Apps have no equivalent; there is no managed serverless-container platform. |
| azurerm_container_registry | Azure Container Registry has no equivalent; there is no managed container registry service. |
| azurerm_cosmosdb_account | Cosmos DB has no equivalent; the platform offers managed PostgreSQL, MySQL, Redis, and Valkey, but no globally-distributed multi-model NoSQL database. |
| azurerm_eventgrid_topic | Event Grid has no equivalent; there is no managed event-routing / pub-sub eventing service. |
| azurerm_eventhub_namespace | Event Hubs has no equivalent; there is no managed high-throughput event-streaming service. |
| azurerm_federated_identity_credential | The platform DOES federate Kubernetes workloads - frostmoln_workload_identity_binding is the counterpart - but nothing is emitted automatically, because this block carries none of the values the binding needs (cluster, namespace, service account, and a grant). |
| azurerm_firewall | Azure Firewall has no equivalent — the platform offers security groups (frostmoln_security_group), not a managed stateful appliance — and where this firewall was also the network's way out, that half is a frostmoln_gateway you must add separately. |
| azurerm_key_vault | Key vaults have no container equivalent; secrets are stored flat, so each azurerm_key_vault_secret becomes a frostmoln_secret directly and the vault is removed. If this vault carries INLINE access_policy blocks, their permissions are removed with it and NOT converted — re-express each one as a standalone azurerm_key_vault_access_policy resource and re-run, or write the frostmoln_iam_policy by hand. |
| azurerm_key_vault_access_policy | Frostmoln grants access from the principal side, not the vault side: the converter reads this resource's secret_permissions and writes a COMMENTED frostmoln_iam_policy candidate you review and uncomment, then attach with frostmoln_iam_policy_attachment to the api_key, workload_identity, or group that needs the secrets. Access policies written INLINE as access_policy blocks on azurerm_key_vault are not read - move them to this standalone resource before converting. |
| azurerm_linux_function_app | Function Apps have no equivalent; there is no serverless functions platform. |
| azurerm_linux_web_app | App Service has no equivalent; there is no managed PaaS web-hosting service. |
| azurerm_log_analytics_workspace | Log Analytics has no equivalent; the platform provides its own observability stack rather than a customer-provisioned logging workspace. |
| azurerm_logic_app_workflow | Logic Apps have no equivalent; there is no managed workflow-orchestration service. |
| azurerm_mysql_flexible_database | The platform does not create logical databases inside a managed instance - a frostmoln_mysql_instance comes up with no application database at all, so you must create yours over the connection with SQL before pointing an app at it. |
| azurerm_mysql_flexible_server_active_directory_administrator | Managed instances have no directory-backed authentication: the admin account on a frostmoln_mysql_instance is platform-issued, and database users are managed inside the engine rather than mapped to platform identities. |
| azurerm_mysql_flexible_server_configuration | Server parameters have no working equivalent yet - parameter groups exist as an API and frostmoln_mysql_instance accepts a parameter_group_id, but nothing applies a group to a running engine, so treat every setting here as unconverted and re-tune after migrating. |
| azurerm_mysql_flexible_server_firewall_rule | There is no IP allow-list on a managed database: a frostmoln_mysql_instance is reachable from inside the VPC it is created in and nowhere else, so access is decided by placement (vpc_id, subnet_id) rather than by firewall rules. |
| azurerm_nat_gateway | A Frostmoln VPC reaches the internet only while a frostmoln_gateway is attached to it, and the converter must not decide for you whether that path egresses from a platform-drawn address or from a Public IP you own. A VPC may have at most ONE, so several NAT gateways serving one VPC collapse into a single gateway. |
| azurerm_nat_gateway_public_ip_association | A frostmoln_gateway carries ONE source address, named on the gateway itself (public_ip_id) rather than bound to it by a separate association resource. |
| azurerm_nat_gateway_public_ip_prefix_association | A frostmoln_gateway carries a single source address — the platform's, or one Public IP you own — and there is no public-IP-prefix concept to attach. |
| azurerm_network_interface_security_group_association | Interface-to-security-group associations are not merged automatically; set security_groups on the converted frostmoln_instance to the converted frostmoln_security_group id. |
| azurerm_postgresql_flexible_server_active_directory_administrator | Managed instances have no directory-backed authentication: the admin account on a frostmoln_postgres_instance is platform-issued, and database roles are managed inside the engine rather than mapped to platform identities. |
| azurerm_postgresql_flexible_server_configuration | Server parameters have no working equivalent yet - parameter groups exist as an API and frostmoln_postgres_instance accepts a parameter_group_id, but nothing applies a group to a running engine, so treat every setting here as unconverted and re-tune after migrating. |
| azurerm_postgresql_flexible_server_database | The platform does not create logical databases inside a managed instance - a frostmoln_postgres_instance comes up with the stock postgres database plus one named after the platform-issued admin user, and any further database is created over the connection with SQL. |
| azurerm_postgresql_flexible_server_firewall_rule | There is no IP allow-list on a managed database: a frostmoln_postgres_instance is reachable from inside the VPC it is created in and nowhere else, so access is decided by placement (vpc_id, subnet_id) rather than by firewall rules. |
| azurerm_private_dns_zone | There are no private DNS zones on the platform: a frostmoln_dns_zone is always a public zone, delegated to the nameserver set the platform assigns it, so there is nothing for a private zone to convert to. |
| azurerm_private_dns_zone_virtual_network_link | A virtual-network link attaches a private DNS zone to a VNet, and the platform has no private DNS zones - so there is no link to convert either (see azurerm_private_dns_zone). |
| azurerm_private_endpoint | Private endpoints have no equivalent; managed services attach to your VPC subnet directly rather than through a private-link endpoint. |
| azurerm_resource_group | Resource groups have no equivalent; Frostmoln resources are tenant-scoped, so the group is removed. |
| azurerm_role_assignment | Frostmoln has a fine-grained access-policy engine, but nothing is converted automatically: re-express the grant as a frostmoln_iam_policy composed from a frostmoln_iam_policy_document, and attach it with frostmoln_iam_policy_attachment to the api_key, workload identity, or group that needs it. |
| azurerm_servicebus_namespace | Service Bus has no equivalent; the managed message broker speaks AMQP, but Service Bus semantics (topics/subscriptions/sessions/dead-lettering) do not map to broker queueing — the gap is the messaging model, not the protocol. |
| azurerm_storage_account | Storage accounts have no equivalent; object storage is tenant-scoped, so containers become buckets directly and the account is removed. |
| azurerm_storage_blob | Individual storage blobs (objects) are out of scope for the converter; upload objects with the S3 API or fm CLI after the bucket is created. |
| azurerm_subnet_nat_gateway_association | Frostmoln's outbound path is attached to the VPC, not to individual subnets: there is no per-subnet association, and the VPC has no outbound path at all until you add a frostmoln_gateway. It may have at most ONE, so every NAT gateway serving its subnets collapses into that one. |
| azurerm_subnet_network_security_group_association | Subnet-to-security-group associations have no equivalent; set security_groups on each converted frostmoln_instance in the subnet to the converted frostmoln_security_group id. |
| data.azurerm_subscription | Frostmoln has no subscription layer and no data source for the one you are in; the nearest boundary is the tenant, which is provider configuration (tenant_id, or FROSTMOLN_TENANT_ID). |
| azurerm_user_assigned_identity | There is no standalone identity resource, but the pattern has a counterpart: a Kubernetes workload gets its identity from frostmoln_workload_identity_binding, and permissions from a frostmoln_iam_policy. |
| azurerm_virtual_network_gateway | VPN / ExpressRoute gateways have no equivalent; there is no managed site-to-site VPN or private interconnect service. |
| azurerm_virtual_network_peering | Virtual network peering has no equivalent; there is no cross-VPC peering primitive. |
azurerm_application_insights
Instrument your application with an APM tool of your choice running on frostmoln_instance or a frostmoln_kubernetes_cluster.
data.azurerm_client_config
This data source reads nothing and creates nothing - it exposes who Terraform is authenticated as. The frostmoln provider already knows: it operates in exactly ONE tenant, taken from the provider's tenant_id (or FROSTMOLN_TENANT_ID), defaulting to the account's default tenant when neither is set. Use a variable for the tenant id and pass it to the provider, then reference that variable wherever data.azurerm_client_config.current.tenant_id was used - most commonly to scope a Key Vault or a role assignment, both of which are tenant-scoped on Frostmoln without being named. There is no counterpart at all for subscription_id (Frostmoln has no subscription layer), client_id, or object_id: the identity behind an API key is not addressable in configuration, and permissions are granted to the api_key, workload_identity, or group directly with frostmoln_iam_policy_attachment rather than to a principal id you look up first.
azurerm_container_app
Run containers on a frostmoln_kubernetes_cluster, which provides equivalent scheduling and scaling.
azurerm_container_registry
Run a registry (e.g. Harbor or the registry image) yourself on frostmoln_instance or a frostmoln_kubernetes_cluster.
azurerm_cosmosdb_account
Re-model the data on a managed relational (frostmoln_postgres_instance / frostmoln_mysql_instance) or cache (frostmoln_redis_instance) engine, or run a NoSQL engine yourself on frostmoln_instance.
azurerm_eventgrid_topic
Re-design event routing around the managed message broker or application-level webhooks.
azurerm_eventhub_namespace
Run a streaming platform yourself on frostmoln_instance, or re-design around the managed message broker where queue semantics suffice.
azurerm_federated_identity_credential
frostmoln_workload_identity_binding is the counterpart, and it is a resource you can write today. Write one per federated credential: cluster_id (the frostmoln_kubernetes_cluster the workload runs in), namespace and service_account (the two halves of the source subject, which is system:serviceaccount:<namespace>:<service-account>), and a grant. Prefer granting it with a frostmoln_iam_policy attached by frostmoln_iam_policy_attachment: that is the shape closest to the azurerm_role_assignment you are replacing, because a policy can name individual resources and deny explicitly. The alternative is the flat scopes list (concrete scopes such as compute:read, wildcards rejected), which grants across a whole service and cannot express a narrow rule. scopes is OPTIONAL - OMIT the attribute for a policy-granted binding (an empty list is not a valid spelling and is rejected). Four things to know about that path: it needs the IAM access policies feature (the iam-policies entitlement) on your tenant; a binding created before its policy is attached is INERT, and the token exchange refuses it rather than minting a credential that grants nothing; while a binding carries BOTH scopes and a policy the scopes are what apply, so the policy's additional authority takes effect only once the scopes are dropped - and dropping them NARROWS the workload to whatever the policy allows, which is never checked against what the scopes covered, so enumerate the binding's access before you drop them; and removing a binding's LAST grant is rejected, so delete the binding to remove a workload's access - which is also why terraform destroy has to take the binding first (-target), since destroying its only attachment is refused. One more step has no counterpart in this block: only a Kubernetes ServiceAccount annotated frostmoln.cloud/workload-identity=true gets a credential injected, so annotate the ServiceAccount as well as writing the binding. Before writing any of them, sort your federated credentials by WHAT the identity is for - several classes should not become a binding at all. An identity that exists only to run a cluster ADD-ON the platform installs itself becomes an entry in frostmoln_kubernetes_cluster.addons rather than a binding (read the offered keys from the frostmoln_kubernetes_addons data source; external-secrets and external-dns are there today). Three things about that: the set is applied ONCE at cluster creation, so changing it REPLACES the cluster; an explicit set replaces the platform defaults, so list external-secrets alongside anything you add if you still want it; and the addon replaces the DEPLOYMENT, not the credential - external-dns still needs the DNS feature on your tenant and a frostmoln_api_key delivered as a Kubernetes Secret before its webhook container starts, and external-secrets likewise authenticates to Frostmoln Secrets with an API key of its own. An identity that fed Azure Communication Services has no counterpart, because that product has none on Frostmoln. An identity a managed DATABASE trusted for Entra/AD authentication has no counterpart either: Frostmoln managed databases authenticate with a username and password - though a pod can avoid baking those in by FETCHING them, which a binding granted database:read allows. An identity used to read SECRETS becomes a binding whose policy allows the operation secrets:secrets:read; note that a three-segment operation string belongs in a POLICY and is not a valid flat scope (the coarse flat spelling is secrets:read), and putting one in scopes is accepted at create and then refused on every request. An ordinary application pod calling the Frostmoln API becomes a binding plus the policy its azurerm_role_assignment described. And one class looks like a binding but is not: an observability pod (loki, tempo, mimir) writing to blob storage speaks S3, and Frostmoln object bytes go directly to the object store rather than through the platform API, so it needs a frostmoln_s3_credential - scoped with allowed_buckets and allowed_actions, which unset grant every bucket and every action in the tenant, wider than the single storage account you are replacing. Its secret is returned only once, at create, so decide how it reaches the cluster before you write it. An application that calls the Frostmoln API itself can instead hold a binding and mint scoped S3 credentials at runtime; loki, tempo and mimir cannot, which is what makes them the exception rather than the rule. The converter does not emit a binding because nothing in this block supplies those values honestly: subject is almost always built from interpolated module outputs rather than a literal string, issuer is the AKS cluster's OIDC issuer URL and does not identify a Frostmoln cluster, and the PERMISSIONS are not here at all - in Azure they live in the separate azurerm_role_assignment resources that target the identity. A generated binding would therefore be placeholders in every position, which is a blocked block wearing a costume. Two further differences to plan for: there is no identity object to federate WITH (azurerm_user_assigned_identity has no counterpart - the binding is the identity), and the audience is fixed by the platform, so api://AzureADTokenExchange carries nothing over. Your tenant must hold the workload-identity entitlement BEFORE the cluster is created; it cannot be turned on afterwards. Federation covers Frostmoln-managed clusters only - a workload running anywhere else needs a frostmoln_api_key held as a secret.
azurerm_firewall
Express allow rules with frostmoln_security_group and frostmoln_security_group_rule; run a firewall appliance on frostmoln_instance for advanced L7 filtering.
Rule expressiveness is not the whole loss, though, and the other half is easier to miss. In the hub-and-spoke design this resource usually sits in, the firewall IS the spokes' outbound path — every one of them default-routes to it — so deleting it takes their internet with it. Frostmoln does not attach an outbound path to a VPC for you: a VPC without a frostmoln_gateway is an isolated network — no outbound internet, and, because the platform DNS resolver and the managed-service control plane are reached over routes that exist only while that path does, no name resolution and no managed-service connectivity either. Add one to the converted VPC:
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
}
The gateway's source address is drawn by the platform, and you do not choose which one you get, so it is not an address to publish. Where a partner allow-lists the address your traffic leaves from, allocate it yourself and name it on the gateway:
resource "frostmoln_public_ip" "egress" {}
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
public_ip_id = frostmoln_public_ip.egress.id
}
That address is one of yours: listed among your Public IPs, drawn from the same public IPv4 quota your customer-facing addresses use, and billed as a Public IP. There is no shared-outbound-address option. See the azurerm_nat_gateway entry for the rest of it, including which provider version carries the resource.
The two halves are also scoped differently. One firewall in a hub served every spoke; a Frostmoln gateway belongs to ONE VPC and a VPC may have at most one, so a hub-and-spoke topology becomes one gateway per VPC, not one shared instance.
azurerm_key_vault
Frostmoln secrets are not grouped in a vault — access is granted from the principal side (a frostmoln_iam_policy attached with frostmoln_iam_policy_attachment) rather than by a vault access policy. The converter derives a commented policy candidate from the STANDALONE azurerm_key_vault_access_policy resource only; an access_policy block written inline here is removed with the vault, so check this resource for one before assuming nothing granted access. Keys and certificates stored in a Key Vault have no equivalent.
azurerm_key_vault_access_policy
There is no vault object to grant on - frostmoln_secret is tenant-scoped, so a secret has no per-vault access list to add a principal to. secret_permissions IS mapped, one Frostmoln operation per permission rather than a blanket wildcard: Get becomes secrets:secrets:read, List becomes secrets:secrets:list, Set becomes secrets:secrets:create plus secrets:secrets:update, Delete becomes secrets:secrets:delete, and Recover and Restore become secrets:secrets:create. ONE widening remains, and it belongs to the platform rather than to the translation, because one Frostmoln operation serves two routes Key Vault kept apart: the recover route is a POST authorized by HTTP method as secrets:secrets:create, and that same operation also authorizes creating NEW secrets under any name, so a policy granting Recover or Restore WITHOUT Set converts BROADER than it was in Key Vault. A second widening used to sit beside it and is now CLOSED: the lookup-by-name route returns the secret VALUE and selects it by query parameter rather than by path id, so it was once authorized by secrets:secrets:list - the same operation as the metadata listing - and List WITHOUT Get converted broader. Since secrets v0.11.5 that route takes an explicit secrets:secrets:read on the secret id it resolves, so :list is metadata-only and List WITHOUT Get converts FAITHFULLY at the route level - subject to the scope caveat below, which is what actually decides it for an API key. The candidate states the remaining widening where it applies, and stays silent where the source already conferred it. READ THAT CAVEAT BEFORE TREATING ANY PERMISSION THIS MAPPING LEAVES OUT AS WITHHELD. An API key must hold a flat legacy scope to reach these routes at all - secrets:read for Get and List, secrets:write for Set, Delete, Recover and Restore - and Frostmoln synthesizes each of those scopes into an allow that is UNIONED with your policy: secrets:read becomes an allow for the read AND list operations on every secrets resource, and secrets:write becomes an allow for create, update AND delete on every secrets resource. A union does not subtract. So an API key holding secrets:read is conferred Get AND List whichever of the two the policy grants, and one holding secrets:write is conferred Set, Delete, Recover and Restore alike - which subsumes the recover widening above rather than adding to it. Only an explicit deny rule withholds anything: a deny overrides every allow including the synthesized one, and the candidate cannot write it for you. Two secret permissions have no counterpart at all and are excluded rather than folded into a wildcard: Purge (a deleted secret is kept for its own recovery window and removed by a background job when that expires, so there is no purge operation to grant) and Backup (there is no secret export, and the nearest grant returns the plaintext, which is more than Backup conferred). key_permissions and certificate_permissions are NOT converted: there are no key or certificate objects to grant on - Frostmoln stores secrets, and a TLS certificate is simply a secret referenced by a load-balancer listener (frostmoln_lb_listener.tls_certificate_id). Nothing is created automatically and nothing counts as converted: a block of THIS type stays blocked, so terraform validate still fails until you act on it. That does not hold for the inline access_policy form, which is removed with its vault at info severity - see azurerm_key_vault. tenant_id and object_id have no counterpart either: the principal is the frostmoln_api_key, workload identity, or group you attach to, named directly rather than looked up by object id, and the converter cannot choose it for you.
azurerm_linux_function_app
Package the function as a long-running service on frostmoln_instance or a frostmoln_kubernetes_cluster.
azurerm_linux_web_app
Deploy the application on frostmoln_instance or a frostmoln_kubernetes_cluster and expose it with a frostmoln_load_balancer.
azurerm_log_analytics_workspace
Use the platform's built-in metrics and logs, or run your own log store on frostmoln_instance.
azurerm_logic_app_workflow
Run a workflow engine yourself on frostmoln_instance or a frostmoln_kubernetes_cluster.
azurerm_mysql_flexible_database
This is a real gap, not a missing mapping: no Frostmoln API and no provider resource creates a database inside a managed instance, so nothing here converts. Read the MySQL case carefully - the credentials endpoint returns a connection pointed at the mysql SYSTEM schema (users and grants), which is not a place to put application data. Create your database the way you would on any MySQL server: CREATE DATABASE over that connection, which stays declarative if you point the community mysql Terraform provider at the instance. charset and collation carry over as arguments to that statement rather than as platform settings. Two practical notes. The connection is reachable from inside the VPC via the instance's private_ip; from a laptop or CI runner outside it, use fm forward <instance-id> <local-port> and point the provider at the local end. And the provider exports admin_username but NOT the password - fetch it with fm database mysql instance credentials <id> (or GET /api/v1/tenants/{tenant}/databases/{id}/credentials). One Azure server holding several databases can stay one instance; nothing in the frostmoln provider will manage those databases for you.
azurerm_mysql_flexible_server_active_directory_administrator
This is a real gap, not a missing mapping. Azure AD authentication lets a directory principal log in to the server as itself; Frostmoln has no equivalent - a managed instance authenticates with engine credentials only, and platform identity (users, api keys, workload identities) does not reach inside the database. The converted instance exports admin_username; the platform-issued password is NOT exported by the provider, so fetch it with fm database mysql instance credentials <id> (or GET /api/v1/tenants/{tenant}/databases/{id}/credentials) and create the users your application needs with SQL over that connection, as on any self-managed MySQL. Plan for the operational difference: sign-ins to the database are not covered by your identity provider, so per-person access is engine users plus credential rotation rather than directory membership, and there is no group-to-role mapping to carry over.
azurerm_mysql_flexible_server_configuration
Be careful with this one: the shape of the answer exists and the effect does not. Frostmoln has a parameter-group API (POST/GET/PUT/DELETE on /api/v1/tenants/{tenant}/database-parameter-groups) and frostmoln_mysql_instance takes an optional parameter_group_id, so a group can be created and referenced today - but the provisioning path never reads it, and the apply endpoint (POST /databases/{id}/parameters/apply) accepts the call and does nothing. Nothing you put in a group reaches the server. Three further limits, so you are not misled by a successful API call: parameter NAMES are never validated, so an unknown or platform-managed setting is stored and silently ignored rather than rejected; neither fm nor the portal exposes parameter groups, so the API is the only way to create one (fm only has a --parameter-group flag that REFERENCES an existing id at instance create); and there is no report that tells you which settings took effect. The practical migration path: record the settings from the blocks commented out here, size the instance so the platform defaults are adequate, and re-tune once parameter apply ships. Where a setting is load-bearing for your workload, raise it before you migrate.
azurerm_mysql_flexible_server_firewall_rule
A managed database instance is private and stays private - the database service has no expose action, so it is never published to the internet and there is nothing for these rules to narrow. Reachability comes from placement: set the converted instance's vpc_id and subnet_id, and the platform opens the engine port to that VPC for you. (Other managed offers do support an opt-in expose; databases do not.) The instance's security group is created and owned by the platform and is not an argument on frostmoln_mysql_instance, so there is nothing to reference from your configuration - and nothing worth adding, since the engine port is already open VPC-wide and no rule can make the instance reachable from outside the VPC. Two Azure idioms need a decision rather than a translation. A rule with start_ip_address and end_ip_address both 0.0.0.0 is the 'Allow public access from any Azure service' switch; it has no counterpart and nothing replaces it - run the client inside the VPC. A rule opening the server to an office or CI range means that client sits outside the VPC today: for a laptop or a CI runner use fm forward <instance-id> <local-port>, which brokers a session to the engine port with no public IP, no open port and no VPN (it needs the instance-access entitlement, and sessions end after 10 minutes idle or 1 hour); for a permanent workload, move it into the VPC. Ranges do not carry over in shape either - Azure takes an arbitrary first-to-last IP range, while Frostmoln security groups take CIDRs, and an arbitrary range is not always one CIDR.
azurerm_nat_gateway
This is not a resource the platform makes unnecessary — it is one you have to replace. A Frostmoln VPC's outbound path is an explicit resource, and a VPC without one is an isolated network: no outbound internet, and — because the platform DNS resolver and the managed-service control plane are reached over routes that exist only while that path does — no name resolution and no managed-service connectivity either. Attach one to the converted VPC:
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
}
That gateway IS the outbound translation this NAT gateway performed: every instance in the VPC egresses through it. Its source address is drawn by the platform, and you do not choose which one you get, so it is not an address to publish. Where a remote peer allow-lists your source address — which is what the public IP (or prefix) you associated with the NAT gateway gave you — allocate the address yourself and name it:
resource "frostmoln_public_ip" "egress" {}
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
public_ip_id = frostmoln_public_ip.egress.id
}
That address is one of yours: listed among your Public IPs, drawn from the same public IPv4 quota your customer-facing addresses use, and billed as a Public IP. There is no shared-outbound-address option — a VPC egresses through its own gateway, never through an address held in common with other tenants.
The converter does not write the gateway for you, deliberately: connectivity is a stated choice, so that a VPC cannot acquire an outbound path — or a billable public address — because a field was omitted.
Two shapes do not carry over at all. A VPC has AT MOST ONE gateway, while this configuration may hold several NAT gateways (typically one per zone or per subnet group) for what becomes a single VPC; one frostmoln_gateway replaces all of them, and that many-to-one collapse is left to you to make rather than performed quietly. And the gateway is attached to the VPC as a whole — there is no per-subnet association, so a design that gave some subnets a NAT gateway and left others without one has no equivalent.
The same gateway can be created outside Terraform with fm network gateway create.
It adopts rather than duplicates. If the VPC already has a gateway — one the platform attached implicitly because a public IP was associated with an instance in it, or one created outside Terraform later — import that one rather than creating a second; the platform refuses the second with GATEWAY_EXISTS. The resource is one-to-one with its VPC, so the VPC's own id is the import id:
terraform import frostmoln_gateway.main <vpc-id>
azurerm_nat_gateway_public_ip_association
This resource nominates the public IP a NAT gateway uses as its outbound source address. Frostmoln has no separate association: the VPC's gateway exposes one source address, and it is named on the gateway. Left unnamed, the gateway egresses from an address the platform draws — one you do not choose and should not publish. Naming a Public IP you own is the analogue of this association, and the answer when a remote peer allow-lists your source IP:
resource "frostmoln_public_ip" "egress" {}
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
public_ip_id = frostmoln_public_ip.egress.id
}
That address is one of yours: listed among your Public IPs, drawn from the same public IPv4 quota your customer-facing addresses use, and billed as a Public IP. You still cannot ask for a PARTICULAR address — the platform allocates it — so an allow-list entry has to be updated once to the address you are given.
The gateway itself is not created for you; see azurerm_nat_gateway, and add it with or without the public_ip_id above:
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
}
The same gateway can be created outside Terraform with fm network gateway create.
It adopts rather than duplicates. If the VPC already has a gateway — one the platform attached implicitly because a public IP was associated with an instance in it, or one created outside Terraform later — import that one rather than creating a second; the platform refuses the second with GATEWAY_EXISTS. The resource is one-to-one with its VPC, so the VPC's own id is the import id:
terraform import frostmoln_gateway.main <vpc-id>
azurerm_nat_gateway_public_ip_prefix_association
A public IP prefix gives an Azure NAT gateway a contiguous block of source addresses, usually for SNAT port headroom. Frostmoln has neither half of that: there is no public IP prefix resource, and a VPC's gateway carries exactly ONE source address.
Left to itself the gateway egresses from an address the platform draws, which you do not choose and should not publish. The closest thing to a prefix you allocated for allow-listing is a single Public IP of your own, named on the gateway:
resource "frostmoln_public_ip" "egress" {}
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
public_ip_id = frostmoln_public_ip.egress.id
}
That address is one of yours: listed among your Public IPs, drawn from the same public IPv4 quota your customer-facing addresses use, and billed as a Public IP. It is one address, not a range, and not one you can nominate.
If a partner allow-lists a whole prefix today, that expectation has to be renegotiated to a single address before this configuration will work.
The gateway itself is not created for you; see azurerm_nat_gateway, and add it with or without the public_ip_id above:
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
}
The same gateway can be created outside Terraform with fm network gateway create.
It adopts rather than duplicates. If the VPC already has a gateway — one the platform attached implicitly because a public IP was associated with an instance in it, or one created outside Terraform later — import that one rather than creating a second; the platform refuses the second with GATEWAY_EXISTS. The resource is one-to-one with its VPC, so the VPC's own id is the import id:
terraform import frostmoln_gateway.main <vpc-id>
azurerm_network_interface_security_group_association
Azure attaches a network security group to the interface, but frostmoln_instance carries security_groups directly. This association is left for you to wire explicitly: on the platform's default-drop model an instance left without its security group is unreachable (unsecured by omission / no ingress), not wide-open — so restore the intended access by setting security_groups = [frostmoln_security_group.<n>.id] on the instance.
azurerm_postgresql_flexible_server_active_directory_administrator
This is a real gap, not a missing mapping. Azure AD authentication lets a directory principal log in to the server as itself; Frostmoln has no equivalent - a managed instance authenticates with engine credentials only, and platform identity (users, api keys, workload identities) does not reach inside the database. The converted instance exports admin_username; the platform-issued password is NOT exported by the provider, so fetch it with fm database postgres instance credentials <id> (or GET /api/v1/tenants/{tenant}/databases/{id}/credentials) and create the roles your application needs with SQL over that connection, as on any self-managed PostgreSQL. Plan for the operational difference: sign-ins to the database are not covered by your identity provider, so per-person access is engine roles plus credential rotation rather than directory membership, and there is no group-to-role mapping to carry over.
azurerm_postgresql_flexible_server_configuration
Be careful with this one: the shape of the answer exists and the effect does not. Frostmoln has a parameter-group API (POST/GET/PUT/DELETE on /api/v1/tenants/{tenant}/database-parameter-groups) and frostmoln_postgres_instance takes an optional parameter_group_id, so a group can be created and referenced today - but the provisioning path never reads it, and the apply endpoint (POST /databases/{id}/parameters/apply) accepts the call and does nothing. Nothing you put in a group reaches the server. Three further limits, so you are not misled by a successful API call: parameter NAMES are never validated, so an unknown or platform-managed setting is stored and silently ignored rather than rejected; neither fm nor the portal exposes parameter groups, so the API is the only way to create one (fm only has a --parameter-group flag that REFERENCES an existing id at instance create); and there is no report that tells you which settings took effect. The practical migration path: record the settings from the blocks commented out here, size the instance so the platform defaults are adequate, and re-tune once parameter apply ships. Where a setting is load-bearing for your workload, raise it before you migrate.
azurerm_postgresql_flexible_server_database
This is a real gap, not a missing mapping: no Frostmoln API and no provider resource creates a database inside a managed instance, so nothing here converts. What you do get is usable - the instance is provisioned with the stock postgres database AND a database named after the admin user (pgadmin), owned by it, which is a fine landing spot for a single-database migration. For the rest, do what you would on any PostgreSQL server: CREATE DATABASE over the connection, which stays declarative if you point the community postgresql Terraform provider at the instance. collation and charset carry over as arguments to that statement rather than as platform settings. Two practical notes. The connection is reachable from inside the VPC via the instance's private_ip; from a laptop or CI runner outside it, use fm forward <instance-id> <local-port> and point the provider at the local end. And the provider exports admin_username but NOT the password - fetch it with fm database postgres instance credentials <id> (or GET /api/v1/tenants/{tenant}/databases/{id}/credentials). One Azure server holding several databases can stay one instance; nothing in the frostmoln provider will manage those databases for you.
azurerm_postgresql_flexible_server_firewall_rule
A managed database instance is private and stays private - the database service has no expose action, so it is never published to the internet and there is nothing for these rules to narrow. Reachability comes from placement: set the converted instance's vpc_id and subnet_id, and the platform opens the engine port to that VPC for you. (Other managed offers do support an opt-in expose; databases do not.) The instance's security group is created and owned by the platform and is not an argument on frostmoln_postgres_instance, so there is nothing to reference from your configuration - and nothing worth adding, since the engine port is already open VPC-wide and no rule can make the instance reachable from outside the VPC. Two Azure idioms need a decision rather than a translation. A rule with start_ip_address and end_ip_address both 0.0.0.0 is the 'Allow public access from any Azure service' switch; it has no counterpart and nothing replaces it - run the client inside the VPC. A rule opening the server to an office or CI range means that client sits outside the VPC today: for a laptop or a CI runner use fm forward <instance-id> <local-port>, which brokers a session to the engine port with no public IP, no open port and no VPN (it needs the instance-access entitlement, and sessions end after 10 minutes idle or 1 hour); for a permanent workload, move it into the VPC. Ranges do not carry over in shape either - Azure takes an arbitrary first-to-last IP range, while Frostmoln security groups take CIDRs, and an arbitrary range is not always one CIDR.
azurerm_private_dns_zone
This is a real gap, not a missing mapping. Every Frostmoln zone is public - it is delegated to the nameserver set the platform assigns it (read it back from name_servers; it is per zone, not one pair shared by the platform) and answers the same records to everyone, and there is no VPC-scoped view of a zone. You cannot approximate one either: A and AAAA records holding a private, loopback, link-local or unspecified address are rejected, so a public zone cannot be loaded with 10.x names as a substitute. The good news is that the usual reason for an Azure private zone rarely survives the move. Private zones are most often there to give a managed service a stable in-VPC name, and Frostmoln managed services do not need one - a frostmoln_postgres_instance, frostmoln_mysql_instance, frostmoln_redis_instance or frostmoln_valkey_instance is created directly in your VPC and subnet (vpc_id, subnet_id) and exports private_ip, so Terraform can wire a consumer to the instance by referencing that attribute rather than by resolving a name. That reference is also more accurate than the Azure original, because it breaks the plan if the instance goes away. If you genuinely need names rather than addresses inside the VPC - service discovery between your own VMs, or an application that only takes a hostname - run a resolver on an instance in the VPC and point the subnet at it with frostmoln_subnet.dns_servers, which is a list of resolvers handed to every instance on that subnet by DHCP. Two things to know about it: the list REPLACES the platform resolvers on that subnet rather than adding to them, so point it at a forwarding resolver unless you intend to lose public name resolution there; and set it when you create the subnet, because changing it later replaces the subnet. Public names for anything internet-facing stay in a frostmoln_dns_zone as normal. soa_record is platform-managed on every zone, and tags carry no meaning without a zone to hold them.
azurerm_private_dns_zone_virtual_network_link
This resource only exists to give a private zone its scope, so it disappears with the zone rather than being unsupported on its own terms. A Frostmoln zone is public and is not attached to a VPC at all: there is no per-VPC view to link, and no registration_enabled equivalent - instances are never auto-registered into a zone. Read azurerm_private_dns_zone for what to do instead; briefly, managed services are reachable in the VPC by their exported private_ip, and if you need in-VPC hostnames, run your own resolver and publish it with frostmoln_subnet.dns_servers. Watch for a converted resource that depended on this link: an Azure managed database is commonly wired to its private zone through private_dns_zone_id, and on the platform that same instance gets its VPC placement from vpc_id and subnet_id instead, so the dependency is satisfied by placement rather than by DNS.
azurerm_private_endpoint
Place managed services (databases, caches) in your VPC subnet; there is no separate private-link resource to create.
azurerm_resource_group
Frostmoln has no resource-group container — every resource belongs to the tenant. The group's name is not propagated to child resources automatically; if you relied on it for organization, add it as a tag on the converted resources yourself.
azurerm_role_assignment
The engine is live and enforcing across the customer-facing services, so this is a conversion gap rather than a platform gap - but three things stop the converter from finishing the job, and each of them is a decision only you can make. First, THE PRINCIPAL: Frostmoln has no standalone machine-identity object to create and grant to, so the attachment must name an api_key, a workload identity (managed Kubernetes only), or a group that already exists. Second, THE SCOPE WIDENS: an Azure assignment is scoped to a subscription, a resource group or a single resource, and Frostmoln has neither a subscription nor a resource-group layer, while a target FRN's region segment must be * - so the nearest expressible grant is tenant-wide and all-region, which is broader than what you had. Third, THE VOCABULARY DIFFERS: an Azure built-in role is a set of ARM control-plane and data-plane actions, a Frostmoln policy rule is a set of service:resource:action operations over FRN targets, and no mechanical translation between them is exact. For a curated set of common Azure built-in roles the converter writes a COMMENTED candidate policy into a generated frostmoln-iam-candidates.tf, stating the mapped operations and how faithful the mapping is; review it, narrow it, choose the principal, then uncomment it to adopt it. Authoring runs under an fm CLI or portal login session - a machine token may not author or attach a policy - the tenant must hold the iam-policies entitlement, and your own authority must already cover every operation the policy grants or the create is refused.
azurerm_servicebus_namespace
For classic queue/exchange messaging, run a managed broker (see the messaging offer). Topic/subscription fan-out, sessions, and dead-letter queues must be re-designed on the broker's own primitives.
azurerm_storage_account
Frostmoln has no storage-account container — each azurerm_storage_container becomes a frostmoln_bucket on its own. If you relied on the account name for global bucket uniqueness, fold it into the bucket names. S3-compatible credentials replace the account access keys (create a frostmoln_s3_credential).
azurerm_storage_blob
Object contents are not managed as Terraform resources on Frostmoln — convert the containing azurerm_storage_container to a frostmoln_bucket and upload objects separately.
azurerm_subnet_nat_gateway_association
This resource is what actually gives an Azure subnet outbound connectivity, so losing it is not a formality. Frostmoln models the outbound path one level up: a gateway belongs to the VPC and serves every subnet in it. There is no per-subnet association, so a design that deliberately gave some subnets a NAT gateway and left others with none has no equivalent — isolate those workloads with security groups, or put them in a separate VPC with no gateway.
The VPC-wide path is not implied either. A Frostmoln VPC without a gateway is an isolated network: no outbound internet, and — because the platform DNS resolver and the managed-service control plane are reached over routes that exist only while that path does — no name resolution and no managed-service connectivity either. Attach one to the converted VPC:
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
}
The gateway's source address is drawn by the platform, and you do not choose which one you get, so it is not an address to publish. Where a partner allow-lists your source address, or DNS points at it, allocate the address yourself and name it — the gateway then egresses from that one:
resource "frostmoln_public_ip" "egress" {}
resource "frostmoln_gateway" "main" {
vpc_id = frostmoln_vpc.main.id
mode = "public_ip"
public_ip_id = frostmoln_public_ip.egress.id
}
That address is one of yours: listed among your Public IPs, drawn from the same public IPv4 quota your customer-facing addresses use, and billed as a Public IP. There is no shared-outbound-address option — a VPC egresses through its own gateway, never through an address held in common with other tenants. The converter writes no gateway: connectivity is a stated choice, so that a VPC cannot acquire an outbound path — or a billable public address — because a field was omitted.
The same gateway can be created outside Terraform with fm network gateway create.
It adopts rather than duplicates. If the VPC already has a gateway — one the platform attached implicitly because a public IP was associated with an instance in it, or one created outside Terraform later — import that one rather than creating a second; the platform refuses the second with GATEWAY_EXISTS. The resource is one-to-one with its VPC, so the VPC's own id is the import id:
terraform import frostmoln_gateway.main <vpc-id>
azurerm_subnet_network_security_group_association
Azure applies a network security group to every interface in a subnet; frostmoln_instance carries security_groups per instance. Apply the converted security group to each instance explicitly: on the platform's default-drop model an instance left without its rules is unreachable (unsecured by omission / no ingress), not wide-open — wiring it restores the intended access.
data.azurerm_subscription
An Azure subscription is a billing and isolation container holding resource groups. Frostmoln has two levels, not three: the organization is the customer, and a tenant is an isolation boundary inside it - there is nothing between them and no resource-group layer below. The provider operates in exactly ONE tenant (tenant_id, or FROSTMOLN_TENANT_ID, defaulting to the account's default tenant); to span tenants, declare a second provider with an alias. Nothing looks the current tenant up at plan time, so replace data.azurerm_subscription.current.subscription_id and .tenant_id with the variable you pass to the provider. display_name has no equivalent that configuration can read.
azurerm_user_assigned_identity
The AKS pattern - a user-assigned identity federated to a service account, then granted access by role assignments - maps to frostmoln_workload_identity_binding (it binds a namespace and service account in a Frostmoln-managed Kubernetes cluster, exchanging the projected ServiceAccount token for a short-lived platform token) granted by a frostmoln_iam_policy attached to that workload_identity. The policy is the counterpart of the role assignments and the preferred grant; the binding's own flat scopes list is OPTIONAL and coarser. There is no identity OBJECT to create first, so this resource disappears rather than converting. Not every identity earns a binding, though, so sort them by purpose first: one that exists only for a cluster add-on the platform installs itself becomes an entry in frostmoln_kubernetes_cluster.addons, chosen at cluster create and still needing its own API-key Secret; one that only fed Azure Communication Services has no counterpart; one a managed database trusted for Entra/AD authentication has none either, since Frostmoln databases authenticate with a username and password a binding can fetch but not replace; and an observability pod writing to blob storage needs a frostmoln_s3_credential rather than a binding, because it speaks S3. The worked breakdown is under azurerm_federated_identity_credential. Outside a Frostmoln-managed cluster there is no federation: a workload elsewhere needs a frostmoln_api_key held as a secret.
azurerm_virtual_network_gateway
Terminate VPNs on a self-managed instance (frostmoln_instance) inside your VPC if you need hybrid connectivity.
azurerm_virtual_network_peering
Design connectivity within a single VPC, or route between services over their public IPs.
Trademarks. Frostmoln is an independent cloud provider and a competing alternative to Amazon Web Services, Microsoft Azure, Google Cloud, and Oracle Cloud. Frostmoln is not affiliated with, endorsed by, or sponsored by any of them. AWS, Azure, Google Cloud, and Oracle Cloud are trademarks of their respective owners; all references are nominative and for interoperability only.