diff --git a/examples/cdn/frontdoor/frontdoor-cmk-byoc-custom-domain/main.tf b/examples/cdn/frontdoor/frontdoor-cmk-byoc-custom-domain/main.tf index 75e6cb3bbf9c..96263508047a 100644 --- a/examples/cdn/frontdoor/frontdoor-cmk-byoc-custom-domain/main.tf +++ b/examples/cdn/frontdoor/frontdoor-cmk-byoc-custom-domain/main.tf @@ -288,14 +288,36 @@ resource "azurerm_cdn_frontdoor_security_policy" "example" { } } +resource "azurerm_cdn_frontdoor_route" "example" { + name = "${var.prefix}-route" + cdn_frontdoor_endpoint_id = azurerm_cdn_frontdoor_endpoint.example.id + cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.example.id + cdn_frontdoor_origin_ids = [azurerm_cdn_frontdoor_origin.example.id] + enabled = true + + https_redirect_enabled = true + forwarding_protocol = "HttpsOnly" + patterns_to_match = ["/*"] + supported_protocols = ["Http", "Https"] + cdn_frontdoor_rule_set_ids = [azurerm_cdn_frontdoor_rule_set.example.id] + + cdn_frontdoor_custom_domain_ids = [azurerm_cdn_frontdoor_custom_domain.contoso.id] + link_to_default_domain = false + + cache { + compression_enabled = true + content_types_to_compress = ["text/html", "text/javascript", "text/xml"] + query_strings = ["account", "settings", "foo", "bar"] + query_string_caching_behavior = "IgnoreSpecifiedQueryStrings" + } +} + resource "azurerm_cdn_frontdoor_custom_domain" "contoso" { name = "${var.prefix}-custom-domain" cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.example.id dns_zone_id = azurerm_dns_zone.example.id host_name = join(".", ["contoso", azurerm_dns_zone.example.name]) - associate_with_cdn_frontdoor_route_id = azurerm_cdn_frontdoor_route.example.id - tls { certificate_type = "CustomerCertificate" minimum_tls_version = "TLS12" @@ -303,6 +325,11 @@ resource "azurerm_cdn_frontdoor_custom_domain" "contoso" { } } +resource "azurerm_cdn_frontdoor_custom_domain_association" "contoso" { + cdn_frontdoor_custom_domain_id = azurerm_cdn_frontdoor_custom_domain.contoso.id + cdn_frontdoor_route_ids = [azurerm_cdn_frontdoor_route.example.id] +} + resource "azurerm_dns_txt_record" "contoso" { name = join(".", ["_dnsauth", "contoso"]) zone_name = azurerm_dns_zone.example.name @@ -314,33 +341,6 @@ resource "azurerm_dns_txt_record" "contoso" { } } -resource "azurerm_cdn_frontdoor_route" "example" { - name = "${var.prefix}-route" - cdn_frontdoor_endpoint_id = azurerm_cdn_frontdoor_endpoint.example.id - cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.example.id - cdn_frontdoor_origin_ids = [azurerm_cdn_frontdoor_origin.example.id] - enabled = true - - https_redirect_enabled = true - forwarding_protocol = "HttpsOnly" - - patterns_to_match = ["/*"] - supported_protocols = ["Http", "Https"] - cdn_frontdoor_rule_set_ids = [azurerm_cdn_frontdoor_rule_set.example.id] - - cache { - compression_enabled = true - content_types_to_compress = ["text/html", "text/javascript", "text/xml"] - query_strings = ["account", "settings", "foo", "bar"] - query_string_caching_behavior = "IgnoreSpecifiedQueryStrings" - } -} - -resource "azurerm_cdn_frontdoor_route_disable_link_to_default_domain" "example" { - cdn_frontdoor_route_id = azurerm_cdn_frontdoor_route.example.id - cdn_frontdoor_custom_domain_ids = [azurerm_cdn_frontdoor_custom_domain.contoso.id] -} - resource "azurerm_dns_cname_record" "contoso" { depends_on = [azurerm_cdn_frontdoor_route.example, azurerm_cdn_frontdoor_security_policy.example] diff --git a/examples/cdn/frontdoor/frontdoor-managed-ssl-certificate-with-multiple-custom-domains/main.tf b/examples/cdn/frontdoor/frontdoor-managed-ssl-certificate-with-multiple-custom-domains/main.tf index 409bb781850d..2f4142ebe9b1 100644 --- a/examples/cdn/frontdoor/frontdoor-managed-ssl-certificate-with-multiple-custom-domains/main.tf +++ b/examples/cdn/frontdoor/frontdoor-managed-ssl-certificate-with-multiple-custom-domains/main.tf @@ -223,6 +223,9 @@ resource "azurerm_cdn_frontdoor_route" "example" { supported_protocols = ["Http", "Https"] cdn_frontdoor_rule_set_ids = [azurerm_cdn_frontdoor_rule_set.example.id] + cdn_frontdoor_custom_domain_ids = [azurerm_cdn_frontdoor_custom_domain.contoso.id, azurerm_cdn_frontdoor_custom_domain.fabrikam.id] + link_to_default_domain = false + cache { compression_enabled = true content_types_to_compress = ["text/html", "text/javascript", "text/xml"] @@ -231,19 +234,12 @@ resource "azurerm_cdn_frontdoor_route" "example" { } } -resource "azurerm_cdn_frontdoor_route_disable_link_to_default_domain" "example" { - cdn_frontdoor_route_id = azurerm_cdn_frontdoor_route.example.id - cdn_frontdoor_custom_domain_ids = [azurerm_cdn_frontdoor_custom_domain.contoso.id, azurerm_cdn_frontdoor_custom_domain.fabrikam.id] -} - resource "azurerm_cdn_frontdoor_custom_domain" "contoso" { name = "${var.prefix}-contoso-custom-domain" cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.example.id dns_zone_id = azurerm_dns_zone.example.id host_name = join(".", ["contoso", azurerm_dns_zone.example.name]) - associate_with_cdn_frontdoor_route_id = azurerm_cdn_frontdoor_route.example.id - tls { certificate_type = "ManagedCertificate" minimum_tls_version = "TLS12" @@ -256,14 +252,22 @@ resource "azurerm_cdn_frontdoor_custom_domain" "fabrikam" { dns_zone_id = azurerm_dns_zone.example.id host_name = join(".", ["fabrikam", azurerm_dns_zone.example.name]) - associate_with_cdn_frontdoor_route_id = azurerm_cdn_frontdoor_route.example.id - tls { certificate_type = "ManagedCertificate" minimum_tls_version = "TLS12" } } +resource "azurerm_cdn_frontdoor_custom_domain_association" "contoso" { + cdn_frontdoor_custom_domain_id = azurerm_cdn_frontdoor_custom_domain.contoso.id + cdn_frontdoor_route_ids = [azurerm_cdn_frontdoor_route.example.id] +} + +resource "azurerm_cdn_frontdoor_custom_domain_association" "fabrikam" { + cdn_frontdoor_custom_domain_id = azurerm_cdn_frontdoor_custom_domain.fabrikam.id + cdn_frontdoor_route_ids = [azurerm_cdn_frontdoor_route.example.id] +} + resource "azurerm_dns_txt_record" "contoso" { name = join(".", ["_dnsauth", "contoso"]) zone_name = azurerm_dns_zone.example.name diff --git a/internal/services/cdn/cdn_frontdoor_custom_domain_association_import.go b/internal/services/cdn/cdn_frontdoor_custom_domain_association_import.go new file mode 100644 index 000000000000..70f51b6dda4c --- /dev/null +++ b/internal/services/cdn/cdn_frontdoor_custom_domain_association_import.go @@ -0,0 +1,31 @@ +package cdn + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" +) + +func importCdnFrontDoorCustomDomainAssociation() pluginsdk.ImporterFunc { + return func(ctx context.Context, d *pluginsdk.ResourceData, meta interface{}) (data []*pluginsdk.ResourceData, err error) { + id, err := parse.FrontDoorCustomDomainAssociationID(d.Id()) + if err != nil { + return []*pluginsdk.ResourceData{}, err + } + + client := meta.(*clients.Client).Cdn.FrontDoorCustomDomainsClient + resp, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.AssociationName) + if err != nil { + return []*pluginsdk.ResourceData{}, fmt.Errorf("retrieving %s: %+v", id, err) + } + + if resp.AFDDomainProperties == nil { + return []*pluginsdk.ResourceData{}, fmt.Errorf("retrieving %s: `AFDDomainProperties` was nil", id) + } + + return []*pluginsdk.ResourceData{d}, nil + } +} diff --git a/internal/services/cdn/cdn_frontdoor_custom_domain_association_resource.go b/internal/services/cdn/cdn_frontdoor_custom_domain_association_resource.go new file mode 100644 index 000000000000..0541f326c61c --- /dev/null +++ b/internal/services/cdn/cdn_frontdoor_custom_domain_association_resource.go @@ -0,0 +1,237 @@ +package cdn + +import ( + "fmt" + "log" + "time" + + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +var cdnFrontDoorCustomDomainResourceName = "azurerm_cdn_frontdoor_custom_domain" +var cdnFrontDoorRouteResourceName = "azurerm_cdn_frontdoor_route" + +func resourceCdnFrontDoorCustomDomainAssociation() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceCdnFrontDoorCustomDomainAssociationCreate, + Read: resourceCdnFrontDoorCustomDomainAssociationRead, + Update: resourceCdnFrontDoorCustomDomainAssociationUpdate, + Delete: resourceCdnFrontDoorCustomDomainAssociationDelete, + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Importer: pluginsdk.ImporterValidatingResourceIdThen(func(id string) error { + _, err := parse.FrontDoorCustomDomainAssociationID(id) + return err + }, importCdnFrontDoorCustomDomainAssociation()), + + Schema: map[string]*pluginsdk.Schema{ + "cdn_frontdoor_custom_domain_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.FrontDoorCustomDomainID, + }, + + "cdn_frontdoor_route_ids": { + Type: pluginsdk.TypeList, + Required: true, + + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validate.FrontDoorRouteID, + }, + }, + }, + } +} + +func resourceCdnFrontDoorCustomDomainAssociationCreate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Cdn.FrontDoorCustomDomainsClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + log.Printf("[INFO] preparing arguments for CDN FrontDoor Route <-> CDN FrontDoor Custom Domain Association creation") + + cdId, err := parse.FrontDoorCustomDomainID(d.Get("cdn_frontdoor_custom_domain_id").(string)) + if err != nil { + return err + } + + id := parse.NewFrontDoorCustomDomainAssociationID(cdId.SubscriptionId, cdId.ResourceGroup, cdId.ProfileName, cdId.CustomDomainName) + + existing, err := client.Get(ctx, cdId.ResourceGroup, cdId.ProfileName, cdId.CustomDomainName) + if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("creating %s: %s was not found", id, cdId) + } + + return fmt.Errorf("creating %s: %+v", id, err) + } + + // make sure the routes exist and are valid for this custom domain... + routes, err := validateRoutes(d, meta, cdId) + if err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + d.SetId(id.ID()) + d.Set("cdn_frontdoor_custom_domain_id", cdId.ID()) + d.Set("cdn_frontdoor_route_ids", routes) + + return resourceCdnFrontDoorCustomDomainAssociationRead(d, meta) +} + +func resourceCdnFrontDoorCustomDomainAssociationRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Cdn.FrontDoorCustomDomainsClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.FrontDoorCustomDomainAssociationID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.AssociationName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return nil +} + +func resourceCdnFrontDoorCustomDomainAssociationUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).Cdn.FrontDoorCustomDomainsClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + if d.HasChange("cdn_frontdoor_route_ids") { + cdId, err := parse.FrontDoorCustomDomainID(d.Get("cdn_frontdoor_custom_domain_id").(string)) + if err != nil { + return err + } + + id := parse.NewFrontDoorCustomDomainAssociationID(cdId.SubscriptionId, cdId.ResourceGroup, cdId.ProfileName, cdId.CustomDomainName) + + existing, err := client.Get(ctx, cdId.ResourceGroup, cdId.ProfileName, cdId.CustomDomainName) + if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("updating %s: %s was not found", id, cdId) + } + + return fmt.Errorf("updating %s: %+v", id, err) + } + + // make sure the routes exist and are valid for this custom domain... + routes, err := validateRoutes(d, meta, cdId) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + d.Set("cdn_frontdoor_route_ids", routes) + } + + return resourceCdnFrontDoorCustomDomainAssociationRead(d, meta) +} + +func resourceCdnFrontDoorCustomDomainAssociationDelete(d *pluginsdk.ResourceData, meta interface{}) error { + // since you are deleting the resource you cannot grab the value from the config + // because it will be empty, you have to get it from the states old value... + oCdId, _ := d.GetChange("cdn_frontdoor_custom_domain_id") + + cdId, err := parse.FrontDoorCustomDomainID(oCdId.(string)) + if err != nil { + return err + } + + id, err := parse.FrontDoorCustomDomainAssociationID(d.Id()) + if err != nil { + return err + } + + oRids, _ := d.GetChange("cdn_frontdoor_route_ids") + oR := oRids.([]interface{}) + + v, _, err := expandRoutes(oR) + if err != nil { + return err + } + + if len(*v) != 0 { + if err := removeCustomDomainAssociationFromRoutes(d, meta, v, cdId); err != nil { + return fmt.Errorf("deleting %s: %+v", id, err) + } + } + + d.SetId("") + + return nil +} + +func validateRoutes(d *pluginsdk.ResourceData, meta interface{}, id *parse.FrontDoorCustomDomainId) ([]interface{}, error) { + out := make([]interface{}, 0) + o, n := d.GetChange("cdn_frontdoor_route_ids") + oRoutes := o.([]interface{}) + nRoutes := n.([]interface{}) + + if len(nRoutes) == 0 || nRoutes == nil || id == nil { + return out, nil + } + + oIds, _, err := expandRoutes(oRoutes) + if err != nil { + return out, err + } + + nIds, result, err := expandRoutes(nRoutes) + if err != nil { + return out, err + } + + // validate the new routes... + if len(*nIds) != 0 { + for _, v := range *nIds { + // Make sure the route exists and get the routes custom domain association list... + associations, _, err := getRouteProperties(d, meta, &v, "cdn_frontdoor_custom_domain_association") + if err != nil { + return out, err + } + + // Make sure the custom domain is in the routes association list + if len(associations) == 0 || !sliceContainsString(associations, id.ID()) { + return out, fmt.Errorf("the CDN FrontDoor Route(Name: %q) is currently not associated with the CDN FrontDoor Custom Domain(Name: %q). Please remove the CDN FrontDoor Route from your 'cdn_frontdoor_custom_domain_association' configuration block", v.RouteName, id.CustomDomainName) + } + } + + if err := validateCustomDomainRoutes(nIds, id); err != nil { + return out, err + } + } + + if len(oRoutes) != 0 { + // now get the delta between the old and the new list, if any custom domains were removed from + // the list we need to remove the custom domain association from those routes... + if delta, _ := routeDelta(oIds, nIds); len(*delta) != 0 { + if err = removeCustomDomainAssociationFromRoutes(d, meta, delta, id); err != nil { + return out, err + } + } + } + + return result, nil +} diff --git a/internal/services/cdn/cdn_frontdoor_custom_domain_association_resource_test.go b/internal/services/cdn/cdn_frontdoor_custom_domain_association_resource_test.go new file mode 100644 index 000000000000..510527eeaa57 --- /dev/null +++ b/internal/services/cdn/cdn_frontdoor_custom_domain_association_resource_test.go @@ -0,0 +1,242 @@ +package cdn_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" + "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type CdnFrontDoorCustomDomainAssociationResource struct { +} + +// NOTE: There isn't a complete test case because the basic and the +// update together equals what the complete test case would be... +func TestAccCdnFrontDoorCustomDomainAssociation_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_custom_domain_association", "test") + r := CdnFrontDoorCustomDomainAssociationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + }) +} + +// NOTE: the 'requiresImport' test is not possible on this resource + +func TestAccCdnFrontDoorCustomDomainAssociation_removeAssociation(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_custom_domain_association", "test") + r := CdnFrontDoorCustomDomainAssociationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.remove(data), + Check: acceptance.ComposeTestCheckFunc(), + ExpectNonEmptyPlan: true, // since deleting this resource actually removes the linked custom domain from the route resource(s) + }, + }) +} + +func TestAccCdnFrontDoorCustomDomainAssociation_removeAssociations(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_custom_domain_association", "test") + r := CdnFrontDoorCustomDomainAssociationResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + { + Config: r.remove(data), + Check: acceptance.ComposeTestCheckFunc(), + ExpectNonEmptyPlan: true, // since deleting this resource actually removes the linked custom domain from the route resource(s) + }, + }) +} + +func (r CdnFrontDoorCustomDomainAssociationResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.FrontDoorCustomDomainAssociationID(state.ID) + if err != nil { + return nil, err + } + + client := clients.Cdn.FrontDoorCustomDomainsClient + resp, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.AssociationName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return utils.Bool(false), nil + } + return nil, fmt.Errorf("retrieving %s: %+v", id, err) + } + + return utils.Bool(true), nil +} + +func (r CdnFrontDoorCustomDomainAssociationResource) basic(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_cdn_frontdoor_custom_domain_association" "test" { + cdn_frontdoor_custom_domain_id = azurerm_cdn_frontdoor_custom_domain.contoso.id + cdn_frontdoor_route_ids = [azurerm_cdn_frontdoor_route.contoso.id] +} +`, template) +} + +func (r CdnFrontDoorCustomDomainAssociationResource) update(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_cdn_frontdoor_route" "fabrikam" { + name = "acctest-fabrikam-%[2]d" + cdn_frontdoor_endpoint_id = azurerm_cdn_frontdoor_endpoint.test.id + cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.test.id + cdn_frontdoor_origin_ids = [azurerm_cdn_frontdoor_origin.test.id] + enabled = true + + https_redirect_enabled = true + forwarding_protocol = "HttpsOnly" + patterns_to_match = ["/sub-%[3]s"] + supported_protocols = ["Http", "Https"] + + cdn_frontdoor_custom_domain_ids = [azurerm_cdn_frontdoor_custom_domain.contoso.id] + link_to_default_domain = true + + cache { + compression_enabled = true + content_types_to_compress = ["text/html", "text/javascript", "text/xml"] + query_strings = ["account", "settings", "foo", "bar"] + query_string_caching_behavior = "IgnoreSpecifiedQueryStrings" + } +} + +resource "azurerm_cdn_frontdoor_custom_domain_association" "test" { + cdn_frontdoor_custom_domain_id = azurerm_cdn_frontdoor_custom_domain.contoso.id + cdn_frontdoor_route_ids = [azurerm_cdn_frontdoor_route.contoso.id, azurerm_cdn_frontdoor_route.fabrikam.id] +} +`, template, data.RandomInteger, data.RandomStringOfLength(10)) +} + +func (r CdnFrontDoorCustomDomainAssociationResource) remove(data acceptance.TestData) string { + template := r.template(data) + return fmt.Sprintf(` +%s +`, template) +} + +func (r CdnFrontDoorCustomDomainAssociationResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-cdn-afdx-%[1]d" + location = "%[2]s" +} + +resource "azurerm_dns_zone" "test" { + name = "acctest-dns-zone.com" + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_cdn_frontdoor_profile" "test" { + name = "acctest-profile-%[1]d" + resource_group_name = azurerm_resource_group.test.name + sku_name = "Standard_AzureFrontDoor" +} + +resource "azurerm_cdn_frontdoor_origin_group" "test" { + name = "acctest-origin-group-%[1]d" + cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id + session_affinity_enabled = true + + health_probe { + interval_in_seconds = 240 + path = "/healthProbe" + protocol = "Https" + request_type = "GET" + } + + load_balancing { + additional_latency_in_milliseconds = 0 + sample_size = 16 + successful_samples_required = 3 + } + + restore_traffic_time_to_healed_or_new_endpoint_in_minutes = 10 +} + +resource "azurerm_cdn_frontdoor_origin" "test" { + name = "acctest-origin-%[1]d" + cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.test.id + enabled = true + + certificate_name_check_enabled = false + host_name = join(".", ["%[3]s", azurerm_dns_zone.test.name]) + priority = 1 + weight = 1 +} + +resource "azurerm_cdn_frontdoor_endpoint" "test" { + name = "acctest-endpoint-%[1]d" + cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id + enabled = true +} + +resource "azurerm_cdn_frontdoor_route" "contoso" { + name = "acctest-contoso-%[1]d" + cdn_frontdoor_endpoint_id = azurerm_cdn_frontdoor_endpoint.test.id + cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.test.id + cdn_frontdoor_origin_ids = [azurerm_cdn_frontdoor_origin.test.id] + enabled = true + + https_redirect_enabled = true + forwarding_protocol = "HttpsOnly" + patterns_to_match = ["/%[3]s"] + supported_protocols = ["Http", "Https"] + + cdn_frontdoor_custom_domain_ids = [azurerm_cdn_frontdoor_custom_domain.contoso.id] + link_to_default_domain = false + + cache { + compression_enabled = true + content_types_to_compress = ["text/html", "text/javascript", "text/xml"] + query_strings = ["account", "settings", "foo", "bar"] + query_string_caching_behavior = "IgnoreSpecifiedQueryStrings" + } +} + +resource "azurerm_cdn_frontdoor_custom_domain" "contoso" { + name = "acctest-contoso-%[1]d" + cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id + dns_zone_id = azurerm_dns_zone.test.id + host_name = join(".", ["%[3]s", azurerm_dns_zone.test.name]) + + tls { + certificate_type = "ManagedCertificate" + minimum_tls_version = "TLS12" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomStringOfLength(10)) +} diff --git a/internal/services/cdn/cdn_frontdoor_custom_domain_resource.go b/internal/services/cdn/cdn_frontdoor_custom_domain_resource.go index b03d1112f8da..2a3dbd8b74ec 100644 --- a/internal/services/cdn/cdn_frontdoor_custom_domain_resource.go +++ b/internal/services/cdn/cdn_frontdoor_custom_domain_resource.go @@ -2,14 +2,12 @@ package cdn import ( "fmt" - "strings" "time" "github.com/Azure/azure-sdk-for-go/services/cdn/mgmt/2021-06-01/cdn" dnsValidate "github.com/hashicorp/go-azure-sdk/resource-manager/dns/2018-05-01/zones" "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" - "github.com/hashicorp/terraform-provider-azurerm/internal/locks" "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/validate" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -19,14 +17,14 @@ import ( ) func resourceCdnFrontDoorCustomDomain() *pluginsdk.Resource { - return &pluginsdk.Resource{ + resource := &pluginsdk.Resource{ Create: resourceCdnFrontDoorCustomDomainCreate, Read: resourceCdnFrontDoorCustomDomainRead, Update: resourceCdnFrontDoorCustomDomainUpdate, Delete: resourceCdnFrontDoorCustomDomainDelete, Timeouts: &pluginsdk.ResourceTimeout{ - // NOTE: These timeouts are extreamly long due to the manual + // NOTE: These timeouts are extremely long due to the manual // step of approving the private link if defined. Create: pluginsdk.DefaultTimeout(12 * time.Hour), Read: pluginsdk.DefaultTimeout(5 * time.Minute), @@ -35,7 +33,7 @@ func resourceCdnFrontDoorCustomDomain() *pluginsdk.Resource { }, Importer: pluginsdk.ImporterValidatingResourceId(func(id string) error { - _, err := parse.FrontdoorCustomDomainID(id) + _, err := parse.FrontDoorCustomDomainID(id) return err }), @@ -55,15 +53,6 @@ func resourceCdnFrontDoorCustomDomain() *pluginsdk.Resource { ValidateFunc: validate.FrontDoorProfileID, }, - // NOTE: I need the reference to ensure the correct destroy order - // this will also offload the task of maintaining the custom domain association - // IDs in the route resource to the custom domains themselves... - "associate_with_cdn_frontdoor_route_id": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validate.FrontDoorRouteID, - }, - "dns_zone_id": { Type: pluginsdk.TypeString, Optional: true, @@ -127,6 +116,8 @@ func resourceCdnFrontDoorCustomDomain() *pluginsdk.Resource { }, }, } + + return resource } func resourceCdnFrontDoorCustomDomainCreate(d *pluginsdk.ResourceData, meta interface{}) error { @@ -141,16 +132,6 @@ func resourceCdnFrontDoorCustomDomainCreate(d *pluginsdk.ResourceData, meta inte id := parse.NewFrontDoorCustomDomainID(profileId.SubscriptionId, profileId.ResourceGroup, profileId.ProfileName, d.Get("name").(string)) - if route := d.Get("associate_with_cdn_frontdoor_route_id").(string); route != "" { - routeId, err := parse.FrontDoorRouteID(route) - if err != nil { - return err - } - if id.ProfileName != routeId.ProfileName { - return fmt.Errorf("azurerm_cdn_frontdoor_custom_domain: the configuration is invalid, the Front Door Custom Domain(Name: %q, Profile: %q) and the Front Door Route(Name: %q, Profile: %q) must belong to the same Front Door Profile", id.CustomDomainName, id.ProfileName, routeId.RouteName, routeId.ProfileName) - } - } - existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.CustomDomainName) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { @@ -162,7 +143,6 @@ func resourceCdnFrontDoorCustomDomainCreate(d *pluginsdk.ResourceData, meta inte return tf.ImportAsExistsError("azurerm_cdn_frontdoor_custom_domain", id.ID()) } - // preValidatedDomain := d.Get("pre_validated_custom_domain_id").(string) dnsZone := d.Get("dns_zone_id").(string) tls := d.Get("tls").([]interface{}) @@ -194,30 +174,6 @@ func resourceCdnFrontDoorCustomDomainCreate(d *pluginsdk.ResourceData, meta inte d.SetId(id.ID()) - // Now that the Custom Domain has been created we need to associate the custom domain with the - // route, if that field was passed... - associateWithRoute := d.Get("associate_with_cdn_frontdoor_route_id").(string) - if associateWithRoute != "" { - // Lock the route for update... - routeId, err := parse.FrontDoorRouteID(associateWithRoute) - if err != nil { - return err - } - - locks.ByName(routeId.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(routeId.RouteName, cdnFrontDoorRouteResourceName) - - // add the association to the route... - writeFieldToState, err := addCustomDomainAssociationToRoute(d, meta, routeId, &id) - if err != nil { - return err - } - - if writeFieldToState { - d.Set("associate_with_cdn_frontdoor_route_id", associateWithRoute) - } - } - return resourceCdnFrontDoorCustomDomainRead(d, meta) } @@ -226,7 +182,7 @@ func resourceCdnFrontDoorCustomDomainRead(d *pluginsdk.ResourceData, meta interf ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) defer cancel() - id, err := parse.FrontdoorCustomDomainID(d.Id()) + id, err := parse.FrontDoorCustomDomainID(d.Id()) if err != nil { return err } @@ -237,6 +193,7 @@ func resourceCdnFrontDoorCustomDomainRead(d *pluginsdk.ResourceData, meta interf d.SetId("") return nil } + return fmt.Errorf("retrieving %s: %+v", id, err) } @@ -273,16 +230,6 @@ func resourceCdnFrontDoorCustomDomainUpdate(d *pluginsdk.ResourceData, meta inte return err } - if route := d.Get("associate_with_cdn_frontdoor_route_id").(string); route != "" { - routeId, err := parse.FrontDoorRouteID(route) - if err != nil { - return err - } - if id.ProfileName != routeId.ProfileName { - return fmt.Errorf("azurerm_cdn_frontdoor_custom_domain: the configuration is invalid, the Front Door Custom Domain(Name: %q, Profile: %q) and the Front Door Route(Name: %q, Profile: %q) must belong to the same Front Door Profile", id.CustomDomainName, id.ProfileName, routeId.RouteName, routeId.ProfileName) - } - } - props := cdn.AFDDomainUpdateParameters{ AFDDomainUpdatePropertiesParameters: &cdn.AFDDomainUpdatePropertiesParameters{}, } @@ -297,8 +244,7 @@ func resourceCdnFrontDoorCustomDomainUpdate(d *pluginsdk.ResourceData, meta inte tls := &cdn.AFDDomainHTTPSParameters{} tlsSettings := d.Get("tls").([]interface{}) v := tlsSettings[0].(map[string]interface{}) - - secret := v["cdn_frontdoor_secret_id"].(string) + secretRaw := v["cdn_frontdoor_secret_id"].(string) // NOTE: Cert type has to always be passed in the update else you will get a // "AfdDomain.TlsSettings.CertificateType' is required but it was not set" error @@ -306,17 +252,22 @@ func resourceCdnFrontDoorCustomDomainUpdate(d *pluginsdk.ResourceData, meta inte // NOTE: Secret always needs to be passed if it is defined else you will // receive a 500 Internal Server Error - if secret != "" { - tls.Secret = expandResourceReference(secret) + if secretRaw != "" { + secret, err := parse.FrontDoorSecretID(secretRaw) + if err != nil { + return err + } + + tls.Secret = expandResourceReference(secret.ID()) } if d.HasChange("tls.0.minimum_tls_version") { tls.MinimumTLSVersion = cdn.AfdMinimumTLSVersion(v["minimum_tls_version"].(string)) } - if tls.CertificateType == cdn.AfdCertificateTypeCustomerCertificate && secret == "" { + if tls.CertificateType == cdn.AfdCertificateTypeCustomerCertificate && secretRaw == "" { return fmt.Errorf("the 'cdn_frontdoor_secret_id' field must be set if the 'certificate_type' is 'CustomerCertificate'") - } else if tls.CertificateType == cdn.AfdCertificateTypeManagedCertificate && secret != "" { + } else if tls.CertificateType == cdn.AfdCertificateTypeManagedCertificate && secretRaw != "" { return fmt.Errorf("the 'cdn_frontdoor_secret_id' field is not supported if the 'certificate_type' is 'ManagedCertificate'") } @@ -332,94 +283,6 @@ func resourceCdnFrontDoorCustomDomainUpdate(d *pluginsdk.ResourceData, meta inte return fmt.Errorf("waiting for the update of %s: %+v", *id, err) } - // Now that the Custom Domain has been updated we need to ensure the referential integrity of the route resource - // and associate/unassociate the custom domain with the route, if that field was defined/removed... - if d.HasChange("associate_with_cdn_frontdoor_route_id") { - var writeFieldToState bool - - old, new := d.GetChange("associate_with_cdn_frontdoor_route_id") - oldRouteValue := old.(string) - newRouteValue := new.(string) - - // If the old value was "" and the new value is something we are adding an association (lock only new value route) - // if the old value was something and it isn't the same as the new value we are associating the custom domain with a different route (lock both new and old routes) - // if the old value was something and the new value is "" we are removing the association with the route (lock only the old value route) - // the only other possibility here is that the old and new value are not empty and are the same value, which is a no op so do nothing... - - switch { - case (oldRouteValue == "" && newRouteValue != ""): - // Add - newRouteId, err := parse.FrontDoorRouteID(newRouteValue) - if err != nil { - return err - } - - // lock the route resource for update... - locks.ByName(newRouteId.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(newRouteId.RouteName, cdnFrontDoorRouteResourceName) - - // add the association to the route... - writeFieldToState, err = addCustomDomainAssociationToRoute(d, meta, newRouteId, id) - if err != nil { - return err - } - - case (oldRouteValue != "" && newRouteValue == ""): - // Remove - oldRouteId, err := parse.FrontDoorRouteID(oldRouteValue) - if err != nil { - return err - } - - // lock the route resource for update... - locks.ByName(oldRouteId.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(oldRouteId.RouteName, cdnFrontDoorRouteResourceName) - - // remove the association from the route.. - err = removeCustomDomainAssociationFromRoute(d, meta, oldRouteId, id) - if err != nil { - return err - } - - case (oldRouteValue != "" && newRouteValue != "" && !strings.EqualFold(oldRouteValue, newRouteValue)): - // Swap - oldRouteId, err := parse.FrontDoorRouteID(oldRouteValue) - if err != nil { - return err - } - - newRouteId, err := parse.FrontDoorRouteID(newRouteValue) - if err != nil { - return err - } - - // lock the route resources for update... - locks.ByName(oldRouteId.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(oldRouteId.RouteName, cdnFrontDoorRouteResourceName) - - locks.ByName(newRouteId.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(newRouteId.RouteName, cdnFrontDoorRouteResourceName) - - // remove the association from the old route.. - err = removeCustomDomainAssociationFromRoute(d, meta, oldRouteId, id) - if err != nil { - return err - } - - // add the association to the new route... - writeFieldToState, err = addCustomDomainAssociationToRoute(d, meta, newRouteId, id) - if err != nil { - return err - } - } - - // Now that all of the routes have been processed and updated correctly - // write the value to the state file if we need too... - if writeFieldToState { - d.Set("associate_with_cdn_frontdoor_route_id", newRouteValue) - } - } - return resourceCdnFrontDoorCustomDomainRead(d, meta) } @@ -433,26 +296,6 @@ func resourceCdnFrontDoorCustomDomainDelete(d *pluginsdk.ResourceData, meta inte return err } - // NOTE: If the custom domain is still associated with a route you cannot delete it - // you must first update the route to remove the association with the custom domain... - disassociateRoute := d.Get("associate_with_cdn_frontdoor_route_id").(string) - if disassociateRoute != "" { - routeId, err := parse.FrontDoorRouteID(disassociateRoute) - if err != nil { - return err - } - - // NOTE: cdnFrontDoorRouteResourceName is defined in the "cdn_frontdoor_route_unlink_default_domain_resource.go" file - locks.ByName(routeId.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(routeId.RouteName, cdnFrontDoorRouteResourceName) - - // remove the association from the route.. - err = removeCustomDomainAssociationFromRoute(d, meta, routeId, id) - if err != nil { - return err - } - } - // delete the custom domain... future, err := client.Delete(ctx, id.ResourceGroup, id.ProfileName, id.CustomDomainName) if err != nil { @@ -467,29 +310,34 @@ func resourceCdnFrontDoorCustomDomainDelete(d *pluginsdk.ResourceData, meta inte } func expandTlsParameters(input []interface{}, isPreValidatedDomain bool) (*cdn.AFDDomainHTTPSParameters, error) { + // NOTE: With the Frontdoor service, they do not treat an empty object like an empty object + // if it is not nil they assume it is fully defined and then end up throwing errors when they + // attempt to get a value from one of the fields. if len(input) == 0 || input[0] == nil { - // NOTE: With the Frontdoor service, they do not treat an empty object like an empty object - // if it is not nil they assume it is fully defined and then end up throwing errors when they - // attempt to get a value from one of the fields. return nil, nil } v := input[0].(map[string]interface{}) certType := v["certificate_type"].(string) - secret := v["cdn_frontdoor_secret_id"].(string) + secretRaw := v["cdn_frontdoor_secret_id"].(string) minTlsVersion := v["minimum_tls_version"].(string) tls := cdn.AFDDomainHTTPSParameters{} - if tls.CertificateType == cdn.AfdCertificateTypeCustomerCertificate && secret == "" { + if tls.CertificateType == cdn.AfdCertificateTypeCustomerCertificate && secretRaw == "" { return nil, fmt.Errorf("the 'cdn_frontdoor_secret_id' field must be set if the 'certificate_type' is 'CustomerCertificate'") - } else if tls.CertificateType == cdn.AfdCertificateTypeManagedCertificate && secret != "" { + } else if tls.CertificateType == cdn.AfdCertificateTypeManagedCertificate && secretRaw != "" { return nil, fmt.Errorf("the 'cdn_frontdoor_secret_id' field is not supported if the 'certificate_type' is 'ManagedCertificate'") } - if secret != "" { - tls.Secret = expandResourceReference(secret) + if secretRaw != "" { + secret, err := parse.FrontDoorSecretID(secretRaw) + if err != nil { + return nil, err + } + + tls.Secret = expandResourceReference(secret.ID()) } // NOTE: Minimum TLS Version is required in both pre-validated and not pre-validated diff --git a/internal/services/cdn/cdn_frontdoor_custom_domain_resource_test.go b/internal/services/cdn/cdn_frontdoor_custom_domain_resource_test.go index 0839693261e6..656c95691239 100644 --- a/internal/services/cdn/cdn_frontdoor_custom_domain_resource_test.go +++ b/internal/services/cdn/cdn_frontdoor_custom_domain_resource_test.go @@ -3,7 +3,6 @@ package cdn_test import ( "context" "fmt" - "os" "testing" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" @@ -20,7 +19,6 @@ type CdnFrontDoorCustomDomainResource struct { func TestAccCdnFrontDoorCustomDomain_basic(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_custom_domain", "test") r := CdnFrontDoorCustomDomainResource{} - r.preCheck(t) data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -36,7 +34,6 @@ func TestAccCdnFrontDoorCustomDomain_basic(t *testing.T) { func TestAccCdnFrontDoorCustomDomain_requiresImport(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_custom_domain", "test") r := CdnFrontDoorCustomDomainResource{} - r.preCheck(t) data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -52,7 +49,6 @@ func TestAccCdnFrontDoorCustomDomain_requiresImport(t *testing.T) { func TestAccCdnFrontDoorCustomDomain_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_custom_domain", "test") r := CdnFrontDoorCustomDomainResource{} - r.preCheck(t) data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -75,7 +71,6 @@ func TestAccCdnFrontDoorCustomDomain_update(t *testing.T) { func TestAccCdnFrontDoorCustomDomain_complete(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_custom_domain", "test") r := CdnFrontDoorCustomDomainResource{} - r.preCheck(t) data.ResourceTest(t, r, []acceptance.TestStep{ { @@ -89,7 +84,7 @@ func TestAccCdnFrontDoorCustomDomain_complete(t *testing.T) { } func (r CdnFrontDoorCustomDomainResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { - id, err := parse.FrontdoorCustomDomainID(state.ID) + id, err := parse.FrontDoorCustomDomainID(state.ID) if err != nil { return nil, err } @@ -102,24 +97,13 @@ func (r CdnFrontDoorCustomDomainResource) Exists(ctx context.Context, clients *c } return nil, fmt.Errorf("retrieving %s: %+v", id, err) } - return utils.Bool(true), nil -} -func (r CdnFrontDoorCustomDomainResource) preCheck(t *testing.T) { - // NOTE: To test custom domain you need to have an actual real hosted domain, - // for manual testing I have purchased my own domain to verify functionality. - if v := os.Getenv("ARM_TEST_CDN_FRONT_DOOR_CUSTOM_DOMAIN_HOST"); v == "" { - t.Skipf("skipping tests `ARM_TEST_CDN_FRONT_DOOR_CUSTOM_DOMAIN_HOST` not defined, live web hosting is required for DNS naming server redirect.") - } + return utils.Bool(true), nil } func (r CdnFrontDoorCustomDomainResource) basic(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` -provider "azurerm" { - features {} -} - %s resource "azurerm_cdn_frontdoor_custom_domain" "test" { @@ -158,27 +142,17 @@ resource "azurerm_cdn_frontdoor_custom_domain" "import" { func (r CdnFrontDoorCustomDomainResource) update(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` -provider "azurerm" { - features {} -} - %s -resource "azurerm_dns_zone" "update" { - name = "acctestzonealt%[2]d.com" - resource_group_name = azurerm_resource_group.test.name -} - resource "azurerm_cdn_frontdoor_custom_domain" "test" { name = "acctestcustomdomain-%[2]d" cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id - - dns_zone_id = azurerm_dns_zone.update.id - host_name = join(".", ["%s", azurerm_dns_zone.test.name]) + dns_zone_id = azurerm_dns_zone.test.id + host_name = join(".", ["sub-%[3]s", azurerm_dns_zone.test.name]) tls { certificate_type = "ManagedCertificate" - minimum_tls_version = "TLS12" + minimum_tls_version = "TLS10" } } `, template, data.RandomInteger, data.RandomStringOfLength(8)) @@ -187,18 +161,13 @@ resource "azurerm_cdn_frontdoor_custom_domain" "test" { func (r CdnFrontDoorCustomDomainResource) complete(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` -provider "azurerm" { - features {} -} - %s resource "azurerm_cdn_frontdoor_custom_domain" "test" { name = "acctestcustomdomain-%d" cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id - - dns_zone_id = azurerm_dns_zone.test.id - host_name = join(".", ["%s", azurerm_dns_zone.test.name]) + dns_zone_id = azurerm_dns_zone.test.id + host_name = join(".", ["%s", azurerm_dns_zone.test.name]) tls { certificate_type = "ManagedCertificate" @@ -215,6 +184,10 @@ resource "azurerm_cdn_frontdoor_custom_domain" "test" { func (r CdnFrontDoorCustomDomainResource) template(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features {} +} + resource "azurerm_resource_group" "test" { name = "acctestRG-cdn-afdx-%[1]d" location = "%[2]s" diff --git a/internal/services/cdn/cdn_frontdoor_endpoint_resource.go b/internal/services/cdn/cdn_frontdoor_endpoint_resource.go index f05993cd1114..52bea3317a6c 100644 --- a/internal/services/cdn/cdn_frontdoor_endpoint_resource.go +++ b/internal/services/cdn/cdn_frontdoor_endpoint_resource.go @@ -72,7 +72,8 @@ func resourceCdnFrontDoorEndpointCreate(d *pluginsdk.ResourceData, meta interfac ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - profileId, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) + profileRaw := d.Get("cdn_frontdoor_profile_id").(string) + profileId, err := parse.FrontDoorProfileID(profileRaw) if err != nil { return err } diff --git a/internal/services/cdn/cdn_frontdoor_firewall_policy_resource.go b/internal/services/cdn/cdn_frontdoor_firewall_policy_resource.go index f31908498665..65d0400c519d 100644 --- a/internal/services/cdn/cdn_frontdoor_firewall_policy_resource.go +++ b/internal/services/cdn/cdn_frontdoor_firewall_policy_resource.go @@ -448,22 +448,19 @@ func resourceCdnFrontDoorFirewallPolicyCreate(d *pluginsdk.ResourceData, meta in name := d.Get("name").(string) resourceGroup := d.Get("resource_group_name").(string) - log.Printf("[INFO] preparing args for Cdn Frontdoor %q Firewall Policy(Resource Group: %q)", name, resourceGroup) id := parse.NewFrontDoorFirewallPolicyID(subscriptionId, resourceGroup, name) - if d.IsNewResource() { - existing, err := client.Get(ctx, id.ResourceGroup, id.FrontDoorWebApplicationFirewallPolicyName) - if err != nil { - if !utils.ResponseWasNotFound(existing.Response) { - return fmt.Errorf("checking for existing %s: %+v", id, err) - } - } - + existing, err := client.Get(ctx, id.ResourceGroup, id.FrontDoorWebApplicationFirewallPolicyName) + if err != nil { if !utils.ResponseWasNotFound(existing.Response) { - return tf.ImportAsExistsError("azurerm_cdn_frontdoor_firewall_policy", id.ID()) + return fmt.Errorf("checking for existing %s: %+v", id, err) } } + if !utils.ResponseWasNotFound(existing.Response) { + return tf.ImportAsExistsError("azurerm_cdn_frontdoor_firewall_policy", id.ID()) + } + enabled := frontdoor.PolicyEnabledStateDisabled if d.Get("enabled").(bool) { diff --git a/internal/services/cdn/cdn_frontdoor_firewall_policy_resource_test.go b/internal/services/cdn/cdn_frontdoor_firewall_policy_resource_test.go index 662635fcc8e7..177bc8a6775d 100644 --- a/internal/services/cdn/cdn_frontdoor_firewall_policy_resource_test.go +++ b/internal/services/cdn/cdn_frontdoor_firewall_policy_resource_test.go @@ -85,7 +85,7 @@ func TestAccCdnFrontDoorFirewallPolicy_complete(t *testing.T) { } func (CdnFrontDoorFirewallPolicyResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { - id, err := parse.FrontDoorFirewallPolicyIDInsensitively(state.ID) + id, err := parse.FrontDoorFirewallPolicyID(state.ID) if err != nil { return nil, err } diff --git a/internal/services/cdn/cdn_frontdoor_helpers.go b/internal/services/cdn/cdn_frontdoor_helpers.go index 59d219f42802..fcf96dd3fd35 100644 --- a/internal/services/cdn/cdn_frontdoor_helpers.go +++ b/internal/services/cdn/cdn_frontdoor_helpers.go @@ -7,6 +7,8 @@ import ( "github.com/Azure/azure-sdk-for-go/services/cdn/mgmt/2021-06-01/cdn" "github.com/Azure/azure-sdk-for-go/services/frontdoor/mgmt/2020-11-01/frontdoor" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" + "github.com/hashicorp/terraform-provider-azurerm/internal/locks" "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/azuresdkhacks" "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" @@ -14,38 +16,37 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/utils" ) -func expandEnabledBool(isEnabled bool) cdn.EnabledState { - if isEnabled { +func expandEnabledBool(input bool) cdn.EnabledState { + if input { return cdn.EnabledStateEnabled } return cdn.EnabledStateDisabled } -func expandEnabledBoolToRouteHttpsRedirect(isEnabled bool) cdn.HTTPSRedirect { - if isEnabled { +func expandEnabledBoolToRouteHttpsRedirect(input bool) cdn.HTTPSRedirect { + if input { return cdn.HTTPSRedirectEnabled } return cdn.HTTPSRedirectDisabled } -// TODO: May not need these anymore... remove if the association resource tests work... -// func expandEnabledBoolToLinkToDefaultDomain(isEnabled bool) cdn.LinkToDefaultDomain { -// if isEnabled { -// return cdn.LinkToDefaultDomainEnabled -// } +func expandEnabledBoolToLinkToDefaultDomain(input bool) cdn.LinkToDefaultDomain { + if input { + return cdn.LinkToDefaultDomainEnabled + } -// return cdn.LinkToDefaultDomainDisabled -// } + return cdn.LinkToDefaultDomainDisabled +} -// func flattenLinkToDefaultDomainToBool(linkToDefaultDomain cdn.LinkToDefaultDomain) bool { -// if len(linkToDefaultDomain) == 0 { -// return false -// } +func flattenLinkToDefaultDomainToBool(input cdn.LinkToDefaultDomain) bool { + if len(input) == 0 { + return false + } -// return linkToDefaultDomain == cdn.LinkToDefaultDomainEnabled -// } + return input == cdn.LinkToDefaultDomainEnabled +} func expandResourceReference(input string) *cdn.ResourceReference { if len(input) == 0 { @@ -104,17 +105,24 @@ func flattenFrontDoorTags(tagMap map[string]*string) *map[string]string { func flattenTransformSlice(input *[]frontdoor.TransformType) []interface{} { result := make([]interface{}, 0) + if input == nil || len(*input) == 0 { + return result + } if input != nil { for _, item := range *input { result = append(result, string(item)) } } + return result } func flattenFrontendEndpointLinkSlice(input *[]frontdoor.FrontendEndpointLink) []interface{} { result := make([]interface{}, 0) + if input == nil || len(*input) == 0 { + return result + } if input != nil { for _, item := range *input { @@ -200,10 +208,30 @@ func expandStringSliceToCsvFormat(input []interface{}) *string { return &csv } +func expandCustomDomainActivatedResourceArray(input []interface{}) *[]cdn.ActivatedResourceReference { + results := make([]cdn.ActivatedResourceReference, 0) + + // NOTE: I have confirmed with the service team that this is required to be an explicit "nil" value, an empty + // list will not work. I had to modify the SDK to allow for nil which in the API means disassociate the custom domains. + if len(input) == 0 { + return nil + } + + for _, customDomain := range input { + if id, err := parse.FrontDoorCustomDomainID(customDomain.(string)); err == nil { + results = append(results, cdn.ActivatedResourceReference{ + ID: utils.String(id.ID()), + }) + } + } + + return &results +} + // Takes a CSV formatted string and transforms it into a Slice of strings. func flattenCsvToStringSlice(input *string) []interface{} { results := make([]interface{}, 0) - if input == nil { + if input == nil || len(*input) == 0 { return results } @@ -216,8 +244,34 @@ func flattenCsvToStringSlice(input *string) []interface{} { return results } +func flattenCustomDomainActivatedResourceArray(input *[]cdn.ActivatedResourceReference) ([]interface{}, error) { + results := make([]interface{}, 0) + if input == nil || len(*input) == 0 { + return results, nil + } + + // Normalize these values in the configuration file we know they are valid because they were set on the + // resource... if these are modified in the portal the will all be lowercased... + for _, customDomain := range *input { + if customDomain.ID == nil { + continue + } + id, err := parse.FrontDoorCustomDomainIDInsensitively(*customDomain.ID) + if err != nil { + return nil, err + } + results = append(results, id.ID()) + } + + return results, nil +} + // determines if the slice contains the value case-insensitively func sliceContainsString(input []interface{}, value string) bool { + if len(input) == 0 { + return false + } + for _, key := range input { v := key.(string) if strings.EqualFold(v, value) { @@ -228,6 +282,22 @@ func sliceContainsString(input []interface{}, value string) bool { return false } +// determines if the slice contains the value case-insensitively +func routeSliceContains(input *[]parse.FrontDoorRouteId, value string) bool { + if len(*input) == 0 || input == nil { + return false + } + + for _, key := range *input { + v := key.ID() + if strings.EqualFold(v, value) { + return true + } + } + + return false +} + // returns the slice with the value removed case-insensitively func sliceRemoveString(input []interface{}, value string) []interface{} { out := make([]interface{}, 0) @@ -246,7 +316,7 @@ func sliceRemoveString(input []interface{}, value string) []interface{} { return out } -func checkIfRouteExists(d *pluginsdk.ResourceData, meta interface{}, id *parse.FrontDoorRouteId, resourceName string) ([]interface{}, *cdn.RouteProperties, error) { +func getRouteProperties(d *pluginsdk.ResourceData, meta interface{}, id *parse.FrontDoorRouteId, resourceName string) ([]interface{}, *cdn.RouteProperties, error) { client := meta.(*clients.Client).Cdn.FrontDoorRoutesClient ctx, routeCancel := timeouts.ForUpdate(meta.(*clients.Client).StopContext, d) defer routeCancel() @@ -262,56 +332,38 @@ func checkIfRouteExists(d *pluginsdk.ResourceData, meta interface{}, id *parse.F return nil, nil, fmt.Errorf("%s: %s properties are 'nil': %+v", resourceName, *id, err) } - customDomains := flattenCdnFrontdoorRouteActivatedResourceArray(props.CustomDomains) - - return customDomains, props, nil -} - -func addCustomDomainAssociationToRoute(d *pluginsdk.ResourceData, meta interface{}, routeId *parse.FrontDoorRouteId, customDomainID *parse.FrontDoorCustomDomainId) (bool, error) { - var associationFailed bool - - // Check to see if the route still exists or not... - customDomains, props, err := checkIfRouteExists(d, meta, routeId, cdnFrontDoorCustomDomainResourceName) + customDomains, err := flattenCustomDomainActivatedResourceArray(props.CustomDomains) if err != nil { - return associationFailed, err + return nil, nil, err } - // Check to make sure the custom domain is not already associated with the route - // if it is, then there is nothing for us to do... - isAssociated := sliceContainsString(customDomains, customDomainID.ID()) - - // if it is not associated update the route to add the association... - if !isAssociated { - customDomains = append(customDomains, customDomainID.ID()) - err := updateRouteAssociations(d, meta, routeId, customDomains, props, customDomainID) - if err != nil { - return associationFailed, err - } - } - - return !associationFailed, nil + return customDomains, props, nil } -func removeCustomDomainAssociationFromRoute(d *pluginsdk.ResourceData, meta interface{}, routeId *parse.FrontDoorRouteId, customDomainID *parse.FrontDoorCustomDomainId) error { - // Check to see if the route still exists or not... - customDomains, props, err := checkIfRouteExists(d, meta, routeId, cdnFrontDoorCustomDomainResourceName) - if err != nil { - return err - } - - // Check to make sure the custom domain is still associated with the route - isAssociated := sliceContainsString(customDomains, customDomainID.ID()) - - if isAssociated { - // it is, now remove the association... - newDomains := sliceRemoveString(customDomains, customDomainID.ID()) - err := updateRouteAssociations(d, meta, routeId, newDomains, props, customDomainID) - if err != nil { - return err +func removeCustomDomainAssociationFromRoutes(d *pluginsdk.ResourceData, meta interface{}, routes *[]parse.FrontDoorRouteId, customDomainID *parse.FrontDoorCustomDomainId) error { + if len(*routes) != 0 && routes != nil { + for _, route := range *routes { + // lock the route resource for update... + locks.ByName(route.RouteName, cdnFrontDoorRouteResourceName) + defer locks.UnlockByName(route.RouteName, cdnFrontDoorRouteResourceName) + + // Check to see if the route still exists and grab its properties... + // NOTE: cdnFrontDoorRouteResourceName is defined in the "cdn_frontdoor_route_disable_link_to_default_domain_resource" file + // ignore the error because that could just mean that the route has already been deleted... + customDomains, props, err := getRouteProperties(d, meta, &route, cdnFrontDoorCustomDomainResourceName) + if err == nil { + // Check to make sure the custom domain is still associated with the route + isAssociated := sliceContainsString(customDomains, customDomainID.ID()) + + if isAssociated { + // it is, now removed the association... + newDomains := sliceRemoveString(customDomains, customDomainID.ID()) + if err := updateRouteAssociations(d, meta, &route, newDomains, props, customDomainID); err != nil { + return err + } + } + } } - - // remove the field from state... - d.Set("associate_with_cdn_frontdoor_route_id", "") } return nil @@ -324,7 +376,7 @@ func updateRouteAssociations(d *pluginsdk.ResourceData, meta interface{}, routeI defer routeCancel() updateProps := azuresdkhacks.RouteUpdatePropertiesParameters{ - CustomDomains: expandCdnFrontdoorRouteActivatedResourceArray(customDomains), + CustomDomains: expandCustomDomainActivatedResourceArray(customDomains), } // NOTE: You must pull the Cache Configuration from the existing route else you will get a diff @@ -339,14 +391,15 @@ func updateRouteAssociations(d *pluginsdk.ResourceData, meta interface{}, routeI updateProps.LinkToDefaultDomain = cdn.LinkToDefaultDomainEnabled } - updatePrarams := azuresdkhacks.RouteUpdateParameters{ + updateParams := azuresdkhacks.RouteUpdateParameters{ RouteUpdatePropertiesParameters: &updateProps, } - future, err := workaroundsClient.Update(ctx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName, updatePrarams) + future, err := workaroundsClient.Update(ctx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName, updateParams) if err != nil { return fmt.Errorf("%s: updating the association with %s: %+v", *customDomainID, *routeId, err) } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { return fmt.Errorf("%s: waiting to update the association with %s: %+v", *customDomainID, *routeId, err) } @@ -354,61 +407,236 @@ func updateRouteAssociations(d *pluginsdk.ResourceData, meta interface{}, routeI return nil } -func validateCustomDomanLinkToDefaultDomainState(resourceCustomDomains []interface{}, routeCustomDomains []interface{}, routeName string, routeProfile string) error { - // Make all of the custom domains belong to the same profile as the route... +func validateCustomDomainLinkToDefaultDomainState(resourceCustomDomains []interface{}, routeCustomDomains []interface{}, routeName string, routeProfile string) error { + // NOTE: Only used in the deprecated custom domain link to default domain resource + if !features.FourPointOhBeta() { + // Make all of the custom domains belong to the same profile as the route... + wrongProfile := make([]string, 0) + + for _, v := range resourceCustomDomains { + customDomain, err := parse.FrontDoorCustomDomainID(v.(string)) + if err != nil { + return err + } + + if customDomain.ProfileName != routeProfile { + wrongProfile = append(wrongProfile, fmt.Sprintf("%q", customDomain.ID())) + } + } + + if len(wrongProfile) > 0 { + return fmt.Errorf("the following CDN FrontDoor Custom Domain(s) do not belong to the expected CDN FrontDoor Profile(Name: %q). Please remove the following CDN FrontDoor Custom Domain(s) from your CDN Route Disable Link To Default Domain configuration: %s", routeProfile, strings.Join(wrongProfile, ", ")) + } + + // Make sure the resource is referencing all of the custom domains that are associated with the route... + missingDomains := make([]string, 0) + + for _, v := range routeCustomDomains { + // If this was updated by the portal, it lowercases to resource ID... + customDomain, err := parse.FrontDoorCustomDomainID(v.(string)) + if err != nil { + return fmt.Errorf("unable to parse %q: %+v", v.(string), err) + } + + if !sliceContainsString(resourceCustomDomains, customDomain.ID()) { + missingDomains = append(missingDomains, fmt.Sprintf("%q", customDomain.ID())) + } + } + + if len(missingDomains) > 0 { + return fmt.Errorf("does not contain all of the CDN FrontDoor Custom Domains that are associated with the CDN FrontDoor Route(Name: %q). Please add the following CDN FrontDoor Custom Domain(s) to your CDN Route Disable Link To Default Domain configuration: %s", routeName, strings.Join(missingDomains, ", ")) + } + + // Make sure all of the custom domains that are referenced by the resource are actually associated with the route... + notAssociated := make([]string, 0) + + for _, v := range resourceCustomDomains { + customDomain, err := parse.FrontDoorCustomDomainID(v.(string)) + if err != nil { + return fmt.Errorf("unable to parse %q: %+v", v.(string), err) + } + + if !sliceContainsString(routeCustomDomains, customDomain.ID()) { + notAssociated = append(notAssociated, fmt.Sprintf("%q", customDomain.ID())) + } + } + + if len(notAssociated) > 0 { + return fmt.Errorf("contains CDN FrontDoor Custom Domains that are not associated with the CDN FrontDoor Route(Name: %q). Please remove the following CDN FrontDoor Custom Domain(s) from your CDN Route Disable Link To Default Domain configuration: %s", routeName, strings.Join(notAssociated, ", ")) + } + } + + return nil +} + +func validateRoutesCustomDomainProfile(customDomains []interface{}, routeName string, routeProfile string) error { wrongProfile := make([]string, 0) - for _, v := range resourceCustomDomains { - customDomain, err := parse.FrontDoorCustomDomainIDInsensitively(v.(string)) - if err != nil { - return err + if len(customDomains) != 0 { + // Verify all of the custom domains belong to the same profile as the route... + for _, v := range customDomains { + customDomain, err := parse.FrontDoorCustomDomainID(v.(string)) + if err != nil { + return err + } + + if customDomain.ProfileName != routeProfile { + wrongProfile = append(wrongProfile, fmt.Sprintf("%q", customDomain.ID())) + } } - if customDomain.ProfileName != routeProfile { - wrongProfile = append(wrongProfile, fmt.Sprintf("%q", customDomain.ID())) + if len(wrongProfile) > 0 { + return fmt.Errorf("the following CDN FrontDoor Custom Domain(s) do not belong to the expected CDN FrontDoor Profile(Name: %q). Please remove the following CDN FrontDoor Custom Domain(s) from your CDN FrontDoor Route configuration block: %s", routeProfile, strings.Join(wrongProfile, ", ")) } } - if len(wrongProfile) > 0 { - return fmt.Errorf("the following CDN Front Door Custom Domain(s) do not belong to the expected CDN Front Door Profile(Name: %q). Please remove the following CDN Front Door Custom Domain(s) from your CDN Route Disable Link To Default Domain configuration: %s", routeProfile, strings.Join(wrongProfile, ", ")) + return nil +} + +// Validates that the CDN FrontDoor Custom Domain can be associated with the CDN FrontDoor Route +func validateCustomDomainRoutes(input *[]parse.FrontDoorRouteId, customDomainID *parse.FrontDoorCustomDomainId) error { + if input == nil || len(*input) == 0 { + return nil + } + + // check for duplicates... + if err := routeSliceHasDuplicates(input, "CDN FrontDoor Route"); err != nil { + return err } - // Make sure the resource is referencing all of the custom domains that are associated with the route... - missingDomains := make([]string, 0) + for i, route := range *input { + // the route and custom domain profiles must match... + if customDomainID.ProfileName != route.ProfileName { + return fmt.Errorf("the CDN FrontDoor Custom Domain(Name: %q, Profile: %q) and the CDN FrontDoor Route(Name: %q, Profile: %q) must belong to the same CDN FrontDoor Profile", customDomainID.CustomDomainName, customDomainID.ProfileName, route.RouteName, route.ProfileName) + } - for _, v := range routeCustomDomains { - // If this was updated by the portal, it lowercases to resource ID... - customDomain, err := parse.FrontDoorCustomDomainIDInsensitively(v.(string)) - if err != nil { - return fmt.Errorf("unable to parse %q: %+v", v.(string), err) + // validate all routes are using the same endpoint because a custom domain can not + // be associated with routes that target two different endpoints... + for t, v := range *input { + if i == t { + continue + } + + if route.AfdEndpointName != v.AfdEndpointName { + return fmt.Errorf("the CDN FrontDoor Route(Name: %q) and CDN FrontDoor Route(Name: %q) do not reference the same CDN FrontDoor Endpoint(Name: %q). All CDN FrontDoor Routes must reference the same CDN FrontDoor Endpoint %q to associate the CDN FrontDoor Custom Domain(Name: %q) with more than one CDN FrontDoor Route", route.RouteName, v.RouteName, route.AfdEndpointName, route.AfdEndpointName, customDomainID.CustomDomainName) + } + } + } + + return nil +} + +// Checks to make sure the list of CDN FrontDoor Custom Domains does not contain duplicate entries +func sliceHasDuplicates(input []interface{}, resourceTxt string) error { + k := make(map[string]bool) + if len(input) == 0 || input == nil { + return nil + } + + for _, v := range input { + if _, d := k[strings.ToLower(v.(string))]; !d { + k[strings.ToLower(v.(string))] = true + } else { + return fmt.Errorf("duplicate %[1]s detected, please remove all duplicate entries for the %[1]s(ID: %q) from your configuration block", resourceTxt, v.(string)) } + } + + return nil +} - if !sliceContainsString(resourceCustomDomains, customDomain.ID()) { - missingDomains = append(missingDomains, fmt.Sprintf("%q", customDomain.ID())) +func routeSliceHasDuplicates(input *[]parse.FrontDoorRouteId, resourceName string) error { + k := make(map[string]bool) + if len(*input) == 0 || input == nil { + return nil + } + + for _, route := range *input { + if _, d := k[strings.ToLower(route.ID())]; !d { + k[strings.ToLower(route.ID())] = true + } else { + return fmt.Errorf("duplicate %[1]s detected, please remove all duplicate entries for the %[1]s(ID: %q) from your configuration block", resourceName, route.ID()) } } - if len(missingDomains) > 0 { - return fmt.Errorf("does not contain all of the CDN Front Door Custom Domains that are associated with the CDN Front Door Route(Name: %q). Please add the following CDN Front Door Custom Domain(s) to your CDN Route Disable Link To Default Domain configuration: %s", routeName, strings.Join(missingDomains, ", ")) + return nil +} + +// Determines what CDN FrontDoor Routes need to be removed/disassociated from this CDN FrontDoor Custom Domain +func routeDelta(oldRoutes *[]parse.FrontDoorRouteId, newRoutes *[]parse.FrontDoorRouteId) (*[]parse.FrontDoorRouteId, *[]parse.FrontDoorRouteId) { + remove := make([]parse.FrontDoorRouteId, 0) + shared := make([]parse.FrontDoorRouteId, 0) + if len(*newRoutes) == 0 || newRoutes == nil { + return oldRoutes, &shared } - // Make sure all of the custom domains that are referenced by the resource are actually associated with the route... - notAssociated := make([]string, 0) + if len(*oldRoutes) == 0 || oldRoutes == nil { + return &remove, &shared + } + + // just find what old routes are not in the new route list... + for _, oldRoute := range *oldRoutes { + if !routeSliceContains(newRoutes, oldRoute.ID()) { + remove = append(remove, oldRoute) + } else { + shared = append(shared, oldRoute) + } + } + + return &remove, &shared +} + +func expandRuleSetIds(input []interface{}) ([]interface{}, error) { + out := make([]interface{}, 0) + if len(input) == 0 || input == nil { + return out, nil + } - for _, v := range resourceCustomDomains { - customDomain, err := parse.FrontDoorCustomDomainIDInsensitively(v.(string)) + for _, ruleSet := range input { + id, err := parse.FrontDoorRuleSetID(ruleSet.(string)) if err != nil { - return fmt.Errorf("unable to parse %q: %+v", v.(string), err) + return nil, err } - if !sliceContainsString(routeCustomDomains, customDomain.ID()) { - notAssociated = append(notAssociated, fmt.Sprintf("%q", customDomain.ID())) + out = append(out, id.ID()) + } + + return out, nil +} + +func expandRoutes(input []interface{}) (*[]parse.FrontDoorRouteId, []interface{}, error) { + out := make([]parse.FrontDoorRouteId, 0) + config := make([]interface{}, 0) + if len(input) == 0 || input == nil { + return &out, config, nil + } + + for _, v := range input { + id, err := parse.FrontDoorRouteID(v.(string)) + if err != nil { + return nil, nil, err } + + out = append(out, *id) + config = append(config, id.ID()) } - if len(notAssociated) > 0 { - return fmt.Errorf("contains CDN Front Door Custom Domains that are not associated with the CDN Front Door Route(Name: %q). Please remove the following CDN Front Door Custom Domain(s) from your CDN Route Disable Link To Default Domain configuration: %s", routeName, strings.Join(notAssociated, ", ")) + return &out, config, nil +} + +func expandCustomDomains(input []interface{}) ([]interface{}, error) { + out := make([]interface{}, 0) + if len(input) == 0 || input == nil { + return out, nil } - return nil + for _, v := range input { + id, err := parse.FrontDoorCustomDomainID(v.(string)) + if err != nil { + return nil, err + } + + out = append(out, id.ID()) + } + + return out, nil } diff --git a/internal/services/cdn/cdn_frontdoor_origin_group_resource.go b/internal/services/cdn/cdn_frontdoor_origin_group_resource.go index 92156cee3c9f..188a0565ac2c 100644 --- a/internal/services/cdn/cdn_frontdoor_origin_group_resource.go +++ b/internal/services/cdn/cdn_frontdoor_origin_group_resource.go @@ -144,12 +144,12 @@ func resourceCdnFrontDoorOriginGroupCreate(d *pluginsdk.ResourceData, meta inter ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - profileId, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) + profile, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) if err != nil { return err } - id := parse.NewFrontDoorOriginGroupID(profileId.SubscriptionId, profileId.ResourceGroup, profileId.ProfileName, d.Get("name").(string)) + id := parse.NewFrontDoorOriginGroupID(profile.SubscriptionId, profile.ResourceGroup, profile.ProfileName, d.Get("name").(string)) existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.OriginGroupName) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { diff --git a/internal/services/cdn/cdn_frontdoor_origin_resource.go b/internal/services/cdn/cdn_frontdoor_origin_resource.go index 5dca884460f3..82fa2aa84e63 100644 --- a/internal/services/cdn/cdn_frontdoor_origin_resource.go +++ b/internal/services/cdn/cdn_frontdoor_origin_resource.go @@ -179,12 +179,14 @@ func resourceCdnFrontDoorOriginCreate(d *pluginsdk.ResourceData, meta interface{ ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - originGroupId, err := parse.FrontDoorOriginGroupID(d.Get("cdn_frontdoor_origin_group_id").(string)) + originGroupRaw := d.Get("cdn_frontdoor_origin_group_id").(string) + originGroup, err := parse.FrontDoorOriginGroupID(originGroupRaw) if err != nil { return err } - id := parse.NewFrontDoorOriginID(originGroupId.SubscriptionId, originGroupId.ResourceGroup, originGroupId.ProfileName, originGroupId.OriginGroupName, d.Get("name").(string)) + id := parse.NewFrontDoorOriginID(originGroup.SubscriptionId, originGroup.ResourceGroup, originGroup.ProfileName, originGroup.OriginGroupName, d.Get("name").(string)) + existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.OriginGroupName, id.OriginName) if err != nil { if !utils.ResponseWasNotFound(existing.Response) { @@ -319,26 +321,33 @@ func resourceCdnFrontDoorOriginUpdate(d *pluginsdk.ResourceData, meta interface{ if d.HasChange("certificate_name_check_enabled") { params.EnforceCertificateNameCheck = utils.Bool(d.Get("certificate_name_check_enabled").(bool)) } + if !features.FourPointOhBeta() { if d.HasChange("health_probes_enabled") { params.EnabledState = expandEnabledBool(d.Get("health_probes_enabled").(bool)) } } + if d.HasChange("enabled") { params.EnabledState = expandEnabledBool(d.Get("enabled").(bool)) } + if d.HasChange("host_name") { params.HostName = utils.String(d.Get("host_name").(string)) } + if d.HasChange("http_port") { params.HTTPPort = utils.Int32(int32(d.Get("http_port").(int))) } + if d.HasChange("https_port") { params.HTTPSPort = utils.Int32(int32(d.Get("https_port").(int))) } + if d.HasChange("origin_host_header") { params.OriginHostHeader = utils.String(d.Get("origin_host_header").(string)) } + if d.HasChange("private_link") { // I need to get the profile SKU so I know if it is valid or not to define a private link as // private links are only allowed in the premium sku... @@ -351,9 +360,11 @@ func resourceCdnFrontDoorOriginUpdate(d *pluginsdk.ResourceData, meta interface{ return fmt.Errorf("retrieving parent %s: %+v", profileId, err) } + if profile.Sku == nil { return fmt.Errorf("retrieving parent %s: 'sku' was nil", profileId) } + skuName := profile.Sku.Name enableCertNameCheck := d.Get("certificate_name_check_enabled").(bool) @@ -361,11 +372,14 @@ func resourceCdnFrontDoorOriginUpdate(d *pluginsdk.ResourceData, meta interface{ if err != nil { return err } + params.SharedPrivateLinkResource = privateLinkSettings } + if d.HasChange("priority") { params.Priority = utils.Int32(int32(d.Get("priority").(int))) } + if d.HasChange("weight") { params.Weight = utils.Int32(int32(d.Get("weight").(int))) } @@ -373,10 +387,12 @@ func resourceCdnFrontDoorOriginUpdate(d *pluginsdk.ResourceData, meta interface{ payload := cdn.AFDOriginUpdateParameters{ AFDOriginUpdatePropertiesParameters: ¶ms, } + future, err := client.Update(ctx, id.ResourceGroup, id.ProfileName, id.OriginGroupName, id.OriginName, payload) if err != nil { return fmt.Errorf("updating %s: %+v", *id, err) } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { return fmt.Errorf("waiting for the update of %s: %+v", *id, err) } @@ -425,8 +441,7 @@ func resourceCdnFrontDoorOriginDelete(d *pluginsdk.ResourceData, meta interface{ func expandPrivateLinkSettings(input []interface{}, skuName cdn.SkuName, enableCertNameCheck bool) (*cdn.SharedPrivateLinkResourceProperties, error) { if len(input) == 0 { - // TODO: Should this return an empty object? - // WS: This cannot return an empty object, the service team requires this to be set to nil else you will get the following error during creation: + // NOTE: This cannot return an empty object, the service team requires this to be set to nil else you will get the following error during creation: // Property 'AfdOrigin.SharedPrivateLinkResource.PrivateLink' is required but it was not set; Property 'AfdOrigin.SharedPrivateLinkResource.RequestMessage' is required but it was not set return nil, nil } diff --git a/internal/services/cdn/cdn_frontdoor_route_disable_link_to_default_domain_resource.go b/internal/services/cdn/cdn_frontdoor_route_disable_link_to_default_domain_resource.go index 64126a986a7b..ea8f101cf869 100644 --- a/internal/services/cdn/cdn_frontdoor_route_disable_link_to_default_domain_resource.go +++ b/internal/services/cdn/cdn_frontdoor_route_disable_link_to_default_domain_resource.go @@ -17,9 +17,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/utils" ) -var cdnFrontDoorCustomDomainResourceName = "azurerm_cdn_frontdoor_custom_domain" -var cdnFrontDoorRouteResourceName = "azurerm_cdn_frontdoor_route" - func resourceCdnFrontDoorRouteDisableLinkToDefaultDomain() *pluginsdk.Resource { return &pluginsdk.Resource{ Create: resourceCdnFrontDoorRouteDisableLinkToDefaultDomainCreate, @@ -46,12 +43,14 @@ func resourceCdnFrontDoorRouteDisableLinkToDefaultDomain() *pluginsdk.Resource { Type: pluginsdk.TypeString, Required: true, ForceNew: true, + Deprecated: "the 'cdn_frontdoor_route_disable_link_to_default_domain' resource has been deprecated and will be removed from the 4.0 AzureRM provider. Please use the 'link_to_default_domain' field in the 'cdn_frontdoor_route' resource to control this value", ValidateFunc: validate.FrontDoorRouteID, }, "cdn_frontdoor_custom_domain_ids": { - Type: pluginsdk.TypeList, - Required: true, + Type: pluginsdk.TypeList, + Required: true, + Deprecated: "the 'cdn_frontdoor_route_disable_link_to_default_domain' resource has been deprecated and will be removed from the 4.0 AzureRM provider. Please use the 'link_to_default_domain' field in the 'cdn_frontdoor_route' resource to control this value", Elem: &pluginsdk.Schema{ Type: pluginsdk.TypeString, @@ -74,7 +73,7 @@ func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainCreate(d *pluginsdk.Reso routeId, err := parse.FrontDoorRouteID(d.Get("cdn_frontdoor_route_id").(string)) if err != nil { - return err + return fmt.Errorf("creating Front Door Route Disable Link To Default Domain: %+v", err) } // create the resource id @@ -82,53 +81,56 @@ func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainCreate(d *pluginsdk.Reso if err != nil { return fmt.Errorf("generating UUID: %+v", err) } + id := parse.NewFrontDoorRouteDisableLinkToDefaultDomainID(routeId.SubscriptionId, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName, uuid) - // Lock the resources to make sure this will be an atomic operation - // as this is a one to many association potentially(e.g. route -> custom domain(s))... locks.ByName(routeId.RouteName, cdnFrontDoorRouteResourceName) defer locks.UnlockByName(routeId.RouteName, cdnFrontDoorRouteResourceName) - // Lock all of the custom domains associated with this unlink default domain resource - // prolly overkill but we don't want the custom domains moving while we are doing this - // operation as well... for _, v := range customDomains { customDomainId, err := parse.FrontDoorCustomDomainID(v.(string)) if err != nil { - return err + return fmt.Errorf("creating %s: %+v", id, err) } locks.ByName(customDomainId.CustomDomainName, cdnFrontDoorCustomDomainResourceName) defer locks.UnlockByName(customDomainId.CustomDomainName, cdnFrontDoorCustomDomainResourceName) } - routeResp, err := routeClient.Get(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName) + existing, err := routeClient.Get(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName) if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("creating %s: %s was not found", id, routeId) + } + return fmt.Errorf("retrieving existing %s: %+v", *routeId, err) } - props := routeResp.RouteProperties + props := existing.RouteProperties if props == nil { - return fmt.Errorf("%s properties are 'nil': %+v", *routeId, err) + return fmt.Errorf("creating %s: %s properties are 'nil'", id, *routeId) } resourceCustomDomains := d.Get("cdn_frontdoor_custom_domain_ids").([]interface{}) - routeCustomDomains := flattenCdnFrontdoorRouteActivatedResourceArray(props.CustomDomains) + routeCustomDomains, err := flattenCustomDomainActivatedResourceArray(props.CustomDomains) + if err != nil { + return err + } // make sure its valid to disable the LinkToDefaultDomain on this route... if len(routeCustomDomains) == 0 { - return fmt.Errorf("it is invalid to disable the 'LinkToDefaultDomain' for the CDN Front Door Route(Name: %s) since the route does not have any CDN Front Door Custom Domains associated with it", routeId.RouteName) + return fmt.Errorf("creating %s: it is invalid to disable the 'LinkToDefaultDomain' for the CDN Front Door Route(Name: %s) since the route does not have any CDN Front Door Custom Domains associated with it", id, routeId.RouteName) } // validate the custom domains... - if err := validateCustomDomanLinkToDefaultDomainState(resourceCustomDomains, routeCustomDomains, routeId.RouteName, routeId.ProfileName); err != nil { - return err + if err := validateCustomDomainLinkToDefaultDomainState(resourceCustomDomains, routeCustomDomains, routeId.RouteName, routeId.ProfileName); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) } // If it is already disabled do not update the route... if props.LinkToDefaultDomain != cdn.LinkToDefaultDomainDisabled { updateProps := azuresdkhacks.RouteUpdatePropertiesParameters{ - CustomDomains: expandCdnFrontdoorRouteActivatedResourceArray(customDomains), + CustomDomains: expandCustomDomainActivatedResourceArray(customDomains), } // Since this unlink default domain resource always set the value to false @@ -139,11 +141,11 @@ func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainCreate(d *pluginsdk.Reso updateProps.CacheConfiguration = props.CacheConfiguration } - updatePrarams := azuresdkhacks.RouteUpdateParameters{ + updateParams := azuresdkhacks.RouteUpdateParameters{ RouteUpdatePropertiesParameters: &updateProps, } - future, err := workaroundsClient.Update(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName, updatePrarams) + future, err := workaroundsClient.Update(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName, updateParams) if err != nil { return fmt.Errorf("creating %s: %+v", id, err) } @@ -153,6 +155,8 @@ func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainCreate(d *pluginsdk.Reso } d.SetId(id.ID()) + d.Set("cdn_frontdoor_route_id", routeId.ID()) + d.Set("cdn_frontdoor_custom_domain_ids", customDomains) return resourceCdnFrontDoorRouteDisableLinkToDefaultDomainRead(d, meta) } @@ -163,143 +167,176 @@ func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainRead(d *pluginsdk.Resour defer routeCancel() customDomainClient := meta.(*clients.Client).Cdn.FrontDoorCustomDomainsClient - customDomainCtx, customDomaincancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) - defer customDomaincancel() + customDomainCtx, customDomainCancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer customDomainCancel() + + id, err := parse.FrontDoorRouteDisableLinkToDefaultDomainID(d.Id()) + if err != nil { + return err + } routeId, err := parse.FrontDoorRouteID(d.Get("cdn_frontdoor_route_id").(string)) if err != nil { - return fmt.Errorf("unable to parse CDN Front Door Route ID: %+v", err) + return fmt.Errorf("front door route disable link to default domain: %+v", err) } // Make sure the route still exist... - routeResp, err := routeClient.Get(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName) + existing, err := routeClient.Get(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName) if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + return fmt.Errorf("creating %s: %s was not found", id, routeId) + } + return fmt.Errorf("retrieving existing %s: %+v", *routeId, err) } - props := routeResp.RouteProperties - if props == nil { - return fmt.Errorf("%s properties are 'nil': %+v", *routeId, err) + customDomains := d.Get("cdn_frontdoor_custom_domain_ids").([]interface{}) + for _, v := range customDomains { + cdId, err := parse.FrontDoorCustomDomainID(v.(string)) + if err != nil { + return fmt.Errorf("%s: unable to parse CDN Front Door Custom Domain ID: %+v", id, err) + } + + _, err = customDomainClient.Get(customDomainCtx, cdId.ResourceGroup, cdId.ProfileName, cdId.CustomDomainName) + if err != nil { + return fmt.Errorf("retrieving existing %s: %+v", cdId, err) + } } - // Make sure all of the custom domains still exist... - routeCustomDomains := flattenCdnFrontdoorRouteActivatedResourceArray(props.CustomDomains) - resourceCustomDomains := d.Get("cdn_frontdoor_custom_domain_ids").([]interface{}) - for _, v := range resourceCustomDomains { - customDomainId, err := parse.FrontDoorCustomDomainID(v.(string)) + return nil +} + +func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + routeClient := meta.(*clients.Client).Cdn.FrontDoorRoutesClient + workaroundsClient := azuresdkhacks.NewCdnFrontDoorRoutesWorkaroundClient(routeClient) + routeCtx, routeCancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer routeCancel() + + if d.HasChange("cdn_frontdoor_custom_domain_ids") { + customDomains := d.Get("cdn_frontdoor_custom_domain_ids").([]interface{}) + + routeId, err := parse.FrontDoorRouteID(d.Get("cdn_frontdoor_route_id").(string)) if err != nil { - return fmt.Errorf("unable to parse CDN Front Door Custom Domain ID: %+v", err) + return fmt.Errorf("updating Front Door Route Disable Link To Default Domain: %+v", err) } - _, err = customDomainClient.Get(customDomainCtx, customDomainId.ResourceGroup, customDomainId.ProfileName, customDomainId.CustomDomainName) + id, err := parse.FrontDoorRouteDisableLinkToDefaultDomainID(d.Id()) if err != nil { - return fmt.Errorf("retrieving existing %s: %+v", customDomainId, err) + return err } - } - // Only do the validation if you are not deleting the resource... - if d.HasChange("cdn_frontdoor_route_id") { - if _, newRoute := d.GetChange("cdn_frontdoor_route_id"); newRoute != "" { - if len(routeCustomDomains) == 0 { - return fmt.Errorf("there are currently no CDN Front Door Custom Domains associated with the CDN Front Door Route(Name: %q). Please remove the resource from your configuration file", routeId.RouteName) - } + locks.ByName(routeId.RouteName, cdnFrontDoorRouteResourceName) + defer locks.UnlockByName(routeId.RouteName, cdnFrontDoorRouteResourceName) - // validate the custom domains... - if err := validateCustomDomanLinkToDefaultDomainState(resourceCustomDomains, routeCustomDomains, routeId.RouteName, routeId.ProfileName); err != nil { - return err + for _, v := range customDomains { + customDomainId, err := parse.FrontDoorCustomDomainID(v.(string)) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) } - // In case someone updates this value in portal... - if props.LinkToDefaultDomain != cdn.LinkToDefaultDomainDisabled { - return fmt.Errorf("the 'LinkToDefaultDomain' field has been 'enabled' on the CDN Front Door Route(Name: %q). Please revert this value to 'disabled' before proceeding", routeId.RouteName) + locks.ByName(customDomainId.CustomDomainName, cdnFrontDoorCustomDomainResourceName) + defer locks.UnlockByName(customDomainId.CustomDomainName, cdnFrontDoorCustomDomainResourceName) + } + + existing, err := routeClient.Get(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName) + if err != nil { + if utils.ResponseWasNotFound(existing.Response) { + d.SetId("") + return nil } + + return fmt.Errorf("%s: retrieving existing %s: %+v", *id, *routeId, err) } - } - d.Set("cdn_frontdoor_route_id", routeId.ID()) - d.Set("cdn_frontdoor_custom_domain_ids", resourceCustomDomains) + props := existing.RouteProperties + if props == nil { + return fmt.Errorf("updating %s: %s properties are 'nil'", id, *routeId) + } - return nil -} + resourceCustomDomains := d.Get("cdn_frontdoor_custom_domain_ids").([]interface{}) + routeCustomDomains, err := flattenCustomDomainActivatedResourceArray(props.CustomDomains) + if err != nil { + return err + } -func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainUpdate(d *pluginsdk.ResourceData, meta interface{}) error { - routeClient := meta.(*clients.Client).Cdn.FrontDoorRoutesClient - routeCtx, routeCancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) - defer routeCancel() + // make sure its valid to disable the LinkToDefaultDomain on this route... + if len(routeCustomDomains) == 0 { + return fmt.Errorf("updating %s: it is invalid to disable the 'LinkToDefaultDomain' for the CDN Front Door Route(Name: %s) since the route does not have any CDN Front Door Custom Domains associated with it", id, routeId.RouteName) + } - routeId, err := parse.FrontDoorRouteID(d.Get("cdn_frontdoor_route_id").(string)) - if err != nil { - return fmt.Errorf("unable to parse CDN Front Door Route ID: %+v", err) - } + // validate the custom domains... + if err := validateCustomDomainLinkToDefaultDomainState(resourceCustomDomains, routeCustomDomains, routeId.RouteName, routeId.ProfileName); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } - routeResp, err := routeClient.Get(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName) - if err != nil { - return fmt.Errorf("retrieving existing %s: %+v", *routeId, err) - } + // If it is already disabled do not update the route... + if props.LinkToDefaultDomain != cdn.LinkToDefaultDomainDisabled { + updateProps := azuresdkhacks.RouteUpdatePropertiesParameters{ + CustomDomains: expandCustomDomainActivatedResourceArray(customDomains), + } - props := routeResp.RouteProperties - if props == nil { - return fmt.Errorf("%s properties are 'nil': %+v", *routeId, err) - } + // Since this unlink default domain resource always set the value to false + updateProps.LinkToDefaultDomain = cdn.LinkToDefaultDomainDisabled - resourceCustomDomains := d.Get("cdn_frontdoor_custom_domain_ids").([]interface{}) - routeCustomDomains := flattenCdnFrontdoorRouteActivatedResourceArray(props.CustomDomains) + // NOTE: You must pull the Cache Configuration from the existing route else you will get a diff, because a nil value means disabled + if props.CacheConfiguration != nil { + updateProps.CacheConfiguration = props.CacheConfiguration + } - // validate the custom domains... - if err := validateCustomDomanLinkToDefaultDomainState(resourceCustomDomains, routeCustomDomains, routeId.RouteName, routeId.ProfileName); err != nil { - return err - } + updateParams := azuresdkhacks.RouteUpdateParameters{ + RouteUpdatePropertiesParameters: &updateProps, + } - d.Set("cdn_frontdoor_custom_domain_ids", resourceCustomDomains) + future, err := workaroundsClient.Update(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName, updateParams) + if err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + if err = future.WaitForCompletionRef(routeCtx, routeClient.Client); err != nil { + return fmt.Errorf("waiting for the update of %s: %+v", id, err) + } + } - return nil + d.Set("cdn_frontdoor_route_id", routeId.ID()) + d.Set("cdn_frontdoor_custom_domain_ids", customDomains) + } + + return resourceCdnFrontDoorRouteDisableLinkToDefaultDomainRead(d, meta) } func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainDelete(d *pluginsdk.ResourceData, meta interface{}) error { - routeClient := meta.(*clients.Client).Cdn.FrontDoorRoutesClient - workaroundsClient := azuresdkhacks.NewCdnFrontDoorRoutesWorkaroundClient(routeClient) - routeCtx, routeCancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) - defer routeCancel() + client := meta.(*clients.Client).Cdn.FrontDoorRoutesClient + workaroundsClient := azuresdkhacks.NewCdnFrontDoorRoutesWorkaroundClient(client) + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() id, err := parse.FrontDoorRouteDisableLinkToDefaultDomainID(d.Id()) if err != nil { return err } - currentRoute := d.Get("cdn_frontdoor_route_id").(string) + oldRoute, _ := d.GetChange("cdn_frontdoor_route_id") - // If this delete was due to a change you need to revert - // the old route not the new route... - if d.HasChange("cdn_frontdoor_route_id") { - if oldRoute, _ := d.GetChange("cdn_frontdoor_route_id"); oldRoute.(string) != "" { - currentRoute = oldRoute.(string) - } - } - - routeId, err := parse.FrontDoorRouteID(currentRoute) + route, err := parse.FrontDoorRouteID(oldRoute.(string)) if err != nil { return err } - locks.ByName(routeId.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(routeId.RouteName, cdnFrontDoorRouteResourceName) + locks.ByName(route.RouteName, cdnFrontDoorRouteResourceName) + defer locks.UnlockByName(route.RouteName, cdnFrontDoorRouteResourceName) - routeResp, err := routeClient.Get(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName) + resp, err := client.Get(ctx, route.ResourceGroup, route.ProfileName, route.AfdEndpointName, route.RouteName) if err != nil { - // No Op: Since the route and the custom domain resources will be deleted before this resource - // in the destroy scenario a 404 can be ignored as it means that it was already destroyed - // so you do not need to set the "link to default domain" value on the route anymore... - if utils.ResponseWasNotFound(routeResp.Response) { + if utils.ResponseWasNotFound(resp.Response) { d.SetId("") return nil } - return fmt.Errorf("retrieving existing %s: %+v", *routeId, err) + return fmt.Errorf("retrieving existing %s: %+v", *route, err) } - props := routeResp.RouteProperties + props := resp.RouteProperties if props == nil { - return fmt.Errorf("%s properties are 'nil': %+v", *routeId, err) + return fmt.Errorf("deleting %s: %s properties are 'nil'", *id, *route) } updateProps := azuresdkhacks.RouteUpdatePropertiesParameters{ @@ -312,23 +349,26 @@ func resourceCdnFrontDoorRouteDisableLinkToDefaultDomainDelete(d *pluginsdk.Reso updateProps.CacheConfiguration = props.CacheConfiguration } - // NOTE: Only update LinkToDefaultDomain to enabled if there are not any custom domains associated with the route - routeCustomDomains := flattenCdnFrontdoorRouteActivatedResourceArray(props.CustomDomains) + customDomains, err := flattenCustomDomainActivatedResourceArray(props.CustomDomains) + if err != nil { + return err + } - if len(routeCustomDomains) == 0 { + // NOTE: Only update LinkToDefaultDomain to enabled if there are not any custom domains associated with the route + if len(customDomains) == 0 { // only update the route if it is currently in the disabled state... if updateProps.LinkToDefaultDomain == cdn.LinkToDefaultDomainDisabled { updateProps.LinkToDefaultDomain = cdn.LinkToDefaultDomainEnabled - updatePrarams := azuresdkhacks.RouteUpdateParameters{ + updateParams := azuresdkhacks.RouteUpdateParameters{ RouteUpdatePropertiesParameters: &updateProps, } - future, err := workaroundsClient.Update(routeCtx, routeId.ResourceGroup, routeId.ProfileName, routeId.AfdEndpointName, routeId.RouteName, updatePrarams) + future, err := workaroundsClient.Update(ctx, route.ResourceGroup, route.ProfileName, route.AfdEndpointName, route.RouteName, updateParams) if err != nil { return fmt.Errorf("deleting %s: %+v", *id, err) } - if err = future.WaitForCompletionRef(routeCtx, routeClient.Client); err != nil { + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { return fmt.Errorf("waiting for the deletion of %s: %+v", *id, err) } } diff --git a/internal/services/cdn/cdn_frontdoor_route_disable_link_to_default_domain_resource_test.go b/internal/services/cdn/cdn_frontdoor_route_disable_link_to_default_domain_resource_test.go index 7f526254cfeb..51adac3787ed 100644 --- a/internal/services/cdn/cdn_frontdoor_route_disable_link_to_default_domain_resource_test.go +++ b/internal/services/cdn/cdn_frontdoor_route_disable_link_to_default_domain_resource_test.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" "github.com/hashicorp/terraform-provider-azurerm/internal/clients" + "github.com/hashicorp/terraform-provider-azurerm/internal/features" "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/parse" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" @@ -16,16 +17,21 @@ import ( type CdnFrontDoorRouteDisableLinkToDefaultDomainResource struct{} func TestAccCdnFrontDoorRouteDisableLinkToDefaultDomain_basic(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_route_disable_link_to_default_domain", "test") - r := CdnFrontDoorRouteDisableLinkToDefaultDomainResource{} - data.ResourceTest(t, r, []acceptance.TestStep{ - { - Config: r.basic(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - }) + if !features.FourPointOhBeta() { + data := acceptance.BuildTestData(t, "azurerm_cdn_frontdoor_route_disable_link_to_default_domain", "test") + r := CdnFrontDoorRouteDisableLinkToDefaultDomainResource{} + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + ExpectNonEmptyPlan: true, // since this resource actually modifies the routes 'linked to default domain' field + }, + }) + } else { + t.Skip("the 'azurerm_cdn_frontdoor_route_disable_link_to_default_domain' resource is no longer supported in the AzureRM provider v4.0, please remove this test case") + } } func (r CdnFrontDoorRouteDisableLinkToDefaultDomainResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { @@ -49,53 +55,39 @@ func (r CdnFrontDoorRouteDisableLinkToDefaultDomainResource) Exists(ctx context. func (r CdnFrontDoorRouteDisableLinkToDefaultDomainResource) basic(data acceptance.TestData) string { template := r.template(data) return fmt.Sprintf(` -provider "azurerm" { - features {} -} - %s resource "azurerm_cdn_frontdoor_route_disable_link_to_default_domain" "test" { cdn_frontdoor_route_id = azurerm_cdn_frontdoor_route.test.id cdn_frontdoor_custom_domain_ids = [azurerm_cdn_frontdoor_custom_domain.test.id] } - -resource "azurerm_cdn_frontdoor_custom_domain" "test" { - name = "acctestcustomdomain-%d" - cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id - dns_zone_id = azurerm_dns_zone.test.id - host_name = join(".", ["%s", azurerm_dns_zone.test.name]) - - associate_with_cdn_frontdoor_route_id = azurerm_cdn_frontdoor_route.test.id - - tls { - certificate_type = "ManagedCertificate" - minimum_tls_version = "TLS12" - } -} -`, template, data.RandomInteger, data.RandomStringOfLength(8)) +`, template) } func (r CdnFrontDoorRouteDisableLinkToDefaultDomainResource) template(data acceptance.TestData) string { return fmt.Sprintf(` +provider "azurerm" { + features {} +} + resource "azurerm_resource_group" "test" { name = "acctestRG-cdn-afdx-%[1]d" location = "%[2]s" } resource "azurerm_dns_zone" "test" { - name = "acctestzone%[1]d.com" + name = "acctest-dns-zone%[1]d.com" resource_group_name = azurerm_resource_group.test.name } resource "azurerm_cdn_frontdoor_profile" "test" { - name = "acctestcdnfdprofile-%[1]d" + name = "acctest-profile-%[1]d" resource_group_name = azurerm_resource_group.test.name sku_name = "Standard_AzureFrontDoor" } resource "azurerm_cdn_frontdoor_origin_group" "test" { - name = "accTestOriginGroup-%[1]d" + name = "accTest-origin-group-%[1]d" cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id load_balancing { @@ -106,7 +98,7 @@ resource "azurerm_cdn_frontdoor_origin_group" "test" { } resource "azurerm_cdn_frontdoor_origin" "test" { - name = "accTestOrigin-%[1]d" + name = "acctest-origin-%[1]d" cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.test.id enabled = true @@ -120,17 +112,36 @@ resource "azurerm_cdn_frontdoor_origin" "test" { } resource "azurerm_cdn_frontdoor_endpoint" "test" { - name = "accTestEndpoint-%[1]d" + name = "acctest-endpoint-%[1]d" cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id } resource "azurerm_cdn_frontdoor_route" "test" { - name = "accTestRoute-%[1]d" + name = "acctest-route-%[1]d" cdn_frontdoor_endpoint_id = azurerm_cdn_frontdoor_endpoint.test.id cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.test.id cdn_frontdoor_origin_ids = [azurerm_cdn_frontdoor_origin.test.id] patterns_to_match = ["/*"] supported_protocols = ["Http", "Https"] + + cdn_frontdoor_custom_domain_ids = [azurerm_cdn_frontdoor_custom_domain.test.id] +} + +resource "azurerm_cdn_frontdoor_custom_domain" "test" { + name = "acctest-custom-domain-%[1]d" + cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.test.id + dns_zone_id = azurerm_dns_zone.test.id + host_name = join(".", ["%[3]s", azurerm_dns_zone.test.name]) + + tls { + certificate_type = "ManagedCertificate" + minimum_tls_version = "TLS10" + } +} + +resource "azurerm_cdn_frontdoor_custom_domain_association" "test" { + cdn_frontdoor_custom_domain_id = azurerm_cdn_frontdoor_custom_domain.test.id + cdn_frontdoor_route_ids = [azurerm_cdn_frontdoor_route.test.id] } -`, data.RandomInteger, data.Locations.Primary) +`, data.RandomInteger, data.Locations.Primary, data.RandomStringOfLength(8)) } diff --git a/internal/services/cdn/cdn_frontdoor_route_resource.go b/internal/services/cdn/cdn_frontdoor_route_resource.go index 30a157814087..5f9fc941cb95 100644 --- a/internal/services/cdn/cdn_frontdoor_route_resource.go +++ b/internal/services/cdn/cdn_frontdoor_route_resource.go @@ -69,6 +69,22 @@ func resourceCdnFrontDoorRoute() *pluginsdk.Resource { }, }, + "cdn_frontdoor_custom_domain_ids": { + Type: pluginsdk.TypeSet, + Optional: true, + + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validate.FrontDoorCustomDomainID, + }, + }, + + "link_to_default_domain": { + Type: pluginsdk.TypeBool, + Optional: true, + Default: true, + }, + // NOTE: Per the service team this cannot just be omitted it must explicitly be set to nil to disable caching "cache": { Type: pluginsdk.TypeList, @@ -157,7 +173,7 @@ func resourceCdnFrontDoorRoute() *pluginsdk.Resource { }, "cdn_frontdoor_rule_set_ids": { - Type: pluginsdk.TypeList, + Type: pluginsdk.TypeSet, Optional: true, Elem: &pluginsdk.Schema{ @@ -187,12 +203,13 @@ func resourceCdnFrontDoorRouteCreate(d *pluginsdk.ResourceData, meta interface{} ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - endpointId, err := parse.FrontDoorEndpointID(d.Get("cdn_frontdoor_endpoint_id").(string)) + endpointRaw := d.Get("cdn_frontdoor_endpoint_id").(string) + endpoint, err := parse.FrontDoorEndpointID(endpointRaw) if err != nil { return err } - id := parse.NewFrontDoorRouteID(endpointId.SubscriptionId, endpointId.ResourceGroup, endpointId.ProfileName, endpointId.AfdEndpointName, d.Get("name").(string)) + id := parse.NewFrontDoorRouteID(endpoint.SubscriptionId, endpoint.ResourceGroup, endpoint.ProfileName, endpoint.AfdEndpointName, d.Get("name").(string)) existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.AfdEndpointName, id.RouteName) if err != nil { @@ -205,33 +222,69 @@ func resourceCdnFrontDoorRouteCreate(d *pluginsdk.ResourceData, meta interface{} return tf.ImportAsExistsError("azurerm_cdn_frontdoor_route", id.ID()) } + var origins []interface{} + var originGroup *cdn.ResourceReference + protocolsRaw := d.Get("supported_protocols").(*pluginsdk.Set).List() + originGroupRaw := d.Get("cdn_frontdoor_origin_group_id").(string) + ruleSetIdsRaw := d.Get("cdn_frontdoor_rule_set_ids").(*pluginsdk.Set).List() + originsRaw := d.Get("cdn_frontdoor_origin_ids").([]interface{}) + customDomainsRaw := d.Get("cdn_frontdoor_custom_domain_ids").(*pluginsdk.Set).List() httpsRedirect := d.Get("https_redirect_enabled").(bool) + linkToDefaultDomain := d.Get("link_to_default_domain").(bool) + // NOTE: If HTTPS Redirect is enabled the Supported Protocols must support both HTTP and HTTPS + // This configuration does not cause an error when provisioned, however the http requests that + // are supposed to be redirected to https remain http requests if httpsRedirect { - // NOTE: If HTTPS Redirect is enabled the Supported Protocols must support both HTTP and HTTPS - // This configuration does not cause an error when provisioned, however the http requests that - // are supposed to be redirected to https remain http requests - err := validate.SupportsBothHttpAndHttps(protocolsRaw, "https_redirect_enabled") + if err := validate.SupportsBothHttpAndHttps(protocolsRaw, "https_redirect_enabled"); err != nil { + return err + } + } + + normalizedCustomDomains, err := expandCustomDomains(customDomainsRaw) + if err != nil { + return err + } + + if !linkToDefaultDomain && len(normalizedCustomDomains) == 0 { + return fmt.Errorf("it is invalid to disable the 'LinkToDefaultDomain' for the CDN Front Door Route(Name: %s) since the route does not have any CDN Front Door Custom Domains associated with it", id.RouteName) + } else if len(normalizedCustomDomains) != 0 { + if err := sliceHasDuplicates(normalizedCustomDomains, "CDN FrontDoor Custom Domain"); err != nil { + return err + } + + if err := validateRoutesCustomDomainProfile(normalizedCustomDomains, id.RouteName, id.ProfileName); err != nil { + return err + } + } + + if originGroupRaw != "" { + id, err := parse.FrontDoorOriginGroupID(originGroupRaw) if err != nil { return err } + + originGroup = expandResourceReference(id.ID()) + } + + normalizedRuleSets, err := expandRuleSetIds(ruleSetIdsRaw) + if err != nil { + return err } props := cdn.Route{ RouteProperties: &cdn.RouteProperties{ - CacheConfiguration: expandCdnFrontdoorRouteCacheConfiguration(d.Get("cache").([]interface{})), - EnabledState: expandEnabledBool(d.Get("enabled").(bool)), - ForwardingProtocol: cdn.ForwardingProtocol(d.Get("forwarding_protocol").(string)), - HTTPSRedirect: expandEnabledBoolToRouteHttpsRedirect(httpsRedirect), - // NOTE: Hack due to the API's design, must create the route with the link to default - // domain as true else you will receive an error from the service this value is now - // controlled by the cdn_frontdoor_route_unlink_default_domain resource... :/ - LinkToDefaultDomain: cdn.LinkToDefaultDomainEnabled, - OriginGroup: expandResourceReference(d.Get("cdn_frontdoor_origin_group_id").(string)), + CustomDomains: expandCustomDomainActivatedResourceArray(normalizedCustomDomains), + CacheConfiguration: expandCdnFrontdoorRouteCacheConfiguration(d.Get("cache").([]interface{})), + EnabledState: expandEnabledBool(d.Get("enabled").(bool)), + ForwardingProtocol: cdn.ForwardingProtocol(d.Get("forwarding_protocol").(string)), + HTTPSRedirect: expandEnabledBoolToRouteHttpsRedirect(httpsRedirect), + LinkToDefaultDomain: expandEnabledBoolToLinkToDefaultDomain(linkToDefaultDomain), + OriginGroup: originGroup, PatternsToMatch: utils.ExpandStringSlice(d.Get("patterns_to_match").([]interface{})), - RuleSets: expandCdnFrontdoorRouteResourceReferenceArray(d.Get("cdn_frontdoor_rule_set_ids").([]interface{})), - SupportedProtocols: expandCdnFrontdoorRouteEndpointProtocolsArray(protocolsRaw), + RuleSets: expandRuleSetReferenceArray(normalizedRuleSets), + SupportedProtocols: expandEndpointProtocolsArray(protocolsRaw), }, } @@ -252,8 +305,17 @@ func resourceCdnFrontDoorRouteCreate(d *pluginsdk.ResourceData, meta interface{} // NOTE: These are not sent to the API, they are only here so Terraform // can provision/destroy the resources in the correct order. - if originIds := d.Get("cdn_frontdoor_origin_ids").([]interface{}); len(originIds) > 0 { - d.Set("cdn_frontdoor_origin_ids", utils.ExpandStringSlice(originIds)) + for _, origin := range originsRaw { + id, err := parse.FrontDoorOriginID(origin.(string)) + if err != nil { + return err + } + + origins = append(origins, id.ID()) + } + + if len(origins) != 0 { + d.Set("cdn_frontdoor_origin_ids", origins) } return resourceCdnFrontDoorRouteRead(d, meta) @@ -289,11 +351,18 @@ func resourceCdnFrontDoorRouteRead(d *pluginsdk.ResourceData, meta interface{}) d.Set("cdn_frontdoor_endpoint_id", parse.NewFrontDoorEndpointID(id.SubscriptionId, id.ResourceGroup, id.ProfileName, id.AfdEndpointName).ID()) if props := resp.RouteProperties; props != nil { + customDomains, err := flattenCustomDomainActivatedResourceArray(props.CustomDomains) + if err != nil { + return err + } + + d.Set("cdn_frontdoor_custom_domain_ids", customDomains) d.Set("enabled", flattenEnabledBool(props.EnabledState)) d.Set("forwarding_protocol", props.ForwardingProtocol) d.Set("https_redirect_enabled", flattenHttpsRedirectToBool(props.HTTPSRedirect)) d.Set("cdn_frontdoor_origin_path", props.OriginPath) d.Set("patterns_to_match", props.PatternsToMatch) + d.Set("link_to_default_domain", flattenLinkToDefaultDomainToBool(props.LinkToDefaultDomain)) if err := d.Set("cache", flattenCdnFrontdoorRouteCacheConfiguration(props.CacheConfiguration)); err != nil { return fmt.Errorf("setting `cache`: %+v", err) @@ -303,7 +372,7 @@ func resourceCdnFrontDoorRouteRead(d *pluginsdk.ResourceData, meta interface{}) return fmt.Errorf("setting `cdn_frontdoor_origin_group_id`: %+v", err) } - if err := d.Set("cdn_frontdoor_rule_set_ids", flattenCdnFrontdoorRouteResourceArray(props.RuleSets)); err != nil { + if err := d.Set("cdn_frontdoor_rule_set_ids", flattenRuleSetResourceArray(props.RuleSets)); err != nil { return fmt.Errorf("setting `cdn_frontdoor_rule_set_ids`: %+v", err) } @@ -326,84 +395,123 @@ func resourceCdnFrontDoorRouteUpdate(d *pluginsdk.ResourceData, meta interface{} return err } - // NOTE: I now need to lock this resource when updating because the - // cdn_frontdoor_route_unlink_default_domain resources may also be - // trying to update it as well... - locks.ByName(id.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(id.RouteName, cdnFrontDoorRouteResourceName) - existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.AfdEndpointName, id.RouteName) if err != nil { return fmt.Errorf("retrieving existing %s: %+v", *id, err) } + if existing.RouteProperties == nil { return fmt.Errorf("retrieving existing %s: 'properties' was nil", *id) } - var checkProtocols bool + // we need to lock the route for update because the custom domain + // association may also be trying to update the route as well... + locks.ByName(id.RouteName, cdnFrontDoorRouteResourceName) + defer locks.UnlockByName(id.RouteName, cdnFrontDoorRouteResourceName) + httpsRedirect := d.Get("https_redirect_enabled").(bool) protocolsRaw := d.Get("supported_protocols").(*pluginsdk.Set).List() + customDomainsRaw := d.Get("cdn_frontdoor_custom_domain_ids").(*pluginsdk.Set).List() + originGroupRaw := d.Get("cdn_frontdoor_origin_group_id").(string) + ruleSetIdsRaw := d.Get("cdn_frontdoor_rule_set_ids").(*pluginsdk.Set).List() + linkToDefaultDomain := d.Get("link_to_default_domain").(bool) + + // NOTE: If HTTPS Redirect is enabled the Supported Protocols must support both HTTP and HTTPS + // This configuration does not cause an error when provisioned, however the http requests that + // are supposed to be redirected to https remain http requests + if httpsRedirect { + if err := validate.SupportsBothHttpAndHttps(protocolsRaw, "https_redirect_enabled"); err != nil { + return err + } + } + + originGroup, err := parse.FrontDoorOriginGroupID(originGroupRaw) + if err != nil { + return err + } + + customDomains, err := expandCustomDomains(customDomainsRaw) + if err != nil { + return err + } + + if !linkToDefaultDomain && len(customDomains) == 0 { + return fmt.Errorf("it is invalid to disable the 'LinkToDefaultDomain' for the CDN Front Door Route(Name: %s) since the route does not have any CDN Front Door Custom Domains associated with it", id.RouteName) + } else if len(customDomains) != 0 { + if err := sliceHasDuplicates(customDomains, "CDN FrontDoor Custom Domain"); err != nil { + return err + } - props := azuresdkhacks.RouteUpdatePropertiesParameters{ - CustomDomains: existing.RouteProperties.CustomDomains, + if err := validateRoutesCustomDomainProfile(customDomains, id.RouteName, id.ProfileName); err != nil { + return err + } + } + + // NOTE: You need to always pass these two on update else you will + // disable your cache and disassociate your custom domains... + updateProps := azuresdkhacks.RouteUpdatePropertiesParameters{ + CustomDomains: existing.RouteProperties.CustomDomains, + CacheConfiguration: existing.RouteProperties.CacheConfiguration, } if d.HasChange("cache") { - props.CacheConfiguration = expandCdnFrontdoorRouteCacheConfiguration(d.Get("cache").([]interface{})) + updateProps.CacheConfiguration = expandCdnFrontdoorRouteCacheConfiguration(d.Get("cache").([]interface{})) } if d.HasChange("enabled") { - props.EnabledState = expandEnabledBool(d.Get("enabled").(bool)) + updateProps.EnabledState = expandEnabledBool(d.Get("enabled").(bool)) } if d.HasChange("forwarding_protocol") { - props.ForwardingProtocol = cdn.ForwardingProtocol(d.Get("forwarding_protocol").(string)) + updateProps.ForwardingProtocol = cdn.ForwardingProtocol(d.Get("forwarding_protocol").(string)) } if d.HasChange("https_redirect_enabled") { - checkProtocols = true - props.HTTPSRedirect = expandEnabledBoolToRouteHttpsRedirect(httpsRedirect) + updateProps.HTTPSRedirect = expandEnabledBoolToRouteHttpsRedirect(httpsRedirect) + } + + if d.HasChange("link_to_default_domain") { + updateProps.LinkToDefaultDomain = expandEnabledBoolToLinkToDefaultDomain(d.Get("link_to_default_domain").(bool)) + } + + if d.HasChange("cdn_frontdoor_custom_domain_ids") { + updateProps.CustomDomains = expandCustomDomainActivatedResourceArray(customDomains) } if d.HasChange("cdn_frontdoor_origin_group_id") { - props.OriginGroup = expandResourceReference(d.Get("cdn_frontdoor_origin_group_id").(string)) + updateProps.OriginGroup = expandResourceReference(originGroup.ID()) } if d.HasChange("cdn_frontdoor_origin_path") { - props.OriginPath = utils.String(d.Get("cdn_frontdoor_origin_path").(string)) + updateProps.OriginPath = utils.String(d.Get("cdn_frontdoor_origin_path").(string)) } if d.HasChange("patterns_to_match") { - props.PatternsToMatch = utils.ExpandStringSlice(d.Get("patterns_to_match").([]interface{})) + updateProps.PatternsToMatch = utils.ExpandStringSlice(d.Get("patterns_to_match").([]interface{})) } if d.HasChange("cdn_frontdoor_rule_set_ids") { - props.RuleSets = expandCdnFrontdoorRouteResourceReferenceArray(d.Get("cdn_frontdoor_rule_set_ids").([]interface{})) - } + ruleSets, err := expandRuleSetIds(ruleSetIdsRaw) + if err != nil { + return err + } - if d.HasChange("supported_protocols") { - checkProtocols = true - props.SupportedProtocols = expandCdnFrontdoorRouteEndpointProtocolsArray(protocolsRaw) + updateProps.RuleSets = expandRuleSetReferenceArray(ruleSets) } - updatePrarams := azuresdkhacks.RouteUpdateParameters{ - RouteUpdatePropertiesParameters: &props, + if d.HasChange("supported_protocols") { + updateProps.SupportedProtocols = expandEndpointProtocolsArray(protocolsRaw) } - if checkProtocols && httpsRedirect { - // NOTE: If HTTPS Redirect is enabled the Supported Protocols must support both HTTP and HTTPS - // This configuration does not cause an error when provisioned, however the http requests that - // are supposed to be redirected to https remain http requests - err := validate.SupportsBothHttpAndHttps(protocolsRaw, "https_redirect_enabled") - if err != nil { - return err - } + updateParams := azuresdkhacks.RouteUpdateParameters{ + RouteUpdatePropertiesParameters: &updateProps, } - future, err := workaroundsClient.Update(ctx, id.ResourceGroup, id.ProfileName, id.AfdEndpointName, id.RouteName, updatePrarams) + future, err := workaroundsClient.Update(ctx, id.ResourceGroup, id.ProfileName, id.AfdEndpointName, id.RouteName, updateParams) if err != nil { return fmt.Errorf("updating %s: %+v", *id, err) } + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { return fmt.Errorf("waiting for the update of %s: %+v", *id, err) } @@ -427,11 +535,6 @@ func resourceCdnFrontDoorRouteDelete(d *pluginsdk.ResourceData, meta interface{} return err } - // I now need to lock this resource when updating because the association resources may also be trying - // to update it as well... - locks.ByName(id.RouteName, cdnFrontDoorRouteResourceName) - defer locks.UnlockByName(id.RouteName, cdnFrontDoorRouteResourceName) - future, err := client.Delete(ctx, id.ResourceGroup, id.ProfileName, id.AfdEndpointName, id.RouteName) if err != nil { return fmt.Errorf("deleting %s: %+v", *id, err) @@ -444,7 +547,7 @@ func resourceCdnFrontDoorRouteDelete(d *pluginsdk.ResourceData, meta interface{} return nil } -func expandCdnFrontdoorRouteEndpointProtocolsArray(input []interface{}) *[]cdn.AFDEndpointProtocols { +func expandEndpointProtocolsArray(input []interface{}) *[]cdn.AFDEndpointProtocols { results := make([]cdn.AFDEndpointProtocols, 0) for _, item := range input { @@ -454,12 +557,13 @@ func expandCdnFrontdoorRouteEndpointProtocolsArray(input []interface{}) *[]cdn.A return &results } -func expandCdnFrontdoorRouteResourceReferenceArray(input []interface{}) *[]cdn.ResourceReference { +func expandRuleSetReferenceArray(input []interface{}) *[]cdn.ResourceReference { results := make([]cdn.ResourceReference, 0) + + // NOTE: The Frontdoor service, do not treat an empty object like an empty object + // if it is not nil they assume it is fully defined and then end up throwing errors + // when they attempt to get a value from one of the fields. if len(input) == 0 || input[0] == nil { - // NOTE: The Frontdoor service, do not treat an empty object like an empty object - // if it is not nil they assume it is fully defined and then end up throwing errors - // when they attempt to get a value from one of the fields. return nil } @@ -473,10 +577,10 @@ func expandCdnFrontdoorRouteResourceReferenceArray(input []interface{}) *[]cdn.R } func expandCdnFrontdoorRouteCacheConfiguration(input []interface{}) *cdn.AfdRouteCacheConfiguration { + // NOTE: If this is not an explicit nil you will receive an "Unsupported QueryStringCachingBehavior type: + // Property 'RouteV2.CacheConfiguration.QueryStringCachingBehavior' is required but it was not set" error. + // The Frontdoor service treats empty slices as if they are fully defined unlike other services. if len(input) == 0 || input[0] == nil { - // NOTE: If this is not an explicit nil you will receive an "Unsupported QueryStringCachingBehavior type: - // Property 'RouteV2.CacheConfiguration.QueryStringCachingBehavior' is required but it was not set" error. - // The Frontdoor service treats empty slices as if they are fully defined unlike other services. return nil } @@ -500,47 +604,17 @@ func expandCdnFrontdoorRouteCacheConfiguration(input []interface{}) *cdn.AfdRout return cacheConfiguration } -func expandCdnFrontdoorRouteActivatedResourceArray(input []interface{}) *[]cdn.ActivatedResourceReference { - results := make([]cdn.ActivatedResourceReference, 0) - if len(input) == 0 { - // NOTE: I have confirmed with the service team that this is required to be an explicit "nil" value, an empty - // list will not work. I had to modify the SDK to allow for nil which in the API means disassociate the custom domains. - return nil - } - - for _, customDomain := range input { - id := customDomain.(string) - results = append(results, cdn.ActivatedResourceReference{ - ID: utils.String(id), - }) - } - - return &results -} - -func flattenCdnFrontdoorRouteActivatedResourceArray(inputs *[]cdn.ActivatedResourceReference) []interface{} { - results := make([]interface{}, 0) - if inputs == nil { - return results - } - - for _, customDomain := range *inputs { - results = append(results, *customDomain.ID) - } - - return results -} - -func flattenCdnFrontdoorRouteResourceArray(input *[]cdn.ResourceReference) []interface{} { +func flattenRuleSetResourceArray(input *[]cdn.ResourceReference) []interface{} { results := make([]interface{}, 0) if input == nil { return results } - for _, item := range *input { - if item.ID != nil { - results = append(results, *item.ID) - } + // Normalize these values in the configuration file we know they are valid because they were set on the + // resource... if these are modified in the portal the will all be lowercased... + for _, ruleSet := range *input { + id, _ := parse.FrontDoorRuleSetID(*ruleSet.ID) + results = append(results, id.ID()) } return results @@ -570,9 +644,9 @@ func flattenCdnFrontdoorRouteCacheConfiguration(input *cdn.AfdRouteCacheConfigur queryParameters = flattenCsvToStringSlice(input.QueryParameters) } - cachingBehaviour := "" + cachingBehavior := "" if input.QueryStringCachingBehavior != "" { - cachingBehaviour = string(input.QueryStringCachingBehavior) + cachingBehavior = string(input.QueryStringCachingBehavior) } compressionEnabled := false @@ -588,7 +662,7 @@ func flattenCdnFrontdoorRouteCacheConfiguration(input *cdn.AfdRouteCacheConfigur map[string]interface{}{ "compression_enabled": compressionEnabled, "content_types_to_compress": contentTypesToCompress, - "query_string_caching_behavior": cachingBehaviour, + "query_string_caching_behavior": cachingBehavior, "query_strings": queryParameters, }, } diff --git a/internal/services/cdn/cdn_frontdoor_rule_resource.go b/internal/services/cdn/cdn_frontdoor_rule_resource.go index 5705b2c0a6e5..fb8bb18c40f2 100644 --- a/internal/services/cdn/cdn_frontdoor_rule_resource.go +++ b/internal/services/cdn/cdn_frontdoor_rule_resource.go @@ -273,7 +273,7 @@ func resourceCdnFrontDoorRule() *pluginsdk.Resource { }, false), }, - // NOTE: CSV implemented as a list, code alread written for the expaned and flatten to CSV + // NOTE: CSV implemented as a list, code already written for the expanded and flatten to CSV // not valid when IncludeAll or ExcludeAll behavior is defined "query_string_parameters": { Type: pluginsdk.TypeList, @@ -618,12 +618,12 @@ func resourceCdnFrontDoorRuleCreate(d *pluginsdk.ResourceData, meta interface{}) ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - ruleSetId, err := parse.FrontDoorRuleSetID(d.Get("cdn_frontdoor_rule_set_id").(string)) + ruleSet, err := parse.FrontDoorRuleSetID(d.Get("cdn_frontdoor_rule_set_id").(string)) if err != nil { return err } - id := parse.NewFrontDoorRuleID(ruleSetId.SubscriptionId, ruleSetId.ResourceGroup, ruleSetId.ProfileName, ruleSetId.RuleSetName, d.Get("name").(string)) + id := parse.NewFrontDoorRuleID(ruleSet.SubscriptionId, ruleSet.ResourceGroup, ruleSet.ProfileName, ruleSet.RuleSetName, d.Get("name").(string)) existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.RuleSetName, id.RuleName) if err != nil { @@ -654,7 +654,7 @@ func resourceCdnFrontDoorRuleCreate(d *pluginsdk.ResourceData, meta interface{}) Actions: &actions, Conditions: &conditions, MatchProcessingBehavior: matchProcessingBehaviorValue, - RuleSetName: &ruleSetId.RuleSetName, + RuleSetName: &ruleSet.RuleSetName, Order: utils.Int32(int32(order)), }, } @@ -683,7 +683,7 @@ func resourceCdnFrontDoorRuleRead(d *pluginsdk.ResourceData, meta interface{}) e return err } - ruleSetId := parse.NewFrontDoorRuleSetID(id.SubscriptionId, id.ResourceGroup, id.ProfileName, id.RuleSetName) + ruleSet := parse.NewFrontDoorRuleSetID(id.SubscriptionId, id.ResourceGroup, id.ProfileName, id.RuleSetName) resp, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.RuleSetName, id.RuleName) if err != nil { @@ -695,7 +695,7 @@ func resourceCdnFrontDoorRuleRead(d *pluginsdk.ResourceData, meta interface{}) e } d.Set("name", id.RuleName) - d.Set("cdn_frontdoor_rule_set_id", ruleSetId.ID()) + d.Set("cdn_frontdoor_rule_set_id", ruleSet.ID()) if props := resp.RuleProperties; props != nil { d.Set("behavior_on_match", props.MatchProcessingBehavior) @@ -703,7 +703,7 @@ func resourceCdnFrontDoorRuleRead(d *pluginsdk.ResourceData, meta interface{}) e // BUG: RuleSetName is not being returned by the API // Tracking issue opened: https://github.com/Azure/azure-rest-api-specs/issues/20560 - d.Set("cdn_frontdoor_rule_set_name", ruleSetId.RuleSetName) + d.Set("cdn_frontdoor_rule_set_name", ruleSet.RuleSetName) actions, err := flattenFrontdoorDeliveryRuleActions(props.Actions) if err != nil { @@ -845,7 +845,7 @@ func expandFrontdoorDeliveryRuleActions(input []interface{}) ([]cdn.BasicDeliver } if len(results) > 5 { - return nil, fmt.Errorf("the 'actions' match block may only contain upto 5 match actions, got %d", len(results)) + return nil, fmt.Errorf("the 'actions' match block may only contain up to 5 match actions, got %d", len(results)) } if err := validate.CdnFrontDoorActionsBlock(results); err != nil { @@ -903,7 +903,7 @@ func expandFrontdoorDeliveryRuleConditions(input []interface{}) ([]cdn.BasicDeli } if len(results) > 10 { - return nil, fmt.Errorf("the 'conditions' match block may only contain upto 10 match conditions, got %d", len(results)) + return nil, fmt.Errorf("the 'conditions' match block may only contain up to 10 match conditions, got %d", len(results)) } return results, nil diff --git a/internal/services/cdn/cdn_frontdoor_rule_set_resource.go b/internal/services/cdn/cdn_frontdoor_rule_set_resource.go index bbf6c411456b..40c07d5f0d02 100644 --- a/internal/services/cdn/cdn_frontdoor_rule_set_resource.go +++ b/internal/services/cdn/cdn_frontdoor_rule_set_resource.go @@ -54,12 +54,12 @@ func resourceCdnFrontDoorRuleSetCreate(d *pluginsdk.ResourceData, meta interface ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - profileId, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) + profile, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) if err != nil { return err } - id := parse.NewFrontDoorRuleSetID(profileId.SubscriptionId, profileId.ResourceGroup, profileId.ProfileName, d.Get("name").(string)) + id := parse.NewFrontDoorRuleSetID(profile.SubscriptionId, profile.ResourceGroup, profile.ProfileName, d.Get("name").(string)) if d.IsNewResource() { existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.RuleSetName) if err != nil { diff --git a/internal/services/cdn/cdn_frontdoor_secret_resource.go b/internal/services/cdn/cdn_frontdoor_secret_resource.go index 7c70e32ac9fe..0397dddf0415 100644 --- a/internal/services/cdn/cdn_frontdoor_secret_resource.go +++ b/internal/services/cdn/cdn_frontdoor_secret_resource.go @@ -98,12 +98,12 @@ func resourceCdnFrontDoorSecretCreate(d *pluginsdk.ResourceData, meta interface{ ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) defer cancel() - profileId, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) + profile, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) if err != nil { return err } - id := parse.NewFrontDoorSecretID(profileId.SubscriptionId, profileId.ResourceGroup, profileId.ProfileName, d.Get("name").(string)) + id := parse.NewFrontDoorSecretID(profile.SubscriptionId, profile.ResourceGroup, profile.ProfileName, d.Get("name").(string)) existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.SecretName) if err != nil { diff --git a/internal/services/cdn/cdn_frontdoor_security_policy_resource.go b/internal/services/cdn/cdn_frontdoor_security_policy_resource.go index 1ce5351706ec..ee8bb671f710 100644 --- a/internal/services/cdn/cdn_frontdoor_security_policy_resource.go +++ b/internal/services/cdn/cdn_frontdoor_security_policy_resource.go @@ -140,13 +140,13 @@ func resourceCdnFrontdoorSecurityPolicyCreate(d *pluginsdk.ResourceData, meta in defer cancel() // NOTE: The profile id is used to retrieve properties from the related profile that must match in this security policy - profileId, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) + profile, err := parse.FrontDoorProfileID(d.Get("cdn_frontdoor_profile_id").(string)) if err != nil { return err } securityPolicyName := d.Get("name").(string) - id := parse.NewFrontDoorSecurityPolicyID(profileId.SubscriptionId, profileId.ResourceGroup, profileId.ProfileName, securityPolicyName) + id := parse.NewFrontDoorSecurityPolicyID(profile.SubscriptionId, profile.ResourceGroup, profile.ProfileName, securityPolicyName) existing, err := client.Get(ctx, id.ResourceGroup, id.ProfileName, id.SecurityPolicyName) if err != nil { @@ -160,16 +160,16 @@ func resourceCdnFrontdoorSecurityPolicyCreate(d *pluginsdk.ResourceData, meta in } profileClient := meta.(*clients.Client).Cdn.FrontDoorProfileClient - profile, err := profileClient.Get(ctx, profileId.ResourceGroup, profileId.ProfileName) + resp, err := profileClient.Get(ctx, profile.ResourceGroup, profile.ProfileName) if err != nil { - return fmt.Errorf("unable to retrieve the 'sku_name' from the linked 'azurerm_cdn_frontdoor_profile': %+v", err) + return fmt.Errorf("unable to retrieve the 'sku_name' from the CDN FrontDoor Profile(Name: %q)': %+v", profile.ProfileName, err) } - if profile.Sku == nil { - return fmt.Errorf("retreving the parent %q: 'sku' was nil", *profileId) + if resp.Sku == nil { + return fmt.Errorf("the CDN FrontDoor Profile(Name: %q) 'sku' was nil", profile.ProfileName) } - isStandardSku := strings.HasPrefix(strings.ToLower(string(profile.Sku.Name)), "standard") + isStandardSku := strings.HasPrefix(strings.ToLower(string(resp.Sku.Name)), "standard") params, err := cdnfrontdoorsecurityparams.ExpandCdnFrontdoorFirewallPolicyParameters(d.Get("security_policies").([]interface{}), isStandardSku) if err != nil { diff --git a/internal/services/cdn/parse/front_door_custom_domain_association.go b/internal/services/cdn/parse/front_door_custom_domain_association.go new file mode 100644 index 000000000000..4a7c2db6a1d6 --- /dev/null +++ b/internal/services/cdn/parse/front_door_custom_domain_association.go @@ -0,0 +1,75 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + "strings" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +type FrontDoorCustomDomainAssociationId struct { + SubscriptionId string + ResourceGroup string + ProfileName string + AssociationName string +} + +func NewFrontDoorCustomDomainAssociationID(subscriptionId, resourceGroup, profileName, associationName string) FrontDoorCustomDomainAssociationId { + return FrontDoorCustomDomainAssociationId{ + SubscriptionId: subscriptionId, + ResourceGroup: resourceGroup, + ProfileName: profileName, + AssociationName: associationName, + } +} + +func (id FrontDoorCustomDomainAssociationId) String() string { + segments := []string{ + fmt.Sprintf("Association Name %q", id.AssociationName), + fmt.Sprintf("Profile Name %q", id.ProfileName), + fmt.Sprintf("Resource Group %q", id.ResourceGroup), + } + segmentsStr := strings.Join(segments, " / ") + return fmt.Sprintf("%s: (%s)", "Front Door Custom Domain Association", segmentsStr) +} + +func (id FrontDoorCustomDomainAssociationId) ID() string { + fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Cdn/profiles/%s/associations/%s" + return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ProfileName, id.AssociationName) +} + +// FrontDoorCustomDomainAssociationID parses a FrontDoorCustomDomainAssociation ID into an FrontDoorCustomDomainAssociationId struct +func FrontDoorCustomDomainAssociationID(input string) (*FrontDoorCustomDomainAssociationId, error) { + id, err := resourceids.ParseAzureResourceID(input) + if err != nil { + return nil, err + } + + resourceId := FrontDoorCustomDomainAssociationId{ + SubscriptionId: id.SubscriptionID, + ResourceGroup: id.ResourceGroup, + } + + if resourceId.SubscriptionId == "" { + return nil, fmt.Errorf("ID was missing the 'subscriptions' element") + } + + if resourceId.ResourceGroup == "" { + return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") + } + + if resourceId.ProfileName, err = id.PopSegment("profiles"); err != nil { + return nil, err + } + if resourceId.AssociationName, err = id.PopSegment("associations"); err != nil { + return nil, err + } + + if err := id.ValidateNoEmptySegments(input); err != nil { + return nil, err + } + + return &resourceId, nil +} diff --git a/internal/services/cdn/parse/front_door_custom_domain_association_test.go b/internal/services/cdn/parse/front_door_custom_domain_association_test.go new file mode 100644 index 000000000000..23bd0cc1ff5d --- /dev/null +++ b/internal/services/cdn/parse/front_door_custom_domain_association_test.go @@ -0,0 +1,128 @@ +package parse + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "testing" + + "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" +) + +var _ resourceids.Id = FrontDoorCustomDomainAssociationId{} + +func TestFrontDoorCustomDomainAssociationIDFormatter(t *testing.T) { + actual := NewFrontDoorCustomDomainAssociationID("12345678-1234-9876-4563-123456789012", "resGroup1", "profile1", "assoc1").ID() + expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/associations/assoc1" + if actual != expected { + t.Fatalf("Expected %q but got %q", expected, actual) + } +} + +func TestFrontDoorCustomDomainAssociationID(t *testing.T) { + testData := []struct { + Input string + Error bool + Expected *FrontDoorCustomDomainAssociationId + }{ + + { + // empty + Input: "", + Error: true, + }, + + { + // missing SubscriptionId + Input: "/", + Error: true, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Error: true, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Error: true, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Error: true, + }, + + { + // missing ProfileName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/", + Error: true, + }, + + { + // missing value for ProfileName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/", + Error: true, + }, + + { + // missing AssociationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/", + Error: true, + }, + + { + // missing value for AssociationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/associations/", + Error: true, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/associations/assoc1", + Expected: &FrontDoorCustomDomainAssociationId{ + SubscriptionId: "12345678-1234-9876-4563-123456789012", + ResourceGroup: "resGroup1", + ProfileName: "profile1", + AssociationName: "assoc1", + }, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CDN/PROFILES/PROFILE1/ASSOCIATIONS/ASSOC1", + Error: true, + }, + } + + for _, v := range testData { + t.Logf("[DEBUG] Testing %q", v.Input) + + actual, err := FrontDoorCustomDomainAssociationID(v.Input) + if err != nil { + if v.Error { + continue + } + + t.Fatalf("Expect a value but got an error: %s", err) + } + if v.Error { + t.Fatal("Expect an error but didn't get one") + } + + if actual.SubscriptionId != v.Expected.SubscriptionId { + t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) + } + if actual.ResourceGroup != v.Expected.ResourceGroup { + t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) + } + if actual.ProfileName != v.Expected.ProfileName { + t.Fatalf("Expected %q but got %q for ProfileName", v.Expected.ProfileName, actual.ProfileName) + } + if actual.AssociationName != v.Expected.AssociationName { + t.Fatalf("Expected %q but got %q for AssociationName", v.Expected.AssociationName, actual.AssociationName) + } + } +} diff --git a/internal/services/cdn/parse/front_door_route_disable_link_to_default_domain.go b/internal/services/cdn/parse/front_door_route_disable_link_to_default_domain.go index 7d7f2d051381..f40ea47a5add 100644 --- a/internal/services/cdn/parse/front_door_route_disable_link_to_default_domain.go +++ b/internal/services/cdn/parse/front_door_route_disable_link_to_default_domain.go @@ -85,83 +85,3 @@ func FrontDoorRouteDisableLinkToDefaultDomainID(input string) (*FrontDoorRouteDi return &resourceId, nil } - -// FrontDoorRouteDisableLinkToDefaultDomainIDInsensitively parses an FrontDoorRouteDisableLinkToDefaultDomain ID into an FrontDoorRouteDisableLinkToDefaultDomainId struct, insensitively -// This should only be used to parse an ID for rewriting, the FrontDoorRouteDisableLinkToDefaultDomainID -// method should be used instead for validation etc. -// -// Whilst this may seem strange, this enables Terraform have consistent casing -// which works around issues in Core, whilst handling broken API responses. -func FrontDoorRouteDisableLinkToDefaultDomainIDInsensitively(input string) (*FrontDoorRouteDisableLinkToDefaultDomainId, error) { - id, err := resourceids.ParseAzureResourceID(input) - if err != nil { - return nil, err - } - - resourceId := FrontDoorRouteDisableLinkToDefaultDomainId{ - SubscriptionId: id.SubscriptionID, - ResourceGroup: id.ResourceGroup, - } - - if resourceId.SubscriptionId == "" { - return nil, fmt.Errorf("ID was missing the 'subscriptions' element") - } - - if resourceId.ResourceGroup == "" { - return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") - } - - // find the correct casing for the 'profiles' segment - profilesKey := "profiles" - for key := range id.Path { - if strings.EqualFold(key, profilesKey) { - profilesKey = key - break - } - } - if resourceId.ProfileName, err = id.PopSegment(profilesKey); err != nil { - return nil, err - } - - // find the correct casing for the 'afdEndpoints' segment - afdEndpointsKey := "afdEndpoints" - for key := range id.Path { - if strings.EqualFold(key, afdEndpointsKey) { - afdEndpointsKey = key - break - } - } - if resourceId.AfdEndpointName, err = id.PopSegment(afdEndpointsKey); err != nil { - return nil, err - } - - // find the correct casing for the 'routes' segment - routesKey := "routes" - for key := range id.Path { - if strings.EqualFold(key, routesKey) { - routesKey = key - break - } - } - if resourceId.RouteName, err = id.PopSegment(routesKey); err != nil { - return nil, err - } - - // find the correct casing for the 'disableLinkToDefaultDomain' segment - disableLinkToDefaultDomainKey := "disableLinkToDefaultDomain" - for key := range id.Path { - if strings.EqualFold(key, disableLinkToDefaultDomainKey) { - disableLinkToDefaultDomainKey = key - break - } - } - if resourceId.DisableLinkToDefaultDomainName, err = id.PopSegment(disableLinkToDefaultDomainKey); err != nil { - return nil, err - } - - if err := id.ValidateNoEmptySegments(input); err != nil { - return nil, err - } - - return &resourceId, nil -} diff --git a/internal/services/cdn/parse/front_door_route_disable_link_to_default_domain_test.go b/internal/services/cdn/parse/front_door_route_disable_link_to_default_domain_test.go index 8319d7a5e9a5..6efe97eb10e6 100644 --- a/internal/services/cdn/parse/front_door_route_disable_link_to_default_domain_test.go +++ b/internal/services/cdn/parse/front_door_route_disable_link_to_default_domain_test.go @@ -158,177 +158,3 @@ func TestFrontDoorRouteDisableLinkToDefaultDomainID(t *testing.T) { } } } - -func TestFrontDoorRouteDisableLinkToDefaultDomainIDInsensitively(t *testing.T) { - testData := []struct { - Input string - Error bool - Expected *FrontDoorRouteDisableLinkToDefaultDomainId - }{ - - { - // empty - Input: "", - Error: true, - }, - - { - // missing SubscriptionId - Input: "/", - Error: true, - }, - - { - // missing value for SubscriptionId - Input: "/subscriptions/", - Error: true, - }, - - { - // missing ResourceGroup - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", - Error: true, - }, - - { - // missing value for ResourceGroup - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", - Error: true, - }, - - { - // missing ProfileName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/", - Error: true, - }, - - { - // missing value for ProfileName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/", - Error: true, - }, - - { - // missing AfdEndpointName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/", - Error: true, - }, - - { - // missing value for AfdEndpointName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdEndpoints/", - Error: true, - }, - - { - // missing RouteName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdEndpoints/endpoint1/", - Error: true, - }, - - { - // missing value for RouteName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdEndpoints/endpoint1/routes/", - Error: true, - }, - - { - // missing DisableLinkToDefaultDomainName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdEndpoints/endpoint1/routes/route1/", - Error: true, - }, - - { - // missing value for DisableLinkToDefaultDomainName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdEndpoints/endpoint1/routes/route1/disableLinkToDefaultDomain/", - Error: true, - }, - - { - // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdEndpoints/endpoint1/routes/route1/disableLinkToDefaultDomain/disableLinkToDefaultDomain1", - Expected: &FrontDoorRouteDisableLinkToDefaultDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resGroup1", - ProfileName: "profile1", - AfdEndpointName: "endpoint1", - RouteName: "route1", - DisableLinkToDefaultDomainName: "disableLinkToDefaultDomain1", - }, - }, - - { - // lower-cased segment names - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdendpoints/endpoint1/routes/route1/disablelinktodefaultdomain/disableLinkToDefaultDomain1", - Expected: &FrontDoorRouteDisableLinkToDefaultDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resGroup1", - ProfileName: "profile1", - AfdEndpointName: "endpoint1", - RouteName: "route1", - DisableLinkToDefaultDomainName: "disableLinkToDefaultDomain1", - }, - }, - - { - // upper-cased segment names - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/PROFILES/profile1/AFDENDPOINTS/endpoint1/ROUTES/route1/DISABLELINKTODEFAULTDOMAIN/disableLinkToDefaultDomain1", - Expected: &FrontDoorRouteDisableLinkToDefaultDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resGroup1", - ProfileName: "profile1", - AfdEndpointName: "endpoint1", - RouteName: "route1", - DisableLinkToDefaultDomainName: "disableLinkToDefaultDomain1", - }, - }, - - { - // mixed-cased segment names - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/PrOfIlEs/profile1/AfDeNdPoInTs/endpoint1/RoUtEs/route1/DiSaBlElInKtOdEfAuLtDoMaIn/disableLinkToDefaultDomain1", - Expected: &FrontDoorRouteDisableLinkToDefaultDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resGroup1", - ProfileName: "profile1", - AfdEndpointName: "endpoint1", - RouteName: "route1", - DisableLinkToDefaultDomainName: "disableLinkToDefaultDomain1", - }, - }, - } - - for _, v := range testData { - t.Logf("[DEBUG] Testing %q", v.Input) - - actual, err := FrontDoorRouteDisableLinkToDefaultDomainIDInsensitively(v.Input) - if err != nil { - if v.Error { - continue - } - - t.Fatalf("Expect a value but got an error: %s", err) - } - if v.Error { - t.Fatal("Expect an error but didn't get one") - } - - if actual.SubscriptionId != v.Expected.SubscriptionId { - t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) - } - if actual.ResourceGroup != v.Expected.ResourceGroup { - t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) - } - if actual.ProfileName != v.Expected.ProfileName { - t.Fatalf("Expected %q but got %q for ProfileName", v.Expected.ProfileName, actual.ProfileName) - } - if actual.AfdEndpointName != v.Expected.AfdEndpointName { - t.Fatalf("Expected %q but got %q for AfdEndpointName", v.Expected.AfdEndpointName, actual.AfdEndpointName) - } - if actual.RouteName != v.Expected.RouteName { - t.Fatalf("Expected %q but got %q for RouteName", v.Expected.RouteName, actual.RouteName) - } - if actual.DisableLinkToDefaultDomainName != v.Expected.DisableLinkToDefaultDomainName { - t.Fatalf("Expected %q but got %q for DisableLinkToDefaultDomainName", v.Expected.DisableLinkToDefaultDomainName, actual.DisableLinkToDefaultDomainName) - } - } -} diff --git a/internal/services/cdn/parse/frontdoor_custom_domain.go b/internal/services/cdn/parse/frontdoor_custom_domain.go deleted file mode 100644 index 3e7576e45d0b..000000000000 --- a/internal/services/cdn/parse/frontdoor_custom_domain.go +++ /dev/null @@ -1,131 +0,0 @@ -package parse - -// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten - -import ( - "fmt" - "strings" - - "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" -) - -type FrontdoorCustomDomainId struct { - SubscriptionId string - ResourceGroup string - ProfileName string - CustomDomainName string -} - -func NewFrontdoorCustomDomainID(subscriptionId, resourceGroup, profileName, customDomainName string) FrontdoorCustomDomainId { - return FrontdoorCustomDomainId{ - SubscriptionId: subscriptionId, - ResourceGroup: resourceGroup, - ProfileName: profileName, - CustomDomainName: customDomainName, - } -} - -func (id FrontdoorCustomDomainId) String() string { - segments := []string{ - fmt.Sprintf("Custom Domain Name %q", id.CustomDomainName), - fmt.Sprintf("Profile Name %q", id.ProfileName), - fmt.Sprintf("Resource Group %q", id.ResourceGroup), - } - segmentsStr := strings.Join(segments, " / ") - return fmt.Sprintf("%s: (%s)", "Frontdoor Custom Domain", segmentsStr) -} - -func (id FrontdoorCustomDomainId) ID() string { - fmtString := "/subscriptions/%s/resourceGroups/%s/providers/Microsoft.Cdn/profiles/%s/customDomains/%s" - return fmt.Sprintf(fmtString, id.SubscriptionId, id.ResourceGroup, id.ProfileName, id.CustomDomainName) -} - -// FrontdoorCustomDomainID parses a FrontdoorCustomDomain ID into an FrontdoorCustomDomainId struct -func FrontdoorCustomDomainID(input string) (*FrontdoorCustomDomainId, error) { - id, err := resourceids.ParseAzureResourceID(input) - if err != nil { - return nil, err - } - - resourceId := FrontdoorCustomDomainId{ - SubscriptionId: id.SubscriptionID, - ResourceGroup: id.ResourceGroup, - } - - if resourceId.SubscriptionId == "" { - return nil, fmt.Errorf("ID was missing the 'subscriptions' element") - } - - if resourceId.ResourceGroup == "" { - return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") - } - - if resourceId.ProfileName, err = id.PopSegment("profiles"); err != nil { - return nil, err - } - if resourceId.CustomDomainName, err = id.PopSegment("customDomains"); err != nil { - return nil, err - } - - if err := id.ValidateNoEmptySegments(input); err != nil { - return nil, err - } - - return &resourceId, nil -} - -// FrontdoorCustomDomainIDInsensitively parses an FrontdoorCustomDomain ID into an FrontdoorCustomDomainId struct, insensitively -// This should only be used to parse an ID for rewriting, the FrontdoorCustomDomainID -// method should be used instead for validation etc. -// -// Whilst this may seem strange, this enables Terraform have consistent casing -// which works around issues in Core, whilst handling broken API responses. -func FrontdoorCustomDomainIDInsensitively(input string) (*FrontdoorCustomDomainId, error) { - id, err := resourceids.ParseAzureResourceID(input) - if err != nil { - return nil, err - } - - resourceId := FrontdoorCustomDomainId{ - SubscriptionId: id.SubscriptionID, - ResourceGroup: id.ResourceGroup, - } - - if resourceId.SubscriptionId == "" { - return nil, fmt.Errorf("ID was missing the 'subscriptions' element") - } - - if resourceId.ResourceGroup == "" { - return nil, fmt.Errorf("ID was missing the 'resourceGroups' element") - } - - // find the correct casing for the 'profiles' segment - profilesKey := "profiles" - for key := range id.Path { - if strings.EqualFold(key, profilesKey) { - profilesKey = key - break - } - } - if resourceId.ProfileName, err = id.PopSegment(profilesKey); err != nil { - return nil, err - } - - // find the correct casing for the 'customDomains' segment - customDomainsKey := "customDomains" - for key := range id.Path { - if strings.EqualFold(key, customDomainsKey) { - customDomainsKey = key - break - } - } - if resourceId.CustomDomainName, err = id.PopSegment(customDomainsKey); err != nil { - return nil, err - } - - if err := id.ValidateNoEmptySegments(input); err != nil { - return nil, err - } - - return &resourceId, nil -} diff --git a/internal/services/cdn/parse/frontdoor_custom_domain_test.go b/internal/services/cdn/parse/frontdoor_custom_domain_test.go deleted file mode 100644 index 7373d1bdfa7d..000000000000 --- a/internal/services/cdn/parse/frontdoor_custom_domain_test.go +++ /dev/null @@ -1,264 +0,0 @@ -package parse - -// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten - -import ( - "testing" - - "github.com/hashicorp/go-azure-helpers/resourcemanager/resourceids" -) - -var _ resourceids.Id = FrontdoorCustomDomainId{} - -func TestFrontdoorCustomDomainIDFormatter(t *testing.T) { - actual := NewFrontdoorCustomDomainID("12345678-1234-9876-4563-123456789012", "resourceGroup1", "profile1", "customDomain1").ID() - expected := "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/profile1/customDomains/customDomain1" - if actual != expected { - t.Fatalf("Expected %q but got %q", expected, actual) - } -} - -func TestFrontdoorCustomDomainID(t *testing.T) { - testData := []struct { - Input string - Error bool - Expected *FrontdoorCustomDomainId - }{ - - { - // empty - Input: "", - Error: true, - }, - - { - // missing SubscriptionId - Input: "/", - Error: true, - }, - - { - // missing value for SubscriptionId - Input: "/subscriptions/", - Error: true, - }, - - { - // missing ResourceGroup - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", - Error: true, - }, - - { - // missing value for ResourceGroup - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", - Error: true, - }, - - { - // missing ProfileName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/", - Error: true, - }, - - { - // missing value for ProfileName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/", - Error: true, - }, - - { - // missing CustomDomainName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/profile1/", - Error: true, - }, - - { - // missing value for CustomDomainName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/profile1/customDomains/", - Error: true, - }, - - { - // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/profile1/customDomains/customDomain1", - Expected: &FrontdoorCustomDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resourceGroup1", - ProfileName: "profile1", - CustomDomainName: "customDomain1", - }, - }, - - { - // upper-cased - Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESOURCEGROUP1/PROVIDERS/MICROSOFT.CDN/PROFILES/PROFILE1/CUSTOMDOMAINS/CUSTOMDOMAIN1", - Error: true, - }, - } - - for _, v := range testData { - t.Logf("[DEBUG] Testing %q", v.Input) - - actual, err := FrontdoorCustomDomainID(v.Input) - if err != nil { - if v.Error { - continue - } - - t.Fatalf("Expect a value but got an error: %s", err) - } - if v.Error { - t.Fatal("Expect an error but didn't get one") - } - - if actual.SubscriptionId != v.Expected.SubscriptionId { - t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) - } - if actual.ResourceGroup != v.Expected.ResourceGroup { - t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) - } - if actual.ProfileName != v.Expected.ProfileName { - t.Fatalf("Expected %q but got %q for ProfileName", v.Expected.ProfileName, actual.ProfileName) - } - if actual.CustomDomainName != v.Expected.CustomDomainName { - t.Fatalf("Expected %q but got %q for CustomDomainName", v.Expected.CustomDomainName, actual.CustomDomainName) - } - } -} - -func TestFrontdoorCustomDomainIDInsensitively(t *testing.T) { - testData := []struct { - Input string - Error bool - Expected *FrontdoorCustomDomainId - }{ - - { - // empty - Input: "", - Error: true, - }, - - { - // missing SubscriptionId - Input: "/", - Error: true, - }, - - { - // missing value for SubscriptionId - Input: "/subscriptions/", - Error: true, - }, - - { - // missing ResourceGroup - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", - Error: true, - }, - - { - // missing value for ResourceGroup - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", - Error: true, - }, - - { - // missing ProfileName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/", - Error: true, - }, - - { - // missing value for ProfileName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/", - Error: true, - }, - - { - // missing CustomDomainName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/profile1/", - Error: true, - }, - - { - // missing value for CustomDomainName - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/profile1/customDomains/", - Error: true, - }, - - { - // valid - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/profile1/customDomains/customDomain1", - Expected: &FrontdoorCustomDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resourceGroup1", - ProfileName: "profile1", - CustomDomainName: "customDomain1", - }, - }, - - { - // lower-cased segment names - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/profiles/profile1/customdomains/customDomain1", - Expected: &FrontdoorCustomDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resourceGroup1", - ProfileName: "profile1", - CustomDomainName: "customDomain1", - }, - }, - - { - // upper-cased segment names - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/PROFILES/profile1/CUSTOMDOMAINS/customDomain1", - Expected: &FrontdoorCustomDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resourceGroup1", - ProfileName: "profile1", - CustomDomainName: "customDomain1", - }, - }, - - { - // mixed-cased segment names - Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resourceGroup1/providers/Microsoft.Cdn/PrOfIlEs/profile1/CuStOmDoMaInS/customDomain1", - Expected: &FrontdoorCustomDomainId{ - SubscriptionId: "12345678-1234-9876-4563-123456789012", - ResourceGroup: "resourceGroup1", - ProfileName: "profile1", - CustomDomainName: "customDomain1", - }, - }, - } - - for _, v := range testData { - t.Logf("[DEBUG] Testing %q", v.Input) - - actual, err := FrontdoorCustomDomainIDInsensitively(v.Input) - if err != nil { - if v.Error { - continue - } - - t.Fatalf("Expect a value but got an error: %s", err) - } - if v.Error { - t.Fatal("Expect an error but didn't get one") - } - - if actual.SubscriptionId != v.Expected.SubscriptionId { - t.Fatalf("Expected %q but got %q for SubscriptionId", v.Expected.SubscriptionId, actual.SubscriptionId) - } - if actual.ResourceGroup != v.Expected.ResourceGroup { - t.Fatalf("Expected %q but got %q for ResourceGroup", v.Expected.ResourceGroup, actual.ResourceGroup) - } - if actual.ProfileName != v.Expected.ProfileName { - t.Fatalf("Expected %q but got %q for ProfileName", v.Expected.ProfileName, actual.ProfileName) - } - if actual.CustomDomainName != v.Expected.CustomDomainName { - t.Fatalf("Expected %q but got %q for CustomDomainName", v.Expected.CustomDomainName, actual.CustomDomainName) - } - } -} diff --git a/internal/services/cdn/registration.go b/internal/services/cdn/registration.go index bfe488f71675..2a27a4340e4e 100644 --- a/internal/services/cdn/registration.go +++ b/internal/services/cdn/registration.go @@ -49,6 +49,7 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { // FrontDoor "azurerm_cdn_frontdoor_custom_domain": resourceCdnFrontDoorCustomDomain(), + "azurerm_cdn_frontdoor_custom_domain_association": resourceCdnFrontDoorCustomDomainAssociation(), "azurerm_cdn_frontdoor_endpoint": resourceCdnFrontDoorEndpoint(), "azurerm_cdn_frontdoor_firewall_policy": resourceCdnFrontDoorFirewallPolicy(), "azurerm_cdn_frontdoor_origin": resourceCdnFrontDoorOrigin(), diff --git a/internal/services/cdn/resourceids.go b/internal/services/cdn/resourceids.go index bc0f9bfa68f6..6dc16a0dfab6 100644 --- a/internal/services/cdn/resourceids.go +++ b/internal/services/cdn/resourceids.go @@ -19,4 +19,5 @@ package cdn //go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FrontDoorSecurityPolicy -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/securityPolicies/securityPolicy1 -rewrite=true // CDN FrontDoor "Associations" -//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FrontDoorRouteDisableLinkToDefaultDomain -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdEndpoints/endpoint1/routes/route1/disableLinkToDefaultDomain/disableLinkToDefaultDomain1 -rewrite=true +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FrontDoorRouteDisableLinkToDefaultDomain -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/afdEndpoints/endpoint1/routes/route1/disableLinkToDefaultDomain/disableLinkToDefaultDomain1 +//go:generate go run ../../tools/generator-resource-id/main.go -path=./ -name=FrontDoorCustomDomainAssociation -id=/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/associations/assoc1 diff --git a/internal/services/cdn/validate/front_door_custom_domain_association_id.go b/internal/services/cdn/validate/front_door_custom_domain_association_id.go new file mode 100644 index 000000000000..ef4591830316 --- /dev/null +++ b/internal/services/cdn/validate/front_door_custom_domain_association_id.go @@ -0,0 +1,23 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import ( + "fmt" + + "github.com/hashicorp/terraform-provider-azurerm/internal/services/cdn/parse" +) + +func FrontDoorCustomDomainAssociationID(input interface{}, key string) (warnings []string, errors []error) { + v, ok := input.(string) + if !ok { + errors = append(errors, fmt.Errorf("expected %q to be a string", key)) + return + } + + if _, err := parse.FrontDoorCustomDomainAssociationID(v); err != nil { + errors = append(errors, err) + } + + return +} diff --git a/internal/services/cdn/validate/front_door_custom_domain_association_id_test.go b/internal/services/cdn/validate/front_door_custom_domain_association_id_test.go new file mode 100644 index 000000000000..09a90130ad7d --- /dev/null +++ b/internal/services/cdn/validate/front_door_custom_domain_association_id_test.go @@ -0,0 +1,88 @@ +package validate + +// NOTE: this file is generated via 'go:generate' - manual changes will be overwritten + +import "testing" + +func TestFrontDoorCustomDomainAssociationID(t *testing.T) { + cases := []struct { + Input string + Valid bool + }{ + + { + // empty + Input: "", + Valid: false, + }, + + { + // missing SubscriptionId + Input: "/", + Valid: false, + }, + + { + // missing value for SubscriptionId + Input: "/subscriptions/", + Valid: false, + }, + + { + // missing ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/", + Valid: false, + }, + + { + // missing value for ResourceGroup + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/", + Valid: false, + }, + + { + // missing ProfileName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/", + Valid: false, + }, + + { + // missing value for ProfileName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/", + Valid: false, + }, + + { + // missing AssociationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/", + Valid: false, + }, + + { + // missing value for AssociationName + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/associations/", + Valid: false, + }, + + { + // valid + Input: "/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/resGroup1/providers/Microsoft.Cdn/profiles/profile1/associations/assoc1", + Valid: true, + }, + + { + // upper-cased + Input: "/SUBSCRIPTIONS/12345678-1234-9876-4563-123456789012/RESOURCEGROUPS/RESGROUP1/PROVIDERS/MICROSOFT.CDN/PROFILES/PROFILE1/ASSOCIATIONS/ASSOC1", + Valid: false, + }, + } + for _, tc := range cases { + t.Logf("[DEBUG] Testing Value %s", tc.Input) + _, errors := FrontDoorCustomDomainAssociationID(tc.Input, "test") + valid := len(errors) == 0 + + if tc.Valid != valid { + t.Fatalf("Expected %t but got %t", tc.Valid, valid) + } + } +} diff --git a/website/docs/r/cdn_frontdoor_custom_domain.html.markdown b/website/docs/r/cdn_frontdoor_custom_domain.html.markdown index da259aea115a..67b35d860140 100644 --- a/website/docs/r/cdn_frontdoor_custom_domain.html.markdown +++ b/website/docs/r/cdn_frontdoor_custom_domain.html.markdown @@ -36,8 +36,6 @@ resource "azurerm_cdn_frontdoor_custom_domain" "example" { dns_zone_id = azurerm_dns_zone.example.id host_name = "contoso.com" - associate_with_cdn_frontdoor_route_id = azurerm_cdn_frontdoor_route.example.id - tls { certificate_type = "ManagedCertificate" minimum_tls_version = "TLS12" @@ -85,8 +83,6 @@ The following arguments are supported: * `host_name` - (Required) The host name of the domain. Changing this forces a new CDN FrontDoor Custom Domain to be created. -* `associate_with_cdn_frontdoor_route_id` (Optional) - The resource ID of the CDN FrontDoor Route this CDN FrontDoor Custom Domain should be associated with. - * `dns_zone_id` - (Optional) The ID of the DNS Zone which should be used for this FrontDoor Custom Domain.