diff --git a/.github/workflows/maintainer_management.yml b/.github/workflows/maintainer_management.yml new file mode 100644 index 000000000..662811c21 --- /dev/null +++ b/.github/workflows/maintainer_management.yml @@ -0,0 +1,270 @@ +name: Maintainer Management Workflow + +on: + pull_request: + types: [closed] + paths: + - 'MAINTAINERS.yaml' +env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + +jobs: + detect_maintainer_changes: + if: github.event.pull_request.merged + runs-on: ubuntu-latest + steps: + - name: Checkout main branch + uses: actions/checkout@v3 + with: + ref: master + path: community-main + - name: List of directory + run: ls -la + - name: Checkout one commit before last one + uses: actions/checkout@v3 + with: + fetch-depth: 2 + ref: master + path: community + - name: List of directory + run: ls -la + - run: cd community && git checkout HEAD^ + - name: Install dependencies + run: npm install js-yaml@4.1.0 + - name: Compare files + id: compare-files + uses: actions/github-script@v6 + with: + script: | + const fs = require("fs"); + const yaml = require('js-yaml'); + + const currentMaintainers = yaml.load(fs.readFileSync('./community-main/MAINTAINERS.yaml', 'utf8')); + const previousMaintainers = yaml.load(fs.readFileSync('./community/MAINTAINERS.yaml', 'utf8')); + + const removed = previousMaintainers.filter( + (newObj) => !currentMaintainers.some((oldObj) => oldObj.github === newObj.github) + ); + const added = currentMaintainers.filter( + (oldObj) => !previousMaintainers.some((newObj) => newObj.github === oldObj.github) + ); + + if (added.length > 0) { + core.setOutput("newMaintainers", added.map((obj) => obj.github).join(",")); + } + if (removed.length > 0) { + core.setOutput("removedMaintainers", removed.map((obj) => obj.github).join(",")); + } + + const removedTscMembers = pr_file.filter( + (obj) => !currentMaintainers.some((mainObj) => mainObj.github === obj.github) && obj.isTscMember === true + ); + + if (removedTscMembers.length > 0) { + core.setOutput("removedTscMembers", removedTscMembers.map((obj) => obj.github).join(",")); + core.info(`Removed TSC Members: ${removedTscMembers.map((obj) => obj.github).join(",")}`); + } + + // Log information for debugging + core.info('Maintainers in main branch:\n' + yaml.dump(currentMaintainers)); + core.info('Location of Maintainers in main branch:'); + core.info(fs.realpathSync('./community-main/MAINTAINERS.yaml')); + core.info('Maintainers in PR branch:\n' + yaml.dump(previousMaintainers)); + core.info('Location of Maintainers in PR branch:'); + core.info(fs.realpathSync('./community/MAINTAINERS.yaml')); + + - name: Debug newMaintainers output + run: | + echo "newMaintainers = $newMaintainers" + + - name: Debug removedMaintainers output + run: | + echo "removedMaintainers = $removedMaintainers" + + outputs: + newMaintainers: ${{ steps.compare-files.outputs.newMaintainers }} + removedMaintainers: ${{ steps.compare-files.outputs.removedMaintainers }} + removedTscMembers: ${{ steps.compare-files.outputs.removedTscMembers }} + + add_maintainer: + needs: detect_maintainer_changes + if: needs.detect_maintainer_changes.outputs.newMaintainers != '' + runs-on: ubuntu-latest + steps: + - name: Invite new maintainers to the organization + uses: actions/github-script@v6 + with: + github-token: ${{ env.GH_TOKEN }} + script: | + const newMaintainers = '${{ needs.detect_maintainer_changes.outputs.newMaintainers }}'.split(','); + for (const maintainer of newMaintainers) { + try { + await github.request('PUT /orgs/{org}/memberships/{username}', { + org: 'asyncapi', + username: maintainer + }); + } catch (error) { + core.setFailed(`Failed to add ${maintainer} to the organization: ${error.message}`); + } + } + + - name: Add new maintainers to the team + uses: actions/github-script@v6 + with: + github-token: ${{ env.GH_TOKEN }} + script: | + const newMaintainers = '${{ needs.detect_maintainer_changes.outputs.newMaintainers }}'.split(','); + for (const maintainer of newMaintainers) { + try { + await github.request('PUT /orgs/{org}/teams/{team_slug}/memberships/{username}', { + org: 'asyncapi', + team_slug: 'maintainers', + username: maintainer + }); + } catch (error) { + core.setFailed(`Failed to add ${maintainer} to the team: ${error.message}`); + } + } + + outputs: + newMaintainers: ${{needs.detect_maintainer_changes.outputs.newMaintainers }} + + display_message: + needs: add_maintainer + if: needs.add_maintainer.outputs.newMaintainers != '' + runs-on: ubuntu-latest + steps: + - name: Display welcome message for new maintainers + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const newMaintainers = "${{ needs.add_maintainer.outputs.newMaintainers }}".split(","); + console.log(`New maintainers: ${newMaintainers}`); + const welcomeMessage = newMaintainers.map((maintainer) => `@${maintainer.trim().replace(/^@/, '')} I have invited you to join the AsyncAPI organization, and you are added to the team that lists all Maintainers.\n + + Please take a moment to verify if you would like to provide more details for your account, such as your LinkedIn profile, Twitter handle, or company affiliation. In case there is anything you want to add, please open up a separate pull request for the \`MAINTAINERS.yaml\` file.\n + + Additionally, if you are interested in becoming a [TSC member](https://github.com/amadeus4dev-examples/amadeus-async-flight-status) and contributing to the direction of the AsyncAPI Initiative, let us know, and we'll be happy to guide you through the process. Every maintainer has the right to become a TSC member, just open a pull request to modify your entry in \`MAINTAINER.yaml\` file and make sure \`isTscMember\` property is set to \`true\`. You can find some basic information about TSC membership in [this TSC-related guide](https://github.com/asyncapi/community/blob/master/TSC_MEMBERSHIP.md). Feel free to reach out to us for more help by dropping a comment in this pull request, or by asking for help in our Slack.\n + + Welcome aboard! We are excited to have you as part of the team.`).join("\n"); + + const { owner, repo } = context.repo; + const { number: issue_number } = context.issue; + return github.rest.issues.createComment({ owner, repo, issue_number, body: welcomeMessage }); + + remove_maintainer: + needs: detect_maintainer_changes + if: needs.detect_maintainer_changes.outputs.removedMaintainers != '' + runs-on: ubuntu-latest + steps: + - name: Remove maintainers from the organization + uses: actions/github-script@v6 + with: + github-token: ${{ env.GH_TOKEN }} + script: | + const removedMaintainers = '${{ needs.detect_maintainer_changes.outputs.removedMaintainers }}'.split(','); + for (const maintainer of removedMaintainers) { + try { + await github.request('DELETE /orgs/asyncapi/memberships/{username}', { + username: maintainer + }); + core.info(`Successfully removed ${maintainer} from the organization.`); + } catch (error) { + core.setFailed(`Failed to remove ${maintainer} from the organization: ${error.message}`); + } + } + + outputs: + removedMaintainers: ${{ needs.detect_maintainer_changes.outputs.removedMaintainers }} + removedTscMembers: ${{ needs.detect_maintainer_changes.outputs.removedTscMembers }} + + remove_maintainer_goodbye: + needs: remove_maintainer + if: needs.remove_maintainer.outputs.removedMaintainers != '' + runs-on: ubuntu-latest + steps: + - name: Display goodbye message to removed maintainers + uses: actions/github-script@v6 + with: + github-token: ${{ env.GH_TOKEN }} + script: | + const removedMaintainers = "${{ needs.remove_maintainer.outputs.removedMaintainers }}".split(","); + const removedTscMembers = "${{ needs.remove_maintainer.outputs.removedTscMembers }}".split(","); + + // Goodbye message to removed maintainers and notification to TSC members + const combinedMessages = removedMaintainers.map((maintainer) => { + const tscNotification = removedTscMembers.includes(maintainer) + ? `@asyncapi/tsc_members We want to inform you that @${maintainer.trim().replace(/^@/, '')} is no longer a maintainer of any repository under AsyncAPI Initiative. It means this maintainer is also no longer a member of TSC.` + : ''; + return `@${maintainer.trim().replace(/^@/, '')} We wanted to express our gratitude for your contributions as a maintainer of AsyncAPI Initiative. Your efforts have been immensely valuable to us, and we truly appreciate your dedication. Thank you once again, and we wish you all the best in your future endeavors!\n\n${tscNotification}`; + }); + + const { owner, repo } = context.repo; + const { number: issue_number } = context.issue; + for (const message of combinedMessages) { + github.rest.issues.createComment({ owner, repo, issue_number, body: message }); + } + + + update_emeritus: + needs: detect_maintainer_changes + if: needs.detect_maintainer_changes.outputs.removedTscMembers != '' + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + + - name: Add TSC members to Emeritus.yaml and print + uses: actions/github-script@v6 + with: + github-token: ${{ env.GH_TOKEN }} + script: | + const fs = require('fs'); + const path = './Emeritus.yaml'; + + // Read the current content of the file + let content = fs.readFileSync(path, 'utf8').trim(); // remove any trailing whitespaces + + // Split the removed maintainers and prepare them for the yaml format + const removedTscMembers = "${{ needs.detect_maintainer_changes.outputs.removedTscMembers }}".split(',') + .map(maintainer => ` - ${maintainer.trim()}`) + .join('\n'); + + // Append the removed maintainers to the file content + if (removedTscMembers) { + content = content + '\n' + removedTscMembers; + } + + // Write the updated content back to the file + fs.writeFileSync(path, content); + + // Log the updated content to the console + core.info('Updated Emeritus.yaml:\n', content); + + - name: Create new branch + run: | + git checkout -b update-emeritus-${{ github.run_id }} + + - name: Commit and push + run: | + git add . + git commit -m "Update Emeritus.yaml" + git push https://${{ secrets.GH_TOKEN}}@github.com/asyncapi/community update-emeritus-${{ github.run_id }} + + - name: Create PR + run: | + gh pr create --title "docs(community): update latest emeritus list" --body "Updated Emeritus list is available and this PR introduces changes with latest information about Emeritus" --head update-emeritus-${{ github.run_id }} + + notify_slack_on_failure: + if: always() && (needs.detect_maintainer_changes.result == 'failure' || needs.add_maintainer.result == 'failure' || needs.display_message.result == 'failure' || needs.remove_maintainer.result == 'failure' || needs.update_emeritus.result == 'failure' || needs.remove_maintainer_goodbye.result == 'failure' ) + needs: [detect_maintainer_changes, add_maintainer.result, display_message, remove_maintainer,remove_maintainer_goodbye, update_emeritus] + runs-on: ubuntu-latest + steps: + - name: Report workflow run status to Slack + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_CI_FAIL_NOTIFY}} + SLACK_TITLE: 🚨 Welcome new contributor action failed 🚨 + SLACK_MESSAGE: Failed to post a message to new Maintainer + MSG_MINIMAL: true diff --git a/.github/workflows/maintainers-tsc-changes-verification.yaml b/.github/workflows/maintainers-tsc-changes-verification.yaml new file mode 100644 index 000000000..4b6c01639 --- /dev/null +++ b/.github/workflows/maintainers-tsc-changes-verification.yaml @@ -0,0 +1,154 @@ +name: Verify tsc and maintainers changes by human and bot + +on: + pull_request: + types: [synchronize, opened, reopened] + paths: + - "MAINTAINERS.yaml" + +jobs: + verify-changes: + # if statement to check if the PR is open. + if: github.event.pull_request.state == 'open' + runs-on: ubuntu-latest + steps: + - name: Checkout main branch + uses: actions/checkout@v3 + with: + ref: master + path: community-main + + - name: Checkout PR branch + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + path: pr-branch + + - name: Install js-yaml + run: npm install js-yaml@4.1.0 + + - name: Verify changes in MAINTAINERS.yaml + id: verify-changes + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN}} + script: | + const yaml = require("js-yaml"); + const fs = require("fs"); + + const mainFile = yaml.load(fs.readFileSync("./community-main/MAINTAINERS.yaml", "utf8")); + const prFile = yaml.load(fs.readFileSync("./pr-branch/MAINTAINERS.yaml", "utf8")); + + const beforeMaintainers = new Map(mainFile.map((maintainer) => [maintainer.name, {github: maintainer.github, repos: maintainer.repos || []}])); + let errorMessages = []; + + const owner = context.repo.owner; + const repo = context.repo.repo; + const pull_number = context.issue.number; + const author = context.payload.pull_request.user.login; + let removedTscMembers = []; + + // If the PR is made by the bot. + if (author === 'asyncapi-bot') { + console.log('Changes made by asyncapi-bot) + + const removedMaintainers = mainFile.filter( + (mainMaintainer) => !prFile.some((maintainer) => mainMaintainer.github === maintainer.github) + ); + removedTscMembers = removedMaintainers.filter(maintainer => maintainer.isTscMember); + + if (removedTscMembers.length > 0) { + core.setOutput("removedTscMembers", JSON.stringify(removedTscMembers)); + } + else { + return + } + } + + // detecting if changes in the PR contain removal of maintainer object + if (prFile.length < mainFile.length) { + errorMessages.push('A maintainer has been removed from `MAINTAINERS.yaml` file. Only `asyncapi-bot` can make such changes. Maintainers are removed from the file in an automated way only if they are no longer mentioned in `CODEOWNERS` file in any repository under AsyncAPI GitHub organization.'); + } + + for (const maintainer of prFile) { + // retrieve the previous data of the maintainer from the main file + const previousData = beforeMaintainers.get(maintainer.name); + // if the maintainer is not found in the previous data, it is a new maintainer + if (!previousData) { + errorMessages.push(`A new maintainer, ${maintainer.name}, has been added to MAINTAINERS.yaml. Only asyncapi-bot can make such changes. Maintainers are added to the file in an automated way only if they are mentioned in the CODEOWNERS file in any repository under AsyncAPI GitHub organization.`); + } else { + // retrieve the previous GitHub key and repositories of the maintainer + const previousGithub = previousData.github; + const previousRepos = previousData.repos; + // check if the GitHub key for the maintainer has been modified + if (previousGithub !== maintainer.github) { + errorMessages.push(`GitHub key for ${maintainer.name} has been modified in MAINTAINERS.yaml. Only asyncapi-bot can make such changes. This information is derived from the CODEOWNERS file located in any repository under AsyncAPI GitHub organization.`); + } + // check if the repositories list for the maintainer has been modified + if (previousRepos.toString() !== (maintainer.repos || []).toString()) { + // Check if a human added a repo to the maintainer's repository list + const previousReposLength = previousData.repos.length; + const currentReposLength = (maintainer.repos || []).length; + // Check if a repository is added by a human + if (currentReposLength > previousReposLength) { + const addedRepos = (maintainer.repos || []).slice(previousReposLength); + errorMessages.push(`New repositories (${addedRepos.join(', ')}) have been added to ${maintainer.name}'s repository list in MAINTAINERS.yaml. Only asyncapi-bot can make such changes. This information is derived from the CODEOWNERS file located in any repository under AsyncAPI GitHub organization.`); + } + // Check if a repository is removed by a human + if (currentReposLength < previousReposLength) { + const removedRepos = previousData.repos.slice(currentReposLength); + errorMessages.push(`Repositories (${removedRepos.join(', ')}) have been removed from ${maintainer.name}'s repository list in MAINTAINERS.yaml. Only asyncapi-bot can make such changes. This information is derived from the CODEOWNERS file located in any repository under AsyncAPI GitHub organization.`); + } + } + } + } + // Console log the error messages + console.log('Error messages:', errorMessages); + // Set the error messages as an output + console.log('Setting error messages output...'); + core.setOutput('errorMessages', JSON.stringify(errorMessages)); + + outputs: + errorMessages: ${{ steps.verify-changes.outputs.errorMessages }} + removedTscMembers: ${{ steps.verify-changes.outputs.removedTscMembers }} + + handle-human-made-critical-changes: + needs: verify-changes + if: needs.verify-changes.outputs.errorMessages != '[]' + runs-on: ubuntu-latest + steps: + - name: Comment and close the PR if there are are any critical changes done by human + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const rawErrorMessages = `${{ needs.verify-changes.outputs.errorMessages }}`; + const owner = context.repo.owner; + const repo = context.repo.repo; + const pull_number = context.issue.number; + + const errorMessages = JSON.parse(rawErrorMessages); + + const commentBody = errorMessages.join('\n'); + github.rest.issues.createComment({ owner, repo, issue_number: pull_number, body: commentBody }); + github.rest.pulls.update({ owner, repo, pull_number, state: 'closed' }); + + bot-removal-notification: + needs: verify-changes + if: needs.verify-changes.outputs.removedTscMembers != '' + runs-on: ubuntu-latest + steps: + - name: Comment on PR if TSC member is removed by asyncapi-bot + if: steps.verify-changes.outputs.removedTscMembers != '' + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN}} + script: | + const github = require('@actions/github') + const issueComment = { + owner: github.context.repo.owner, + repo: github.context.repo.repo, + issue_number: github.context.issue.number, + body: 'A TSC member has been removed in this PR. Maintainers of this repository need to review and approve this PR.' + } + github.rest.issues.createComment(issueComment); \ No newline at end of file diff --git a/.github/workflows/msg-to-new-member-pr-merged.yml b/.github/workflows/msg-to-new-member-pr-merged.yml deleted file mode 100644 index 1d24c861e..000000000 --- a/.github/workflows/msg-to-new-member-pr-merged.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Welcome New Contributor - -on: - pull_request: - types: [closed] - paths: - - 'MAINTAINERS.yaml' - -jobs: - welcome: - if: github.event.pull_request.merged - runs-on: ubuntu-latest - steps: - - name: Checkout main branch - uses: actions/checkout@v3 - with: - ref: master - path: community-main - - name: List of directory - run: ls -la - - name: Checkout one commit before last one - uses: actions/checkout@v3 - with: - fetch-depth: 2 - ref: master - path: community - - name: List of directory - run: ls -la - - run: cd community && git checkout HEAD^ - - name: Install dependencies - run: npm install js-yaml@4.1.0 - - name: Compare files - id: compare-files - uses: actions/github-script@v6 - with: - script: | - const fs = require("fs"); - const yaml = require('js-yaml'); - - const main_file = yaml.load('./community-main/MAINTAINERS.yaml', 'utf8'); - const pr_file = yaml.load('./community/MAINTAINERS.yaml', 'utf8'); - const removed = pr_file.filter( - (newObj) => !main_file.some((oldObj) => oldObj.github === newObj.github) - ); - const added = main_file.filter( - (oldObj) => !pr_file.some((newObj) => newObj.github === oldObj.github) - ); - if(added.length > 0) { - core.info(`Added Contributors:\n${yaml.dump(added)}`); - core.setOutput("newContributors", added.map((obj) => obj.github).join(",")); - } - if(removed.length > 0) { - core.info(`Removed Contributors:\n${yaml.dump(removed)}`); - core.setOutput("removed", "true"); - } - core.info('Maintainers in main branch:\n' + yaml.dump(main_file)); - core.info('Location of Maintainers in main branch:'); - core.info(fs.realpathSync('./community-main/MAINTAINERS.yaml')); - core.info('Maintainers in PR branch:\n' + yaml.dump(pr_file)); - core.info('Location of Maintainers in PR branch:'); - core.info(fs.realpathSync('./community/MAINTAINERS.yaml')); - - - name: Set newMaintainers as environment variable - run: echo "newMaintainers=${{ steps.compare-files.outputs.newMaintainers }}" >> $GITHUB_ENV - - name: Debug newMaintainers output - run: | - echo "newMaintainers = $newMaintainers" - - name: Display welcome message - if: env.newMaintainers != '' - uses: actions/github-script@v6 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const newMaintainers = process.env.newMaintainers.split(","); - const welcomeMessage = newMaintainers.map((maintainer) => `@${maintainer.trim().replace(/^@/, '')} I have invited you to join the AsyncAPI organization and you will soon be added to the team that lists all Maintainers. - We use this team to mention in different discussions in different places in GitHub where Maintainer's opinion is needed or even voting on some topic. Once Maintainers are mentioned: - - You get GitHub notification - - We also drop notification in our slack in #95_bot-maintainers-mentioned channel - - We drop an email to people that subscribed to Maintainer news here https://www.asyncapi.com/community/maintainers - Pick the channel for notifications that you prefer. Welcome aboard! We are excited to have you as part of the team.`).join("\n"); - const { owner, repo } = context.repo; - const { number: issue_number } = context.issue; - return github.rest.issues.createComment({ owner, repo, issue_number, body: welcomeMessage }); - - if: failure() # Only, on failure, send a message on the 94_bot-failing-ci slack channel - name: Report workflow run status to Slack - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_CI_FAIL_NOTIFY}} - SLACK_TITLE: 🚨 Welcome new contributor action failed 🚨 - SLACK_MESSAGE: Failed to post a message to new Maintainer - MSG_MINIMAL: true \ No newline at end of file diff --git a/.github/workflows/tsc_management.yml b/.github/workflows/tsc_management.yml new file mode 100644 index 000000000..98f5b7f5d --- /dev/null +++ b/.github/workflows/tsc_management.yml @@ -0,0 +1,259 @@ +name: TSC Management Workflow + +on: + pull_request: + types: [closed] + paths: + - 'MAINTAINERS.yaml' + +jobs: + detect_tsc_membership_changes: + if: github.event.pull_request.merged + name: Update TSC Member + runs-on: ubuntu-latest + + steps: + - name: Checkout main branch + uses: actions/checkout@v2 + with: + ref: master + path: community-main + + - name: Checkout one commit before last one + uses: actions/checkout@v2 + with: + fetch-depth: 2 + ref: master + path: community + + - run: cd community && git checkout HEAD^ + + - name: Install js-yaml + run: npm install js-yaml@4.1.0 + + - name: Compare files + id: compare-files + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const yaml = require('js-yaml'); + + const mainFile = yaml.load(fs.readFileSync('./community-main/MAINTAINERS.yaml', 'utf8')); + const prFile = yaml.load(fs.readFileSync('./community/MAINTAINERS.yaml', 'utf8')); + + // Variables to store the updated value and GitHub user + let updatedMaintainers = []; + let updatedValue; + + // Function to check if isTscMember value has changed + function hasIsTscMemberChanged(maintainerGithub) { + const mainMaintainer = mainFile.find(m => m.github === maintainerGithub); + const prMaintainer = prFile.find(m => m.github === maintainerGithub); + + core.info(`Checking for ${maintainerGithub}`); + + if (!mainMaintainer || !prMaintainer) { + console.error('Maintainer not found:', maintainerGithub); + return; + } + + core.info(`${maintainerGithub} in mainFile has isTscMember as:`, mainMaintainer.isTscMember); + core.info(`${maintainerGithub} in prFile has isTscMember as:`, prMaintainer.isTscMember); + + if (mainMaintainer.isTscMember !== prMaintainer.isTscMember) { + core.info(`isTscMember value changed for ${maintainerGithub}`); + updatedMaintainers.push({ githubUser: maintainerGithub, updatedValue: mainMaintainer.isTscMember }); + updatedValue = mainMaintainer.isTscMember; + } + } + + // Loop over all maintainers and find the changes + mainFile.forEach(maintainer => hasIsTscMemberChanged(maintainer.github)); + + // Log final results + core.info("Final updatedValue:", updatedValue); + core.info("Final updatedMaintainers:", JSON.stringify(updatedMaintainers)); + + // Set outputs + core.setOutput("updatedValue", updatedValue); + core.setOutput("updatedMaintainers", JSON.stringify(updatedMaintainers)); + outputs: + updatedValue: ${{ steps.compare-files.outputs.updatedValue }} + updatedMaintainers: ${{ steps.compare-files.outputs.updatedMaintainers }} + + add_tsc_member: + needs: detect_tsc_membership_changes + if: needs.detect_tsc_membership_changes.outputs.updatedValue == 'true' + runs-on: ubuntu-latest + steps: + - name: Add new TSC members to the team + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const newTscMembers = ${{ needs.detect_tsc_membership_changes.outputs.updatedMaintainers }}; + for (const tscMember of newTscMembers) { + try { + await github.request('PUT /orgs/asyncapi/teams/tsc_members/memberships/{username}', { + username: tscMember.githubUser + }); + core.info(`Successfully added ${tscMember.githubUser} to the team.`); + } catch (error) { + core.setFailed(`Failed to add ${tscMember.githubUser} to the team: ${error.message}`); + } + } + outputs: + newTscMember: ${{ needs.detect_tsc_membership_changes.outputs.updatedMaintainers }} + + display_message: + needs: add_tsc_member + if: needs.add_tsc_member.outputs.newTscMember != '' + runs-on: ubuntu-latest + steps: + - name: Filter GitHub users with updatedValue + id: filter_users + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const newTscMembers = JSON.parse('${{ needs.add_tsc_member.outputs.newTscMember }}'); + const filteredUsers = newTscMembers.filter(user => user.updatedValue === true).map(user => user.githubUser); + core.setOutput('filteredUsers', JSON.stringify(filteredUsers)); + + - name: Display welcome message to new TSC members + if: steps.filter_users.outputs.filteredUsers != '[]' + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const newTscMembers = ${{ steps.filter_users.outputs.filteredUsers }}; + console.log(`New TSC members: ${newTscMembers}`); + const welcomeMessage = newTscMembers.map(tscMember => { + return `@${tscMember.trim().replace(/^@/, '')} Welcome to the AsyncAPI Initiative's TSC Teams! + We value your expertise and look forward to collaborating with you. Feel free to engage in discussions and share your ideas with the TSC. + If you have any questions, reach out on Slack or comment on this pull request. + We use this team to mention in different discussions in different places in GitHub where Maintainer's opinion is needed or even voting on some topic. Once Maintainers are mentioned: + - You get GitHub notification + - We also drop notification in our slack in #95_bot-maintainers-mentioned channel + - We drop an email to people that subscribed to Maintainer news here https://www.asyncapi.com/community/maintainers + Pick the channel for notifications that you prefer. Welcome aboard! We are excited to have you as part of the team.` + }).join('\n\n'); + + const { owner, repo } = context.repo; + const { number: issue_number } = context.issue; + github.rest.issues.createComment({ owner, repo, issue_number, body: welcomeMessage }); + + remove_tsc_member: + needs: detect_tsc_membership_changes + if: needs.detect_tsc_membership_changes.outputs.updatedValue == 'false' + runs-on: ubuntu-latest + steps: + - name: Remove TSC members from the team + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const removedMaintainers = ${{ needs.detect_tsc_membership_changes.outputs.updatedMaintainers }}; + for (const maintainer of removedMaintainers) { + try { + await github.request('DELETE /orgs/asyncapi/teams/tsc_members/memberships/{username}', { + username: maintainer.githubUser + }); + core.info(`Successfully removed ${maintainer.githubUser} from the team.`); + } catch (error) { + core.setFailed(`Failed to remove ${maintainer.githubUser} from the team: ${error.message}`); + } + } + outputs: + removedMaintainers: ${{ needs.detect_tsc_membership_changes.outputs.updatedMaintainers }} + + remove_tsc_goodbye: + needs: remove_tsc_member + if: needs.remove_tsc_member.outputs.removedMaintainers != '' + runs-on: ubuntu-latest + steps: + - name: Display goodbye message to removed TSC members + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const removedMaintainers = JSON.parse('${{ needs.remove_tsc_member.outputs.removedMaintainers }}'); + const removedTscMembers = removedMaintainers.map(maintainer => maintainer.githubUser); + + // Goodbye message to removed TSC members + const tscMessages = removedTscMembers.map(tscMember => { + return `@asyncapi/tsc_members We want to inform you that @${tscMember.trim().replace(/^@/, '')} is no longer a maintainer of any repository under AsyncAPI Initiative. It means this maintainer is also no longer a member of TSC.`; + }); + + const { owner, repo } = context.repo; + const { number: issue_number } = context.issue; + for (const message of tscMessages) { + github.rest.issues.createComment({ owner, repo, issue_number, body: message }); + } + + update_emeritus: + needs: remove_tsc_member + if: needs.remove_tsc_member.outputs.removedMaintainers != '' + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v2 + + - name: Add Former TSC members to Emeritus.yaml and print + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const fs = require('fs'); + const path = './Emeritus.yaml'; + + // Read the current content of the file + let content = fs.readFileSync(path, 'utf8').trim(); // remove any trailing whitespaces + + // Parse the removedMaintainers JSON string to an array of objects + const removedMaintainers = JSON.parse('${{ needs.remove_tsc_member.outputs.removedMaintainers }}'); + + // Filter the maintainers whose updatedValue is false and prepare them for the yaml format + const removedTscMembers = removedMaintainers + .filter(maintainer => maintainer.updatedValue === false) + .map(maintainer => ` - ${maintainer.githubUser.trim()}`) + .join('\n'); + + // Append the added maintainers to the file content + if (removedTscMembers) { + content = content + '\n' + removedTscMembers; + } + + // Write the updated content back to the file + fs.writeFileSync(path, content); + + // Log the updated content to the console + console.log('Updated Emeritus.yaml:\n', content); + + - name: Create new branch + run: | + git checkout -b update-emeritus-${{ github.run_id }} + + - name: Commit and push + run: | + git add . + git commit -m "Update Emeritus.yaml" + git push https://${{ secrets.GH_TOKEN}}@github.com/asyncapi/community update-emeritus-${{ github.run_id }} + + - name: Create PR + run: | + gh pr create --title "docs(community): update latest emeritus list" --body "Updated Emeritus list is available and this PR introduces changes with latest information about Emeritus" --head update-emeritus-${{ github.run_id }} + + notify_slack_on_failure: + if: always() && (needs.detect_tsc_membership_changes.result == 'failure' || needs. add_tsc_member.result == 'failure' || needs.display_message.result == 'failure' || needs.update_emeritus.result == 'failure' || needs.remove_tsc_member.result == 'failure' || needs.remove_tsc_goodbye.result == 'failure' || needs.remove_tsc_emeritus.result == 'failure' ) + needs: [detect_tsc_membership_changes, add_tsc_member.result, display_message, update_emeritus,remove_tsc_goodbye, remove_tsc_member, remove_tsc_emeritus] + runs-on: ubuntu-latest + steps: + - name: Report workflow run status to Slack + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_CI_FAIL_NOTIFY}} + SLACK_TITLE: 🚨 Welcome new contributor action failed 🚨 + SLACK_MESSAGE: Failed to post a message to new Maintainer + MSG_MINIMAL: true diff --git a/tweets/recurring-discuss-ideas/2023-08-01.tweet b/tweets/recurring-discuss-ideas/2023-08-01.tweet new file mode 100644 index 000000000..92b19fefe --- /dev/null +++ b/tweets/recurring-discuss-ideas/2023-08-01.tweet @@ -0,0 +1,5 @@ +Do you have some nice ideas for #AsyncAPI-related tools? Do you want to validate and share with the AsyncAPI community? + +Drop it πŸ‘‡ and let us have an open discussion πŸš€ + +https://github.com/asyncapi/community/discussions/categories/ideas \ No newline at end of file diff --git a/tweets/recurring-slack-link/2023-07-29.tweet b/tweets/recurring-slack-link/2023-07-29.tweet new file mode 100644 index 000000000..0bd3e8865 --- /dev/null +++ b/tweets/recurring-slack-link/2023-07-29.tweet @@ -0,0 +1,7 @@ +✨ Did you know #AsyncAPI is on Slack? ✨ + +Join our Slack workspace to chat with anyone from our Open-Source community! + +πŸ”— asyncapi.com/slack-invite + +Ask for help and help others too. πŸ’ͺ🏿πŸ’ͺ🏽🦾 \ No newline at end of file diff --git a/tweets/recurring-slack-link/2023-08-05.tweet b/tweets/recurring-slack-link/2023-08-05.tweet new file mode 100644 index 000000000..0bd3e8865 --- /dev/null +++ b/tweets/recurring-slack-link/2023-08-05.tweet @@ -0,0 +1,7 @@ +✨ Did you know #AsyncAPI is on Slack? ✨ + +Join our Slack workspace to chat with anyone from our Open-Source community! + +πŸ”— asyncapi.com/slack-invite + +Ask for help and help others too. πŸ’ͺ🏿πŸ’ͺ🏽🦾 \ No newline at end of file