Skip to content

Commit 4c8bf77

Browse files
authored
ci: add ambassador_management.yml workflow (#1915)
Co-authored-by: Viacheslav Turovskyi <aeworxet@protonmail.com> Co-authored-by: V Thulisile Sibanda <66913810+thulieblack@users.noreply.github.com>
1 parent ea9b187 commit 4c8bf77

6 files changed

Lines changed: 812 additions & 732 deletions

File tree

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
name: Ambassador Management Workflow
2+
3+
on:
4+
pull_request_target:
5+
types: [closed]
6+
paths:
7+
- 'AMBASSADORS_MEMBERS.yaml'
8+
9+
jobs:
10+
detect_ambassador_changes:
11+
if: github.event.pull_request.merged
12+
name: Update Ambassadors Members
13+
runs-on: ubuntu-latest
14+
15+
steps:
16+
- name: Checkout base commit
17+
uses: actions/checkout@v4
18+
with:
19+
ref: ${{ github.event.pull_request.base.sha }}
20+
path: community-main
21+
22+
- name: Checkout head commit
23+
uses: actions/checkout@v4
24+
with:
25+
ref: ${{ github.event.pull_request.head.sha }}
26+
path: community
27+
28+
- name: Install js-yaml
29+
run: npm install js-yaml@4.1.0
30+
31+
- name: Compare files
32+
id: compare-files
33+
uses: actions/github-script@v7
34+
with:
35+
script: |
36+
const fs = require('fs');
37+
const yaml = require('js-yaml');
38+
39+
const currentAmbassadors = yaml.load(fs.readFileSync('./community-main/AMBASSADORS_MEMBERS.yaml', 'utf8'));
40+
const prAmbassadors = yaml.load(fs.readFileSync('./community/AMBASSADORS_MEMBERS.yaml', 'utf8'));
41+
42+
const added = prAmbassadors.filter(
43+
(newObj) => !currentAmbassadors.some((oldObj) => oldObj.github === newObj.github)
44+
);
45+
const removed = currentAmbassadors.filter(
46+
(oldObj) => !prAmbassadors.some((newObj) => newObj.github === oldObj.github)
47+
);
48+
49+
if (added.length > 0) {
50+
core.setOutput("newAmbassadors", added.map((obj) => obj.github).join(","));
51+
} else {
52+
core.setOutput("newAmbassadors", "");
53+
}
54+
55+
if (removed.length > 0) {
56+
core.setOutput("removedAmbassadors", removed.map((obj) => obj.github).join(","));
57+
} else {
58+
core.setOutput("removedAmbassadors", "");
59+
}
60+
61+
// Log information for debugging
62+
core.info('Ambassadors in main branch:\n' + yaml.dump(currentAmbassadors));
63+
core.info('Location of Ambassadors in main branch:');
64+
core.info(fs.realpathSync('./community-main/AMBASSADORS_MEMBERS.yaml'));
65+
core.info('Ambassadors in PR branch:\n' + yaml.dump(prAmbassadors));
66+
core.info('Location of Ambassadors in PR branch:');
67+
core.info(fs.realpathSync('./community/AMBASSADORS_MEMBERS.yaml'));
68+
69+
- name: Debug newAmbassadors output
70+
run: |
71+
echo "newAmbassadors = ${{ steps.compare-files.outputs.newAmbassadors }}"
72+
73+
- name: Debug removedAmbassadors output
74+
run: |
75+
echo "removedAmbassadors = ${{ steps.compare-files.outputs.removedAmbassadors }}"
76+
77+
outputs:
78+
newAmbassadors: ${{ steps.compare-files.outputs.newAmbassadors }}
79+
removedAmbassadors: ${{ steps.compare-files.outputs.removedAmbassadors }}
80+
81+
add_ambassador:
82+
needs: detect_ambassador_changes
83+
if: needs.detect_ambassador_changes.outputs.newAmbassadors != ''
84+
runs-on: ubuntu-latest
85+
steps:
86+
- name: Invite new ambassadors to the organization
87+
uses: actions/github-script@v7
88+
with:
89+
github-token: ${{ secrets.GH_TOKEN_ORG_ADMIN }}
90+
script: |
91+
const newAmbassadors = '${{ needs.detect_ambassador_changes.outputs.newAmbassadors }}'.split(',');
92+
for (const ambassador of newAmbassadors) {
93+
try {
94+
await github.request('PUT /orgs/{org}/memberships/{username}', {
95+
org: 'asyncapi',
96+
username: ambassador
97+
});
98+
} catch (error) {
99+
core.setFailed(`Failed to add ${ambassador} to the organization: ${error.message}`);
100+
}
101+
}
102+
103+
- name: Add new ambassadors to the team
104+
uses: actions/github-script@v7
105+
with:
106+
github-token: ${{ secrets.GH_TOKEN_ORG_ADMIN }}
107+
script: |
108+
const newAmbassadors = '${{ needs.detect_ambassador_changes.outputs.newAmbassadors }}'.split(',');
109+
for (const ambassador of newAmbassadors) {
110+
try {
111+
await github.request('PUT /orgs/{org}/teams/{team_slug}/memberships/{username}', {
112+
org: 'asyncapi',
113+
team_slug: 'ambassadors',
114+
username: ambassador
115+
});
116+
} catch (error) {
117+
core.setFailed(`Failed to add ${ambassador} to the team: ${error.message}`);
118+
}
119+
}
120+
121+
outputs:
122+
newAmbassadors: ${{needs.detect_ambassador_changes.outputs.newAmbassadors }}
123+
124+
display_message:
125+
needs: add_ambassador
126+
if: needs.add_ambassador.outputs.newAmbassadors != ''
127+
runs-on: ubuntu-latest
128+
steps:
129+
- name: Display welcome message for new ambassadors
130+
uses: actions/github-script@v7
131+
with:
132+
github-token: ${{ secrets.GH_TOKEN }}
133+
script: |
134+
const newAmbassadors = "${{ needs.add_ambassador.outputs.newAmbassadors }}".split(",");
135+
console.log(`New ambassadors: ${newAmbassadors}`);
136+
const welcomeMessage = newAmbassadors.map((ambassador) => `@${ambassador.trim().replace(/^@/, '')} We invited you to join the AsyncAPI organization, and you are added to the team that lists all Ambassadors.\n
137+
138+
Welcome aboard! We are excited to have you as part of the team.`).join("\n");
139+
140+
const { owner, repo } = context.repo;
141+
const { number: issue_number } = context.issue;
142+
await github.rest.issues.createComment({ owner, repo, issue_number, body: welcomeMessage });
143+
144+
remove_ambassador:
145+
needs: detect_ambassador_changes
146+
if: needs.detect_ambassador_changes.outputs.removedAmbassadors != ''
147+
runs-on: ubuntu-latest
148+
steps:
149+
- name: Remove ambassadors from the organization
150+
uses: actions/github-script@v7
151+
with:
152+
github-token: ${{ secrets.GH_TOKEN_ORG_ADMIN }}
153+
script: |
154+
const removedAmbassadors = '${{ needs.detect_ambassador_changes.outputs.removedAmbassadors }}'.split(',');
155+
for (const ambassador of removedAmbassadors) {
156+
try {
157+
await github.request('DELETE /orgs/{org}/memberships/{username}', {
158+
org: 'asyncapi',
159+
username: ambassador
160+
});
161+
core.info(`Successfully removed ${ambassador} from the organization.`);
162+
} catch (error) {
163+
if (error.message.startsWith('Cannot find')) {
164+
core.info(`Failed to remove ${ambassador} from the organization: ${error.message}`);
165+
} else {
166+
core.setFailed(`Failed to remove ${ambassador} from the organization: ${error.message}`);
167+
}
168+
}
169+
}
170+
171+
outputs:
172+
removedAmbassadors: ${{ needs.detect_ambassador_changes.outputs.removedAmbassadors }}
173+
174+
remove_ambassador_goodbye:
175+
needs: remove_ambassador
176+
if: needs.remove_ambassador.outputs.removedAmbassadors != ''
177+
runs-on: ubuntu-latest
178+
steps:
179+
- name: Display goodbye message to removed ambassadors
180+
uses: actions/github-script@v7
181+
with:
182+
github-token: ${{ secrets.GH_TOKEN_ORG_ADMIN }}
183+
script: |
184+
const removedAmbassadors = "${{ needs.remove_ambassador.outputs.removedAmbassadors }}".split(",");
185+
186+
// Goodbye message to removed ambassadors
187+
const combinedMessages = removedAmbassadors.map((ambassador) => {
188+
return `@${ambassador.trim().replace(/^@/, '')} We would like to express our gratitude for your work as an Ambassador 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`;
189+
});
190+
191+
const { owner, repo } = context.repo;
192+
const { number: issue_number } = context.issue;
193+
for (const message of combinedMessages) {
194+
await github.rest.issues.createComment({ owner, repo, issue_number, body: message });
195+
}
196+
197+
update_emeritus:
198+
needs: remove_ambassador
199+
if: needs.remove_ambassador.outputs.removedAmbassadors != ''
200+
runs-on: ubuntu-latest
201+
env:
202+
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
203+
steps:
204+
- name: Check out code
205+
uses: actions/checkout@v4
206+
207+
- name: Install js-yaml
208+
run: npm install js-yaml@4.1.0
209+
210+
- name: Add Former Ambassador to Emeritus.yaml and print
211+
uses: actions/github-script@v7
212+
with:
213+
script: |
214+
const fs = require('fs');
215+
const yaml = require('js-yaml');
216+
const path = './Emeritus.yaml';
217+
218+
const yamlContent = [];
219+
220+
// Read the current content of the file
221+
const content = fs.readFileSync(path, 'utf8').trim(); // remove any trailing whitespaces
222+
223+
// Parse the removedAmbassadors comma-separated string to an array
224+
const removedAmbassadors = '${{ needs.remove_ambassador.outputs.removedAmbassadors }}'
225+
.split(',')
226+
.map((ambassador) => ambassador.trim())
227+
.filter(Boolean);
228+
229+
// Strip the first line that contains a comment
230+
yamlContent[0] = content
231+
.split('\n')
232+
.filter((line) => line && line.startsWith('#'));
233+
234+
// Get the actual YAML data
235+
yamlContent[1] = yaml.load(content);
236+
237+
// Extract existing names of Emeritus Ambassadors from the file
238+
const existingEmeritusAmbassadors = new Set(yamlContent[1].emeritus_ambassadors);
239+
240+
// Filter ambassadors who should be added and are not already in the list
241+
const emeritusAmbassadorsDiffList = removedAmbassadors.filter(
242+
(ambassador) => !existingEmeritusAmbassadors.has(ambassador),
243+
);
244+
245+
// Append new ambassadors if there are any new ones
246+
if (emeritusAmbassadorsDiffList.length > 0) {
247+
yamlContent[1].emeritus_ambassadors.push(...emeritusAmbassadorsDiffList);
248+
249+
// Merge the comment and the programmatically changed YAML into new content
250+
// that will be written to `Emeritus.yaml`
251+
const newContent = `${yamlContent[0].join('\n')}\n\n${yaml.dump(yamlContent[1])}`;
252+
253+
// Write the new content back into the YAML file
254+
fs.writeFileSync(path, newContent);
255+
256+
console.log('Updated Emeritus.yaml:\n', fs.readFileSync(path, 'utf8'));
257+
} else {
258+
console.log('No new names to add. Emeritus Ambassador is already in the list.');
259+
}
260+
261+
- name: Config git
262+
run: |
263+
git config --global user.name asyncapi-bot
264+
git config --global user.email info@asyncapi.io
265+
266+
- name: Create new branch
267+
run: |
268+
git checkout -b update-emeritus-${{ github.run_id }}
269+
270+
- name: Commit and push
271+
run: |
272+
git add Emeritus.yaml
273+
if ! git diff --cached --quiet; then
274+
git commit -m "Update Emeritus.yaml"
275+
git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/asyncapi/community.git
276+
git push origin update-emeritus-${{ github.run_id }}
277+
else
278+
echo "No updates to Emeritus.yaml; skipping commit/push."
279+
fi
280+
281+
- name: Create PR
282+
run: |
283+
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 }}
284+
285+
notify_slack_on_failure:
286+
if: always() && (needs.detect_ambassador_changes.result == 'failure' || needs.add_ambassador.result == 'failure' || needs.display_message.result == 'failure' || needs.update_emeritus.result == 'failure' || needs.remove_ambassador.result == 'failure' || needs.remove_ambassador_goodbye.result == 'failure')
287+
needs: [detect_ambassador_changes, add_ambassador, display_message, update_emeritus, remove_ambassador_goodbye, remove_ambassador]
288+
runs-on: ubuntu-latest
289+
steps:
290+
- name: Report workflow run status to Slack
291+
uses: rtCamp/action-slack-notify@v2
292+
env:
293+
SLACK_WEBHOOK: ${{secrets.SLACK_CI_FAIL_NOTIFY}}
294+
SLACK_TITLE: '🚨 Ambassador Management Workflow failed 🚨'
295+
SLACK_MESSAGE: 'Failed to post a message to new Ambassador'
296+
MSG_MINIMAL: true

