-
-
Save jessehouwing/75e89531e915033afbb66cda3fd09b38 to your computer and use it in GitHub Desktop.
Update Repository Cost Centers based on Custom Properties
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
| $ErrorActionPreference = 'Stop' | |
| function Invoke-Gh { | |
| <# | |
| .Synopsis | |
| Wrapper function that deals with Powershell's peculiar error output when gh uses the error stream. | |
| .Example | |
| Invoke-Git ThrowError | |
| $LASTEXITCODE | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory = $false)] | |
| [switch]$slurp, | |
| [Parameter(Mandatory = $false)] | |
| [switch]$fromJson, | |
| [parameter(ValueFromRemainingArguments = $true)] | |
| [string[]]$Arguments | |
| ) | |
| $output = & { | |
| [CmdletBinding()] | |
| param( | |
| [parameter(ValueFromRemainingArguments = $true)] | |
| [string[]]$InnerArgs | |
| ) | |
| $sleep = 0 | |
| do { | |
| if ($sleep -gt 0) { | |
| Start-Sleep -Seconds $sleep | |
| } | |
| $allOutput = & gh @InnerArgs 2>&1 | |
| $stderr = $allOutput | ? { $_ -is [System.Management.Automation.ErrorRecord] } | |
| $output = $allOutput | ? { $_ -isnot [System.Management.Automation.ErrorRecord] } | |
| $sleep += 5 | |
| } | |
| while ( | |
| $stderr -like "*GraphQL: was submitted too quickly*" -or | |
| $stderr -like "*API rate limit exceeded*" | |
| ) | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "Error executing gh command: $InnerArgs `n" + $stderr | |
| } | |
| if ($slurp) { | |
| $output = $output | & jq -s | |
| } | |
| if ($fromJson) { | |
| $output = $output | ConvertFrom-Json | |
| } | |
| return $output | |
| } -ErrorAction SilentlyContinue -ErrorVariable fail -InnerArgs:$Arguments | |
| if ($fail) { | |
| throw $fail.Exception | |
| } | |
| return $output | |
| } | |
| function Update-CostCenterResources { | |
| param( | |
| [Parameter(Mandatory = $true)] | |
| [string[]]$Handles, | |
| [ValidateSet('User', 'Repo', 'Org')] | |
| [string]$ResourceType = "User", | |
| [Parameter(Mandatory = $true)] | |
| [ValidateSet('Add', 'Delete')] | |
| [string]$Action, | |
| [Parameter(Mandatory = $true)] | |
| $CostCenter, | |
| [Parameter(Mandatory = $true)] | |
| [string]$Enterprise | |
| ) | |
| switch ($Action) { | |
| 'Add' { | |
| $method = 'POST' | |
| $Handles = $Handles | Where-Object { | |
| $handle = $_ | |
| return (($costCenter.resources | ? { $_.type -eq $ResourceType } | ? { $_.name -eq $handle }).Count -eq 0) | |
| } | |
| } | |
| 'Delete' { | |
| $method = 'DELETE' | |
| $Handles = $Handles | Where-Object { | |
| $handle = $_ | |
| return (($costCenter.resources | ? { $_.type -eq $ResourceType } | ? { $_.name -eq $handle }).Count -gt 0) | |
| } | |
| } | |
| } | |
| # Call fails when processing too many users at once. Thus batching the calls... | |
| $count = 0 | |
| do { | |
| $batch = $Handles | Select-Object -Skip $count -First 30 | |
| $count += $batch.Count | |
| if ($batch.Count -gt 0) { | |
| switch ($ResourceType) { | |
| 'User' { | |
| $body = @{ | |
| users = [string[]]$batch | |
| } | |
| } | |
| 'Org' { | |
| $body = @{ | |
| organizations = [string[]]$batch | |
| } | |
| } | |
| 'Repo' { | |
| $body = @{ | |
| repositories = [string[]]$batch | |
| } | |
| } | |
| } | |
| $_ = ($body | ConvertTo-Json) | gh api --method $method /enterprises/$Enterprise/settings/billing/cost-centers/$($CostCenter.id)/resource --input - | |
| } | |
| } while ($batch.Count -gt 0) | |
| } | |
| function get-allorganizations { | |
| param( | |
| [string]$enterprise | |
| ) | |
| return (invoke-gh -slurp -- api graphql --paginate -F enterprise=$enterprise -f query='query($endCursor: String, $enterprise: String!) { | |
| enterprise(slug: $enterprise) { | |
| id, | |
| organizations(first: 100, after: $endCursor) { | |
| nodes | |
| { | |
| login, | |
| name | |
| }, | |
| pageInfo { | |
| hasNextPage | |
| endCursor | |
| } | |
| } | |
| } | |
| }' --jq '.data.enterprise.organizations.nodes' | ConvertFrom-Json) | |
| } | |
| function update-repocostcenters { | |
| Write-Output "Updating Costcenters for repositories in $enterprise." | |
| foreach ($org in $orgs) { | |
| Write-Output "Processing organization $org" | |
| $repos = invoke-gh -slurp api /orgs/$org/repos --paginate --jq '.[] | { name: .name, full_name: .full_name }' | ConvertFrom-Json | |
| foreach ($repo in $repos) { | |
| $customProperties = invoke-gh api /repos/$($repo.full_name)/properties/values --jq '.' | ConvertFrom-Json | |
| $repoCostCenterProperty = $customProperties | Where-Object { $_.property_name -eq "cost-center" } | Select-Object -ExpandProperty value -ErrorAction Ignore | |
| $repo | Add-Member -NotePropertyName cost_center -NotePropertyValue $repoCostCenterProperty -Force | |
| $currentCostCenter = $costCenters | ? { $_.resources | ? { $_.type -eq "Repo" -and $_.name -eq $repo.full_name } } | |
| if ( $repo.cost_center -eq "inherit" ) { | |
| $repo.cost_center = $null | |
| } | |
| $targetCostCenter = $null | |
| if ($repo.cost_center) { | |
| $targetCostCenter = $costCenters | ? { $_.name -eq $repo.cost_center } | |
| if (-not $targetCostCenter) { | |
| Write-Warning "Costcenter for repository $($repo.full_name) not found." | |
| } | |
| } | |
| if ($null -eq $currentCostCenter) { | |
| if ($null -ne $targetCostCenter) { | |
| Write-Output "Updating costcenter for repository $($repo.full_name) from unassigned to $($targetCostCenter.name)." | |
| Update-CostCenterResources -Handles $repo.full_name -ResourceType "Repo" -Action "Add" -CostCenter $targetCostCenter -Enterprise $enterprise | |
| } | |
| else { | |
| Write-Verbose "Repository $($repo.full_name) does not have a cost center assigned." | |
| } | |
| } | |
| else { | |
| if ($null -eq $targetCostCenter) { | |
| Write-Verbose "Repository $($repo.full_name) does not have a cost center assigned." | |
| Write-Output "Updating costcenter for repository $($repo.full_name) from $($currentCostCenter.name) to unassigned." | |
| Update-CostCenterResources -Handles $repo.full_name -ResourceType "Repo" -Action "Delete" -CostCenter $currentCostCenter -Enterprise $enterprise | |
| } | |
| elseif ($currentCostCenter.id -ne $targetCostCenter.id) { | |
| Write-Output "Updating costcenter for repository $($repo.full_name) from $($currentCostCenter.name) to $($targetCostCenter.name)." | |
| Update-CostCenterResources -Handles $repo.full_name -ResourceType "Repo" -Action "Delete" -CostCenter $currentCostCenter -Enterprise $enterprise | |
| Update-CostCenterResources -Handles $repo.full_name -ResourceType "Repo" -Action "Add" -CostCenter $targetCostCenter -Enterprise $enterprise | |
| } | |
| } | |
| } | |
| } | |
| } | |
| $enterprise = "xebia" | |
| $orgs = get-allorganizations $enterprise | % { $_.login } | |
| $costCenters = (invoke-gh -fromJson -- api /enterprises/$enterprise/settings/billing/cost-centers).costCenters | |
| update-repocostcenters |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment