Blog
GitHub Billing - Lassen Sie die Administratoren von Organisationen und Repositories die Kostenstelle in GitHub festlegen

Wir verlassen uns auf GitHub-Kostenstellen, um die GitHub-Rechnung auf die verschiedenen Einheiten unseres Unternehmens aufzuteilen. Welche Ressource an welche Kostenstelle gebunden ist, wird in den GitHub Enterprise-Einstellungen verwaltet und kann derzeit nur von einem Benutzer mit den Berechtigungen Enterprise Owner oder Billing Administrator vorgenommen werden.
Ich habe nach einer Möglichkeit gesucht, Organisationsinhabern und Repository-Administratoren die Möglichkeit zu geben, die Kostenstelle für ein Repository selbst festzulegen, ohne einen dieser hoch privilegierten Accounts einbeziehen zu müssen. Wir haben eine Lösung gefunden, die GitHub Actions und Repository Custom Properties verwendet.
Seit meinem letzten Blogbeitrag über Kostenstellen wurden von GitHub einige kleine Verbesserungen veröffentlicht:
Es gibt jetzt eine API, um ein Repository mit einer Kostenstelle zu verknüpfen.
Es ist jetzt möglich, ein Repository einer anderen Kostenstelle zuzuordnen als der Organisation, die es besitzt.
Mit diesen beiden Verbesserungen können wir dieses Problem lösen.
Erstellen der benutzerdefinierten Eigenschaft
GitHub verfügt über eine Funktion namens Benutzerdefinierte Eigenschaften für Repositories, mit der Sie beliebige Daten mit einem Repo verknüpfen können. Sie können diese Eigenschaften auf der Ebene des Unternehmens, der Organisation und des Repositorys definieren.
Da unsere Kostenstellen auf der Unternehmensebene definiert sind, habe ich mich entschieden, diese benutzerdefinierte Eigenschaft auf der gleichen Ebene zu erstellen. Um eine neue benutzerdefinierte Eigenschaft hinzuzufügen, navigieren Sie zu Ihrem Unternehmen, wählen Sie die Registerkarte Richtlinien und gehen Sie dann auf Repository und dann auf Benutzerdefinierte Eigenschaften. Wählen Sie Neue Eigenschaft, um Ihre neue Eigenschaft zu erstellen:

Navigieren Sie zum Abschnitt Benutzerdefinierte Eigenschaften und klicken Sie auf Neue Eigenschaft
Geben Sie der Eigenschaft einen eindeutigen Namen und eine Beschreibung. Da unsere Liste der Kostenstellen klar definiert ist, habe ich den Typ Einfachauswahl gewählt, damit unsere Benutzer eine Kostenstelle aus einer vordefinierten Liste auswählen können.

Geben Sie der Eigenschaft einen eindeutigen Namen und setzen Sie den Typ auf single select
Als Nächstes fügen Sie die Liste der Kostenstellen hinzu, die Sie als Optionen zur Verfügung stellen möchten. Ich habe auch eine Option Vererben hinzugefügt, die ich später erläutern werde:

Fügen Sie die gewünschten Kostenstellenoptionen hinzu.
Und schließlich machen Sie das Feld erforderlich und setzen den Standardwert auf vererben.

Legen Sie die Berechtigung fest, damit Benutzer mit der Berechtigung für Eigenschaften dieses Feld bearbeiten können, machen Sie es erforderlich und legen Sie den Standardwert fest.
Wenn Sie jetzt ein neues Repository erstellen oder zu den Einstellungen eines bestehenden Repositorys navigieren, können Sie das benutzerdefinierte Feld für die Kostenstelle festlegen:

Benutzer müssen eine Kostenstelle auswählen, wenn sie ein neues Repository erstellen.
Und in bestehenden Repositories wird die benutzerdefinierte Eigenschaft nun in den Repository-Einstellungen angezeigt:

