Modules/AzureDevOpsDsc.Common/Api/Functions/Private/Api/Pipeline/Set-DevOpsPipeline.ps1
|
<#
.SYNOPSIS Updates a YAML pipeline's name, folder, YAML path, repository and default branch. .DESCRIPTION The Pipelines API ('_apis/pipelines') can create and list pipelines but has no update verb - a PATCH or PUT to '_apis/pipelines/{id}' is refused with '405 Method Not Allowed'. A YAML pipeline is a classic build definition underneath, so the update reads that definition, changes the managed fields, and writes the whole definition back with a PUT. Every field this function does not manage (triggers, variables, retention, options) is sent back as it was read. The repository is only rewritten when its identity changes (type, name or service connection). Otherwise just its default branch is set, so the rest of the repository block the service filled in is kept. .PARAMETER ApiUri The base organization API URI, e.g. 'https://dev.azure.com/myorg'. .PARAMETER ProjectName The Azure DevOps project name. .PARAMETER PipelineId The pipeline id, which is also its build definition id. .PARAMETER PipelineName The pipeline name. .PARAMETER FolderPath The folder the pipeline sits in, e.g. '\' or '\Team\CI'. .PARAMETER YamlFilePath The path to the YAML file within the repository. .PARAMETER RepositoryId The Azure Repos repository id. Used only when 'RepositoryType' is 'TfsGit'. .PARAMETER RepositoryName The repository name: an Azure Repos repository name, or 'owner/repo' for GitHub, GitHub Enterprise and Bitbucket. .PARAMETER RepositoryType The build definition repository type: 'TfsGit', 'GitHub', 'GitHubEnterprise' or 'Bitbucket'. .PARAMETER RepositoryUrl The repository clone URL. Sent only when the repository identity changes. .PARAMETER ServiceConnectionId The id of the service connection that reaches an external repository. .PARAMETER DefaultBranch The default branch, as a full ref, e.g. 'refs/heads/main'. .PARAMETER ApiVersion The REST API version to use. Defaults to '7.1'. .EXAMPLE Set-DevOpsPipeline -ApiUri 'https://dev.azure.com/myorg' -ProjectName 'Contoso' -PipelineId 12 -PipelineName 'CI' ` -FolderPath '\Team' -YamlFilePath 'azure-pipelines.yml' -RepositoryId $repo.id -RepositoryName 'Contoso' #> Function Set-DevOpsPipeline { [CmdletBinding()] param( [Parameter(Mandatory)][string]$ApiUri, [Parameter(Mandatory)][string]$ProjectName, [Parameter(Mandatory)][int]$PipelineId, [Parameter(Mandatory)][string]$PipelineName, [Parameter()][string]$FolderPath = '\', [Parameter()][string]$YamlFilePath = '/azure-pipelines.yml', [Parameter()][string]$RepositoryId, [Parameter()][string]$RepositoryName, [Parameter()][ValidateSet('TfsGit', 'GitHub', 'GitHubEnterprise', 'Bitbucket')][string]$RepositoryType = 'TfsGit', [Parameter()][string]$RepositoryUrl, [Parameter()][string]$ServiceConnectionId, [Parameter()][string]$DefaultBranch = 'refs/heads/main', [Parameter()][string]$ApiVersion = '7.1' ) # A JSON-deserialized definition is a PSCustomObject, which throws on a dot-assignment to a # property the API left out; a hashtable does not have that problem. $setMember = { param($Object, [string]$Name, $Value) if ($Object -is [System.Collections.IDictionary]) { $Object[$Name] = $Value } elseif ($Object.PSObject.Properties[$Name]) { $Object.PSObject.Properties[$Name].Value = $Value } else { $Object | Add-Member -MemberType NoteProperty -Name $Name -Value $Value -Force } } try { $definition = Get-DevOpsBuildDefinition -ApiUri $ApiUri -ProjectName $ProjectName -DefinitionId $PipelineId -ApiVersion $ApiVersion } catch { Throw "[Set-DevOpsPipeline] Failed to update pipeline '$PipelineId': $_" } if ($null -eq $definition) { Throw "[Set-DevOpsPipeline] Failed to update pipeline '$PipelineId': its build definition could not be read." } & $setMember $definition 'name' $PipelineName & $setMember $definition 'path' $FolderPath if ($null -eq $definition.process) { # type 2 is a YAML process. & $setMember $definition 'process' ([PSCustomObject]@{ type = 2 }) } & $setMember $definition.process 'yamlFilename' $YamlFilePath if ($null -eq $definition.repository) { & $setMember $definition 'repository' ([PSCustomObject]@{}) } $repository = $definition.repository $sameRepository = ("$($repository.type)" -eq $RepositoryType) -and ("$($repository.name)" -eq $RepositoryName) -and (($RepositoryType -eq 'TfsGit') -or ("$($repository.properties.connectedServiceId)" -eq $ServiceConnectionId)) if (-not $sameRepository) { & $setMember $repository 'type' $RepositoryType & $setMember $repository 'name' $RepositoryName if ($null -eq $repository.properties) { & $setMember $repository 'properties' ([PSCustomObject]@{}) } if ($RepositoryType -eq 'TfsGit') { & $setMember $repository 'id' $RepositoryId if ($repository.properties -isnot [System.Collections.IDictionary]) { $repository.properties.PSObject.Properties.Remove('connectedServiceId') } else { $repository.properties.Remove('connectedServiceId') } } else { # An external repository has no id of its own: the build definition uses its full name. & $setMember $repository 'id' $RepositoryName & $setMember $repository.properties 'connectedServiceId' $ServiceConnectionId } if (-not [String]::IsNullOrWhiteSpace($RepositoryUrl)) { & $setMember $repository 'url' $RepositoryUrl } } & $setMember $repository 'defaultBranch' $DefaultBranch try { return Set-DevOpsBuildDefinition -ApiUri $ApiUri -ProjectName $ProjectName -DefinitionId $PipelineId -Definition $definition -ApiVersion $ApiVersion } catch { Throw "[Set-DevOpsPipeline] Failed to update pipeline '$PipelineId': $_" } } |