Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add script to reconcile branch protection #204

Closed

Conversation

BobyMCbobs
Copy link
Contributor

@BobyMCbobs BobyMCbobs commented Oct 6, 2023

pass through the output of ./hack/list-checks.sh to ./hack/set-checks.sh and create or update the branch protection rule for the main branch.

Long term, tools like Prow's branchprotector
https://docs.prow.k8s.io/docs/components/optional/branchprotector/
would be better, since it's more robust and scaled to fit many communities.

@BobyMCbobs BobyMCbobs force-pushed the add-script-to-reconcile-branch-protection-checks branch from b1106f6 to d1eb732 Compare October 6, 2023 00:24
@BobyMCbobs BobyMCbobs requested a review from a team October 6, 2023 00:25
@BobyMCbobs
Copy link
Contributor Author

tested on my experimental org. Only works with orgs and doesn't work with user repos

Demonstrating usage

setting rules

🐚 ./hack/list-checks.sh thisisnottheorganisationyourelookingfor thecoolthing | ./hack/set-checks.sh apply-and-agree-to-risk
thisisnottheorganisationyourelookingfor/thecoolthing : ["hi"]
{
  "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection",
  "required_status_checks": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_status_checks",
    "strict": true,
    "contexts": [
      "hi"
    ],
    "contexts_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_status_checks/contexts",
    "checks": [
      {
        "context": "hi",
        "app_id": 15368
      }
    ]
  },
  "restrictions": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions",
    "users_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/users",
    "teams_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/teams",
    "apps_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/apps",
    "users": [],
    "teams": [],
    "apps": []
  },
  "required_signatures": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_signatures",
    "enabled": false
  },
  "enforce_admins": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/enforce_admins",
    "enabled": false
  },
  "required_linear_history": {
    "enabled": false
  },
  "allow_force_pushes": {
    "enabled": false
  },
  "allow_deletions": {
    "enabled": false
  },
  "block_creations": {
    "enabled": false
  },
  "required_conversation_resolution": {
    "enabled": false
  },
  "lock_branch": {
    "enabled": false
  },
  "allow_fork_syncing": {
    "enabled": false
  }
}
Config difference:
{								{
  "url": "https://api.github.com/repos/thisisnottheorganisati |	  "url": null,
  "required_status_checks": {					  "required_status_checks": {
    "url": "https://api.github.com/repos/thisisnottheorganisa <
    "strict": true,						    "strict": true,
    "contexts": [					      <
      "hi"						      <
    ],							      <
    "contexts_url": "https://api.github.com/repos/thisisnotth <
    "checks": [							    "checks": [
      {								      {
        "context": "hi",					        "context": "hi",
        "app_id": 15368						        "app_id": 15368
      }								      }
    ]								    ]
  },								  },
  "restrictions": {						  "restrictions": {
    "url": "https://api.github.com/repos/thisisnottheorganisa <
    "users_url": "https://api.github.com/repos/thisisnottheor <
    "teams_url": "https://api.github.com/repos/thisisnottheor <
    "apps_url": "https://api.github.com/repos/thisisnottheorg <
    "users": [],						    "users": [],
    "teams": [],						    "teams": [],
    "apps": []							    "apps": []
  },								  },
  "required_signatures": {				      |	  "required_signatures": null,
    "url": "https://api.github.com/repos/thisisnottheorganisa |	  "enforce_admins": null,
    "enabled": false					      |	  "required_linear_history": null,
  },							      |	  "allow_force_pushes": null,
  "enforce_admins": {					      |	  "allow_deletions": null,
    "url": "https://api.github.com/repos/thisisnottheorganisa |	  "block_creations": null,
    "enabled": false					      |	  "required_conversation_resolution": null,
  },							      |	  "lock_branch": null,
  "required_linear_history": {				      |	  "allow_fork_syncing": null,
    "enabled": false					      |	  "required_pull_request_reviews": null
  },							      <
  "allow_force_pushes": {				      <
    "enabled": false					      <
  },							      <
  "allow_deletions": {					      <
    "enabled": false					      <
  },							      <
  "block_creations": {					      <
    "enabled": false					      <
  },							      <
  "required_conversation_resolution": {			      <
    "enabled": false					      <
  },							      <
  "lock_branch": {					      <
    "enabled": false					      <
  },							      <
  "allow_fork_syncing": {				      <
    "enabled": false					      <
  }							      <
}								}
Updating branch protection for thisisnottheorganisationyourelookingfor/thecoolthing on branch main
{
  "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection",
  "required_status_checks": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_status_checks",
    "strict": true,
    "contexts": [
      "hi"
    ],
    "contexts_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_status_checks/contexts",
    "checks": [
      {
        "context": "hi",
        "app_id": 15368
      }
    ]
  },
  "restrictions": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions",
    "users_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/users",
    "teams_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/teams",
    "apps_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/apps",
    "users": [

    ],
    "teams": [

    ],
    "apps": [

    ]
  },
  "required_signatures": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_signatures",
    "enabled": false
  },
  "enforce_admins": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/enforce_admins",
    "enabled": false
  },
  "required_linear_history": {
    "enabled": false
  },
  "allow_force_pushes": {
    "enabled": false
  },
  "allow_deletions": {
    "enabled": false
  },
  "block_creations": {
    "enabled": false
  },
  "required_conversation_resolution": {
    "enabled": false
  },
  "lock_branch": {
    "enabled": false
  },
  "allow_fork_syncing": {
    "enabled": false
  }
}

