diff --git a/CHANGELOG.md b/CHANGELOG.md index 234cb6b..51b63b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,14 @@ # Utils Changelog +## v0.0.9 + +* Added support for creating pipelines in Azure DevOps via API. + ## v0.0.8 * Updated `GetFileContent` in `azuredevops` to allow specifying of the version type. -## v.0.0.7 +## v0.0.7 * Allowed configuring of filename in `GetFileContent` in `azuredevops`. diff --git a/azuredevops/azuredevops.go b/azuredevops/azuredevops.go index f2a6401..2462830 100644 --- a/azuredevops/azuredevops.go +++ b/azuredevops/azuredevops.go @@ -9,11 +9,6 @@ import ( "github.com/microsoft/azure-devops-go-api/azuredevops" ) -type AzureDevOps struct { - connection *azuredevops.Connection - ctx context.Context -} - // NewAzureDevOps creates a new AzureDevOps func NewAzureDevOps(organisationName string, personalAccessToken string) *AzureDevOps { ctx := context.Background() diff --git a/azuredevops/build.go b/azuredevops/build.go index e51dd4c..f68c1dd 100644 --- a/azuredevops/build.go +++ b/azuredevops/build.go @@ -15,15 +15,67 @@ import ( "github.com/microsoft/azure-devops-go-api/azuredevops/build" ) +// CreateBuildDefinition creates a new BuildDefinition +func (a *AzureDevOps) CreateBuildDefinition(projectName string, repositoryId string, folderPath string, definitionName string, yamlFilename string) (*build.BuildDefinition, error) { + client, err := build.NewClient(a.ctx, a.connection) + if err != nil { + return nil, err + } + + agentPoolQueueName := "Azure Pipelines" + yamlProcessType := 2 + buildRepositoryDefaultBranch := "refs/heads/main" + buildRepositoryType := "tfsgit" + triggerBatchChanges := false + triggerBranchFilters := []string{} + triggerMaxConcurrentBuildsPerBranch := 1 + triggerPathFilters := []string{} + triggerSettingsSourceType := 2 + var triggers []interface{} // []build.ContinuousIntegrationTrigger # TODO: Why doesn't this work? + triggers = append(triggers, build.ContinuousIntegrationTrigger{ + BatchChanges: &triggerBatchChanges, + BranchFilters: &triggerBranchFilters, + MaxConcurrentBuildsPerBranch: &triggerMaxConcurrentBuildsPerBranch, + PathFilters: &triggerPathFilters, + SettingsSourceType: &triggerSettingsSourceType, + TriggerType: &build.DefinitionTriggerTypeValues.ContinuousIntegration, + }) + createDefinitionArgs := build.CreateDefinitionArgs{ + Definition: &build.BuildDefinition{ + Name: &definitionName, + Path: &folderPath, + Process: &build.YamlProcess{ + Type: &yamlProcessType, + YamlFilename: &yamlFilename, + }, + Queue: &build.AgentPoolQueue{ + Name: &agentPoolQueueName, + }, + QueueStatus: &build.DefinitionQueueStatusValues.Enabled, + Repository: &build.BuildRepository{ + Id: &repositoryId, + DefaultBranch: &buildRepositoryDefaultBranch, + Properties: &map[string]string{ + "reportBuildStatus": "true", + }, + Type: &buildRepositoryType, + }, + Triggers: &triggers, + }, + Project: &projectName, + } + return client.CreateDefinition(a.ctx, createDefinitionArgs) +} + // GetBuild gets a Build -func (a *AzureDevOps) GetBuild(projectName string, buildId *int) (*build.Build, error) { +func (a *AzureDevOps) GetBuild(projectName string, buildId int) (*build.Build, error) { client, err := build.NewClient(a.ctx, a.connection) if err != nil { return nil, err } getBuildArgs := build.GetBuildArgs{ - BuildId: buildId, + BuildId: &buildId, Project: &projectName, } return client.GetBuild(a.ctx, getBuildArgs) @@ -46,7 +98,11 @@ func (a *AzureDevOps) GetBuildDefinitionByName(projectName string, name string) } if len(definitions.Value) == 0 { - return nil, fmt.Errorf("build definition with name '%s' not found in project '%s'", name, projectName) + // return nil, fmt.Errorf("build definition with name '%s' not found in project '%s'", name, projectName) + return nil, &BuildNotFoundError{ + name: name, + projectName: projectName, + } } if len(definitions.Value) > 1 { return nil, fmt.Errorf("multiple build definitions with name '%s' found in project '%s'", name, projectName) @@ -66,7 +122,7 @@ type CustomDefinition struct { } // QueueBuild queues and returns a new Build -func (a *AzureDevOps) QueueBuild(projectName string, definitionId *int, sourceBranch string, templateParameters map[string]string, tags []string) (*build.Build, error) { +func (a *AzureDevOps) QueueBuild(projectName string, definitionId int, sourceBranch string, templateParameters map[string]string, tags []string) (*build.Build, error) { buildClient, err := build.NewClient(a.ctx, a.connection) if err != nil { return nil, err @@ -92,7 +148,7 @@ func (a *AzureDevOps) QueueBuild(projectName string, definitionId *int, sourceBr queueBuildArgs := &CustomQueueBuildArgs{ Definition: CustomDefinition{ - ID: definitionId, + ID: &definitionId, }, SourceBranch: sourceBranch, TemplateParameters: templateParameters, @@ -113,10 +169,9 @@ func (a *AzureDevOps) QueueBuild(projectName string, definitionId *int, sourceBr response, err := client.Send(a.ctx, http.MethodPost, locationId, "5.1", routeValues, queryParams, bytes.NewReader(body), "application/json", "application/json", nil) if err != nil { - if _, ok := err.(azuredevops.WrappedError); ok { - wrappedError := err.(azuredevops.WrappedError) - if *wrappedError.TypeKey == "BuildRequestValidationFailedException" { - validationResults := (*wrappedError.CustomProperties)["ValidationResults"] + if e, ok := err.(azuredevops.WrappedError); ok { + if *e.TypeKey == "BuildRequestValidationFailedException" { + validationResults := (*e.CustomProperties)["ValidationResults"] builder := &strings.Builder{} for i, v := range validationResults.([]interface{}) { message := v.(map[string]interface{})["message"] @@ -149,15 +204,14 @@ func (a *AzureDevOps) QueueBuild(projectName string, definitionId *int, sourceBr } // WaitForBuild waits for a Build to complete -func (a *AzureDevOps) WaitForBuild(projectName string, buildId *int, attempts uint, interval int) error { +func (a *AzureDevOps) WaitForBuild(projectName string, buildId int, attempts uint, interval int) error { err := retry.Do( func() error { var err error build, err := a.GetBuild(projectName, buildId) if err != nil { - if _, ok := err.(azuredevops.WrappedError); ok { - wrappedError := err.(azuredevops.WrappedError) - if *wrappedError.TypeKey == "BuildNotFoundException" { + if e, ok := err.(azuredevops.WrappedError); ok { + if *e.TypeKey == "BuildNotFoundException" { return retry.Unrecoverable(err) } } diff --git a/azuredevops/types.go b/azuredevops/types.go new file mode 100644 index 0000000..15ccb9a --- /dev/null +++ b/azuredevops/types.go @@ -0,0 +1,22 @@ +package azuredevops + +import ( + "context" + "fmt" + + "github.com/microsoft/azure-devops-go-api/azuredevops" +) + +type AzureDevOps struct { + connection *azuredevops.Connection + ctx context.Context +} + +type BuildNotFoundError struct { + name string + projectName string +} + +func (b *BuildNotFoundError) Error() string { + return fmt.Sprintf("build definition with name '%s' not found in project '%s'", b.name, b.projectName) +} diff --git a/azuredevops/wiki_test.go b/azuredevops/wiki_test.go new file mode 100644 index 0000000..5338789 --- /dev/null +++ b/azuredevops/wiki_test.go @@ -0,0 +1 @@ +package azuredevops diff --git a/exec/exec.go b/exec/exec.go index 52a7e40..94d00a7 100644 --- a/exec/exec.go +++ b/exec/exec.go @@ -21,8 +21,8 @@ func RunCommand(name string, cwd string, args ...string) (stdout string, stderr if err != nil { // try to get the exit code - if exitError, ok := err.(*exec.ExitError); ok { - ws := exitError.Sys().(syscall.WaitStatus) + if e, ok := err.(*exec.ExitError); ok { + ws := e.Sys().(syscall.WaitStatus) exitCode = ws.ExitStatus() } else { // This will happen (in OSX) if `name` is not available in $PATH,