diff --git a/azuredevops/internal/acceptancetests/resource_git_repository_test.go b/azuredevops/internal/acceptancetests/resource_git_repository_test.go index 29c2dc338..06c0cbb2e 100644 --- a/azuredevops/internal/acceptancetests/resource_git_repository_test.go +++ b/azuredevops/internal/acceptancetests/resource_git_repository_test.go @@ -20,18 +20,11 @@ import ( "github.com/microsoft/terraform-provider-azuredevops/azuredevops/internal/utils/converter" ) -// Verifies that the following sequence of events occurrs without error: -// -// (1) TF apply creates resource -// (2) TF state values are set -// (3) resource can be queried by ID and has expected name -// (4) TF destroy deletes resource -// (5) resource can no longer be queried by ID -func TestAccGitRepo_CreateAndUpdate(t *testing.T) { +func TestAccGitRepository_update(t *testing.T) { projectName := testutils.GenerateResourceName() gitRepoNameFirst := testutils.GenerateResourceName() gitRepoNameSecond := testutils.GenerateResourceName() - tfRepoNode := "azuredevops_git_repository.repository" + tfRepoNode := "azuredevops_git_repository.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testutils.PreCheck(t, nil) }, @@ -39,7 +32,7 @@ func TestAccGitRepo_CreateAndUpdate(t *testing.T) { CheckDestroy: checkGitRepoDestroyed, Steps: []resource.TestStep{ { - Config: testutils.HclGitRepoResource(projectName, gitRepoNameFirst, "Uninitialized"), + Config: hclGitRepositoryBasic(projectName, gitRepoNameFirst, "Uninitialized"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoNameFirst), @@ -53,7 +46,7 @@ func TestAccGitRepo_CreateAndUpdate(t *testing.T) { ), }, { - Config: testutils.HclGitRepoResource(projectName, gitRepoNameSecond, "Uninitialized"), + Config: hclGitRepositoryBasic(projectName, gitRepoNameSecond, "Uninitialized"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoNameSecond), @@ -70,17 +63,10 @@ func TestAccGitRepo_CreateAndUpdate(t *testing.T) { }) } -// This test verifies the following: -// 1. You can create a repo with disabled = true -// 2. You cannot update a disabled repo -// 3. You can enable a disabled repo -func TestAccGitRepo_CreateAndDisable(t *testing.T) { +func TestAccGitRepository_disabled(t *testing.T) { projectName := testutils.GenerateResourceName() gitRepoName := testutils.GenerateResourceName() - gitRepoName2 := testutils.GenerateResourceName() - tfRepoNode := "azuredevops_git_repository.repository" - disabledTrue := "true" - disabledFalse := "false" + tfRepoNode := "azuredevops_git_repository.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testutils.PreCheck(t, nil) }, @@ -88,74 +74,79 @@ func TestAccGitRepo_CreateAndDisable(t *testing.T) { CheckDestroy: checkGitRepoDestroyed, Steps: []resource.TestStep{ { - Config: gitRepoDisabledResourceConfig(projectName, gitRepoName, disabledTrue), + Config: hclGitRepositoryDisable(projectName, gitRepoName, true), Check: resource.ComposeTestCheckFunc( + checkGitRepoExists(gitRepoName), resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoName), - checkGitRepoExists(gitRepoName), - resource.TestCheckResourceAttr(tfRepoNode, "disabled", disabledTrue), + resource.TestCheckResourceAttr(tfRepoNode, "disabled", "true"), ), }, { - Config: gitRepoDisabledResourceConfig(projectName, gitRepoName2, disabledTrue), + Config: hclGitRepositoryDisable(projectName, gitRepoName, false), Check: resource.ComposeTestCheckFunc( + checkGitRepoExists(gitRepoName), resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoName), - checkGitRepoExists(gitRepoName), - resource.TestCheckResourceAttr(tfRepoNode, "disabled", disabledTrue), + resource.TestCheckResourceAttr(tfRepoNode, "disabled", "false"), ), - ExpectError: regexp.MustCompile(`A disabled repository cannot be updated, please enable the repository before attempting to update`), }, + }, + }) +} + +func TestAccGitRepository_disabledCannotUpdate(t *testing.T) { + projectName := testutils.GenerateResourceName() + gitRepoName := testutils.GenerateResourceName() + gitRepoNameUpdate := gitRepoName + "update" + tfRepoNode := "azuredevops_git_repository.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testutils.PreCheck(t, nil) }, + Providers: testutils.GetProviders(), + CheckDestroy: checkGitRepoDestroyed, + Steps: []resource.TestStep{ { - Config: gitRepoDisabledResourceConfig(projectName, gitRepoName, disabledFalse), + Config: hclGitRepositoryDisable(projectName, gitRepoName, true), Check: resource.ComposeTestCheckFunc( + checkGitRepoExists(gitRepoName), resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoName), - checkGitRepoExists(gitRepoName), - resource.TestCheckResourceAttr(tfRepoNode, "disabled", disabledFalse), + resource.TestCheckResourceAttr(tfRepoNode, "disabled", "true"), ), }, { - Config: gitRepoDisabledResourceConfig(projectName, gitRepoName, disabledTrue), + Config: hclGitRepositoryDisable(projectName, gitRepoNameUpdate, true), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), - resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoName), checkGitRepoExists(gitRepoName), - resource.TestCheckResourceAttr(tfRepoNode, "disabled", disabledTrue), + resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), + resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoNameUpdate), + resource.TestCheckResourceAttr(tfRepoNode, "disabled", "true"), ), + ExpectError: regexp.MustCompile(`A disabled repository cannot be updated, please enable the repository before attempting to update`), }, { - Config: gitRepoDisabledResourceConfig(projectName, gitRepoName, disabledFalse), + Config: hclGitRepositoryDisable(projectName, gitRepoName, false), Check: resource.ComposeTestCheckFunc( + checkGitRepoExists(gitRepoName), resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoName), - checkGitRepoExists(gitRepoName), - resource.TestCheckResourceAttr(tfRepoNode, "disabled", disabledFalse), + resource.TestCheckResourceAttr(tfRepoNode, "disabled", "false"), ), }, }, }) } -// Verifies that the create operation fails if the initialization is -// not specified. -func TestAccGitRepo_Create_IncorrectInitialization(t *testing.T) { +func TestAccGitRepository_incorrectInitialization(t *testing.T) { projectName := testutils.GenerateResourceName() gitRepoName := testutils.GenerateResourceName() - azureGitRepoResource := fmt.Sprintf(` - resource "azuredevops_git_repository" "repository" { - project_id = azuredevops_project.project.id - name = "%s" - }`, gitRepoName) - projectResource := testutils.HclProjectResource(projectName) - gitRepoResource := fmt.Sprintf("%s\n%s", projectResource, azureGitRepoResource) - resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testutils.PreCheck(t, nil) }, Providers: testutils.GetProviders(), Steps: []resource.TestStep{ { - Config: gitRepoResource, + Config: hclGitRepositoryIncorrectInitialization(projectName, gitRepoName), ExpectError: regexp.MustCompile(`Insufficient initialization blocks`), }, }, @@ -163,17 +154,17 @@ func TestAccGitRepo_Create_IncorrectInitialization(t *testing.T) { } -func TestAccGitRepo_Create_Import(t *testing.T) { +func TestAccGitRepository_import(t *testing.T) { projectName := testutils.GenerateResourceName() gitRepoName := testutils.GenerateResourceName() - repoImportConfig := testutils.HclProjectGitRepositoryImport(gitRepoName, projectName) - tfRepoNode := "azuredevops_git_repository.repository" + + tfRepoNode := "azuredevops_git_repository.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testutils.PreCheck(t, nil) }, Providers: testutils.GetProviders(), Steps: []resource.TestStep{ { - Config: repoImportConfig, + Config: hclGitRepositoryImport(projectName, gitRepoName), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttrSet(tfRepoNode, "is_fork"), resource.TestCheckResourceAttrSet(tfRepoNode, "remote_url"), @@ -189,12 +180,10 @@ func TestAccGitRepo_Create_Import(t *testing.T) { } -// Verifies that a newly created repo with init_type of "Clean" has the expected -// master branch available -func TestAccGitRepo_RepoInitialization_Clean(t *testing.T) { +func TestAccGitRepository_initializationClean(t *testing.T) { projectName := testutils.GenerateResourceName() gitRepoName := testutils.GenerateResourceName() - tfRepoNode := "azuredevops_git_repository.repository" + tfRepoNode := "azuredevops_git_repository.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testutils.PreCheck(t, nil) }, @@ -202,7 +191,7 @@ func TestAccGitRepo_RepoInitialization_Clean(t *testing.T) { CheckDestroy: checkGitRepoDestroyed, Steps: []resource.TestStep{ { - Config: testutils.HclGitRepoResource(projectName, gitRepoName, "Clean"), + Config: hclGitRepositoryBasic(projectName, gitRepoName, "Clean"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), resource.TestCheckResourceAttr(tfRepoNode, "name", gitRepoName), @@ -215,12 +204,10 @@ func TestAccGitRepo_RepoInitialization_Clean(t *testing.T) { }) } -// Verifies that a newly created repo with init_type of "Uninitialized" does NOT -// have a master branch established -func TestAccGitRepo_RepoInitialization_Uninitialized(t *testing.T) { +func TestAccGitRepository_uninitialized(t *testing.T) { projectName := testutils.GenerateResourceName() gitRepoName := testutils.GenerateResourceName() - tfRepoNode := "azuredevops_git_repository.repository" + tfRepoNode := "azuredevops_git_repository.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testutils.PreCheck(t, nil) }, @@ -228,7 +215,7 @@ func TestAccGitRepo_RepoInitialization_Uninitialized(t *testing.T) { CheckDestroy: checkGitRepoDestroyed, Steps: []resource.TestStep{ { - Config: testutils.HclGitRepoResource(projectName, gitRepoName, "Uninitialized"), + Config: hclGitRepositoryBasic(projectName, gitRepoName, "Uninitialized"), Check: resource.ComposeTestCheckFunc( checkGitRepoExists(gitRepoName), resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), @@ -240,13 +227,12 @@ func TestAccGitRepo_RepoInitialization_Uninitialized(t *testing.T) { }) } -// Verifies that a newly forked repo does NOT return an empty branch_name -func TestAccGitRepo_RepoFork_BranchNotEmpty(t *testing.T) { +func TestAccGitRepository_forkBranchNotEmpty(t *testing.T) { projectName := testutils.GenerateResourceName() gitRepoName := testutils.GenerateResourceName() gitForkedRepoName := testutils.GenerateResourceName() - tfRepoNode := "azuredevops_git_repository.repository" - tfForkedRepoNode := "azuredevops_git_repository.gitforkedrepo" + tfRepoNode := "azuredevops_git_repository.test" + tfForkedRepoNode := "azuredevops_git_repository.fork" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testutils.PreCheck(t, nil) }, @@ -254,7 +240,7 @@ func TestAccGitRepo_RepoFork_BranchNotEmpty(t *testing.T) { CheckDestroy: checkGitRepoDestroyed, Steps: []resource.TestStep{ { - Config: testutils.HclForkedGitRepoResource(projectName, gitRepoName, gitForkedRepoName, "Clean", "Uninitialized"), + Config: hclGitRepositoryForkBranchNotEmpty(projectName, gitRepoName, gitForkedRepoName, "Uninitialized"), Check: resource.ComposeTestCheckFunc( checkGitRepoExists(gitRepoName), resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), @@ -268,7 +254,7 @@ func TestAccGitRepo_RepoFork_BranchNotEmpty(t *testing.T) { }) } -func TestAccGitRepo_PrivateImport_BranchNotEmpty(t *testing.T) { +func TestAccGitRssepository_privateImportBranchNotEmpty(t *testing.T) { if os.Getenv("AZDO_GENERIC_GIT_SERVICE_CONNECTION_USERNAME") == "" || os.Getenv("AZDO_GENERIC_GIT_SERVICE_CONNECTION_PASSWORD") == "" { t.Skip("Skipping as AZDO_GENERIC_GIT_SERVICE_CONNECTION_USERNAME or AZDO_GENERIC_GIT_SERVICE_CONNECTION_PASSWORD is not specified") @@ -292,7 +278,7 @@ func TestAccGitRepo_PrivateImport_BranchNotEmpty(t *testing.T) { CheckDestroy: checkGitRepoDestroyed, Steps: []resource.TestStep{ { - Config: testutils.HclProjectGitRepoImportPrivate(projectName, gitRepoName, gitImportRepoName, serviceEndpointName), + Config: hclGitRepositoryImportPrivate(projectName, gitRepoName, gitImportRepoName, serviceEndpointName), Check: resource.ComposeTestCheckFunc( checkGitRepoExists(gitRepoName), resource.TestCheckResourceAttrSet(tfRepoNode, "project_id"), @@ -312,9 +298,9 @@ func checkGitRepoExists(expectedName string) resource.TestCheckFunc { return func(s *terraform.State) error { clients := testutils.GetProvider().Meta().(*client.AggregatedClient) - gitRepo, ok := s.RootModule().Resources["azuredevops_git_repository.repository"] + gitRepo, ok := s.RootModule().Resources["azuredevops_git_repository.test"] if !ok { - return fmt.Errorf("Did not find a repo definition in the TF state") + return fmt.Errorf(" Did not find a repo definition in the TF state") } repoID := gitRepo.Primary.ID @@ -326,7 +312,7 @@ func checkGitRepoExists(expectedName string) resource.TestCheckFunc { } if *repo.Name != expectedName { - return fmt.Errorf("AzDO Git Repository has Name=%s, but expected Name=%s", *repo.Name, expectedName) + return fmt.Errorf(" AzDO Git Repository has Name=%s, but expected Name=%s", *repo.Name, expectedName) } return nil @@ -387,16 +373,105 @@ func readGitRepo(clients *client.AggregatedClient, repoID string, projectID stri return repo, nil } -func gitRepoDisabledResourceConfig(projectName string, gitRepoName string, disabled string) string { - projectResource := testutils.HclProjectResource(projectName) - azureGitRepoResource := fmt.Sprintf(` - resource "azuredevops_git_repository" "repository" { - project_id = azuredevops_project.project.id - name = "%s" - disabled = %s - initialization { - init_type = "Clean" - } - }`, gitRepoName, disabled) - return fmt.Sprintf("%s\n%s", projectResource, azureGitRepoResource) +func hclGitRepositoryBasic(projectName, repoName, initType string) string { + return fmt.Sprintf(` +resource "azuredevops_project" "test" { + name = "%s" +} + +resource "azuredevops_git_repository" "test" { + project_id = azuredevops_project.test.id + name = "%s" + initialization { + init_type = "%s" + } +} +`, projectName, repoName, initType) +} + +func hclGitRepositoryDisable(projectName, repoName string, disabled bool) string { + return fmt.Sprintf(` +resource "azuredevops_project" "test" { + name = "%s" +} + +resource "azuredevops_git_repository" "test" { + project_id = azuredevops_project.test.id + name = "%s" + disabled = %t + initialization { + init_type = "Clean" + } +} +`, projectName, repoName, disabled) +} + +func hclGitRepositoryIncorrectInitialization(projectName, repoName string) string { + return fmt.Sprintf(` +resource "azuredevops_project" "test" { + name = "%s" +} + +resource "azuredevops_git_repository" "test" { + project_id = azuredevops_project.test.id + name = "%s" +} +`, projectName, repoName) +} + +func hclGitRepositoryImport(projectName, repoName string) string { + return fmt.Sprintf(` +resource "azuredevops_project" "test" { + name = "%s" +} + +resource "azuredevops_git_repository" "test" { + project_id = azuredevops_project.test.id + name = "%s" + initialization { + init_type = "Import" + source_type = "Git" + source_url = "https://github.com/microsoft/terraform-provider-azuredevops.git" + } +} +`, projectName, repoName) +} + +func hclGitRepositoryForkBranchNotEmpty(projectName, repoName, forkRepoName, forkInitType string) string { + repoInit := hclGitRepositoryBasic(projectName, repoName, "Clean") + return fmt.Sprintf(` +%s + +resource "azuredevops_git_repository" "fork" { + project_id = azuredevops_project.test.id + parent_repository_id = azuredevops_git_repository.test.id + name = "%s" + initialization { + init_type = "%s" + } +}`, repoInit, forkRepoName, forkInitType) +} + +func hclGitRepositoryImportPrivate(projectName, repoName, importRepoName, serviceEndpointName string) string { + repoInit := hclGitRepositoryBasic(projectName, repoName, "Clean") + return fmt.Sprintf(` +%s + +resource "azuredevops_serviceendpoint_generic_git" "test" { + project_id = azuredevops_project.test.id + service_endpoint_name = "%s" + repository_url = azuredevops_git_repository.test.remote_url +} + +resource "azuredevops_git_repository" "import" { + project_id = azuredevops_project.test.id + name = "%s" + initialization { + init_type = "Import" + source_type = "Git" + source_url = azuredevops_git_repository.test.remote_url + service_connection_id = azuredevops_serviceendpoint_generic_git.test.id + } +} +`, repoInit, serviceEndpointName, importRepoName) } diff --git a/azuredevops/internal/acceptancetests/testutils/hcl.go b/azuredevops/internal/acceptancetests/testutils/hcl.go index 9ed5b4664..9c0ea8334 100644 --- a/azuredevops/internal/acceptancetests/testutils/hcl.go +++ b/azuredevops/internal/acceptancetests/testutils/hcl.go @@ -93,7 +93,6 @@ resource "azuredevops_project" "project" { }`, projectName, projectName, featureStateTestplans, featureStateArtifacts) } -// HclProjectGitRepositoryImport HCL describing a AzDO git repositories func HclProjectGitRepositoryImport(gitRepoName string, projectName string) string { azureGitRepoResource := fmt.Sprintf(` resource "azuredevops_git_repository" "repository" { @@ -109,29 +108,6 @@ func HclProjectGitRepositoryImport(gitRepoName string, projectName string) strin return fmt.Sprintf("%s\n%s", projectResource, azureGitRepoResource) } -func HclProjectGitRepoImportPrivate(projectName, gitRepoName, gitImportRepoName, serviceEndpointName string) string { - gitRepoResource := HclGitRepoResource(projectName, gitRepoName, "Clean") - serviceEndpointResource := fmt.Sprintf(` - resource "azuredevops_serviceendpoint_generic_git" "serviceendpoint" { - project_id = azuredevops_project.project.id - service_endpoint_name = "%s" - repository_url = azuredevops_git_repository.repository.remote_url - } - `, serviceEndpointName) - importGitRepoResource := fmt.Sprintf(` - resource "azuredevops_git_repository" "import" { - project_id = azuredevops_project.project.id - name = "%s" - initialization { - init_type = "Import" - source_type = "Git" - source_url = azuredevops_git_repository.repository.remote_url - service_connection_id = azuredevops_serviceendpoint_generic_git.serviceendpoint.id - } - }`, gitImportRepoName) - return fmt.Sprintf("%s\n%s\n%s", gitRepoResource, serviceEndpointResource, importGitRepoResource) -} - // HclSecurityroleDefinitionsDataSource HCL describing a data source for securityrole definitions func HclSecurityroleDefinitionsDataSource() string { return ` @@ -141,35 +117,6 @@ data "azuredevops_securityrole_definitions" "definitions-list" { ` } -// HclUserEntitlementResource HCL describing an AzDO UserEntitlement -func HclUserEntitlementResource(principalName string) string { - return fmt.Sprintf(` -resource "azuredevops_user_entitlement" "user" { - principal_name = "%s" - account_license_type = "express" -}`, principalName) -} - -// HclGroupEntitlementResource HCL describing an AzDO GroupEntitlement -func HclGroupEntitlementResource(displayName string) string { - return fmt.Sprintf(` -resource "azuredevops_group_entitlement" "group" { - display_name = "%s" - account_license_type = "express" -}`, displayName) -} - -// HclGroupEntitlementResource HCL describing an AzDO GroupEntitlement linked -// with Azure AD -func HclGroupEntitlementResourceAAD(originId string) string { - return fmt.Sprintf(` -resource "azuredevops_group_entitlement" "group_aad" { - origin_id = "%s" - origin = "aad" - account_license_type = "express" -}`, originId) -} - // HclServiceEndpointGitHubResource HCL describing an AzDO service endpoint func HclServiceEndpointGitHubResource(projectName string, serviceEndpointName string) string { serviceEndpointResource := fmt.Sprintf(` @@ -832,22 +779,6 @@ output "user_descriptor" { `, projectName, groupName, userPrincipalName) } -// HclGroupResource HCL describing an AzDO group, if the projectName is empty, only a azuredevops_group instance is returned -func HclGroupResource(groupResourceName, projectName, groupName string) string { - return fmt.Sprintf(` -%s - -resource "azuredevops_group" "%s" { - scope = azuredevops_project.project.id - display_name = "%s" -} - -output "group_id_%s" { - value = azuredevops_group.%s.id -} -`, HclProjectResource(projectName), groupResourceName, groupName, groupResourceName, groupResourceName) -} - // HclResourceAuthorization HCL describing a resource authorization func HclResourceAuthorization(resourceID string, authorized bool) string { return fmt.Sprintf(` @@ -934,34 +865,6 @@ resource "azuredevops_git_permissions" "git-permissions" { `, projectResource) } -// HclGitPermissionsForRepository creates HCl for testing to set permissions for a the all Git repositories of AzDO project -func HclGitPermissionsForRepository(projectName string, gitRepoName string) string { - projectResource := HclProjectResource(projectName) - gitRepository := getGitRepoResource(gitRepoName, "clean") - - return fmt.Sprintf(` -%s - -%s - -data "azuredevops_group" "project-readers" { - project_id = azuredevops_project.project.project_id - name = "Readers" -} - -resource "azuredevops_git_permissions" "git-permissions" { - project_id = azuredevops_project.project.project_id - repository_id = azuredevops_git_repository.gitrepo.id - principal = data.azuredevops_group.project-readers.id - permissions = { - CreateRepository = "Deny" - DeleteRepository = "Deny" - RenameRepository = "NotSet" - } -} -`, projectResource, gitRepository) -} - func HclTeamConfiguration(projectName string, teamName string, teamDescription string, teamAdministrators *[]string, teamMembers *[]string) string { var teamResource string projectResource := HclProjectResource(projectName) diff --git a/azuredevops/internal/service/git/resource_git_repository.go b/azuredevops/internal/service/git/resource_git_repository.go index 02197966a..6b7a265a4 100644 --- a/azuredevops/internal/service/git/resource_git_repository.go +++ b/azuredevops/internal/service/git/resource_git_repository.go @@ -2,7 +2,6 @@ package git import ( "fmt" - "log" "strings" "time" @@ -69,7 +68,6 @@ func ResourceGitRepository() *schema.Resource { }, "name": { Type: schema.TypeString, - ForceNew: false, Required: true, ValidateFunc: validation.NoZeroValues, DiffSuppressFunc: suppress.CaseDifference, @@ -131,6 +129,11 @@ func ResourceGitRepository() *schema.Resource { Computed: true, ValidateFunc: validation.NoZeroValues, }, + "disabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "is_fork": { Type: schema.TypeBool, Computed: true, @@ -155,11 +158,6 @@ func ResourceGitRepository() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "disabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, }, } } @@ -195,8 +193,6 @@ func resourceGitRepositoryCreate(d *schema.ResourceData, m interface{}) error { return fmt.Errorf(" Creating repository in Azure DevOps: %+v", err) } - // set the id immediately after successfully creating the repository, which will allow terraform to track the - // repository in state and will taint the resource if further errors are encountered during initialization d.SetId(createdRepo.Id.String()) if initialization != nil { @@ -249,7 +245,7 @@ func resourceGitRepositoryCreate(d *schema.ResourceData, m interface{}) error { if v := d.Get("disabled").(bool); v { _, err = updateIsDisabledGitRepository(clients, createdRepo.Id.String(), projectID.String(), true) if err != nil { - return fmt.Errorf("Error disabling created repository in Azure DevOps: %+v", err) + return fmt.Errorf(" disabling created repository in Azure DevOps: %+v", err) } } @@ -285,27 +281,35 @@ func resourceGitRepositoryRead(d *schema.ResourceData, m interface{}) error { func resourceGitRepositoryUpdate(d *schema.ResourceData, m interface{}) error { clients := m.(*client.AggregatedClient) + repo, _, projectID, err := expandGitRepository(d) - disabled := d.Get("disabled").(bool) if err != nil { - return fmt.Errorf("Error converting terraform data model to AzDO project reference: %+v", err) + return fmt.Errorf(" converting terraform data model to AzDO project reference: %+v", err) + } + + parsedID, err := uuid.Parse(d.Id()) + if err != nil { + return err } + repo.Id = &parsedID + + disabled := d.Get("disabled").(bool) - // you cannot update a disabled repo. We must temporarily enable to update + // you cannot update a disabled repo repoActual, err := gitRepositoryRead(clients, repo.Id.String(), *repo.Name, projectID.String()) if err != nil { if utils.ResponseWasNotFound(err) { d.SetId("") return nil } - return fmt.Errorf("Error looking up repository with ID %s and Name %s. Error: %v", *repo.Id, *repo.Name, err) + return fmt.Errorf(" looking up repository with ID %s and Name %s. Error: %v", *repo.Id, *repo.Name, err) } - // potentially enabling repo to match the config + // Enable before update to match the config, disabled repository cannot be updated. Disabled -> Enabled if *repoActual.IsDisabled && !disabled { repoActual, err = updateIsDisabledGitRepository(clients, repo.Id.String(), projectID.String(), disabled) if err != nil { - return fmt.Errorf("Error enabling repository in Azure DevOps: %+v", err) + return fmt.Errorf(" enabling repository in Azure DevOps: %+v", err) } } @@ -315,14 +319,14 @@ func resourceGitRepositoryUpdate(d *schema.ResourceData, m interface{}) error { _, err = updateGitRepository(clients, repo, projectID) if err != nil { - return fmt.Errorf("Error updating repository in Azure DevOps: %+v", err) + return fmt.Errorf(" updating repository in Azure DevOps: %+v", err) } - // potentially disabling repo to match the config + // Disable after updating to match the config, disabled repository cannot be updated Enabled -> Disabled if !*repoActual.IsDisabled && disabled { _, err = updateIsDisabledGitRepository(clients, repo.Id.String(), projectID.String(), disabled) if err != nil { - return fmt.Errorf("Error disabling repository in Azure DevOps: %+v", err) + return fmt.Errorf(" disabling repository in Azure DevOps: %+v", err) } } @@ -337,7 +341,7 @@ func resourceGitRepositoryDelete(d *schema.ResourceData, m interface{}) error { uuid, err := uuid.Parse(repoID) if err != nil { - return fmt.Errorf("Invalid repositoryId UUID: %s", repoID) + return fmt.Errorf(" invalid repositoryId UUID: %s", repoID) } // you cannot delete a disabled repo @@ -347,19 +351,20 @@ func resourceGitRepositoryDelete(d *schema.ResourceData, m interface{}) error { d.SetId("") return nil } - return fmt.Errorf("Error looking up repository with ID %s and Name %s. Error: %v", repoID, repoName, err) + return fmt.Errorf(" looking up repository with ID %s and Name %s. Error: %v", repoID, repoName, err) } if *repoActual.IsDisabled { - return fmt.Errorf("A disabled repository cannot be deleted, please enable the repository before attempting to delete : %s", repoID) + return fmt.Errorf(" A disabled repository cannot be deleted, please enable the repository before attempting to delete : %s", repoID) } - err = deleteGitRepository(clients, uuid) + err = clients.GitReposClient.DeleteRepository(clients.Ctx, git.DeleteRepositoryArgs{ + RepositoryId: &uuid, + }) if err != nil { return err } - d.SetId("") return nil } @@ -460,25 +465,15 @@ func initializeGitRepository(clients *client.AggregatedClient, repo *git.GitRepo } func updateGitRepository(clients *client.AggregatedClient, repository *git.GitRepository, project fmt.Stringer) (*git.GitRepository, error) { - if nil == project { - return nil, fmt.Errorf("updateGitRepository: ID of project cannot be nil") - } - projectID := project.String() return clients.GitReposClient.UpdateRepository( clients.Ctx, git.UpdateRepositoryArgs{ NewRepositoryInfo: repository, RepositoryId: repository.Id, - Project: &projectID, + Project: converter.String(project.String()), }) } -func deleteGitRepository(clients *client.AggregatedClient, uuid uuid.UUID) error { - return clients.GitReposClient.DeleteRepository(clients.Ctx, git.DeleteRepositoryArgs{ - RepositoryId: &uuid, - }) -} - // Lookup an Azure Git Repository using the ID, or name if the ID is not set. func gitRepositoryRead(clients *client.AggregatedClient, repoID string, repoName string, projectID string) (*git.GitRepository, error) { identifier := repoID @@ -521,7 +516,7 @@ func flattenGitRepository(d *schema.ResourceData, repository *git.GitRepository) d.SetId(repository.Id.String()) d.Set("name", repository.Name) if repository.Project == nil || repository.Project.Id == nil { - return fmt.Errorf("Unable to flatten Git repository without a valid projectID") + return fmt.Errorf(" Unable to flatten Git repository without a valid projectID") } d.Set("project_id", repository.Project.Id.String()) d.Set("default_branch", repository.DefaultBranch) @@ -532,32 +527,18 @@ func flattenGitRepository(d *schema.ResourceData, repository *git.GitRepository) d.Set("url", repository.Url) d.Set("web_url", repository.WebUrl) d.Set("disabled", repository.IsDisabled) - return nil } // Convert internal Terraform data structure to an AzDO data structure. Note: only the params that are // not generated by the service are expanded here func expandGitRepository(d *schema.ResourceData) (*git.GitRepository, *repoInitializationMeta, *uuid.UUID, error) { - // an "error" is OK here as it is expected in the case that the ID is not set in the resource data - var repoID *uuid.UUID - id := d.Id() - if strings.EqualFold(id, "") { - log.Print("[DEBUG] expandGitRepository: ID is empty (not set)") - } else { - parsedID, err := uuid.Parse(id) - if err == nil { - repoID = &parsedID - } - } - projectID, err := uuid.Parse(d.Get("project_id").(string)) if err != nil { return nil, nil, nil, err } repo := &git.GitRepository{ - Id: repoID, Name: converter.String(d.Get("name").(string)), DefaultBranch: converter.String(d.Get("default_branch").(string)), } @@ -581,7 +562,7 @@ func expandGitRepository(d *schema.ResourceData) (*git.GitRepository, *repoIniti initialization.serviceConnectionID = "" } } else if len(initData) > 1 { - return nil, nil, nil, fmt.Errorf("Multiple initialization blocks") + return nil, nil, nil, fmt.Errorf(" multiple initialization blocks") } return repo, initialization, &projectID, nil } @@ -590,7 +571,7 @@ func expandGitRepository(d *schema.ResourceData) (*git.GitRepository, *repoIniti func updateIsDisabledGitRepository(clients *client.AggregatedClient, repoID string, projectID string, isDisabled bool) (*git.GitRepository, error) { uuid, err := uuid.Parse(repoID) if err != nil { - return nil, fmt.Errorf("Invalid repositoryId UUID: %s", repoID) + return nil, fmt.Errorf(" invalid repositoryId UUID: %s", repoID) } repo, err := clients.GitReposClient.UpdateRepository( clients.Ctx, diff --git a/azuredevops/internal/service/git/resource_git_repository_test.go b/azuredevops/internal/service/git/resource_git_repository_test.go index ef5ab66be..a905c7f39 100644 --- a/azuredevops/internal/service/git/resource_git_repository_test.go +++ b/azuredevops/internal/service/git/resource_git_repository_test.go @@ -110,7 +110,7 @@ func TestGitRepo_FlattenExpand_RoundTrip(t *testing.T) { project := core.TeamProjectReference{Id: &projectID} repoID := uuid.New() - repoName := "name" + repoName := "repoName" gitRepo := git.GitRepository{Id: &repoID, Name: &repoName, Project: &project} resourceData := schema.TestResourceDataRaw(t, ResourceGitRepository().Schema, nil) @@ -122,8 +122,6 @@ func TestGitRepo_FlattenExpand_RoundTrip(t *testing.T) { require.Nil(t, err) require.NotNil(t, expandedGitRepo) - require.NotNil(t, expandedGitRepo.Id) - require.Equal(t, *expandedGitRepo.Id, repoID) require.NotNil(t, expandedProjectID) require.Equal(t, *expandedProjectID, projectID) require.NotNil(t, repoInitialization) @@ -252,7 +250,7 @@ func TestGitRepo_Delete_ChecksForValidUUID(t *testing.T) { err := resourceGitRepositoryDelete(resourceData, &client.AggregatedClient{}) require.NotNil(t, err) - require.Contains(t, err.Error(), "Invalid repositoryId UUID") + require.Contains(t, err.Error(), "invalid repositoryId UUID") } func TestGitRepo_Delete_DoesNotSwallowErrorFromFailedDeleteCall(t *testing.T) {