From c3af35f19d02342ec4fc21a07d75bd788063870a Mon Sep 17 00:00:00 2001 From: Sune Keller Date: Mon, 24 Aug 2020 17:35:31 +0200 Subject: [PATCH] Add `azurerm_api_management_custom_domain` resource Signed-off-by: Sune Keller --- azurerm/helpers/azure/string.go | 10 + .../api_management_custom_domain_resource.go | 276 ++++++++++++++++++ .../apimanagement/api_management_resource.go | 2 +- .../services/apimanagement/registration.go | 1 + ..._management_custom_domain_resource_test.go | 177 +++++++++++ website/docs/r/api_management.html.markdown | 4 + ...api_management_custom_domain.html.markdown | 152 ++++++++++ 7 files changed, 621 insertions(+), 1 deletion(-) create mode 100644 azurerm/internal/services/apimanagement/api_management_custom_domain_resource.go create mode 100644 azurerm/internal/services/apimanagement/tests/api_management_custom_domain_resource_test.go create mode 100644 website/docs/r/api_management_custom_domain.html.markdown diff --git a/azurerm/helpers/azure/string.go b/azurerm/helpers/azure/string.go index 229086d3f324b..496e5c213b87c 100644 --- a/azurerm/helpers/azure/string.go +++ b/azurerm/helpers/azure/string.go @@ -1,10 +1,20 @@ package azure import ( + "regexp" "strings" ) +var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") +var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") + func StringContains(sourceStr, subStr string) bool { sourceStr, subStr = strings.ToUpper(sourceStr), strings.ToUpper(subStr) return strings.Contains(sourceStr, subStr) } + +func ToSnakeCase(str string) string { + snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") + snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") + return strings.ToLower(snake) +} diff --git a/azurerm/internal/services/apimanagement/api_management_custom_domain_resource.go b/azurerm/internal/services/apimanagement/api_management_custom_domain_resource.go new file mode 100644 index 0000000000000..0689b42f6a0fa --- /dev/null +++ b/azurerm/internal/services/apimanagement/api_management_custom_domain_resource.go @@ -0,0 +1,276 @@ +package apimanagement + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/apimanagement/mgmt/2019-12-01/apimanagement" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +var apiManagementCustomDomainResourceName = "azurerm_api_management_custom_domain" + +func resourceArmApiManagementCustomDomain() *schema.Resource { + return &schema.Resource{ + Create: apiManagementCustomDomainCreateUpdate, + Read: apiManagementCustomDomainRead, + Update: apiManagementCustomDomainCreateUpdate, + Delete: apiManagementCustomDomainDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(30 * time.Minute), + Read: schema.DefaultTimeout(5 * time.Minute), + Update: schema.DefaultTimeout(30 * time.Minute), + Delete: schema.DefaultTimeout(30 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "resource_group_name": azure.SchemaResourceGroupName(), + + "api_management_name": azure.SchemaApiManagementName(), + + "management": { + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: []string{"management", "portal", "developer_portal", "proxy", "scm"}, + Elem: &schema.Resource{ + Schema: apiManagementResourceHostnameSchema(), + }, + }, + "portal": { + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: []string{"management", "portal", "developer_portal", "proxy", "scm"}, + Elem: &schema.Resource{ + Schema: apiManagementResourceHostnameSchema(), + }, + }, + "developer_portal": { + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: []string{"management", "portal", "developer_portal", "proxy", "scm"}, + Elem: &schema.Resource{ + Schema: apiManagementResourceHostnameSchema(), + }, + }, + "proxy": { + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: []string{"management", "portal", "developer_portal", "proxy", "scm"}, + Elem: &schema.Resource{ + Schema: apiManagementResourceHostnameProxySchema(), + }, + }, + "scm": { + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: []string{"management", "portal", "developer_portal", "proxy", "scm"}, + Elem: &schema.Resource{ + Schema: apiManagementResourceHostnameSchema(), + }, + }, + }, + } +} + +func apiManagementCustomDomainCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).ApiManagement.ServiceClient + ctx, cancel := timeouts.ForCreate(meta.(*clients.Client).StopContext, d) + defer cancel() + + log.Printf("[INFO] preparing arguments for API Management Custom domain creation.") + + name := d.Get("api_management_name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + existing, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error finding API Management (API Management %q / Resource Group %q): %s", name, resourceGroup, err) + } + + if d.IsNewResource() { + if existing.ServiceProperties.HostnameConfigurations != nil { + return tf.ImportAsExistsError(apiManagementCustomDomainResourceName, *existing.ID) + } + } + + existing.ServiceProperties.HostnameConfigurations = expandAzureRmApiManagementHostnameConfigurations(d) + + if _, err := client.CreateOrUpdate(ctx, resourceGroup, name, existing); err != nil { + return fmt.Errorf("Error creating/updating Custom Custom domain (API Management %q / Resource Group %q): %+v", name, resourceGroup, err) + } + + read, err := client.Get(ctx, resourceGroup, name) + if err != nil { + return fmt.Errorf("Error retrieving Custom Custom domain (API Management %q / Resource Group %q): %+v", name, resourceGroup, err) + } + if read.ID == nil { + return fmt.Errorf("Cannot read Custom domain (API Management %q / Resource Group %q) ID", name, resourceGroup) + } + + d.SetId(*read.ID) + + return apiManagementCustomDomainRead(d, meta) +} + +func apiManagementCustomDomainRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).ApiManagement.ServiceClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + name := id.Path["service"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("API Management Service %q was not found in Resource Group %q - removing from state!", name, resourceGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("making Read request on API Management Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + d.Set("resource_group_name", resourceGroup) + d.Set("api_management_name", resp.Name) + + if props := resp.ServiceProperties.HostnameConfigurations; props != nil { + configs := flattenApiManagementHostnameConfiguration(resp.ServiceProperties.HostnameConfigurations, d) + for _, config := range configs { + for key, v := range config.(map[string]interface{}) { + if err := d.Set(key, v); err != nil { + return fmt.Errorf("setting `hostname_configuration` %q: %+v", key, err) + } + } + } + } + + return nil +} + +func apiManagementCustomDomainDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).ApiManagement.ServiceClient + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := azure.ParseAzureResourceID(d.Id()) + if err != nil { + return err + } + + resourceGroup := id.ResourceGroup + name := id.Path["service"] + + resp, err := client.Get(ctx, resourceGroup, name) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + log.Printf("API Management Service %q was not found in Resource Group %q - removing from state!", name, resourceGroup) + d.SetId("") + return nil + } + + return fmt.Errorf("making Read request on API Management Service %q (Resource Group %q): %+v", name, resourceGroup, err) + } + + log.Printf("[DEBUG] Deleting API Management Custom domain (API Management %q / Resource Group %q)", name, resourceGroup) + + resp.ServiceProperties.HostnameConfigurations = nil + + if _, err := client.CreateOrUpdate(ctx, resourceGroup, name, resp); err != nil { + return fmt.Errorf("Error deleting Custom Custom domain (API Management %q / Resource Group %q): %+v", name, resourceGroup, err) + } + + return nil +} + +func flattenApiManagementHostnameConfiguration(input *[]apimanagement.HostnameConfiguration, d *schema.ResourceData) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + managementResults := make([]interface{}, 0) + portalResults := make([]interface{}, 0) + developerPortalResults := make([]interface{}, 0) + proxyResults := make([]interface{}, 0) + scmResults := make([]interface{}, 0) + + for _, config := range *input { + output := make(map[string]interface{}) + + if config.HostName != nil { + output["host_name"] = *config.HostName + } + + if config.NegotiateClientCertificate != nil { + output["negotiate_client_certificate"] = *config.NegotiateClientCertificate + } + + if config.KeyVaultID != nil { + output["key_vault_id"] = *config.KeyVaultID + } + + // Iterate through old state to find sensitive props not returned by API. + // This must be done in order to avoid state diffs. + // NOTE: this information won't be available during times like Import, so this is a best-effort. + snakeCaseConfigType := azure.ToSnakeCase(string(config.Type)) + if valsRaw, ok := d.GetOk(snakeCaseConfigType); ok { + vals := valsRaw.([]interface{}) + for _, val := range vals { + oldConfig := val.(map[string]interface{}) + + if oldConfig["host_name"] == *config.HostName { + output["certificate_password"] = oldConfig["certificate_password"] + output["certificate"] = oldConfig["certificate"] + } + } + } + + switch strings.ToLower(string(config.Type)) { + case strings.ToLower(string(apimanagement.HostnameTypeProxy)): + // only set SSL binding for proxy types + if config.DefaultSslBinding != nil { + output["default_ssl_binding"] = *config.DefaultSslBinding + } + proxyResults = append(proxyResults, output) + + case strings.ToLower(string(apimanagement.HostnameTypeManagement)): + managementResults = append(managementResults, output) + + case strings.ToLower(string(apimanagement.HostnameTypePortal)): + portalResults = append(portalResults, output) + + case strings.ToLower(string(apimanagement.HostnameTypeDeveloperPortal)): + developerPortalResults = append(developerPortalResults, output) + + case strings.ToLower(string(apimanagement.HostnameTypeScm)): + scmResults = append(scmResults, output) + } + } + + return []interface{}{ + map[string]interface{}{ + "management": managementResults, + "portal": portalResults, + "developer_portal": developerPortalResults, + "proxy": proxyResults, + "scm": scmResults, + }, + } +} diff --git a/azurerm/internal/services/apimanagement/api_management_resource.go b/azurerm/internal/services/apimanagement/api_management_resource.go index 6ce38d1c6cf9d..15cbc10aaab86 100644 --- a/azurerm/internal/services/apimanagement/api_management_resource.go +++ b/azurerm/internal/services/apimanagement/api_management_resource.go @@ -835,7 +835,7 @@ func flattenApiManagementHostnameConfigurations(input *[]apimanagement.HostnameC if len(existingHostnames) > 0 { v := existingHostnames[0].(map[string]interface{}) - if valsRaw, ok := v[strings.ToLower(string(config.Type))]; ok { + if valsRaw, ok := v[azure.ToSnakeCase(string(config.Type))]; ok { vals := valsRaw.([]interface{}) for _, val := range vals { oldConfig := val.(map[string]interface{}) diff --git a/azurerm/internal/services/apimanagement/registration.go b/azurerm/internal/services/apimanagement/registration.go index 1c1160925d1ea..1b38d6ce0a431 100644 --- a/azurerm/internal/services/apimanagement/registration.go +++ b/azurerm/internal/services/apimanagement/registration.go @@ -44,6 +44,7 @@ func (r Registration) SupportedResources() map[string]*schema.Resource { "azurerm_api_management_authorization_server": resourceArmApiManagementAuthorizationServer(), "azurerm_api_management_backend": resourceArmApiManagementBackend(), "azurerm_api_management_certificate": resourceArmApiManagementCertificate(), + "azurerm_api_management_custom_domain": resourceArmApiManagementCustomDomain(), "azurerm_api_management_diagnostic": resourceArmApiManagementDiagnostic(), "azurerm_api_management_group": resourceArmApiManagementGroup(), "azurerm_api_management_group_user": resourceArmApiManagementGroupUser(), diff --git a/azurerm/internal/services/apimanagement/tests/api_management_custom_domain_resource_test.go b/azurerm/internal/services/apimanagement/tests/api_management_custom_domain_resource_test.go new file mode 100644 index 0000000000000..bd1b4b327ba2a --- /dev/null +++ b/azurerm/internal/services/apimanagement/tests/api_management_custom_domain_resource_test.go @@ -0,0 +1,177 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func TestAccAzureRMApiManagementCustomDomain_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_api_management_custom_domain", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMApiManagementCustomDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMApiManagementCustomDomain_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMApiManagementCustomDomainExists(data.ResourceName), + ), + }, + data.ImportStep(), + }, + }) +} + +func TestAccAzureRMApiManagementCustomDomain_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_api_management_custom_domain", "test") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + CheckDestroy: testCheckAzureRMApiManagementCustomDomainDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAzureRMApiManagementCustomDomain_basic(data), + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMApiManagementCustomDomainExists(data.ResourceName), + ), + }, + data.RequiresImportErrorStep(testAccAzureRMApiManagementCustomDomain_requiresImport), + }, + }) +} + +func testCheckAzureRMApiManagementCustomDomainDestroy(s *terraform.State) error { + conn := acceptance.AzureProvider.Meta().(*clients.Client).ApiManagement.ServiceClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_api_management_custom_domain" { + continue + } + + serviceName := rs.Primary.Attributes["api_management_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(ctx, resourceGroup, serviceName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + // TODO: Is this an error case? If Custom Domains is destroyed, it should not destroy the API Management Service, so what are we testing for here? + // If the entire configuration is torn down, including the API Management Service, then it, too, will not be found, and returning nil is correct. + return nil + } + + if resp.ServiceProperties != nil && resp.ServiceProperties.HostnameConfigurations != nil && len(*resp.ServiceProperties.HostnameConfigurations) > 0 { + return fmt.Errorf("Bad: Expected there to be no Custom Domains in the hostname_configurations field: %+v", resp.ServiceProperties.HostnameConfigurations) + } + + return err + } + + return nil + } + + return nil +} + +func testCheckAzureRMApiManagementCustomDomainExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acceptance.AzureProvider.Meta().(*clients.Client).ApiManagement.ServiceClient + ctx := acceptance.AzureProvider.Meta().(*clients.Client).StopContext + + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + serviceName := rs.Primary.Attributes["api_management_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(ctx, resourceGroup, serviceName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Bad: Custom Domains on API Management Service %q / Resource Group: %q does not exist (because API Management Service %q does not exist)", serviceName, resourceGroup, serviceName) + } + + if resp.ServiceProperties == nil || resp.ServiceProperties.HostnameConfigurations == nil || len(*resp.ServiceProperties.HostnameConfigurations) == 0 { + return fmt.Errorf("Bad: Expected there to be Custom Domains defined in the hostname_configurations field for API Management Service %q / Resource Group: %q", serviceName, resourceGroup) + } + + return fmt.Errorf("Bad: Get on apiManagementCustomDomainsClient: %+v", err) + } + + return nil + } +} + +func testAccAzureRMApiManagementCustomDomain_basic(data acceptance.TestData) string { + template := testAccAzureRMApiManagementCustomDomain_template(data) + return fmt.Sprintf(` +%s + +resource "azurerm_api_management_custom_domain" "test" { + api_management_name = azurerm_api_management.test.name + resource_group_name = azurerm_resource_group.test.name +} +`, template) +} + +func testAccAzureRMApiManagementCustomDomain_requiresImport(data acceptance.TestData) string { + template := testAccAzureRMApiManagementCustomDomain_basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_api_management_custom_domain" "import" { + api_management_name = azurerm_api_management_custom_domain.test.api_management_name + resource_group_name = azurerm_api_management_custom_domain.test.resource_group_name +} +`, template) +} + +func testAccAzureRMApiManagementCustomDomain_template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_api_management" "test" { + name = "acctestAM-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + publisher_name = "pub1" + publisher_email = "pub1@email.com" + sku_name = "Developer_1" +} + +resource "azurerm_api_management_api" "test" { + name = "acctestapi-%d" + resource_group_name = azurerm_resource_group.test.name + api_management_name = azurerm_api_management.test.name + display_name = "Butter Parser" + path = "butter-parser" + protocols = ["https", "http"] + revision = "3" + description = "What is my purpose? You parse butter." + service_url = "https://example.com/foo/bar" + + subscription_key_parameter_names { + header = "X-Butter-Robot-API-Key" + query = "location" + } +} +`, data.RandomInteger, data.Locations.Primary, data.RandomInteger, data.RandomInteger) +} diff --git a/website/docs/r/api_management.html.markdown b/website/docs/r/api_management.html.markdown index e2e2fdb4416bf..0886b9276cb86 100644 --- a/website/docs/r/api_management.html.markdown +++ b/website/docs/r/api_management.html.markdown @@ -10,6 +10,10 @@ description: |- Manages an API Management Service. +## Disclaimers + +~> **Note:** It's possible to define Custom Domains both within [the `azurerm_api_management` resource](api_management.html) via the `hostname_configurations` block and by using [the `azurerm_api_management_custom_domain` resource](api_management_custom_domain.html). However it's not possible to use both methods to manage Custom Domains within an API Management Service, since there'll be conflicts. + ## Example Usage ```hcl diff --git a/website/docs/r/api_management_custom_domain.html.markdown b/website/docs/r/api_management_custom_domain.html.markdown new file mode 100644 index 0000000000000..e82d888cb596f --- /dev/null +++ b/website/docs/r/api_management_custom_domain.html.markdown @@ -0,0 +1,152 @@ +--- +subcategory: "API Management" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_api_management_custom_domain" +description: |- + Manages a API Management Custom Domain. +--- + +# azurerm_api_management_custom_domain + +Manages a API Management Custom Domain. + +## Disclaimers + +~> **Note:** It's possible to define Custom Domains both within [the `azurerm_api_management` resource](api_management.html) via the `hostname_configurations` block and by using [the `azurerm_api_management_custom_domain` resource](api_management_custom_domain.html). However it's not possible to use both methods to manage Custom Domains within an API Management Service, since there'll be conflicts. + +## Example Usage + +```hcl +resource "azurerm_key_vault" "example" { + // TODO +} + +resource "azurerm_key_vault_certificate" "example" { + key_vault_id = azurerm_key_vault.example.id + // TODO +} + +resource "azurerm_api_management_custom_domain" "example" { + resource_group_name = "example" + api_management_name = "example" + proxy { + host_name = "api.example.com" + key_vault_id = azurerm_key_vault_certificate.example.secret_id + } +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `api_management_name` - (Required) TODO. Changing this forces a new API Management Custom Domain to be created. + +* `resource_group_name` - (Required) The name of the Resource Group where the API Management Custom Domain should exist. Changing this forces a new API Management Custom Domain to be created. + +--- + +* `developer_portal` - (Optional) One or more `developer_portal` blocks as defined below. + +* `management` - (Optional) One or more `management` blocks as defined below. + +* `portal` - (Optional) One or more `portal` blocks as defined below. + +* `proxy` - (Optional) One or more `proxy` blocks as defined below. + +* `scm` - (Optional) One or more `scm` blocks as defined below. + +--- + +A `developer_portal` block supports the following: + +* `host_name` - (Required) The Hostname to use for the Developer Portal. + +* `certificate` - (Optional) The Base64 Encoded Certificate. (Mutually exlusive with `key_vault_id`.) + +* `certificate_password` - (Optional) The password associated with the certificate provided above. + +* `key_vault_id` - (Optional) The ID of the Key Vault Secret containing the SSL Certificate, which must be should be of the type application/x-pkcs12. + +* `negotiate_client_certificate` - (Optional) Should Client Certificate Negotiation be enabled for this Hostname? Defaults to false. + +--- + +A `management` block supports the following: + +* `host_name` - (Required) The Hostname to use for the Management API. + +* `certificate` - (Optional) The Base64 Encoded Certificate. (Mutually exlusive with `key_vault_id`.) + +* `certificate_password` - (Optional) The password associated with the certificate provided above. + +* `key_vault_id` - (Optional) The ID of the Key Vault Secret containing the SSL Certificate, which must be should be of the type application/x-pkcs12. + +* `negotiate_client_certificate` - (Optional) Should Client Certificate Negotiation be enabled for this Hostname? Defaults to false. + +--- + +A `portal` block supports the following: + +* `host_name` - (Required) The Hostname to use for the legacy Developer Portal. + +* `certificate` - (Optional) The Base64 Encoded Certificate. (Mutually exlusive with `key_vault_id`.) + +* `certificate_password` - (Optional) The password associated with the certificate provided above. + +* `key_vault_id` - (Optional) The ID of the Key Vault Secret containing the SSL Certificate, which must be should be of the type application/x-pkcs12. + +* `negotiate_client_certificate` - (Optional) Should Client Certificate Negotiation be enabled for this Hostname? Defaults to false. + +--- + +A `proxy` block supports the following: + +* `host_name` - (Required) The Hostname to use for the legacy Developer Portal. + +* `certificate` - (Optional) The Base64 Encoded Certificate. (Mutually exlusive with `key_vault_id`.) + +* `certificate_password` - (Optional) The password associated with the certificate provided above. + +* `default_ssl_binding` - (Optional) Is the certificate associated with this Hostname the Default SSL Certificate? This is used when an SNI header isn't specified by a client. Defaults to false. + +* `key_vault_id` - (Optional) The ID of the Key Vault Secret containing the SSL Certificate, which must be should be of the type application/x-pkcs12. + +* `negotiate_client_certificate` - (Optional) Should Client Certificate Negotiation be enabled for this Hostname? Defaults to false. + +--- + +A `scm` block supports the following: + +* `host_name` - (Required) The Hostname to use for the SCM domain. + +* `certificate` - (Optional) The Base64 Encoded Certificate. (Mutually exlusive with `key_vault_id`.) + +* `certificate_password` - (Optional) The password associated with the certificate provided above. + +* `key_vault_id` - (Optional) The ID of the Key Vault Secret containing the SSL Certificate, which must be should be of the type application/x-pkcs12. + +* `negotiate_client_certificate` - (Optional) Should Client Certificate Negotiation be enabled for this Hostname? Defaults to false. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the API Management Custom Domain. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the API Management Custom Domain. +* `read` - (Defaults to 5 minutes) Used when retrieving the API Management Custom Domain. +* `update` - (Defaults to 30 minutes) Used when updating the API Management Custom Domain. +* `delete` - (Defaults to 30 minutes) Used when deleting the API Management Custom Domain. + +## Import + +API Management Custom Domains can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_api_management_custom_domain.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/mygroup1/providers/Microsoft.ApiManagement/service/instance1 +```