AzD Passing Artifact Path Name to Release
On the path to make something generic, one task is to make as much as possible be variable based. By that I mean one place to define a value as a variable, then use it throughout the pipeline by referencing saied variable . This is good practise because it will make it more robust for reuse, and are good first steps on the journey towards a more holistic approach towards treating pipeline config as code.
One such need I have found, is that there is no robust way in the release pipeline task to reference a file inside the artifact build produces.
1 | $(System.DefaultWorkingDirectory)/ArtifactAliasName/ArtifactName/Folder/File |
Hardcoding the value seems trivial at first, though once pipelines become complex. Changing anything with hardcoded values will break the pipeline in unpredictable ways.
One way of making this a bit more robust is to use the built in Release.PrimaryArtifactSourceAlias variable. This will be populated at agent runtime with the name of the primary artifact. So if one uses multiple artifacts, one of these must be set as primary for this to work as expected. So it has some downside, though with careful thought one can make it work.
1 | $(System.DefaultWorkingDirectory)/$(Release.PrimaryArtifactSourceAlias)/ArtifactName/Folder/File |
This looks better, though we still have to input the artifact name.
One way to solve this is to build on my previous post about how to pass information between Build and Release.
So if we take the evolved build yaml from that post:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | name: Build $(Version.Major).$(Version.Minor) pool: name: Azure Pipelines # Default hosted pipeline name vmImage: 'windows-2019' variables: VSteamAccount: 'https://dev.azure.com/MyAccountName' Version.Major: 0 Version.Minor: $[counter(variables['Version.Major'], 0)] PSScriptAnalyzerErrorLevel: 'Error' VariableGroupName: 'SharedRunTimeVariables' GitDiffVariableName: 'GitFileDiff' ArtifactName: AAdeploy ForceDeployAll: 'false' trigger: branches: include: - master paths: include: - '*' exclude: - 'Azure-Automation-Runbook-CD.json' - 'Deploy/Pipeline/*' - 'Build/*' pr: none steps: - task: PowerShell@2 displayName: 'Run Tests' inputs: targetType: filePath filePath: '$(System.DefaultWorkingDirectory)\Build\AzP-RunTests.ps1' arguments: '-TestScriptPath ''$(System.DefaultWorkingDirectory)\Build\Tests\*'' -TestPath ''$(System.DefaultWorkingDirectory)\Deploy'' -TestOutPutFilePath ''$(System.DefaultWorkingDirectory)\Test-Pester.xml'' -MinimumSeverityLevel ''$(PSScriptAnalyzerErrorLevel)''' failOnStderr: true - task: PowerShell@2 displayName: 'Run Pipeline Helpers' inputs: targetType: filePath filePath: '$(System.DefaultWorkingDirectory)\Build\AzP-Helpers.ps1' arguments: '-VSteamAccount ''$(VSteamAccount)'' -VSteamProject ''$(System.TeamProject)'' -AccessToken ''$(System.AccessToken)'' -VariableGroupName ''$(VariableGroupName)'' -GitDiffVariableName ''$(GitDiffVariableName)'' -ForceDeployAll ''$(ForceDeployAll)'' -ArtifactRootPathName ''$(ArtifactName)''' failOnStderr: true - task: PublishTestResults@2 displayName: 'Publish Pester Test Results' inputs: testResultsFormat: NUnit failTaskOnFailedTests: true - task: PublishPipelineArtifact@1 displayName: 'Publish AA resources as artifact' inputs: targetPath: '$(System.DefaultWorkingDirectory)\Deploy' artifactName: '$(ArtifactName)' |
Here I have added a new variable called ArtifactName, this is then populated with the value one wants to use.
The next change is to the PublishBuildArtifacts task, where one instead of naming the artifact directly there. Instead reference it through the variable name ‘$(ArtifactName)’.
Now we will need to get this information over to the Release pipeline.
This is where we build upon the last post, where we in the AzP-Helpers.ps1 script add an input parameter called ArtifactRootPathName. Here we pass inn the same variable used to name the artifact.
Now inside the script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [ValidateNotNullorEmpty()] [string]$VSteamAccount, [Parameter(Mandatory=$true)] [ValidateNotNullorEmpty()] [string]$VSteamProject, [Parameter(Mandatory=$true)] [ValidateNotNullorEmpty()] [string]$AccessToken, [Parameter(Mandatory=$true)] [ValidateNotNullorEmpty()] [string]$VariableGroupName, [Parameter(Mandatory=$true)] [ValidateNotNullorEmpty()] [string]$GitDiffVariableName, [Parameter(Mandatory=$true)] [ValidateNotNullorEmpty()] [string]$ArtifactRootPathName, [Parameter(Mandatory=$false)] [ValidateSet("true", "false")] [string]$ForceDeployAll = "false" ) try { Write-Host -Object "Starting helper task at time: $(get-Date -format r).`nRunning PS version: $($PSVersionTable.PSVersion)`nOn agent: $($env:computername)" Write-Host -Object "Build number: $env:Version_Major.$env:Version_Minor" if($ForceDeployAll -eq "true") { Write-Host -Object "Force Deploy of Runbook parameter set" $ForceDeploy = $true } else { Write-Host -Object "Force Deploy of Runbook parameter not set" $ForceDeploy = $false } Write-Host -Object "Trusting PSGallery" Set-PSRepository PSGallery -InstallationPolicy Trusted -ErrorAction Continue -ErrorVariable oErr if($oErr) { Write-Host -Object "##vso[task.logissue type=error;]Failed to set PSGallery as trusted" Write-Error -Message "Failed to set PSGallery as trusted" -ErrorAction Stop } Write-Host -Object "Installing VSTeam module" Install-Module VSTeam -Scope CurrentUser -Force -ErrorAction Continue -ErrorVariable oErr if($oErr) { Write-Host -Object "##vso[task.logissue type=error;]Failed to install VSTeam module" Write-Error -Message "Failed to install VSTeam module" -ErrorAction Stop } Write-Host -Object "Importing modules" Import-Module VSTeam -ErrorAction Continue -ErrorVariable oErr if ($oErr) { Write-Host -Object "##vso[task.logissue type=error;]Failed to load needed modules for build script: VSTeam" Write-Error -Message "Failed to load needed modules for build script: VSTeam" -ErrorAction Stop } Write-Host -Object "Fetching changed files from github since last PR" # Get files changed since last PR $GitResult = git diff --name-only HEAD~ | Where-Object { $_ -like "*/Runbooks/*" } | ForEach-Object { $_.split('/')[-1] } Write-Host -Object "Connecting to Azure Pipeline account: $VSteamAccount in project: $VSteamProject" Set-VSTeamAccount -Account $VSteamAccount -Token $AccessToken -UseBearerToken -ErrorAction Continue -ErrorVariable oErr if ($oErr) { Write-Host -Object "##vso[task.logissue type=error;]Failed to connect to AzD account: $VSteamAccount" Write-Error -Message "Failed to connect to AzD account: $VSteamAccount" -ErrorAction Stop } else { # Get variable group Write-Host -Object "Fetching variable group: $VariableGroupName from project: $VSteamProject" $VariableGroup = Get-VSTeamVariableGroup -ProjectName $VSteamProject -Name $VariableGroupName -ErrorAction Continue -ErrorVariable oErr if ($oErr) { Write-Host -Object "##vso[task.logissue type=error;]Failed to fetch variable group: $VariableGroupName" Write-Error -Message "Failed to fetch variable group: $VariableGroupName" -ErrorAction Stop } # Check if object structure has changed for variable group if(-not ([string]::IsNullOrEmpty($VariableGroup.id) -and [string]::IsNullOrEmpty($VariableGroup.name)) ) { if($GitResult) { $GitFileDiff = $GitResult -join "," Write-Host -Object "Runbook file changes since last PR:`n$GitFileDiff" # Update variable group with file diff values from github Write-Host -Object "Setting shared variable: $GitDiffVariableName and ArtifactRootPathName" $methodParameters = @{ Name = $VariableGroup.name id = $VariableGroup.id ProjectName = $VSteamProject Description = "Used for data to be shared Build and Release" Type = "Vsts" Variables = @{ $GitDiffVariableName = @{ value = $GitFileDiff isSecret = $false } ArtifactRootPathName = @{ value = $ArtifactRootPathName isSecret = $false } } } } # Update all Runbooks if force deploy or first time build elseif( $ForceDeploy -or ($env:Version_Major -eq "0" -and $env:Version_Minor -eq "0") ) { Write-Host -Object "Forcing publish of all Runbooks" Write-Host -Object "Setting shared variable: $GitDiffVariableName and ArtifactRootPathName" $methodParameters = @{ Name = $VariableGroup.name id = $VariableGroup.id ProjectName = $VSteamProject Description = "Used for data to be shared Build and Release" Type = "Vsts" Variables = @{ $GitDiffVariableName = @{ value = "" isSecret = $false } ArtifactRootPathName = @{ value = $ArtifactRootPathName isSecret = $false } } } } else { Write-Host -Object "No Runbooks changed since last PR" Write-Host -Object "Setting shared variable: $GitDiffVariableName and ArtifactRootPathName" $methodParameters = @{ Name = $VariableGroup.name id = $VariableGroup.id ProjectName = $VSteamProject Description = "Used for data to be shared Build and Release" Type = "Vsts" Variables = @{ $GitDiffVariableName = @{ value = "!nochange!" isSecret = $false } ArtifactRootPathName = @{ value = $ArtifactRootPathName isSecret = $false } } } } Update-VSTeamVariableGroup @methodParameters -ErrorAction Continue -ErrorVariable oErr if ($oErr) { Write-Host -Object "##vso[task.logissue type=error;]Failed to update variable group variable: $GitDiffVariableName and ArtifactRootPathName" Write-Error -Message "Failed to update variable group variable: $GitDiffVariableName and ArtifactRootPathName"-ErrorAction Stop } else { Write-Host -Object "Successfully added file diff list from last PR to variable group variable: $GitDiffVariableName and Artifact root path to ArtifactRootPathName" } } else { Write-Host -Object "##vso[task.logissue type=error;]Object structure for Get-VSTeamVariableGroup has changed, therefore could not extract variables from: $VariableGroupName" } } } catch { if ($_.Exception.Message) { Write-Host -Object "##vso[task.logissue type=error;]$($_.Exception.Message)" Write-Error -Message "$($_.Exception.Message)" -ErrorAction Continue } else { Write-Host -Object "##vso[task.logissue type=error;]$($_.Exception)" Write-Error -Message "$($_.Exception)" -ErrorAction Continue } } finally { Write-Host -Object "Helper task ended at time: $(Get-Date -Format r)" } |
Note where the variable is added:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | $methodParameters = @{ Name = $VariableGroup.name id = $VariableGroup.id ProjectName = $VSteamProject Description = "Used for data to be shared Build and Release" Type = "Vsts" Variables = @{ $GitDiffVariableName = @{ value = $GitFileDiff isSecret = $false } ArtifactRootPathName = @{ value = $ArtifactRootPathName isSecret = $false } } |
Be aware that the shared variable ArtifactRootPathName must be first created before first time use.
Finally we can change out the static path in the release pipeline with runtime logic like this.
Congratulation on one tiny step on the road to a more generic and robust pipeline.
Happy tinkering!