AzD Passing Artifact Path Name to Release

AzD Passing Artifact Path Name to Release

November 2, 2019 0 By Morten Lerudjordet

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.

$(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.

$(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:

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:

[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:

$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!