Modules/AzureDevOpsDsc.Common/Resources/Functions/Public/AzDoServiceConnection/Set-AzDoServiceConnection.ps1
|
Function Set-AzDoServiceConnection { [CmdletBinding()] param ( [Parameter(Mandatory = $true)][string]$ProjectName, [Parameter(Mandatory = $true)][string]$ConnectionName, # Not mandatory: ConnectionType is in the class's NoSetSupport list, so the base class # strips it from every Set call. The existing connection's type is used instead. [Parameter()][string]$ConnectionType, [Parameter()][string]$Description, [Parameter()][bool]$AllowAllPipelines = $false, [Parameter()][HashTable]$Authorization, [Parameter()][HashTable]$Data, [Parameter()][string[]]$SharedWithProjects, [Parameter()][HashTable]$SharedNameOverrides, [Parameter()][HashTable]$LookupResult, [Parameter()][Ensure]$Ensure, [Parameter()][System.Management.Automation.SwitchParameter]$Force ) Write-Verbose "[Set-AzDoServiceConnection] Updating service connection '$ConnectionName'." $orgName = Get-AzDoOrganizationName $orgApiUri = 'https://dev.azure.com/{0}/' -f $orgName $project = Resolve-AzDoProject -ProjectName $ProjectName $scKey = '{0}\{1}' -f $ProjectName, $ConnectionName $sc = Get-CacheItem -Key $scKey -Type 'LiveServiceConnections' if ((-not $sc) -and $project) { # Service connection may have been created earlier in this run — fall back to a live lookup. Write-Verbose "[Set-AzDoServiceConnection] Service connection '$ConnectionName' not in cache — falling back to live API lookup." $allSCs = List-DevOpsServiceConnections -ApiUri "https://dev.azure.com/$orgName" -ProjectName $ProjectName $sc = $allSCs | Where-Object { $_.name -eq $ConnectionName } | Select-Object -First 1 if ($sc) { Add-CacheItem -Key $scKey -Value $sc -Type 'LiveServiceConnections' } } if ((-not $project) -or (-not $sc)) { Write-Error "[Set-AzDoServiceConnection] Project or service connection not found." return } $params = @{ ApiUri = $orgApiUri ProjectId = $project.id ProjectName = $ProjectName ServiceConnectionId = $sc.id ServiceConnectionName = $ConnectionName ServiceConnectionType = if ([System.String]::IsNullOrWhiteSpace($ConnectionType)) { $sc.type } else { $ConnectionType } Description = $Description Authorization = if ($Authorization) { $Authorization } else { @{} } Data = if ($Data) { $Data } else { @{} } # The update call requires the endpoint's top-level url; Data.url wins when configured. Url = $sc.url } # The owning project always holds the endpoint. The other references are the projects it is # currently shared with. $ownerRef = @{ projectReference = @{ id = $project.id; name = $ProjectName }; name = $ConnectionName; description = $Description } $existingRefs = @($sc.serviceEndpointProjectReferences | Where-Object { $_.projectReference.id }) $sharedRefs = @($existingRefs | Where-Object { $_.projectReference.id -ne $project.id }) $existingIds = @($existingRefs | ForEach-Object { $_.projectReference.id }) + $project.id # Checked by value, not $PSBoundParameters.ContainsKey(...): Invoke-DscResource always binds # every DSC property (including SharedWithProjects), so ContainsKey is always true through # that path. The class property has no default initializer, so $null reliably means "not # configured" while @() means "configured empty" - in every call path, DSC-splatted or direct. $projectReferences = $null $addedRefs = @() $removedRefs = @() if ($null -ne $SharedWithProjects) { try { $projectReferences = @(Resolve-AzDoSharedProjectReferences -ProjectName $ProjectName -SharedWithProjects $SharedWithProjects -SharedNameOverrides $SharedNameOverrides -DefaultName $ConnectionName -Description $Description) } catch { Write-Error "[Set-AzDoServiceConnection] $_" return } # The PUT only edits references the endpoint already has. New projects are shared through # the dedicated share call, and dropped projects are unshared with a DELETE per project. $desiredIds = @($projectReferences | ForEach-Object { $_.projectReference.id } | Where-Object { $_ }) $addedRefs = @($projectReferences | Where-Object { $_.projectReference.id -notin $existingIds }) $removedRefs = @($sharedRefs | Where-Object { $_.projectReference.id -notin $desiredIds }) $params.ProjectReferences = @($projectReferences | Where-Object { $_.projectReference.id -in $existingIds }) } elseif ($sharedRefs.Count -gt 0) { # Sharing is not configured: keep the projects the endpoint is already shared with rather # than sending only the owning project's reference. $params.ProjectReferences = @($ownerRef) + $sharedRefs } $value = Set-DevOpsServiceConnection @params if ($null -eq $value) { Write-Error "[Set-AzDoServiceConnection] Set-DevOpsServiceConnection returned null. Check authentication token and organization settings." return } $shareErrors = [System.Collections.Generic.List[string]]::new() if ($addedRefs.Count -gt 0) { Write-Verbose "[Set-AzDoServiceConnection] Sharing service connection '$ConnectionName' with project(s): $(($addedRefs | ForEach-Object { $_.projectReference.name }) -join ', ')." try { $null = Add-DevOpsServiceConnectionProjectReferences -ApiUri $orgApiUri -ServiceConnectionId $sc.id -ProjectReferences $addedRefs } catch { $shareErrors.Add("sharing failed: $_") } } foreach ($removedRef in $removedRefs) { Write-Verbose "[Set-AzDoServiceConnection] Unsharing service connection '$ConnectionName' from project '$($removedRef.projectReference.name)'." try { Remove-DevOpsServiceConnection -ApiUri $orgApiUri -ProjectId $removedRef.projectReference.id -ServiceConnectionId $sc.id } catch { $shareErrors.Add("unsharing from project '$($removedRef.projectReference.name)' failed: $_") } } # Cache the update that did succeed before reporting any sharing failure. The desired # references are only recorded when every share and unshare call went through. if (($null -ne $projectReferences) -and ($shareErrors.Count -eq 0)) { $value.serviceEndpointProjectReferences = $projectReferences } Add-CacheItem -Key ('{0}\{1}' -f $ProjectName, $ConnectionName) -Value $value -Type 'LiveServiceConnections' Export-CacheObject -CacheType 'LiveServiceConnections' -Content $AzDoLiveServiceConnections Refresh-CacheObject -CacheType 'LiveServiceConnections' if ($shareErrors.Count -gt 0) { # Thrown rather than written: a Write-Error inside a class-based DSC method never reaches # the Invoke-DscResource caller, so Set() would report success while Test() keeps failing. throw "[Set-AzDoServiceConnection] Service connection '$ConnectionName' was updated but could not be shared/unshared: $($shareErrors -join '; ')" } Write-Verbose "[Set-AzDoServiceConnection] Service connection '$ConnectionName' updated." } |