Skip to content

Instantly share code, notes, and snippets.

@jdschleicher
Last active April 9, 2024 12:10
Show Gist options
  • Save jdschleicher/e8aeec2f6092ecfc098f715a5b0aa2b8 to your computer and use it in GitHub Desktop.
Save jdschleicher/e8aeec2f6092ecfc098f715a5b0aa2b8 to your computer and use it in GitHub Desktop.
POWERSHELL SETUP AND SCRIPT FOR BRANCH PROTECTIONS AS CODE IN GITHUB REPOSITORIES
<#
PREREQUISITES:
1. GITHUB CLI IS INSTALLED IN YOUR MACHINE (https://cli.github.com/)
2. GITHUB CLI IS AUTHENTICATED (https://cli.github.com/manual/gh_auth_login) USING THE WEB BROWSER LOGIN IS REALLY SIMPLE IF ALREADY LOGGED IN BROWSER
3. KNOW YOUR GITHUB USERNAME OR GITHUB ORGANIZATION NAME
4. IF USING BYPASS ADMIN TEAM IN EXAMPLE BELOW, GET TEAM SLUG BY GOING TO TEAM AND GRABBING SLUG NAME FROM URL
DOCUMENTATION FOR CREATING BRANCH PROTECTOIN WITH GRAPH QL: https://docs.github.com/en/graphql/reference/mutations#createbranchprotectionrule
IF NEEDING TO VALIDATE "Branch Protections as Code" GROUNDWORK IS HERE: https://stackoverflow.com/questions/76384359/github-graphql-api-branch-protection-rule-how-do-i-get-require-a-pull-reque/76422520#76422520
Where the dollar-sign character isn't ignored with a backtick, this will be a dynamic powershell variable that will be
updated as the repository and branch protections are looped and iterated over
#>
function get_expected_branch_protection_pattern_to_json_configurations {
$branch_protection_configurations = @{
"feature*" = @"
{
"query": "mutation (`$input: CreateBranchProtectionRuleInput!) { createBranchProtectionRule(input: `$input) { branchProtectionRule { id } } }",
"variables": {
"input": {
"repositoryId": "$repository_id",
"pattern": "$pattern",
"requiresApprovingReviews": true,
"dismissesStaleReviews": true,
"requiredApprovingReviewCount": 2
}
}
}
"@
"master" = @"
{
"query": "mutation (`$input: CreateBranchProtectionRuleInput!) { createBranchProtectionRule(input: `$input) { branchProtectionRule { id } } }",
"variables": {
"input": {
"repositoryId" : "$repository_id",
"pattern" : "$pattern",
"requiredStatusCheckContexts" : ["pull-request-job-that-runs-on-opening-pull-request"],
"requiresApprovingReviews" : true,
"requiredApprovingReviewCount" : 1,
"allowsForcePushes" : false,
"dismissesStaleReviews" : true,
"requiresStatusChecks" : true,
"bypassPullRequestActorIds" : [ "$expected_team_admin_id" ]
}
}
}
"@
}
$branch_protection_configurations
}
function get_expected_github_team_slug_id_by_team_name {
param(
[Parameter(Mandatory=$true)]
$github_team_slug,
[Parameter(Mandatory=$true)]
$github_organization
)
$gh_team_slug_response_json = gh api `
-H "Accept: application/vnd.github+json" `
-H "X-GitHub-Api-Version: 2022-11-28" `
/orgs/$github_organization/teams/$github_team_slug
$gh_team_slug_response = $gh_team_slug_response_json | ConvertFrom-Json -Depth 12
$gh_team_slug_response.node_id
}
$expected_github_organization = "ENTERPRISE_ORGANIZATION_OR_GITHUB_USERNAME"
### NEEDS TO BE IN STRUCTURE GITHUB_ORGANIZATION/REPOSITORY
$customer_repositories = @(
"$expected_github_organization/test-repo-1",
"$expected_github_organization/test-repo-2",
"$expected_github_organization/test-repo-3"
)
### IF NOT USING TEAM SLUG REMOVE FROM ABOVE JSON
$expected_github_slug_name = "EXPECTED_GITHUB_TEAM_SLUG_WOULDNT_WORK_ON_PERSONAL_REPOSITORIES"
$expected_team_admin_id = get_expected_github_team_slug_id_by_team_name -github_team_slug $expected_github_slug_name -github_organization $expected_github_organization
$branch_protection_configurations = get_expected_branch_protection_pattern_to_json_configurations
foreach ( $full_repository_name in $customer_repositories ) {
Write-Host "REPOSITORY TO PROCESS: $full_repository_name"
$github_repository = $full_repository_name.split("/")[1]
$repository_id = $( gh api graphql -f github_organization="$expected_github_organization" -F name="$github_repository" -f query='
query($name: String!, $github_organization: String!) {
repository(owner: $github_organization, name: $name) {
id
}
}
' -q .data.repository.id)
Write-Host "REPOSITORY ID : $repository_id"
$pattern = $null
foreach ( $branch_protection_key in $branch_protection_configurations.Keys ) {
$pattern = $branch_protection_key
Write-Host "BRANCH PROTECTION TO PROCESS: $branch_protection_key"
$branch_protection_mutation_json = $branch_protection_configurations[$branch_protection_key]
$branch_protection_mutation = $branch_protection_mutation_json | ConvertFrom-Json -Depth 10
$branch_protection_mutation.variables.input.pattern = $pattern
$branch_protection_mutation.variables.input.repositoryId = $repository_id
### ENSURE DEPTH IS SET HIGH SO ANY ARRAYS ARE CONVERTED CORRECTLY FROM POWERSHELL;
### THERE COULD BE ONLY 1 EXPECTED VALUE FOR bypassPullRequestActorIds SO PIPING WILL IMPLICITLY CONVERT THAT ARRAY WITH 1 ITEM INTO A STRING VALUE
$updated_branch_protection_mutation_json = ConvertTo-Json -Depth 10 $branch_protection_mutation
$json_branch_configurations_file = "branch_configurations.json"
if (Test-Path $json_branch_configurations_file ) {
Remove-Item -Force $json_branch_configurations_file
}
New-Item $json_branch_configurations_file -Value $updated_branch_protection_mutation_json
gh api graphql --input $json_branch_configurations_file
### UNCOMMENT IF YOU WANT TO IMMEDIATELY DELETE THE FILE FOLLOWING GH API CALL
# Remove-Item -Force $json_branch_configurations_file
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment