From 91de844d768e3d2ff5999ec8ba82bf4700d0b548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=A5hlin?= Date: Sun, 9 Jul 2023 19:20:42 +0200 Subject: [PATCH 1/3] Update ADO Pipeline instructions --- docs/wiki/Azure-Pipelines.md | 176 ++++++++++++++++------------------- 1 file changed, 80 insertions(+), 96 deletions(-) diff --git a/docs/wiki/Azure-Pipelines.md b/docs/wiki/Azure-Pipelines.md index ee97e0e8..ee606dc0 100644 --- a/docs/wiki/Azure-Pipelines.md +++ b/docs/wiki/Azure-Pipelines.md @@ -66,123 +66,107 @@ $SubscriptionId = '' $ARM_CLIENT_ID = '' $ARM_CLIENT_SECRET = '' -# Create a new project -$Project = az devops project list --query "value[?name=='$ProjectName'].{name:name, id:id}" --organization "https://dev.azure.com/$Organization" | ConvertFrom-Json -if ($null -eq $Project) { - $Project = az devops project create --name $ProjectName --organization "https://dev.azure.com/$Organization" ` - --query "{name:name, id:id}" | ConvertFrom-Json +$OrgParams = @{ + Organization = $Organization + Project = $ProjectName } -# Set the defaults for the local Azure Cli shell -az devops configure ` - --defaults organization="https://dev.azure.com/$Organization" project="$ProjectName" +# Install the ADOPS PowerShell module +Install-Module -Name ADOPS -Scope CurrentUser -RequiredVersion '2.0.0' -Force -# Create a new repository from the AzOps Accelerator template repository -$Repo = az repos list --query "[?name=='$RepoName'].{name:name, id:id}" | ConvertFrom-Json -if ($null -eq $Repo) { - $Repo = az repos create --name $RepoName --query "{name:name, id:id}" | ConvertFrom-Json +# Connect to Azure DevOps (This will open a browser window for you to login) +Connect-ADOPS -Organization $Organization + +# Create a new project and wait for it to be created +$Project = Get-ADOPSProject @OrgParams +if ($null -eq $Project) { + $Request = New-ADOPSProject -Name $ProjectName -Organization $Organization -Visibility Private + $Count = 0 + do { + Start-Sleep -Seconds 1 + $Count++ + $Result = Invoke-ADOPSRestMethod -Uri $Request.Url + if ($Count -gt 30) { + throw "Project creation timed out" + } + } while ($Result.status -ne 'succeeded') + $Project = Get-ADOPSProject @OrgParams } -az repos import create ` - --git-url "https://github.com/Azure/AzOps-Accelerator.git" --repository "$($Repo.name)" -$null = az repos update --repository $RepoName --default-branch 'main' -# Add a variable group for authenticating pipelines with Azure Resource Manager and record the id output -if ($ARM_CLIENT_SECRET) { - $VariableGroupId = az pipelines variable-group create ` - --name 'credentials' ` - --variables ` - "ARM_TENANT_ID=$TenantId" "ARM_SUBSCRIPTION_ID=$SubscriptionId" "ARM_CLIENT_ID=$ARM_CLIENT_ID" ` - --query 'id' -} else { - $VariableGroupId = az pipelines variable-group create ` - --name 'credentials' ` - --variables ` - "ARM_TENANT_ID=$TenantId" "ARM_SUBSCRIPTION_ID=$SubscriptionId" "ARM_CLIENT_ID=$ARM_CLIENT_ID" "ARM_CLIENT_SECRET=$ARM_CLIENT_SECRET" ` - --query 'id' +# Create a new repository from the AzOps Accelerator template repository +try { + $Repo = Get-ADOPSRepository @OrgParams -Repository $RepoName +} +catch { + $Repo = New-ADOPSRepository @OrgParams -Name $RepoName } -$ConfigVariableGroupId = az pipelines variable-group create ` - --name 'azops' ` - --variables ` - "AZOPS_MODULE_VERSION=" "AZOPS_CUSTOM_SORT_ORDER=false" ` - --query 'id' +# Import the AzOps Accelerator template repository and wait for the import to complete +$ImportRequest = Import-ADOPSRepository @OrgParams -RepositoryName $RepoName -GitSource 'https://github.com/Azure/AzOps-Accelerator.git' +$Count = 0 +do { + Start-Sleep -Seconds 1 + $Count++ + $Result = Invoke-ADOPSRestMethod -Uri $ImportRequest.Url + if ($Count -gt 30) { + throw "Repository import task timed out" + } +} while ($Result.status -ne 'completed') +$null = Set-ADOPSRepository -RepositoryId $repo.id -DefaultBranch 'main' @OrgParams -# Add a secret to the variable group just created using id from above if using service principal +# Add a variable group for authenticating pipelines with Azure Resource Manager and record the id output +$CredentialVariableGroup = @( + @{Name = 'ARM_TENANT_ID'; Value = $TenantId; IsSecret = $false } + @{Name = 'ARM_SUBSCRIPTION_ID'; Value = $SubscriptionId; IsSecret = $false } + @{Name = 'ARM_CLIENT_ID'; Value = $ARM_CLIENT_ID; IsSecret = $false } +) if ($ARM_CLIENT_SECRET) { - az pipelines variable-group variable create ` - --id $VariableGroupId --name 'ARM_CLIENT_SECRET' --secret true --value $ARM_CLIENT_SECRET + $CredentialVariableGroup += @{Name = 'ARM_CLIENT_SECRET'; Value = $ARM_CLIENT_SECRET; IsSecret = $true } } +$null = New-ADOPSVariableGroup -VariableGroupName 'credentials' -VariableHashtable $CredentialVariableGroup @OrgParams -# Create three new pipelines from existing YAML manifests. -az pipelines create --skip-first-run true ` - --name 'AzOps - Push' --branch main --repository "$RepoName" --repository-type tfsgit --yaml-path .pipelines/push.yml - -az pipelines create --skip-first-run true ` - --name 'AzOps - Pull' --branch main --repository "$RepoName" --repository-type tfsgit --yaml-path .pipelines/pull.yml +$ConfigVariableGroup = @( + @{Name = 'AZOPS_MODULE_VERSION'; Value = ''; IsSecret = $false } + @{Name = 'AZOPS_CUSTOM_SORT_ORDER'; Value = 'false'; IsSecret = $false } +) +$null = New-ADOPSVariableGroup -VariableGroupName 'azops' -VariableHashtable $ConfigVariableGroup @OrgParams -az pipelines create --skip-first-run true ` - --name 'AzOps - Validate' --branch main --repository "$RepoName" --repository-type tfsgit --yaml-path .pipelines/validate.yml +# Create three new pipelines from existing YAML manifests. +$null = New-ADOPSPipeline -Name 'AzOps - Push' -YamlPath '.pipelines/push.yml' -Repository $RepoName @OrgParams +$null = New-ADOPSPipeline -Name 'AzOps - Pull' -YamlPath '.pipelines/pull.yml' -Repository $RepoName @OrgParams +$null = New-ADOPSPipeline -Name 'AzOps - Validate' -YamlPath '.pipelines/validate.yml' -Repository $RepoName @OrgParams # Add build validation policy to validate pull requests -az repos policy build create --blocking true --branch main ` - --build-definition-id (az pipelines show --name 'AzOps - Validate' --query 'id') ` - --display-name 'Validate' --enabled true --queue-on-source-update-only false ` - --repository-id (az repos list --query "[?name=='$RepoName'].id" -o tsv) ` - --manual-queue-only false --valid-duration 0 --path-filter '/root/*' +$RepoId = Get-ADOPSRepository -Repository $RepoName @OrgParams | Select-Object -ExpandProperty Id +$PipelineId = Get-ADOPSPipeline -Name 'AzOps - Validate' @OrgParams | Select-Object -ExpandProperty Id +$BuildPolicyParam = @{ + RepositoryId = $RepoId + Branch = 'main' + PipelineId = $PipelineId + Displayname = 'Validate' + filenamePatterns = '/root/*' +} +$null = New-ADOPSBuildPolicy @BuildPolicyParam @OrgParams # Add branch policy to limit merge types to squash only -az repos policy merge-strategy create --blocking true --branch main ` - --repository-id (az repos list --query "[?name=='$RepoName'].id" -o tsv) --enabled true ` - --allow-no-fast-forward false --allow-rebase false --allow-rebase-merge false ` - --allow-squash true +$null = New-ADOPSMergePolicy -RepositoryId $RepoId -Branch 'main' -allowSquash @OrgParams # Add permissions for the Build Service account to the git repository -$AzureDevOpsGlobalAppId = '499b84ac-1321-427f-aa17-267ca6975798' -$AzureReposSecurityNamespaceId = '2e9eb7ed-3c0a-47d4-87c1-0ffdd275fd87' -$ProjectId = az devops project list --query "value[?name=='$ProjectName'].id" -o tsv -$RepoId = az repos list --query "[?name=='$RepoName'].id" -o tsv -$QueryStrings = "searchFilter=General&queryMembership=None&api-version=6.0&filtervalue=$ProjectName Build Service ($Organization)" -$Uri = "`"https://vssps.dev.azure.com/$Organization/_apis/identities?$QueryStrings`"" -$Subject = az rest --method get --uri $Uri --resource $AzureDevOpsGlobalAppId -o json | ConvertFrom-Json -$Body = @{ - token = "repov2/$ProjectId/$RepoId" - merge = $true - accessControlEntries = @( - @{ - # Contribute: 4 - # Force push: 8 - # CreateBranch: 16 - # Contribute to pull requests: 16384 - # Bypass policies when completing pull requests: 32768 - allow = 4 + 8 + 16 + 16384 + 32768 - deny = 0 - descriptor = $Subject.value.descriptor - } - ) -} | ConvertTo-Json -Compress | ConvertTo-Json # Convert to json twice to properly escape characters for Python interpreter -$Uri = "`"https://dev.azure.com/$Organization/_apis/accesscontrolentries/${AzureReposSecurityNamespaceId}?api-version=6.0`"" -az rest --method post --uri $Uri --body $Body --resource $AzureDevOpsGlobalAppId -o json +$ProjectId = Get-ADOPSProject @OrgParams | Select-Object -ExpandProperty Id +$BuildAccount = Get-ADOPSUser -Organization $Organization | + Where-Object displayName -eq "$ProjectName Build Service ($Organization)" +foreach ($permission in 'GenericContribute', 'ForcePush', 'CreateBranch', 'PullRequestContribute', 'PullRequestBypassPolicy') { + $null = Set-ADOPSGitPermission -ProjectId $ProjectId -RepositoryId $RepoId -Descriptor $BuildAccount.descriptor -Allow $permission +} # Add pipeline permissions for all three pipelines to the credentials Variable Groups -$AzureDevOpsGlobalAppId = '499b84ac-1321-427f-aa17-267ca6975798' -$Pipelines = az pipelines list --query "[? contains(name,'AzOps')].{id:id,name:name}" | ConvertFrom-Json -$Body = @( - @{ - resource = @{} - pipelines = @( - foreach ($pipeline in $Pipelines) { - @{ - id = $pipeline.id - authorized = $true - } - } - ) +$Uri = "https://dev.azure.com/$Organization/$ProjectName/_apis/distributedtask/variablegroups?api-version=7.1-preview.2" +$VariableGroups = (Invoke-ADOPSRestMethod -Uri $Uri -Method 'Get').value | Where-Object name -in 'credentials', 'azops' +foreach ($pipeline in 'AzOps - Push', 'AzOps - Pull', 'AzOps - Validate') { + $PipelineId = Get-ADOPSPipeline -Name $pipeline @OrgParams | Select-Object -ExpandProperty Id + foreach ($groupId in $VariableGroups.id) { + $null = Grant-ADOPSPipelinePermission -PipelineId $PipelineId -ResourceType 'VariableGroup' -ResourceId $groupId @OrgParams } -) | ConvertTo-Json -Depth 5 -Compress | ConvertTo-Json # Convert to json twice to properly escape characters for Python interpreter -foreach($groupName in 'credentials','azops') { - $VariableGroup = az pipelines variable-group list --query "[?name=='$groupName'].{id:id,name:name}" | ConvertFrom-Json - $Uri = "`"https://dev.azure.com/$Organization/$ProjectName/_apis/pipelines/pipelinepermissions/variablegroup/$($VariableGroup.id)?api-version=6.1-preview.1`"" - az rest --method patch --uri $Uri --body $Body --resource $AzureDevOpsGlobalAppId -o json } ``` From ea30ffcfa72da07c970201c0754d0c8144e6dab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=A5hlin?= Date: Tue, 11 Jul 2023 21:39:44 +0200 Subject: [PATCH 2/3] Use parameter -Wait instead of while-loop --- docs/wiki/Azure-Pipelines.md | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/docs/wiki/Azure-Pipelines.md b/docs/wiki/Azure-Pipelines.md index ee606dc0..0848b0ae 100644 --- a/docs/wiki/Azure-Pipelines.md +++ b/docs/wiki/Azure-Pipelines.md @@ -66,6 +66,7 @@ $SubscriptionId = '' $ARM_CLIENT_ID = '' $ARM_CLIENT_SECRET = '' + $OrgParams = @{ Organization = $Organization Project = $ProjectName @@ -80,17 +81,7 @@ Connect-ADOPS -Organization $Organization # Create a new project and wait for it to be created $Project = Get-ADOPSProject @OrgParams if ($null -eq $Project) { - $Request = New-ADOPSProject -Name $ProjectName -Organization $Organization -Visibility Private - $Count = 0 - do { - Start-Sleep -Seconds 1 - $Count++ - $Result = Invoke-ADOPSRestMethod -Uri $Request.Url - if ($Count -gt 30) { - throw "Project creation timed out" - } - } while ($Result.status -ne 'succeeded') - $Project = Get-ADOPSProject @OrgParams + $Project = New-ADOPSProject -Name $ProjectName -Organization $Organization -Visibility Private -Wait } # Create a new repository from the AzOps Accelerator template repository @@ -102,16 +93,7 @@ catch { } # Import the AzOps Accelerator template repository and wait for the import to complete -$ImportRequest = Import-ADOPSRepository @OrgParams -RepositoryName $RepoName -GitSource 'https://github.com/Azure/AzOps-Accelerator.git' -$Count = 0 -do { - Start-Sleep -Seconds 1 - $Count++ - $Result = Invoke-ADOPSRestMethod -Uri $ImportRequest.Url - if ($Count -gt 30) { - throw "Repository import task timed out" - } -} while ($Result.status -ne 'completed') +$null = Import-ADOPSRepository @OrgParams -RepositoryName $RepoName -GitSource 'https://github.com/Azure/AzOps-Accelerator.git' -Wait $null = Set-ADOPSRepository -RepositoryId $repo.id -DefaultBranch 'main' @OrgParams # Add a variable group for authenticating pipelines with Azure Resource Manager and record the id output From c692e4ec4cd5ba615d8f1005668aaf2fa2313e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20W=C3=A5hlin?= Date: Tue, 11 Jul 2023 21:40:49 +0200 Subject: [PATCH 3/3] Update ADOPS module version --- docs/wiki/Azure-Pipelines.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/wiki/Azure-Pipelines.md b/docs/wiki/Azure-Pipelines.md index 0848b0ae..b5ca72e4 100644 --- a/docs/wiki/Azure-Pipelines.md +++ b/docs/wiki/Azure-Pipelines.md @@ -73,7 +73,7 @@ $OrgParams = @{ } # Install the ADOPS PowerShell module -Install-Module -Name ADOPS -Scope CurrentUser -RequiredVersion '2.0.0' -Force +Install-Module -Name ADOPS -Scope CurrentUser -RequiredVersion '2.0.1' -Force # Connect to Azure DevOps (This will open a browser window for you to login) Connect-ADOPS -Organization $Organization