From fe3269d09c9e88133b9532e7c61c26a408ddc581 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 6 Nov 2025 11:01:11 +0800 Subject: [PATCH 1/7] New resource: azurerm_container_app_environment_managed_certificate --- ...nvironment_managed_certificate_resource.go | 243 ++++++++++++++ ...nment_managed_certificate_resource_test.go | 306 ++++++++++++++++++ .../services/containerapps/registration.go | 1 + ...ironment_managed_certificate.html.markdown | 85 +++++ 4 files changed, 635 insertions(+) create mode 100644 internal/services/containerapps/container_app_environment_managed_certificate_resource.go create mode 100644 internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go create mode 100644 website/docs/r/container_app_environment_managed_certificate.html.markdown diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go new file mode 100644 index 000000000000..3560c89dd7dd --- /dev/null +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go @@ -0,0 +1,243 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package containerapps + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-helpers/resourcemanager/commonschema" + "github.com/hashicorp/go-azure-helpers/resourcemanager/tags" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2025-07-01/managedenvironments" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/containerapps/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" +) + +type ContainerAppEnvironmentManagedCertificateResource struct{} + +type ContainerAppEnvironmentManagedCertificateModel struct { + Name string `tfschema:"name"` + ContainerAppEnvironmentId string `tfschema:"container_app_environment_id"` + SubjectName string `tfschema:"subject_name"` + DomainControlValidation string `tfschema:"domain_control_validation"` + Tags map[string]interface{} `tfschema:"tags"` + ValidationToken string `tfschema:"validation_token"` +} + +var _ sdk.ResourceWithUpdate = ContainerAppEnvironmentManagedCertificateResource{} + +func (r ContainerAppEnvironmentManagedCertificateResource) ModelObject() interface{} { + return &ContainerAppEnvironmentManagedCertificateModel{} +} + +func (r ContainerAppEnvironmentManagedCertificateResource) ResourceType() string { + return "azurerm_container_app_environment_managed_certificate" +} + +func (r ContainerAppEnvironmentManagedCertificateResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return managedenvironments.ValidateManagedCertificateID +} + +func (r ContainerAppEnvironmentManagedCertificateResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.CertificateName, + Description: "The name of the Container Apps Managed Certificate.", + }, + + "container_app_environment_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: managedenvironments.ValidateManagedEnvironmentID, + Description: "The Container App Managed Environment ID to configure this Managed Certificate on.", + }, + + "subject_name": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + Description: "The Subject Name of the Certificate. Must be a valid domain name.", + }, + + "domain_control_validation": { + Type: pluginsdk.TypeString, + Optional: true, + ForceNew: true, + Default: string(managedenvironments.ManagedCertificateDomainControlValidationHTTP), + ValidateFunc: validation.StringInSlice( + managedenvironments.PossibleValuesForManagedCertificateDomainControlValidation(), + false, + ), + Description: "The domain control validation type for the managed certificate. Possible values are `CNAME`, `HTTP` and `TXT`. Defaults to `HTTP`.", + }, + + "tags": commonschema.Tags(), + } +} + +func (r ContainerAppEnvironmentManagedCertificateResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "validation_token": { + Type: pluginsdk.TypeString, + Computed: true, + Description: "The validation token for the managed certificate.", + }, + } +} + +func (r ContainerAppEnvironmentManagedCertificateResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.ContainerApps.ManagedEnvironmentClient + + var model ContainerAppEnvironmentManagedCertificateModel + + if err := metadata.Decode(&model); err != nil { + return err + } + + envId, err := managedenvironments.ParseManagedEnvironmentID(model.ContainerAppEnvironmentId) + if err != nil { + return err + } + + id := managedenvironments.NewManagedCertificateID(envId.SubscriptionId, envId.ResourceGroupName, envId.ManagedEnvironmentName, model.Name) + + existing, err := client.ManagedCertificatesGet(ctx, id) + if err != nil { + if !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + } + + if !response.WasNotFound(existing.HttpResponse) { + return metadata.ResourceRequiresImport(r.ResourceType(), id) + } + + env, err := client.Get(ctx, *envId) + if err != nil { + return fmt.Errorf("reading %s for %s: %+v", *envId, id, err) + } + + certificate := managedenvironments.ManagedCertificate{ + Location: env.Model.Location, + Properties: &managedenvironments.ManagedCertificateProperties{ + SubjectName: pointer.To(model.SubjectName), + DomainControlValidation: pointer.To(managedenvironments.ManagedCertificateDomainControlValidation(model.DomainControlValidation)), + }, + Tags: tags.Expand(model.Tags), + } + + if err := client.ManagedCertificatesCreateOrUpdateThenPoll(ctx, id, certificate); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + + return nil + }, + } +} + +func (r ContainerAppEnvironmentManagedCertificateResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.ContainerApps.ManagedEnvironmentClient + + id, err := managedenvironments.ParseManagedCertificateID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + existing, err := client.ManagedCertificatesGet(ctx, *id) + if err != nil { + if response.WasNotFound(existing.HttpResponse) { + return metadata.MarkAsGone(id) + } + return fmt.Errorf("reading %s: %+v", *id, err) + } + + var state ContainerAppEnvironmentManagedCertificateModel + + state.Name = id.ManagedCertificateName + state.ContainerAppEnvironmentId = managedenvironments.NewManagedEnvironmentID(id.SubscriptionId, id.ResourceGroupName, id.ManagedEnvironmentName).ID() + + if model := existing.Model; model != nil { + state.Tags = tags.Flatten(model.Tags) + + if props := model.Properties; props != nil { + state.SubjectName = pointer.From(props.SubjectName) + state.DomainControlValidation = string(pointer.From(props.DomainControlValidation)) + state.ValidationToken = pointer.From(props.ValidationToken) + } + } + + return metadata.Encode(&state) + }, + } +} + +func (r ContainerAppEnvironmentManagedCertificateResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.ContainerApps.ManagedEnvironmentClient + + id, err := managedenvironments.ParseManagedCertificateID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if _, err := client.ManagedCertificatesDelete(ctx, *id); err != nil { + return fmt.Errorf("deleting %s: %+v", *id, err) + } + + return nil + }, + } +} + +func (r ContainerAppEnvironmentManagedCertificateResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 30 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.ContainerApps.ManagedEnvironmentClient + + var model ContainerAppEnvironmentManagedCertificateModel + + if err := metadata.Decode(&model); err != nil { + return err + } + + id, err := managedenvironments.ParseManagedCertificateID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if metadata.ResourceData.HasChange("tags") { + patch := managedenvironments.ManagedCertificatePatch{ + Tags: tags.Expand(model.Tags), + } + + if _, err = client.ManagedCertificatesUpdate(ctx, *id, patch); err != nil { + return fmt.Errorf("updating tags for %s: %+v", *id, err) + } + } + + return nil + }, + } +} diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go new file mode 100644 index 000000000000..0230b27cc64f --- /dev/null +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go @@ -0,0 +1,306 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package containerapps_test + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "github.com/hashicorp/go-azure-helpers/lang/response" + "github.com/hashicorp/go-azure-sdk/resource-manager/containerapps/2025-07-01/managedenvironments" + "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/tf/pluginsdk" +) + +type ContainerAppEnvironmentManagedCertificateResource struct{} + +func TestAccContainerAppEnvironmentManagedCertificate_basic(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_environment_managed_certificate", "test") + r := ContainerAppEnvironmentManagedCertificateResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccContainerAppEnvironmentManagedCertificate_requiresImport(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_environment_managed_certificate", "test") + r := ContainerAppEnvironmentManagedCertificateResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccContainerAppEnvironmentManagedCertificate_update(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_environment_managed_certificate", "test") + r := ContainerAppEnvironmentManagedCertificateResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.update(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccContainerAppEnvironmentManagedCertificate_domainControlValidationHTTP(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_environment_managed_certificate", "test") + r := ContainerAppEnvironmentManagedCertificateResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.domainControlValidationHTTP(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r ContainerAppEnvironmentManagedCertificateResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := managedenvironments.ParseManagedCertificateID(state.ID) + if err != nil { + return nil, err + } + + resp, err := client.ContainerApps.ManagedEnvironmentClient.ManagedCertificatesGet(ctx, *id) + if err != nil { + if response.WasNotFound(resp.HttpResponse) { + return pointer.To(false), nil + } + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return pointer.To(resp.Model != nil), nil +} + +func (r ContainerAppEnvironmentManagedCertificateResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%[1]s + +resource "azurerm_container_app_custom_domain" "test" { + name = trimsuffix(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] + } +} + +resource "azurerm_container_app_environment_managed_certificate" "test" { + name = "acctest-cacertmgd%[2]d" + container_app_environment_id = azurerm_container_app_environment.test.id + subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") + domain_control_validation = "HTTP" + + depends_on = [azurerm_container_app_custom_domain.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r ContainerAppEnvironmentManagedCertificateResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_container_app_environment_managed_certificate" "import" { + name = azurerm_container_app_environment_managed_certificate.test.name + container_app_environment_id = azurerm_container_app_environment_managed_certificate.test.container_app_environment_id + subject_name = azurerm_container_app_environment_managed_certificate.test.subject_name + domain_control_validation = "HTTP" +} +`, r.basic(data)) +} + +func (r ContainerAppEnvironmentManagedCertificateResource) update(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%[1]s + +resource "azurerm_container_app_custom_domain" "test" { + name = trimsuffix(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] + } +} + +resource "azurerm_container_app_environment_managed_certificate" "test" { + name = "acctest-cacertmgd%[2]d" + container_app_environment_id = azurerm_container_app_environment.test.id + subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") + domain_control_validation = "HTTP" + + tags = { + environment = "testing" + } + + depends_on = [azurerm_container_app_custom_domain.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r ContainerAppEnvironmentManagedCertificateResource) domainControlValidationHTTP(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%[1]s + +resource "azurerm_container_app_custom_domain" "test" { + name = trimsuffix(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] + } +} + +resource "azurerm_container_app_environment_managed_certificate" "test" { + name = "acctest-cacertmgd%[2]d" + container_app_environment_id = azurerm_container_app_environment.test.id + subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") + domain_control_validation = "HTTP" + + depends_on = [azurerm_container_app_custom_domain.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r ContainerAppEnvironmentManagedCertificateResource) template(data acceptance.TestData) string { + dnsZone := os.Getenv("ARM_TEST_DNS_ZONE") + dataResourceGroup := os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") + + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-CAEnv-%[1]d" + location = "%[2]s" +} + +data "azurerm_dns_zone" "test" { + name = "%[3]s" + resource_group_name = "%[4]s" +} + +resource "azurerm_log_analytics_workspace" "test" { + name = "acctestCAEnv-%[1]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + sku = "PerGB2018" + retention_in_days = 30 +} + +resource "azurerm_container_app_environment" "test" { + name = "acctest-CAEnv%[1]d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + log_analytics_workspace_id = azurerm_log_analytics_workspace.test.id +} + +resource "azurerm_container_app" "test" { + name = "acctest-capp-%[1]d" + resource_group_name = azurerm_resource_group.test.name + container_app_environment_id = azurerm_container_app_environment.test.id + revision_mode = "Single" + + template { + container { + name = "acctest-cont-%[1]d" + image = "jackofallops/azure-containerapps-python-acctest:v0.0.1" + cpu = 0.25 + memory = "0.5Gi" + } + } + + ingress { + allow_insecure_connections = true + external_enabled = true + target_port = 5000 + transport = "http" + + traffic_weight { + latest_revision = true + percentage = 100 + } + } +} + +resource "azurerm_dns_cname_record" "test" { + name = "containerapp%[1]d" + resource_group_name = data.azurerm_dns_zone.test.resource_group_name + zone_name = data.azurerm_dns_zone.test.name + ttl = 300 + record = azurerm_container_app.test.latest_revision_fqdn +} + +resource "azurerm_dns_txt_record" "test" { + name = "asuid.containerapp%[1]d" + resource_group_name = data.azurerm_dns_zone.test.resource_group_name + zone_name = data.azurerm_dns_zone.test.name + ttl = 60 + + record { + value = azurerm_container_app.test.custom_domain_verification_id + } + + depends_on = [ + azurerm_dns_cname_record.test + ] +} +`, data.RandomInteger, data.Locations.Primary, dnsZone, dataResourceGroup) +} diff --git a/internal/services/containerapps/registration.go b/internal/services/containerapps/registration.go index e9553b12bd5c..a8710e70c118 100644 --- a/internal/services/containerapps/registration.go +++ b/internal/services/containerapps/registration.go @@ -38,6 +38,7 @@ func (r Registration) Resources() []sdk.Resource { ContainerAppEnvironmentCertificateResource{}, ContainerAppEnvironmentCustomDomainResource{}, ContainerAppEnvironmentDaprComponentResource{}, + ContainerAppEnvironmentManagedCertificateResource{}, ContainerAppEnvironmentResource{}, ContainerAppEnvironmentStorageResource{}, ContainerAppResource{}, diff --git a/website/docs/r/container_app_environment_managed_certificate.html.markdown b/website/docs/r/container_app_environment_managed_certificate.html.markdown new file mode 100644 index 000000000000..97e2063613ae --- /dev/null +++ b/website/docs/r/container_app_environment_managed_certificate.html.markdown @@ -0,0 +1,85 @@ +--- +subcategory: "Container Apps" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_container_app_environment_managed_certificate" +description: |- + Manages a Container App Environment Managed Certificate. +--- + +# azurerm_container_app_environment_managed_certificate + +Manages a Container App Environment Managed Certificate. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_log_analytics_workspace" "example" { + name = "example-workspace" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + sku = "PerGB2018" + retention_in_days = 30 +} + +resource "azurerm_container_app_environment" "example" { + name = "example-environment" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + log_analytics_workspace_id = azurerm_log_analytics_workspace.example.id +} + +resource "azurerm_container_app_environment_managed_certificate" "example" { + name = "example-managed-cert" + container_app_environment_id = azurerm_container_app_environment.example.id + subject_name = "example.com" + domain_control_validation = "HTTP" +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) The name of the Container Apps Environment Managed Certificate. Changing this forces a new resource to be created. + +* `container_app_environment_id` - (Required) The Container App Managed Environment ID to configure this Managed Certificate on. Changing this forces a new resource to be created. + +* `subject_name` - (Required) The Subject Name of the Certificate. Must be a valid domain name. Changing this forces a new resource to be created. + +--- + +* `domain_control_validation` - (Optional) The domain control validation type for the managed certificate. Possible values are `CNAME`, `HTTP` and `TXT`. Defaults to `HTTP`. Changing this forces a new resource to be created. + +~> **Note:** The supported validation methods depend on the domain. Azure will validate domain ownership based on the specified method. `HTTP` validation requires an HTTP endpoint at the domain, `CNAME` validation requires DNS CNAME record configuration, and `TXT` validation requires DNS TXT record configuration. + +* `tags` - (Optional) A mapping of tags to assign to the resource. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Container App Environment Managed Certificate. + +* `validation_token` - The validation token for the managed certificate. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/configure#define-operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Container App Environment Managed Certificate. +* `read` - (Defaults to 5 minutes) Used when retrieving the Container App Environment Managed Certificate. +* `update` - (Defaults to 30 minutes) Used when updating the Container App Environment Managed Certificate. +* `delete` - (Defaults to 30 minutes) Used when deleting the Container App Environment Managed Certificate. + +## Import + +A Container App Environment Managed Certificate can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_container_app_environment_managed_certificate.example "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.App/managedEnvironments/myenv/managedCertificates/mycertificate" +``` From c10a6f95e648cd59d69221eb169dd295a5d261e7 Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 19 Nov 2025 13:42:04 +0800 Subject: [PATCH 2/7] Fixing indent --- ...nment_managed_certificate_resource_test.go | 32 +++++++++---------- ...ironment_managed_certificate.html.markdown | 14 +++++--- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go index 0230b27cc64f..319a202cb142 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go @@ -138,10 +138,10 @@ resource "azurerm_container_app_custom_domain" "test" { } resource "azurerm_container_app_environment_managed_certificate" "test" { - name = "acctest-cacertmgd%[2]d" - container_app_environment_id = azurerm_container_app_environment.test.id - subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") - domain_control_validation = "HTTP" + name = "acctest-cacertmgd%[2]d" + container_app_environment_id = azurerm_container_app_environment.test.id + subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") + domain_control_validation = "HTTP" depends_on = [azurerm_container_app_custom_domain.test] } @@ -153,10 +153,10 @@ func (r ContainerAppEnvironmentManagedCertificateResource) requiresImport(data a %s resource "azurerm_container_app_environment_managed_certificate" "import" { - name = azurerm_container_app_environment_managed_certificate.test.name - container_app_environment_id = azurerm_container_app_environment_managed_certificate.test.container_app_environment_id - subject_name = azurerm_container_app_environment_managed_certificate.test.subject_name - domain_control_validation = "HTTP" + name = azurerm_container_app_environment_managed_certificate.test.name + container_app_environment_id = azurerm_container_app_environment_managed_certificate.test.container_app_environment_id + subject_name = azurerm_container_app_environment_managed_certificate.test.subject_name + domain_control_validation = "HTTP" } `, r.basic(data)) } @@ -180,10 +180,10 @@ resource "azurerm_container_app_custom_domain" "test" { } resource "azurerm_container_app_environment_managed_certificate" "test" { - name = "acctest-cacertmgd%[2]d" - container_app_environment_id = azurerm_container_app_environment.test.id - subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") - domain_control_validation = "HTTP" + name = "acctest-cacertmgd%[2]d" + container_app_environment_id = azurerm_container_app_environment.test.id + subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") + domain_control_validation = "HTTP" tags = { environment = "testing" @@ -212,10 +212,10 @@ resource "azurerm_container_app_custom_domain" "test" { } resource "azurerm_container_app_environment_managed_certificate" "test" { - name = "acctest-cacertmgd%[2]d" - container_app_environment_id = azurerm_container_app_environment.test.id - subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") - domain_control_validation = "HTTP" + name = "acctest-cacertmgd%[2]d" + container_app_environment_id = azurerm_container_app_environment.test.id + subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") + domain_control_validation = "HTTP" depends_on = [azurerm_container_app_custom_domain.test] } diff --git a/website/docs/r/container_app_environment_managed_certificate.html.markdown b/website/docs/r/container_app_environment_managed_certificate.html.markdown index 97e2063613ae..b48cc4ee9e69 100644 --- a/website/docs/r/container_app_environment_managed_certificate.html.markdown +++ b/website/docs/r/container_app_environment_managed_certificate.html.markdown @@ -34,10 +34,10 @@ resource "azurerm_container_app_environment" "example" { } resource "azurerm_container_app_environment_managed_certificate" "example" { - name = "example-managed-cert" - container_app_environment_id = azurerm_container_app_environment.example.id - subject_name = "example.com" - domain_control_validation = "HTTP" + name = "example-managed-cert" + container_app_environment_id = azurerm_container_app_environment.example.id + subject_name = "example.com" + domain_control_validation = "HTTP" } ``` @@ -83,3 +83,9 @@ A Container App Environment Managed Certificate can be imported using the `resou ```shell terraform import azurerm_container_app_environment_managed_certificate.example "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/resGroup1/providers/Microsoft.App/managedEnvironments/myenv/managedCertificates/mycertificate" ``` + +## API Providers + +This resource uses the following Azure API Providers: + +* `Microsoft.App` - 2025-07-01 From 4baa2664c7d4af74c090c2e556baad697041a821 Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 7 Apr 2026 17:32:16 +0800 Subject: [PATCH 3/7] Address review feedback from sreallymatt - Use consistent error message format with retrieving/updating - Add nil check for env.Model before accessing nested fields - Use pointer.FromEnum for DomainControlValidation - Add CNAME and TXT domain control validation tests - Shorten DNS record name to avoid exceeding 64 char limit - Add custom domain with depends_on to docs example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- ...nvironment_managed_certificate_resource.go | 12 ++- ...nment_managed_certificate_resource_test.go | 98 ++++++++++++++++++- ...ironment_managed_certificate.html.markdown | 38 +++++++ 3 files changed, 142 insertions(+), 6 deletions(-) diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go index 3560c89dd7dd..e4c3a80624e0 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go @@ -128,7 +128,11 @@ func (r ContainerAppEnvironmentManagedCertificateResource) Create() sdk.Resource env, err := client.Get(ctx, *envId) if err != nil { - return fmt.Errorf("reading %s for %s: %+v", *envId, id, err) + return fmt.Errorf("retrieving %s: %+v", *envId, err) + } + + if env.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", envId) } certificate := managedenvironments.ManagedCertificate{ @@ -167,7 +171,7 @@ func (r ContainerAppEnvironmentManagedCertificateResource) Read() sdk.ResourceFu if response.WasNotFound(existing.HttpResponse) { return metadata.MarkAsGone(id) } - return fmt.Errorf("reading %s: %+v", *id, err) + return fmt.Errorf("retrieving %s: %+v", *id, err) } var state ContainerAppEnvironmentManagedCertificateModel @@ -180,7 +184,7 @@ func (r ContainerAppEnvironmentManagedCertificateResource) Read() sdk.ResourceFu if props := model.Properties; props != nil { state.SubjectName = pointer.From(props.SubjectName) - state.DomainControlValidation = string(pointer.From(props.DomainControlValidation)) + state.DomainControlValidation = pointer.FromEnum(props.DomainControlValidation) state.ValidationToken = pointer.From(props.ValidationToken) } } @@ -233,7 +237,7 @@ func (r ContainerAppEnvironmentManagedCertificateResource) Update() sdk.Resource } if _, err = client.ManagedCertificatesUpdate(ctx, *id, patch); err != nil { - return fmt.Errorf("updating tags for %s: %+v", *id, err) + return fmt.Errorf("updating %s: %+v", *id, err) } } diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go index 319a202cb142..f3093d8cab03 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go @@ -103,6 +103,44 @@ func TestAccContainerAppEnvironmentManagedCertificate_domainControlValidationHTT }) } +func TestAccContainerAppEnvironmentManagedCertificate_domainControlValidationCNAME(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_environment_managed_certificate", "test") + r := ContainerAppEnvironmentManagedCertificateResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.domainControlValidationCNAME(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccContainerAppEnvironmentManagedCertificate_domainControlValidationTXT(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_environment_managed_certificate", "test") + r := ContainerAppEnvironmentManagedCertificateResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.domainControlValidationTXT(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (r ContainerAppEnvironmentManagedCertificateResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := managedenvironments.ParseManagedCertificateID(state.ID) if err != nil { @@ -222,6 +260,62 @@ resource "azurerm_container_app_environment_managed_certificate" "test" { `, r.template(data), data.RandomInteger) } +func (r ContainerAppEnvironmentManagedCertificateResource) domainControlValidationCNAME(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%[1]s + +resource "azurerm_container_app_custom_domain" "test" { + name = trimsuffix(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] + } +} + +resource "azurerm_container_app_environment_managed_certificate" "test" { + name = "acctest-cacertmgd%[2]d" + container_app_environment_id = azurerm_container_app_environment.test.id + subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") + domain_control_validation = "CNAME" + + depends_on = [azurerm_container_app_custom_domain.test] +} +`, r.template(data), data.RandomInteger) +} + +func (r ContainerAppEnvironmentManagedCertificateResource) domainControlValidationTXT(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%[1]s + +resource "azurerm_container_app_custom_domain" "test" { + name = trimsuffix(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] + } +} + +resource "azurerm_container_app_environment_managed_certificate" "test" { + name = "acctest-cacertmgd%[2]d" + container_app_environment_id = azurerm_container_app_environment.test.id + subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") + domain_control_validation = "TXT" + + depends_on = [azurerm_container_app_custom_domain.test] +} +`, r.template(data), data.RandomInteger) +} + func (r ContainerAppEnvironmentManagedCertificateResource) template(data acceptance.TestData) string { dnsZone := os.Getenv("ARM_TEST_DNS_ZONE") dataResourceGroup := os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") @@ -281,7 +375,7 @@ resource "azurerm_container_app" "test" { } resource "azurerm_dns_cname_record" "test" { - name = "containerapp%[1]d" + name = "contapp%[1]d" resource_group_name = data.azurerm_dns_zone.test.resource_group_name zone_name = data.azurerm_dns_zone.test.name ttl = 300 @@ -289,7 +383,7 @@ resource "azurerm_dns_cname_record" "test" { } resource "azurerm_dns_txt_record" "test" { - name = "asuid.containerapp%[1]d" + name = "asuid.contapp%[1]d" resource_group_name = data.azurerm_dns_zone.test.resource_group_name zone_name = data.azurerm_dns_zone.test.name ttl = 60 diff --git a/website/docs/r/container_app_environment_managed_certificate.html.markdown b/website/docs/r/container_app_environment_managed_certificate.html.markdown index b48cc4ee9e69..faa99fe61f80 100644 --- a/website/docs/r/container_app_environment_managed_certificate.html.markdown +++ b/website/docs/r/container_app_environment_managed_certificate.html.markdown @@ -33,11 +33,49 @@ resource "azurerm_container_app_environment" "example" { log_analytics_workspace_id = azurerm_log_analytics_workspace.example.id } +resource "azurerm_container_app" "example" { + name = "example-app" + resource_group_name = azurerm_resource_group.example.name + container_app_environment_id = azurerm_container_app_environment.example.id + revision_mode = "Single" + + template { + container { + name = "example-container" + image = "mcr.microsoft.com/k8se/quickstart:latest" + cpu = 0.25 + memory = "0.5Gi" + } + } + + ingress { + external_enabled = true + target_port = 80 + transport = "http" + + traffic_weight { + latest_revision = true + percentage = 100 + } + } +} + +resource "azurerm_container_app_custom_domain" "example" { + name = "example.com" + container_app_id = azurerm_container_app.example.id + + lifecycle { + ignore_changes = [certificate_binding_type, container_app_environment_certificate_id] + } +} + resource "azurerm_container_app_environment_managed_certificate" "example" { name = "example-managed-cert" container_app_environment_id = azurerm_container_app_environment.example.id subject_name = "example.com" domain_control_validation = "HTTP" + + depends_on = [azurerm_container_app_custom_domain.example] } ``` From 630b677fdfba3943ff212cff5cf3f9f331ea6c52 Mon Sep 17 00:00:00 2001 From: JT Date: Thu, 9 Apr 2026 09:37:55 +0800 Subject: [PATCH 4/7] Increase create timeout for managed certificate to 60 minutes TXT domain control validation requires longer polling time due to DNS propagation and CA signing chain being slower than HTTP/CNAME methods. Co-Authored-By: Claude Opus 4.6 --- .../container_app_environment_managed_certificate_resource.go | 2 +- .../container_app_environment_managed_certificate.html.markdown | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go index e4c3a80624e0..21d461914b81 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go @@ -98,7 +98,7 @@ func (r ContainerAppEnvironmentManagedCertificateResource) Attributes() map[stri func (r ContainerAppEnvironmentManagedCertificateResource) Create() 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.ManagedEnvironmentClient diff --git a/website/docs/r/container_app_environment_managed_certificate.html.markdown b/website/docs/r/container_app_environment_managed_certificate.html.markdown index faa99fe61f80..e0c09e750fe1 100644 --- a/website/docs/r/container_app_environment_managed_certificate.html.markdown +++ b/website/docs/r/container_app_environment_managed_certificate.html.markdown @@ -109,7 +109,7 @@ In addition to the Arguments listed above - the following Attributes are exporte The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/configure#define-operation-timeouts) for certain actions: -* `create` - (Defaults to 30 minutes) Used when creating the Container App Environment Managed Certificate. +* `create` - (Defaults to 1 hour) Used when creating the Container App Environment Managed Certificate. * `read` - (Defaults to 5 minutes) Used when retrieving the Container App Environment Managed Certificate. * `update` - (Defaults to 30 minutes) Used when updating the Container App Environment Managed Certificate. * `delete` - (Defaults to 30 minutes) Used when deleting the Container App Environment Managed Certificate. From 74be517693f099d15b84cad7789d7bb828fe8027 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 10 Apr 2026 09:55:58 +0800 Subject: [PATCH 5/7] Fix CNAME record target in managed certificate test The CNAME record was pointing to latest_revision_fqdn instead of the container app's ingress FQDN. Azure requires the CNAME to map directly to the app's generated domain name (ingress fqdn) for domain validation to succeed. This was causing TXT validation to stay in Pending state indefinitely, resulting in context deadline exceeded. Also reverts the temporary timeout increase from the previous commit. Co-Authored-By: Claude Opus 4.6 --- .../container_app_environment_managed_certificate_resource.go | 2 +- ...ntainer_app_environment_managed_certificate_resource_test.go | 2 +- .../container_app_environment_managed_certificate.html.markdown | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go index 21d461914b81..e4c3a80624e0 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go @@ -98,7 +98,7 @@ func (r ContainerAppEnvironmentManagedCertificateResource) Attributes() map[stri func (r ContainerAppEnvironmentManagedCertificateResource) Create() 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.ManagedEnvironmentClient diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go index f3093d8cab03..ed08f435869d 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go @@ -379,7 +379,7 @@ resource "azurerm_dns_cname_record" "test" { resource_group_name = data.azurerm_dns_zone.test.resource_group_name zone_name = data.azurerm_dns_zone.test.name ttl = 300 - record = azurerm_container_app.test.latest_revision_fqdn + record = azurerm_container_app.test.ingress[0].fqdn } resource "azurerm_dns_txt_record" "test" { diff --git a/website/docs/r/container_app_environment_managed_certificate.html.markdown b/website/docs/r/container_app_environment_managed_certificate.html.markdown index e0c09e750fe1..faa99fe61f80 100644 --- a/website/docs/r/container_app_environment_managed_certificate.html.markdown +++ b/website/docs/r/container_app_environment_managed_certificate.html.markdown @@ -109,7 +109,7 @@ In addition to the Arguments listed above - the following Attributes are exporte The `timeouts` block allows you to specify [timeouts](https://developer.hashicorp.com/terraform/language/resources/configure#define-operation-timeouts) for certain actions: -* `create` - (Defaults to 1 hour) Used when creating the Container App Environment Managed Certificate. +* `create` - (Defaults to 30 minutes) Used when creating the Container App Environment Managed Certificate. * `read` - (Defaults to 5 minutes) Used when retrieving the Container App Environment Managed Certificate. * `update` - (Defaults to 30 minutes) Used when updating the Container App Environment Managed Certificate. * `delete` - (Defaults to 30 minutes) Used when deleting the Container App Environment Managed Certificate. From 2c274d1099e892815ebf87d282f2f52733107817 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 10 Apr 2026 13:31:22 +0800 Subject: [PATCH 6/7] Remove TXT domain control validation from docs and tests TXT validation is accepted by the API but does not appear to be functional on the Azure service side - certificates remain in Pending state indefinitely. Remove the TXT test case and update documentation to only advertise CNAME and HTTP as supported validation methods. The validation code still accepts TXT to match the API definition. Co-Authored-By: Claude Opus 4.6 --- ...nvironment_managed_certificate_resource.go | 2 +- ...nment_managed_certificate_resource_test.go | 49 +------------------ ...ironment_managed_certificate.html.markdown | 4 +- 3 files changed, 4 insertions(+), 51 deletions(-) diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go index e4c3a80624e0..ef6f7d2bbafd 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go @@ -79,7 +79,7 @@ func (r ContainerAppEnvironmentManagedCertificateResource) Arguments() map[strin managedenvironments.PossibleValuesForManagedCertificateDomainControlValidation(), false, ), - Description: "The domain control validation type for the managed certificate. Possible values are `CNAME`, `HTTP` and `TXT`. Defaults to `HTTP`.", + Description: "The domain control validation type for the managed certificate. Possible values are `CNAME` and `HTTP`. Defaults to `HTTP`.", }, "tags": commonschema.Tags(), diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go index ed08f435869d..ab14fe8eab7d 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource_test.go @@ -122,25 +122,6 @@ func TestAccContainerAppEnvironmentManagedCertificate_domainControlValidationCNA }) } -func TestAccContainerAppEnvironmentManagedCertificate_domainControlValidationTXT(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_environment_managed_certificate", "test") - r := ContainerAppEnvironmentManagedCertificateResource{} - - data.ResourceTest(t, r, []acceptance.TestStep{ - { - Config: r.domainControlValidationTXT(data), - Check: acceptance.ComposeTestCheckFunc( - check.That(data.ResourceName).ExistsInAzure(r), - ), - }, - data.ImportStep(), - }) -} - func (r ContainerAppEnvironmentManagedCertificateResource) Exists(ctx context.Context, client *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { id, err := managedenvironments.ParseManagedCertificateID(state.ID) if err != nil { @@ -288,34 +269,6 @@ resource "azurerm_container_app_environment_managed_certificate" "test" { `, r.template(data), data.RandomInteger) } -func (r ContainerAppEnvironmentManagedCertificateResource) domainControlValidationTXT(data acceptance.TestData) string { - return fmt.Sprintf(` -provider "azurerm" { - features {} -} - -%[1]s - -resource "azurerm_container_app_custom_domain" "test" { - name = trimsuffix(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] - } -} - -resource "azurerm_container_app_environment_managed_certificate" "test" { - name = "acctest-cacertmgd%[2]d" - container_app_environment_id = azurerm_container_app_environment.test.id - subject_name = trimsuffix(trimprefix(azurerm_dns_txt_record.test.fqdn, "asuid."), ".") - domain_control_validation = "TXT" - - depends_on = [azurerm_container_app_custom_domain.test] -} -`, r.template(data), data.RandomInteger) -} - func (r ContainerAppEnvironmentManagedCertificateResource) template(data acceptance.TestData) string { dnsZone := os.Getenv("ARM_TEST_DNS_ZONE") dataResourceGroup := os.Getenv("ARM_TEST_DATA_RESOURCE_GROUP") @@ -379,7 +332,7 @@ resource "azurerm_dns_cname_record" "test" { resource_group_name = data.azurerm_dns_zone.test.resource_group_name zone_name = data.azurerm_dns_zone.test.name ttl = 300 - record = azurerm_container_app.test.ingress[0].fqdn + record = azurerm_container_app.test.latest_revision_fqdn } resource "azurerm_dns_txt_record" "test" { diff --git a/website/docs/r/container_app_environment_managed_certificate.html.markdown b/website/docs/r/container_app_environment_managed_certificate.html.markdown index faa99fe61f80..e2bba9c46fa7 100644 --- a/website/docs/r/container_app_environment_managed_certificate.html.markdown +++ b/website/docs/r/container_app_environment_managed_certificate.html.markdown @@ -91,9 +91,9 @@ The following arguments are supported: --- -* `domain_control_validation` - (Optional) The domain control validation type for the managed certificate. Possible values are `CNAME`, `HTTP` and `TXT`. Defaults to `HTTP`. Changing this forces a new resource to be created. +* `domain_control_validation` - (Optional) The domain control validation type for the managed certificate. Possible values are `CNAME` and `HTTP`. Defaults to `HTTP`. Changing this forces a new resource to be created. -~> **Note:** The supported validation methods depend on the domain. Azure will validate domain ownership based on the specified method. `HTTP` validation requires an HTTP endpoint at the domain, `CNAME` validation requires DNS CNAME record configuration, and `TXT` validation requires DNS TXT record configuration. +~> **Note:** The supported validation methods depend on the domain. Azure will validate domain ownership based on the specified method. `HTTP` validation requires an HTTP endpoint at the domain, `CNAME` validation requires DNS CNAME record configuration. * `tags` - (Optional) A mapping of tags to assign to the resource. From 08d04ca245265ebf2461ca6b0534761dd1c86fdc Mon Sep 17 00:00:00 2001 From: JT Date: Tue, 14 Apr 2026 10:04:51 +0800 Subject: [PATCH 7/7] Omit TXT from domain control validation ValidateFunc Per review feedback, remove TXT from the allowed validation values since it does not currently function on the Azure service side. Co-Authored-By: Claude Opus 4.6 --- ...container_app_environment_managed_certificate_resource.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go index ef6f7d2bbafd..579b80412f6c 100644 --- a/internal/services/containerapps/container_app_environment_managed_certificate_resource.go +++ b/internal/services/containerapps/container_app_environment_managed_certificate_resource.go @@ -76,7 +76,10 @@ func (r ContainerAppEnvironmentManagedCertificateResource) Arguments() map[strin ForceNew: true, Default: string(managedenvironments.ManagedCertificateDomainControlValidationHTTP), ValidateFunc: validation.StringInSlice( - managedenvironments.PossibleValuesForManagedCertificateDomainControlValidation(), + []string{ + string(managedenvironments.ManagedCertificateDomainControlValidationCNAME), + string(managedenvironments.ManagedCertificateDomainControlValidationHTTP), + }, false, ), Description: "The domain control validation type for the managed certificate. Possible values are `CNAME` and `HTTP`. Defaults to `HTTP`.",