-
Notifications
You must be signed in to change notification settings - Fork 338
Add support for Azure DevOps in azd pipeline config #693
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
Changes from all commits
bdc3359
32bc049
a4467f5
4d9501f
351502a
0bbd031
e7d6ec9
ffe676f
86b15d1
47e7d1b
33a3e91
20ba445
c5d4778
ac62489
8a78c3d
73a9ee3
b0dcd30
69449ee
f2324a9
d1b2a5b
77dcf1f
ef8c377
7e0faac
92addbf
e484a5d
3752407
93a099e
c5acdee
4c18f41
1e1e899
3f3927e
920c0be
8d93985
22da58e
861a68d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package azdo | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/microsoft/azure-devops-go-api/azuredevops" | ||
| ) | ||
|
|
||
| var ( | ||
| AzDoHostName = "dev.azure.com" // hostname of the AzDo PaaS service. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this overridable for self-host situations.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, these variable are exported as part of the AzDo package and overridable. We can add helper method to make it easier, but they were built specifically with the idea that we could override to use a self host AzDo server. After this PR I will set up a self hosted AzDo and test against that. |
||
| AzDoPatName = "AZURE_DEVOPS_EXT_PAT" // environment variable that holds the Azure DevOps PAT | ||
| AzDoEnvironmentOrgName = "AZURE_DEVOPS_ORG_NAME" // environment variable that holds the Azure DevOps Organization Name | ||
| AzDoEnvironmentProjectIdName = "AZURE_DEVOPS_PROJECT_ID" // Environment Configuration name used to store the project Id | ||
| AzDoEnvironmentProjectName = "AZURE_DEVOPS_PROJECT_NAME" // Environment Configuration name used to store the project name | ||
| AzDoEnvironmentRepoIdName = "AZURE_DEVOPS_REPOSITORY_ID" // Environment Configuration name used to store repo ID | ||
| AzDoEnvironmentRepoName = "AZURE_DEVOPS_REPOSITORY_NAME" // Environment Configuration name used to store the Repo Name | ||
| AzDoEnvironmentRepoWebUrl = "AZURE_DEVOPS_REPOSITORY_WEB_URL" // web url for the configured repo. This is displayed on a the command line after a successful invocation of azd pipeline config | ||
| AzdoConfigSuccessMessage = "\nSuccessfully configured Azure DevOps Repository %s\n" // success message after azd pipeline config is successful | ||
| AzurePipelineName = "Azure Dev Deploy" // name of the azure pipeline that will be created | ||
| AzurePipelineYamlPath = ".azdo/pipelines/azure-dev.yml" // path to the azure pipeline yaml | ||
| CloudEnvironment = "AzureCloud" // target Azure Cloud | ||
| DefaultBranch = "master" // default branch for pipeline and branch policy | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be main right?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, we kept it as master because the original GitHub flow created a master branch. We can switch the default branch to main for both.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added an issue to track this #725
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure? I believe it defaults to what the developer machine has as the default and we want that default to be main. |
||
| AzDoProjectDescription = "Azure Developer CLI Project" // azure devops project description | ||
| ServiceConnectionName = "azconnection" // name of the service connection that will be used in the AzDo project. This will store the Azure service principal | ||
| ) | ||
|
|
||
| type AzureServicePrincipalCredentials struct { | ||
| TenantId string `json:"tenantId"` | ||
| ClientId string `json:"clientId"` | ||
| ClientSecret string `json:"clientSecret"` | ||
| SubscriptionId string `json:"subscriptionId"` | ||
| } | ||
|
|
||
| // helper method to return an Azure DevOps connection used the AzDo go sdk | ||
| func GetAzdoConnection(ctx context.Context, organization string, personalAccessToken string) (*azuredevops.Connection, error) { | ||
| if organization == "" { | ||
| return nil, fmt.Errorf("organization name is required") | ||
| } | ||
|
|
||
| if personalAccessToken == "" { | ||
| return nil, fmt.Errorf("personal access token is required") | ||
| } | ||
|
|
||
| organizationUrl := fmt.Sprintf("https://%s/%s", AzDoHostName, organization) | ||
| connection := azuredevops.NewPatConnection(organizationUrl, personalAccessToken) | ||
|
|
||
| return connection, nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package azdo | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| func Test_getAzdoConnection(t *testing.T) { | ||
| ctx := context.Background() | ||
| t.Run("empty organization name error", func(t *testing.T) { | ||
| _, err := GetAzdoConnection(ctx, "", "") | ||
| assert.EqualError(t, err, "organization name is required") | ||
| }) | ||
|
|
||
| t.Run("empty pat error", func(t *testing.T) { | ||
| _, err := GetAzdoConnection(ctx, "fake_org", "") | ||
| assert.EqualError(t, err, "personal access token is required") | ||
| }) | ||
| t.Run("returns a connection", func(t *testing.T) { | ||
| connection, err := GetAzdoConnection(ctx, "fake_org", "fake_pat") | ||
| assert.Nil(t, err) | ||
| assert.NotNil(t, connection) | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package azdo | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
|
|
||
| "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" | ||
| ) | ||
|
|
||
| // returns a build policy type named "Build." Used to created the PR build policy on the default branch | ||
| func getBuildType(ctx context.Context, projectId *string, policyClient policy.Client) (*policy.PolicyType, error) { | ||
| getPolicyTypesArgs := policy.GetPolicyTypesArgs{ | ||
| Project: projectId, | ||
| } | ||
| policyTypes, err := policyClient.GetPolicyTypes(ctx, getPolicyTypesArgs) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| for _, policy := range *policyTypes { | ||
| if *policy.DisplayName == "Build" { | ||
| return &policy, nil | ||
| } | ||
| } | ||
| return nil, fmt.Errorf("could not find 'Build' policy type in project") | ||
| } | ||
|
|
||
| // create the PR build policy to ensure that the pipeline runs on a new pull request | ||
| // 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, | ||
| projectId string, | ||
| repoId string, | ||
| buildDefinition *build.BuildDefinition) error { | ||
| client, err := policy.NewClient(ctx, connection) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| buildPolicyType, err := getBuildType(ctx, &projectId, client) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| policyTypeRef := &policy.PolicyTypeRef{ | ||
| Id: buildPolicyType.Id, | ||
| } | ||
| policyRevision := 1 | ||
| policyIsDeleted := false | ||
| policyIsBlocking := true | ||
| policyIsEnabled := true | ||
|
|
||
| policySettingsScope := make(map[string]interface{}) | ||
| policySettingsScope["repositoryId"] = repoId | ||
| policySettingsScope["refName"] = fmt.Sprintf("refs/heads/%s", DefaultBranch) | ||
| policySettingsScope["matchKind"] = "Exact" | ||
|
|
||
| policySettingsScopes := make([]map[string]interface{}, 1) | ||
| policySettingsScopes[0] = policySettingsScope | ||
|
|
||
| policySettings := make(map[string]interface{}) | ||
| policySettings["buildDefinitionId"] = buildDefinition.Id | ||
| policySettings["displayName"] = "Azure Dev Deploy PR" | ||
| policySettings["manualQueueOnly"] = false | ||
| policySettings["queueOnSourceUpdateOnly"] = true | ||
| policySettings["validDuration"] = 720 | ||
| policySettings["scope"] = policySettingsScopes | ||
|
|
||
| policyConfiguration := &policy.PolicyConfiguration{ | ||
| Type: policyTypeRef, | ||
| Revision: &policyRevision, | ||
| IsDeleted: &policyIsDeleted, | ||
| IsBlocking: &policyIsBlocking, | ||
| IsEnabled: &policyIsEnabled, | ||
| Settings: policySettings, | ||
| } | ||
|
|
||
| createPolicyConfigurationArgs := policy.CreatePolicyConfigurationArgs{ | ||
| Project: &projectId, | ||
| Configuration: policyConfiguration, | ||
| } | ||
|
|
||
| _, err = client.CreatePolicyConfiguration(ctx, createPolicyConfigurationArgs) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| return nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| package azdo | ||
|
|
||
| import ( | ||
| "context" | ||
| "testing" | ||
|
|
||
| "github.com/microsoft/azure-devops-go-api/azuredevops/policy" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| func Test_getBuildType(t *testing.T) { | ||
| ctx := context.Background() | ||
|
|
||
| t.Run("getPolicyTypesArgs contains projectId", func(t *testing.T) { | ||
| //arrange | ||
| mockClient := MockPolicyClient{} | ||
| projectId := "111222" | ||
| //act | ||
| policyType, err := getBuildType(ctx, &projectId, &mockClient) | ||
| //assert | ||
| require.NoError(t, err) | ||
| require.NotNil(t, policyType) | ||
| require.EqualValues(t, *mockClient.getPolicyTypesArgs.Project, projectId) | ||
| }) | ||
|
|
||
| t.Run("returns only 'Build' policy", func(t *testing.T) { | ||
| //arrange | ||
| mockClient := MockPolicyClient{} | ||
| projectId := "111222" | ||
| //act | ||
| policyType, err := getBuildType(ctx, &projectId, &mockClient) | ||
| //assert | ||
| require.NoError(t, err) | ||
| require.NotNil(t, policyType) | ||
| require.EqualValues(t, *policyType.DisplayName, "Build") | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please scribe for extra lines
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No problem, I will remove them. |
||
| }) | ||
|
|
||
| } | ||
|
|
||
| type MockPolicyClient struct { | ||
| getPolicyTypesArgs policy.GetPolicyTypesArgs | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) CreatePolicyConfiguration( | ||
| context.Context, | ||
| policy.CreatePolicyConfigurationArgs) (*policy.PolicyConfiguration, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) DeletePolicyConfiguration( | ||
| context.Context, | ||
| policy.DeletePolicyConfigurationArgs) error { | ||
| return nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) GetPolicyConfiguration( | ||
| context.Context, | ||
| policy.GetPolicyConfigurationArgs) (*policy.PolicyConfiguration, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) GetPolicyConfigurationRevision( | ||
| context.Context, | ||
| policy.GetPolicyConfigurationRevisionArgs) (*policy.PolicyConfiguration, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) GetPolicyConfigurationRevisions(context.Context, | ||
| policy.GetPolicyConfigurationRevisionsArgs) (*[]policy.PolicyConfiguration, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) GetPolicyConfigurations(context.Context, | ||
| policy.GetPolicyConfigurationsArgs) (*policy.GetPolicyConfigurationsResponseValue, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) GetPolicyEvaluation( | ||
| context.Context, | ||
| policy.GetPolicyEvaluationArgs) (*policy.PolicyEvaluationRecord, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) GetPolicyEvaluations( | ||
| context.Context, | ||
| policy.GetPolicyEvaluationsArgs) (*[]policy.PolicyEvaluationRecord, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) GetPolicyType( | ||
| context.Context, | ||
| policy.GetPolicyTypeArgs) (*policy.PolicyType, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) GetPolicyTypes(ctx context.Context, args policy.GetPolicyTypesArgs) (*[]policy.PolicyType, error) { | ||
| c.getPolicyTypesArgs = args | ||
| policyTypes := make([]policy.PolicyType, 2) | ||
| buildPolicyType := "Build" | ||
| testPolicyType := "Test" | ||
| policyTypes[0] = policy.PolicyType{ | ||
| DisplayName: &buildPolicyType, | ||
| } | ||
| policyTypes[1] = policy.PolicyType{ | ||
| DisplayName: &testPolicyType, | ||
| } | ||
| return &policyTypes, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) RequeuePolicyEvaluation( | ||
| context.Context, | ||
| policy.RequeuePolicyEvaluationArgs) (*policy.PolicyEvaluationRecord, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (c *MockPolicyClient) UpdatePolicyConfiguration( | ||
| context.Context, | ||
| policy.UpdatePolicyConfigurationArgs) (*policy.PolicyConfiguration, error) { | ||
| return nil, nil | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.