diff options
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | azuredevops/data_git_repositories.go | 218 | ||||
| -rw-r--r-- | azuredevops/data_git_repositories_test.go | 299 | ||||
| -rw-r--r-- | azuredevops/data_projects_test.go | 37 | ||||
| -rw-r--r-- | azuredevops/provider.go | 7 | ||||
| -rw-r--r-- | azuredevops/provider_test.go | 3 | ||||
| -rw-r--r-- | azuredevops/resource_git_repository.go (renamed from azuredevops/resource_azure_git_repository.go) | 175 | ||||
| -rw-r--r-- | azuredevops/resource_git_repository_test.go (renamed from azuredevops/resource_azure_git_repository_test.go) | 82 | ||||
| -rw-r--r-- | azuredevops/utils/converter/converter.go | 5 | ||||
| -rw-r--r-- | azuredevops/utils/testhelper/dataGenerator.go | 9 | ||||
| -rw-r--r-- | azuredevops/utils/testhelper/hcl.go | 2 | ||||
| -rw-r--r-- | docs/testing.md | 2 | ||||
| -rw-r--r-- | examples/azdo-based-cicd/main.tf | 14 | ||||
| -rw-r--r-- | website/docs/d/data_git_repositories.html.markdown | 85 | ||||
| -rw-r--r-- | website/docs/r/azure_git_repository.html.markdown | 33 | ||||
| -rw-r--r-- | website/docs/r/build_definition.html.markdown | 6 | ||||
| -rw-r--r-- | website/index.html.markdown | 1 |
17 files changed, 837 insertions, 147 deletions
@@ -53,7 +53,7 @@ resource "azuredevops_project" "project" { description = "All of my awesomee things" } -resource "azuredevops_azure_git_repository" "repository" { +resource "azuredevops_git_repository" "repository" { project_id = azuredevops_project.project.id name = "My Awesome Repo" initialization { @@ -68,8 +68,8 @@ resource "azuredevops_build_definition" "build_definition" { repository { repo_type = "TfsGit" - repo_name = azuredevops_azure_git_repository.repository.name - branch_name = azuredevops_azure_git_repository.repository.default_branch + repo_name = azuredevops_git_repository.repository.name + branch_name = azuredevops_git_repository.repository.default_branch yml_path = "azure-pipelines.yml" } } diff --git a/azuredevops/data_git_repositories.go b/azuredevops/data_git_repositories.go new file mode 100644 index 00000000..add88c21 --- /dev/null +++ b/azuredevops/data_git_repositories.go @@ -0,0 +1,218 @@ +package azuredevops + +import ( + "crypto/sha1" + "encoding/base64" + "fmt" + "log" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/config" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/converter" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/suppress" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/validate" +) + +func dataGitRepositories() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGitRepositoriesRead, + Schema: map[string]*schema.Schema{ + "project_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.UUID, + DiffSuppressFunc: suppress.CaseDifference, + }, + "name": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.NoEmptyStrings, + DiffSuppressFunc: suppress.CaseDifference, + }, + "include_hidden": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "repositories": { + Type: schema.TypeSet, + Computed: true, + Set: getGitRepositoryHash, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "url": { + Type: schema.TypeString, + Computed: true, + }, + "ssh_url": { + Type: schema.TypeString, + Computed: true, + }, + "web_url": { + Type: schema.TypeString, + Computed: true, + }, + "remote_url": { + Type: schema.TypeString, + Computed: true, + }, + "project_id": { + Type: schema.TypeString, + Computed: true, + }, + "size": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func getGitRepositoryHash(v interface{}) int { + return hashcode.String(v.(map[string]interface{})["id"].(string)) +} + +func dataSourceGitRepositoriesRead(d *schema.ResourceData, m interface{}) error { + clients := m.(*config.AggregatedClient) + + projectRepos, err := getGitRepositoriesByNameAndProject(d, clients) + if err != nil { + return fmt.Errorf("Error finding repositories. Error: %v", err) + } + log.Printf("[TRACE] plugin.terraform-provider-azuredevops: Read [%d] Git repositories", len(*projectRepos)) + + results, err := flattenGitRepositories(projectRepos) + if err != nil { + return fmt.Errorf("Error flattening projects. Error: %v", err) + } + + repoNames, err := getAttributeValues(results, "name") + if err != nil { + return fmt.Errorf("Failed to get list of repository names: %v", err) + } + id, err := createGitRepositoryDataSourceID(d, &repoNames) + if err != nil { + return err + } + d.SetId(id) + err = d.Set("repositories", results) + if err != nil { + d.SetId("") + return err + } + return nil +} + +func createGitRepositoryDataSourceID(d *schema.ResourceData, repoNames *[]string) (string, error) { + h := sha1.New() + var names []string + if nil == repoNames { + names = []string{} + } else { + names = *repoNames + } + if len(names) <= 0 { + names = append(names, "empty") + } + projectID := d.Get("project_id").(string) + if projectID != "" { + names = append([]string{projectID}, names...) + } + if _, err := h.Write([]byte(strings.Join(names, "-"))); err != nil { + return "", fmt.Errorf("Unable to compute hash for Git repository names: %v", err) + } + return "gitRepos#" + base64.URLEncoding.EncodeToString(h.Sum(nil)), nil +} + +func flattenGitRepositories(repos *[]git.GitRepository) ([]interface{}, error) { + if repos == nil { + return []interface{}{}, nil + } + + results := make([]interface{}, 0) + + for _, element := range *repos { + output := make(map[string]interface{}) + if element.Name != nil { + output["name"] = *element.Name + } + + if element.Id != nil { + output["id"] = element.Id.String() + } + + if element.Url != nil { + output["url"] = *element.Url + } + + if element.RemoteUrl != nil { + output["remote_url"] = *element.RemoteUrl + } + + if element.SshUrl != nil { + output["ssh_url"] = *element.SshUrl + } + + if element.WebUrl != nil { + output["web_url"] = *element.WebUrl + } + + if element.Project != nil && element.Project.Id != nil { + output["project_id"] = element.Project.Id.String() + } + + if element.Size != nil { + output["size"] = *element.Size + } + + results = append(results, output) + } + + return results, nil +} + +func getGitRepositoriesByNameAndProject(d *schema.ResourceData, clients *config.AggregatedClient) (*[]git.GitRepository, error) { + var repos *[]git.GitRepository + var err error + name, projectID := d.Get("name").(string), d.Get("project_id").(string) + includeHidden := d.Get("include_hidden").(bool) + + if name != "" && projectID != "" { + repo, err := gitRepositoryRead(clients, "", name, projectID) + if err != nil { + return nil, err + } + repos = &[]git.GitRepository{*repo} + } else { + repos, err = clients.GitReposClient.GetRepositories(clients.Ctx, git.GetRepositoriesArgs{ + Project: converter.String(projectID), + IncludeHidden: converter.Bool(includeHidden), + }) + if err != nil { + return nil, err + } + if name != "" { + for _, repo := range *repos { + if strings.EqualFold(*repo.Name, name) { + repos = &[]git.GitRepository{repo} + break + } + } + } + } + return repos, nil +} diff --git a/azuredevops/data_git_repositories_test.go b/azuredevops/data_git_repositories_test.go new file mode 100644 index 00000000..8a8d338f --- /dev/null +++ b/azuredevops/data_git_repositories_test.go @@ -0,0 +1,299 @@ +// +build all core data_git_repositories + +package azuredevops + +import ( + "context" + "errors" + "testing" + + "github.com/golang/mock/gomock" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/microsoft/azure-devops-go-api/azuredevops/core" + "github.com/microsoft/azure-devops-go-api/azuredevops/git" + "github.com/microsoft/terraform-provider-azuredevops/azdosdkmocks" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/config" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/converter" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/testhelper" + "github.com/stretchr/testify/require" +) + +func init() { + /* add code for test setup here */ +} + +var gitRepoListEmpty = []git.GitRepository{} + +var azProjectRef = &core.TeamProjectReference{ + Id: testhelper.CreateUUID(), + Name: converter.String("project-01"), +} + +var gitRepoList = []git.GitRepository{ + { + Links: nil, + DefaultBranch: nil, + Id: testhelper.CreateUUID(), + IsFork: converter.Bool(false), + Name: converter.String("repo-01"), + ParentRepository: nil, + Project: azProjectRef, + RemoteUrl: nil, + Size: nil, + SshUrl: nil, + Url: nil, + ValidRemoteUrls: nil, + WebUrl: nil, + }, + { + Links: nil, + DefaultBranch: converter.String("master"), + Id: testhelper.CreateUUID(), + IsFork: converter.Bool(true), + Name: converter.String("repo-02"), + ParentRepository: &git.GitRepositoryRef{ + Id: testhelper.CreateUUID(), + Name: converter.String("repo-parent-02"), + }, + Project: azProjectRef, + RemoteUrl: nil, + Size: converter.UInt64(0), + SshUrl: nil, + Url: nil, + ValidRemoteUrls: nil, + WebUrl: nil, + }, + { + Links: nil, + DefaultBranch: converter.String("dev"), + Id: testhelper.CreateUUID(), + IsFork: nil, + Name: converter.String("repo-03"), + ParentRepository: nil, + Project: &core.TeamProjectReference{ + Id: testhelper.CreateUUID(), + Name: converter.String("project-02"), + }, + RemoteUrl: nil, + Size: converter.UInt64(1234), + SshUrl: nil, + Url: nil, + ValidRemoteUrls: nil, + WebUrl: nil, + }, +} + +func TestGitRepositoriesDataSource_Read_TestHandleError(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + graphClient := azdosdkmocks.NewMockGraphClient(ctrl) + repoClient := azdosdkmocks.NewMockGitClient(ctrl) + + clients := &config.AggregatedClient{ + GitReposClient: repoClient, + GraphClient: graphClient, + Ctx: context.Background(), + } + + expectedGetRepositoriesArgs := git.GetRepositoriesArgs{ + IncludeHidden: converter.Bool(false), + } + + repoClient. + EXPECT(). + GetRepositories(clients.Ctx, expectedGetRepositoriesArgs). + Return(nil, errors.New("GetRepositories() Failed")). + Times(1) + + resourceData := schema.TestResourceDataRaw(t, dataGitRepositories().Schema, nil) + + err := dataSourceGitRepositoriesRead(resourceData, clients) + require.NotNil(t, err) + require.Zero(t, resourceData.Id()) + repos := resourceData.Get("repositories").(*schema.Set) + require.NotNil(t, repos) + require.Zero(t, repos.Len()) +} + +func TestGitRepositoriesDataSource_Read_TestHandleErrorWithSpecificRepository(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + graphClient := azdosdkmocks.NewMockGraphClient(ctrl) + repoClient := azdosdkmocks.NewMockGitClient(ctrl) + + clients := &config.AggregatedClient{ + GitReposClient: repoClient, + GraphClient: graphClient, + Ctx: context.Background(), + } + + repo := gitRepoList[2] + expectedGetRepositoryArgs := git.GetRepositoryArgs{ + RepositoryId: repo.Name, + Project: converter.String(repo.Project.Id.String()), + } + repoClient. + EXPECT(). + GetRepository(clients.Ctx, expectedGetRepositoryArgs). + Return(nil, errors.New("GetRepository() Failed")) + + resourceData := schema.TestResourceDataRaw(t, dataGitRepositories().Schema, nil) + resourceData.Set("name", *repo.Name) + resourceData.Set("project_id", repo.Project.Id.String()) + + err := dataSourceGitRepositoriesRead(resourceData, clients) + require.NotNil(t, err) + require.Zero(t, resourceData.Id()) + repos := resourceData.Get("repositories").(*schema.Set) + require.NotNil(t, repos) + require.Zero(t, repos.Len()) +} + +func TestGitRepositoriesDataSource_Read_NoRepositories(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + graphClient := azdosdkmocks.NewMockGraphClient(ctrl) + repoClient := azdosdkmocks.NewMockGitClient(ctrl) + + clients := &config.AggregatedClient{ + GitReposClient: repoClient, + GraphClient: graphClient, + Ctx: context.Background(), + } + + expectedGetRepositoriesArgs := git.GetRepositoriesArgs{ + IncludeHidden: converter.Bool(false), + } + + repoClient. + EXPECT(). + GetRepositories(clients.Ctx, expectedGetRepositoriesArgs). + Return(&[]git.GitRepository{}, nil). + Times(1) + + resourceData := schema.TestResourceDataRaw(t, dataGitRepositories().Schema, nil) + + err := dataSourceGitRepositoriesRead(resourceData, clients) + require.Nil(t, err) + repos := resourceData.Get("repositories").(*schema.Set) + require.NotNil(t, repos) + require.Zero(t, repos.Len()) +} + +func TestGitRepositoriesDataSource_Read_AllRepositories(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + graphClient := azdosdkmocks.NewMockGraphClient(ctrl) + repoClient := azdosdkmocks.NewMockGitClient(ctrl) + + clients := &config.AggregatedClient{ + GitReposClient: repoClient, + GraphClient: graphClient, + Ctx: context.Background(), + } + + expectedGetRepositoriesArgs := git.GetRepositoriesArgs{ + IncludeHidden: converter.Bool(false), + } + + repoClient. + EXPECT(). + GetRepositories(clients.Ctx, expectedGetRepositoriesArgs). + Return(&gitRepoList, nil). + Times(1) + + resourceData := schema.TestResourceDataRaw(t, dataGitRepositories().Schema, nil) + + err := dataSourceGitRepositoriesRead(resourceData, clients) + require.Nil(t, err) + repos := resourceData.Get("repositories").(*schema.Set) + require.NotNil(t, repos) + require.Equal(t, repos.Len(), 3) +} + +func TestGitRepositoriesDataSource_Read_AllRepositoriesByProject(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + graphClient := azdosdkmocks.NewMockGraphClient(ctrl) + repoClient := azdosdkmocks.NewMockGitClient(ctrl) + + clients := &config.AggregatedClient{ + GitReposClient: repoClient, + GraphClient: graphClient, + Ctx: context.Background(), + } + + expectedGetRepositoriesArgs := git.GetRepositoriesArgs{ + Project: converter.String(azProjectRef.Id.String()), + IncludeHidden: converter.Bool(false), + } + + repoClient. + EXPECT(). + GetRepositories(clients.Ctx, expectedGetRepositoriesArgs). + Return(&[]git.GitRepository{ + gitRepoList[0], + gitRepoList[1], + }, nil). + Times(1) + + resourceData := schema.TestResourceDataRaw(t, dataGitRepositories().Schema, nil) + resourceData.Set("project_id", azProjectRef.Id.String()) + + err := dataSourceGitRepositoriesRead(resourceData, clients) + require.Nil(t, err) + repos := resourceData.Get("repositories").(*schema.Set) + require.NotNil(t, repos) + require.Equal(t, repos.Len(), 2) + repoMap := make(map[string]interface{}) + for _, item := range repos.List() { + repoData := item.(map[string]interface{}) + repoMap[repoData["name"].(string)] = repoData + } + + for i := 0; i < 2; i++ { + require.Contains(t, repoMap, *gitRepoList[i].Name) + repo := repoMap[*gitRepoList[i].Name].(map[string]interface{}) + require.Equal(t, gitRepoList[i].Project.Id.String(), repo["project_id"]) + require.Equal(t, gitRepoList[i].Id.String(), repo["id"]) + } +} + +func TestGitRepositoriesDataSource_Read_SingleRepository(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + graphClient := azdosdkmocks.NewMockGraphClient(ctrl) + repoClient := azdosdkmocks.NewMockGitClient(ctrl) + + clients := &config.AggregatedClient{ + GitReposClient: repoClient, + GraphClient: graphClient, + Ctx: context.Background(), + } + + repo := gitRepoList[1] + expectedGetRepositoryArgs := git.GetRepositoryArgs{ + RepositoryId: repo.Name, + Project: converter.String(repo.Project.Id.String()), + } + repoClient. + EXPECT(). + GetRepository(clients.Ctx, expectedGetRepositoryArgs). + Return(&repo, nil) + + resourceData := schema.TestResourceDataRaw(t, dataGitRepositories().Schema, nil) + resourceData.Set("name", *repo.Name) + resourceData.Set("project_id", repo.Project.Id.String()) + + err := dataSourceGitRepositoriesRead(resourceData, clients) + require.Nil(t, err) + repos := resourceData.Get("repositories").(*schema.Set) + require.NotNil(t, repos) + require.Equal(t, repos.Len(), 1) +} diff --git a/azuredevops/data_projects_test.go b/azuredevops/data_projects_test.go index 73f21068..f0f0c50f 100644 --- a/azuredevops/data_projects_test.go +++ b/azuredevops/data_projects_test.go @@ -16,6 +16,7 @@ import ( "github.com/microsoft/terraform-provider-azuredevops/azdosdkmocks" "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/config" "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/converter" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/testhelper" "github.com/stretchr/testify/require" "github.com/golang/mock/gomock" @@ -25,33 +26,24 @@ func init() { /* add code for test setup here */ } -var idList = []uuid.UUID{ - uuid.New(), - uuid.New(), - uuid.New(), - uuid.New(), - uuid.New(), - uuid.New(), -} - var prjListEmpty = []core.TeamProjectReference{} var prjListStateMixed = []core.TeamProjectReference{ { Name: converter.String("vsteam-0177"), - Id: &idList[0], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.WellFormed, Url: nil, }, { Name: converter.String("vsteam-0178"), - Id: &idList[1], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.Deleted, Url: nil, }, { Name: converter.String("vsteam-0179"), - Id: &idList[2], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.New, Url: nil, }, @@ -60,19 +52,19 @@ var prjListStateMixed = []core.TeamProjectReference{ var prjListStateWellFormed = []core.TeamProjectReference{ { Name: converter.String("vsteam-0177"), - Id: &idList[0], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.WellFormed, Url: nil, }, { Name: converter.String("vsteam-0178"), - Id: &idList[1], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.WellFormed, Url: nil, }, { Name: converter.String("vsteam-0179"), - Id: &idList[2], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.WellFormed, Url: nil, }, @@ -81,40 +73,41 @@ var prjListStateWellFormed = []core.TeamProjectReference{ var prjListStateWellFormed2 = []core.TeamProjectReference{ { Name: converter.String("vsteam-0277"), - Id: &idList[0+len(prjListStateWellFormed)], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.WellFormed, Url: nil, }, { Name: converter.String("vsteam-0278"), - Id: &idList[1+len(prjListStateWellFormed)], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.WellFormed, Url: nil, }, { Name: converter.String("vsteam-0279"), - Id: &idList[2+len(prjListStateWellFormed)], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.WellFormed, Url: nil, }, } +var duplicatePrjID *uuid.UUID = testhelper.CreateUUID() var prjListDoubleID = []core.TeamProjectReference{ { Name: converter.String("vsteam-0177"), - Id: &idList[0], + Id: duplicatePrjID, State: &core.ProjectStateValues.WellFormed, Url: nil, }, { Name: converter.String("vsteam-0178"), - Id: &idList[1], + Id: testhelper.CreateUUID(), State: &core.ProjectStateValues.WellFormed, Url: nil, }, { Name: converter.String("vsteam-0179"), - Id: &idList[0], + Id: duplicatePrjID, State: &core.ProjectStateValues.WellFormed, Url: nil, }, @@ -160,7 +153,7 @@ func TestDataSourceProjects_Read_TestFindProjectByName(t *testing.T) { projectReference := projectSet.List()[0].(map[string]interface{}) require.NotNil(t, projectReference) require.Equal(t, "vsteam-0178", projectReference["name"]) - require.Equal(t, idList[1].String(), projectReference["project_id"]) + require.Equal(t, prjListStateWellFormed[1].Id.String(), projectReference["project_id"]) require.Equal(t, "wellFormed", projectReference["state"]) } diff --git a/azuredevops/provider.go b/azuredevops/provider.go index 60aaa999..d702e036 100644 --- a/azuredevops/provider.go +++ b/azuredevops/provider.go @@ -15,15 +15,16 @@ func Provider() *schema.Provider { "azuredevops_serviceendpoint_github": resourceServiceEndpointGitHub(), "azuredevops_serviceendpoint_dockerhub": resourceServiceEndpointDockerHub(), "azuredevops_serviceendpoint_azurerm": resourceServiceEndpointAzureRM(), - "azuredevops_azure_git_repository": resourceAzureGitRepository(), + "azuredevops_git_repository": resourceGitRepository(), "azuredevops_user_entitlement": resourceUserEntitlement(), "azuredevops_group_membership": resourceGroupMembership(), "azuredevops_agent_pool": resourceAzureAgentPool(), "azuredevops_group": resourceGroup(), }, DataSourcesMap: map[string]*schema.Resource{ - "azuredevops_group": dataGroup(), - "azuredevops_projects": dataProjects(), + "azuredevops_group": dataGroup(), + "azuredevops_projects": dataProjects(), + "azuredevops_git_repositories": dataGitRepositories(), }, Schema: map[string]*schema.Schema{ "org_service_url": { diff --git a/azuredevops/provider_test.go b/azuredevops/provider_test.go index e47d7a51..dcb2b657 100644 --- a/azuredevops/provider_test.go +++ b/azuredevops/provider_test.go @@ -22,7 +22,7 @@ func TestAzureDevOpsProvider_HasChildResources(t *testing.T) { "azuredevops_serviceendpoint_dockerhub", "azuredevops_serviceendpoint_azurerm", "azuredevops_variable_group", - "azuredevops_azure_git_repository", + "azuredevops_git_repository", "azuredevops_user_entitlement", "azuredevops_group_membership", "azuredevops_group", @@ -42,6 +42,7 @@ func TestAzureDevOpsProvider_HasChildDataSources(t *testing.T) { expectedDataSources := []string{ "azuredevops_group", "azuredevops_projects", + "azuredevops_git_repositories", } dataSources := provider.DataSourcesMap diff --git a/azuredevops/resource_azure_git_repository.go b/azuredevops/resource_git_repository.go index c191695d..8453d9b9 100644 --- a/azuredevops/resource_azure_git_repository.go +++ b/azuredevops/resource_git_repository.go @@ -2,6 +2,8 @@ package azuredevops import ( "fmt" + "log" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/helper/validation" @@ -9,28 +11,43 @@ import ( "github.com/microsoft/azure-devops-go-api/azuredevops/git" "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/config" "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/converter" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/suppress" + "github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/validate" ) -func resourceAzureGitRepository() *schema.Resource { +func resourceGitRepository() *schema.Resource { return &schema.Resource{ - Create: resourceAzureGitRepositoryCreate, - Read: resourceAzureGitRepositoryRead, - Update: resourceAzureGitRepositoryUpdate, - Delete: resourceAzureGitRepositoryDelete, + Create: resourceGitRepositoryCreate, + Read: resourceGitRepositoryRead, + Update: resourceGitRepositoryUpdate, + Delete: resourceGitRepositoryDelete, Schema: map[string]*schema.Schema{ "project_id": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, // repositories cannot be moved + ValidateFunc: validation.NoZeroValues, + DiffSuppressFunc: suppress.CaseDifference, }, "name": { - Type: schema.TypeString, - ForceNew: false, - Required: true, + Type: schema.TypeString, + ForceNew: false, + Required: true, + ValidateFunc: validation.NoZeroValues, + DiffSuppressFunc: suppress.CaseDifference, + }, + "parent_repository_id": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.UUID, + DiffSuppressFunc: suppress.CaseDifference, }, "default_branch": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.NoZeroValues, }, "is_fork": { Type: schema.TypeBool, @@ -58,15 +75,14 @@ func resourceAzureGitRepository() *schema.Resource { }, "initialization": { Type: schema.TypeSet, - Required: true, - MinItems: 1, + Optional: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "init_type": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringInSlice([]string{"Uninitialized", "Clean", "Fork", "Import"}, false), + ValidateFunc: validation.StringInSlice([]string{"uninitialized", "clean", "import"}, true), }, "source_type": { Type: schema.TypeString, @@ -92,45 +108,64 @@ type repoInitializationMeta struct { sourceURL string } -func resourceAzureGitRepositoryCreate(d *schema.ResourceData, m interface{}) error { +func resourceGitRepositoryCreate(d *schema.ResourceData, m interface{}) error { clients := m.(*config.AggregatedClient) - repo, initialization, projectID, err := expandAzureGitRepository(d) + repo, initialization, projectID, err := expandGitRepository(d) if err != nil { return fmt.Errorf("Error expanding repository resource data: %+v", err) } - createdRepo, err := createAzureGitRepository(clients, repo.Name, projectID) + var parentRepoRef *git.GitRepositoryRef = nil + if parentRepoID, ok := d.GetOkExists("parent_repository_id"); ok { + parentRepo, err := gitRepositoryRead(clients, parentRepoID.(string), "", "") + if err != nil { + return fmt.Errorf("Failed to locate parent repository [%s]: %+v", parentRepoID, err) + } + parentRepoRef = &git.GitRepositoryRef{ + Id: parentRepo.Id, + Name: parentRepo.Name, + Project: parentRepo.Project, + } + } + + createdRepo, err := createGitRepository(clients, repo.Name, projectID, parentRepoRef) if err != nil { return fmt.Errorf("Error creating repository in Azure DevOps: %+v", err) } - if initialization.initType == "Clean" { - err = initializeAzureGitRepository(clients, createdRepo) + if initialization != nil && initialization.initType == "Clean" { + err = initializeGitRepository(clients, createdRepo) if err != nil { + if err := deleteGitRepository(clients, createdRepo.Id.String()); err != nil { + log.Printf("[WARN] Unable to delete new Git Repository after initialization failed: %+v", err) + } return fmt.Errorf("Error initializing repository in Azure DevOps: %+v", err) } } - flattenAzureGitRepository(d, createdRepo) - - return resourceAzureGitRepositoryRead(d, m) + d.SetId(createdRepo.Id.String()) + return resourceGitRepositoryRead(d, m) } -func createAzureGitRepository(clients *config.AggregatedClient, repoName *string, projectID *uuid.UUID) (*git.GitRepository, error) { +func createGitRepository(clients *config.AggregatedClient, repoName *string, projectID *uuid.UUID, parentRepo *git.GitRepositoryRef) (*git.GitRepository, error) { args := git.CreateRepositoryArgs{ GitRepositoryToCreate: &git.GitRepositoryCreateOptions{ Name: repoName, Project: &core.TeamProjectReference{ Id: projectID, }, + ParentRepository: parentRepo, }, } createdRepository, err := clients.GitReposClient.CreateRepository(clients.Ctx, args) + if err != nil { + return nil, err + } - return createdRepository, err + return createdRepository, nil } -func initializeAzureGitRepository(clients *config.AggregatedClient, repo *git.GitRepository) error { +func initializeGitRepository(clients *config.AggregatedClient, repo *git.GitRepository) error { args := git.CreatePushArgs{ RepositoryId: repo.Name, Project: repo.Project.Name, @@ -166,38 +201,40 @@ func initializeAzureGitRepository(clients *config.AggregatedClient, repo *git.Gi return err } -func resourceAzureGitRepositoryRead(d *schema.ResourceData, m interface{}) error { +func resourceGitRepositoryRead(d *schema.ResourceData, m interface{}) error { repoID := d.Id() repoName := d.Get("name").(string) projectID := d.Get("project_id").(string) clients := m.(*config.AggregatedClient) - repo, err := azureGitRepositoryRead(clients, repoID, repoName, projectID) + repo, err := gitRepositoryRead(clients, repoID, repoName, projectID) if err != nil { return fmt.Errorf("Error looking up repository with ID %s and Name %s. Error: %v", repoID, repoName, err) } - flattenAzureGitRepository(d, repo) + flattenGitRepository(d, repo) return nil } -func resourceAzureGitRepositoryUpdate(d *schema.ResourceData, m interface{}) error { +func resourceGitRepositoryUpdate(d *schema.ResourceData, m interface{}) error { clients := m.(*config.AggregatedClient) - repo, _, projectID, err := expandAzureGitRepository(d) + repo, _, projectID, err := expandGitRepository(d) if err != nil { return fmt.Errorf("Error converting terraform data model to AzDO project reference: %+v", err) } - repo, err = updateAzureGitRepository(clients, repo, projectID) + repo, err = updateGitRepository(clients, repo, projectID) if err != nil { return fmt.Errorf("Error updating repository in Azure DevOps: %+v", err) } - flattenAzureGitRepository(d, repo) - return resourceAzureGitRepositoryRead(d, m) + return resourceGitRepositoryRead(d, m) } -func updateAzureGitRepository(clients *config.AggregatedClient, repository *git.GitRepository, project *uuid.UUID) (*git.GitRepository, error) { +func updateGitRepository(clients *config.AggregatedClient, repository *git.GitRepository, project *uuid.UUID) (*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, @@ -208,13 +245,18 @@ func updateAzureGitRepository(clients *config.AggregatedClient, repository *git. }) } -func resourceAzureGitRepositoryDelete(d *schema.ResourceData, m interface{}) error { +func resourceGitRepositoryDelete(d *schema.ResourceData, m interface{}) error { repoID := d.Id() clients := m.(*config.AggregatedClient) - return deleteAzureGitRepository(clients, repoID) + err := deleteGitRepository(clients, repoID) + if err != nil { + return err + } + d.SetId("") + return nil } -func deleteAzureGitRepository(clients *config.AggregatedClient, repoID string) error { +func deleteGitRepository(clients *config.AggregatedClient, repoID string) error { uuid, err := uuid.Parse(repoID) if err != nil { return fmt.Errorf("Invalid repositoryId UUID: %s", repoID) @@ -226,7 +268,7 @@ func deleteAzureGitRepository(clients *config.AggregatedClient, repoID string) e } // Lookup an Azure Git Repository using the ID, or name if the ID is not set. -func azureGitRepositoryRead(clients *config.AggregatedClient, repoID string, repoName string, projectID string) (*git.GitRepository, error) { +func gitRepositoryRead(clients *config.AggregatedClient, repoID string, repoName string, projectID string) (*git.GitRepository, error) { identifier := repoID if identifier == "" { identifier = repoName @@ -238,9 +280,7 @@ func azureGitRepositoryRead(clients *config.AggregatedClient, repoID string, rep }) } -func flattenAzureGitRepository(d *schema.ResourceData, repository *git.GitRepository) { - d.SetId(repository.Id.String()) - +func flattenGitRepository(d *schema.ResourceData, repository *git.GitRepository) { d.Set("name", converter.ToString(repository.Name, "")) d.Set("project_id", repository.Project.Id.String()) d.Set("default_branch", converter.ToString(repository.DefaultBranch, "")) @@ -254,12 +294,17 @@ func flattenAzureGitRepository(d *schema.ResourceData, repository *git.GitReposi // 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 expandAzureGitRepository(d *schema.ResourceData) (*git.GitRepository, *repoInitializationMeta, *uuid.UUID, error) { +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 - parsedID, err := uuid.Parse(d.Id()) - if err == nil { - repoID = &parsedID + id := d.Id() + if 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)) @@ -268,32 +313,34 @@ func expandAzureGitRepository(d *schema.ResourceData) (*git.GitRepository, *repo } repo := &git.GitRepository{ - Id: repoID, - Name: converter.String(d.Get("name").(string)), + Id: repoID, + Name: converter.String(d.Get("name").(string)), + DefaultBranch: converter.String(d.Get("default_branch").(string)), } + var initialization *repoInitializationMeta = nil initData := d.Get("initialization").(*schema.Set).List() // Note: If configured, this will be of length 1 based on the schema definition above. - if len(initData) != 1 { - return nil, nil, nil, fmt.Errorf("Unexpectedly did not find repository initialization metadata in the resource data") - } + if len(initData) == 1 { + initValues := initData[0].(map[string]interface{}) - initValues := initData[0].(map[string]interface{}) - - initialization := &repoInitializationMeta{ - initType: initValues["init_type"].(string), - sourceType: initValues["source_type"].(string), - sourceURL: initValues["source_url"].(string), - } + initialization = &repoInitializationMeta{ + initType: initValues["init_type"].(string), + sourceType: initValues["source_type"].(string), + sourceURL: initValues["source_url"].(string), + } - if initialization.initType == "Fork" || initialization.initType == "Import" { - return nil, nil, nil, fmt.Errorf("Initialization strategy not implemented: %s", initialization.initType) - } + if initialization.initType == "Import" { + return nil, nil, nil, fmt.Errorf("Initialization strategy not implemented: %s", initialization.initType) + } - if initialization.initType == "Clean" { - initialization.sourceType = "" - initialization.sourceURL = "" + if initialization.initType == "Clean" { + initialization.sourceType = "" + initialization.sourceURL = "" + } + } else if len(initData) > 1 { + return nil, nil, nil, fmt.Errorf("Multiple initialization blocks") } return repo, initialization, &projectID, nil diff --git a/azuredevops/resource_azure_git_repository_test.go b/azuredevops/resource_git_repository_test.go index 8a116f77..4c50af2f 100644 --- a/azuredevops/resource_azure_git_repository_test.go +++ b/azuredevops/resource_git_repository_test.go @@ -50,8 +50,9 @@ func TestAzureGitRepo_Create_DoesNotSwallowErrorFromFailedCreateCall(t *testing. ctrl := gomock.NewController(t) defer ctrl.Finish() - resourceData := schema.TestResourceDataRaw(t, resourceAzureGitRepository().Schema, nil) - flattenAzureGitRepository(resourceData, &testAzureGitRepository) + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) + resourceData.SetId(testAzureGitRepository.Id.String()) + flattenGitRepository(resourceData, &testAzureGitRepository) configureCleanInitialization(resourceData) reposClient := azdosdkmocks.NewMockGitClient(ctrl) @@ -71,7 +72,7 @@ func TestAzureGitRepo_Create_DoesNotSwallowErrorFromFailedCreateCall(t *testing. Return(nil, errors.New("CreateAzureGitRepository() Failed")). Times(1) - err := resourceAzureGitRepositoryCreate(resourceData, clients) + err := resourceGitRepositoryCreate(resourceData, clients) require.Regexp(t, ".*CreateAzureGitRepository\\(\\) Failed$", err.Error()) } @@ -81,8 +82,9 @@ func TestAzureGitRepo_Update_DoesNotSwallowErrorFromFailedCreateCall(t *testing. ctrl := gomock.NewController(t) defer ctrl.Finish() - resourceData := schema.TestResourceDataRaw(t, resourceAzureGitRepository().Schema, nil) - flattenAzureGitRepository(resourceData, &testAzureGitRepository) + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) + resourceData.SetId(testAzureGitRepository.Id.String()) + flattenGitRepository(resourceData, &testAzureGitRepository) configureCleanInitialization(resourceData) reposClient := azdosdkmocks.NewMockGitClient(ctrl) @@ -94,7 +96,7 @@ func TestAzureGitRepo_Update_DoesNotSwallowErrorFromFailedCreateCall(t *testing. Return(nil, errors.New("UpdateAzureGitRepository() Failed")). Times(1) - err := resourceAzureGitRepositoryUpdate(resourceData, clients) + err := resourceGitRepositoryUpdate(resourceData, clients) require.Regexp(t, ".*UpdateAzureGitRepository\\(\\) Failed$", err.Error()) } @@ -116,15 +118,43 @@ func TestAzureGitRepo_FlattenExpand_RoundTrip(t *testing.T) { repoName := "name" gitRepo := git.GitRepository{Id: &repoID, Name: &repoName, Project: &project} - resourceData := schema.TestResourceDataRaw(t, resourceAzureGitRepository().Schema, nil) - flattenAzureGitRepository(resourceData, &gitRepo) + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) + resourceData.SetId(gitRepo.Id.String()) + flattenGitRepository(resourceData, &gitRepo) + + expandedGitRepo, repoInitialization, expandedProjectID, err := expandGitRepository(resourceData) + + 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.Nil(t, repoInitialization) +} + +func TestAzureGitRepo_FlattenExpandInitialization_RoundTrip(t *testing.T) { + projectID := uuid.New() + project := core.TeamProjectReference{Id: &projectID} + + repoID := uuid.New() + repoName := "name" + gitRepo := git.GitRepository{Id: &repoID, Name: &repoName, Project: &project} + + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) + resourceData.SetId(gitRepo.Id.String()) + flattenGitRepository(resourceData, &gitRepo) configureCleanInitialization(resourceData) - expandedGitRepo, repoInitialization, expandedProjectID, err := expandAzureGitRepository(resourceData) + expandedGitRepo, repoInitialization, expandedProjectID, err := expandGitRepository(resourceData) 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) require.Equal(t, repoInitialization.initType, "Clean") require.Equal(t, repoInitialization.sourceType, "") require.Equal(t, repoInitialization.sourceURL, "") @@ -142,7 +172,7 @@ func TestAzureGitRepo_Read_DoesNotSwallowErrorFromFailedReadCall(t *testing.T) { Ctx: context.Background(), } - resourceData := schema.TestResourceDataRaw(t, resourceAzureGitRepository().Schema, nil) + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) resourceData.SetId("an-id") resourceData.Set("project_id", "a-project") @@ -153,7 +183,7 @@ func TestAzureGitRepo_Read_DoesNotSwallowErrorFromFailedReadCall(t *testing.T) { Return(nil, fmt.Errorf("GetRepository() Failed")). Times(1) - err := resourceAzureGitRepositoryRead(resourceData, clients) + err := resourceGitRepositoryRead(resourceData, clients) require.Contains(t, err.Error(), "GetRepository() Failed") } @@ -168,7 +198,7 @@ func TestAzureGitRepo_Read_UsesIdIfSet(t *testing.T) { Ctx: context.Background(), } - resourceData := schema.TestResourceDataRaw(t, resourceAzureGitRepository().Schema, nil) + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) resourceData.SetId("an-id") resourceData.Set("project_id", "a-project") @@ -179,14 +209,14 @@ func TestAzureGitRepo_Read_UsesIdIfSet(t *testing.T) { Return(nil, fmt.Errorf("error")). Times(1) - resourceAzureGitRepositoryRead(resourceData, clients) + resourceGitRepositoryRead(resourceData, clients) } func TestAzureGitRepo_Delete_ChecksForValidUUID(t *testing.T) { - resourceData := schema.TestResourceDataRaw(t, resourceAzureGitRepository().Schema, nil) + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) resourceData.SetId("not-a-uuid-id") - err := resourceAzureGitRepositoryDelete(resourceData, &config.AggregatedClient{}) + err := resourceGitRepositoryDelete(resourceData, &config.AggregatedClient{}) require.NotNil(t, err) require.Contains(t, err.Error(), "Invalid repositoryId UUID") } @@ -201,7 +231,7 @@ func TestAzureGitRepo_Delete_DoesNotSwallowErrorFromFailedDeleteCall(t *testing. Ctx: context.Background(), } - resourceData := schema.TestResourceDataRaw(t, resourceAzureGitRepository().Schema, nil) + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) id := uuid.New() resourceData.SetId(id.String()) @@ -212,7 +242,7 @@ func TestAzureGitRepo_Delete_DoesNotSwallowErrorFromFailedDeleteCall(t *testing. Return(fmt.Errorf("DeleteRepository() Failed")). Times(1) - err := resourceAzureGitRepositoryDelete(resourceData, clients) + err := resourceGitRepositoryDelete(resourceData, clients) require.Contains(t, err.Error(), "DeleteRepository() Failed") } @@ -227,7 +257,7 @@ func TestAzureGitRepo_Read_UsesNameIfIdNotSet(t *testing.T) { Ctx: context.Background(), } - resourceData := schema.TestResourceDataRaw(t, resourceAzureGitRepository().Schema, nil) + resourceData := schema.TestResourceDataRaw(t, resourceGitRepository().Schema, nil) resourceData.Set("name", "a-name") resourceData.Set("project_id", "a-project") @@ -238,7 +268,7 @@ func TestAzureGitRepo_Read_UsesNameIfIdNotSet(t *testing.T) { Return(nil, fmt.Errorf("error")). Times(1) - resourceAzureGitRepositoryRead(resourceData, clients) + resourceGitRepositoryRead(resourceData, clients) } /** @@ -255,7 +285,7 @@ func TestAccAzureGitRepo_CreateAndUpdate(t *testing.T) { projectName := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) gitRepoNameFirst := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) gitRepoNameSecond := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - tfRepoNode := "azuredevops_azure_git_repository.gitrepo" + tfRepoNode := "azuredevops_git_repository.gitrepo" resource.Test(t, resource.TestCase{ PreCheck: func() { testhelper.TestAccPreCheck(t, nil) }, @@ -300,7 +330,7 @@ func testAccCheckAzureGitRepoResourceExists(expectedName string) resource.TestCh return func(s *terraform.State) error { clients := testAccProvider.Meta().(*config.AggregatedClient) - gitRepo, ok := s.RootModule().Resources["azuredevops_azure_git_repository.gitrepo"] + gitRepo, ok := s.RootModule().Resources["azuredevops_git_repository.gitrepo"] if !ok { return fmt.Errorf("Did not find a repo definition in the TF state") } @@ -308,7 +338,7 @@ func testAccCheckAzureGitRepoResourceExists(expectedName string) resource.TestCh repoID := gitRepo.Primary.ID projectID := gitRepo.Primary.Attributes["project_id"] - repo, err := azureGitRepositoryRead(clients, repoID, "", projectID) + repo, err := gitRepositoryRead(clients, repoID, "", projectID) if err != nil { return err } @@ -326,7 +356,7 @@ func testAccAzureGitRepoCheckDestroy(s *terraform.State) error { // verify that every repository referenced in the state does not exist in AzDO for _, resource := range s.RootModule().Resources { - if resource.Type != "azuredevops_azure_git_repository" { + if resource.Type != "azuredevops_git_repository" { continue } @@ -334,7 +364,7 @@ func testAccAzureGitRepoCheckDestroy(s *terraform.State) error { projectID := resource.Primary.Attributes["project_id"] // indicates the git repository still exists - this should fail the test - if _, err := azureGitRepositoryRead(clients, repoID, "", projectID); err == nil { + if _, err := gitRepositoryRead(clients, repoID, "", projectID); err == nil { return fmt.Errorf("repository with ID %s should not exist", repoID) } } @@ -347,7 +377,7 @@ func testAccAzureGitRepoCheckDestroy(s *terraform.State) error { func TestAccAzureGitRepo_RepoInitialization_Clean(t *testing.T) { projectName := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) gitRepoName := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - tfRepoNode := "azuredevops_azure_git_repository.gitrepo" + tfRepoNode := "azuredevops_git_repository.gitrepo" resource.Test(t, resource.TestCase{ PreCheck: func() { testhelper.TestAccPreCheck(t, nil) }, @@ -372,7 +402,7 @@ func TestAccAzureGitRepo_RepoInitialization_Clean(t *testing.T) { func TestAccAzureGitRepo_RepoInitialization_Uninitialized(t *testing.T) { projectName := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) gitRepoName := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - tfRepoNode := "azuredevops_azure_git_repository.gitrepo" + tfRepoNode := "azuredevops_git_repository.gitrepo" resource.Test(t, resource.TestCase{ PreCheck: func() { testhelper.TestAccPreCheck(t, nil) }, diff --git a/azuredevops/utils/converter/converter.go b/azuredevops/utils/converter/converter.go index 3caf428b..2cc4d177 100644 --- a/azuredevops/utils/converter/converter.go +++ b/azuredevops/utils/converter/converter.go @@ -24,6 +24,11 @@ func Int(value int) *int { return &value } +// UInt64 Get a pointer to an uint64 value +func UInt64(value uint64) *uint64 { + return &value +} + // ToString Given a pointer return its value, or a default value of the poitner is nil func ToString(value *string, defaultValue string) string { if value != nil { diff --git a/azuredevops/utils/testhelper/dataGenerator.go b/azuredevops/utils/testhelper/dataGenerator.go new file mode 100644 index 00000000..1ec8f4d7 --- /dev/null +++ b/azuredevops/utils/testhelper/dataGenerator.go @@ -0,0 +1,9 @@ +package testhelper + +import "github.com/google/uuid" + +// CreateUUID creates a new UUID +func CreateUUID() *uuid.UUID { + val := uuid.New() + return &val +} diff --git a/azuredevops/utils/testhelper/hcl.go b/azuredevops/utils/testhelper/hcl.go index f849b666..0c29fe14 100644 --- a/azuredevops/utils/testhelper/hcl.go +++ b/azuredevops/utils/testhelper/hcl.go @@ -8,7 +8,7 @@ import ( // TestAccAzureGitRepoResource HCL describing an AzDO GIT repository resource func TestAccAzureGitRepoResource(projectName string, gitRepoName string, initType string) string { azureGitRepoResource := fmt.Sprintf(` -resource "azuredevops_azure_git_repository" "gitrepo" { +resource "azuredevops_git_repository" "gitrepo" { project_id = azuredevops_project.project.id name = "%s" initialization { diff --git a/docs/testing.md b/docs/testing.md index fb732b76..8c9419b1 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -32,7 +32,7 @@ func TestAccAzureGitRepo_CreateAndUpdate(t *testing.T) { projectName := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) gitRepoNameFirst := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) gitRepoNameSecond := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum) - tfRepoNode := "azuredevops_azure_git_repository.gitrepo" + tfRepoNode := "azuredevops_git_repository.gitrepo" ... } diff --git a/examples/azdo-based-cicd/main.tf b/examples/azdo-based-cicd/main.tf index dd25b388..a5bda0a5 100644 --- a/examples/azdo-based-cicd/main.tf +++ b/examples/azdo-based-cicd/main.tf @@ -42,8 +42,8 @@ resource "azuredevops_build_definition" "build" { repository { repo_type = "TfsGit" - repo_name = azuredevops_azure_git_repository.repository.name - branch_name = azuredevops_azure_git_repository.repository.default_branch + repo_name = azuredevops_git_repository.repository.name + branch_name = azuredevops_git_repository.repository.default_branch yml_path = "azure-pipelines.yml" } @@ -76,7 +76,7 @@ resource "azuredevops_variable_group" "vg" { } // This section configures an Azure DevOps Git Repository with branch policies -resource "azuredevops_azure_git_repository" "repository" { +resource "azuredevops_git_repository" "repository" { project_id = azuredevops_project.project.id name = "Sample Repo" initialization { @@ -100,8 +100,8 @@ resource "azuredevops_serviceendpoint_azurerm" "endpoint1" { # https://github.com/microsoft/terraform-provider-azuredevops/issues/83 # resource "azuredevops_policy_build" "p1" { # scope { -# repository_id = azuredevops_azure_git_repository.repository.id -# repository_ref = azuredevops_azure_git_repository.repository.default_branch +# repository_id = azuredevops_git_repository.repository.id +# repository_ref = azuredevops_git_repository.repository.default_branch # match_type = "Exact" # } # settings { @@ -111,8 +111,8 @@ resource "azuredevops_serviceendpoint_azurerm" "endpoint1" { # } # resource "azuredevops_policy_min_reviewers" "p1" { # scope { -# repository_id = azuredevops_azure_git_repository.repository.id -# repository_ref = azuredevops_azure_git_repository.repository.default_branch +# repository_id = azuredevops_git_repository.repository.id +# repository_ref = azuredevops_git_repository.repository.default_branch # match_type = "Exact" # } # settings { diff --git a/website/docs/d/data_git_repositories.html.markdown b/website/docs/d/data_git_repositories.html.markdown new file mode 100644 index 00000000..20d381e0 --- /dev/null +++ b/website/docs/d/data_git_repositories.html.markdown @@ -0,0 +1,85 @@ +# Data Source: azuredevops_git_repositories + +Use this data source to access information about an existing Projects within Azure DevOps. + +## Example Usage + +```hcl + +# Make sure to set the following environment variables: +# AZDO_PERSONAL_ACCESS_TOKEN +# AZDO_ORG_SERVICE_URL +provider "azuredevops" { + version = ">= 0.0.1" +} + +# Load all projects of an organization, +# that are accessible by the current user +data "azuredevops_projects" "tf-projects" { +} + +# Build a local map, to access projects by name +locals { + project_map = { + for project in data.azuredevops_projects.tf-projects.projects : project["name"] => project + } +} + +# Load all Git repositories of an organization, +# which are accessible for the current user +data "azuredevops_git_repositories" "tf-git-repos-all" { +} + +output "out-tf-git-repos-all" { + value = data.azuredevops_git_repositories.tf-git-repos-all.repositories +} + +# Build a local map, to access Git repositories by name +locals { + repo_map = { + for repo in data.azuredevops_git_repositories.tf-git-repos-all.repositories : repo["name"] => repo + } +} + +# Load all Git repositories of a project, +# which are accessible for the current user +data "azuredevops_git_repositories" "tf-git-repos-project" { + project_id = local.project_map[var.project_name].project_id +} + +# Load a specific Git repository by name +data "azuredevops_git_repositories" "tf-git-repos-project-reponame" { + project_id = local.project_map[var.project_name].project_id + name = var.git_repo_name +} + +``` + +## Argument Reference + +The following arguments are supported: + +- `project_id` - (Optional) ID of project to list Git repositories +- `name` - (Optional) Name of the Git repository to retrieve; requires `project_id` to be specified as well +- `include_hidden` - (Optional, default: false) + +DataSource without specifying any arguments will return all Git repositories of an organization. + +## Attributes Reference + +The following attributes are exported: + +- `repositories` - A list of existing projects in your Azure DevOps Organization with details about every project which includes: + + - `id` - Git repository identifier. + - `name` - Git repository name. + - `url` - Details REST API endpoint for the Git Repository. + - `ssh_url` - SSH Url to clone the Git repository + - `web_url` - Url of the Git repository web view + - `remote_url` - HTTPS Url to clone the Git repository + - `project_id` - Project identifier to which the Git repository belongs. + - `size` - Compressed size (bytes) of the repository. + +## Relevant Links + +- [Azure DevOps Service REST API 5.1 - Git API](https://docs.microsoft.com/en-us/rest/api/azure/devops/git/?view=azure-devops-rest-5.1) diff --git a/website/docs/r/azure_git_repository.html.markdown b/website/docs/r/azure_git_repository.html.markdown index 07bc990d..58f9da76 100644 --- a/website/docs/r/azure_git_repository.html.markdown +++ b/website/docs/r/azure_git_repository.html.markdown @@ -1,8 +1,10 @@ -# azuredevops_azure_git_repository +# azuredevops_git_repository Manages a git repository within Azure DevOps. ## Example Usage +### Create Git repository + ```hcl resource "azuredevops_project" "project" { project_name = "Sample Project" @@ -11,7 +13,7 @@ resource "azuredevops_project" "project" { work_item_template = "Agile" } -resource "azuredevops_azure_git_repository" "repo" { +resource "azuredevops_git_repository" "repo" { project_id = azuredevops_project.project.id name = "Sample Empty Git Repository" initialization { @@ -19,16 +21,14 @@ resource "azuredevops_azure_git_repository" "repo" { } ``` +### Create Fork of another Azure DevOps Git repository ```hcl -resource "azuredevops_azure_git_repository" "repo" { +resource "azuredevops_git_repository" "repo" { project_id = azuredevops_project.project.id name = "Sample Fork an Existing Repository" - initialization { - init_type = "Fork" - source_type = "" - source_url = "" - } + parent_id = azuredevops_git_repository.parent.id +} ``` ## Argument Reference @@ -37,26 +37,27 @@ The following arguments are supported: * `project_id` - (Required) The project ID or project name. * `name` - (Required) The name of the git repository. -* `initialization` - (Required) An `initialization` block as documented below. +* `parent_id` - (Optional) The ID of a Git project from which a fork is to be created. +* `initialization` - (Optional) An `initialization` block as documented below. `initialization` block supports the following: -* `init_type` - (Required) The type of repository to create. Valid values: `Uninitialized`, `Clean`, `Fork`, or `Import`. Defaults to `Uninitialized`. -* `source_type` - (Optional) Type type of the source repository. Used if the init type is `Fork` or `Import`. -* `source_url` - (Optional) The url of the source repository. Used if the init type is `Fork` or `Import`. +* `init_type` - (Required) The type of repository to create. Valid values: `Uninitialized`, `Clean`, or `Import`. Defaults to `Uninitialized`. +* `source_type` - (Optional) Type type of the source repository. Used if the `init_type` is `Import`. +* `source_url` - (Optional) The URL of the source repository. Used if the `init_type` is `Import`. ## Attributes Reference In addition to all arguments above, except `initialization`, the following attributes are exported: -* `id` - The ID of the agent pool. +* `id` - The ID of the Git repository. * `default_branch` - The name of the default branch. * `is_fork` - True if the repository was created as a fork. -* `remote_url` - If `init_type` is `Fork` the url of the remote repository. +* `remote_url` - Git HTTPS URL of the repository * `size` - Size in bytes. -* `ssh_url` - Git SSH Url of the repository. -* `url` - Git Url of the repository. +* `ssh_url` - Git SSH URL of the repository. +* `url` - REST API URL of the repository. * `web_url` - Web link to the repository. ## Relevant Links diff --git a/website/docs/r/build_definition.html.markdown b/website/docs/r/build_definition.html.markdown index f0b0ee6f..613f96ad 100644 --- a/website/docs/r/build_definition.html.markdown +++ b/website/docs/r/build_definition.html.markdown @@ -11,7 +11,7 @@ resource "azuredevops_project" "project" { work_item_template = "Agile" } -resource "azuredevops_azure_git_repository" "repository" { +resource "azuredevops_git_repository" "repository" { project_id = azuredevops_project.project.id name = "Sample Repository" initialization { @@ -26,8 +26,8 @@ resource "azuredevops_build_definition" "build" { repository { repo_type = "TfsGit" - repo_name = azuredevops_azure_git_repository.repository.name - branch_name = azuredevops_azure_git_repository.repository.default_branch + repo_name = azuredevops_git_repository.repository.name + branch_name = azuredevops_git_repository.repository.default_branch yml_path = "azure-pipelines.yml" } diff --git a/website/index.html.markdown b/website/index.html.markdown index 40f9ed53..3c24a118 100644 --- a/website/index.html.markdown +++ b/website/index.html.markdown @@ -10,6 +10,7 @@ The Azure DevOps provider can be used to configure Azure DevOps project in [Micr - [azuredevops_group](docs/d/data_group.html.markdown) - [azuredevops_project](docs/d/data_project.html.markdown) +- [azuredevops_git_repositories](docs/d/data_git_repositories.html.markdown) ## Resources |
