KnowIT.DevOps.psm1
#region === Source functions === ### Source file: 'ConvertFrom-ADOResponse.ps1' ### filter ConvertFrom-ADOResponse { param( [Parameter(Mandatory, ValueFromPipeline)] [PSTypeNameAttribute('KnowIT.DevOps.ADOResponse')] $InputObject ) $statusCode = $InputObject.StatusCode if($InputObject.Success) { return $InputObject.Value } if($statusCode -eq 404) { Write-Verbose "Response ADO REST: No se obtuvo ningun registro" return } Write-Error -ErrorRecord ([Management.Automation.ErrorRecord]::new( [Exception]$InputObject.Error.message, $InputObject.Error.typeKey, [Management.Automation.ErrorCategory]::InvalidResult, $InputObject)) } ### Source file: 'Invoke-AzDevOpsAPI.ps1' ### function Invoke-AzDevOpsAPI { param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$Organization, [string]$Project, [string]$Path, [ValidateSet('GET', 'POST', 'PATCH', 'PUT', 'DELETE')] [string]$Method = 'GET', [object]$Body, [hashtable]$Headers = @{}, [string]$Version = '7.1', [switch]$DetailResponse ) try { Update-CallerPreference $PSCmdlet $token = GetADOToken $Headers.Authorization = "Bearer $token" $Headers.Accept = "application/json; api-version=$Version" if(-not [string]::IsNullOrWhiteSpace($Project)) { $Project += '/' } $restParams = @{ Method = $Method Uri = 'https://dev.azure.com/{0}/{1}_apis/{2}' -f $Organization, $Project, $Path.TrimStart('/') Headers = $Headers Body = $Body ? (ConvertTo-Json $Body -Depth 10) : $null ContentType = 'application/json' Verbose = $false } Write-Verbose "Request ADO REST: [v$Version] $($restParams.Method.ToUpper()) $($restParams.Uri)" if($Body) { Write-Debug "Body: $($restParams.Body)" } $result = Invoke-RestMethod @restParams -SkipHttpErrorCheck -StatusCodeVariable statusCode if($statusCode -lt 400) { Write-Verbose "Response ADO REST: [$statusCode] $([Net.HttpStatusCode]$statusCode)" $response = [PSCustomObject]@{ PSTypeName = 'KnowIT.DevOps.ADOResponse' Success = $true StatusCode = $statusCode Value = $result.value ?? $result Count = $result.count } } else { #TODO: Manjerar la respuesta 404 cuando la ruta es incorrecta Write-Debug 'Procesando error en la respuesta del API...' Write-Verbose "Response ADO REST: [$statusCode] $($result.typeKey) - $($result.message -replace "`r?`n", ' ')" $response = [PSCustomObject]@{ PSTypeName = 'KnowIT.DevOps.ADOResponse' Success = $false StatusCode = $statusCode Error = $result } } if($DetailResponse) { $response } else { $response | ConvertFrom-ADOResponse } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'Set-AzDevopsAzureConnection.ps1' ### function Set-AzDevOpsAzureConnection { [CmdletBinding()] param( [Parameter(Mandatory)] [ValidatePattern('^[a-z]+(-[a-z0-9]+)*$')] [string]$Name, [Parameter(Mandatory)] [string]$Organization, [Parameter(Mandatory)] [string]$ProjectName, [Parameter(Mandatory)] [string]$ResourceGroup, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string[]]$Roles, [switch]$PassThru ) try { Update-CallerPreference $PSCmdlet Write-Verbose "[+] Establece conexión [$Name] en el proyecto '$ProjectName' de Azure DevOps" -Verbose $azContext = Get-AzContext if($azContext.Subscription.TenantId -ne $azContext.Subscription.HomeTenantId) { throw "La conexión actual a Azure se realizo mediante una cuenta de administración delegada ($($azContext.Account)) a la suscripción '$($azContext.Subscription.Name)'. Es necesario conectarse con una cuenta con permisos de acceso al Tenant propietario de la suscripción [$($azContext.Subscription.HomeTenantId)]." } $project = Invoke-AzDevOpsApi -Organization $Organization -Path "projects/$ProjectName" if(!$project) { throw "No existe o no se tiene acceso al proyecto '$ProjectName' en la Organización [$Organization] de Azure DevOps" } $devopsSP = Register-EntraServicePrincipal "AzDevOps/$Name" $connection = @{ name = $Name type = 'AzureRM' url = 'https://management.azure.com/' data = @{ subscriptionId = $azContext.Subscription.Id subscriptionName = $azContext.Subscription.Name environment = 'AzureCloud' scopeLevel = 'Subscription' } authorization = @{ scheme = 'WorkloadIdentityFederation' parameters = @{ serviceprincipalid = $devopsSP.appId tenantid = $devopsSP.appOwnerOrganizationId } } serviceEndpointProjectReferences = @( @{ projectReference = @{ id = $project.id name = $project.name } name = $Name } ) } $endpoint = Invoke-AzDevOpsApi -Organization $Organization -Project $project.name -Path "serviceEndpoint/endpoints?endpointNames=$Name" if($endpoint) { $endpoint = Invoke-AzDevOpsApi -Organization $Organization -Project $project.name -Path "serviceEndpoint/endpoints/$($endpoint.id)" -Method PUT -Body $connection } else { $endpoint = Invoke-AzDevOpsApi -Organization $Organization -Project $project.name -Path 'serviceEndpoint/endpoints' -Method POST -Body $connection } Set-EntraAppFederatedIdentity $devopsSP.appId -Name 'AzDevOps-Federated' -Issuer $endpoint.authorization.parameters.workloadIdentityFederationIssuer -Subject $endpoint.authorization.parameters.workloadIdentityFederationSubject Write-Verbose " Asignando roles requeridos a Resource Group: [$ResourceGroup]" -Verbose $assignedRoles = (Get-AzRoleAssignment -ResourceGroupName $ResourceGroup -ObjectId $devopsSP.id).RoleDefinitionName $Roles.Where({ $_ -notin $assignedRoles }).ForEach({ $null = New-AzRoleAssignment -ResourceGroupName $ResourceGroup -RoleDefinitionName $_ -ObjectId $devopsSP.id }) if($PassThru) { $devopsSP } } catch { $PSCmdlet.WriteError($_) } } ### Source file: 'KnowIT.ModuleHelpers.ps1' ### function Update-CallerPreference { # https://devblogs.microsoft.com/scripting/weekend-scripter-access-powershell-preference-variables/ param( [ValidateNotNull()] [PSTypeName('System.Management.Automation.PSScriptCmdlet')]$ScriptCmdlet = (Get-Variable PSCmdlet -Scope 1 -ValueOnly), [ValidateSet('ErrorAction', 'Warning', 'Verbose', 'Debug', 'Information', 'Progress', 'Confirm', 'WhatIf')] [string[]]$Skip ) $commonParameters = 'ErrorAction', 'Warning', 'Verbose', 'Debug', 'Information', 'Progress', 'Confirm', 'WhatIf' $currentDebugPreference = $DebugPreference Write-Debug "Updating [$($ScriptCmdlet.MyInvocation.MyCommand)] Preference variables:" foreach($p in $commonParameters) { if($ScriptCmdlet.MyInvocation.BoundParameters.ContainsKey($p)) { continue } $var = "${p}Preference" if($p -eq 'ErrorAction') { $val = 'Stop' $scope = 'Forced' } elseif($p -in $Skip) { $val = Get-Variable -Scope Global -Name $var -ValueOnly $scope = 'Global' } else { $val = $ScriptCmdlet.GetVariableValue($var) $scope = 'Caller' } Write-Debug " (From $scope scope) $var = $val " -Debug:$currentDebugPreference Set-Variable -Scope 1 -Name $var -Value $val } } function Map-Object { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseApprovedVerbs', '', Justification = 'Internal functions')] param([scriptblock]$ScriptBlock) begin { $code = "& { process { $ScriptBlock } }" $pipeline = [scriptblock]::Create($code).GetSteppablePipeline() $pipeline.Begin($true) } process { $pipeline.Process($_) } end { $pipeline.End() } } ### Source file: 'Tokens.ps1' ### function GetADOToken { $token = Get-AzAccessToken -ResourceUrl 'https://app.vssps.visualstudio.com' -AsSecureString -Debug:$false ConvertFrom-SecureString $token.Token -AsPlainText } #endregion |