Open PavelPikat opened 1 year ago
@PavelPikat, thanks for opening this issue. That said, even when I expose this new data source
I do not believe it will solve the issue you are running up against unfortunately. The reason being is that the azurerm_cdn_frontdoor_custom_domain
still needs to be "associated" with the azurerm_cdn_frontdoor_route
via the routes cdn_frontdoor_custom_domain_ids
field. Meaning that the custom domain resource needs to be defined and "associated" with the route in the same configuration file as the route. The azurerm_cdn_frontdoor_custom_domain_association
resource is named, somewhat poorly, as it is really a disassociation
resource rather than an association
resource. The azurerm_cdn_frontdoor_custom_domain_association
sole purpose within the provider is to allow custom domain(s) to be "disassociated" with a route prior to the route being deleted to avoid a service side error that would be raised if the custom domain was still "associated" with the route.
The behavior you are requesting/expecting from the azurerm_cdn_frontdoor_custom_domain_association
resource (in your Potential Terraform Configuration example above) is not what that resource actually does. I am currently trying to implement that exact behavior in the new azurerm_cdn_frontdoor_rule_sets_association
PR #20859 due to a similar issue being raised about the rule set resource (issue #20744).
In order to get your Potential Terraform Configuration example to work, the azurerm_cdn_frontdoor_custom_domain_association
resource would have to "own" the routes cdn_frontdoor_custom_domain_ids
field, which it currently does not.
I have written a test case that follows the Potential Terraform Configuration example and it still will not work even with the azurerm_cdn_frontdoor_route
data source being exposed. You will still receive the error '_the CDN FrontDoor Route(Name: "acctestRoute-XXX") is currently not associated with the CDN FrontDoor Custom Domain(Name: "acctest-contoso-XXX"). Please remove the CDN FrontDoor Route from your 'cdn_frontdoor_custom_domainassociation' configuration block' due to the issues I have already mentioned above.
@WodansSon, thank you for looking into it so quickly. I reviewed a potential workaround via usage of azcli provider, but quickly found that az afd route update required a list of IDs of all custom domains, like you mention. This makes my use case difficult to achieve.
Speaking of the use case, let me expand on it and describe our setup:
We have a centrally placed Platform Engineering team that provides managed services to the rest of the dev organization within a company. That team manages global resources like AKS clusters and Front Door profile. AFD routes are pre-configured and hooked up with AKS via Terraform config in a Git repo that this team owns.
The intention here is that every dev & product team can consume these services from their own product-specific Terraform configurations and add their own custom domains for each app to the existing AFD profile, endpoint and route. Since there is a dedicated azurerm_cdn_frontdoor_custom_domain
, we thought it would be possible to create custom domains from Terraform configurations separate to the one with AFD route. But unfortunaly it seems to be Azrure API design limitation.
Just for the record, a couple of potential workarounds that I can think of (and reject):
I wonder if others have a similar use case with centrally placed AFD and custom domains managed somewhere else and how they solve the limitation with Azure API. 🤔
Regarding Azure API design, I wish that Front Door routes/custom domain association was structured in a way that one can POST/DELETE domain one by one, instead of PUT entire route with a list of all custom domains.
Imagine if it worked that way with Storage Accounts - in order to add a blob container, one must pass IDs of all other containers already added to the account :smirk:
@WodansSon Actually, came to think about azurerm_cdn_frontdoor_custom_domain_association
and Azure API. Would it not make sense for the azurerm_cdn_frontdoor_custom_domain_association
resource to satisfy Azure API's requirement of knowing a full list of custom domain IDs attached to the route by invoking a couple of internal requests under the hood, something like:
azurerm_cdn_frontdoor_custom_domain_association
resource with 11th domain referencing the route IDazurerm
provider does an internal lookup against route ID to get a list of all existing (10) custom domainsazurerm_cdn_frontdoor_custom_domain_association
, then the provider does another internal lookup to fetch the full list of 11 domains, remove the one user wants to delete, then update the route with 10 domains@PavelPikat, that is kind of sort of what I have it doing now, but I still have it taking a list of custom domains and from reading your use case is sounds like you want it to be an iterative addition to the route instead of monolithic list of custom domains. The issue with that design is that no one resource "owns" the list of custom domains on the route at that point and if you pass me one custom domain I don't know if you want me to add it to the existing list of custom domains or replace the list of existing custom domains with this one new one that you just passed in. Plus, once the custom domain is added, this would cause a diff in all the other association resources as at that point the other configuration files would be out of sync and the next time they run their terraform config it would want to remove the custom domain you just added. This is a complex problem, which on the surface sounds like it would be a simple fix, but unfortunately it's not. Let me think on it a bit and I will get back with you. 🚀
@PavelPikat, I have given it some thought and I don't see a way around this natively within Terraform for these two reasons.
That said, the only thing I could come up with that would workaround your issue is if you kicked off some other custom tool, after running a terraform -refresh
command to update the values in the data source, that you or one of your devs can write that would take a custom domain
ID as input, if the ID was not passed in it would just skip the function that adds the custom domain
to the routes
custom domain association
list. It would then fetch the list of custom domains
from the azurerm_cdn_frontdoor_route
data source which I have exposed with this PR, or call directly into Azure to get the values from the route
itself. It could then check the list to see if the passed in custom domain
ID was already it the routes
custom domain
list or not. If not the tool would then add it to the custom domain
list and then write that back to the configuration files azurerm_cdn_frontdoor_custom_domain_association
cdn_frontdoor_custom_domain_ids
field. That way, you can still have your one off dev/product team control their own custom domain
(s) creation/destruction. Keep in mind that this tool would have to be run prior to terraform being run every single time by every single team else you would end up deleting a custom domain
association in the route
without knowing that some other team had created another custom domain
without your knowledge. It's a little hacky, but I do believe it would work and solve your current issue.
Another way of doing this would be to set up a small CosmosDB
or an Excel Spreadsheet where all the devs/teams would add and remove their own custom domain
IDs to it. Then all the tool would have to do is call into that DB/Spreadsheet and pull the latest list of Custom Domain IDs from there and insert them into the azurerm_cdn_frontdoor_custom_domain_association
cdn_frontdoor_custom_domain_ids
field.
I believe that the safest way to do this one would be to do it in two runs, the first run is where the dev/team would actually create the Custom Domain
, so it would exist in Azure. Then they would update the list of custom domain
IDs it the DB/Spreadsheet, adding their new one to the list and then run Terraform again to associate the new custom domain
with the route
via the azurerm_cdn_frontdoor_custom_domain_association
resource.
Creating a dedicate AFD route per app would hit the AFD limit of 100 routes per profile instantly (we have 4000 apps).
Where did you find documented a 100 routes per Azure Front Door profile limit ?
From the docs: https://learn.microsoft.com/en-us/azure/frontdoor/front-door-routing-limits
Each Front Door profile has a composite route limit.
The composite route metric for each Front Door profile can't exceed 5000.
If you are serving only HTTPS and each route only has 1 path your 4000 apps will fit into the 5000 composite route limit.
Please let me know if I missed anything. Thanks
I found the documented 100 routes limit: https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-front-door-standard-and-premium-tier-service-limits
It is actually 100 in AFD Standard tier and 200 in Premium Tier
@WodansSon, @zioproto appreciate your quick response guys. Let me take these findings and discuss them with my team internally and see what other potential workarounds we can come up with, either with Terraform or outside of it. I'll let you know by the end of this week
@reganlives take a look at this
Hi we have been experiencing some issues with thsi also and have a potentiaal work around but terraform (via Azure Devops) gives a pretty useless error
error with names redacted:
Error: creating Front Door Custom Domain Association: (Association Name "****-***-afd" / Profile Name "**-****-***" / Resource Group "rg-****-***-uicq"): the CDN FrontDoor Route(Name: "****-**") is currently not associated with the CDN FrontDoor Custom Domain(Name: "****-***-***"). Please remove the CDN FrontDoor Route from your 'cdn_frontdoor_custom_domain_association' configuration block
│
│ with module.azurerm_front_door.azurerm_cdn_frontdoor_custom_domain_association.***["****-***-association"],
│ on ../../../../terraform-azurerm-frontdoor/modern.tf line 575, in resource "azurerm_cdn_frontdoor_custom_domain_association" "***":
│ 575: resource "azurerm_cdn_frontdoor_custom_domain_association" "***" {
the way we have attempted to get aroundd the lack of a data source for routes is to do the following
resource "azurerm_cdn_frontdoor_custom_domain_association" "cag" {
for_each = var.modern_frontdoor_custom_domain_associations # Loop over each association defined in the variable
cdn_frontdoor_custom_domain_id = data.azurerm_cdn_frontdoor_custom_domain.frontdoor_association_custom_domain_name[each.value.cdn_frontdoor_custom_domain_name].id # Set the custom domain ID
cdn_frontdoor_route_ids = [for route_name in each.value.cdn_frontdoor_route_names : local.route_ids_map[route_name]] # Set the route IDs
depends_on = [
azurerm_cdn_frontdoor_custom_domain.this,
azurerm_cdn_frontdoor_route.this
]
}
locals{
cdn_frontdoor_domain_association_route_ids = flatten([
for config in var.modern_frontdoor_custom_domain_associations : [
for route_name in config.cdn_frontdoor_route_names : {
route_name = route_name
route_id = format("/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Cdn/profiles/%s/afdEndpoints/%s/routes/%s", data.azurerm_client_config.this.subscription_id, var.resource_group_name, config.cdn_frontdoor_profile_name, config.cdn_frontdoor_endpoint_name, route_name)
}
]
])
route_ids_map = { for route in local.cdn_frontdoor_domain_association_route_ids : route.route_name => route.route_id }
}
}
which seems to be fine but complains about a lack of association which makes no sense as the associations exist like so
# Define the resource for Azure CDN Frontdoor Route
resource "azurerm_cdn_frontdoor_route" "this" {
# Loop over each route config defined in the variable
for_each = var.modern_front_door_route_configs
# Basic configuration
name = each.key # Name of the route
# Cache configuration
dynamic "cache" {
for_each = each.value.cache != null ? [each.value.cache] : [] # Check if cache is defined
content {
compression_enabled = cache.value.compression_enabled != null ? cache.value.compression_enabled : null # Whether compression is enabled
content_types_to_compress = cache.value.content_types_to_compress != null ? cache.value.content_types_to_compress : null # Content types to compress
query_string_caching_behavior = cache.value.query_string_caching_behavior != null ? cache.value.query_string_caching_behavior : null # Query string caching behavior
}
}
# CDN Frontdoor configuration
cdn_frontdoor_custom_domain_ids = [for domain_name in each.value.cdn_frontdoor_custom_domain_names : data.azurerm_cdn_frontdoor_custom_domain.frontdoor_custom_domain_name[domain_name].id] # IDs of the custom domains
cdn_frontdoor_endpoint_id = data.azurerm_cdn_frontdoor_endpoint.frontdoor_route_endpoint[each.key].id
cdn_frontdoor_origin_group_id = data.azurerm_cdn_frontdoor_origin_group.frontdoor_route_origin_group[each.key].id # ID of the origin group
cdn_frontdoor_origin_path = each.value.cdn_frontdoor_origin_path != null ? each.value.cdn_frontdoor_origin_path : null # Path of the origin
cdn_frontdoor_origin_ids = local.origin_ids # IDs of the origins
cdn_frontdoor_rule_set_ids = each.value.cdn_frontdoor_rule_set_ids != null ? each.value.cdn_frontdoor_rule_set_ids : null # IDs of the rule sets
enabled = each.value.enabled != null ? each.value.enabled : null # Whether the route is enabled
forwarding_protocol = each.value.forwarding_protocol != null ? each.value.forwarding_protocol : null # Forwarding protocol
https_redirect_enabled = each.value.https_redirect_enabled != null ? each.value.https_redirect_enabled : null # Whether HTTPS redirect is enabled
link_to_default_domain = each.value.link_to_default_domain != null ? each.value.link_to_default_domain : null # Whether to link to default domain
patterns_to_match = each.value.patterns_to_match != null ? each.value.patterns_to_match : null # Patterns to match
supported_protocols = each.value.supported_protocols != null ? each.value.supported_protocols : null # Supported protocols
# Add depends_on block
depends_on = [
azurerm_cdn_frontdoor_profile.this,
azurerm_cdn_frontdoor_endpoint.this,
azurerm_cdn_frontdoor_origin_group.this,
azurerm_cdn_frontdoor_origin.this,
azurerm_cdn_frontdoor_rule_set.this #,
#azurerm_cdn_frontdoor_custom_domain.this #removed to prevent circular dependency
]
}
locals {
tags = {
Application = var.application_name
ADO-Project = var.azdo_project_name
Repository = var.azdo_repo_name
Environment = var.environment_tag
Managed-By = "Terraform"
Owner = join(" ", [var.azdo_project_name, "Contributors"]) # Join project name and "Contributors" with a space
}
endpoint_name = distinct([for config in values(var.modern_front_door_route_configs) : config.cdn_frontdoor_endpoint_name])
origin_group_name = distinct([for config in values(var.modern_front_door_route_configs) : config.cdn_frontdoor_origin_group_name])
origin_ids = flatten([
for config in values(var.modern_front_door_route_configs) : [
for cdn_frontdoor_origin_name in config.cdn_frontdoor_origin_names :
join("/", [data.azurerm_cdn_frontdoor_origin_group.frontdoor_route_origin_group[cdn_frontdoor_origin_name].id, "origins", cdn_frontdoor_origin_name])
]
])
flat_route_configs = flatten([
for config_key, config in var.modern_front_door_route_configs : [
for domain in config.cdn_frontdoor_custom_domain_names : {
domain = domain
profile_name = config.cdn_frontdoor_profile_name
}
]
])
data "azurerm_cdn_frontdoor_origin_group" "frontdoor_route_origin_group" {
for_each = {
for key, config in var.modern_front_door_route_configs : key => config
if contains(local.origin_group_name, config.cdn_frontdoor_origin_group_name)
}
name = each.value.cdn_frontdoor_origin_group_name
profile_name = each.value.cdn_frontdoor_profile_name
resource_group_name = var.resource_group_name
depends_on = [
azurerm_cdn_frontdoor_origin_group.this,
azurerm_cdn_frontdoor_profile.this
]
}
data "azurerm_cdn_frontdoor_endpoint" "frontdoor_route_endpoint" {
for_each = {
for key, config in var.modern_front_door_route_configs : key => config
if contains(local.endpoint_name, config.cdn_frontdoor_endpoint_name)
}
name = each.value.cdn_frontdoor_endpoint_name
profile_name = each.value.cdn_frontdoor_profile_name
resource_group_name = var.resource_group_name
depends_on = [
azurerm_cdn_frontdoor_endpoint.this,
azurerm_cdn_frontdoor_profile.this
]
}
data "azurerm_cdn_frontdoor_custom_domain" "frontdoor_custom_domain_name" {
for_each = { for item in local.flat_route_configs : item.domain => item }
name = each.value.domain
profile_name = each.value.profile_name
resource_group_name = var.resource_group_name
depends_on = [azurerm_cdn_frontdoor_profile.this,
azurerm_cdn_frontdoor_custom_domain.this]
}
Is there an existing issue for this?
Community Note
Description
We would like to manage Azure Front Door custom domains from appliation-specific Terraform configuration and target existing AFD routes which were created by a different, central Terraform configuration.
Currently, it's not possible, because there is no data resource for azurerm_cdn_frontdoor_route. Existing Terraform example for custom domain assumes that the route is created in the same Terraform configuration:
New or Affected Resource(s)/Data Source(s)
azurerm_cdn_frontdoor_route
Potential Terraform Configuration
References
No response