Modules/AzureDevOpsDsc.Common/Resources/Functions/Public/AzDoPipeline/Get-AzDoPipeline.ps1
|
<#
.SYNOPSIS Retrieves the current state of an Azure DevOps YAML pipeline. .DESCRIPTION Looks the pipeline up by name, then reads its classic build definition to compare the repository (name, type, default branch, and - for an external repository - the connected service id) and any managed variables against the desired state. The Pipelines API alone cannot drive this comparison: it does not return an external repository's type or connected service, and has no concept of variables at all. Secret variable values are write-only - the API never returns one - so a secret is compared only on its presence and its 'IsSecret'/'AllowOverride' flags, never its value. .PARAMETER ProjectName The Azure DevOps project name. .PARAMETER PipelineName The name of the pipeline. .PARAMETER RepositoryName The repository that contains the YAML file. An Azure Repos repository name, or 'owner/repo' for GitHub/GitHub Enterprise/Bitbucket. .PARAMETER YamlPath The path to the YAML pipeline definition file. .PARAMETER FolderPath The folder path under which the pipeline is organised. .PARAMETER DefaultBranch The default branch for the pipeline. .PARAMETER RepositoryType The type of repository backing the pipeline. 'TfsGit', 'GitHub', 'GitHubEnterprise' or 'Bitbucket'. .PARAMETER ServiceConnectionName The service connection used to reach the repository. Required when 'RepositoryType' is not 'TfsGit'. .PARAMETER Variables Pipeline variables to manage, as an array of hashtables shaped '@{ Name; Value; IsSecret; AllowOverride }'. .PARAMETER LookupResult The lookup result supplied by the DSC base class. .PARAMETER Ensure The desired state, supplied by the DSC base class. .PARAMETER Force Forces the operation, supplied by the DSC base class. .EXAMPLE Get-AzDoPipeline -ProjectName 'Contoso' -PipelineName 'CI' -RepositoryName 'Contoso' -YamlPath 'azure-pipelines.yml' #> Function Get-AzDoPipeline { [CmdletBinding()] [OutputType([System.Management.Automation.PSObject[]])] param ( [Parameter(Mandatory = $true)][string]$ProjectName, [Parameter(Mandatory = $true)][string]$PipelineName, [Parameter(Mandatory = $true)][string]$RepositoryName, [Parameter(Mandatory = $true)][string]$YamlPath, [Parameter()][string]$FolderPath = '\', [Parameter()][string]$DefaultBranch = 'main', [Parameter()][ValidateSet('TfsGit', 'GitHub', 'GitHubEnterprise', 'Bitbucket')][string]$RepositoryType = 'TfsGit', [Parameter()][string]$ServiceConnectionName, [Parameter()][Hashtable[]]$Variables, [Parameter()][HashTable]$LookupResult, [Parameter()][Ensure]$Ensure, [Parameter()][System.Management.Automation.SwitchParameter]$Force ) Write-Verbose "[Get-AzDoPipeline] Started." $result = @{ Ensure = [Ensure]::Absent propertiesChanged = @() status = $null reason = $null } $cacheKey = '{0}\{1}' -f $ProjectName, $PipelineName $pipeline = Get-CacheItem -Key $cacheKey -Type 'LivePipelines' if (-not $pipeline) { Write-Verbose "[Get-AzDoPipeline] Pipeline '$PipelineName' not in cache — falling back to live API lookup." $OrgName = Get-AzDoOrganizationName $allPipelines = List-DevOpsPipelines -ApiUri "https://dev.azure.com/$OrgName" -ProjectName $ProjectName $pipeline = $allPipelines | Where-Object { $_.name -eq $PipelineName } | Select-Object -First 1 if ($pipeline) { Add-CacheItem -Key $cacheKey -Value $pipeline -Type 'LivePipelines' } } if (-not $pipeline) { Write-Verbose "[Get-AzDoPipeline] Pipeline '$PipelineName' not found." $result.status = [DSCGetSummaryState]::NotFound return $result } Write-Verbose "[Get-AzDoPipeline] Pipeline '$PipelineName' found." $result.liveCache = $pipeline $result.Ensure = [Ensure]::Present # A GitHub/GitHub Enterprise/Bitbucket pipeline needs a service connection to resolve to an # endpoint id. This check is repeated in New-/Set-AzDoPipeline, since 'Error' here still calls # 'Set' (see AzDevOpsDscResourceBase.GetDscRequiredAction()). if ($RepositoryType -ne 'TfsGit' -and [String]::IsNullOrWhiteSpace($ServiceConnectionName)) { Write-Error "[Get-AzDoPipeline] 'ServiceConnectionName' is required when 'RepositoryType' is '$RepositoryType'." $result.status = [DSCGetSummaryState]::Error $result.reason = 'ServiceConnectionNameRequired' return $result } $OrgName = Get-AzDoOrganizationName $ApiUri = "https://dev.azure.com/$OrgName" try { $definition = Get-DevOpsBuildDefinition -ApiUri $ApiUri -ProjectName $ProjectName -DefinitionId $pipeline.id } catch { Write-Error "[Get-AzDoPipeline] Failed to read the build definition for pipeline '$PipelineName': $_" $result.status = [DSCGetSummaryState]::Error $result.reason = 'BuildDefinitionLookupFailed' return $result } if ($null -eq $definition) { Write-Error "[Get-AzDoPipeline] Build definition for pipeline '$PipelineName' returned nothing." $result.status = [DSCGetSummaryState]::Error $result.reason = 'BuildDefinitionLookupFailed' return $result } $result.buildDefinition = $definition $serviceConnectionId = $null if ($RepositoryType -ne 'TfsGit') { $connection = Resolve-AzDoServiceConnection -ProjectName $ProjectName -ConnectionName $ServiceConnectionName if (-not $connection) { Write-Error "[Get-AzDoPipeline] Service connection '$ServiceConnectionName' not found in project '$ProjectName'." $result.status = [DSCGetSummaryState]::Error $result.reason = 'ServiceConnectionNotFound' return $result } $serviceConnectionId = $connection.id $result.serviceConnectionId = $serviceConnectionId } $propertiesChanged = @() if ("$($definition.repository.name)" -ne "$RepositoryName") { Write-Verbose "[Get-AzDoPipeline] RepositoryName differs for pipeline '$PipelineName'." $propertiesChanged += 'RepositoryName' } $currentYaml = "$($definition.process.yamlFilename)".TrimStart('/') if ($currentYaml -ne "$YamlPath".TrimStart('/')) { Write-Verbose "[Get-AzDoPipeline] YamlPath differs for pipeline '$PipelineName'." $propertiesChanged += 'YamlPath' } if ((Format-AzDoPipelineFolderPath -Path $definition.path) -ne (Format-AzDoPipelineFolderPath -Path $FolderPath)) { Write-Verbose "[Get-AzDoPipeline] FolderPath differs for pipeline '$PipelineName'." $propertiesChanged += 'FolderPath' } $currentBranch = "$($definition.repository.defaultBranch)" -replace '^refs/heads/', '' if ($currentBranch -ne "$DefaultBranch") { Write-Verbose "[Get-AzDoPipeline] DefaultBranch differs for pipeline '$PipelineName'." $propertiesChanged += 'DefaultBranch' } if ("$($definition.repository.type)" -ne "$RepositoryType") { Write-Verbose "[Get-AzDoPipeline] RepositoryType differs for pipeline '$PipelineName'." $propertiesChanged += 'RepositoryType' } if ($RepositoryType -ne 'TfsGit') { $currentConnectionId = "$($definition.repository.properties.connectedServiceId)" if ($currentConnectionId -ne "$serviceConnectionId") { Write-Verbose "[Get-AzDoPipeline] ServiceConnectionName differs for pipeline '$PipelineName'." $propertiesChanged += 'ServiceConnectionName' } } if ($PSBoundParameters.ContainsKey('Variables') -and $Variables) { $liveVariables = $definition.variables foreach ($variable in $Variables) { $name = [string]$variable.Name $desiredIsSecret = if ($variable.ContainsKey('IsSecret')) { [bool]$variable.IsSecret } else { $false } $desiredAllowOverride = if ($variable.ContainsKey('AllowOverride')) { [bool]$variable.AllowOverride } else { $false } $liveVariableProperty = if ($liveVariables) { $liveVariables.PSObject.Properties[$name] } else { $null } if (-not $liveVariableProperty) { Write-Verbose "[Get-AzDoPipeline] Variable '$name' is missing on pipeline '$PipelineName'." $propertiesChanged += 'Variables' continue } $liveVariable = $liveVariableProperty.Value if ([bool]$liveVariable.isSecret -ne $desiredIsSecret) { Write-Verbose "[Get-AzDoPipeline] Variable '$name' IsSecret differs for pipeline '$PipelineName'." $propertiesChanged += 'Variables' continue } if ([bool]$liveVariable.allowOverride -ne $desiredAllowOverride) { Write-Verbose "[Get-AzDoPipeline] Variable '$name' AllowOverride differs for pipeline '$PipelineName'." $propertiesChanged += 'Variables' continue } # Secret values are write-only - the API never returns one, so only presence and the # IsSecret/AllowOverride flags can be compared here. A secret value changed only on the # live pipeline (or in the configuration) is never detected as drift. if (-not $desiredIsSecret) { $desiredValue = if ($null -eq $variable.Value) { '' } else { [string]$variable.Value } if ("$($liveVariable.value)" -ne $desiredValue) { Write-Verbose "[Get-AzDoPipeline] Variable '$name' Value differs for pipeline '$PipelineName'." $propertiesChanged += 'Variables' } } } } $result.propertiesChanged = @($propertiesChanged | Select-Object -Unique) $result.status = if ($result.propertiesChanged.Count -gt 0) { [DSCGetSummaryState]::Changed } else { [DSCGetSummaryState]::Unchanged } Write-Verbose "[Get-AzDoPipeline] Pipeline '$PipelineName' status: $($result.status)." return $result } |