Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
bdc3359
Adding changes to support debugging pipeline provider
hattan Sep 13, 2022
32bc049
Removing local debug values
hattan Sep 13, 2022
a4467f5
add support for project selection
hattan Sep 13, 2022
4d9501f
Adding Azdo remote validate.
hattan Sep 14, 2022
351502a
Configure git remote for azdo and select Repo
hattan Sep 14, 2022
0bbd031
Added changes to support git push with PAT and credential helper
hattan Sep 14, 2022
e7d6ec9
adding support for create project & azdo details in .env
hattan Sep 15, 2022
ffe676f
Cleanup and speed up azdo project creation
hattan Sep 15, 2022
86b15d1
Utilizing default repo when creating a new project
hattan Sep 15, 2022
47e7d1b
Cleanup + support for create new repos on existing projects.
hattan Sep 15, 2022
33a3e91
Adding Pipeline creation for Azure DevOps
hattan Sep 16, 2022
20ba445
Merge branch 'main' of github.com:azure/azure-dev into add-azdo-provider
vhvb1989 Sep 17, 2022
c5d4778
build
vhvb1989 Sep 17, 2022
ac62489
adding create pipeline, service connection and build policy
hattan Sep 17, 2022
8a78c3d
Merge branch 'add-azdo-provider' of github.com:kaizentm/azure-dev int…
hattan Sep 17, 2022
73a9ee3
Merging in pipeline detector code.
hattan Sep 17, 2022
b0dcd30
Added comments to azdo provider functions
hattan Sep 17, 2022
69449ee
added further comments to azdo.go
hattan Sep 17, 2022
f2324a9
Removing azdcontext from DetectProviders
hattan Sep 17, 2022
d1b2a5b
Adding azdo provider api literal property names to cpell global list.
hattan Sep 17, 2022
77dcf1f
Adding missing error checks.
hattan Sep 17, 2022
ef8c377
Removing unused variable
hattan Sep 17, 2022
7e0faac
Fixing golint error
hattan Sep 17, 2022
92addbf
Removing unused development test.
hattan Sep 17, 2022
e484a5d
Adding versioncontrol string literal to cspell. Used by the azdo api.
hattan Sep 17, 2022
3752407
Modifying pipeline test check. Punctuation removed from error in code…
hattan Sep 17, 2022
93a099e
Azdo provider refactor (#45)
hattan Sep 19, 2022
c5acdee
Adding build policy tests
hattan Sep 19, 2022
4c18f41
Added copyright and license comments to azdo package.
hattan Sep 19, 2022
1e1e899
Modifying azd project description and existing sp env variable name.
hattan Sep 19, 2022
3f3927e
Modifying existing sp env var to AZURE_CREDENTIALS
hattan Sep 19, 2022
920c0be
Azdo demo refactor (#48)
hattan Sep 21, 2022
8d93985
save pipeline provider election to env
hattan Sep 21, 2022
22da58e
add err check to savePipelineProviderToEnv
hattan Sep 21, 2022
861a68d
Apply suggestions from code review
vhvb1989 Sep 21, 2022
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
10 changes: 10 additions & 0 deletions .vscode/cspell.global.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,16 @@ ignoreWords:
- aztfmod
- menuid
- PLACEHOLDERIACTOOLS
- Azdo
- azconnection
- azuredevops
- taskagent
- tfsgit
- serviceendpoint
- serviceprincipalid
- serviceprincipalkey
- tenantid
- versioncontrol
useGitignore: true
dictionaryDefinitions:
- name: gitHubUserAliases
Expand Down
50 changes: 43 additions & 7 deletions cli/azd/cmd/pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import (
"context"
"fmt"
"log"
"strings"

"github.com/azure/azure-dev/cli/azd/internal"
"github.com/azure/azure-dev/cli/azd/pkg/commands"
"github.com/azure/azure-dev/cli/azd/pkg/commands/pipeline"
"github.com/azure/azure-dev/cli/azd/pkg/environment"
"github.com/azure/azure-dev/cli/azd/pkg/environment/azdcontext"
"github.com/azure/azure-dev/cli/azd/pkg/input"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -68,6 +70,7 @@ func (p *pipelineConfigAction) SetupFlags(
local.StringVar(&p.manager.PipelineServicePrincipalName, "principal-name", "", "The name of the service principal to use to grant access to Azure resources as part of the pipeline.")
local.StringVar(&p.manager.PipelineRemoteName, "remote-name", "origin", "The name of the git remote to configure the pipeline to run on.")
local.StringVar(&p.manager.PipelineRoleName, "principal-role", "Contributor", "The role to assign to the service principal.")
local.StringVar(&p.manager.PipelineProvider, "provider", "github", "The pipeline provider to use (GitHub and Azdo supported).")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
local.StringVar(&p.manager.PipelineProvider, "provider", "github", "The pipeline provider to use (GitHub and Azdo supported).")
local.StringVar(&p.manager.PipelineProvider, "provider", "github", "The pipeline provider to use (github and azdo supported).")

}

// Run implements action interface
Expand All @@ -94,13 +97,36 @@ func (p *pipelineConfigAction) Run(
return fmt.Errorf("loading environment: %w", err)
}

// Detect the SCM and CI providers based on the project directory
p.manager.ScmProvider,
p.manager.CiProvider,
err = pipeline.DetectProviders(ctx, console)

if err != nil {
return err
overrideProvider := strings.ToLower(p.manager.PipelineProvider)
if overrideProvider == "" {
// Detect the SCM and CI providers based on the project directory
p.manager.ScmProvider,
p.manager.CiProvider,
err = pipeline.DetectProviders(ctx, console, env)
if err != nil {
return err
}
} else if overrideProvider == "github" {
p.manager.ScmProvider = &pipeline.GitHubScmProvider{}
p.manager.CiProvider = &pipeline.GitHubCiProvider{}
err := savePipelineProviderToEnv(ctx, "github", env)
if err != nil {
return nil
}
} else if overrideProvider == "azdo" {
p.manager.ScmProvider = &pipeline.AzdoHubScmProvider{
Env: env,
AzdContext: azdCtx}
p.manager.CiProvider = &pipeline.AzdoCiProvider{
Env: env,
AzdContext: azdCtx,
}
err := savePipelineProviderToEnv(ctx, "azdo", env)
if err != nil {
return err
}
} else {
return fmt.Errorf("unknown pipeline provider: %s. Supported providers: [github, azdo]", overrideProvider)
}

// set context for manager
Expand All @@ -109,3 +135,13 @@ func (p *pipelineConfigAction) Run(

return p.manager.Configure(ctx)
}

func savePipelineProviderToEnv(ctx context.Context, provider string, env *environment.Environment) error {
env.Values["PIPELINE_PROVIDER"] = provider
err := env.Save()
if err != nil {
return err
}

return nil
}
52 changes: 52 additions & 0 deletions cli/azd/pkg/azdo/azdo.go
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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this overridable for self-host situations.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be main right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added an issue to track this #725

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
}
29 changes: 29 additions & 0 deletions cli/azd/pkg/azdo/azdo_test.go
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)
})
}
94 changes: 94 additions & 0 deletions cli/azd/pkg/azdo/build_policy.go
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
}
124 changes: 124 additions & 0 deletions cli/azd/pkg/azdo/build_policy_test.go
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")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please scribe for extra lines

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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
}
Loading