From cd373c29fc61618682291b9ea17c02c7b566bc68 Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Tue, 24 Jan 2023 15:40:32 -0700 Subject: [PATCH 1/6] Branch Protection Rules support for main branch This adds a playbook `update_branch_protection_rules.yml` for setting Branch Protection Rules for the `main` branch. The default rules are quite strict - require approvals, require status checks, clear approvals if a new commit is pushed, do not allow admin override, and more. The status checks are not currently configurable per group or per role, but you can have additional status checks. For example, the network role has additional status checks not used by the other roles. There are additional python status checks, and additional shell checks, for the roles that support and use those checks. More default status checks will be added - we're in the process of revising the names of the baseos ci status checks, so more will be coming soon. Signed-off-by: Rich Megginson --- inventory/group_vars/active_roles.yml | 34 ++++++ inventory/group_vars/python_roles.yml | 9 ++ inventory/group_vars/shellcheck_roles.yml | 3 + inventory/host_vars/network.yml | 9 ++ .../create_branch_protection_rules.graphql | 14 +++ .../delete_branch_protection_rules.graphql | 7 ++ .../get_branch_protection_rules.graphql | 15 +++ playbooks/update_branch_protection_rules.yml | 112 ++++++++++++++++++ 8 files changed, 203 insertions(+) create mode 100644 playbooks/graphql_files/create_branch_protection_rules.graphql create mode 100644 playbooks/graphql_files/delete_branch_protection_rules.graphql create mode 100644 playbooks/graphql_files/get_branch_protection_rules.graphql create mode 100644 playbooks/update_branch_protection_rules.yml diff --git a/inventory/group_vars/active_roles.yml b/inventory/group_vars/active_roles.yml index 8ddecdf..2196d58 100644 --- a/inventory/group_vars/active_roles.yml +++ b/inventory/group_vars/active_roles.yml @@ -28,3 +28,37 @@ lsr_name: linux_system_roles lsr_role_namespace: linux_system_roles # for ansible-lint gha_checkout_action: actions/checkout@v3 tox_lsr_url: "git+https://github.com/linux-system-roles/tox-lsr@2.13.2" +default_status_check_contexts: + - ansible_lint + - ansible_managed_var_comment + - ansible_plugin_scan + - ansible_test + - Fedora-37/ansible-2.14/(citool) + - Fedora-37/ansible-2.9/(citool) + - Fedora-36/ansible-2.14/(citool) + - Fedora-36/ansible-2.9/(citool) +# https://docs.github.com/en/graphql/reference/objects#branchprotectionrule +branchProtectionRules: + - allowsDeletions: false + allowsForcePushes: false + blocksCreations: false + dismissesStaleReviews: true + isAdminEnforced: true + lockAllowsFetchAndMerge: false + lockBranch: false + pattern: main + requireLastPushApproval: true + requiredApprovingReviewCount: 1 + requiredStatusCheckContexts: "{{ default_status_check_contexts + + status_check_contexts | d([]) + + python_status_check_contexts | d([]) + + shellcheck_status_check_contexts | d([]) | unique }}" + requiresApprovingReviews: true + requiresCodeOwnerReviews: true + requiresCommitSignatures: true + requiresConversationResolution: false + requiresLinearHistory: true + requiresStatusChecks: true + requiresStrictStatusChecks: true + restrictsPushes: false + restrictsReviewDismissals: false diff --git a/inventory/group_vars/python_roles.yml b/inventory/group_vars/python_roles.yml index dd6aa8c..c477c0c 100644 --- a/inventory/group_vars/python_roles.yml +++ b/inventory/group_vars/python_roles.yml @@ -3,3 +3,12 @@ present_python_templates: - .github/workflows/codeql.yml - .github/workflows/python-unit-test.yml absent_python_files: [] +# these are additional status checks for python code +python_status_check_contexts: + - CodeQL + - python (2.7, ubuntu-20.04) + - python (3.6, ubuntu-20.04) + - python (3.8, ubuntu-latest) + - python (3.9, ubuntu-latest) + - python (3.10, ubuntu-latest) + - python (3.11, ubuntu-latest) diff --git a/inventory/group_vars/shellcheck_roles.yml b/inventory/group_vars/shellcheck_roles.yml index 41d0dbc..1a527f5 100644 --- a/inventory/group_vars/shellcheck_roles.yml +++ b/inventory/group_vars/shellcheck_roles.yml @@ -3,3 +3,6 @@ present_shellcheck_templates: - .github/workflows/shellcheck.yml absent_shellcheck_files: - .github/workflows/differential-shellcheck.yml +# these are additional status checks for shell code +shellcheck_status_check_contexts: + - shellcheck diff --git a/inventory/host_vars/network.yml b/inventory/host_vars/network.yml index e74bbe8..93a277c 100644 --- a/inventory/host_vars/network.yml +++ b/inventory/host_vars/network.yml @@ -44,3 +44,12 @@ yamllint: /tests/tests_bond_cloned_mac_initscripts.yml /tests/tests_bridge_cloned_mac_initscripts.yml /tests/tests_bridge_cloned_mac_nm.yml +# these are added to the list of default status check contexts +status_check_contexts: + - DCO + - markdownlint + - integration (c8s) + - integration (c9s) + - integration (c7) + - python-26 + - woke diff --git a/playbooks/graphql_files/create_branch_protection_rules.graphql b/playbooks/graphql_files/create_branch_protection_rules.graphql new file mode 100644 index 0000000..958efac --- /dev/null +++ b/playbooks/graphql_files/create_branch_protection_rules.graphql @@ -0,0 +1,14 @@ +mutation { + createBranchProtectionRule(input:{ +{% for key, value in bpr.items() %} + {{ key }}:{{ value | to_nice_json }}, +{% endfor %} + repositoryId:"{{ repo.id }}" + }) { + branchProtectionRule { + id + allowsForcePushes + } + clientMutationId + } +} diff --git a/playbooks/graphql_files/delete_branch_protection_rules.graphql b/playbooks/graphql_files/delete_branch_protection_rules.graphql new file mode 100644 index 0000000..52ca317 --- /dev/null +++ b/playbooks/graphql_files/delete_branch_protection_rules.graphql @@ -0,0 +1,7 @@ +mutation { + deleteBranchProtectionRule(input:{ + branchProtectionRuleId:"{{ bpr_id }}" + }) { + clientMutationId + } +} diff --git a/playbooks/graphql_files/get_branch_protection_rules.graphql b/playbooks/graphql_files/get_branch_protection_rules.graphql new file mode 100644 index 0000000..94b2ffb --- /dev/null +++ b/playbooks/graphql_files/get_branch_protection_rules.graphql @@ -0,0 +1,15 @@ +query { + repository(owner:"{{ github_org }}", name:"{{ inventory_hostname }}") { + id + branchProtectionRules(first:50) { + edges { + node { + id +{% for param in __bpr_fields %} + {{ param }} +{% endfor %} + } + } + } + } +} diff --git a/playbooks/update_branch_protection_rules.yml b/playbooks/update_branch_protection_rules.yml new file mode 100644 index 0000000..7470867 --- /dev/null +++ b/playbooks/update_branch_protection_rules.yml @@ -0,0 +1,112 @@ +# use the graphql variable names +- name: Manage branch protection rules + hosts: active_roles + connection: local + gather_facts: false + tasks: + - name: Get gh config file + slurp: + path: "{{ lookup('env', 'HOME') ~ '/.config/gh/hosts.yml' }}" + register: gh_cfg_file + + - name: Get token + set_fact: + token: "{{ cfg_dict['github.com']['oauth_token'] }}" + vars: + cfg_dict: "{{ gh_cfg_file.content | b64decode | from_yaml }}" + + - name: Get Branch Protection Rules + uri: + url: https://api.github.com/graphql + method: POST + headers: + content-type: "application/json" + authorization: "Bearer {{ token }}" + X-Github-Next-Global-ID: "1" + body_format: json + body: + query: "{{ lookup('template', 'graphql_files/get_branch_protection_rules.graphql') }}" + # variables: "{{ lookup('template', './test_vars.yml') | from_yaml | to_nice_json }}" + vars: + __bpr_fields: "{{ (branchProtectionRules | first).keys() | list }}" + register: __query_result + + - name: Result + debug: + msg: "{{ __query_result.json.data | to_nice_json }}" + verbosity: 3 + + - name: Convert result to canonical format + set_fact: + repo: | + {% set __repo = {} %} + {% for key, value in __query_result.json.data.repository.items() %} + {% if key == "branchProtectionRules" %} + {% set _ = __repo.update({key: []})%} + {% for edge in value["edges"] %} + {% if "node" in edge %} + {% set _ = __repo[key].append(edge["node"]) %} + {% endif %} + {% endfor %} + {% else %} + {% set _ = __repo.update({key: value})%} + {% endif %} + {% endfor %} + {{ __repo }} + + - name: Show converted result + debug: + msg: repo {{ repo | to_nice_json }} + verbosity: 3 + + - name: Update Branch Protection Rules if needed + vars: + __expected: "{{ branchProtectionRules | sort(attribute='pattern') | map('combine', {'id': ''}) }}" + __actual: "{{ repo.branchProtectionRules | sort(attribute='pattern') | map('combine', {'id': ''}) }}" + when: __expected != __actual + block: + - name: Remove existing rules + uri: + url: https://api.github.com/graphql + method: POST + headers: + content-type: "application/json" + authorization: "Bearer {{ token }}" + X-Github-Next-Global-ID: "1" + body_format: json + body: + query: "{{ lookup('template', 'graphql_files/delete_branch_protection_rules.graphql') }}" + # variables: "{{ lookup('template', './test_vars.yml') | from_yaml | to_nice_json }}" + register: __delete_result + vars: + bpr_id: "{{ item.id }}" + loop: "{{ repo.branchProtectionRules }}" + + - name: Show delete result + debug: + msg: __delete_result {{ __delete_result.json.data | to_nice_json }} + verbosity: 3 + when: __delete_result.json is defined + + - name: Add expected rules + uri: + url: https://api.github.com/graphql + method: POST + headers: + content-type: "application/json" + authorization: "Bearer {{ token }}" + X-Github-Next-Global-ID: "1" + body_format: json + body: + query: "{{ lookup('template', 'graphql_files/create_branch_protection_rules.graphql') }}" + # variables: "{{ lookup('template', './test_vars.yml') | from_yaml | to_nice_json }}" + register: __create_result + loop: "{{ branchProtectionRules }}" + loop_control: + loop_var: bpr + + - name: Show create result + debug: + msg: "{{ __create_result.json.data | to_nice_json }}" + verbosity: 3 + when: __create_result.json is defined From 23268ceb6d1929f9c14c3d1631033cc23b6810b3 Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Fri, 17 Feb 2023 17:59:21 -0700 Subject: [PATCH 2/6] update README --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index f437f0a..c61344e 100644 --- a/README.md +++ b/README.md @@ -145,3 +145,33 @@ ansible-playbook -vv -i inventory -e update_files_commit_file=/path/to/git-commi NOTE: This process may create multiple commits if you need to make edits to an existing PR. Use the `Squash commits and merge` functionality in the github PR to merge. + +# Manage Branch Protection Rules + +The playbook `playbooks/update_branch_protection_rules.yml` is used to manage +branch protection rules for the `main` branch. The default rules are specified +in `branchProtectionRules` in `inventory/group_vars/active_roles.yml`. The +additional status checks for python code are specified in +`python_status_check_contexts` in `inventory/group_vars/python_roles.yml`. The +additional status checks for shell code are specified in +`shellcheck_status_check_contexts` in +`inventory/group_vars/shellcheck_roles.yml`. + +These use the github graphql api https://docs.github.com/en/graphql. The +graphql files are in the `playbooks/graphql_files/` directory. + +The general strategy is to get the existing role rules. If they are different +than the specified rules, delete them and add the specified rules. + +There aren't any parameters, but if you use `-vvv` or higher verbosity, +you can see the data being sent and received. + +*NOTE WELL*: This playbook does not create PRs - it modifies the role +configuration directly - so be careful. + +## Run it + +Run it like this: +``` +ansible-playbook -vv -i inventory playbooks/update_branch_protection_rules.yml +``` From 966e972e2ed61de2d903d63658467f8ccd868e94 Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Sat, 18 Feb 2023 15:42:41 -0700 Subject: [PATCH 3/6] sort status checks --- inventory/group_vars/active_roles.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/inventory/group_vars/active_roles.yml b/inventory/group_vars/active_roles.yml index 2196d58..d4fb901 100644 --- a/inventory/group_vars/active_roles.yml +++ b/inventory/group_vars/active_roles.yml @@ -52,7 +52,8 @@ branchProtectionRules: requiredStatusCheckContexts: "{{ default_status_check_contexts + status_check_contexts | d([]) + python_status_check_contexts | d([]) + - shellcheck_status_check_contexts | d([]) | unique }}" + shellcheck_status_check_contexts | d([]) | unique | + sort(case_sensitive=false) }}" requiresApprovingReviews: true requiresCodeOwnerReviews: true requiresCommitSignatures: true From 0f89832e3f0fe7524e0233ee38374b935a5600c2 Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Tue, 21 Feb 2023 10:19:39 -0700 Subject: [PATCH 4/6] allow isAdminEnforced per-role --- inventory/group_vars/active_roles.yml | 3 ++- inventory/host_vars/cockpit.yml | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/inventory/group_vars/active_roles.yml b/inventory/group_vars/active_roles.yml index d4fb901..a231567 100644 --- a/inventory/group_vars/active_roles.yml +++ b/inventory/group_vars/active_roles.yml @@ -38,12 +38,13 @@ default_status_check_contexts: - Fedora-36/ansible-2.14/(citool) - Fedora-36/ansible-2.9/(citool) # https://docs.github.com/en/graphql/reference/objects#branchprotectionrule +isAdminEnforced: false branchProtectionRules: - allowsDeletions: false allowsForcePushes: false blocksCreations: false dismissesStaleReviews: true - isAdminEnforced: true + isAdminEnforced: "{{ isAdminEnforced }}" lockAllowsFetchAndMerge: false lockBranch: false pattern: main diff --git a/inventory/host_vars/cockpit.yml b/inventory/host_vars/cockpit.yml index 396e3cb..d95f0d5 100644 --- a/inventory/host_vars/cockpit.yml +++ b/inventory/host_vars/cockpit.yml @@ -2,3 +2,5 @@ github_actions: weekly_ci: schedule: - cron: "0 12 * * 6" +# branch protection +isAdminEnforced: true From bbf071d9fcfad77e1a42526b80e2a386ca0291b4 Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Tue, 21 Feb 2023 10:19:46 -0700 Subject: [PATCH 5/6] additional debugging --- .../get_branch_protection_rules.graphql | 16 ++++++++++++++++ playbooks/update_branch_protection_rules.yml | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/playbooks/graphql_files/get_branch_protection_rules.graphql b/playbooks/graphql_files/get_branch_protection_rules.graphql index 94b2ffb..b3681f4 100644 --- a/playbooks/graphql_files/get_branch_protection_rules.graphql +++ b/playbooks/graphql_files/get_branch_protection_rules.graphql @@ -8,6 +8,22 @@ query { {% for param in __bpr_fields %} {{ param }} {% endfor %} +{# + requiredStatusChecks { + context + app { + createdAt + databaseId + description + logoBackgroundColor + logoUrl + name + slug + updatedAt + url + } + } +#} } } } diff --git a/playbooks/update_branch_protection_rules.yml b/playbooks/update_branch_protection_rules.yml index 7470867..c4ad217 100644 --- a/playbooks/update_branch_protection_rules.yml +++ b/playbooks/update_branch_protection_rules.yml @@ -56,7 +56,7 @@ - name: Show converted result debug: - msg: repo {{ repo | to_nice_json }} + msg: repo {{ repo | to_nice_json }} bpr {{ branchProtectionRules | to_nice_json }} verbosity: 3 - name: Update Branch Protection Rules if needed From 88c0572876780b14c572680a6ed353a9473c6827 Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Tue, 7 Mar 2023 11:03:24 -0700 Subject: [PATCH 6/6] merge group permissions wip --- playbooks/templates/.github/workflows/ansible-lint.yml | 1 + .../templates/.github/workflows/ansible-managed-var-comment.yml | 1 + playbooks/templates/.github/workflows/ansible-plugin-scan.yml | 1 + playbooks/templates/.github/workflows/ansible-test.yml | 1 + playbooks/templates/.github/workflows/codeql.yml | 1 + playbooks/templates/.github/workflows/markdownlint.yml | 1 + playbooks/templates/.github/workflows/python-unit-test.yml | 1 + 7 files changed, 7 insertions(+) diff --git a/playbooks/templates/.github/workflows/ansible-lint.yml b/playbooks/templates/.github/workflows/ansible-lint.yml index 27a472a..8ed0a44 100644 --- a/playbooks/templates/.github/workflows/ansible-lint.yml +++ b/playbooks/templates/.github/workflows/ansible-lint.yml @@ -10,6 +10,7 @@ on: # yamllint disable-line rule:truthy push: branches: - main + merge_group: workflow_dispatch: permissions: contents: read diff --git a/playbooks/templates/.github/workflows/ansible-managed-var-comment.yml b/playbooks/templates/.github/workflows/ansible-managed-var-comment.yml index b90852f..3fbf949 100644 --- a/playbooks/templates/.github/workflows/ansible-managed-var-comment.yml +++ b/playbooks/templates/.github/workflows/ansible-managed-var-comment.yml @@ -10,6 +10,7 @@ on: # yamllint disable-line rule:truthy push: branches: - main + merge_group: workflow_dispatch: permissions: contents: read diff --git a/playbooks/templates/.github/workflows/ansible-plugin-scan.yml b/playbooks/templates/.github/workflows/ansible-plugin-scan.yml index 382c5eb..00c4054 100644 --- a/playbooks/templates/.github/workflows/ansible-plugin-scan.yml +++ b/playbooks/templates/.github/workflows/ansible-plugin-scan.yml @@ -10,6 +10,7 @@ on: # yamllint disable-line rule:truthy push: branches: - main + merge_group: workflow_dispatch: permissions: contents: read diff --git a/playbooks/templates/.github/workflows/ansible-test.yml b/playbooks/templates/.github/workflows/ansible-test.yml index b905ff7..9cd7b24 100644 --- a/playbooks/templates/.github/workflows/ansible-test.yml +++ b/playbooks/templates/.github/workflows/ansible-test.yml @@ -10,6 +10,7 @@ on: # yamllint disable-line rule:truthy push: branches: - main + merge_group: workflow_dispatch: env: LSR_ROLE2COLL_NAMESPACE: {{ lsr_namespace }} diff --git a/playbooks/templates/.github/workflows/codeql.yml b/playbooks/templates/.github/workflows/codeql.yml index c72b306..f7e2ae9 100644 --- a/playbooks/templates/.github/workflows/codeql.yml +++ b/playbooks/templates/.github/workflows/codeql.yml @@ -12,6 +12,7 @@ on: # yamllint disable-line rule:truthy - checks_requested schedule: {{ github_actions.codeql.schedule | to_nice_yaml(indent=2) | indent(width=4, first=true) -}} + workflow_dispatch: jobs: analyze: name: Analyze diff --git a/playbooks/templates/.github/workflows/markdownlint.yml b/playbooks/templates/.github/workflows/markdownlint.yml index d47b063..7f67606 100644 --- a/playbooks/templates/.github/workflows/markdownlint.yml +++ b/playbooks/templates/.github/workflows/markdownlint.yml @@ -11,6 +11,7 @@ on: # yamllint disable-line rule:truthy push: branches: - main + merge_group: workflow_dispatch: permissions: contents: read diff --git a/playbooks/templates/.github/workflows/python-unit-test.yml b/playbooks/templates/.github/workflows/python-unit-test.yml index 5731f8f..2b644c4 100644 --- a/playbooks/templates/.github/workflows/python-unit-test.yml +++ b/playbooks/templates/.github/workflows/python-unit-test.yml @@ -11,6 +11,7 @@ on: # yamllint disable-line rule:truthy push: branches: - main + merge_group: workflow_dispatch: permissions: contents: read