diff --git a/internal/services/extendedlocation/extended_location_custom_locations.go b/internal/services/extendedlocation/extended_location_custom_location.go similarity index 77% rename from internal/services/extendedlocation/extended_location_custom_locations.go rename to internal/services/extendedlocation/extended_location_custom_location.go index fff63c03add06..484a8afeff5f9 100644 --- a/internal/services/extendedlocation/extended_location_custom_locations.go +++ b/internal/services/extendedlocation/extended_location_custom_location.go @@ -3,33 +3,38 @@ package extendedlocation import ( "context" "fmt" + "github.com/hashicorp/go-azure-helpers/lang/pointer" + "time" + "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-sdk/resource-manager/extendedlocation/2021-08-15/customlocations" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" "github.com/hashicorp/terraform-provider-azurerm/utils" - "time" ) type CustomLocationResource struct{} type CustomLocationResourceModel struct { - Name string `tfschema:"name"` - ResourceGroupName string `tfschema:"resource_group_name"` - Location string `tfschema:"location"` - AuthenticationType string `tfschema:"authentication_type"` - AuthenticationValue string `tfschema:"authentication_value"` - ClusterExtensionIds []string `tfschema:"cluster_extension_ids"` - DisplayName string `tfschema:"display_name"` - HostResourceId string `tfschema:"host_resource_id"` - HostType string `tfschema:"host_type"` - Namespace string `tfschema:"namespace"` + Name string `tfschema:"name"` + ResourceGroupName string `tfschema:"resource_group_name"` + Location string `tfschema:"location"` + Authentication []AuthModel `tfschema:"authentication"` + ClusterExtensionIds []string `tfschema:"cluster_extension_ids"` + DisplayName string `tfschema:"display_name"` + HostResourceId string `tfschema:"host_resource_id"` + HostType string `tfschema:"host_type"` + Namespace string `tfschema:"namespace"` +} + +type AuthModel struct { + Type string `tfschema:"type"` + Value string `tfschema:"value"` } -func (r CustomLocationResource) Arguments() map[string]*schema.Schema { - return map[string]*schema.Schema{ +func (r CustomLocationResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ "name": { Type: pluginsdk.TypeString, Required: true, @@ -51,6 +56,13 @@ func (r CustomLocationResource) Arguments() map[string]*schema.Schema { ValidateFunc: validation.StringIsNotEmpty, }, + "namespace": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + "cluster_extension_ids": { Type: pluginsdk.TypeList, Required: true, @@ -65,16 +77,24 @@ func (r CustomLocationResource) Arguments() map[string]*schema.Schema { ValidateFunc: validation.StringIsNotEmpty, }, - "authentication_type": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, - - "authentication_value": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, + "authentication": { + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "type": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "value": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, }, "display_name": { @@ -90,17 +110,11 @@ func (r CustomLocationResource) Arguments() map[string]*schema.Schema { string(customlocations.HostTypeKubernetes), }, false), }, - - "namespace": { - Type: pluginsdk.TypeString, - Optional: true, - ValidateFunc: validation.StringIsNotEmpty, - }, } } -func (r CustomLocationResource) Attributes() map[string]*schema.Schema { - return map[string]*schema.Schema{} +func (r CustomLocationResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} } func (r CustomLocationResource) ModelObject() interface{} { @@ -108,7 +122,7 @@ func (r CustomLocationResource) ModelObject() interface{} { } func (r CustomLocationResource) ResourceType() string { - return "azurerm_extended_custom_locations" + return "azurerm_extended_custom_location" } func (r CustomLocationResource) Create() sdk.ResourceFunc { @@ -135,10 +149,11 @@ func (r CustomLocationResource) Create() sdk.ResourceFunc { customLocationProps := customlocations.CustomLocationProperties{} - if model.AuthenticationValue != "" && model.AuthenticationType != "" { + if model.Authentication != nil { + auth := model.Authentication[0] customLocationProps.Authentication = &customlocations.CustomLocationPropertiesAuthentication{ - Type: &model.AuthenticationType, - Value: &model.AuthenticationValue, + Type: &auth.Type, + Value: &auth.Value, } } @@ -207,9 +222,15 @@ func (r CustomLocationResource) Read() sdk.ResourceFunc { Location: model.Location, } - if props.Authentication != nil && props.Authentication.Type != nil && props.Authentication.Value != nil { - state.AuthenticationType = *props.Authentication.Type - state.AuthenticationValue = *props.Authentication.Value + if props != nil && props.Authentication != nil { + authType := pointer.From(props.Authentication.Type) + authValue := pointer.From(props.Authentication.Value) + state.Authentication = []AuthModel{ + { + Type: authType, + Value: authValue, + }, + } } if props.ClusterExtensionIds != nil { @@ -280,10 +301,13 @@ func (r CustomLocationResource) Update() sdk.ResourceFunc { customLocationProps := customlocations.CustomLocationProperties{} d := metadata.ResourceData - if d.HasChanges("authentication_type", "authentication_value") { - customLocationProps.Authentication = &customlocations.CustomLocationPropertiesAuthentication{ - Type: &state.AuthenticationType, - Value: &state.AuthenticationValue, + if d.HasChanges("authentication") { + if state.Authentication != nil { + auth := state.Authentication[0] + customLocationProps.Authentication = &customlocations.CustomLocationPropertiesAuthentication{ + Type: &auth.Type, + Value: &auth.Value, + } } } diff --git a/internal/services/extendedlocation/extended_location_custom_locations_test.go b/internal/services/extendedlocation/extended_location_custom_location_test.go similarity index 79% rename from internal/services/extendedlocation/extended_location_custom_locations_test.go rename to internal/services/extendedlocation/extended_location_custom_location_test.go index 08475ec4dfd8f..ea356f6c89570 100644 --- a/internal/services/extendedlocation/extended_location_custom_locations_test.go +++ b/internal/services/extendedlocation/extended_location_custom_location_test.go @@ -8,6 +8,10 @@ import ( "encoding/base64" "encoding/pem" "fmt" + "math/rand" + "os" + "testing" + "github.com/hashicorp/go-azure-helpers/lang/response" "github.com/hashicorp/go-azure-sdk/resource-manager/extendedlocation/2021-08-15/customlocations" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -16,9 +20,6 @@ import ( "github.com/hashicorp/terraform-provider-azurerm/internal/clients" "github.com/hashicorp/terraform-provider-azurerm/internal/tf/pluginsdk" "github.com/hashicorp/terraform-provider-azurerm/utils" - "math/rand" - "os" - "testing" ) type CustomLocationResource struct{} @@ -40,7 +41,7 @@ func (r CustomLocationResource) Exists(ctx context.Context, client *clients.Clie } func TestAccExtendedLocationCustomLocations_basic(t *testing.T) { - data := acceptance.BuildTestData(t, "azurerm_extended_custom_locations", "test") + data := acceptance.BuildTestData(t, "azurerm_extended_custom_location", "test") r := CustomLocationResource{} credential, privateKey, publicKey := r.getCredentials(t) @@ -55,20 +56,63 @@ func TestAccExtendedLocationCustomLocations_basic(t *testing.T) { }) } +func TestAccExtendedLocationCustomLocations_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_extended_custom_location", "test") + r := CustomLocationResource{} + credential, privateKey, publicKey := r.getCredentials(t) + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data, credential, privateKey, publicKey), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.update(data, credential, privateKey, publicKey), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + func (r CustomLocationResource) basic(data acceptance.TestData, credential string, privateKey string, publicKey string) string { template := r.template(data, credential, publicKey, privateKey) return fmt.Sprintf(` %s -resource "azurerm_extended_custom_locations" "test" { - name = "acctestcustomlocation%d" +resource "azurerm_extended_custom_location" "test" { + name = "acctestcustomlocation%d" resource_group_name = azurerm_resource_group.test.name - location = azurerm_resource_group.test.location + location = azurerm_resource_group.test.location cluster_extension_ids = [ - "${azurerm_arc_kubernetes_cluster_extension.test.id}" + "${azurerm_arc_kubernetes_cluster_extension.test.id}" ] - display_name = "customlocation%[2]d" - namespace = "namespace%[2]d" + display_name = "customlocation%[2]d" + namespace = "namespace%[2]d" + host_resource_id = azurerm_arc_kubernetes_cluster.test.id +} +`, template, data.RandomInteger) +} + +func (r CustomLocationResource) update(data acceptance.TestData, credential string, privateKey string, publicKey string) string { + template := r.template(data, credential, publicKey, privateKey) + return fmt.Sprintf(` +%s + +resource "azurerm_extended_custom_location" "test" { + name = "acctestcustomlocation%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + cluster_extension_ids = [ + "${azurerm_arc_kubernetes_cluster_extension.test.id}", + ] + + display_name = "customlocationupdate%[2]d" + namespace = "namespace%[2]d" host_resource_id = azurerm_arc_kubernetes_cluster.test.id } `, template, data.RandomInteger) @@ -80,14 +124,14 @@ func (r CustomLocationResource) template(data acceptance.TestData, credential st return fmt.Sprintf(` provider "azurerm" { features { - resource_group { - prevent_deletion_if_contains_resources = false - } + resource_group { + prevent_deletion_if_contains_resources = false + } } } resource "azurerm_resource_group" "test" { - name = "acctestRG-%[1]d" + name = "acctestRG-%[1]d" location = "%[2]s" } @@ -139,6 +183,12 @@ resource "azurerm_network_security_group" "my_terraform_nsg" { source_address_prefix = "*" destination_address_prefix = "*" } + + lifecycle { + ignore_changes = [ + security_rule, + ] + } } resource "azurerm_network_interface_security_group_association" "example" { @@ -169,6 +219,13 @@ resource "azurerm_linux_virtual_machine" "test" { sku = "18.04-LTS" version = "latest" } + + lifecycle { + ignore_changes = [ + identity, + tags, + ] + } } resource "azurerm_arc_kubernetes_cluster" "test" { @@ -188,14 +245,13 @@ resource "azurerm_arc_kubernetes_cluster" "test" { } resource "azurerm_arc_kubernetes_cluster_extension" "test" { - name = "acctest-kce-%[1]d" - cluster_id = azurerm_arc_kubernetes_cluster.test.id - extension_type = "microsoft.contoso.clusters" - target_namespace = "tf-ns4" - version = "1.2.0" + name = "extension4" + cluster_id = azurerm_arc_kubernetes_cluster.test.id + extension_type = "microsoft.vmware" + release_namespace = "vmware-extension" configuration_settings = { - "Microsoft.CustomLocation.ServiceAccount" = "tf-operator" + "Microsoft.CustomLocation.ServiceAccount" = "vmware-operator" } identity { diff --git a/internal/services/extendedlocation/testdata/install_agent.py b/internal/services/extendedlocation/testdata/install_agent.py index d39bccfab4f2e..2513e9aa7cbb4 100644 --- a/internal/services/extendedlocation/testdata/install_agent.py +++ b/internal/services/extendedlocation/testdata/install_agent.py @@ -184,6 +184,10 @@ def helm_install_release(chart_path, subscription_id, kubernetes_distro, kuberne "--set", "global.azureEnvironment={}".format( cloud_name), "--set", "systemDefaultValues.clusterconnect-agent.enabled=true", + "--set", "systemDefaultValues.customLocations.enabled=true", + # 51dfe1e8-70c6-4de5-a08e-e18aff23d815 is from: az ad sp show --id bc313c14-388c-4e7d-a58e-70017303ee3b --query id -o tsv + # refs: https://learn.microsoft.com/en-us/azure/azure-arc/kubernetes/custom-locations#enable-custom-locations-on-your-cluster + "--set", "systemDefaultValues.customLocations.oid={}".format("51dfe1e8-70c6-4de5-a08e-e18aff23d815"), "--namespace", "{}".format("azure-arc-release"), "--create-namespace", "--output", "json"] @@ -225,7 +229,7 @@ def install_agent(): # Retrieving Helm chart OCI Artifact location registry_path = get_helm_registry("https://westeurope.dp.kubernetesconfiguration.azure.com") - + # Get helm chart path chart_path = get_chart_path( registry_path, None, None, helm_client_location) diff --git a/website/allowed-subcategories b/website/allowed-subcategories index b1dfd82be8506..78133ba8cb4c9 100644 --- a/website/allowed-subcategories +++ b/website/allowed-subcategories @@ -47,6 +47,7 @@ Dev Test Digital Twins Disks Elastic +Extended Location Fluid Relay Graph Services HDInsight @@ -105,4 +106,4 @@ Time Series Insights VMware (AVS) Video Analyzer Voice Services -Web PubSub \ No newline at end of file +Web PubSub diff --git a/website/docs/r/extended_location_custom_location.html.markdown b/website/docs/r/extended_location_custom_location.html.markdown new file mode 100644 index 0000000000000..b80df9484a67d --- /dev/null +++ b/website/docs/r/extended_location_custom_location.html.markdown @@ -0,0 +1,105 @@ +--- +subcategory: "Extended Location" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_extended_location_custom_location" +description: |- + Manages a Custom Location within an Extended Location. +--- + +# azurerm_extended_location_custom_location + +Manages a Custom Location within an Extended Location. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_arc_kubernetes_cluster" "example" { + name = "example-akcc" + resource_group_name = azurerm_resource_group.example.name + location = "West Europe" + agent_public_key_certificate = filebase64("testdata/public.cer") + + identity { + type = "SystemAssigned" + } + + tags = { + ENV = "Test" + } +} + +resource "azurerm_arc_kubernetes_cluster_extension" "example" { + name = "example-ext" + cluster_id = azurerm_arc_kubernetes_cluster.example.id + extension_type = "microsoft.flux" +} + +resource "azurerm_extended_location_custom_location" "example" { + name = "example-custom-location" + resource_group_name = azurerm_resource_group.example.name + location = "West Europe" + cluster_extension_ids = [ + "${azurerm_arc_kubernetes_cluster_extension.test.id}" + ] + display_name = "example-custom-location" + namespace = "example-namespace" + host_resource_id = azurerm_arc_kubernetes_cluster.example.id +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `name` - (Required) Specifies the name which should be used for this Custom Location. Changing this forces a new Custom Location to be created. + +* `resource_group_name` - (Required) Specifies the name of the Resource Group where the Custom Location should exist. Changing this forces a new Custom Location to be created. + +* `location` - (Required) Specifies the Azure location where the Custom Location should exist. Changing this forces a new Custom Location to be created. + +* `namespace` - (Required) Specifies the namespace of the Custom Location.Changing this forces a new Custom Location to be created. + +* `cluster_extension_ids` - (Required) Specifies the list of Cluster Extension IDs. + +* `host_resource_id` - (Required) Specifies the host resource ID. + +* `authentication` - (Optional) An `authentication` block as defined below. + +* `display_name` - (Optional) Specifies the display name of the Custom Location. + +* `host_type` - (Optional) Specifies the host type of the Custom Location. The only possible values is `KubernetesCluster`. + +--- + +An `authentication` block supports the following: + +* `type` - (Required) Specifies the type of authentication. + +* `value` - (Required) Specifies the value of authentication. + +## Attributes Reference + +* `id` - The ID of the Custom Location. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Custom Location. +* `read` - (Defaults to 5 minutes) Used when retrieving the Custom Location. +* `update` - (Defaults to 30 minutes) Used when updating the Custom Location. +* `delete` - (Defaults to 30 minutes) Used when deleting the Custom Location. + +## Import + +Custom Locations can be imported using the resource id, e.g. + +```shell +terraform import azurerm_extended_location_custom_location.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-resources/providers/Microsoft.ExtendedLocation/customLocations/example-custom-location +``` +