frostmoln_appgw_route (Resource)
Manages a route on an Application Gateway listener: a host and path match that forwards to a backend pool.
Priority is explicit and lower wins. There is no implicit longest-prefix rule, because that would make one route's effect depend on another route's contents — adding a route would silently reorder the others while the plan showed no change. Leave priority unset and the server places the route last.
The route API has no update operation, so every attribute forces a new resource.
A route is authored, not live: it starts serving on the gateway's next configuration apply.
~> Header values are secrets, and cannot be read back. request_headers_set and response_headers_set are sensitive: their values are redacted from plan output, though they are still stored in state. The platform is withdrawing header values from its read responses; once a route is read back with only its header NAMES, the provider keeps the values you configured from state and compares the names alone. A route cannot be edited, so its values cannot change without its names or its ID changing too.
For the same reason terraform import cannot recover header values once the platform returns only names. An imported route that sets headers then plans one REPLACEMENT, which re-creates it with the values from your configuration — the provider warns when this happens rather than adopting values nobody has seen. Hold the replacement off with lifecycle { ignore_changes = [request_headers_set, response_headers_set] } until you can take it.
~> Only on an http or https listener. A tcp listener has no routes at all — routing is host, path and header matching, which needs bytes the gateway does not parse at layer 4 — so it names its one pool with backend_pool_id instead, and a route pointed at one is refused.
Authoritative scope — who owns what on this resource, declared in internal/scopedecl and machine-checked against the schema.
Enacted and reconciled — every configurable attribute not listed below: the platform applies it, and a refresh reads the truth back.
Create-immutable — action, backend_pool_id, gateway_id, host, listener_id, name, path, path_match_type, priority, request_headers_remove, request_headers_set, response_headers_set, rewrite_path_prefix: the API has no update route for a route — the server registers POST, GET and DELETE and nothing else — so any change destroys and re-creates it.
Observed, not enacted — tags_all: keys set outside Terraform — in the portal, by the fm CLI, or stamped by the platform — appear only here and are kept on every apply, never removed.
Example Usage
# Routes hang off a listener, and PRIORITY IS EXPLICIT: lower wins.
#
# There is no implicit longest-prefix rule, because that would make one route's
# effect depend on another route's contents. Give the specific routes low
# numbers and the catch-all a high one.
resource "frostmoln_appgw_route" "api" {
gateway_id = frostmoln_application_gateway.edge.id
listener_id = frostmoln_appgw_listener.https.id
name = "api"
priority = 100
host = "api.example.com"
path_match_type = "prefix"
path = "/v1"
backend_pool_id = frostmoln_appgw_backend_pool.api.id
request_headers_set = {
"X-Forwarded-Host" = "api.example.com"
}
request_headers_remove = ["X-Internal-Token"]
# Resource tags: your own metadata for search and filtering. Merged with
# the provider's default_tags; changing them updates in place.
tags = {
env = "prod"
}
}
# The catch-all, deliberately last.
resource "frostmoln_appgw_route" "spa" {
gateway_id = frostmoln_application_gateway.edge.id
listener_id = frostmoln_appgw_listener.https.id
name = "spa"
priority = 900
path_match_type = "prefix"
path = "/"
backend_pool_id = frostmoln_appgw_backend_pool.web.id
}Schema
Required
backend_pool_id(String) The backend pool to forward matching requests to.gateway_id(String) The Application Gateway this route belongs to.listener_id(String) The listener this route hangs off.name(String) The name of the route.
Optional
action(String) What the route does. Onlyforwardis available.host(String) The host to match, e.g.api.example.comor*.example.com. Omit to match any host.path(String) The path to match. Required whenpath_match_typeisregex.path_match_type(String) Howpathis matched:prefix(default),exactorregex(RE2).priority(Number) Evaluation priority; lower wins. Omit to have the server place this route last, which is the safe default — a route given priority 0 by accident would take precedence over everything already configured.request_headers_remove(List of String) Headers to strip from requests before forwarding. This is also how you remove a header: setting one to an empty string is refused. Names follow the same rule asrequest_headers_set.request_headers_set(Map of String, Sensitive) Headers to set on requests forwarded to the backend. Names are HTTP tokens and additionally may not contain#or an apostrophe, which the gateway cannot render. Values must be non-empty, at most 1024 bytes, and free of control characters. Checked at plan time. Values are secrets — this is where an upstream credential usually lives — so the map issensitive, and where the API returns only the header names the provider keeps the values from state and compares the names alone;terraform importcannot recover a value the API does not return. Stored in Terraform state in plaintext —sensitiveredacts CLI output, not the state file. See the Secrets in Terraform state guide.response_headers_set(Map of String, Sensitive) Headers to set on responses returned to the client. Same name and value rules asrequest_headers_set, checked at plan time, and handled the same way:sensitive, kept from state where the API returns only the header names, and not recoverable byterraform import. A response header is sent to every client that reaches the route, so it is no place for a credential. Stored in Terraform state in plaintext —sensitiveredacts CLI output, not the state file. See the Secrets in Terraform state guide.rewrite_path_prefix(String) Replace the matched path prefix with this before forwarding.tags(Map of String) Resource tags on the route: your own key/value metadata for search and filtering, stored by the platform only and never pushed to the appliance. Changing them updates the route in place (every other attribute replaces it): no config apply, noconfig_generationchange. At most 32 keys including the tenant's default tags, which the platform merges in at create; a key is 1-64 bytes matching^[a-zA-Z0-9]([a-zA-Z0-9._:/-]*[a-zA-Z0-9])?$, a value at most 256 bytes; keys beginningfrostmoln_orfrostmoln-(any case) are reserved. Merged with the provider'sdefault_tagson every write (a key set here wins). Holds only the keys this configuration sets; the full set is intags_all.
Read-Only
created_at(String) The creation timestamp.enabled(Boolean) Whether the route is serving.id(String) The unique identifier of the route.tags_all(Map of String) Every tag the platform holds on this resource: the provider'sdefault_tags, this resource's owntags(which win on a shared key), and any key set outside Terraform — in the portal, by the fm CLI, or stamped by the platform. Keys set outside Terraform appear only here and are kept on every apply, never removed.updated_at(String) The last update timestamp.waf_policy_id(String) The WAF policy applied to this route, if any — anoverlay-scoped policy. Read-only here: attach one withfrostmoln_appgw_waf_policy_attachment, which is where the attachment's lifecycle lives.
Import
Import is supported using the following syntax:
The terraform import command can be used, for example:
# A route is imported by its gateway, its listener and its own id.
terraform import frostmoln_appgw_route.api <gateway_id>/<listener_id>/<route_id>
# WARNING: header VALUES may not be importable. They are secrets, and once the
# platform returns only the header names, `request_headers_set` and
# `response_headers_set` arrive with their names but without their values. A
# route that sets headers then plans one REPLACE on the first apply after the
# import, which re-creates it with the values in your configuration; the
# provider warns when this happens. A route that sets no headers imports
# cleanly. To hold the replace off, add
# `lifecycle { ignore_changes = [request_headers_set, response_headers_set] }`
# until you can take it.