From 719dee7b6f62925e1d0431a1b91d434afb2d0f32 Mon Sep 17 00:00:00 2001 From: jackofallops Date: Thu, 21 Mar 2024 10:51:31 +0100 Subject: [PATCH 1/3] make compatible with Azure managed certificates --- .../container_app_custom_domain_resource.go | 41 ++++++++++++------- ...ntainer_app_custom_domain_resource_test.go | 40 ++++++++++++++++++ .../containerapps/container_app_resource.go | 2 +- .../container_app_custom_domain.html.markdown | 24 ++++++++++- 4 files changed, 90 insertions(+), 17 deletions(-) diff --git a/internal/services/containerapps/container_app_custom_domain_resource.go b/internal/services/containerapps/container_app_custom_domain_resource.go index db607af67413..fc0f3930e351 100644 --- a/internal/services/containerapps/container_app_custom_domain_resource.go +++ b/internal/services/containerapps/container_app_custom_domain_resource.go @@ -53,14 +53,15 @@ func (a ContainerAppCustomDomainResource) Arguments() map[string]*pluginsdk.Sche "container_app_environment_certificate_id": { Type: pluginsdk.TypeString, - Required: true, + Optional: true, ForceNew: true, + RequiredWith: []string{"certificate_binding_type"}, ValidateFunc: managedenvironments.ValidateCertificateID, }, "certificate_binding_type": { Type: pluginsdk.TypeString, - Required: true, + Optional: true, ForceNew: true, ValidateFunc: validation.StringInSlice(containerapps.PossibleValuesForBindingType(), false), Description: "The Binding type. Possible values include `Disabled` and `SniEnabled`.", @@ -106,9 +107,12 @@ func (a ContainerAppCustomDomainResource) Create() sdk.ResourceFunc { id := parse.NewContainerAppCustomDomainId(containerAppId.SubscriptionId, containerAppId.ResourceGroupName, containerAppId.ContainerAppName, model.Name) - certificateId, err := managedenvironments.ParseCertificateID(model.CertificateId) - if err != nil { - return err + var certificateId *managedenvironments.CertificateId + if model.CertificateId != "" { + certificateId, err = managedenvironments.ParseCertificateID(model.CertificateId) + if err != nil { + return err + } } containerApp, err := client.Get(ctx, *containerAppId) @@ -149,11 +153,17 @@ func (a ContainerAppCustomDomainResource) Create() sdk.ResourceFunc { customDomains = *existingCustomDomains } - customDomains = append(customDomains, containerapps.CustomDomain{ - BindingType: pointer.To(containerapps.BindingType(model.BindingType)), - CertificateId: pointer.To(certificateId.ID()), - Name: model.Name, - }) + customDomain := containerapps.CustomDomain{ + Name: model.Name, + BindingType: pointer.To(containerapps.BindingTypeDisabled), + } + + if certificateId != nil { + customDomain.CertificateId = pointer.To(certificateId.ID()) + customDomain.BindingType = pointer.To(containerapps.BindingType(model.BindingType)) + } + + customDomains = append(customDomains, customDomain) containerApp.Model.Properties.Configuration.Ingress.CustomDomains = pointer.To(customDomains) @@ -200,11 +210,14 @@ func (a ContainerAppCustomDomainResource) Read() sdk.ResourceFunc { found = true state.Name = id.CustomDomainName state.ContainerAppId = containerAppId.ID() - certId, err := managedenvironments.ParseCertificateIDInsensitively(pointer.From(v.CertificateId)) - if err != nil { - return err + if pointer.From(v.CertificateId) != "" { + certId, err := managedenvironments.ParseCertificateIDInsensitively(pointer.From(v.CertificateId)) + if err != nil { + return err + } + state.CertificateId = certId.ID() } - state.CertificateId = certId.ID() + state.BindingType = string(pointer.From(v.BindingType)) } } diff --git a/internal/services/containerapps/container_app_custom_domain_resource_test.go b/internal/services/containerapps/container_app_custom_domain_resource_test.go index 19b9e5d2470c..74f93e87be55 100644 --- a/internal/services/containerapps/container_app_custom_domain_resource_test.go +++ b/internal/services/containerapps/container_app_custom_domain_resource_test.go @@ -75,6 +75,25 @@ func TestAccContainerAppCustomDomainResource_basic(t *testing.T) { }) } +func TestAccContainerAppCustomDomainResource_managedCertificate(t *testing.T) { + if os.Getenv("ARM_TEST_DNS_ZONE") == "" || os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") == "" { + t.Skipf("Skipping as either ARM_TEST_DNS_ZONE or ARM_TEST_DATA_RESOURCE_GROUP is not set") + } + + data := acceptance.BuildTestData(t, "azurerm_container_app_custom_domain", "test") + r := ContainerAppCustomDomainResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.managedCertificate(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func TestAccContainerAppCustomDomainResource_multiple(t *testing.T) { if os.Getenv("ARM_TEST_DNS_ZONE") == "" || os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") == "" { t.Skipf("Skipping as either ARM_TEST_DNS_ZONE or ARM_TEST_DATA_RESOURCE_GROUP is not set") @@ -142,6 +161,27 @@ resource "azurerm_container_app_custom_domain" "test" { certificate_binding_type = "SniEnabled" } +`, r.template(data)) +} + +func (r ContainerAppCustomDomainResource) managedCertificate(data acceptance.TestData) string { + return fmt.Sprintf(` +provider azurerm { + features {} +} + +%s + +resource "azurerm_container_app_custom_domain" "test" { + name = trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid.") + container_app_id = azurerm_container_app.test.id + + lifecycle { + ignore_changes = [certificate_binding_type, container_app_environment_certificate_id] + } +} + + `, r.template(data)) } diff --git a/internal/services/containerapps/container_app_resource.go b/internal/services/containerapps/container_app_resource.go index 8159e489b447..95dc32215cbd 100644 --- a/internal/services/containerapps/container_app_resource.go +++ b/internal/services/containerapps/container_app_resource.go @@ -301,7 +301,7 @@ func (r ContainerAppResource) Read() sdk.ResourceFunc { func (r ContainerAppResource) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ - Timeout: 30 * time.Minute, + Timeout: 60 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.ContainerApps.ContainerAppClient diff --git a/website/docs/r/container_app_custom_domain.html.markdown b/website/docs/r/container_app_custom_domain.html.markdown index 15d8fcd232e0..3d91211aba36 100644 --- a/website/docs/r/container_app_custom_domain.html.markdown +++ b/website/docs/r/container_app_custom_domain.html.markdown @@ -91,6 +91,22 @@ resource "azurerm_container_app_custom_domain" "example" { ``` +## Example Usage - Managed Certificate + +```hcl +resource "azurerm_container_app_custom_domain" "example" { + name = trimprefix(azurerm_dns_txt_record.example.fqdn, "asuid.") + container_app_id = azurerm_container_app.example.id + + lifecycle { + // When using an Azure created Managed Certificate these values must be added to ignore_changes to prevent resource recreation. + ignore_changes = [certificate_binding_type, container_app_environment_certificate_id] + } +} + +``` + + ## Arguments Reference The following arguments are supported: @@ -101,9 +117,13 @@ The following arguments are supported: * `container_app_id` - (Required) The ID of the Container App to which this Custom Domain should be bound. Changing this forces a new resource to be created. -* `container_app_environment_certificate_id` - (Required) The ID of the Container App Environment Certificate to use. Changing this forces a new resource to be created. +* `container_app_environment_certificate_id` - (Optional) The ID of the Container App Environment Certificate to use. Changing this forces a new resource to be created. + +-> **NOTE:** Omit this value if you wish to use an Azure Managed certificate. You must create the relevant DNS verification steps before this process will be successful. + +* `certificate_binding_type` - (Optional) The Certificate Binding type. Possible values include `Disabled` and `SniEnabled`. Required with `container_app_environment_certificate_id`. Changing this forces a new resource to be created. -* `certificate_binding_type` - (Required) The Certificate Binding type. Possible values include `Disabled` and `SniEnabled`. Changing this forces a new resource to be created. +!> **NOTE:** If using an Azure Managed Certificate `container_app_environment_certificate_id` and `certificate_binding_type` should be added to `ignore_changes` to prevent resource recreation due to these values being modified asynchronously outside of Terraform. ## Timeouts From 308d0551bd4d69b04f663f1da2e9db8445b8b1a1 Mon Sep 17 00:00:00 2001 From: jackofallops Date: Mon, 25 Mar 2024 15:41:24 +0100 Subject: [PATCH 2/3] terrafmt docs --- website/docs/r/container_app_custom_domain.html.markdown | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/r/container_app_custom_domain.html.markdown b/website/docs/r/container_app_custom_domain.html.markdown index 3d91211aba36..8ed03c7a5952 100644 --- a/website/docs/r/container_app_custom_domain.html.markdown +++ b/website/docs/r/container_app_custom_domain.html.markdown @@ -95,9 +95,9 @@ resource "azurerm_container_app_custom_domain" "example" { ```hcl resource "azurerm_container_app_custom_domain" "example" { - name = trimprefix(azurerm_dns_txt_record.example.fqdn, "asuid.") - container_app_id = azurerm_container_app.example.id - + name = trimprefix(azurerm_dns_txt_record.example.fqdn, "asuid.") + container_app_id = azurerm_container_app.example.id + lifecycle { // When using an Azure created Managed Certificate these values must be added to ignore_changes to prevent resource recreation. ignore_changes = [certificate_binding_type, container_app_environment_certificate_id] From a56e2088cc90d6fb687186175a58b6cfb1fabc2c Mon Sep 17 00:00:00 2001 From: jackofallops Date: Thu, 25 Apr 2024 11:31:28 +0200 Subject: [PATCH 3/3] restore default timeout for delete to 30m --- internal/services/containerapps/container_app_resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/containerapps/container_app_resource.go b/internal/services/containerapps/container_app_resource.go index 95dc32215cbd..8159e489b447 100644 --- a/internal/services/containerapps/container_app_resource.go +++ b/internal/services/containerapps/container_app_resource.go @@ -301,7 +301,7 @@ func (r ContainerAppResource) Read() sdk.ResourceFunc { func (r ContainerAppResource) Delete() sdk.ResourceFunc { return sdk.ResourceFunc{ - Timeout: 60 * time.Minute, + Timeout: 30 * time.Minute, Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { client := metadata.Client.ContainerApps.ContainerAppClient