Last active
May 6, 2025 15:42
-
-
Save rleap-m/27ab8174cb479ec55d328fd9351657f3 to your computer and use it in GitHub Desktop.
Script to assign team's permissions to organizational repositories in MSR registry.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.SYNOPSIS | |
Script to assign team's permissions to organizational repositories in MSR registry. | |
.PARAMETER MkeUrl | |
The URL of the MKE server. | |
.PARAMETER MkrCred | |
The credentials for the MKE server. | |
.PARAMETER MsrRegistryHost | |
The MSR Registry hostname. | |
.PARAMETER MsrAccessToken | |
The access token for the MSR registry. | |
.PARAMETER MkeMgmtModuleName | |
The name of the MKE management module to import. | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $true)] | |
[string] $MkeUrl, | |
[Parameter(Mandatory=$true,ParameterSetName='Cred')] | |
[ValidateNotNull()] | |
[System.Management.Automation.PSCredential] | |
[System.Management.Automation.Credential()] | |
$MkeCred, | |
[Parameter(Mandatory = $true)] | |
[string] $MsrRegistryHost, | |
[Parameter(Mandatory = $false)] | |
[string] $MsrUsername = 'admin', | |
[Parameter(Mandatory = $true)] | |
[string] $MsrAccessToken, | |
[Parameter(Mandatory=$false)] | |
[string] $MkeMgmtModuleName = 'mke.mgmt' | |
) | |
function Invoke-WithTimeout { | |
param ( | |
[Parameter(Mandatory)] | |
[ScriptBlock]$ScriptBlock, | |
[int]$TimeoutSeconds = 30, | |
[int]$MaxRetries = 3, | |
[object[]]$ArgumentList = @() | |
) | |
for ($attempt = 1; $attempt -le $MaxRetries; $attempt++) { | |
Write-Verbose "Attempt [$attempt]: Starting background job with timeout $TimeoutSeconds seconds..." | |
$job = Start-Job -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList | |
if (Wait-Job -Job $job -Timeout $TimeoutSeconds) { | |
try { | |
$result = Receive-Job -Job $job -ErrorAction Stop | |
Remove-Job $job -Force | |
return $result | |
} | |
catch { | |
Write-Warning "Attempt [$attempt]: Job completed but returned an error: $_" | |
Remove-Job $job -Force | |
} | |
} else { | |
Write-Warning "Attempt [$attempt] timed out after $TimeoutSeconds seconds. Retrying..." | |
Stop-Job $job -Force | |
Remove-Job $job -Force | |
} | |
} | |
throw "Operation failed after [$MaxRetries] attempts due to timeouts or errors." | |
} | |
function Get-MsrRepo { | |
<# | |
.SYNOPSIS | |
Get repositories in a namespace | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $true)] | |
[string] $RegistryHost, | |
[Parameter(Mandatory = $false)] | |
[string] $Namespace, | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string] $UserName = 'admin', | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string] $AccessToken | |
) | |
begin { | |
} | |
process { | |
} | |
end { | |
$uri = "https://$($RegistryHost)/api/v0/repositories/$($Namespace)".TrimEnd('/') + "?pageSize=1000&count=false" | |
$headers = @{ | |
"accept" = "application/json" | |
"Content-Type" = "application/json" | |
"Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($AccessToken)")))" | |
} | |
$splat = @{ | |
'Uri' = $uri | |
'Headers' = $headers | |
'Method' = 'GET' | |
'UseBasicParsing' = $true | |
} | |
if ($PSEdition -eq 'Core') { | |
$splat.Add('SkipCertificateCheck', $true) | |
} | |
Try { | |
Write-Verbose "Obtaining MSR repos for namespace [$Namespace]..." | |
$response = Invoke-WebRequest @splat -ErrorAction Stop | |
if ($response.StatusCode -eq 200) { | |
if ($response.Headers.'Content-Type' -eq 'application/json') { | |
($response.content | ConvertFrom-Json).repositories | |
} | |
else { | |
$response.Content | |
} | |
} | |
else { | |
Write-Warning "MSR returned an unexpected response. Status Code: [$($response.StatusCode)] Status Description: [$($response.StatusDescription)]" | |
} | |
Write-Verbose "Obtaining MSR repos for namespace [$Namespace] complete." | |
} | |
Catch { | |
Write-Warning "Obtaining MSR repos failed." | |
Write-Warning "Reason: $($_)" | |
} | |
} | |
} | |
function Get-MsrRepoTeamAccess { | |
<# | |
.SYNOPSIS | |
Get teams granted access to an organization-owned repository. | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $true)] | |
[string] $RegistryHost, | |
[Parameter(Mandatory = $true)] | |
[string] $Namespace, | |
[Parameter(Mandatory = $false)] | |
[string] $RepoName, | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string] $UserName = 'admin', | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string] $AccessToken | |
) | |
begin { | |
} | |
process { | |
} | |
end { | |
$uri = "https://$($RegistryHost)/api/v0/repositories/$($Namespace)/$($RepoName)/teamAccess".TrimEnd('/') + "?pageSize=1000&count=false" | |
$headers = @{ | |
"accept" = "application/json" | |
"Content-Type" = "application/json" | |
"Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($AccessToken)")))" | |
} | |
$splat = @{ | |
'Uri' = $uri | |
'Headers' = $headers | |
'Method' = 'GET' | |
'UseBasicParsing' = $true | |
} | |
if ($PSEdition -eq 'Core') { | |
$splat.Add('SkipCertificateCheck', $true) | |
} | |
Try { | |
Write-Verbose "Obtaining MSR team access for repo [$RepoName] for namespace [$Namespace]..." | |
$response = Invoke-WebRequest @splat -ErrorAction Stop | |
if ($response.StatusCode -eq 200) { | |
if ($response.Headers.'Content-Type' -eq 'application/json') { | |
($response.content | ConvertFrom-Json).teamAccessList | |
} | |
else { | |
$response.Content | |
} | |
} | |
else { | |
Write-Warning "MSR returned an unexpected response. Status Code: [$($response.StatusCode)] Status Description: [$($response.StatusDescription)]" | |
} | |
Write-Verbose "Obtaining MSR team access for repo [$RepoName] for namespace [$Namespace] complete." | |
} | |
Catch { | |
Write-Warning "Obtaining MSR repo team access failed." | |
Write-Warning "Reason: $($_)" | |
} | |
} | |
} | |
function Get-MsrNamespaceTeamAccess { | |
<# | |
.SYNOPSIS | |
Get teams granted access to an organization-owned namespace of repositories. | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $true)] | |
[string] $RegistryHost, | |
[Parameter(Mandatory = $true)] | |
[string] $Namespace, | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string] $UserName = 'admin', | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string] $AccessToken | |
) | |
begin { | |
} | |
process { | |
} | |
end { | |
$uri = "https://$($RegistryHost)/api/v0/repositoryNamespaces/$($Namespace)/teamAccess".TrimEnd('/') + "?pageSize=1000&count=false" | |
$headers = @{ | |
"accept" = "application/json" | |
"Content-Type" = "application/json" | |
"Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($AccessToken)")))" | |
} | |
$splat = @{ | |
'Uri' = $uri | |
'Headers' = $headers | |
'Method' = 'GET' | |
'UseBasicParsing' = $true | |
} | |
if ($PSEdition -eq 'Core') { | |
$splat.Add('SkipCertificateCheck', $true) | |
} | |
Try { | |
Write-Verbose "Obtaining MSR team access for namespace [$Namespace]..." | |
$response = Invoke-WebRequest @splat -ErrorAction Stop | |
if ($response.StatusCode -eq 200) { | |
if ($response.Headers.'Content-Type' -eq 'application/json') { | |
($response.content | ConvertFrom-Json).teamAccessList | |
} | |
else { | |
$response.Content | |
} | |
} | |
else { | |
Write-Warning "MSR returned an unexpected response. Status Code: [$($response.StatusCode)] Status Description: [$($response.StatusDescription)]" | |
} | |
Write-Verbose "Obtaining MSR team access for namespace [$Namespace] complete." | |
} | |
Catch { | |
Write-Warning "Obtaining MSR repo team access failed." | |
Write-Warning "Reason: $($_)" | |
} | |
} | |
} | |
function Set-MsrNamespaceTeamAccess { | |
<# | |
.SYNOPSIS | |
Set team access to an organization-owned namespace of repositories. | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory = $true)] | |
[string] $RegistryHost, | |
[Parameter(Mandatory = $true)] | |
[string] $Namespace, | |
[Parameter(Mandatory = $true)] | |
[string] $TeamName, | |
[Parameter(Mandatory = $false)] | |
[ValidateSet('read-only','read-write','admin')] | |
[string] $AccessLevel = 'read-only', | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string] $UserName = 'admin', | |
[Parameter()] | |
[ValidateNotNullOrEmpty()] | |
[string] $AccessToken | |
) | |
begin { | |
} | |
process { | |
} | |
end { | |
$uri = "https://$($RegistryHost)/api/v0/repositoryNamespaces/$($Namespace)/teamAccess/$($TeamName)".TrimEnd('/') | |
$body = @{'accessLevel' = $AccessLevel} | ConvertTo-Json -Compress | |
$headers = @{ | |
"accept" = "application/json" | |
"Content-Type" = "application/json" | |
"Authorization" = "Basic $([Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("$($UserName):$($AccessToken)")))" | |
} | |
$splat = @{ | |
'Uri' = $uri | |
'Headers' = $headers | |
'Method' = 'PUT' | |
'Body' = $body | |
'UseBasicParsing' = $true | |
} | |
if ($PSEdition -eq 'Core') { | |
$splat.Add('SkipCertificateCheck', $true) | |
} | |
if ($PSVersionTable.PSVersion -ge [Version]"7.4") { | |
$splat.Add('ConnectionTimeoutSeconds', 60) | |
$splat.Add('OperationTimeoutSeconds', 120) | |
$splat.Add('HttpVersion', '1.1') | |
$splat.Add('DisableKeepAlive', $true) | |
$splat.Add('MaximumRetryCount', 5) | |
$splat.Add('RetryIntervalSec', 5) | |
} | |
Try { | |
Write-Verbose "Assigning MSR team [$TeamName] [$AccessLevel] access to repos in namespace [$Namespace]..." | |
$response = Invoke-WebRequest @splat -ErrorAction Stop | |
if ($response.StatusCode -eq 200) { | |
if ($response.Headers.'Content-Type' -eq 'application/json') { | |
$response.content | ConvertFrom-Json | |
} | |
else { | |
$response.Content | |
} | |
} | |
else { | |
Write-Warning "MSR returned an unexpected response. Status Code: [$($response.StatusCode)] Status Description: [$($response.StatusDescription)]" | |
} | |
Write-Verbose "Assigning MSR team [$TeamName] [$AccessLevel] access to repos in namespace [$Namespace] complete." | |
} | |
Catch { | |
Write-Warning "Assigning MSR team access failed." | |
Write-Warning "Reason: $($_)" | |
} | |
} | |
} | |
Try { | |
Import-Module -Name $MkeMgmtModuleName -ErrorAction Stop | |
} | |
Catch { | |
Write-Warning "Unable to import the MKE management module. Please ensure it is available on the system." | |
return | |
} | |
Write-Verbose "Establishing MKE session..." | |
New-MkeSession -url $MkeUrl -Credential $MkeCred -AllowInsecureTransfer | |
if ($null -eq (Get-MkeSession -Active)) { | |
Write-Warning "Unable to create an MKE session. Please check your credentials and URL." | |
return | |
} | |
Write-Verbose "Establishing MKE session complete." | |
Write-Verbose "Obtaining MKE organization list..." | |
$mkeOrgs = Get-MkeOrg | |
# $mkeOrgs = @(Get-MkeOrg -Name '000001') | |
# $mkeOrgs += Get-MkeOrg -Name '000002' | |
# $mkeOrgs += Get-MkeOrg -Name '000003' | |
if ($null -eq $mkeOrgs) { | |
Write-Warning "Unable to obtain MKE organization list." | |
return | |
} | |
Write-Verbose "Obtaining MKE organization list complete. Total Orgs: [$($mkeOrgs.Count)]" | |
[string] $setMsrNamespaceTeamAccessDef = (Get-Command Set-MsrNamespaceTeamAccess).ScriptBlock.ToString() | |
# Looking for the Orgs with a numeric name as that is the convention we used for scale testing | |
$mkeOrgs = $mkeOrgs | Where-Object { $_.name -match '^\d+$' } | |
foreach ($mkeOrg in $mkeOrgs) { | |
Write-Verbose "Getting teams for MKE org [$($mkeOrg.name)]..." | |
$teams = @( | |
Invoke-WithTimeout -TimeoutSeconds 30 -MaxRetries 3 -ScriptBlock { | |
param ( | |
[string] $MkeMgmtModuleName, | |
[object] $MkeSession, | |
[string] $MkeOrgName | |
) | |
$VerbosePreference = $using:VerbosePreference | |
Try { | |
Import-Module -Name $MkeMgmtModuleName -Verbose:$false -ErrorAction Stop | |
} | |
Catch { | |
Write-Warning "Unable to import the MKE management module. Please ensure it is available on the system." | |
return | |
} | |
Set-MkeSession -Session $MkeSession | |
Get-MkeTeam -OrgName $MkeOrgName | |
} -ArgumentList $MkeMgmtModuleName, (Get-MkeSession -Active), $mkeOrg.name | |
) | |
Write-Verbose "Getting teams for MKE org [$($mkeOrg.name)] complete." | |
if ($teams.Count -gt 0) { | |
$teams | ForEach-Object -Parallel { | |
$VerbosePreference = $using:VerbosePreference | |
$accessLevel = switch (($_.name).Split('-')[1]) { | |
'testers' { 'read-only' } | |
'developers' { 'read-write' } | |
'operators' { 'admin' } | |
default { 'read-only' } | |
} | |
# Bring the script-scope defined function into the runspace scope | |
$SetMsrNamespaceTeamAccessFunc = [scriptblock]::Create($using:setMsrNamespaceTeamAccessDef) | |
$splat = @{ | |
'RegistryHost' = $using:MsrRegistryHost | |
'Namespace' = $using:mkeOrg.name | |
'TeamName' = $_.name | |
'AccessLevel' = $accessLevel | |
'UserName' = $using:MsrUsername | |
'AccessToken' = $using:MsrAccessToken | |
} | |
& $SetMsrNamespaceTeamAccessFunc @splat | |
} -ThrottleLimit $teams.count -TimeoutSeconds 120 -Verbose:($VerbosePreference -eq 'Continue') | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment