Going through all jupyterhub repos and adding the most basic branch protection rule for main
I use a token stored in my keychain. I have two of these, one read-only and one with full access.
You can create similar tokens with:
visit https://github.com/settings/personal-access-tokens/new
store with:
python -m keyring set api.github.com read-only
import requests
import keyring
s = requests.Session()
s.headers['Authorization'] = f"bearer {keyring.get_password('api.github.com', 'full-access')}"
s.headers['X-Github-Next-Global-ID'] = '1'
s.get("https://api.github.com/user").json()['login']
'minrk'
Use graphQL endpoint to collect repositories
from jinja2 import Template
import json
from tqdm.notebook import tqdm
query_template = Template("""
{
organization(login:"jupyterhub") {
# user(login: "minrk") {
repositories(isFork: false, ownerAffiliations: OWNER, first: 100{%- if after %}, after: "{{ after }}"{%- endif %}) {
totalCount
pageInfo {
endCursor
hasNextPage
}
nodes {
id
name
defaultBranchRef {
name
}
branchProtectionRules(first: 5) {
nodes {
pattern
matchingRefs(first: 5) {
nodes {
name
}
}
}
}
}
}
}
}
""")
github_graphql = "https://api.github.com/graphql"
def fetch_repos():
after = None
repos = []
has_next_page = True
progress = tqdm(desc="fetching", unit="repos")
while has_next_page:
r = s.post(github_graphql, data=json.dumps(dict(query=query_template.render(after=after))))
r.raise_for_status()
response = r.json()["data"]["organization"]["repositories"]
progress.total = response["totalCount"]
progress.update(len(response['nodes']))
repos.extend(response['nodes'])
after = response['pageInfo']['endCursor']
has_next_page = response['pageInfo']['hasNextPage']
progress.close()
return repos
repos = fetch_repos()
fetching: 0repos [00:00, ?repos/s]
repos[0]
{'id': 'R_kgDOAT0rQg', 'name': 'jupyterhub', 'defaultBranchRef': {'name': 'main'}, 'branchProtectionRules': {'nodes': []}}
Iterate through repos, finding:
to_protect = []
no_main = []
is_protected = []
for repo in repos:
print(repo['name'])
if repo['defaultBranchRef'] is None:
print(" Empty repo!")
continue
default_branch = repo['defaultBranchRef']['name']
if default_branch != 'main':
print(f" non-main default branch: {default_branch}")
no_main.append(repo)
rules = repo['branchProtectionRules']['nodes']
matched = False
for rule in rules:
if default_branch in [ref['name'] for ref in rule['matchingRefs']['nodes']]:
print(f" Has rule: {rule['pattern']}")
matched = True
is_protected.append(repo)
break
if not matched:
print(f" Needs rule")
to_protect.append(repo)
print(f"{len(repos)} total repos")
print(f"{len(no_main)} repos missing main branch")
print(f"{len(is_protected)} repos with protected main")
print(f"{len(to_protect)} repos will get new protection rules")
jupyterhub Has rule: main configurable-http-proxy Has rule: main oauthenticator Has rule: main dockerspawner Has rule: main sudospawner non-main default branch: master Needs rule batchspawner non-main default branch: master Needs rule kubespawner Has rule: main ldapauthenticator non-main default branch: master Needs rule dummyauthenticator non-main default branch: master Needs rule simplespawner non-main default branch: master Needs rule jupyterhub-deploy-docker non-main default branch: master Needs rule jupyterhub-deploy-teaching non-main default branch: master Needs rule jupyterhub-tutorial non-main default branch: master Needs rule jupyterhub-deploy-hpc non-main default branch: master Needs rule systemdspawner non-main default branch: master Needs rule wrapspawner non-main default branch: master Needs rule jupyterlab-hub non-main default branch: master Needs rule jupyter-server-proxy Has rule: main firstuseauthenticator Has rule: main jupyterhub-example-kerberos non-main default branch: master Needs rule hubshare non-main default branch: master Needs rule jupyter-rsession-proxy non-main default branch: master Needs rule tmpauthenticator non-main default branch: master Needs rule zero-to-jupyterhub-k8s Has rule: main helm-chart non-main default branch: master Needs rule binderhub Has rule: main repo2docker Has rule: main mybinder.org-deploy non-main default branch: master Has rule: master nbgitpuller Has rule: main mybinder.org-user-guide non-main default branch: master Needs rule nullauthenticator non-main default branch: master Needs rule team-compass Has rule: main ltiauthenticator Has rule: main binder-data non-main default branch: master Needs rule binder-billing non-main default branch: master Needs rule jhub-proposals non-main default branch: master Needs rule chartpress Has rule: main mybinder-tools non-main default branch: master Needs rule the-littlest-jupyterhub Has rule: main design non-main default branch: master Needs rule research-facilities non-main default branch: master Needs rule outreachy Has rule: main alabaster-jupyterhub non-main default branch: master Needs rule traefik-proxy non-main default branch: master Needs rule nativeauthenticator Has rule: main yarnspawner non-main default branch: master Needs rule simpervisor Has rule: main jupyterhub-on-hadoop non-main default branch: master Needs rule kerberosauthenticator non-main default branch: master Needs rule jupyterhub-the-hard-way non-main default branch: master Needs rule autodoc-traits Has rule: main jupyter-remote-desktop-proxy Has rule: main repo2docker-action non-main default branch: master Needs rule .github non-main default branch: master Needs rule jupyterhub-idle-culler Has rule: main pebble-helm-chart Has rule: main action-k3s-helm Has rule: main action-major-minor-tag-calculator Has rule: main grafana-dashboards Has rule: main action-k8s-namespace-report Has rule: main action-k8s-await-workloads Has rule: main katacoda-scenarios Has rule: main docker-image-cleaner Has rule: main nbgitpuller-downloader-googledrive Has rule: main nbgitpuller-downloader-dropbox Has rule: main nbgitpuller-downloader-generic-web Has rule: main nbgitpuller-downloader-plugins Has rule: main gh-scoped-creds Has rule: main 68 total repos 34 repos missing main branch 35 repos with protected main 33 repos will get new protection rules
mutation_template = Template("""
mutation {
{% for repo in repos %}
{{ repo['name'] | replace("-", "") | replace(".","") }}: createBranchProtectionRule(input: {repositoryId: "{{ repo['id'] }}", pattern: "{{ repo['defaultBranchRef']['name'] }}"}) {
clientMutationId
}
{% endfor %}
}
""")
print(mutation_template.render(repos=to_protect)[:1024])
mutation { sudospawner: createBranchProtectionRule(input: {repositoryId: "R_kgDOAZ2C_g", pattern: "master"}) { clientMutationId } batchspawner: createBranchProtectionRule(input: {repositoryId: "R_kgDOAq7ijQ", pattern: "master"}) { clientMutationId } ldapauthenticator: createBranchProtectionRule(input: {repositoryId: "R_kgDOAvfy_A", pattern: "master"}) { clientMutationId } dummyauthenticator: createBranchProtectionRule(input: {repositoryId: "R_kgDOAxUgLA", pattern: "master"}) { clientMutationId } simplespawner: createBranchProtectionRule(input: {repositoryId: "R_kgDOAxVTvw", pattern: "master"}) { clientMutationId } jupyterhubdeploydocker: createBranchProtectionRule(input: {repositoryId: "R_kgDOA1otfA", pattern: "master"}) { clientMutationId } jupyterhubdeployteaching: createBranchProtectionRule(input: {repositoryId: "R_kgDOA1oucQ", pattern: "master"}) { clientMutationId } jupyterhubtutorial: createBranchProtectionRule(input: {re
r = s.post(github_graphql, data=json.dumps(dict(query=mutation_template.render(repos=to_protect))))
r.raise_for_status()
r.json()
{'data': {'sudospawner': {'clientMutationId': None}, 'batchspawner': {'clientMutationId': None}, 'ldapauthenticator': {'clientMutationId': None}, 'dummyauthenticator': {'clientMutationId': None}, 'simplespawner': {'clientMutationId': None}, 'jupyterhubdeploydocker': {'clientMutationId': None}, 'jupyterhubdeployteaching': {'clientMutationId': None}, 'jupyterhubtutorial': {'clientMutationId': None}, 'jupyterhubdeployhpc': {'clientMutationId': None}, 'systemdspawner': {'clientMutationId': None}, 'wrapspawner': {'clientMutationId': None}, 'jupyterlabhub': {'clientMutationId': None}, 'jupyterhubexamplekerberos': {'clientMutationId': None}, 'hubshare': {'clientMutationId': None}, 'jupyterrsessionproxy': {'clientMutationId': None}, 'tmpauthenticator': {'clientMutationId': None}, 'helmchart': {'clientMutationId': None}, 'mybinderorguserguide': {'clientMutationId': None}, 'nullauthenticator': {'clientMutationId': None}, 'binderdata': {'clientMutationId': None}, 'binderbilling': {'clientMutationId': None}, 'jhubproposals': {'clientMutationId': None}, 'mybindertools': {'clientMutationId': None}, 'design': {'clientMutationId': None}, 'researchfacilities': {'clientMutationId': None}, 'alabasterjupyterhub': {'clientMutationId': None}, 'traefikproxy': {'clientMutationId': None}, 'yarnspawner': {'clientMutationId': None}, 'jupyterhubonhadoop': {'clientMutationId': None}, 'kerberosauthenticator': {'clientMutationId': None}, 'jupyterhubthehardway': {'clientMutationId': None}, 'repo2dockeraction': {'clientMutationId': None}, 'github': {'clientMutationId': None}}}