Der Grund für Inherit
Im obigen Beispiel haben wir einen speziellen Wert inherit (vererben) hinzugefügt. Wenn dieser Wert gesetzt ist, legen wir für das Repository keine Kostenstelle fest und dies bewirkt, dass alle von diesem Repository erzeugten Kosten automatisch an die Kostenstelle fließen, die mit der Organisation verbunden ist. Wenn die Organisation keiner Kostenstelle zugeordnet ist, fließen die Kosten an das Unternehmen.
Mit GitHub Actions alles zum Laufen bringen
Das Erstellen der benutzerdefinierten Eigenschaft selbst bewirkt noch nichts. Um das Repository tatsächlich mit der Kostenstelle zu verknüpfen, müssen wir noch die Konfiguration im Abschnitt GitHub Enterprise Billing & Licensing aktualisieren:

Rechnungsadministratoren können Organisationen, Repositories und Benutzer mit Kostenstellen verknüpfen.
Ohne zusätzliche Automatisierung muss jedes Repository manuell zu einer Kostenstelle hinzugefügt werden, indem die Kostenstelle bearbeitet und das Repository hinzugefügt wird. Dies ist ein langwieriger manueller Prozess.
Stattdessen habe ich zur Aktualisierung der Kostenstellenkonfiguration einen GitHub Actions-Workflow erstellt, der einmal am Tag ausgeführt wird und die Kostenstellenkonfiguration mit den Werten der benutzerdefinierten Eigenschaften vergleicht und bei Bedarf Aktualisierungen der Kostenstellenkonfiguration vornimmt.
Der grundlegende Arbeitsablauf ist recht einfach:
- Abfrage der aktuellen Kostenstellen-Konfiguration
- Abfrage aller Organisationen in unserem GitHub Unternehmen
- Abfrage aller Repositories in jeder GitHub Organisation
- Abfrage der benutzerdefinierten Eigenschaften für jedes Repository
- Aktualisieren Sie die Konfiguration der Kostenstelle, falls sie nicht mit der benutzerdefinierten Eigenschaft übereinstimmt.
Der Arbeitsablauf verlässt sich auf die GitHub CLI, um die Daten abzufragen.
$costCenters = (invoke-gh -fromJson -- api /enterprises/$enterprise/settings/billing/cost-centers).costCenters
$orgs = get-allorganizations $enterprise | % { $_.login }
update-repocostcenters
Die Funktion update-repocostcenters
prüft, welche Aktualisierungen erforderlich sind:
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
}
}
}
}
}
Leider können Sie die zugehörige Kostenstelle nicht einfach mit einem einzigen Aufruf aktualisieren. Wenn Sie den aktuellen Wert ändern, ist ein Hinzufügen plus Löschen erforderlich.
Ich habe die Funktion Update-CostCenterResources in einem früheren Blogpost vorgestellt. GitHub hat vor kurzem die Möglichkeit hinzugefügt, Organisationen und Repositories festzulegen. Hier ist also die aktualisierte Version:
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)
}
All dies läuft über einen einfachen GitHub-Workflow, der Zugriff auf ein persönliches Zugriffstoken mit den erforderlichen Berechtigungen für den Zugriff auf die Kostenstelleninformationen sowie ausreichende Berechtigungen für die Abfrage aller Repositories in allen Organisationen des Unternehmens hat.
name: Set Costcenters
on:
schedule:
- cron: "5 4 * * *"
jobs:
update:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
with:
fetch-depth: 0
- name: Assign Costcenters
run: |
.\assign-costcenters.ps1
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
shell: pwsh
Das gesamte Skript finden Sie hier: https://gist.github.com/jessehouwing/75e89531e915033afbb66cda3fd09b38?ref=jessehouwing.net
Verfasst von

Jesse Houwing
Jesse is a passionate trainer and coach, helping teams improve their productivity and quality all while trying to keep work fun. He is a Professional Scrum Trainer (PST) through Scrum.org, Microsoft Certified Trainer and GitHub Accredited Trainer. Jesse regularly blogs and you'll find him on StackOverflow, he has received the Microsoft Community Contributor Award three years in a row and has been awarded the Microsoft Most Valuable Professional award since 2015. He loves espresso and dark chocolate, travels a lot and takes photos everywhere he goes.
Unsere Ideen
Weitere Blogs
Contact