diff options
| author | Arnon S <arnon.san@cpf.co.th> | 2022-11-05 23:56:16 +0700 |
|---|---|---|
| committer | Arnon S <arnon.san@cpf.co.th> | 2022-11-05 23:56:16 +0700 |
| commit | 332c82b84685abca54d83b5f23adeea7bbbbef84 (patch) | |
| tree | 0fff5571e71d0c7c7195236922590d46791e6c51 /azuredevops | |
| parent | 270599b9a42e85380a0ac0e48ab164be922238fc (diff) | |
feat(serviceendpoint_externaltfs): initialized externaltfs service endpoint resource, tests, and docs
Diffstat (limited to 'azuredevops')
5 files changed, 453 insertions, 0 deletions
diff --git a/azuredevops/internal/acceptancetests/resource_serviceendpoint_externaltfs_test.go b/azuredevops/internal/acceptancetests/resource_serviceendpoint_externaltfs_test.go new file mode 100644 index 00000000..6ee6f16a --- /dev/null +++ b/azuredevops/internal/acceptancetests/resource_serviceendpoint_externaltfs_test.go @@ -0,0 +1,148 @@ +//go:build (all || resource_serviceendpoint_externaltfs) && !exclude_serviceendpoints +// +build all resource_serviceendpoint_externaltfs +// +build !exclude_serviceendpoints + +package acceptancetests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/acceptancetests/testutils" +) + +func TestAccServiceEndpointExternalTFS_PersonalTokenBasic(t *testing.T) { + projectName := testutils.GenerateResourceName() + serviceEndpointName := testutils.GenerateResourceName() + resourceType := "azuredevops_serviceendpoint_externaltfs" + tfSvcEpNode := resourceType + ".serviceendpoint" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testutils.PreCheck(t, nil) }, + Providers: testutils.GetProviders(), + CheckDestroy: testutils.CheckServiceEndpointDestroyed(resourceType), + Steps: []resource.TestStep{ + { + Config: hclSvcEndpointExternalTFSResourceBasic(projectName, serviceEndpointName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(tfSvcEpNode, "project_id"), + resource.TestCheckResourceAttr(tfSvcEpNode, "auth_personal.#", "1"), + resource.TestCheckResourceAttr(tfSvcEpNode, "service_endpoint_name", serviceEndpointName), + resource.TestCheckResourceAttr(tfSvcEpNode, "description", "Managed by Terraform"), + resource.TestCheckResourceAttr(tfSvcEpNode, "connection_url", "https://dev.azure.com/myorganization"), + testutils.CheckServiceEndpointExistsWithName(tfSvcEpNode, serviceEndpointName), + ), + }, + }, + }) +} + +func TestAccServiceEndpointExternalTFS_PersonalTokenUpdate(t *testing.T) { + projectName := testutils.GenerateResourceName() + serviceEndpointNameFirst := testutils.GenerateResourceName() + serviceEndpointNameSecond := testutils.GenerateResourceName() + description := "Managed by Terraform" + resourceType := "azuredevops_serviceendpoint_externaltfs" + tfSvcEpNode := resourceType + ".serviceendpoint" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testutils.PreCheck(t, nil) }, + Providers: testutils.GetProviders(), + CheckDestroy: testutils.CheckServiceEndpointDestroyed(resourceType), + Steps: []resource.TestStep{ + { + Config: hclSvcEndpointExternalTFSResourceBasic(projectName, serviceEndpointNameFirst), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(tfSvcEpNode, "project_id"), + resource.TestCheckResourceAttr(tfSvcEpNode, "auth_personal.#", "1"), + resource.TestCheckResourceAttr(tfSvcEpNode, "service_endpoint_name", serviceEndpointNameFirst), + resource.TestCheckResourceAttr(tfSvcEpNode, "connection_url", "https://dev.azure.com/myorganization"), + testutils.CheckServiceEndpointExistsWithName(tfSvcEpNode, serviceEndpointNameFirst), + ), + }, + { + Config: hclSvcEndpointExternalTFSResourceUpdate(projectName, serviceEndpointNameSecond, description), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(tfSvcEpNode, "project_id"), + resource.TestCheckResourceAttr(tfSvcEpNode, "auth_personal.#", "1"), + resource.TestCheckResourceAttr(tfSvcEpNode, "service_endpoint_name", serviceEndpointNameSecond), + resource.TestCheckResourceAttr(tfSvcEpNode, "description", description), + resource.TestCheckResourceAttr(tfSvcEpNode, "connection_url", "https://dev.azure.com/myorganization"), + testutils.CheckServiceEndpointExistsWithName(tfSvcEpNode, serviceEndpointNameSecond), + ), + }, + }, + }) +} + +func TestAccServiceEndpointExternalTFS_CreateAndUpdate(t *testing.T) { + projectName := testutils.GenerateResourceName() + serviceEndpointNameFirst := testutils.GenerateResourceName() + serviceEndpointNameSecond := testutils.GenerateResourceName() + resourceType := "azuredevops_serviceendpoint_externaltfs" + tfSvcEpNode := resourceType + ".serviceendpoint" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testutils.PreCheck(t, nil) }, + Providers: testutils.GetProviders(), + CheckDestroy: testutils.CheckServiceEndpointDestroyed(resourceType), + Steps: []resource.TestStep{ + { + Config: hclSvcEndpointExternalTFSResourceBasic(projectName, serviceEndpointNameFirst), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(tfSvcEpNode, "project_id"), + resource.TestCheckResourceAttr(tfSvcEpNode, "auth_personal.#", "1"), + resource.TestCheckResourceAttr(tfSvcEpNode, "service_endpoint_name", serviceEndpointNameFirst), + resource.TestCheckResourceAttr(tfSvcEpNode, "description", "Managed by Terraform"), + resource.TestCheckResourceAttr(tfSvcEpNode, "connection_url", "https://dev.azure.com/myorganization"), + testutils.CheckServiceEndpointExistsWithName(tfSvcEpNode, serviceEndpointNameFirst), + ), + }, + { + Config: hclSvcEndpointExternalTFSResourceBasic(projectName, serviceEndpointNameSecond), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(tfSvcEpNode, "project_id"), + resource.TestCheckResourceAttr(tfSvcEpNode, "auth_personal.#", "1"), + resource.TestCheckResourceAttr(tfSvcEpNode, "service_endpoint_name", serviceEndpointNameSecond), + resource.TestCheckResourceAttr(tfSvcEpNode, "description", "Managed by Terraform"), + resource.TestCheckResourceAttr(tfSvcEpNode, "connection_url", "https://dev.azure.com/myorganization"), + testutils.CheckServiceEndpointExistsWithName(tfSvcEpNode, serviceEndpointNameSecond), + ), + }, + { + ResourceName: tfSvcEpNode, + ImportStateIdFunc: testutils.ComputeProjectQualifiedResourceImportID(tfSvcEpNode), + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"auth_personal"}, + }, + }, + }) +} + +func hclSvcEndpointExternalTFSResourceBasic(projectName string, serviceEndpointName string) string { + projectResource := testutils.HclProjectResource(projectName) + serviceEndpointResource := fmt.Sprintf(` +resource "azuredevops_serviceendpoint_externaltfs" "serviceendpoint" { + project_id = azuredevops_project.project.id + service_endpoint_name = "%[1]s" + connection_url = "https://dev.azure.com/myorganization" + auth_personal { + personal_access_token = "test_token_basic" + } +}`, serviceEndpointName) + return fmt.Sprintf("%s\n%s", projectResource, serviceEndpointResource) +} + +func hclSvcEndpointExternalTFSResourceUpdate(projectName string, serviceEndpointName string, description string) string { + projectResource := testutils.HclProjectResource(projectName) + serviceEndpointResource := fmt.Sprintf(` +resource "azuredevops_serviceendpoint_externaltfs" "serviceendpoint" { + project_id = azuredevops_project.project.id + service_endpoint_name = "%[1]s" + connection_url = "https://dev.azure.com/myorganization" + auth_personal { + personal_access_token = "test_token_update" + } + description = "%[2]s" +}`, serviceEndpointName, description) + return fmt.Sprintf("%s\n%s", projectResource, serviceEndpointResource) +} diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs.go new file mode 100644 index 00000000..9e97e39c --- /dev/null +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs.go @@ -0,0 +1,111 @@ +package serviceendpoint + +import ( + "strings" + + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "github.com/microsoft/azure-devops-go-api/azuredevops/v6/serviceendpoint" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/converter" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/tfhelper" +) + +const ( + personalAccessTokenExternalTFS = "personal_access_token" +) + +func ResourceServiceEndpointExternalTFS() *schema.Resource { + r := genBaseServiceEndpointResource(flattenServiceEndpointExternalTFS, expandServiceEndpointExternalTFS) + r.Schema["connection_url"] = &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.IsURLWithHTTPorHTTPS, + Required: true, + Description: "URL of the Azure DevOps organization or the TFS Project Collection to connect to.", + } + authPersonal := &schema.Resource{ + Schema: map[string]*schema.Schema{ + personalAccessTokenExternalTFS: { + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("AZDO_PERSONAL_ACCESS_TOKEN", nil), + Description: "Personal access tokens are applicable only for connections targeting Azure DevOps organization or TFS 2017 (and higher)", + Sensitive: true, + ValidateFunc: validation.StringIsNotEmpty, + }, + }, + } + patHashKey, patHashSchema := tfhelper.GenerateSecreteMemoSchema(personalAccessTokenExternalTFS) + authPersonal.Schema[patHashKey] = patHashSchema + r.Schema["auth_personal"] = &schema.Schema{ + Type: schema.TypeSet, + MinItems: 1, + MaxItems: 1, + Elem: authPersonal, + Required: true, + } + return r +} + +func expandServiceEndpointExternalTFS(d *schema.ResourceData) (*serviceendpoint.ServiceEndpoint, *uuid.UUID, error) { + serviceEndpoint, projectID := doBaseExpansion(d) + serviceEndpoint.Type = converter.String("externaltfs") + serviceEndpoint.Url = converter.String(d.Get("connection_url").(string)) + + scheme := "Token" + parameters := map[string]string{} + + if config, ok := d.GetOk("auth_personal"); ok { + scheme = "Token" + parameters = expandAuthPersonalSetExternalTFS(config.(*schema.Set)) + } + + serviceEndpoint.Authorization = &serviceendpoint.EndpointAuthorization{ + Parameters: ¶meters, + Scheme: &scheme, + } + return serviceEndpoint, projectID, nil +} + +func expandAuthPersonalSetExternalTFS(d *schema.Set) map[string]string { + authPerson := make(map[string]string) + val := d.List()[0].(map[string]interface{}) + + authPerson["apitoken"] = val[personalAccessTokenExternalTFS].(string) + return authPerson +} + +func flattenServiceEndpointExternalTFS( + d *schema.ResourceData, + serviceEndpoint *serviceendpoint.ServiceEndpoint, + projectID *uuid.UUID, +) { + doBaseFlattening(d, serviceEndpoint, projectID) + + if strings.EqualFold(*serviceEndpoint.Authorization.Scheme, "Token") { + authPersonalSet := d.Get("auth_personal").(*schema.Set).List() + authPersonal := flattenAuthPersonExternalTFS(d, authPersonalSet) + if authPersonal != nil { + d.Set("auth_personal", authPersonal) + } + } + + d.Set("connection_url", *serviceEndpoint.Url) +} + +func flattenAuthPersonExternalTFS(d *schema.ResourceData, authPersonalSet []interface{}) []interface{} { + if len(authPersonalSet) == 1 { + if authPersonal, ok := authPersonalSet[0].(map[string]interface{}); ok { + newHash, hashKey := tfhelper.HelpFlattenSecretNested( + d, + "auth_personal", + authPersonal, + personalAccessTokenExternalTFS, + ) + authPersonal[hashKey] = newHash + return []interface{}{authPersonal} + } + } + return nil +} diff --git a/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs_test.go b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs_test.go new file mode 100644 index 00000000..fc7fc7be --- /dev/null +++ b/azuredevops/internal/service/serviceendpoint/resource_serviceendpoint_externaltfs_test.go @@ -0,0 +1,192 @@ +//go:build (all || resource_serviceendpoint_externaltfs) && !exclude_serviceendpoints +// +build all resource_serviceendpoint_externaltfs +// +build !exclude_serviceendpoints + +package serviceendpoint + +import ( + "context" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/microsoft/azure-devops-go-api/azuredevops/v6/serviceendpoint" + "github.com/microsoft/terraform-provider-azuredevops/azdosdkmocks" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/client" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/converter" + "github.com/stretchr/testify/require" +) + +var ( + externalTfsTestServiceEndpointID = uuid.New() + externalTfsRandomServiceEndpointProjectID = uuid.New() + externalTfsTestServiceEndpointProjectID = &externalTfsRandomServiceEndpointProjectID +) + +var externalTfsTestServiceEndpoint = serviceendpoint.ServiceEndpoint{ + Authorization: &serviceendpoint.EndpointAuthorization{ + Parameters: &map[string]string{ + "apitoken": "UNIT_TEST_ACCESS_TOKEN", + }, + Scheme: converter.String("Token"), + }, + Id: &externalTfsTestServiceEndpointID, + Name: converter.String("UNIT_TEST_NAME"), + Owner: converter.String("library"), + Type: converter.String("externaltfs"), + Url: converter.String("https://dev.azure.com/myorganization"), + ServiceEndpointProjectReferences: &[]serviceendpoint.ServiceEndpointProjectReference{ + { + ProjectReference: &serviceendpoint.ProjectReference{ + Id: externalTfsTestServiceEndpointProjectID, + }, + Name: converter.String("UNIT_TEST_NAME"), + Description: converter.String("UNIT_TEST_DESCRIPTION"), + }, + }, +} + +func TestServiceEndpointExternalTFS_ExpandFlatten_Roundtrip(t *testing.T) { + resourceData := schema.TestResourceDataRaw(t, ResourceServiceEndpointExternalTFS().Schema, nil) + configureExternalTfsAuthPersonal(resourceData) + flattenServiceEndpointExternalTFS( + resourceData, + &externalTfsTestServiceEndpoint, + externalTfsTestServiceEndpointProjectID, + ) + + serviceEndpointAfterRoundTrip, projectID, err := expandServiceEndpointExternalTFS(resourceData) + + require.Nil(t, err) + require.Equal(t, externalTfsTestServiceEndpoint, *serviceEndpointAfterRoundTrip) + require.Equal(t, externalTfsTestServiceEndpointProjectID, projectID) +} + +func TestServiceEndpointExternalTFS_Create_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointExternalTFS() + resourceData := schema.TestResourceDataRaw(t, r.Schema, nil) + configureExternalTfsAuthPersonal(resourceData) + flattenServiceEndpointExternalTFS( + resourceData, + &externalTfsTestServiceEndpoint, + externalTfsTestServiceEndpointProjectID, + ) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + expectedArgs := serviceendpoint.CreateServiceEndpointArgs{Endpoint: &externalTfsTestServiceEndpoint} + buildClient. + EXPECT(). + CreateServiceEndpoint(clients.Ctx, expectedArgs). + Return(nil, errors.New("CreateServiceEndpoint() Failed")). + Times(1) + + err := r.Create(resourceData, clients) + require.Contains(t, err.Error(), "CreateServiceEndpoint() Failed") +} + +func TestServiceEndpointExternalTFS_Read_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointExternalTFS() + resourceData := schema.TestResourceDataRaw(t, r.Schema, nil) + flattenServiceEndpointExternalTFS( + resourceData, + &externalTfsTestServiceEndpoint, + externalTfsTestServiceEndpointProjectID, + ) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + expectedArgs := serviceendpoint.GetServiceEndpointDetailsArgs{ + EndpointId: externalTfsTestServiceEndpoint.Id, + Project: converter.String(externalTfsTestServiceEndpointProjectID.String()), + } + buildClient. + EXPECT(). + GetServiceEndpointDetails(clients.Ctx, expectedArgs). + Return(nil, errors.New("GetServiceEndpoint() Failed")). + Times(1) + + err := r.Read(resourceData, clients) + require.Contains(t, err.Error(), "GetServiceEndpoint() Failed") +} + +func TestServiceEndpointExternalTFS_Delete_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointExternalTFS() + resourceData := schema.TestResourceDataRaw(t, r.Schema, nil) + flattenServiceEndpointExternalTFS( + resourceData, + &externalTfsTestServiceEndpoint, + externalTfsTestServiceEndpointProjectID, + ) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + expectedArgs := serviceendpoint.DeleteServiceEndpointArgs{ + EndpointId: externalTfsTestServiceEndpoint.Id, + ProjectIds: &[]string{ + externalTfsTestServiceEndpointProjectID.String(), + }, + } + + buildClient. + EXPECT(). + DeleteServiceEndpoint(clients.Ctx, expectedArgs). + Return(errors.New("DeleteServiceEndpoint() Failed")). + Times(1) + + err := r.Delete(resourceData, clients) + require.Contains(t, err.Error(), "DeleteServiceEndpoint() Failed") +} + +func TestServiceEndpointExternalTFS_Update_DoesNotSwallowError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + r := ResourceServiceEndpointExternalTFS() + resourceData := schema.TestResourceDataRaw(t, r.Schema, nil) + configureExternalTfsAuthPersonal(resourceData) + flattenServiceEndpointExternalTFS( + resourceData, + &externalTfsTestServiceEndpoint, + externalTfsTestServiceEndpointProjectID, + ) + + buildClient := azdosdkmocks.NewMockServiceendpointClient(ctrl) + clients := &client.AggregatedClient{ServiceEndpointClient: buildClient, Ctx: context.Background()} + + expectedArgs := serviceendpoint.UpdateServiceEndpointArgs{ + Endpoint: &externalTfsTestServiceEndpoint, + EndpointId: externalTfsTestServiceEndpoint.Id, + } + + buildClient. + EXPECT(). + UpdateServiceEndpoint(clients.Ctx, expectedArgs). + Return(nil, errors.New("UpdateServiceEndpoint() Failed")). + Times(1) + + err := r.Update(resourceData, clients) + require.Contains(t, err.Error(), "UpdateServiceEndpoint() Failed") +} + +func configureExternalTfsAuthPersonal(d *schema.ResourceData) { + d.Set("auth_personal", &[]map[string]interface{}{ + { + personalAccessTokenExternalTFS: "UNIT_TEST_ACCESS_TOKEN", + }, + }) +} diff --git a/azuredevops/provider.go b/azuredevops/provider.go index 96229c24..9aa3726b 100644 --- a/azuredevops/provider.go +++ b/azuredevops/provider.go @@ -66,6 +66,7 @@ func Provider() *schema.Provider { "azuredevops_serviceendpoint_npm": serviceendpoint.ResourceServiceEndpointNpm(), "azuredevops_serviceendpoint_generic": serviceendpoint.ResourceServiceEndpointGeneric(), "azuredevops_serviceendpoint_generic_git": serviceendpoint.ResourceServiceEndpointGenericGit(), + "azuredevops_serviceendpoint_externaltfs": serviceendpoint.ResourceServiceEndpointExternalTFS(), "azuredevops_git_repository": git.ResourceGitRepository(), "azuredevops_git_repository_file": git.ResourceGitRepositoryFile(), "azuredevops_user_entitlement": memberentitlementmanagement.ResourceUserEntitlement(), diff --git a/azuredevops/provider_test.go b/azuredevops/provider_test.go index b5c27fb9..92d264c7 100644 --- a/azuredevops/provider_test.go +++ b/azuredevops/provider_test.go @@ -43,6 +43,7 @@ func TestProvider_HasChildResources(t *testing.T) { "azuredevops_serviceendpoint_generic_git", "azuredevops_serviceendpoint_octopusdeploy", "azuredevops_serviceendpoint_incomingwebhook", + "azuredevops_serviceendpoint_externaltfs", "azuredevops_variable_group", "azuredevops_repository_policy_author_email_pattern", "azuredevops_repository_policy_case_enforcement", |
