Skip to content

Commit

Permalink
Import build definitions (#228)
Browse files Browse the repository at this point in the history
Implement import for build definitions
  • Loading branch information
Kalarrs Topham authored and Nicholas M. Iodice committed Jan 23, 2020
1 parent 0188940 commit 9d5dfcf
Showing 12 changed files with 96 additions and 61 deletions.
1 change: 0 additions & 1 deletion azuredevops/resource_agentpool.go
Original file line number Diff line number Diff line change
@@ -21,7 +21,6 @@ func resourceAzureAgentPool() *schema.Resource {
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
18 changes: 15 additions & 3 deletions azuredevops/resource_build_definition.go
Original file line number Diff line number Diff line change
@@ -21,7 +21,18 @@ func resourceBuildDefinition() *schema.Resource {
Read: resourceBuildDefinitionRead,
Update: resourceBuildDefinitionUpdate,
Delete: resourceBuildDefinitionDelete,

Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
projectID, buildDefinitionID, err := ParseImportedProjectIDAndID(meta.(*config.AggregatedClient), d.Id())
if err != nil {
return nil, fmt.Errorf("Error parsing the build definition ID from the Terraform resource data: %v", err)
}
d.Set("project_id", projectID)
d.SetId(fmt.Sprintf("%d", buildDefinitionID))

return []*schema.ResourceData{d}, nil
},
},
Schema: map[string]*schema.Schema{
"project_id": {
Type: schema.TypeString,
@@ -40,8 +51,8 @@ func resourceBuildDefinition() *schema.Resource {
"path": {
Type: schema.TypeString,
Optional: true,
Default: "\\",
ValidateFunc: validate.FilePathOrEmpty,
Default: `\`,
ValidateFunc: validate.Path,
},
"variable_groups": {
Type: schema.TypeSet,
@@ -115,6 +126,7 @@ func flattenBuildDefinition(d *schema.ResourceData, buildDefinition *build.Build

d.Set("project_id", projectID)
d.Set("name", *buildDefinition.Name)
d.Set("path", *buildDefinition.Path)
d.Set("repository", flattenRepository(buildDefinition))
d.Set("agent_pool_name", *buildDefinition.Queue.Pool.Name)

27 changes: 20 additions & 7 deletions azuredevops/resource_build_definition_test.go
Original file line number Diff line number Diff line change
@@ -74,17 +74,24 @@ func TestAzureDevOpsBuildDefinition_RepoTypeListIsCorrect(t *testing.T) {
}
}

// validates that and error is thrown if any of the un-supported file path characters are used
// validates that an error is thrown if any of the un-supported path characters are used
func TestAzureDevOpsBuildDefinition_PathInvalidCharacterListIsError(t *testing.T) {
expectedInvalidPathCharacters := []string{"<", ">", "|", ":", "$", "@", "\"", "/", "%", "+", "*", "?"}
pathSchema := resourceBuildDefinition().Schema["path"]

for _, repoType := range expectedInvalidPathCharacters {
_, errors := pathSchema.ValidateFunc(repoType, "")
require.Equal(t, "<>|:$@\"/%+*? are not allowed", errors[0].Error())
for _, invalidCharacter := range expectedInvalidPathCharacters {
_, errors := pathSchema.ValidateFunc(`\`+invalidCharacter, "")
require.Equal(t, "<>|:$@\"/%+*? are not allowed in path", errors[0].Error())
}
}

// validates that an error is thrown if path does not start with slash
func TestAzureDevOpsBuildDefinition_PathInvalidStartingSlashIsError(t *testing.T) {
pathSchema := resourceBuildDefinition().Schema["path"]
_, errors := pathSchema.ValidateFunc("dir\\dir", "")
require.Equal(t, "path must start with backslash", errors[0].Error())
}

// verifies that the flatten/expand round trip yields the same build definition
func TestAzureDevOpsBuildDefinition_ExpandFlatten_Roundtrip(t *testing.T) {
resourceData := schema.TestResourceDataRaw(t, resourceBuildDefinition().Schema, nil)
@@ -205,15 +212,15 @@ func TestAzureDevOpsBuildDefinition_Update_DoesNotSwallowError(t *testing.T) {
// underlying terraform state.
func TestAccAzureDevOpsBuildDefinition_CreateAndUpdate(t *testing.T) {
projectName := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
buildDefinitionPathEmpty := ""
buildDefinitionPathEmpty := `\`
buildDefinitionNameFirst := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
buildDefinitionNameSecond := testhelper.TestAccResourcePrefix + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)

buildDefinitionPathFirst := `\` + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
buildDefinitionPathSecond := `\` + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)

buildDefinitionPathThird := buildDefinitionNameFirst + `\` + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
buildDefinitionPathFourth := buildDefinitionNameSecond + `\` + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
buildDefinitionPathThird := `\` + buildDefinitionNameFirst + `\` + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)
buildDefinitionPathFourth := `\` + buildDefinitionNameSecond + `\` + acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum)

tfBuildDefNode := "azuredevops_build_definition.build"
resource.Test(t, resource.TestCase{
@@ -276,6 +283,12 @@ func TestAccAzureDevOpsBuildDefinition_CreateAndUpdate(t *testing.T) {
resource.TestCheckResourceAttr(tfBuildDefNode, "path", buildDefinitionPathFourth),
testAccCheckBuildDefinitionResourceExists(buildDefinitionNameFirst),
),
}, {
// Resource Acceptance Testing https://www.terraform.io/docs/extend/resources/import.html#resource-acceptance-testing-implementation
ResourceName: tfBuildDefNode,
ImportStateIdFunc: testAccImportStateIDFunc(tfBuildDefNode),
ImportState: true,
ImportStateVerify: true,
},
},
})
1 change: 0 additions & 1 deletion azuredevops/resource_group.go
Original file line number Diff line number Diff line change
@@ -29,7 +29,6 @@ func resourceGroup() *schema.Resource {
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"scope": {
Type: schema.TypeString,
35 changes: 33 additions & 2 deletions azuredevops/resource_project.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package azuredevops

import (
"fmt"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/tfhelper"
"log"
"os"
"strconv"
@@ -32,8 +33,6 @@ func resourceProject() *schema.Resource {
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

//https://godoc.org/github.com/hashicorp/terraform/helper/schema#Schema
Schema: map[string]*schema.Schema{
"project_name": {
Type: schema.TypeString,
@@ -330,3 +329,35 @@ func lookupProcessTemplateName(clients *config.AggregatedClient, templateID stri

return *process.Name, nil
}

// ParseImportedProjectIDAndID : Parse the Id (projectId/int) or (projectName/int)
func ParseImportedProjectIDAndID(clients *config.AggregatedClient, id string) (string, int, error) {
project, resourceID, err := tfhelper.ParseImportedID(id)
if err != nil {
return "", 0, err
}

// Get the project ID
currentProject, err := projectRead(clients, project, project)
if err != nil {
return "", 0, err
}

return currentProject.Id.String(), resourceID, nil
}

// ParseImportedProjectIDAndUUID : Parse the Id (projectId/uuid) or (projectName/uuid)
func ParseImportedProjectIDAndUUID(clients *config.AggregatedClient, id string) (string, string, error) {
project, resourceID, err := tfhelper.ParseImportedUUID(id)
if err != nil {
return "", "", err
}

// Get the project ID
currentProject, err := projectRead(clients, project, project)
if err != nil {
return "", "", err
}

return currentProject.Id.String(), resourceID, nil
}
19 changes: 1 addition & 18 deletions azuredevops/resource_variable_group.go
Original file line number Diff line number Diff line change
@@ -18,10 +18,9 @@ func resourceVariableGroup() *schema.Resource {
Read: resourceVariableGroupRead,
Update: resourceVariableGroupUpdate,
Delete: resourceVariableGroupDelete,

Importer: &schema.ResourceImporter{
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
projectID, variableGroupID, err := ParseImportedProjectIDAndVariableGroupID(meta.(*config.AggregatedClient), d.Id())
projectID, variableGroupID, err := ParseImportedProjectIDAndID(meta.(*config.AggregatedClient), d.Id())
if err != nil {
return nil, fmt.Errorf("Error parsing the variable group ID from the Terraform resource data: %v", err)
}
@@ -349,19 +348,3 @@ func flattenAllowAccess(d *schema.ResourceData, definitionResource *[]build.Defi
}
d.Set("allow_access", allowAccess)
}

// ParseImportedProjectIDAndVariableGroupID : Parse the Id (projectId/variableGroupId) or (projectName/variableGroupId)
func ParseImportedProjectIDAndVariableGroupID(clients *config.AggregatedClient, id string) (string, int, error) {
project, resourceID, err := tfhelper.ParseImportedID(id)
if err != nil {
return "", 0, err
}

// Get the project ID
currentProject, err := projectRead(clients, project, project)
if err != nil {
return "", 0, err
}

return currentProject.Id.String(), resourceID, nil
}
10 changes: 5 additions & 5 deletions azuredevops/utils/tfhelper/tfhelper.go
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ package tfhelper
import (
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/microsoft/terraform-provider-azuredevops/azuredevops/utils/secretmemo"
"log"
@@ -114,16 +114,16 @@ func ParseImportedID(id string) (string, int, error) {
return project, resourceID, nil
}

// ParseImportedUUID parse the imported uuid Id from the terraform import
// ParseImportedUUID parse the imported uuid from the terraform import
func ParseImportedUUID(id string) (string, string, error) {
parts := strings.SplitN(id, "/", 2)
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return "", "", fmt.Errorf("unexpected format of ID (%s), expected projectid/resourceId", id)
}
project := parts[0]
resourceUUID, err := uuid.Parse(parts[1])
_, err := uuid.ParseUUID(parts[1])
if err != nil {
return "", "", fmt.Errorf("error a uuid but got: %+v", err)
return "", "", fmt.Errorf("%s isn't a valid UUID", parts[1])
}
return project, resourceUUID.String(), nil
return project, parts[1], nil
}
31 changes: 12 additions & 19 deletions azuredevops/utils/validate/file_path.go
Original file line number Diff line number Diff line change
@@ -5,37 +5,30 @@ import (
"regexp"
)

// InvalidWindowsFilePathRegExp is a regex helper.
var InvalidWindowsFilePathRegExp = regexp.MustCompile("[<>|:$@\"/%+*?]")
// InvalidWindowsPathRegExp is a regex helper.
var InvalidWindowsPathRegExp = regexp.MustCompile("[<>|:$@\"/%+*?]")

// FilePath validates that the string does not contain characters (equal to [<>|:$@\"/%+*?])
func FilePath(i interface{}, k string) (warnings []string, errors []error) {
// Path validates that the string does not contain characters (equal to [<>|:$@\"/%+*?])
func Path(i interface{}, k string) (warnings []string, errors []error) {
v, ok := i.(string)
if !ok {
errors = append(errors, fmt.Errorf("expected type of %q to be string", k))
return
}

p := InvalidWindowsFilePathRegExp.MatchString(v)
if p {
errors = append(errors, fmt.Errorf("<>|:$@\"/%%+*? are not allowed"))
return
if len(v) < 1 {
errors = append(errors, fmt.Errorf("path can not be empty"))
}

return warnings, errors
}

// FilePathOrEmpty allows empty string otherwise validates that the string does not contain characters (equal to [<>|:$@\"/%+*?])
func FilePathOrEmpty(i interface{}, k string) (warnings []string, errors []error) {
v, ok := i.(string)
if !ok {
errors = append(errors, fmt.Errorf("expected type of %q to be string", k))
return
if v[:1] != `\` {
errors = append(errors, fmt.Errorf("path must start with backslash"))
}

if v == "" {
p := InvalidWindowsPathRegExp.MatchString(v)
if p {
errors = append(errors, fmt.Errorf("<>|:$@\"/%%+*? are not allowed in path"))
return
}

return FilePath(i, k)
return warnings, errors
}
2 changes: 1 addition & 1 deletion website/docs/r/agent_pool.html.markdown
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ In addition to all arguments above, the following attributes are exported:
* [Azure DevOps Service REST API 5.1 - Agent Pools](https://docs.microsoft.com/en-us/rest/api/azure/devops/distributedtask/pools?view=azure-devops-rest-5.1)

## Import
Azure DevOps Agent Pools can be imported using the agent pool id, e.g.
Azure DevOps Agent Pools can be imported using the agent pool Id, e.g.

```
terraform import azuredevops_agent_pool.pool 42
9 changes: 7 additions & 2 deletions website/docs/r/build_definition.html.markdown
Original file line number Diff line number Diff line change
@@ -67,5 +67,10 @@ In addition to all arguments above, the following attributes are exported:
* [Azure DevOps Service REST API 5.1 - Build Definitions](https://docs.microsoft.com/en-us/rest/api/azure/devops/build/definitions?view=azure-devops-rest-5.1)

## Import

Not supported
Azure DevOps Build Definitions can be imported using the project name/definitions Id or by the project Guid/definitions Id, e.g.

```
terraform import azuredevops_build_definition.build "Test Project"/10
or
terraform import azuredevops_build_definition.build 782a8123-1019-xxxx-xxxx-xxxxxxxx/10
```
2 changes: 1 addition & 1 deletion website/docs/r/project.html.markdown
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ In addition to all arguments above, the following attributes are exported:
* [Azure DevOps Service REST API 5.1 - Projects](https://docs.microsoft.com/en-us/rest/api/azure/devops/core/projects?view=azure-devops-rest-5.1)

## Import
Azure DevOps Projects can be imported using the project name or by the project Guid id, e.g.
Azure DevOps Projects can be imported using the project name or by the project Guid, e.g.

```
terraform import azuredevops_project.project "Test Project"
2 changes: 1 addition & 1 deletion website/docs/r/variable_group.html.markdown
Original file line number Diff line number Diff line change
@@ -54,7 +54,7 @@ In addition to all arguments above, the following attributes are exported:
* [Azure DevOps Service REST API 5.1 - Authorized Resources](https://docs.microsoft.com/en-us/rest/api/azure/devops/build/authorizedresources?view=azure-devops-rest-5.1)

## Import
Azure DevOps Variable groups can be imported using the project name/variable group Id or by the project Guid id/variable group Id, e.g.
Azure DevOps Variable groups can be imported using the project name/variable group Id or by the project Guid/variable group Id, e.g.

```
terraform import azuredevops_project.project "Test Project"/10

0 comments on commit 9d5dfcf

Please sign in to comment.