From 79b20367b5ad8e189e5cf68dd38ff06ecf4e304f Mon Sep 17 00:00:00 2001 From: Heng Lu Date: Tue, 30 Apr 2024 10:17:36 +0800 Subject: [PATCH 1/4] new resource: `azurerm_data_factory_credential_service_principal` --- ...y_credential_service_principal_resource.go | 393 ++++++++++++++++++ ...dential_service_principal_resource_test.go | 238 +++++++++++ internal/services/datafactory/registration.go | 1 + ...credential_service_principal.html.markdown | 132 ++++++ 4 files changed, 764 insertions(+) create mode 100644 internal/services/datafactory/data_factory_credential_service_principal_resource.go create mode 100644 internal/services/datafactory/data_factory_credential_service_principal_resource_test.go create mode 100644 website/docs/r/data_factory_credential_service_principal.html.markdown diff --git a/internal/services/datafactory/data_factory_credential_service_principal_resource.go b/internal/services/datafactory/data_factory_credential_service_principal_resource.go new file mode 100644 index 000000000000..30a2124e7f19 --- /dev/null +++ b/internal/services/datafactory/data_factory_credential_service_principal_resource.go @@ -0,0 +1,393 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package datafactory + +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-sdk/resource-manager/datafactory/2018-06-01/credentials" + "github.com/hashicorp/terraform-provider-azurerm/helpers/tf" + "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" +) + +type DataFactoryCredentialServicePrincipalResource struct{} + +var _ sdk.Resource = DataFactoryCredentialServicePrincipalResource{} +var _ sdk.ResourceWithUpdate = DataFactoryCredentialServicePrincipalResource{} + +func (DataFactoryCredentialServicePrincipalResource) ResourceType() string { + return "azurerm_data_factory_credential_service_principal" +} + +type DataFactoryCredentialServicePrincipalResourceSchema struct { + Name string `tfschema:"name"` + DataFactoryId string `tfschema:"data_factory_id"` + TenantId string `tfschema:"tenant_id"` + ServicePrincipalId string `tfschema:"service_principal_id"` + ServicePrincipalKey []ServicePrincipalKey `tfschema:"service_principal_key"` + Description string `tfschema:"description"` + Annotations []string `tfschema:"annotations"` +} + +type ServicePrincipalKey struct { + LinkedServiceName string `tfschema:"linked_service_name"` + SecretName string `tfschema:"secret_name"` + SecretVersion string `tfschema:"secret_version"` +} + +func (DataFactoryCredentialServicePrincipalResource) Arguments() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{ + "name": { + Description: "The desired name of the credential resource", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "data_factory_id": { + Description: "The resource ID of the parent Data Factory", + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: credentials.ValidateFactoryID, + }, + + "tenant_id": { + Description: "The Tenant ID of the Service Principal", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, + }, + + "service_principal_id": { + Description: "The Client ID of the Service Principal", + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.IsUUID, + }, + + "service_principal_key": { + Type: pluginsdk.TypeList, + Optional: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "linked_service_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "secret_name": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "secret_version": { + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + }, + }, + + "description": { + Description: "(Optional) Short text description", + Type: pluginsdk.TypeString, + Optional: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + + "annotations": { // this property is not visible in the azure portal + Description: "(Optional) List of string annotations.", + Type: pluginsdk.TypeList, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + }, + }, + } +} + +func (DataFactoryCredentialServicePrincipalResource) Attributes() map[string]*pluginsdk.Schema { + return map[string]*pluginsdk.Schema{} +} + +func (DataFactoryCredentialServicePrincipalResource) ModelObject() interface{} { + return &DataFactoryCredentialServicePrincipalResourceSchema{} +} + +func (DataFactoryCredentialServicePrincipalResource) IDValidationFunc() pluginsdk.SchemaValidateFunc { + return credentials.ValidateCredentialID +} + +func (DataFactoryCredentialServicePrincipalResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + d := metadata.ResourceData + client := metadata.Client.DataFactory.Credentials + + id, err := credentials.ParseCredentialID(d.Id()) + if err != nil { + return err + } + + existing, err := client.CredentialOperationsGet(ctx, *id, credentials.DefaultCredentialOperationsGetOperationOptions()) + if err != nil { + if response.WasNotFound(existing.HttpResponse) { + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + state := DataFactoryCredentialServicePrincipalResourceSchema{ + Name: id.CredentialName, + DataFactoryId: credentials.NewFactoryID(id.SubscriptionId, id.ResourceGroupName, id.FactoryName).ID(), + } + + if model := existing.Model; model != nil { + props, ok := model.Properties.(credentials.ServicePrincipalCredential) + if !ok { + return fmt.Errorf("retrieving %s: expected `credentials.ServicePrincipalCredential` but got %+v", id, model.Properties) + } + + state.Description = pointer.From(props.Description) + state.Annotations = flattenDataFactoryAnnotations(props.Annotations) + + if props.TypeProperties.Tenant != nil { + if v, ok := (*props.TypeProperties.Tenant).(string); ok { + state.TenantId = v + } + } + + if props.TypeProperties.ServicePrincipalId != nil { + if v, ok := (*props.TypeProperties.ServicePrincipalId).(string); ok { + state.ServicePrincipalId = v + } + } + + if props.TypeProperties.ServicePrincipalKey != nil { + state.ServicePrincipalKey = flattenDataFactoryCredentialKeyVaultSecretReference(props.TypeProperties.ServicePrincipalKey) + } + + if props.Description != nil { + state.Description = *props.Description + } + } + + return metadata.Encode(&state) + }, + } +} + +func (r DataFactoryCredentialServicePrincipalResource) Create() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.DataFactory.Credentials + + var data DataFactoryCredentialServicePrincipalResourceSchema + if err := metadata.Decode(&data); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + dataFactoryId, err := credentials.ParseFactoryID(data.DataFactoryId) + if err != nil { + return err + } + + id := credentials.NewCredentialID(dataFactoryId.SubscriptionId, dataFactoryId.ResourceGroupName, dataFactoryId.FactoryName, data.Name) + existing, err := client.CredentialOperationsGet(ctx, id, credentials.DefaultCredentialOperationsGetOperationOptions()) + if err != nil && !response.WasNotFound(existing.HttpResponse) { + return fmt.Errorf("checking for presence of existing %s: %+v", id, err) + } + + if !response.WasNotFound(existing.HttpResponse) { + return tf.ImportAsExistsError("azurerm_data_factory_dataset_http", id.ID()) + } + + var servicePrincipalId interface{} = data.ServicePrincipalId + var tenantId interface{} = data.TenantId + + props := credentials.ServicePrincipalCredential{ + TypeProperties: credentials.ServicePrincipalCredentialTypeProperties{ + ServicePrincipalId: pointer.To(servicePrincipalId), + ServicePrincipalKey: expandDataFactoryCredentialKeyVaultSecretReference(data.ServicePrincipalKey), + Tenant: pointer.To(tenantId), + }, + } + if len(data.Annotations) > 0 { + annotations := make([]interface{}, len(data.Annotations)) + for i, v := range data.Annotations { + annotations[i] = v + } + props.Annotations = &annotations + } + if data.Description != "" { + props.Description = &data.Description + } + + payload := credentials.CredentialResource{ + Properties: props, + } + if _, err = client.CredentialOperationsCreateOrUpdate(ctx, id, payload, credentials.DefaultCredentialOperationsCreateOrUpdateOperationOptions()); err != nil { + return fmt.Errorf("creating %s: %+v", id, err) + } + + metadata.SetID(id) + + return nil + }, + } +} + +func (r DataFactoryCredentialServicePrincipalResource) Update() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.DataFactory.Credentials + id, err := credentials.ParseCredentialID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + var data DataFactoryCredentialServicePrincipalResourceSchema + if err := metadata.Decode(&data); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + existing, err := client.CredentialOperationsGet(ctx, *id, credentials.DefaultCredentialOperationsGetOperationOptions()) + if err != nil { + return fmt.Errorf("checking for presence of existing %s: %+v", id.ID(), err) + } + + if existing.Model == nil { + return fmt.Errorf("retrieving %s: `model` was nil", *id) + } + + props, ok := existing.Model.Properties.(credentials.ServicePrincipalCredential) + if !ok { + return fmt.Errorf("retrieving %s: expected `credentials.ServicePrincipalCredential` but got %+v", id, existing.Model.Properties) + } + + if metadata.ResourceData.HasChange("description") { + props.Description = &data.Description + } + + if metadata.ResourceData.HasChange("annotations") { + if len(data.Annotations) > 0 { + annotations := make([]interface{}, len(data.Annotations)) + for i, v := range data.Annotations { + annotations[i] = v + } + props.Annotations = &annotations + } else { + props.Annotations = nil + } + } + + if metadata.ResourceData.HasChange("service_principal_key") { + props.TypeProperties.ServicePrincipalKey = expandDataFactoryCredentialKeyVaultSecretReference(data.ServicePrincipalKey) + } + + if metadata.ResourceData.HasChange("service_principal_id") { + var servicePrincipalId interface{} = data.ServicePrincipalId + props.TypeProperties.ServicePrincipalId = pointer.To(servicePrincipalId) + } + + if metadata.ResourceData.HasChange("tenant_id") { + var tenantId interface{} = data.TenantId + props.TypeProperties.Tenant = pointer.To(tenantId) + } + + payload := credentials.CredentialResource{ + Properties: props, + } + if _, err = client.CredentialOperationsCreateOrUpdate(ctx, *id, payload, credentials.DefaultCredentialOperationsCreateOrUpdateOperationOptions()); err != nil { + return fmt.Errorf("updating %s: %+v", id, err) + } + + metadata.SetID(id) + + return nil + }, + } +} + +func (DataFactoryCredentialServicePrincipalResource) Delete() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.DataFactory.Credentials + + id, err := credentials.ParseCredentialID(metadata.ResourceData.Id()) + if err != nil { + return err + } + + if _, err = client.CredentialOperationsDelete(ctx, *id); err != nil { + return fmt.Errorf("deleting %s: %+v", *id, err) + } + + return nil + }, + } +} + +func expandDataFactoryCredentialKeyVaultSecretReference(input []ServicePrincipalKey) *credentials.AzureKeyVaultSecretReference { + if len(input) == 0 { + return nil + } + + out := credentials.AzureKeyVaultSecretReference{ + SecretName: input[0].SecretName, + Store: credentials.LinkedServiceReference{ + Type: credentials.TypeLinkedServiceReference, + ReferenceName: input[0].LinkedServiceName, + }, + } + + if input[0].SecretVersion != "" { + var secretVersion interface{} = input[0].SecretVersion + out.SecretVersion = pointer.To(secretVersion) + } + + return pointer.To(out) +} + +func flattenDataFactoryCredentialKeyVaultSecretReference(input *credentials.AzureKeyVaultSecretReference) []ServicePrincipalKey { + if input == nil { + return []ServicePrincipalKey{} + } + var linkedServiceName, secretName, secretVersion string + if input.SecretName != nil { + if v, ok := input.SecretName.(string); ok { + secretName = v + } + } + if input.SecretVersion != nil { + if v, ok := (*input.SecretVersion).(string); ok { + secretVersion = v + } + } + linkedServiceName = input.Store.ReferenceName + return []ServicePrincipalKey{ + { + LinkedServiceName: linkedServiceName, + SecretName: secretName, + SecretVersion: secretVersion, + }, + } +} diff --git a/internal/services/datafactory/data_factory_credential_service_principal_resource_test.go b/internal/services/datafactory/data_factory_credential_service_principal_resource_test.go new file mode 100644 index 000000000000..f53dbcb951de --- /dev/null +++ b/internal/services/datafactory/data_factory_credential_service_principal_resource_test.go @@ -0,0 +1,238 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package datafactory_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/go-azure-sdk/resource-manager/datafactory/2018-06-01/credentials" + "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" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +type CredentialServicePrincipalResource struct{} + +func TestAccDataFactoryCredentialServicePrincipal_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_factory_credential_service_principal", "test") + r := CredentialServicePrincipalResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccDataFactoryCredentialServicePrincipal_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_factory_credential_service_principal", "test") + r := CredentialServicePrincipalResource{} + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + +func TestAccDataFactoryCredentialServicePrincipal_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_factory_credential_service_principal", "test") + r := CredentialServicePrincipalResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccDataFactoryCredentialServicePrincipal_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_factory_credential_service_principal", "test") + r := CredentialServicePrincipalResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r CredentialServicePrincipalResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := credentials.ParseCredentialID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.DataFactory.Credentials.CredentialOperationsGet(ctx, *id, credentials.DefaultCredentialOperationsGetOperationOptions()) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %+v", *id, err) + } + + return utils.Bool(resp.Model != nil), nil +} + +func (r CredentialServicePrincipalResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features { + key_vault { + purge_soft_delete_on_destroy = false + purge_soft_deleted_keys_on_destroy = false + } + } +} + +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-df-%[2]d" + location = "%[1]s" +} + +resource "azurerm_data_factory" "test" { + name = "acctestdf%[2]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv%[3]d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_retention_days = 7 + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Get", + ] + + secret_permissions = [ + "Set", + "Get", + "Delete", + "Purge", + "Recover" + ] + } +} + +resource "azurerm_key_vault_secret" "test" { + name = "acctestkvsecret" + value = "fakedsecret" + key_vault_id = azurerm_key_vault.test.id +} + +resource "azurerm_data_factory_linked_service_key_vault" "test" { + name = "acctestlskv%[2]d" + data_factory_id = azurerm_data_factory.test.id + key_vault_id = azurerm_key_vault.test.id +} + +resource "azurerm_data_factory_linked_service_key_vault" "test2" { + name = "acctestlskv2%[2]d" + data_factory_id = azurerm_data_factory.test.id + key_vault_id = azurerm_key_vault.test.id +} + +resource "azurerm_key_vault_secret" "test2" { + name = "anothersecret" + value = "fakedsecret" + key_vault_id = azurerm_key_vault.test.id +} +`, data.Locations.Primary, data.RandomInteger, data.RandomIntOfLength(8)) +} + +func (r CredentialServicePrincipalResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_data_factory_credential_service_principal" "test" { + name = "credential%d" + data_factory_id = azurerm_data_factory.test.id + tenant_id = data.azurerm_client_config.current.tenant_id + service_principal_id = data.azurerm_client_config.current.object_id + service_principal_key { + linked_service_name = azurerm_data_factory_linked_service_key_vault.test.name + secret_name = azurerm_key_vault_secret.test.name + } +} +`, r.template(data), data.RandomInteger) +} + +func (r CredentialServicePrincipalResource) requiresImport(data acceptance.TestData) string { + config := r.basic(data) + return fmt.Sprintf(` +%s + +resource "azurerm_data_factory_credential_service_principal" "import" { + name = azurerm_data_factory_credential_service_principal.test.name + data_factory_id = azurerm_data_factory_credential_service_principal.test.data_factory_id + tenant_id = data.azurerm_client_config.current.tenant_id + service_principal_id = data.azurerm_client_config.current.object_id + service_principal_key { + linked_service_name = azurerm_data_factory_linked_service_key_vault.test.name + secret_name = azurerm_key_vault_secret.test.name + } +} +`, config) +} + +func (r CredentialServicePrincipalResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%[1]s + +resource "azurerm_data_factory_credential_service_principal" "test" { + name = "credential%[2]d" + description = "UPDATED DESCRIPTION" + data_factory_id = azurerm_data_factory.test.id + tenant_id = "00000000-0000-0000-0000-000000000000" + service_principal_id = "00000000-0000-0000-0000-000000000000" + service_principal_key { + linked_service_name = azurerm_data_factory_linked_service_key_vault.test2.name + secret_name = azurerm_key_vault_secret.test2.name + secret_version = azurerm_key_vault_secret.test2.version + } + annotations = ["1", "2"] +} +`, r.template(data), data.RandomInteger) +} diff --git a/internal/services/datafactory/registration.go b/internal/services/datafactory/registration.go index 4d8d3f9ba441..ad326d5e68b1 100644 --- a/internal/services/datafactory/registration.go +++ b/internal/services/datafactory/registration.go @@ -40,6 +40,7 @@ func (Registration) DataSources() []sdk.DataSource { func (Registration) Resources() []sdk.Resource { return []sdk.Resource{ DataFactoryDatasetAzureSQLTableResource{}, + DataFactoryCredentialServicePrincipalResource{}, DataFactoryCredentialUserAssignedManagedIdentityResource{}, } } diff --git a/website/docs/r/data_factory_credential_service_principal.html.markdown b/website/docs/r/data_factory_credential_service_principal.html.markdown new file mode 100644 index 000000000000..d9f937d7c583 --- /dev/null +++ b/website/docs/r/data_factory_credential_service_principal.html.markdown @@ -0,0 +1,132 @@ +--- +subcategory: "Data Factory" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_data_factory_credential_user_managed_identity" +description: |- + Manage a Data Factory Service Principal credential resource +--- + +# azurerm_data_factory_credential_user_managed_identity + +Manage a Data Factory Service Principal credential resource. These resources are used by Data Factory to access data sources. + +## Example Usage + +```hcl +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "westeurope" +} + +resource "azurerm_data_factory" "example" { + name = "example" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name +} + +resource "azurerm_key_vault" "example" { + name = "example" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_retention_days = 7 + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Get", + ] + + secret_permissions = [ + "Set", + "Get", + "Delete", + "Purge", + "Recover" + ] + } +} + +resource "azurerm_key_vault_secret" "example" { + name = "example" + value = "example-secret" + key_vault_id = azurerm_key_vault.example.id +} + +resource "azurerm_data_factory_linked_service_key_vault" "example" { + name = "example" + data_factory_id = azurerm_data_factory.example.id + key_vault_id = azurerm_key_vault.example.id +} + +resource "azurerm_data_factory_credential_service_principal" "example" { + name = "example" + description = "example description" + data_factory_id = azurerm_data_factory.example.id + tenant_id = data.azurerm_client_config.current.tenant_id + service_principal_id = data.azurerm_client_config.current.client_id + service_principal_key { + linked_service_name = azurerm_data_factory_linked_service_key_vault.example.name + secret_name = azurerm_key_vault_secret.example.name + secret_version = azurerm_key_vault_secret.example.version + } + annotations = ["1", "2"] +} +``` + +## Argument Reference + +* `name` - (Required) Specifies the name of the Credential. Changing this forces a new resource to be created. + +* `data_factory_id` - (Required) The Data Factory ID in which to associate the Credential with. Changing this forces a new resource. + +* `tenant_id` - (Required) The Tenant ID of the Service Principal. + +* `service_principal_id` - (Required) The Client ID of the Service Principal. + +* `service_principal_key` - (Required) A `service_principal_key` block as defined below. + +* `annotations` - (Optional) List of tags that can be used for describing the Data Factory Credential. + +* `description` - (Optional) The description for the Data Factory Credential. + +--- + +A `service_principal_key` block supports the following: + +* `linked_service_name` - (Required) The name of the Linked Service to use for the Service Principal Key. + +* `secret_name` - (Required) The name of the Secret in the Key Vault. + +* `secret_version` - (Optional) The version of the Secret in the Key Vault. + +--- + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the Data Factory Credential. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/language/resources/syntax#operation-timeouts) for certain actions: + +* `create` - (Defaults to 5 minutes) Used when creating the Data Factory Credential. +* `update` - (Defaults to 5 minutes) Used when updating the Data Factory Credential. +* `read` - (Defaults to 5 minutes) Used when retrieving the Data Factory Credential. +* `delete` - (Defaults to 5 minutes) Used when deleting the Data Factory Credential. + +## Import + +Data Factory Credentials can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_data_factory_credential_service_principal.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-resources/providers/Microsoft.DataFactory/factories/example/credentials/credential1 +``` From b164874161186ac885133c29916f9370cd0370fd Mon Sep 17 00:00:00 2001 From: Heng Lu Date: Tue, 30 Apr 2024 11:20:30 +0800 Subject: [PATCH 2/4] fix tests --- ...y_credential_service_principal_resource.go | 2 +- ...user_assigned_managed_identity_resource.go | 2 +- ...assigned_managed_identity_resource_test.go | 26 +++++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/internal/services/datafactory/data_factory_credential_service_principal_resource.go b/internal/services/datafactory/data_factory_credential_service_principal_resource.go index 30a2124e7f19..e9bca1902cb9 100644 --- a/internal/services/datafactory/data_factory_credential_service_principal_resource.go +++ b/internal/services/datafactory/data_factory_credential_service_principal_resource.go @@ -215,7 +215,7 @@ func (r DataFactoryCredentialServicePrincipalResource) Create() sdk.ResourceFunc } if !response.WasNotFound(existing.HttpResponse) { - return tf.ImportAsExistsError("azurerm_data_factory_dataset_http", id.ID()) + return tf.ImportAsExistsError("azurerm_data_factory_credential_service_principal", id.ID()) } var servicePrincipalId interface{} = data.ServicePrincipalId diff --git a/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource.go b/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource.go index 860528c2e5ef..57b6373f26c6 100644 --- a/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource.go +++ b/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource.go @@ -158,7 +158,7 @@ func (r DataFactoryCredentialUserAssignedManagedIdentityResource) Create() sdk.R } if !response.WasNotFound(existing.HttpResponse) { - return tf.ImportAsExistsError("azurerm_data_factory_dataset_http", id.ID()) + return tf.ImportAsExistsError("azurerm_data_factory_credential_user_managed_identity", id.ID()) } props := credentials.ManagedIdentityCredential{ diff --git a/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource_test.go b/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource_test.go index c89abbbfdea4..e3672d1ea94b 100644 --- a/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource_test.go +++ b/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource_test.go @@ -35,6 +35,20 @@ func TestAccDataFactoryCredentialUserAssignedManagedIdentity_basic(t *testing.T) }) } +func TestAccDataFactoryCredentialUserAssignedManagedIdentity_requiresImport(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_data_factory_credential_user_managed_identity", "test") + r := CredentialUserAssignedManagedIdentityResource{} + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.RequiresImportErrorStep(r.requiresImport), + }) +} + func TestAccDataFactoryCredentialUserAssignedManagedIdentity_update(t *testing.T) { data := acceptance.BuildTestData(t, "azurerm_data_factory_credential_user_managed_identity", "test") r := CredentialUserAssignedManagedIdentityResource{} @@ -118,6 +132,18 @@ resource "azurerm_data_factory_credential_user_managed_identity" "test" { `, base, data.RandomInteger) } +func (r CredentialUserAssignedManagedIdentityResource) requiresImport(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_data_factory_credential_user_managed_identity" "import" { + name = azurerm_data_factory_credential_user_managed_identity.test.name + data_factory_id = azurerm_data_factory_credential_user_managed_identity.test.data_factory_id + identity_id = azurerm_data_factory_credential_user_managed_identity.test.identity_id +} +`, r.basic(data)) +} + func (r CredentialUserAssignedManagedIdentityResource) update(data acceptance.TestData) string { base := templateBase(data) From b3f490fec601fabe3ed0d27dfb96cc0ed20ab5c0 Mon Sep 17 00:00:00 2001 From: Heng Lu Date: Tue, 30 Apr 2024 11:30:00 +0800 Subject: [PATCH 3/4] update --- ...y_credential_service_principal_resource.go | 124 +++++++++--------- ...user_assigned_managed_identity_resource.go | 4 +- ...credential_service_principal.html.markdown | 4 +- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/internal/services/datafactory/data_factory_credential_service_principal_resource.go b/internal/services/datafactory/data_factory_credential_service_principal_resource.go index e9bca1902cb9..7f4ee0e3d1a7 100644 --- a/internal/services/datafactory/data_factory_credential_service_principal_resource.go +++ b/internal/services/datafactory/data_factory_credential_service_principal_resource.go @@ -131,67 +131,6 @@ func (DataFactoryCredentialServicePrincipalResource) IDValidationFunc() pluginsd return credentials.ValidateCredentialID } -func (DataFactoryCredentialServicePrincipalResource) Read() sdk.ResourceFunc { - return sdk.ResourceFunc{ - Timeout: 5 * time.Minute, - Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { - d := metadata.ResourceData - client := metadata.Client.DataFactory.Credentials - - id, err := credentials.ParseCredentialID(d.Id()) - if err != nil { - return err - } - - existing, err := client.CredentialOperationsGet(ctx, *id, credentials.DefaultCredentialOperationsGetOperationOptions()) - if err != nil { - if response.WasNotFound(existing.HttpResponse) { - return metadata.MarkAsGone(id) - } - - return fmt.Errorf("retrieving %s: %+v", *id, err) - } - - state := DataFactoryCredentialServicePrincipalResourceSchema{ - Name: id.CredentialName, - DataFactoryId: credentials.NewFactoryID(id.SubscriptionId, id.ResourceGroupName, id.FactoryName).ID(), - } - - if model := existing.Model; model != nil { - props, ok := model.Properties.(credentials.ServicePrincipalCredential) - if !ok { - return fmt.Errorf("retrieving %s: expected `credentials.ServicePrincipalCredential` but got %+v", id, model.Properties) - } - - state.Description = pointer.From(props.Description) - state.Annotations = flattenDataFactoryAnnotations(props.Annotations) - - if props.TypeProperties.Tenant != nil { - if v, ok := (*props.TypeProperties.Tenant).(string); ok { - state.TenantId = v - } - } - - if props.TypeProperties.ServicePrincipalId != nil { - if v, ok := (*props.TypeProperties.ServicePrincipalId).(string); ok { - state.ServicePrincipalId = v - } - } - - if props.TypeProperties.ServicePrincipalKey != nil { - state.ServicePrincipalKey = flattenDataFactoryCredentialKeyVaultSecretReference(props.TypeProperties.ServicePrincipalKey) - } - - if props.Description != nil { - state.Description = *props.Description - } - } - - return metadata.Encode(&state) - }, - } -} - func (r DataFactoryCredentialServicePrincipalResource) Create() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, @@ -253,6 +192,67 @@ func (r DataFactoryCredentialServicePrincipalResource) Create() sdk.ResourceFunc } } +func (DataFactoryCredentialServicePrincipalResource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Timeout: 5 * time.Minute, + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + d := metadata.ResourceData + client := metadata.Client.DataFactory.Credentials + + id, err := credentials.ParseCredentialID(d.Id()) + if err != nil { + return err + } + + existing, err := client.CredentialOperationsGet(ctx, *id, credentials.DefaultCredentialOperationsGetOperationOptions()) + if err != nil { + if response.WasNotFound(existing.HttpResponse) { + return metadata.MarkAsGone(id) + } + + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + state := DataFactoryCredentialServicePrincipalResourceSchema{ + Name: id.CredentialName, + DataFactoryId: credentials.NewFactoryID(id.SubscriptionId, id.ResourceGroupName, id.FactoryName).ID(), + } + + if model := existing.Model; model != nil { + props, ok := model.Properties.(credentials.ServicePrincipalCredential) + if !ok { + return fmt.Errorf("retrieving %s: expected `credentials.ServicePrincipalCredential` but got %T", id, model.Properties) + } + + state.Description = pointer.From(props.Description) + state.Annotations = flattenDataFactoryAnnotations(props.Annotations) + + if props.TypeProperties.Tenant != nil { + if v, ok := (*props.TypeProperties.Tenant).(string); ok { + state.TenantId = v + } + } + + if props.TypeProperties.ServicePrincipalId != nil { + if v, ok := (*props.TypeProperties.ServicePrincipalId).(string); ok { + state.ServicePrincipalId = v + } + } + + if props.TypeProperties.ServicePrincipalKey != nil { + state.ServicePrincipalKey = flattenDataFactoryCredentialKeyVaultSecretReference(props.TypeProperties.ServicePrincipalKey) + } + + if props.Description != nil { + state.Description = *props.Description + } + } + + return metadata.Encode(&state) + }, + } +} + func (r DataFactoryCredentialServicePrincipalResource) Update() sdk.ResourceFunc { return sdk.ResourceFunc{ Timeout: 5 * time.Minute, @@ -279,7 +279,7 @@ func (r DataFactoryCredentialServicePrincipalResource) Update() sdk.ResourceFunc props, ok := existing.Model.Properties.(credentials.ServicePrincipalCredential) if !ok { - return fmt.Errorf("retrieving %s: expected `credentials.ServicePrincipalCredential` but got %+v", id, existing.Model.Properties) + return fmt.Errorf("retrieving %s: expected `credentials.ServicePrincipalCredential` but got %T", id, existing.Model.Properties) } if metadata.ResourceData.HasChange("description") { diff --git a/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource.go b/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource.go index 57b6373f26c6..3d96bb9f441d 100644 --- a/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource.go +++ b/internal/services/datafactory/data_factory_credential_user_assigned_managed_identity_resource.go @@ -116,7 +116,7 @@ func (DataFactoryCredentialUserAssignedManagedIdentityResource) Read() sdk.Resou if model := existing.Model; model != nil { props, ok := model.Properties.(credentials.ManagedIdentityCredential) if !ok { - return fmt.Errorf("retrieving %s: expected `credentials.ManagedIdentityCredential` but got %+v", id, model.Properties) + return fmt.Errorf("retrieving %s: expected `credentials.ManagedIdentityCredential` but got %T", id, model.Properties) } if props.Description != nil { @@ -217,7 +217,7 @@ func (r DataFactoryCredentialUserAssignedManagedIdentityResource) Update() sdk.R props, ok := existing.Model.Properties.(credentials.ManagedIdentityCredential) if !ok { - return fmt.Errorf("retrieving %s: expected `credentials.ManagedIdentityCredential` but got %+v", id, existing.Model.Properties) + return fmt.Errorf("retrieving %s: expected `credentials.ManagedIdentityCredential` but got %T", id, existing.Model.Properties) } if metadata.ResourceData.HasChange("description") { diff --git a/website/docs/r/data_factory_credential_service_principal.html.markdown b/website/docs/r/data_factory_credential_service_principal.html.markdown index d9f937d7c583..b4386d9bc8a4 100644 --- a/website/docs/r/data_factory_credential_service_principal.html.markdown +++ b/website/docs/r/data_factory_credential_service_principal.html.markdown @@ -1,12 +1,12 @@ --- subcategory: "Data Factory" layout: "azurerm" -page_title: "Azure Resource Manager: azurerm_data_factory_credential_user_managed_identity" +page_title: "Azure Resource Manager: azurerm_data_factory_credential_service_principal" description: |- Manage a Data Factory Service Principal credential resource --- -# azurerm_data_factory_credential_user_managed_identity +# azurerm_data_factory_credential_service_principal Manage a Data Factory Service Principal credential resource. These resources are used by Data Factory to access data sources. From b71621ea31054cb8bc08f1cd03676c6f9aff4690 Mon Sep 17 00:00:00 2001 From: Heng Lu Date: Tue, 7 May 2024 09:56:17 +0800 Subject: [PATCH 4/4] update tests --- ...data_factory_credential_service_principal_resource_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/services/datafactory/data_factory_credential_service_principal_resource_test.go b/internal/services/datafactory/data_factory_credential_service_principal_resource_test.go index f53dbcb951de..79af682ab05d 100644 --- a/internal/services/datafactory/data_factory_credential_service_principal_resource_test.go +++ b/internal/services/datafactory/data_factory_credential_service_principal_resource_test.go @@ -130,7 +130,7 @@ resource "azurerm_data_factory" "test" { } resource "azurerm_key_vault" "test" { - name = "acctestkv%[3]d" + name = "kv%[2]d" location = azurerm_resource_group.test.location resource_group_name = azurerm_resource_group.test.name tenant_id = data.azurerm_client_config.current.tenant_id @@ -179,7 +179,7 @@ resource "azurerm_key_vault_secret" "test2" { value = "fakedsecret" key_vault_id = azurerm_key_vault.test.id } -`, data.Locations.Primary, data.RandomInteger, data.RandomIntOfLength(8)) +`, data.Locations.Primary, data.RandomInteger) } func (r CredentialServicePrincipalResource) basic(data acceptance.TestData) string {