empting rules

🐚 echo 'thisisnottheorganisationyourelookingfor/thecoolthing: []' | ./hack/set-checks.sh apply-and-agree-to-risk
thisisnottheorganisationyourelookingfor/thecoolthing : []
{
  "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection",
  "required_status_checks": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_status_checks",
    "strict": true,
    "contexts": [
      "hi"
    ],
    "contexts_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_status_checks/contexts",
    "checks": [
      {
        "context": "hi",
        "app_id": 15368
      }
    ]
  },
  "restrictions": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions",
    "users_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/users",
    "teams_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/teams",
    "apps_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/apps",
    "users": [],
    "teams": [],
    "apps": []
  },
  "required_signatures": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_signatures",
    "enabled": false
  },
  "enforce_admins": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/enforce_admins",
    "enabled": false
  },
  "required_linear_history": {
    "enabled": false
  },
  "allow_force_pushes": {
    "enabled": false
  },
  "allow_deletions": {
    "enabled": false
  },
  "block_creations": {
    "enabled": false
  },
  "required_conversation_resolution": {
    "enabled": false
  },
  "lock_branch": {
    "enabled": false
  },
  "allow_fork_syncing": {
    "enabled": false
  }
}
Config difference:
{								{
  "url": "https://api.github.com/repos/thisisnottheorganisati |	  "url": null,
  "required_status_checks": {					  "required_status_checks": {
    "url": "https://api.github.com/repos/thisisnottheorganisa <
    "strict": true,						    "strict": true,
    "contexts": [					      |	    "checks": []
      "hi"						      <
    ],							      <
    "contexts_url": "https://api.github.com/repos/thisisnotth <
    "checks": [						      <
      {							      <
        "context": "hi",				      <
        "app_id": 15368					      <
      }							      <
    ]							      <
  },								  },
  "restrictions": {						  "restrictions": {
    "url": "https://api.github.com/repos/thisisnottheorganisa <
    "users_url": "https://api.github.com/repos/thisisnottheor <
    "teams_url": "https://api.github.com/repos/thisisnottheor <
    "apps_url": "https://api.github.com/repos/thisisnottheorg <
    "users": [],						    "users": [],
    "teams": [],						    "teams": [],
    "apps": []							    "apps": []
  },								  },
  "required_signatures": {				      |	  "required_signatures": null,
    "url": "https://api.github.com/repos/thisisnottheorganisa |	  "enforce_admins": null,
    "enabled": false					      |	  "required_linear_history": null,
  },							      |	  "allow_force_pushes": null,
  "enforce_admins": {					      |	  "allow_deletions": null,
    "url": "https://api.github.com/repos/thisisnottheorganisa |	  "block_creations": null,
    "enabled": false					      |	  "required_conversation_resolution": null,
  },							      |	  "lock_branch": null,
  "required_linear_history": {				      |	  "allow_fork_syncing": null,
    "enabled": false					      |	  "required_pull_request_reviews": null
  },							      <
  "allow_force_pushes": {				      <
    "enabled": false					      <
  },							      <
  "allow_deletions": {					      <
    "enabled": false					      <
  },							      <
  "block_creations": {					      <
    "enabled": false					      <
  },							      <
  "required_conversation_resolution": {			      <
    "enabled": false					      <
  },							      <
  "lock_branch": {					      <
    "enabled": false					      <
  },							      <
  "allow_fork_syncing": {				      <
    "enabled": false					      <
  }							      <
}								}
Updating branch protection for thisisnottheorganisationyourelookingfor/thecoolthing on branch main
{
  "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection",
  "required_status_checks": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_status_checks",
    "strict": true,
    "contexts": [

    ],
    "contexts_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_status_checks/contexts",
    "checks": [

    ]
  },
  "restrictions": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions",
    "users_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/users",
    "teams_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/teams",
    "apps_url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/restrictions/apps",
    "users": [

    ],
    "teams": [

    ],
    "apps": [

    ]
  },
  "required_signatures": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/required_signatures",
    "enabled": false
  },
  "enforce_admins": {
    "url": "https://api.github.com/repos/thisisnottheorganisationyourelookingfor/thecoolthing/branches/main/protection/enforce_admins",
    "enabled": false
  },
  "required_linear_history": {
    "enabled": false
  },
  "allow_force_pushes": {
    "enabled": false
  },
  "allow_deletions": {
    "enabled": false
  },
  "block_creations": {
    "enabled": false
  },
  "required_conversation_resolution": {
    "enabled": false
  },
  "lock_branch": {
    "enabled": false
  },
  "allow_fork_syncing": {
    "enabled": false
  }
}