.github/workflows/update-website-ambassador.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66
branches:
77
- 'master'
88
paths:
9-
- 'AMBASSADORS_MEMBERS.json'
9+
- 'AMBASSADORS_MEMBERS.yaml'
1010

1111
jobs:
1212
update-website:
@@ -16,11 +16,11 @@ jobs:
1616
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
1717
steps:
1818
- name: Checkout Current repository
19-
uses: actions/checkout@v2
19+
uses: actions/checkout@v4
2020
with:
2121
path: community
2222
- name: Checkout Another repository
23-
uses: actions/checkout@v2
23+
uses: actions/checkout@v4
2424
with:
2525
repository: asyncapi/website
2626
path: website
@@ -33,6 +33,11 @@ jobs:
3333
working-directory: ./website
3434
run: |
3535
git checkout -b update-ambassadors-${{ github.sha }}
36+
- name: Convert YAML to JSON using Python
37+
working-directory: ./community
38+
run: |
39+
pip install pyyaml==6.0.1
40+
python -c 'import sys, yaml, json; json.dump(yaml.safe_load(sys.stdin), sys.stdout, indent=4)' < AMBASSADORS_MEMBERS.yaml > AMBASSADORS_MEMBERS.json
3641
- name: Copy ambassadors file from Current Repo to Another
3742
working-directory: ./website
3843
run: |

0 commit comments

Comments
 (0)