forked from asyncapi/.github
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ci: refresh MAINTAINERS.yaml for each CODEOWNERS file change
- Loading branch information
Showing
12 changed files
with
4,685 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,7 +52,7 @@ jobs: | |
commit_message: "ci: update of files from global .github repo" | ||
bot_branch_name: bot/update-files-from-global-repo | ||
|
||
replicate_go_workflows: | ||
replicate_go_workflows: | ||
if: startsWith(github.repository, 'asyncapi/') | ||
name: Replicate workflows for Go projects | ||
runs-on: ubuntu-latest | ||
|
@@ -139,6 +139,7 @@ jobs: | |
with: | ||
github_token: ${{ secrets.GH_TOKEN }} | ||
patterns_to_include: .github/workflows/scripts,.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml,.github/workflows/add-good-first-issue-labels.yml,.github/workflows/automerge-for-humans-merging.yml,.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml,.github/workflows/automerge-orphans.yml,.github/workflows/automerge.yml,.github/workflows/autoupdate.yml,.github/workflows/help-command.yml,.github/workflows/issues-prs-notifications.yml,.github/workflows/lint-pr-title.yml,.github/workflows/notify-tsc-members-mention.yml,.github/workflows/stale-issues-prs.yml,.github/workflows/welcome-first-time-contrib.yml,.github/workflows/release-announcements.yml,.github/workflows/bounty-program-commands.yml,.github/workflows/please-take-a-look-command.yml,.github/workflows/update-pr.yml | ||
patterns_to_ignore: .github/workflows/scripts/maintainers | ||
committer_username: asyncapi-bot | ||
committer_email: [email protected] | ||
commit_message: "ci: update of files from global .github repo" | ||
|
@@ -162,7 +163,7 @@ jobs: | |
committer_email: [email protected] | ||
commit_message: "ci: update of files from global .github repo" | ||
bot_branch_name: bot/update-files-from-global-repo | ||
|
||
replicate_validate_workflow_schema: | ||
if: startsWith(github.repository, 'asyncapi/') | ||
name: Replicate workflow schema validation to repositories | ||
|
@@ -179,8 +180,8 @@ jobs: | |
committer_username: asyncapi-bot | ||
committer_email: [email protected] | ||
commit_message: "ci: update of files from global .github repo" | ||
bot_branch_name: bot/update-files-from-global-repo | ||
bot_branch_name: bot/update-files-from-global-repo | ||
|
||
replicate_docs_workflow: | ||
if: startsWith(github.repository, 'asyncapi/') | ||
name: Replicate update-docs workflow to repositories | ||
|
@@ -190,7 +191,7 @@ jobs: | |
uses: actions/checkout@v4 | ||
- name: Replicating file | ||
uses: derberg/manage-files-in-multiple-repositories@beecbe897cf5ed7f3de5a791a3f2d70102fe7c25 | ||
with: | ||
with: | ||
github_token: ${{ secrets.GH_TOKEN }} | ||
patterns_to_include: .github/workflows/update-docs-on-docs-commits.yml | ||
topics_to_include: get-global-docs-autoupdate | ||
|
@@ -215,4 +216,21 @@ jobs: | |
committer_username: asyncapi-bot | ||
committer_email: [email protected] | ||
commit_message: "ci: update .prettierignore from global .github repo" | ||
bot_branch_name: bot/update-files-from-global-repo | ||
bot_branch_name: bot/update-files-from-global-repo | ||
|
||
replicate_refresh_maintainers_workflow: | ||
if: startsWith(github.repository, 'asyncapi/') | ||
name: Replicate refresh-maintainers.yml workflow in the required repositories | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
- name: Replicating file | ||
uses: derberg/manage-files-in-multiple-repositories@beecbe897cf5ed7f3de5a791a3f2d70102fe7c25 | ||
with: | ||
github_token: ${{ secrets.GH_TOKEN }} | ||
patterns_to_include: .github/workflows/refresh-maintainers.yaml,.github/workflows/scripts/maintainers | ||
committer_username: asyncapi-bot | ||
committer_email: [email protected] | ||
commit_message: "ci: update refresh-maintainers.yml workflow from global .github repo" | ||
bot_branch_name: bot/update-files-from-global-repo |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
name: Refresh MAINTAINERS.yaml file | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- 'CODEOWNERS' | ||
- '.github/workflows/scripts/maintainers/**' | ||
- '.github/workflows/refresh-maintainers.yaml' | ||
workflow_dispatch: | ||
|
||
concurrency: | ||
group: ${{ github.workflow }} | ||
cancel-in-progress: false | ||
|
||
env: | ||
IGNORED_REPOSITORIES: "github-action-for-cli, shape-up-process" | ||
IGNORED_USERS: "asyncapi-bot-eve" | ||
|
||
GIT_USER: asyncapi-bot | ||
GIT_EMAIL: [email protected] | ||
|
||
BRANCH_NAME: "bot/update-maintainers-${{ github.run_id }}" | ||
PR_TITLE: "docs(maintainers): Update MAINTAINERS.yaml file with the latest CODEOWNERS changes" | ||
|
||
jobs: | ||
update-maintainers: | ||
permissions: | ||
contents: write | ||
pull-requests: write | ||
|
||
name: Update MAINTAINERS.yaml based on CODEOWNERS files in all organization repositories | ||
runs-on: ubuntu-latest | ||
|
||
steps: | ||
- name: Wait for active pull requests to be merged | ||
env: | ||
GH_TOKEN: ${{ github.token }} | ||
TIMEOUT: 300 # Timeout in seconds | ||
INTERVAL: 5 # Check interval in seconds | ||
run: | | ||
check_active_prs() { | ||
ACTIVE_PULL_REQUESTS=$(gh -R $GITHUB_REPOSITORY pr list --search "is:pr ${PR_TITLE} in:title" --json id) | ||
if [ "$ACTIVE_PULL_REQUESTS" == "[]" ]; then | ||
return 1 # No active PRs | ||
else | ||
return 0 # Active PRs found | ||
fi | ||
} | ||
# Loop with timeout | ||
elapsed_time=0 | ||
while [ $elapsed_time -lt $TIMEOUT ]; do | ||
if check_active_prs; then | ||
echo "There is an active pull request. Waiting for it to be merged..." | ||
else | ||
echo "There is no active pull request. Proceeding with refreshing MAINTAINERS file." | ||
exit 0 | ||
fi | ||
sleep $INTERVAL | ||
elapsed_time=$((elapsed_time + INTERVAL)) | ||
done | ||
echo "Timeout reached. Proceeding with refreshing MAINTAINERS file with active pull request(s) present. It may result in merge conflict." | ||
exit 0 | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Setup Node.js | ||
uses: actions/setup-node@v4 | ||
with: | ||
cache: 'npm' | ||
node-version-file: './.github/workflows/scripts/maintainers/package.json' | ||
cache-dependency-path: './.github/workflows/scripts/maintainers/package-lock.json' | ||
|
||
- name: Install dependencies | ||
working-directory: ./.github/workflows/scripts/maintainers | ||
run: npm install | ||
|
||
- name: Restore cached GitHub API calls | ||
uses: actions/cache/restore@v4 | ||
id: restore-cache | ||
with: | ||
path: ./.github/workflows/scripts/maintainers/github.api.cache.json | ||
key: github-api-cache | ||
restore-keys: | | ||
github-api-cache- | ||
- name: Run script updating MAINTAINERS.yaml | ||
working-directory: ./.github/workflows/scripts/maintainers | ||
run: npm run start | ||
env: | ||
GH_TOKEN: ${{ github.token }} | ||
MAINTAINERS_FILE_PATH: "${{ github.workspace }}/MAINTAINERS.yaml" | ||
|
||
- name: Save cached GitHub API calls | ||
uses: actions/cache/save@v4 | ||
with: | ||
path: ./.github/workflows/scripts/maintainers/github.api.cache.json | ||
# re-evaluate the key, so we update cache when file changes | ||
key: github-api-cache-${{ hashfiles('./.github/workflows/scripts/maintainers/github.api.cache.json') }} | ||
|
||
- name: Detect git changes | ||
id: check_changes | ||
run: | | ||
if [[ $(git diff --stat) != '' ]]; then | ||
echo "CHANGES=true" >> $GITHUB_OUTPUT | ||
else | ||
echo '✔ No changes detected. Have a nice day :-)' | ||
fi | ||
- name: Create PR with latest changes | ||
if: steps.check_changes.outputs.CHANGES == 'true' | ||
env: | ||
GH_TOKEN: ${{ github.token }} | ||
run: | | ||
git config --global user.name "${{ env.GIT_USER }}" | ||
git config --global user.email ${{ env.GIT_EMAIL }} | ||
git checkout -b "${{ env.BRANCH_NAME }}" | ||
git add -A | ||
git commit -m "${{ env.PR_TITLE }}" | ||
git push origin "${{ env.BRANCH_NAME }}" -f | ||
gh pr create --title "${{ env.PR_TITLE }}" --body "Updated MAINTAINERS.yaml based on CODEOWNERS files in all organization repositories" --head ${{ env.BRANCH_NAME }} | ||
- name: Report workflow run status to Slack | ||
uses: rtCamp/action-slack-notify@4e5fb42d249be6a45a298f3c9543b111b02f7907 # https://github.com/rtCamp/action-slack-notify/releases/tag/v2.3.0 | ||
if: failure() | ||
env: | ||
SLACK_WEBHOOK: ${{secrets.SLACK_CI_FAIL_NOTIFY}} | ||
SLACK_TITLE: 🚨 Refresh MAINTAINERS.yaml file Workflow failed 🚨 | ||
SLACK_MESSAGE: Failed to refresh MAINTAINERS.yaml file. | ||
MSG_MINIMAL: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
env: | ||
es6: true | ||
node: true | ||
commonjs: true | ||
globals: | ||
Atomics: readonly | ||
SharedArrayBuffer: readonly | ||
|
||
ignorePatterns: | ||
- "!.*" | ||
- "**/node_modules/.*" | ||
- "**/dist/.*" | ||
- "**/coverage/.*" | ||
- "*.json" | ||
|
||
parserOptions: | ||
ecmaVersion: 2023 | ||
sourceType: module | ||
requireConfigFile: false | ||
|
||
extends: | ||
- eslint:recommended | ||
- plugin:github/recommended | ||
|
||
rules: | ||
{ | ||
"camelcase": "off", | ||
"eslint-comments/no-use": "off", | ||
"eslint-comments/no-unused-disable": "off", | ||
"i18n-text/no-en": "off", | ||
"import/no-commonjs": "off", | ||
"import/no-namespace": "off", | ||
"no-console": "off", | ||
"no-unused-vars": "off", | ||
"prettier/prettier": "error", | ||
"semi": "off", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/node_modules | ||
github.api.cache.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
dist/ | ||
node_modules/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Maintainers Refresher | ||
|
||
The ["Refresh MAINTAINERS.yaml file"](../../refresh-maintainers.yaml) workflow, defined in the `community` repository performs a complete refresh by fetching all public repositories under AsyncAPI and their respective `CODEOWNERS` files. | ||
|
||
### Workflow Steps | ||
|
||
1. **Load Cache**: Attempt to read previously cached data from `github.api.cache.json` to optimize API calls. | ||
2. **List All Repositories**: Retrieve a list of all public repositories under the AsyncAPI organization, skipping any repositories listed in the `IGNORED_REPOSITORIES` environment variable. | ||
3. **Fetch `CODEOWNERS` Files**: For each repository: | ||
- Detect the default branch (e.g., `main`, `master`, or a custom branch). | ||
- Check for `CODEOWNERS` files in all valid locations as specified in the [GitHub documentation](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-file-location). | ||
4. **Process `CODEOWNERS` Files**: | ||
1. Extract GitHub usernames from each `CODEOWNERS` file, excluding emails, team names, and users listed by the `IGNORED_USERS` environment variable. | ||
2. Retrieve profile information for each unique GitHub username. | ||
3. Collect a fresh list of repositories currently owned by each GitHub user. | ||
5. **Refresh Maintainers List**: Iterate through the existing maintainers list: | ||
- Delete the entry if it: | ||
- Refers to a deleted GitHub account. | ||
- Was not found in any `CODEOWNERS` file across all repositories in the AsyncAPI organization. | ||
- Otherwise, update only the `repos` property. | ||
6. **Add New Maintainers**: Append any new maintainers not present in the previous list. | ||
7. **Changes Summary**: Provide details on why a maintainer was removed or changed directly on the GitHub Action [summary page](https://github.blog/2022-05-09-supercharging-github-actions-with-job-summaries/). | ||
8. **Save Cache**: Save retrieved data in `github.api.cache.json`. | ||
|
||
## Workflow Execution | ||
|
||
The "Refresh MAINTAINERS.yaml file" workflow is executed in the following scenarios: | ||
1. **Weekly Schedule**: The workflow runs automatically every week. | ||
2. **On-Demand**: When a `CODEOWNERS` file is changed in any repository under the AsyncAPI organization, the source repository triggers the workflow by emitting an event. | ||
3. **Manual Trigger**: Users can manually trigger the workflow as needed. | ||
|
||
## Job Details | ||
|
||
- **Concurrency**: Ensures the workflow does not run multiple times concurrently to avoid conflicts. | ||
- **Wait for PRs to be Merged**: The workflow waits for pending pull requests to be merged before execution. If the merged pull request addresses all necessary fixes, it prevents unnecessary executions. | ||
|
||
## Handling Conflicts | ||
|
||
Since the job performs a full refresh each time, resolving conflicts is straightforward: | ||
|
||
1. Close the pull request with conflicts. | ||
2. Navigate to the "Refresh MAINTAINERS.yaml file" workflow. | ||
3. Trigger it manually by clicking "Run workflow". | ||
|
||
## Caching Mechanism | ||
|
||
Each execution of this action performs a full refresh through the following API calls: | ||
|
||
``` | ||
ListRepos(AsyncAPI) # 1 call using GraphQL - not cached. | ||
for each Repo | ||
GetCodeownersFile(Repo) # N calls using REST API - all are cached. N refers to the number of public repositories under AsyncAPI. | ||
for each codeowner | ||
GetGitHubProfile(owner) # Y calls using REST API - all are cached. Y refers to unique GitHub users found across all CODEOWNERS files. | ||
``` | ||
|
||
To avoid hitting the GitHub API rate limits, [conditional requests](https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api?apiVersion=2022-11-28#use-conditional-requests-if-appropriate) are used via `if-modified-since`. The API responses are saved into a `github.api.cache.json` file, which is later uploaded as a GitHub action cache item. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
const fs = require("fs"); | ||
const core = require("@actions/core"); | ||
|
||
module.exports = { | ||
fetchWithCache, | ||
saveCache, | ||
loadCache, | ||
printAPICallsStats, | ||
}; | ||
|
||
const CODEOWNERS_CACHE_PATH = "github.api.cache.json"; | ||
|
||
let cacheEntries = {}; | ||
|
||
let numberOfFullFetches = 0; | ||
let numberOfCacheHits = 0; | ||
|
||
async function loadCache() { | ||
try { | ||
cacheEntries = JSON.parse(fs.readFileSync(CODEOWNERS_CACHE_PATH, "utf8")); | ||
} catch (error) { | ||
core.warning(`Cache was not restored: ${error}`); | ||
} | ||
} | ||
|
||
async function saveCache() { | ||
fs.writeFileSync(CODEOWNERS_CACHE_PATH, JSON.stringify(cacheEntries)); | ||
} | ||
|
||
async function fetchWithCache(cacheKey, fetchFn) { | ||
const cachedResp = cacheEntries[cacheKey]; | ||
|
||
try { | ||
const { data, headers } = await fetchFn({ | ||
headers: { | ||
"if-modified-since": cachedResp?.lastModified ?? "", | ||
}, | ||
}); | ||
|
||
cacheEntries[cacheKey] = { | ||
// last modified header is more reliable than etag while executing calls on GitHub Action | ||
lastModified: headers["last-modified"], | ||
data, | ||
}; | ||
|
||
numberOfFullFetches++; | ||
return data; | ||
} catch (error) { | ||
if (error.status === 304) { | ||
numberOfCacheHits++; | ||
core.debug(`Returning cached data for ${cacheKey}`); | ||
return cachedResp.data; | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
function printAPICallsStats() { | ||
core.startGroup("API calls statistic"); | ||
core.info( | ||
`Number of API calls count against rate limit: ${numberOfFullFetches}`, | ||
); | ||
core.info(`Number of cache hits: ${numberOfCacheHits}`); | ||
core.endGroup(); | ||
} |
Oops, something went wrong.