@BobyMCbobs BobyMCbobs force-pushed the add-script-to-reconcile-branch-protection-checks branch 3 times, most recently from ff61e56 to ea7eddf Compare October 8, 2023 21:53
pass through the output of ./hack/list-checks.sh to ./hack/set-checks.sh
and create or update the branch protection rule for the main branch
@BobyMCbobs BobyMCbobs force-pushed the add-script-to-reconcile-branch-protection-checks branch from ea7eddf to 918fb8e Compare October 9, 2023 22:53
@BobyMCbobs
Copy link
Contributor Author

updates in 918fb8e include

  • add comments
  • add retry for other PRs where there are checks
  • update PR list to be only from merged and remove dependabot submissions
  • update commit in PR to be the most recent instead of the first

@BobyMCbobs
Copy link
Contributor Author

BobyMCbobs commented Oct 9, 2023

for testing against the repos, with this PR still open to see passing checks I'd recommend selecting two or three repos to see if it reflects the protection rules as expected

  1. validate with ./hack/list-checks.sh Actions base-images | ./hack/set-checks.sh
  2. apply with ./hack/list-checks.sh Actions base-images | ./hack/set-checks.sh apply-and-agree-to-risk
  3. check the protection pages (e.g: https://github.com/GeoNet/Actions/settings/branches under main)
  4. check this PR to see all passing checks have the (required) badge

to rollback, run echo -e 'GeoNet/Actions: []\nGeoNet/base-images: []' | ./hack/set-checks.sh

to completely apply against all repos run

# write output to inspect
OUTPUT="$(mktemp)"
./hack/list-checks.sh | tee "$OUTPUT"
# inspect
less "$OUTPUT"
# apply all the values
< "$OUTPUT" ./hack/set-checks.sh apply-and-agree-to-risk
./hack/list-checks.sh | ./hack/set-checks.sh apply-and-agree-to-risk

note that this will take at least 5mins to discovery all the checks from all the repos before applying them.

complete rollback

repos=($(gh api "orgs/$ORG/repos" --jq '.[] | select(.fork==false) | select(.archived==false) | .name' --paginate \
    | sort \
    | tr ' ' '\n' \
    | xargs -I{} \
      sh -c "gh api \"repos/$ORG/{}/contents/.github/workflows\" --jq \". | length | . > 0\" 2>&1>/dev/null && echo $ORG/{}" \
      | grep -E "^$ORG/.*" | cat))
echo "${repos[@]}" | sed 's/\([ ]\|$\)/: []\n/g' | ./hack/set-checks.sh

cc @quiffman @ardrigh

@BobyMCbobs
Copy link
Contributor Author

set-checks likely won't be able to patch the diff.
#205 will mean to a source of truth being in a repo.

@BobyMCbobs BobyMCbobs closed this Oct 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant