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.
Provider configuration and tags
Provider block
Each provider "azurerm" block becomes a provider "frostmoln" block, and an aliased block keeps a literal alias. Features, subscription and credentials are not carried. azurerm has no provider-level default tags, so the frostmoln provider block gets no default_tags; each resource's own tags carry with the resource. The converted configuration declares frostmoln/frostmoln with version ~> 0.82 and requires frostmoln provider 0.82.0 or newer, so a private registry mirror must carry that version.
Tenant default tags
Frostmoln also has tenant default tags, a per-tenant setting of the platform. No source provider setting is tenant-scoped, and the converter never sets tenant default tags. Azure Policy assignments that add or inherit tags (for example the built-in "Inherit a tag from the resource group" policy, assigned with azurerm_resource_group_policy_assignment, azurerm_subscription_policy_assignment or azurerm_management_group_policy_assignment) are organisational controls with no converter mapping.
Tag colours
Azure tags have no colour. Frostmoln tag colours (set per organization, for display) have no source equivalent: set them after migration with frostmoln_tag_color or in the portal.
azurerm_application_gateway → frostmoln_application_gateway — partially supported
One Application Gateway becomes a family of resources: the gateway itself plus a frostmoln_appgw_listener per listener, a frostmoln_appgw_backend_pool per backend setting, a frostmoln_appgw_backend per pool address, a frostmoln_appgw_route per routing rule, a frostmoln_appgw_health_check per probe, a frostmoln_appgw_certificate per certificate, the frostmoln_appgw_config_apply without which none of it is served, and — where the source carries a waf_configuration — a frostmoln_appgw_waf_policy with its attachment and its publication, emitted in detect mode whatever the source said.
NOTHING THE GATEWAY IS TOLD IS SERVED UNTIL IT IS APPLIED. Listeners, routes, backend pools, backends, certificates and published firewall versions are authored against the gateway; the appliance keeps serving what it last acknowledged until a frostmoln_appgw_config_apply dispatches the change. A configuration that creates every listener and route and no frostmoln_appgw_config_apply plans clean, applies clean, and serves nothing. The converter therefore generates one, from the sku block — the one block Azure requires exactly once, used here as the anchor for the one-per-gateway resource rather than as a translation of the sku itself. Its triggers map is an FM-TODO: fill it with each generated listener, route, pool, backend and certificate's updated_at, so Terraform runs the apply last and re-runs it whenever one of them moves. A child left out of that map is a change the gateway is never told about.
THE SAME IS TRUE OF THE FIREWALL, TWICE OVER. A frostmoln_appgw_waf_policy is authored, not enforced: what the gateway inspects with is the last PUBLISHED version, so a policy and its rules without a frostmoln_appgw_waf_policy_publication inspect nothing at all, and a policy with no frostmoln_appgw_waf_policy_attachment is not applied anywhere. A waf_configuration block is therefore converted into all three — policy, attachment and publication — never the policy alone.
THE CONVERTED POLICY IS EMITTED IN detect MODE, WHATEVER THE SOURCE SAID, INCLUDING Prevention. Detect runs the ruleset and records what it WOULD have refused, refusing nothing. That is deliberate and it is not the safe-looking default it resembles: the converted policy inspects with the PLATFORM's managed ruleset, and your disabled_rule_group and exclusion tuning does not convert with it, so a converted gateway put straight into blocking mode would refuse legitimate traffic your Azure gateway allowed. The cost is that a Prevention-mode source applies cleanly and is NOT protected until you move it. The generated mode carries an FM-TODO saying so.
Do not lean on the publication's dry-run to catch that for you. It refuses to publish while more than max_newly_blocked requests IN ITS SAMPLE would newly be refused, and that limit defaults to 0 — but a gateway that has just been created has served nothing, so the sample is empty and the check passes vacuously. It becomes a real gate only once the gateway has seen real traffic, which is also when it is worth moving to block.
The pieces do not carry each other's identifiers. Azure joins a listener, a backend address pool and a backend setting by NAME inside one resource; Frostmoln joins them by ID across separate resources, and the converter does not resolve those name references. Every generated route therefore needs its listener_id and backend_pool_id, every generated backend its pool_id, and every generated health check its pool_id. Each is reported.
Ports move. A listener's port lives in a separate frontend_port block on Azure and on the listener itself on Frostmoln; a backend's port lives in the backend setting on Azure and on each individual backend on Frostmoln. The converter does not follow either reference, so the generated listeners and backends have no port and terraform validate refuses them until you set one. That is deliberate — a guessed port is a gateway answering on the wrong socket, or forwarding to the wrong service, with nothing to show for it.
The frontend is not optional. A frostmoln_application_gateway attaches a public address in both public_ip_mode values, so an Azure gateway with a private-only frontend_ip_configuration has no equivalent: the converted gateway is reachable from the internet. Restrict it with allowed_cidrs, denied_cidrs and geo_block_mode on each generated listener.
Backends are not reachable just because they exist. A frostmoln_appgw_backend must lie inside the gateway's own VPC range, and creating one does not open the path to it — the platform never edits your security groups. Add one frostmoln_appgw_backend_authorization per (security group, port), not one per backend.
No counterpart, checked against the pinned provider rather than assumed: frostmoln_application_gateway has no autoscaling (capacity is the flavor — see the frostmoln_appgw_flavors data source), no HTTP/2, FIPS, zone, buffering or custom-error-page setting, and no managed identity. A rewrite_rule_set has no equivalent as a set — header edits become request_headers_set, request_headers_remove and response_headers_set on a frostmoln_appgw_route and a path rewrite becomes its rewrite_path_prefix, but a condition-based rewrite does not convert. A redirect_configuration has no equivalent either: a frostmoln_appgw_route only forwards, and the one redirect the platform offers is redirect_to_https on a frostmoln_appgw_listener. ssl_policy has no cipher-suite list — a frostmoln_appgw_listener takes tls_min_version and a tls_cipher_profile of modern or intermediate.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| location | Not needed on Frostmoln | the platform is single-region; the default region applies. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Converted | Tags become the gateway's frostmoln tags (at most 32, your tenant's default tags included). The generated listeners, pools, backends, routes, certificates and firewall resources do not inherit them — each has its own tags. |
| zones | Not carried over | frostmoln_application_gateway has no availability-zone setting. |
| http2_enabled | Not carried over | frostmoln_application_gateway has no HTTP/2 setting. |
| fips_enabled | Not carried over | frostmoln_application_gateway has no FIPS setting. |
| global | Not carried over | frostmoln_application_gateway has no request- or response-buffering setting. |
| custom_error_configuration | Not carried over | frostmoln_application_gateway has no custom-error-page setting. |
| identity | Manual action required | frostmoln_application_gateway has no managed-identity setting; a certificate is uploaded to a frostmoln_appgw_certificate rather than read from an external vault. |
| private_link_configuration | Manual action required | there is no Private Link equivalent; the gateway is reached over its public address, restricted with allowed_cidrs, denied_cidrs and geo_block_mode on each frostmoln_appgw_listener. |
| gateway_ip_configuration | Manual action required | this block's subnet_id is the gateway's own subnet: set subnet_id, and the vpc_id that subnet belongs to, on the converted frostmoln_application_gateway — both are FM-TODO placeholders in the output. |
| frontend_ip_configuration | Manual action required | a frostmoln_application_gateway attaches a public address in both public_ip_mode values, so a private-only frontend has no equivalent — the converted gateway is reachable from the internet. Set public_ip_mode to allocated to draw an address from the pool, or to selected with public_ip_id for one you already hold, and restrict who may reach it with allowed_cidrs, denied_cidrs and geo_block_mode on each frostmoln_appgw_listener. |
| frontend_port | Manual action required | a frostmoln_appgw_listener carries its own port, so there is nothing for this block to convert to; copy each port onto the listener generated from the http_listener (or listener) block that names it. |
| sku | Manual action required | size is the gateway's flavor_id, which is an FM-TODO placeholder in the output; pick one from the frostmoln_appgw_flavors data source. The converter also uses this block — the one Azure requires exactly once — as the anchor for the gateway's frostmoln_appgw_config_apply. |
| autoscale_configuration | Manual action required | frostmoln_application_gateway has no autoscaling: capacity is the flavor. Pick a size from the frostmoln_appgw_flavors data source that covers the peak this block was scaling to. |
| ssl_policy | Manual action required | a frostmoln_appgw_listener takes tls_min_version (1.2 or 1.3) and tls_cipher_profile (modern or intermediate); an explicit cipher-suite list and a named predefined policy have no equivalent. Set the two attributes on each generated listener. |
| ssl_profile | Manual action required | client-certificate (mutual TLS) profiles have no equivalent; a frostmoln_appgw_listener presents a certificate and does not request one. |
| trusted_client_certificate | Manual action required | client-certificate (mutual TLS) trust has no equivalent; a frostmoln_appgw_listener does not request a client certificate. |
| trusted_root_certificate | Manual action required | a backend CA is supplied inline: set tls_ca_certificate on the frostmoln_appgw_backend_pool that re-encrypts to the backend, together with tls_server_name and tls_verify_backend. |
| url_path_map | Manual action required | path routing is per route on Frostmoln: each path_rule becomes its own frostmoln_appgw_route with path_match_type (prefix, exact or regex) and path, and the default pool becomes a catch-all route with a higher priority number. The converter does not generate them. |
| rewrite_rule_set | Manual action required | there is no rewrite-rule-set equivalent: header edits become request_headers_set, request_headers_remove and response_headers_set on a frostmoln_appgw_route, and a path rewrite becomes its rewrite_path_prefix. A rewrite that depends on a condition does not convert. |
| redirect_configuration | Manual action required | a frostmoln_appgw_route only forwards; the one redirect the platform offers is redirect_to_https on a frostmoln_appgw_listener. Any other redirect has to be served by the application behind the gateway. |
| firewall_policy_id | Manual action required | convert the azurerm_web_application_firewall_policy this points at, then attach it with a frostmoln_appgw_waf_policy_attachment and publish it with a frostmoln_appgw_waf_policy_publication. A frostmoln_appgw_waf_policy on its own is authored, not enforced: the gateway inspects with the last published version, so an unattached or unpublished policy inspects nothing while the plan looks complete. |
| force_firewall_policy_association | Not needed on Frostmoln | there is no association-mode flag; a policy applies where a frostmoln_appgw_waf_policy_attachment puts it. |
Inline sku blocks are split into frostmoln_appgw_config_apply resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| name | Manual action required | size is the gateway's flavor_id: pick one from the frostmoln_appgw_flavors data source and set it on the converted frostmoln_application_gateway, where it is an FM-TODO placeholder. |
| tier | Manual action required | there is no tier concept; a gateway always carries the Web Application Firewall, which is attached and published separately (frostmoln_appgw_waf_policy, frostmoln_appgw_waf_policy_attachment, frostmoln_appgw_waf_policy_publication). |
| capacity | Manual action required | capacity is the flavor: there is no instance count. Pick a size from the frostmoln_appgw_flavors data source that covers the peak this capacity was serving. |
Inline http_listener blocks are split into frostmoln_appgw_listener resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| protocol | Converted | |
| frontend_port_name | Manual action required | a frostmoln_appgw_listener carries its own port and the converter does not follow this name reference, so the generated listener has NO port and terraform validate refuses it until you set one. Copy the number from the frontend_port block this name points at — a guessed port is a listener bound to the wrong socket. |
| frontend_ip_configuration_name | Manual action required | there is no per-listener frontend: a frostmoln_application_gateway has one address, and it is public. Restrict who may reach this listener with allowed_cidrs, denied_cidrs and geo_block_mode. |
| host_name | Manual action required | host matching moves to the route: set host on each frostmoln_appgw_route that hangs off this listener. A listener with no host match serves every host. |
| host_names | Manual action required | host matching moves to the route, one host per frostmoln_appgw_route (host takes a single name or a wildcard such as *.example.com). A listener with no host match serves every host. |
| ssl_certificate_name | Manual action required | a certificate is selected by id: set default_certificate_id — or add it to sni_certificate_ids — to the frostmoln_appgw_certificate generated from the ssl_certificate block this name points at. |
| ssl_profile_name | Manual action required | client-certificate (mutual TLS) profiles have no equivalent; a frostmoln_appgw_listener presents a certificate and does not request one. |
| require_sni | Not carried over | there is no require_sni flag: a frostmoln_appgw_listener serves default_certificate_id when no SNI matches and picks from sni_certificate_ids when one does. |
| firewall_policy_id | Manual action required | a per-listener firewall is an overlay-scoped frostmoln_appgw_waf_policy attached with a frostmoln_appgw_waf_policy_attachment naming this listener. An overlay is compiled WITHOUT the managed ruleset and carries only your own rules, and like every policy it inspects nothing until a frostmoln_appgw_waf_policy_publication publishes it. |
| custom_error_configuration | Not carried over | frostmoln_appgw_listener has no custom-error-page setting. |
Inline listener blocks are split into frostmoln_appgw_listener resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| protocol | Converted | |
| frontend_port_name | Manual action required | a frostmoln_appgw_listener carries its own port and the converter does not follow this name reference, so the generated listener has NO port and terraform validate refuses it until you set one. Copy the number from the frontend_port block this name points at — a guessed port is a listener bound to the wrong socket. |
| frontend_ip_configuration_name | Manual action required | there is no per-listener frontend: a frostmoln_application_gateway has one address, and it is public. Restrict who may reach this listener with allowed_cidrs, denied_cidrs and geo_block_mode. |
| host_names | Manual action required | host matching moves to the route, one host per frostmoln_appgw_route (host takes a single name or a wildcard such as *.example.com). A listener with no host match serves every host. |
| ssl_certificate_name | Manual action required | a certificate is selected by id: set default_certificate_id — or add it to sni_certificate_ids — to the frostmoln_appgw_certificate generated from the ssl_certificate block this name points at. |
| ssl_profile_name | Manual action required | client-certificate (mutual TLS) profiles have no equivalent; a frostmoln_appgw_listener presents a certificate and does not request one. |
Inline backend_http_settings blocks are split into frostmoln_appgw_backend_pool resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| protocol | Converted | |
| cookie_based_affinity | Converted to session_affinity | Enabled becomes session_affinity = "cookie", Disabled becomes "none". A cookie pool also REQUIRES session_cookie_name, and Azure does not: affinity_cookie_name is optional there and defaults server-side, so a source that relied on that default converts to a pool the provider refuses until you name the cookie yourself. |
| affinity_cookie_name | Mapped to session_cookie_name | Carried over as session_cookie_name. It is optional on Azure and required on a cookie-affinity Frostmoln pool, so if the source left it out, pick a name here. |
| port | Manual action required | the port belongs to each backend on Frostmoln, not to the pool: set it on every frostmoln_appgw_backend in this pool. Those backends are generated from the backend_address_pool a request_routing_rule pairs with this setting, and terraform validate refuses a backend without a port. |
| request_timeout | Manual action required | request_timeout is in SECONDS and frostmoln_appgw_backend_pool.timeout_response_ms is in MILLISECONDS, so it is refused rather than dropped: set timeout_response_ms to this value multiplied by 1000, and set timeout_connect_ms alongside it. It is not converted for you because the platform default is far shorter than a long Azure request_timeout, and a pool that silently inherits that default returns 504 on exactly the slow requests this setting existed to allow. |
| path | Manual action required | a forwarded-path prefix belongs to the route: set rewrite_path_prefix on each frostmoln_appgw_route that forwards to this pool. |
| host_name | Manual action required | there is no per-pool Host override: set the header with request_headers_set on each frostmoln_appgw_route that forwards to this pool. On an https pool the name VERIFIED against the backend certificate is tls_server_name, which is a separate setting. |
| sni_name | Mapped to tls_server_name | |
| pick_host_name_from_backend_address | Not carried over | there is no derive-the-Host-from-the-backend setting; set the header explicitly with request_headers_set on the route. |
| probe_name | Manual action required | a health check is keyed on the pool: set pool_id on the frostmoln_appgw_health_check generated from the probe block this name points at. |
| trusted_root_certificate_names | Manual action required | a backend CA is supplied inline: set tls_ca_certificate on this pool to the PEM bundle, alongside tls_server_name and tls_verify_backend. |
| certificate_chain_validation_enabled | Manual action required | backend certificate verification is tls_verify_backend on this pool. Leave it unset to keep the platform default — an explicit false disables verification, which is why the converter never writes one for you. |
| sni_validation_enabled | Manual action required | backend certificate verification is tls_verify_backend on this pool, and the name checked against the certificate is tls_server_name; there is no separate SNI-validation switch. |
| dedicated_backend_connection_enabled | Not carried over | frostmoln_appgw_backend_pool has no connection-reuse setting. |
| connection_draining | Not carried over | frostmoln_appgw_backend_pool has no connection-draining setting. |
Inline backend blocks are split into frostmoln_appgw_backend_pool resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| protocol | Converted | |
| port | Manual action required | the port belongs to each backend on Frostmoln, not to the pool: set it on every frostmoln_appgw_backend in this pool. Those backends are generated from the backend_address_pool a routing_rule pairs with this setting, and terraform validate refuses a backend without a port. |
| timeout_in_seconds | Manual action required | timeout_in_seconds is in SECONDS and frostmoln_appgw_backend_pool.timeout_response_ms is in MILLISECONDS, so it is refused rather than dropped: set timeout_response_ms to this value multiplied by 1000, and set timeout_connect_ms alongside it. It is not converted for you because the platform default is far shorter than a long Azure timeout, and a pool that silently inherits that default returns 504 on exactly the slow requests this setting existed to allow. |
| host_name | Manual action required | there is no per-pool Host override: set the header with request_headers_set on each frostmoln_appgw_route that forwards to this pool. On an https pool the name VERIFIED against the backend certificate is tls_server_name, which is a separate setting. |
| probe_name | Manual action required | a health check is keyed on the pool: set pool_id on the frostmoln_appgw_health_check generated from the probe block this name points at. |
| trusted_root_certificate_names | Manual action required | a backend CA is supplied inline: set tls_ca_certificate on this pool to the PEM bundle, alongside tls_server_name and tls_verify_backend. |
| client_ip_preservation_enabled | Not carried over | frostmoln_appgw_backend_pool has no client-IP-preservation setting; the gateway terminates the connection and forwards. |
Inline backend_address_pool blocks are split into frostmoln_appgw_backend resources (one per element of ip_addresses):
| Block attribute | Outcome | Notes |
|---|---|---|
| ip_addresses | One resource per element, becomes address | |
| name | Manual action required | the pool identity is carried by the frostmoln_appgw_backend_pool generated from the backend setting a routing rule pairs with this address pool. Each generated frostmoln_appgw_backend still needs that pool_id and a port — terraform validate refuses a backend without one — and an address only becomes reachable once a frostmoln_appgw_backend_authorization opens the path, one per (security group, port). |
| fqdns | Manual action required | a frostmoln_appgw_backend is named by an IP address inside the gateway's own VPC range; an FQDN backend, and any address outside that range, is refused. Resolve each name to its VPC address, or put the external service behind something inside the VPC. |
Inline request_routing_rule blocks are split into frostmoln_appgw_route resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| priority | Mapped to priority | Carried over unchanged. Azure allows 1–20000 and the Frostmoln range is narrower, so a rule sitting at the top of Azure's range — a catch-all at 20000 is the common one — is refused at apply. Renumber those into a compact sequence; only the relative order matters. |
| http_listener_name | Manual action required | a route hangs off a listener by id: set listener_id to the frostmoln_appgw_listener generated from the listener block this name points at. terraform validate refuses a route without one. |
| backend_address_pool_name | Manual action required | a route forwards to a pool by id: set backend_pool_id to the frostmoln_appgw_backend_pool generated from the backend setting paired with this address pool. terraform validate refuses a route without one. |
| backend_http_settings_name | Manual action required | the frostmoln_appgw_backend_pool generated from this backend setting is the one to point backend_pool_id at — on Frostmoln the pool carries the protocol, affinity and timeouts that this block carried on Azure. |
| rule_type | Manual action required | there is no rule-type switch: a path-based rule becomes one frostmoln_appgw_route per path, each with path_match_type (prefix, exact or regex) and path. Priority is explicit and LOWER WINS — there is no longest-prefix rule — so give the specific routes low numbers and the catch-all a high one. |
| url_path_map_name | Manual action required | each path_rule in the named url_path_map becomes its own frostmoln_appgw_route with path_match_type and path, and the map's default pool becomes a catch-all route with a higher priority number. The converter does not generate them. |
| redirect_configuration_name | Manual action required | a frostmoln_appgw_route only forwards; the one redirect the platform offers is redirect_to_https on a frostmoln_appgw_listener. Any other redirect has to be served by the application behind the gateway. |
| rewrite_rule_set_name | Manual action required | header edits become request_headers_set, request_headers_remove and response_headers_set on this route, and a path rewrite becomes its rewrite_path_prefix. A rewrite that depends on a condition does not convert. |
Inline routing_rule blocks are split into frostmoln_appgw_route resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| priority | Mapped to priority | Carried over unchanged. Azure allows 1–20000 and the Frostmoln range is narrower, so a rule sitting at the top of Azure's range — a catch-all at 20000 is the common one — is refused at apply. Renumber those into a compact sequence; only the relative order matters. |
| listener_name | Manual action required | a route hangs off a listener by id: set listener_id to the frostmoln_appgw_listener generated from the listener block this name points at. terraform validate refuses a route without one. |
| backend_address_pool_name | Manual action required | a route forwards to a pool by id: set backend_pool_id to the frostmoln_appgw_backend_pool generated from the backend setting paired with this address pool. terraform validate refuses a route without one. |
| backend_name | Manual action required | the frostmoln_appgw_backend_pool generated from this backend setting is the one to point backend_pool_id at — on Frostmoln the pool carries the protocol and timeouts that this block carried on Azure. |
Inline probe blocks are split into frostmoln_appgw_health_check resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| protocol | Converted | |
| path | Mapped to path | |
| interval | Mapped to interval_seconds | |
| timeout | Mapped to timeout_seconds | |
| unhealthy_threshold | Mapped to unhealthy_threshold | |
| name | Not carried over | frostmoln_appgw_health_check has no name; a pool has at most one check and the resource is keyed on the pool. |
| host | Not carried over | frostmoln_appgw_health_check has no probe-host setting; it requests path on each backend's own address and port. |
| pick_host_name_from_backend_http_settings | Not carried over | frostmoln_appgw_health_check has no probe-host setting. |
| port | Not carried over | health checks run against each backend's own port. |
| match | Manual action required | expected_status takes one status or one range (200, or 200-299), not a list, and there is no response-body match. Set expected_status to the range that covers what this block accepted. |
| minimum_servers | Not carried over | frostmoln_appgw_health_check has no minimum-healthy-server setting. |
| proxy_protocol_header_enabled | Not carried over | frostmoln_appgw_health_check has no PROXY-protocol setting. |
Inline ssl_certificate blocks are split into frostmoln_appgw_certificate resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| data | Manual action required | a frostmoln_appgw_certificate takes a PEM chain in chain_pem (leaf first) and its key separately, not a base64 PFX, and the converter never moves key material for you. Convert the bundle to PEM and set chain_pem, then supply the key as private_key_pem_wo with private_key_pem_wo_version — private_key_pem is written to Terraform state in plaintext. terraform validate refuses a certificate without chain_pem. |
| password | Manual action required | there is no bundle passphrase: the key is supplied as PEM, and private_key_pem_wo keeps it out of the plan and out of state. |
| key_vault_secret_id | Manual action required | a certificate held in an external vault has no equivalent; upload the PEM chain and key to a frostmoln_appgw_certificate, or attach a platform-issued certificate by id. |
Inline waf_configuration blocks are split into frostmoln_appgw_waf_policy resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| firewall_mode | Manual action required | firewall_mode does NOT carry over, and a Prevention-mode source is the case this refusal exists for. The converted policy inspects with the PLATFORM's managed ruleset, and your disabled_rule_group and exclusion tuning is not converted with it — so emitting block here would put a ruleset you have never run into refusing mode against your production traffic, with none of the suppressions you built up in Azure. The converted policy is emitted in detect instead, whatever this said: it runs the ruleset and refuses nothing. Read the generated frostmoln_appgw_waf_policy's mode FM-TODO, then move to block deliberately once the publication's dry-run has shown you what would newly be refused. |
| enabled | Manual action required | there is no enable switch on a Web Application Firewall policy: a policy inspects where a frostmoln_appgw_waf_policy_attachment puts it, and only once a frostmoln_appgw_waf_policy_publication has published it. Where this block was disabled, remove the generated attachment and publication rather than leaving an enforced policy behind; where it was enabled, check that the generated mode is the one you want before the publication runs. |
| max_request_body_size_kb | Manual action required | max_request_body_size_kb is in KILOBYTES and frostmoln_appgw_waf_policy.request_body_limit_bytes is in bytes, between 4096 and 1048576 with a default of 40960. Set it deliberately: a body over the limit is refused 413 rather than passed uninspected, so this is also the largest request your application receives. |
| file_upload_limit_mb | Not carried over | there is no separate upload limit; request_body_limit_bytes bounds every request body. |
| request_body_check | Not carried over | there is no request-body-inspection switch; request_body_limit_bytes bounds what is inspected and anything larger is refused. |
| rule_set_type | Not carried over | there is one managed ruleset and it is the platform's; managed_ruleset_version is read, not written. |
| rule_set_version | Not carried over | the managed ruleset version is the platform's to migrate; managed_ruleset_version is read, not written. |
| disabled_rule_group | Manual action required | turning a managed rule off is a frostmoln_appgw_waf_rule with kind = "managedOverride", managed_secrule_id and managed_action = "disable" — one per rule, not per group. Prefer a frostmoln_appgw_waf_exclusion where the rule is only a false positive on one field: it narrows the rule instead of switching it off everywhere. Either way the change lands on the draft and inspects nothing until it is published. |
| exclusion | Manual action required | each exclusion becomes a frostmoln_appgw_waf_exclusion with variable, selector and an optional path_pattern, targeting one managed rule (target_secrule_id) or one tag (target_tag). It lands on the draft and takes effect only once a frostmoln_appgw_waf_policy_publication publishes it. |
Each split also creates a frostmoln_appgw_waf_policy_attachment resource.
Each split also creates a frostmoln_appgw_waf_policy_publication resource.
- The converter adds
flavor_idautomatically. frostmoln_application_gateway requires a flavor_id; pick a size from the frostmoln_appgw_flavors data source, which also carries each size's structural limits. - The converter adds
vpc_idautomatically. frostmoln_application_gateway requires vpc_id; set it to the converted VPC the gateway's subnet belongs to. - The converter adds
subnet_idautomatically. frostmoln_application_gateway requires subnet_id; set it to the converted subnet named by the gateway_ip_configuration block.
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 | alias records have no equivalent; use an explicit A record with IP addresses. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | alias records have no equivalent; use an explicit AAAA record with IP addresses. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | CAA record blocks cannot be aggregated into the records set; recreate them as rdata strings ("<flags> <tag> <value>") — a placeholder value could block certificate issuance. |
| ttl | Mapped to ttl | |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | alias records have no equivalent; use an explicit CNAME record with a target value. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | SRV record blocks cannot be aggregated into the records set; recreate them as rdata strings ("<priority> <weight> <port> <target>") — a placeholder value could misdirect service discovery. |
| ttl | Mapped to ttl | |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | there is no resource-group concept; resources are tenant-scoped. |
| tags | Not carried over | frostmoln_dns_record has no tags. |
- 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 | SOA record parameters are platform-managed; set the zone email instead. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| 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 | secrets are not scoped to a vault; access is granted with a frostmoln_iam_policy attached to the principal that needs the secret. |
| expiration_date | Not carried over | secret expiry has no equivalent; rotate secrets explicitly. |
| not_before_date | Not carried over | secret activation dates have no equivalent. |
| 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 the optional workload-identity cluster add-on (add it to addons, at create or later) rather than a cluster flag, so workload_identity_enabled carries nothing over. 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 | the API endpoint hostname is platform-managed. |
| dns_prefix_private_cluster | Not carried over | the API endpoint hostname is platform-managed. |
| identity | Manual action required | managed identities have no equivalent; the platform manages cluster identity. |
| service_principal | Manual action required | service principals have no equivalent; the platform manages cluster identity. |
| role_based_access_control_enabled | Not needed on Frostmoln | RBAC is always enabled and platform-managed. |
| azure_active_directory_role_based_access_control | Manual action required | Azure AD integration has no equivalent; access is governed by platform roles. |
| network_profile | Not carried over | the CNI, pod CIDR, and service CIDR are platform-managed. |
| 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 | the OIDC issuer is platform-managed. |
| workload_identity_enabled | Not carried over | Workload identity is available on managed Kubernetes as the optional workload-identity cluster add-on rather than a cluster flag, so there is nothing to set here. Add workload-identity to the frostmoln_kubernetes_cluster addons set, when the cluster is created or later (it is applied in place to a running cluster). 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 | Azure Policy add-ons have no equivalent. |
| oms_agent | Not carried over | the monitoring agent has no equivalent; use the platform's observability. |
| key_vault_secrets_provider | Manual action required | the Key Vault CSI driver has no equivalent; use the secrets service. |
| sku_tier | Not carried over | the control-plane SKU tier is platform-managed. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| node_resource_group | Not needed on Frostmoln | there is no node resource group; node infrastructure is platform-managed. |
| location | Not needed on Frostmoln | the platform is single-region; the default region applies. |
| tags | Converted | Tags become frostmoln tags (at most 32 per resource, your tenant's default tags included). They are cluster tags, never Kubernetes node labels; the initial node pool gets a one-time copy at create. |
- 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 | cluster autoscaling has no equivalent; set a fixed node_count. |
| min_count | Manual action required | cluster autoscaling has no equivalent; set a fixed node_count. |
| max_count | Manual action required | cluster autoscaling has no equivalent; set a fixed node_count. |
| vnet_subnet_id | Not carried over | the subnet is set on the cluster, not per node pool. |
| zones | Not carried over | availability zones are platform-managed. |
| node_labels | Not carried over | node labels have no equivalent yet. |
| tags | Converted | Tags become frostmoln tags on the node pool (at most 32, your tenant's default tags included) — resource tags, never Kubernetes node labels. |
| node_taints | Manual action required | node taints have no equivalent yet. |
| mode | Not carried over | system/user pool modes have no equivalent; all pools are equivalent. |
| os_type | Not carried over | the node OS is platform-managed. |
| os_disk_size_gb | Not carried over | the node disk is determined by the flavor. |
| os_disk_type | Not carried over | the node disk is determined by the flavor. |
| orchestrator_version | Not carried over | the node Kubernetes version follows the cluster version. |
| priority | Manual action required | spot node pools have no equivalent. |
| eviction_policy | Manual action required | spot eviction policies have no equivalent. |
- 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 | set the boot script via custom_data (decoded into user_data); Azure user_data has no separate equivalent. |
| network_interface_ids | Not needed on Frostmoln | the network interface is absorbed into the instance; subnet_id comes from the interface. |
| location | Converted | The platform picks the zone; set zone to a zone id to pin one. |
| zone | Not carried over | Azure availability-zone numbers do not map to platform zones; the platform picks the zone — set zone to pin one. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Converted | |
| admin_username | Not carried over | the admin account is platform-managed. |
| admin_password | Not carried over | passwords are platform-issued; use SSH keys. |
| disable_password_authentication | Not needed on Frostmoln | password authentication is platform-managed. |
| computer_name | Not carried over | the hostname is platform-managed. |
| os_disk | Not carried over | the root disk is determined by the flavor and image; add a frostmoln_volume for extra storage. |
| identity | Manual action required | managed identities have no equivalent; grant access with scoped API credentials. |
| boot_diagnostics | Not carried over | boot diagnostics are platform-managed. |
| availability_set_id | Manual action required | availability sets have no equivalent. |
| proximity_placement_group_id | Manual action required | proximity placement groups have no equivalent. |
| priority | Manual action required | spot/low-priority VMs have no equivalent; instances are always regular. |
| eviction_policy | Manual action required | spot eviction policies have no equivalent. |
| patch_mode | Not carried over | OS patching is customer-managed inside the instance. |
| secret | Manual action required | VM secret injection has no equivalent; use the secrets service. |
| additional_capabilities | Not carried over | additional capabilities have no equivalent. |
| plan | Not carried over | marketplace plans have no equivalent. |
- 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 | volume tiers are platform-defined; the default tier applies — pick a volume_type from the volume tiers catalog to change it. |
| source_resource_id | Converted to snapshot_id | verify this references a snapshot (create_option Copy); for a disk-to-disk copy, create a frostmoln_snapshot first. |
| create_option | Not needed on Frostmoln | the platform infers create semantics from snapshot_id. |
| os_type | Not carried over | the OS is determined by the instance image, not the volume. |
| zone | Converted | The platform picks the zone; set zone to a zone id to pin one. |
| disk_iops_read_write | Not carried over | IOPS are set by the volume tier. |
| disk_mbps_read_write | Not carried over | throughput is set by the volume tier. |
| disk_iops_read_only | Not carried over | IOPS are set by the volume tier. |
| disk_mbps_read_only | Not carried over | throughput is set by the volume tier. |
| tier | Not carried over | performance tiers are set by the volume tier. |
| source_uri | Manual action required | importing a disk from a blob URI has no equivalent. |
| image_reference_id | Manual action required | creating a disk from an image has no equivalent; boot instances from a frostmoln_image instead. |
| disk_encryption_set_id | Manual action required | customer-managed disk encryption keys have no equivalent; volumes are encrypted with platform-managed keys. |
| encryption_settings | Manual action required | customer-managed encryption keys have no equivalent; volumes are always encrypted at rest with platform-managed keys. |
| network_access_policy | Not carried over | disk network-access policies have no equivalent. |
| public_network_access_enabled | Not carried over | disk public-network access has no equivalent. |
| max_shares | Manual action required | shared disks (multi-attach) are not supported. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| location | Not needed on Frostmoln | the platform picks the zone. |
| 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, and the storage block's size_gb becomes storage_gb. A high_availability block is DROPPED: high availability is available for PostgreSQL only.
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 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 | Not carried over | high availability is available for PostgreSQL only; the API rejects a MySQL instance created with ha_enabled, so emitting it would produce HCL that fails at apply. |
| delegated_subnet_id | Mapped to subnet_id | |
| administrator_login | Not carried over | the admin account is platform-issued. |
| administrator_password | Not carried over | the admin password is platform-issued. |
| backup_retention_days | Not carried over | Azure's 1-35 day retention differs from the platform's 35-90 day range; set backup_retention_days manually. |
| geo_redundant_backup_enabled | Not carried over | backups are platform-managed. |
| zone | Not carried over | the platform picks the zone. |
| maintenance_window | Not carried over | maintenance windows are platform-managed. |
| private_dns_zone_id | Not carried over | private DNS integration is platform-managed. |
| customer_managed_key | Manual action required | customer-managed encryption keys have no equivalent. |
| tags | Converted | Tags become frostmoln tags (at most 32 per resource, your tenant's default tags included). |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| location | Not needed on Frostmoln | the platform is single-region; the default region applies. |
- 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 | the interface is absorbed; the instance carries its own name. |
| 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 | the interface is absorbed; the platform picks the zone. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| dns_servers | Manual action required | custom DNS servers on the interface have no equivalent; set dns_servers on the subnet instead. |
| accelerated_networking_enabled (also matches enable_accelerated_networking) | Not carried over | accelerated networking is platform-managed. |
| ip_forwarding_enabled (also matches enable_ip_forwarding) | Not carried over | Not carried over: forwarding is not a flag here, and no Azure route table converts. Write a frostmoln_vpc_route naming this interface's address as the next hop -- this is manual -- and read the cost in the VPC routes documentation first: that route also lets the interface source addresses it does not own. |
| tags | Not carried over | tags belong on the instance. |
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 (the platform adds one IPv4 and one IPv6 allow-any egress rule to every new group and 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 | the platform is single-region; the default region applies. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| 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 | multiple source prefixes cannot be one rule; create one frostmoln_security_group_rule per CIDR. |
| destination_address_prefixes | Manual action required | multiple destination prefixes cannot be one rule; create one frostmoln_security_group_rule per CIDR. |
| destination_port_ranges | Manual action required | multiple port ranges cannot be one rule; create one frostmoln_security_group_rule per range. |
| source_port_ranges | Manual action required | a frostmoln security-group rule filters the DESTINATION port only, so a source-port restriction cannot be carried. 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. |
| source_port_range | Not needed on Frostmoln | consumed by the access guard: only the unrestricted wildcard "*" converts, and a real source-port restriction blocks the whole rule. |
| priority | Not carried over | the platform does not order rules; there is no priority. |
| name | Not carried over | security-group rules have no name. |
- 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 | multiple source prefixes cannot be one rule; create one frostmoln_security_group_rule per CIDR. |
| destination_address_prefixes | Manual action required | multiple destination prefixes cannot be one rule; create one frostmoln_security_group_rule per CIDR. |
| destination_port_ranges | Manual action required | multiple port ranges cannot be one rule; create one frostmoln_security_group_rule per range. |
| source_port_ranges | Manual action required | a frostmoln security-group rule filters the DESTINATION port only, so a source-port restriction cannot be carried. 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. |
| source_port_range | Not needed on Frostmoln | consumed by the access guard: only the unrestricted wildcard "*" converts, and a real source-port restriction blocks the whole rule. |
| priority | Not carried over | the platform does not order rules; there is no priority. |
| description | Mapped to description | |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
- 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 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 | the admin account is platform-issued. |
| administrator_password | Not carried over | the admin password is platform-issued. |
| backup_retention_days | Not carried over | Azure's 7-35 day retention differs from the platform's 35-90 day range; set backup_retention_days manually. |
| geo_redundant_backup_enabled | Not carried over | backups are platform-managed. |
| storage_tier | Not carried over | storage performance tiers are platform-managed. |
| storage_type | Not carried over | storage performance tiers are platform-managed. |
| storage_iops | Not carried over | storage performance tiers are platform-managed; IOPS are not tunable. |
| storage_throughput | Not carried over | storage performance tiers are platform-managed; throughput is not tunable. |
| auto_grow_enabled | Not carried over | storage auto-grow is platform-managed. |
| zone | Not carried over | the platform picks the zone. |
| maintenance_window | Not carried over | maintenance windows are platform-managed. |
| authentication | Manual action required | authentication modes (Azure AD, password policy) have no equivalent; access is platform-managed. |
| private_dns_zone_id | Not carried over | private DNS integration is platform-managed. |
| customer_managed_key | Manual action required | customer-managed encryption keys have no equivalent. |
| tags | Converted | Tags become frostmoln tags (at most 32 per resource, your tenant's default tags included). |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| location | Not needed on Frostmoln | the platform is single-region; the default region applies. |
- 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 | allocation is platform-managed; the platform assigns the address. |
| sku | Not needed on Frostmoln | there is no SKU tier; all public IPs are equivalent. |
| sku_tier | Not needed on Frostmoln | there is no SKU tier; all public IPs are equivalent. |
| location | Not needed on Frostmoln | the platform is single-region; the default region applies. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Converted | |
| domain_name_label | Manual action required | DNS labels have no equivalent; create a frostmoln_dns_record pointing at the address. |
| ip_version | Manual action required | IPv6 addressing is not supported yet. |
| zones | Not carried over | availability zones are platform-managed. |
| public_ip_prefix_id | Manual action required | public IP prefixes have no equivalent; the platform allocates the address. |
| idle_timeout_in_minutes | Not carried over | idle timeouts are platform-managed. |
| reverse_fqdn | Manual action required | reverse DNS has no equivalent. |
| ip_tags | Not carried over | IP tags have no equivalent. |
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 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 | the Azure capacity tier does not map to a platform flavor; set flavor_id manually. |
| family | Not carried over | the Azure family (C/P) does not map to a platform flavor; set flavor_id manually. |
| sku_name | Not carried over | the Azure SKU tier does not map to a platform flavor; set flavor_id manually. |
| redis_configuration | Not carried over | set eviction_policy (from maxmemory_policy) and persistence_mode (rdb/aof) on the instance manually. |
| non_ssl_port_enabled (also matches enable_non_ssl_port) | Manual action required | the managed cache has no TLS listener — it serves the cache protocol in plaintext on port 6379 at its private address in your VPC, so a TLS-only posture cannot be reproduced and the AUTH password crosses the wire in cleartext; review your transport-security requirement before migrating. |
| minimum_tls_version | Manual action required | there is no TLS listener to set a minimum version on — the managed cache serves plaintext on port 6379. |
| patch_schedule | Not carried over | patching is platform-managed. |
| zones | Not carried over | the platform picks the zone. |
| private_static_ip_address | Manual action required | static private IPs are not supported; the platform assigns one. |
| public_network_access_enabled | Not carried over | network access is platform-managed. |
| tags | Converted | Tags become frostmoln tags (at most 32 per resource, your tenant's default tags included). |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| location | Not needed on Frostmoln | the platform is single-region; the default region applies. |
- 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 | verify this references the source managed disk (converted to a frostmoln_volume). |
| create_option | Not needed on Frostmoln | the platform infers snapshot semantics from volume_id. |
| source_uri | Manual action required | importing a snapshot from a blob URI has no equivalent. |
| storage_account_id | Manual action required | blob-backed snapshots have no equivalent. |
| disk_size_gb | Not carried over | the snapshot size is derived from the source volume. |
| incremental_enabled | Not carried over | incremental snapshots are platform-managed. |
| encryption_settings | Manual action required | customer-managed encryption keys have no equivalent; snapshots are always encrypted at rest with platform-managed keys. |
| disk_encryption_set_id (retired by the provider) | Manual action required | customer-managed encryption keys have no equivalent. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| location | Not needed on Frostmoln | the platform picks the zone. |
| 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 | there is no resource-group concept; resources are tenant-scoped. |
| location | Not needed on Frostmoln | the platform is single-region; the default region applies. |
| tags | Not carried over | frostmoln_ssh_key has no tags. |
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 | there is no storage-account container; the bucket is tenant-scoped — fold the storage account's name into the bucket name if needed for uniqueness. |
| container_access_type | Manual action required | buckets are private; a public container has no equivalent — the bucket's acl field is inert and there is no bucket policy, so re-grant access with a frostmoln_s3_credential scoped to the bucket, or hand out a presigned URL. |
| metadata | Not carried over | container metadata has no equivalent. |
| default_encryption_scope | Not needed on Frostmoln | objects are always encrypted at rest; encryption is not configurable. |
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 | resource-group scoping has no equivalent; look the subnet up by name. |
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 | there is no resource-group concept; resources are tenant-scoped. |
| service_endpoint (also matches service_endpoints) | Not carried over | service endpoints have no equivalent; the platform routes service traffic directly. |
| service_endpoint_policy_ids | Not carried over | service-endpoint policies have no equivalent. |
| delegation | Not carried over | subnet delegations have no equivalent; managed services attach to subnets directly. |
| private_endpoint_network_policies (also matches private_endpoint_network_policies_enabled) | Not carried over | private-endpoint network policies have no equivalent. |
| private_link_service_network_policies_enabled | Not carried over | private-link network policies have no equivalent. |
| default_outbound_access_enabled | Not carried over | there is no default outbound access to enable or disable, and no per-subnet switch for it: a Frostmoln VPC has NO outbound path at all unless a frostmoln_gateway is attached to the VPC (see azurerm_nat_gateway for what to add), and one attached there serves every subnet. |
- 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 | the logical unit number is platform-assigned; the device path is exported. |
| caching | Not carried over | disk caching is platform-managed. |
| write_accelerator_enabled | Not carried over | write acceleration is platform-managed. |
| create_option | Not needed on Frostmoln | attach semantics are platform-managed. |
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 | resource-group scoping has no equivalent; look the VPC up by name. |
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 control plane, because those are reached over routes that exist only while the egress path does (a managed database, cache or broker deployed INTO the VPC still answers on its private address; reaching it by name needs DNS).
| 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 | the platform is single-region; the default region applies. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Converted | |
| dns_servers | Manual action required | custom DNS servers have no equivalent at the VPC level; set dns_servers on the subnet. |
| ddos_protection_plan | Manual action required | DDoS protection plans have no equivalent; the platform protects the edge. |
| subnet | Manual action required | inline subnet blocks are not supported; recreate them as frostmoln_subnet resources. |
| bgp_community | Manual action required | BGP communities have no equivalent. |
| flow_timeout_in_minutes | Not carried over | flow timeouts are platform-managed. |
| edge_zone | Manual action required | edge zones have no equivalent. |
- The converter adds
nameautomatically. frostmoln_vpc requires a name; it is taken from the Name tag or the resource address.
azurerm_web_application_firewall_policy → frostmoln_appgw_waf_policy — partially supported
A firewall policy becomes a frostmoln_appgw_waf_policy together with the frostmoln_appgw_waf_policy_attachment that puts it somewhere and the frostmoln_appgw_waf_policy_publication that makes the gateway inspect with it; its managed-rule overrides and custom rules do not convert, and the policy is emitted in detect mode — refusing nothing — whatever the source said.
AUTHORING A POLICY IS NOT ENFORCING IT, and the gap is invisible in a plan. A frostmoln_appgw_waf_policy records what you want; what the gateway inspects with is the last PUBLISHED version. A policy whose rules are written but never published inspects nothing, and a policy that is never attached is not applied anywhere — terraform apply succeeds in both cases. That is why this conversion emits three resources rather than one: the policy, a frostmoln_appgw_waf_policy_attachment, and a frostmoln_appgw_waf_policy_publication. Keep all three. And a published version still only reaches the appliance on the gateway's next frostmoln_appgw_config_apply.
THE CONVERTED POLICY IS EMITTED IN detect MODE, WHATEVER THE SOURCE SAID, INCLUDING Prevention. Detect runs the ruleset and records what it WOULD have refused, refusing nothing. That is deliberate and it is not the safe-looking default it resembles: the converted policy inspects with the PLATFORM's managed ruleset, and your managed-rule overrides and exclusions do not convert with it, so a converted policy put straight into blocking mode would refuse legitimate traffic your Azure policy allowed. The cost is that a Prevention-mode source applies cleanly and is NOT protected until you move it. The generated mode carries an FM-TODO saying so, and mode is the one FM-TODO in this conversion that is a valid, server-accepted value — every other one fails the apply until you resolve it, and this one does not.
Do not lean on the publication's dry-run to catch that for you. It refuses to publish while more than max_newly_blocked requests IN ITS SAMPLE would newly be refused, and that limit defaults to 0 — but a gateway that has just been created has served nothing, so the sample is empty and the check passes vacuously. It becomes a real gate only once the gateway has seen real traffic, which is also when it is worth moving to block.
One combination the converter can produce and nothing else warns about: if you follow the scope FM-TODO to overlay and leave mode at detect, you have explicitly switched blocking OFF for that one listener or route while the gateway policy blocks everywhere else. An overlay's cautious setting is inherit, not detect.
The attachment and the publication are generated from the managed_rules block — the one block Azure requires exactly once, used as the anchor for these one-per-policy resources rather than as a translation of the managed rules themselves, which do not convert.
Scope is an FM-TODO because the source does not answer it. A gateway-scoped policy attaches to the whole gateway and carries the managed ruleset along with the dials that tune it — paranoia_level, anomaly_score_threshold, managed_ruleset_version. An overlay-scoped policy attaches to ONE listener or ONE route, is compiled WITHOUT the managed ruleset, and carries only your own rules; those dials do not apply to it and the server refuses them there. Where this policy was named by an application gateway's firewall_policy_id, it is gateway-scoped; where it was named by a single listener or path rule, it is an overlay, and the attachment then needs that listener_id (and route_id). An overlay defaults to mode = "inherit", which is not the cautious setting: under a blocking gateway policy an inheriting overlay blocks. Read effective_mode, never mode.
Rules do not convert. A custom rule becomes a frostmoln_appgw_waf_rule with kind = "builder" and a builder_json condition set; a managed rule turned off becomes one with kind = "managedOverride"; an exclusion becomes a frostmoln_appgw_waf_exclusion. Each lands on the policy's DRAFT, so each needs its revision fed into the publication's rule_revisions map before it is enforced. Rate-limit rules have no equivalent here — per-source rate limiting is rate_limit_rps and rate_limit_burst on a frostmoln_appgw_listener.
| Attribute | Outcome | Notes |
|---|---|---|
| name | Mapped to name | |
| location | Not needed on Frostmoln | the platform is single-region; the default region applies. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Converted | Tags become the policy's frostmoln tags (at most 32, your tenant's default tags included). |
| policy_settings | Manual action required | the settings live on the policy itself. mode does NOT carry over: the converter emits detect whatever this said, including Prevention, because your managed-rule overrides and exclusions do not convert and blocking with an untuned ruleset would refuse legitimate traffic — so a Prevention-mode policy converts to one that inspects and refuses nothing until you move it deliberately. max_request_body_size_in_kb becomes request_body_limit_bytes in BYTES, between 4096 and 1048576 with a default of 40960 — a body over it is refused 413 rather than passed uninspected, so it is also the largest request your application receives. The upload limits, the log-scrubbing rules and the JS-challenge settings have no equivalent. |
| custom_rules | Manual action required | each custom rule becomes a frostmoln_appgw_waf_rule with kind = "builder" and a builder_json condition set, which lands on the policy's DRAFT and inspects nothing until a frostmoln_appgw_waf_policy_publication publishes it — feed each rule's revision into that publication's rule_revisions. Rate-limit rules have no equivalent: per-source rate limiting is rate_limit_rps and rate_limit_burst on a frostmoln_appgw_listener. |
Inline managed_rules blocks are split into frostmoln_appgw_waf_policy_attachment resources:
| Block attribute | Outcome | Notes |
|---|---|---|
| managed_rule_set | Manual action required | there is one managed ruleset and it is the platform's — managed_ruleset_version is read, not written, and paranoia_level and anomaly_score_threshold are how it is tuned (on a gateway-scoped policy only). A rule_group_override becomes a frostmoln_appgw_waf_rule with kind = "managedOverride" and managed_action = "disable", one per rule; prefer a frostmoln_appgw_waf_exclusion where the rule is only a false positive on one field. |
| exclusion | Manual action required | each exclusion becomes a frostmoln_appgw_waf_exclusion with variable, selector and an optional path_pattern, targeting one managed rule (target_secrule_id) or one tag (target_tag). It lands on the policy's DRAFT and takes effect only once a frostmoln_appgw_waf_policy_publication publishes it. |
Each split also creates a frostmoln_appgw_waf_policy_publication resource.
- The converter adds
gateway_idautomatically. frostmoln_appgw_waf_policy requires gateway_id; set it to the converted frostmoln_application_gateway this policy protects. - The converter adds
scopeautomatically. set scope togatewaywhere an application gateway's firewall_policy_id named this policy, or tooverlaywhere a single listener or path rule did. An overlay is compiled WITHOUT the managed ruleset and the managed-ruleset dials are refused on it; changing scope later replaces the policy. - The converter adds
modeautomatically. 🔴 DETECT REFUSES NOTHING, AND THIS IS THE ONE PLACEHOLDER IN THIS CONVERSION THAT APPLIES CLEANLY. The others are visibly fake ("YOUR-APPLICATION-GATEWAY-ID") and fail until you resolve them; this is a real, server-accepted value, so a source policy in Prevention mode converts, plans, applies, attaches, publishes -- and protects nothing, with a green plan the whole way. It isdetectrather than a translation of policy_settings.mode because your managed-rule overrides and exclusions do not convert, and blocking with an untuned ruleset would refuse legitimate traffic. Watch the decision log under real traffic, add a frostmoln_appgw_waf_exclusion or a managedOverride frostmoln_appgw_waf_rule for each false positive, publish, and THEN set mode = "block".
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 | the network interface is absorbed into the instance; subnet_id comes from the interface. |
| location | Converted | The platform picks the zone; set zone to a zone id to pin one. |
| zone | Not carried over | Azure availability-zone numbers do not map to platform zones; the platform picks the zone — set zone to pin one. |
| resource_group_name | Not needed on Frostmoln | there is no resource-group concept; resources are tenant-scoped. |
| tags | Converted | |
| admin_username | Not carried over | the admin account is platform-managed. |
| admin_password | Not carried over | passwords are platform-issued; retrieve console access via the platform. |
| computer_name | Not carried over | the hostname is platform-managed. |
| os_disk | Not carried over | the root disk is determined by the flavor and image; add a frostmoln_volume for extra storage. |
| identity | Manual action required | managed identities have no equivalent; grant access with scoped API credentials. |
| boot_diagnostics | Not carried over | boot diagnostics are platform-managed. |
| availability_set_id | Manual action required | availability sets have no equivalent. |
| proximity_placement_group_id | Manual action required | proximity placement groups have no equivalent. |
| priority | Manual action required | spot/low-priority VMs have no equivalent; instances are always regular. |
| eviction_policy | Manual action required | spot eviction policies have no equivalent. |
| patch_mode | Not carried over | OS patching is customer-managed inside the instance. |
| secret | Manual action required | VM secret injection has no equivalent; use the secrets service. |
| winrm_listener | Not carried over | WinRM listeners are configured inside the instance. |
| additional_capabilities | Not carried over | additional capabilities have no equivalent. |
| plan | Not carried over | marketplace plans have no equivalent. |
- 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 the converter does not convert
| 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 | Frostmoln has a managed container registry, but this resource does not convert: a registry is enabled once per tenant and nothing about it is configurable, so an Azure registry's arguments have nothing to map onto. |
| 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 | Frostmoln has a managed webserver offer, but this resource does not convert: it serves static and PHP sites, so an App Service on another runtime has no counterpart and the rest is rewritten rather than translated. |
| 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 database 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 instance, 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 database 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 instance, 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
Add a frostmoln_container_registry to enable the tenant's registry — it takes no arguments, and the endpoint and namespace it exports are platform-assigned. Mint push/pull credentials with frostmoln_container_registry_credential, and use frostmoln_container_registry_cache where this registry was a pull-through cache of an upstream. A tenant has exactly one registry and one namespace, so several Azure registries collapse into one — tell them apart by repository name, and re-point every image reference at the new endpoint. SKU/tier, geo-replication, the admin account, retention policies and network rules have no counterpart; the platform manages storage, availability and access.
azurerm_cosmosdb_account
Re-model the data on a managed relational offer (frostmoln_postgres_instance / frostmoln_mysql_instance) or cache (frostmoln_redis_instance), 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). Three things to know about that path: 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. The cluster needs the workload-identity add-on in its addons (at create or added later) for a pod to receive the credential. 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 reach to that control plane 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
For a static or PHP site, create a frostmoln_apache_instance or a frostmoln_nginx_instance, bind the hostname with a frostmoln_webserver_domain (which carries its TLS), and ship the content as a frostmoln_webserver_deployment archive rather than through a deployment slot or a container image. An App Service running .NET, Node, Python, Java or Ruby has NO counterpart on that offer: run it on a frostmoln_instance or a frostmoln_kubernetes_cluster and expose it with a frostmoln_application_gateway. Deployment slots, autoscale rules, always-on and the app-settings/connection-string model have no equivalent either — settings travel with the deployed content, and a frostmoln_secret holds what used to be a connection string.
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 database 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 database 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 (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 reach to that control plane 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 database 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 database 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 (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 - 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 reach to that control plane 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.