Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ado with oidc #3924

Merged
merged 16 commits into from
Jun 1, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions cli/azd/pkg/azdo/azdo.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"context"
"fmt"

"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7"
)

var (
Expand Down Expand Up @@ -44,19 +44,35 @@ var (
ServiceConnectionName = "azconnection"
)

type Connection struct {
*azuredevops.Connection
Organization Organization
}

type Organization struct {
Name string
}
vhvb1989 marked this conversation as resolved.
Show resolved Hide resolved

// helper method to return an Azure DevOps connection used the AzDo go sdk
func GetConnection(
ctx context.Context, organization string, personalAccessToken string) (*azuredevops.Connection, error) {
ctx context.Context, organization string, personalAccessToken string) (Connection, error) {
if organization == "" {
return nil, fmt.Errorf("organization name is required")
return Connection{}, fmt.Errorf("organization name is required")
}

if personalAccessToken == "" {
return nil, fmt.Errorf("personal access token is required")
return Connection{}, fmt.Errorf("personal access token is required")
}
vhvb1989 marked this conversation as resolved.
Show resolved Hide resolved

organizationUrl := fmt.Sprintf("https://%s/%s", AzDoHostName, organization)
connection := azuredevops.NewPatConnection(organizationUrl, personalAccessToken)

return connection, nil
adoConnection := Connection{
Connection: connection,
Organization: Organization{
Name: organization,
},
}

return adoConnection, nil
}
121 changes: 116 additions & 5 deletions cli/azd/pkg/azdo/build_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ package azdo
import (
"context"
"fmt"
"net/http"
"net/url"

"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/build"
"github.com/microsoft/azure-devops-go-api/azuredevops/policy"
"github.com/google/uuid"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/build"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/policy"
)

// returns a build policy type named "Build." Used to created the PR build policy on the default branch
Expand All @@ -34,12 +37,12 @@ func getBuildType(ctx context.Context, projectId *string, policyClient policy.Cl
// this also disables direct pushes to the default branch and requires changes to go through a PR.
func CreateBuildPolicy(
ctx context.Context,
connection *azuredevops.Connection,
connection Connection,
projectId string,
repoId string,
buildDefinition *build.BuildDefinition,
env *environment.Environment) error {
client, err := policy.NewClient(ctx, connection)
client, err := policy.NewClient(ctx, connection.Connection)
if err != nil {
return err
}
Expand All @@ -49,6 +52,49 @@ func CreateBuildPolicy(
return err
}

localClient, err := newLocalClient(ctx, connection.Connection)
if err != nil {
return err
}

// check if the policy already exists
existingPoliciesResponse, error := localClient.getPolicyConfigurations(ctx, getPolicyConfigurationsArgs{
Project: &projectId,
PolicyType: buildPolicyType.Id,
})
if error != nil {
return error
}
existingPolicies := []policy.PolicyConfiguration{}
for existingPoliciesResponse != nil {
existingPolicies = append(existingPolicies, existingPoliciesResponse.Value...)
if existingPoliciesResponse.ContinuationToken == "" {
break
}
existingPoliciesResponse, error = localClient.getPolicyConfigurations(ctx, getPolicyConfigurationsArgs{
Project: &projectId,
PolicyType: buildPolicyType.Id,
ContinuationToken: existingPoliciesResponse.ContinuationToken,
})
if error != nil {
return error
}
}

for _, policy := range existingPolicies {
pSettings := policy.Settings.(map[string]interface{})
if def, exists := pSettings["buildDefinitionId"]; exists {
defId, castOk := def.(float64)
if !castOk {
return fmt.Errorf("could not cast buildDefinitionId to int")
}
if defId == float64(*buildDefinition.Id) {
// policy already exists
return nil
}
}
}

policyTypeRef := &policy.PolicyTypeRef{
Id: buildPolicyType.Id,
}
Expand Down Expand Up @@ -97,3 +143,68 @@ func CreateBuildPolicy(

return nil
}

func newLocalClient(ctx context.Context, connection *azuredevops.Connection) (*clientImpl, error) {
vhvb1989 marked this conversation as resolved.
Show resolved Hide resolved
client, err := connection.GetClientByResourceAreaId(ctx, uuid.MustParse("fb13a388-40dd-4a04-b530-013a739c72ef"))
if err != nil {
return nil, err
}
return &clientImpl{
Client: *client,
}, nil
}

type clientImpl struct {
Client azuredevops.Client
}

// local implementation for GetPolicyConfigurations
// The implementation from the policy client is broken because it does not support taking a continuation token
// see: https://github.com/microsoft/azure-devops-go-api/issues/156
vhvb1989 marked this conversation as resolved.
Show resolved Hide resolved
func (client *clientImpl) getPolicyConfigurations(
ctx context.Context, args getPolicyConfigurationsArgs) (*getPolicyConfigurationsResponseValue, error) {
routeValues := make(map[string]string)
if args.Project == nil || *args.Project == "" {
return nil, &azuredevops.ArgumentNilOrEmptyError{ArgumentName: "args.Project"}
}
routeValues["project"] = *args.Project

queryParams := url.Values{}
if args.Scope != nil {
queryParams.Add("scope", *args.Scope)
}
if args.PolicyType != nil {
queryParams.Add("policyType", (*args.PolicyType).String())
}
if args.ContinuationToken != "" {
queryParams.Add("continuationToken", args.ContinuationToken)
}
locationId, _ := uuid.Parse("dad91cbe-d183-45f8-9c6e-9c1164472121")
vhvb1989 marked this conversation as resolved.
Show resolved Hide resolved
resp, err := client.Client.Send(
ctx, http.MethodGet, locationId, "7.1-preview.1", routeValues, queryParams, nil, "", "application/json", nil)
if err != nil {
return nil, err
}

var responseValue getPolicyConfigurationsResponseValue
responseValue.ContinuationToken = resp.Header.Get(azuredevops.HeaderKeyContinuationToken)
err = client.Client.UnmarshalCollectionBody(resp, &responseValue.Value)
return &responseValue, err
}

// Arguments for the GetPolicyConfigurations function
type getPolicyConfigurationsArgs struct {
// (required) Project ID or project name
Project *string
// (optional) [Provided for legacy reasons] The scope on which a subset of policies is defined.
Scope *string
// (optional) Filter returned policies to only this type
PolicyType *uuid.UUID
ContinuationToken string
}

// Return type for the GetPolicyConfigurations function
type getPolicyConfigurationsResponseValue struct {
Value []policy.PolicyConfiguration
ContinuationToken string
}
2 changes: 1 addition & 1 deletion cli/azd/pkg/azdo/build_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"context"
"testing"

"github.com/microsoft/azure-devops-go-api/azuredevops/policy"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/policy"
"github.com/stretchr/testify/require"
)

Expand Down
2 changes: 1 addition & 1 deletion cli/azd/pkg/azdo/paged_response_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"context"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/microsoft/azure-devops-go-api/azuredevops/build"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/build"
)

// Define an API for client methods that returns a page and a continuation token.
Expand Down
23 changes: 12 additions & 11 deletions cli/azd/pkg/azdo/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ import (
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/azure/azure-dev/cli/azd/pkg/output"
"github.com/azure/azure-dev/cli/azd/pkg/tools/azcli"
"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/build"
"github.com/microsoft/azure-devops-go-api/azuredevops/taskagent"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/build"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/taskagent"
)

// Creates a variable to be associated with a Pipeline
Expand All @@ -31,9 +30,9 @@ func createBuildDefinitionVariable(value string, isSecret bool, allowOverride bo
func getAgentQueue(
ctx context.Context,
projectId string,
connection *azuredevops.Connection,
connection Connection,
) (*taskagent.TaskAgentQueue, error) {
client, err := taskagent.NewClient(ctx, connection)
client, err := taskagent.NewClient(ctx, connection.Connection)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -88,15 +87,15 @@ func CreatePipeline(
projectId string,
name string,
repoName string,
connection *azuredevops.Connection,
connection Connection,
credentials *azcli.AzureCredentials,
env *environment.Environment,
console input.Console,
provisioningProvider provisioning.Options,
additionalSecrets map[string]string,
additionalVariables map[string]string) (*build.BuildDefinition, error) {

client, err := build.NewClient(ctx, connection)
client, err := build.NewClient(ctx, connection.Connection)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -270,10 +269,11 @@ func createAzureDevPipelineArgs(
// run a pipeline. This is used to invoke the deploy pipeline after a successful push of the code
func QueueBuild(
ctx context.Context,
connection *azuredevops.Connection,
connection Connection,
projectId string,
buildDefinition *build.BuildDefinition) error {
client, err := build.NewClient(ctx, connection)
buildDefinition *build.BuildDefinition,
branchName string) error {
client, err := build.NewClient(ctx, connection.Connection)
if err != nil {
return err
}
Expand All @@ -282,7 +282,8 @@ func QueueBuild(
}

newBuild := &build.Build{
Definition: definitionReference,
Definition: definitionReference,
SourceBranch: &branchName,
}
queueBuildArgs := build.QueueBuildArgs{
Project: &projectId,
Expand Down
21 changes: 10 additions & 11 deletions cli/azd/pkg/azdo/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,8 @@ import (

"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/microsoft/azure-devops-go-api/azuredevops"
"github.com/microsoft/azure-devops-go-api/azuredevops/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/operations"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/core"
"github.com/microsoft/azure-devops-go-api/azuredevops/v7/operations"
)

// returns a process template (basic, agile etc) used in the new project creation flow
Expand All @@ -31,12 +30,12 @@ func getProcessTemplateId(ctx context.Context, client core.Client) (string, erro
// creates a new Azure DevOps project
func createProject(
ctx context.Context,
connection *azuredevops.Connection,
connection Connection,
name string,
description string,
console input.Console,
) (*core.TeamProjectReference, error) {
coreClient, err := core.NewClient(ctx, connection)
coreClient, err := core.NewClient(ctx, connection.Connection)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -67,7 +66,7 @@ func createProject(
return nil, err
}

operationsClient := operations.NewClient(ctx, connection)
operationsClient := operations.NewClient(ctx, connection.Connection)

getOperationsArgs := operations.GetOperationArgs{
OperationId: res.Id,
Expand Down Expand Up @@ -107,7 +106,7 @@ func createProject(
func GetProjectFromNew(
ctx context.Context,
repoPath string,
connection *azuredevops.Connection,
connection Connection,
env *environment.Environment,
console input.Console,
) (string, string, error) {
Expand Down Expand Up @@ -153,10 +152,10 @@ func GetProjectFromNew(
// return an azdo project by name
func GetProjectByName(
ctx context.Context,
connection *azuredevops.Connection,
connection Connection,
name string,
) (*core.TeamProjectReference, error) {
coreClient, err := core.NewClient(ctx, connection)
coreClient, err := core.NewClient(ctx, connection.Connection)
if err != nil {
return nil, err
}
Expand All @@ -180,10 +179,10 @@ func GetProjectByName(
// prompt the user to select form a list of existing Azure DevOps projects
func GetProjectFromExisting(
ctx context.Context,
connection *azuredevops.Connection,
connection Connection,
console input.Console,
) (string, string, error) {
coreClient, err := core.NewClient(ctx, connection)
coreClient, err := core.NewClient(ctx, connection.Connection)
if err != nil {
return "", "", err
}
Expand Down
Loading
Loading