From 715b9ea83a42a740d54290f9148cbf5f4342b3c2 Mon Sep 17 00:00:00 2001 From: Kyle Harding Date: Fri, 22 Sep 2023 16:47:09 -0400 Subject: [PATCH] Create repo manager workflow to apply repo settings Change-type: patch Signed-off-by: Kyle Harding --- .github/settings.yml | 191 +++++++++++++++ .github/workflows/repo-manager.yml | 222 ++++++++++++++++++ .../{tests.yml => test-flowzone.yml} | 2 +- .github/workflows/test-repo-manager.yml | 21 ++ 4 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 .github/settings.yml create mode 100644 .github/workflows/repo-manager.yml rename .github/workflows/{tests.yml => test-flowzone.yml} (98%) create mode 100644 .github/workflows/test-repo-manager.yml diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 000000000..b75e65c05 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,191 @@ +# https://github.com/andrewthetechie/gha-repo-manager/blob/main/examples/settings.yml + +# settings.yml can live in two places: +# 1. in the repo itself +# 2. in a centralized repo + +# The Action is able to apply settings to any repo that its token can manage +# You can run Action from each repo, acting on that repo's settings.yml, or +# from a central repo, using a single settings.yml to control many repos. + +# Which method you choose is up to you. See README.md for more info and example +# Workflows to implement these strategies. +settings: + # See https://docs.github.com/en/rest/repos/repos#update-a-repository for all available settings. + # any of these settings can be ommitted to just leave the repo's current setting + # If a setting has a value in settings.yml, it will always overwrite what exists in the repo. + + # A short description of the repository that will show up on GitHub. Set to an empty string to clear. + # description: description of repo + + # A URL with more information about the repository. Set to an empty string to clear. + # homepage: https://example.github.io/ + + # A list of strings to apply as topics on the repo. Set to an empty string to clear topics. Omit or set to null to leave what repo already has + # topics: + # - gha + # - foo + # - bar + + # Either `true` to make the repository private, or `false` to make it public. + # private: false + + # Either `true` to enable issues for this repository, `false` to disable them. + # has_issues: true + + # Either `true` to enable projects for this repository, or `false` to disable them. + # If projects are disabled for the organization, passing `true` will cause an API error. + # has_projects: true + + # Either `true` to enable the wiki for this repository, `false` to disable it. + # has_wiki: true + + # Either `true` to enable downloads for this repository, `false` to disable them. + # has_downloads: true + + # Set the default branch for this repository. + # default_branch: main + + # Either `true` to allow squash-merging pull requests, or `false` to prevent + # squash-merging. + allow_squash_merge: false + + # Either `true` to allow merging pull requests with a merge commit, or `false` + # to prevent merging pull requests with merge commits. + allow_merge_commit: true + + # Either `true` to allow rebase-merging pull requests, or `false` to prevent + # rebase-merging. + allow_rebase_merge: false + + # Either `true` to enable automatic deletion of branches on merge, or `false` to disable + delete_branch_on_merge: true + + # Either `true` to enable automated security fixes, or `false` to disable + # automated security fixes. + # enable_automated_security_fixes: true + + # Either `true` to enable vulnerability alerts, or `false` to disable + # vulnerability alerts. + # enable_vulnerability_alerts: true + +# Labels: define labels for Issues and Pull Requests +# labels: +# - name: bug +# color: CC0000 +# description: An issue with the system. + +# - name: feature +# # If including a `#`, make sure to wrap it with quotes! +# color: "#336699" +# description: New functionality. + +# - name: Help Wanted +# # Provide a new name to rename an existing label. A rename that results in a 'not found' will not fail a run +# new_name: first-timers-only + +# - name: Old Label +# # set exists: false to delete a label. A delete that results in a "not found" will not fail a run +# exists: false + +branch_protections: + # branch protection can only be created for branches that exist. + - name: $DEFAULT_BRANCH + # https://docs.github.com/en/rest/branches/branch-protection#update-branch-protection + # Branch Protection settings. Leave a value out to leave set at current repo settings + protection: + # Require at least one approving review on a pull request, before merging. Set to null to disable. + pr_options: null + # # The number of approvals required. (1-6) + # required_approving_review_count: 1 + # # Dismiss approved reviews automatically when a new commit is pushed. + # dismiss_stale_reviews: false + # # Blocks merge until code owners have reviewed. + # require_code_owner_reviews: false + # # Specify which users and teams can dismiss pull request reviews. Pass an empty dismissal_restrictions object to disable. User and team dismissal_restrictions are only available for organization-owned repositories. Omit this parameter for personal repositories. + # # dismissal_restrictions: + # # users: [] + # # teams: [] + # Require status checks to pass before merging. Set to null to disable + required_status_checks: + # Require branches to be up to date before merging. + strict: true + # The list of status checks to require in order to merge into this branch + checks: + - Flowzone / All jobs + - "policy-bot: $DEFAULT_BRANCH" + # Blocks merge until all conversations on a pull request have been resolved + require_conversation_resolution: false + # Enforce all configured restrictions for administrators. Set to true to enforce required status checks for repository administrators. Set to null to disable. + enforce_admins: false + # Prevent merge commits from being pushed to matching branches + require_linear_history: false + # Permit force pushes for all users with push access. + allow_force_pushes: false + # Allow users with push access to delete matching branches. + allow_deletions: false + # If set to true, the restrictions branch protection settings which limits who can push will also block pushes which create new branches, unless the push is initiated by a user, team, or app which has the ability to push. Set to true to restrict new branch creation. + block_creations: false + # Restrict who can push to this branch. Team and user restrictions are only available for organization-owned repositories. Set to null to disable. + restrictions: null + # users: [] + # teams: [] + # - name: dev + # # will clear any branch protection on the dev branch, IF the dev branch exists. If you setup protection for a non-existant branch, this action cannot delete it + # exists: False + # # if the repo has a third branch named test with branch protections setup, by not adding a protection with name: test, this config will not change + # # those existing protections. + # - name: test + # exists: True + +# secrets: +# # Manage secrets in your repo. Useful to manage secrets from a central repo for non organizations or to manage secrets org wide +# - key: SECRET_KEY +# # pull the value from an environment variable. If this variable is not found in the env, throw an error and fail the run +# # Set env vars on the github action job from secrets in your repo to sync screts across repos +# env: SECRET_VALUE +# # Set a dependabot secret on the repo +# - key: SECRET_KEY +# env: SECRET_VALUE +# type: dependabot +# - key: ANOTHER_SECRET +# # set a value directly in your yaml, probably not a good idea for things that are actually a secret +# value: bar +# - key: THIRD_SECRET +# # pull the value from an environment variable +# env: THIRD_VALUE +# # setting a value as not required allows you to not pass in an env var. if THIRD_VALUE is not set in the env, this secret won't be set but no error will be thrown +# required: false +# - key: DELETED_SECRET +# # setting exists to false will delete a secret. A delete that results in "not found" won't fail a run, so you can use this to make sure a secret is always deleted +# exists: false + +# # Can copy files from your local context to the repo. +# # Manipulate files in the target repo +# # * move files around +# # * delete files +# # Changes are automatically commited and pushed to a target branch (default is default branch) +# # File operations are applied sequentially +# files: +# # copy templates/actions/my_workflow.yml to .github/workflows/my_workflow.yml in your target repo +# # and commit it with the default commit message and to your repo's default branch. +# # default commit message is "repo_manager file commit" +# - src_file: templates/actions/my_workflow.yml +# dest_file: .github/workflows/my_workflow.yml +# - src_file: templates/issues/issue_template.md +# dest_file: .github/ISSUE_TEMPLATE/issue.md +# commit_msg: update issue template +# # Update this file in the dev branch. If the dev branch doesn't exist, this will fail the workflow +# - src_file: templates/dev/dev.md +# dest_file: dev.md +# target_branch: dev +# # This moves README.md to README.rst in the remote. If README.md doesn't exist, the workflow will not fail and will emit a warning. +# - src_file: remote://README.md +# dest_file: README.rst +# move: true +# commit_msg: "move readme" +# # This removes OLDDOC.md in the dev branch. If OLDDOC.md doesn't exist, the workflow will emit a warning +# - dest_file: OLDDOC.md +# exists: false +# branch: dev +# commit_msg: "remove OLDDOC.md from dev" diff --git a/.github/workflows/repo-manager.yml b/.github/workflows/repo-manager.yml new file mode 100644 index 000000000..af53ec01e --- /dev/null +++ b/.github/workflows/repo-manager.yml @@ -0,0 +1,222 @@ +name: Repo Manager + +on: + workflow_call: + secrets: + REPO_MGR_PRIVATE_KEY: + description: GitHub App to generate ephemeral access tokens + required: false + inputs: + owner: + description: The organization to manage, uses the organization of the triggering workflow if not provided + type: string + required: false + topics: + description: Comma-separated list of repository topics for filtering + type: string + required: false + default: repo-manager + topics-operator: + description: Logic operator to use when filtering repositories by topics (OR or AND) + type: string + required: false + default: AND + action: + description: "Action to run (validate, check, or apply)" + type: string + required: false + default: "check" + workflow_dispatch: + inputs: + org: + description: The organization to manage, uses the organization of the triggering workflow if not provided + type: string + required: false + topics: + description: Comma-separated list of repository topics for filtering + type: string + required: false + default: repo-manager + topics-operator: + description: Logic operator to use when filtering repositories by topics (OR or AND) + type: choice + required: false + options: + - AND + - OR + default: AND + action: + description: "Action to run (validate, check, or apply)" + type: choice + required: false + options: + - validate + - check + - apply + default: check + +# https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + group: ${{ github.workflow }} + # cancel jobs in progress for updated PRs, but not merge or tag events + cancel-in-progress: ${{ github.event.action == 'synchronize' }} + +jobs: + prepare: + name: Prepare + runs-on: ubuntu-latest + + outputs: + repos: ${{ steps.get-repos.outputs.repos }} + action: ${{ inputs.action || steps.set-action.outputs.action || 'check' }} + + steps: + # https://github.com/marketplace/actions/github-app-token + - name: Generate GitHub App installation token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + id: github-app-token + with: + app_id: ${{ vars.REPO_MGR_APP_ID }} + installation_retrieval_mode: organization + installation_retrieval_payload: ${{ inputs.owner || github.repository_owner }} + private_key: ${{ secrets.REPO_MGR_PRIVATE_KEY }} + permissions: >- + { + "metadata": "read" + } + + # https://github.com/raven-actions/get-repos + - name: Get organization repositories + id: get-repos + uses: raven-actions/get-repos@v1.0.2 + with: + github-token: ${{ steps.github-app-token.outputs.token }} + owner: ${{ inputs.owner || github.repository_owner }} + topics: ${{ inputs.topics || 'repo-manager' }} + operator: ${{ inputs.topics-operator || 'AND' }} + format: json + matrix-use: true + + - name: Set action to "apply" + id: set-action + if: inputs.action == '' && github.event_name == 'schedule' + run: echo 'action=apply' >> $GITHUB_OUTPUT + + repo-manager: + name: Repo Manager + runs-on: ubuntu-latest + needs: [prepare] + if: ${{ needs.prepare.outputs.repos != '[]' }} + + defaults: + run: + shell: bash + working-directory: . + + strategy: + fail-fast: false + max-parallel: 8 + matrix: + repo: ${{ fromJson(needs.prepare.outputs.repos) }} + + steps: + # https://github.com/marketplace/actions/github-app-token + - name: Generate GitHub App installation token + uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 + id: github-app-token + with: + app_id: ${{ vars.REPO_MGR_APP_ID }} + installation_retrieval_mode: organization + installation_retrieval_payload: ${{ inputs.owner || github.repository_owner }} + private_key: ${{ secrets.REPO_MGR_PRIVATE_KEY }} + repositories: >- + [ + "${{ matrix.repo.name }}" + ] + permissions: >- + { + "administration": "write", + "contents": "read", + "metadata": "read" + } + + # Checkout the repo that triggered the workflow. This may be the .github repo + # at the root of an organization, or some other repo being used for testing. + # Either way, we need to checkout the repo that triggered the workflow so we + # have a default settings.yml file to apply. + # https://github.com/actions/checkout + - name: Checkout trigger repo + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3 + with: + repository: ${{ github.event.repository.full_name }} + path: ${{ github.event.repository.name }} + token: ${{ secrets.GITHUB_TOKEN }} + + # Checkout the target repo. This is the repo that we are managing. + # If it has a .github/settings.yml, we will use that instead of the default + # from the triggering repo. + # https://github.com/actions/checkout + - name: Checkout target repo + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3 + with: + repository: ${{ matrix.repo.full_name }} + ref: ${{ matrix.repo.default_branch }} + path: ${{ matrix.repo.name }} + token: ${{ steps.github-app-token.outputs.token }} + + # Create a symlink to the preferred settings file. + - name: Link to settings file + env: + FILES: >- + ${{ matrix.repo.name }}/.github/settings.yml + ${{ github.event.repository.name }}/repo-settings.yml + ${{ github.event.repository.name }}/.github/settings.yml + run: | + for file in $FILES; do + if [ -f "$file" ]; then + echo "Found settings file: $file" + ln -sv $file settings.yml + break + fi + done + + - name: Substitute env vars + env: + DEFAULT_BRANCH: ${{ matrix.repo.default_branch }} + run: | + envsubst < settings.yml > settings.yml.tmp + mv settings.yml.tmp settings.yml + yq . settings.yml + + - name: Save default branch required checks + continue-on-error: true + id: get-branch-protection + env: + GH_DEBUG: "true" + GH_PAGER: "cat" + GH_PROMPT_DISABLED: "true" + GH_TOKEN: ${{ steps.github-app-token.outputs.token }} + run: | + gh api repos/${{ matrix.repo.full_name }}/branches/${{ matrix.repo.default_branch }}/protection --jq '.required_status_checks.contexts' | yq eval -P > response.yml + + - name: Remove any ResinCI checks + if: steps.get-branch-protection.outcome == 'success' + run: | + yq e 'del(.[] | select(. == "ResinCI*"))' response.yml > response.yml.tmp + mv response.yml.tmp response.yml + + - name: Merge default branch required checks + if: steps.get-branch-protection.outcome == 'success' + run: | + yq eval-all '.branch_protections[0].protection.required_status_checks.checks += load("response.yml") | + .branch_protections[0].protection.required_status_checks.checks |= unique' settings.yml > settings.yml.tmp + mv settings.yml.tmp settings.yml + yq . settings.yml + + # https://github.com/andrewthetechie/gha-repo-manager + - name: Run repo manager + uses: andrewthetechie/gha-repo-manager@v1.7.1 + with: + action: ${{ needs.prepare.outputs.action }} + token: ${{ steps.github-app-token.outputs.token }} + settings_file: settings.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/test-flowzone.yml similarity index 98% rename from .github/workflows/tests.yml rename to .github/workflows/test-flowzone.yml index 0de6c5480..98520b4cc 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/test-flowzone.yml @@ -1,4 +1,4 @@ -name: Tests +name: Test Flowzone on: pull_request: diff --git a/.github/workflows/test-repo-manager.yml b/.github/workflows/test-repo-manager.yml new file mode 100644 index 000000000..2f28a0d60 --- /dev/null +++ b/.github/workflows/test-repo-manager.yml @@ -0,0 +1,21 @@ +name: Test Repo Manager + +on: + pull_request: + branches: + - main + - master + paths: + - '.github/workflows/repo-manager.yml' + - '.github/settings.yml' + +jobs: + test-repo-manager: + name: Repo Manager + uses: ./.github/workflows/repo-manager.yml + secrets: inherit + with: + action: check + # these topics only exist in the test repo for now + topics: flowzone,repo-manager + topics-operator: AND