Repository navigation
Repo sync #32989
This file contains hidden or 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
| name: Confirm internal staff meant to post in public | ||
|
Check warning on line 1 in .github/workflows/confirm-internal-staff-work-in-docs.yml
|
||
| # Asks GitHub staff to confirm public issues and PRs belong in github/docs. | ||
| on: | ||
| issues: | ||
| types: | ||
| - opened | ||
| - transferred | ||
| # pull_request_target lets the workflow check org membership for forked PR authors. | ||
| pull_request_target: | ||
| types: | ||
| - opened | ||
| permissions: | ||
| contents: read | ||
| jobs: | ||
| check-team-membership: | ||
| runs-on: ubuntu-latest | ||
| continue-on-error: true | ||
| if: github.repository == 'github/docs' && github.actor != 'docs-bot' | ||
| steps: | ||
| - id: membership_check | ||
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 | ||
| env: | ||
| TEAM_CONTENT_REPO: ${{ secrets.TEAM_CONTENT_REPO }} | ||
| with: | ||
| github-token: ${{ secrets.DOCS_BOT_PAT_BASE }} | ||
| script: | | ||
| // Only GitHub employees need confirmation before public posts stay public. | ||
| try { | ||
| await github.rest.teams.getMembershipForUserInOrg({ | ||
| org: 'github', | ||
| team_slug: 'employees', | ||
| username: context.payload.sender.login, | ||
| }); | ||
| } catch(err) { | ||
| // The employee membership lookup throws for non-employees, so skip confirmation. | ||
| return | ||
| } | ||
| // Docs team members can intentionally work in github/docs. | ||
| try { | ||
| // 9919 is the github org and 325922 the docs team; numeric IDs survive renames. | ||
| await github.request('GET /organizations/{org_id}/team/{team_id}/memberships/{username}', { | ||
| org_id: 9919, | ||
| team_id: 325922, | ||
| username: context.payload.sender.login, | ||
| }); | ||
| return | ||
| } catch(err) { | ||
| // The docs team membership lookup throws for non-members, who need an internal confirmation issue. | ||
| } | ||
| const issueNo = context.number || context.issue.number | ||
| await github.rest.issues.create({ | ||
| owner: 'github', | ||
| repo: process.env.TEAM_CONTENT_REPO, | ||
| title: `@${context.payload.sender.login} confirm that \#${issueNo} should be in the public github/docs repo`, | ||
| body: `@${context.payload.sender.login} opened https://github.com/github/docs/issues/${issueNo} publicly in the github/docs repo, instead of the private github/${process.env.TEAM_CONTENT_REPO} repo.\n\n@${context.payload.sender.login}, if this specifically belongs in the public repo, and no sensitive information is disclosed, please ignore this message. Otherwise, please ${context.payload.pull_request ? 'close the pull request and recreate it internally in https://github.com/github/docs-content.' : 'close the issue and reopen it internally in https://github.com/github/docs-content, or transfer it there.'}\n\nIf sensitive information was shared, please close https://github.com/github/docs/issues/${issueNo} and notify us in the \#docs-open-source channel, so we can delete it. Thanks!`, | ||
| labels: ['OS confirmation', 'skip FR board'], | ||
| }); | ||
| core.setOutput('did_warn', 'true') | ||
| - name: Resolve Slack user ID from username | ||
| id: slack_user | ||
| if: ${{ steps.membership_check.outputs.did_warn == 'true' }} | ||
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 | ||
| env: | ||
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} | ||
| with: | ||
| github-token: ${{ github.token }} | ||
| script: | | ||
| const githubLogin = context.payload.sender.login.toLowerCase(); | ||
| core.info(`Looking up Slack user for @${githubLogin}...`); | ||
| core.setOutput('slack_user_id', ''); | ||
| core.setOutput('slack_mention', `GitHub user \`${context.payload.sender.login}\``); | ||
| const token = process.env.SLACK_BOT_TOKEN; | ||
| if (!token) { | ||
| throw new Error('SLACK_DOCS_BOT_TOKEN is not configured.'); | ||
| } | ||
| let cursor = ''; | ||
| let pages = 0; | ||
| const matches = []; | ||
| do { | ||
| core.info(`Fetching Slack users.list page ${pages + 1}...`); | ||
| const url = new URL('https://slack.com/api/users.list'); | ||
| url.searchParams.set('limit', '200'); | ||
| if (cursor) { | ||
| url.searchParams.set('cursor', cursor); | ||
| } | ||
| const response = await fetch(url, { | ||
| headers: { | ||
| Authorization: `Bearer ${token}`, | ||
| }, | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error(`Slack users.list returned HTTP ${response.status}.`); | ||
| } | ||
| const result = await response.json(); | ||
| if (!result.ok) { | ||
| throw new Error(`Slack users.list failed: ${result.error || 'unknown error'}.`); | ||
| } | ||
| if (!Array.isArray(result.members)) { | ||
| throw new Error('Slack users.list response did not include a members array.'); | ||
| } | ||
| pages += 1; | ||
| matches.push(...result.members.filter((member) => | ||
| !member.deleted && | ||
| !member.is_bot && | ||
| !member.is_app_user && | ||
| typeof member.id === 'string' && | ||
| typeof member.name === 'string' && | ||
| member.name.toLowerCase() === githubLogin | ||
| )); | ||
| cursor = result.response_metadata?.next_cursor || ''; | ||
| } while (cursor); | ||
| core.info(`Checked ${pages} pages of users.list. Found ${matches.length} match(es) for @${githubLogin}.`); | ||
| if (matches.length !== 1) { | ||
| core.warning(`Expected one Slack username matching @${githubLogin}; found ${matches.length}. No DM will be sent.`); | ||
| return; | ||
| } | ||
| core.info(`Will DM Slack user ${matches[0].id}.`); | ||
| core.setOutput('slack_user_id', matches[0].id); | ||
| core.setOutput('slack_mention', `<@${matches[0].id}>`); | ||
| - name: DM the author in Slack | ||
| id: slack_dm | ||
| if: ${{ steps.slack_user.outputs.slack_user_id != '' }} | ||
| uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 | ||
| with: | ||
| method: chat.postMessage | ||
| token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} | ||
| errors: true | ||
| payload: | | ||
| channel: ${{ steps.slack_user.outputs.slack_user_id }} | ||
| text: ${{ toJSON(format('You opened {0} publicly in github/docs instead of the private repo. An internal confirmation issue was created.', github.event.issue.html_url || github.event.pull_request.html_url)) }} | ||
| - name: Log Slack DM result | ||
| if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }} | ||
| env: | ||
| SLACK_USER_ID: ${{ steps.slack_user.outputs.slack_user_id }} | ||
| SLACK_LOOKUP_OUTCOME: ${{ steps.slack_user.outcome }} | ||
| SLACK_DM_OUTCOME: ${{ steps.slack_dm.outcome }} | ||
| SLACK_DM_OK: ${{ steps.slack_dm.outputs.ok }} | ||
| SLACK_DM_RESPONSE: ${{ steps.slack_dm.outputs.response }} | ||
| SLACK_DM_TS: ${{ steps.slack_dm.outputs.ts }} | ||
| run: | | ||
| echo "Slack user lookup outcome: ${SLACK_LOOKUP_OUTCOME:-not run}" | ||
| if [ -z "$SLACK_USER_ID" ]; then | ||
| echo "Slack DM skipped because no Slack user ID was resolved." | ||
| elif [ "$SLACK_DM_OK" = "true" ]; then | ||
| echo "Slack DM sent successfully (timestamp: ${SLACK_DM_TS:-unavailable})." | ||
| else | ||
| echo "Slack DM did not return success (step outcome: ${SLACK_DM_OUTCOME:-unknown})." | ||
| if [ -n "$SLACK_DM_RESPONSE" ]; then | ||
| printf '%s\n' "$SLACK_DM_RESPONSE" | jq -r 'if type == "object" and (.error | type) == "string" then "Slack API error: " + .error else "Slack API response did not include an error field." end' | ||
| else | ||
| echo "Slack DM API response was unavailable." | ||
| fi | ||
| fi | ||
| - name: Notify the docs-open-source channel | ||
| id: slack_channel_notification | ||
| if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }} | ||
| uses: slackapi/slack-github-action@dcb1066f776dd043e64d0e8ba94ca15cc7e1875d # v4.0.0 | ||
| env: | ||
| SLACK_CHANNEL_ID: ${{ secrets.DOCS_OPEN_SOURCE_SLACK_CHANNEL_ID }} | ||
| SLACK_MESSAGE: ${{ format('{0} opened {1} publicly in github/docs instead of the private repo. An internal confirmation issue was created.', steps.slack_user.outputs.slack_mention, github.event.issue.html_url || github.event.pull_request.html_url) }} | ||
| with: | ||
| method: chat.postMessage | ||
| token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} | ||
| errors: true | ||
| payload: | | ||
| channel: ${{ toJSON(env.SLACK_CHANNEL_ID) }} | ||
| text: ${{ toJSON(env.SLACK_MESSAGE) }} | ||
| - name: Log Slack channel notification result | ||
| if: ${{ !cancelled() && steps.membership_check.outputs.did_warn == 'true' }} | ||
| env: | ||
| SLACK_CHANNEL_OUTCOME: ${{ steps.slack_channel_notification.outcome }} | ||
| SLACK_CHANNEL_OK: ${{ steps.slack_channel_notification.outputs.ok }} | ||
| SLACK_CHANNEL_RESPONSE: ${{ steps.slack_channel_notification.outputs.response }} | ||
| SLACK_CHANNEL_TS: ${{ steps.slack_channel_notification.outputs.ts }} | ||
| run: | | ||
| if [ "$SLACK_CHANNEL_OK" = "true" ]; then | ||
| echo "Slack channel notification sent successfully (timestamp: ${SLACK_CHANNEL_TS:-unavailable})." | ||
| else | ||
| echo "Slack channel notification did not return success (step outcome: ${SLACK_CHANNEL_OUTCOME:-unknown})." | ||
| if [ -n "$SLACK_CHANNEL_RESPONSE" ]; then | ||
| printf '%s\n' "$SLACK_CHANNEL_RESPONSE" | jq -r 'if type == "object" and (.error | type) == "string" then "Slack API error: " + .error else "Slack API response did not include an error field." end' | ||
| else | ||
| echo "Slack channel API response was unavailable." | ||
| fi | ||
| fi | ||
| - name: Check out repo | ||
| if: ${{ failure() && github.event_name != 'pull_request_target' }} | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| - uses: ./.github/actions/slack-alert | ||
| if: ${{ failure() && github.event_name != 'pull_request_target' }} | ||
| with: | ||
| slack_token: ${{ secrets.SLACK_DOCS_BOT_TOKEN }} | ||