Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Configuration is done via env variables
* `SLACK_BOT_TOKEN` - Slack bot token. Mandatory parameter. scopes: channels:history, chat:write, reactions:read, users:read.email, users:read
* `SLACK_APP_TOKEN` - Slack app token. Mandatory parameter. scopes: connections:write
* `SLACK_CHANNEL_NAME` - Slack channel name. Also channel_id can be used
* `TRIGGERED_BY_EMAIL` - Email of the person who triggered the build, for example by pressing the merge button. Optional parameter. When set, a `Triggered by` line is added to the approval message, mentioning that person if the email matches a Slack profile. Useful because the commit itself does not always point at a person: squash merges on GitHub, for instance, are committed as `noreply@github.com`. A GitHub-generated noreply address (bare `noreply@github.com` or the privacy-enabled `<id>+<username>@users.noreply.github.com` form) never resolves to a person, so the `Triggered by` line is omitted entirely rather than showing that address

# Slack App manifect example
```yaml
Expand Down Expand Up @@ -93,6 +94,9 @@ settings:
BRANCHES_TO_PROMOTE: "${{ env.GIT_DESTINATION_BRANCH }}"
TIMEOUT_MINUTES: 1
REPOSITORY_URL: "${{ fromJson(steps.repo.outputs.result).html_url }}"
# Who clicked Merge - not always the commit author/committer (e.g. squash
# merges are committed by GitHub as noreply@github.com). Optional.
TRIGGERED_BY_EMAIL: "${{ github.event.pusher.email }}"
run: >
mkdir -p magic-button/reports && chmod 777 magic-button/reports
&& docker run --rm
Expand All @@ -101,7 +105,7 @@ settings:
-e SLACK_BOT_TOKEN -e SLACK_APP_TOKEN -e BUILD_JOB_NAME -e BUILD_JOB_URL
-e CURRENT_GIT_COMMIT="$(git rev-parse HEAD)" -e REPOSITORY_NAME="$(basename $(git rev-parse --show-toplevel))"
-e REPOSITORY_URL -e BRANCHES_TO_PROMOTE -e TIMEOUT_MINUTES -e TIMEZONE="Europe/Oslo"
-e PRODUCTION_BRANCHES -e SLACK_CHANNEL_NAME
-e PRODUCTION_BRANCHES -e SLACK_CHANNEL_NAME -e TRIGGERED_BY_EMAIL
ghcr.io/fivexl/magic-button:${{ env.MAGIC_BUTTON_VERSION }}
&& ls -all magic-button/reports && cat magic-button/reports/report.json
continue-on-error: true
Expand All @@ -123,7 +127,7 @@ settings:

script:
- |
mkdir -p magic-button/reports && chmod 777 magic-button/reports && docker run --rm -v "$(pwd)/.git":/app/.git -v "$(pwd)/magic-button/reports":/app/reports -e SLACK_BOT_TOKEN -e SLACK_APP_TOKEN -e BUILD_JOB_NAME -e BUILD_JOB_URL -e CURRENT_GIT_COMMIT="$(git rev-parse HEAD)" -e REPOSITORY_NAME="$(basename $(git rev-parse --show-toplevel))" -e REPOSITORY_URL -e BRANCHES_TO_PROMOTE -e TIMEOUT_MINUTES -e TIMEZONE="Europe/Oslo" -e PRODUCTION_BRANCHES -e SLACK_CHANNEL_NAME ghcr.io/fivexl/magic-button:$MAGIC_BUTTON_VERSION && ls -all magic-button/reports && cat magic-button/reports/report.json
mkdir -p magic-button/reports && chmod 777 magic-button/reports && docker run --rm -v "$(pwd)/.git":/app/.git -v "$(pwd)/magic-button/reports":/app/reports -e SLACK_BOT_TOKEN -e SLACK_APP_TOKEN -e BUILD_JOB_NAME -e BUILD_JOB_URL -e CURRENT_GIT_COMMIT="$(git rev-parse HEAD)" -e REPOSITORY_NAME="$(basename $(git rev-parse --show-toplevel))" -e REPOSITORY_URL -e BRANCHES_TO_PROMOTE -e TIMEOUT_MINUTES -e TIMEZONE="Europe/Oslo" -e PRODUCTION_BRANCHES -e SLACK_CHANNEL_NAME -e TRIGGERED_BY_EMAIL="$GITLAB_USER_EMAIL" ghcr.io/fivexl/magic-button:$MAGIC_BUTTON_VERSION && ls -all magic-button/reports && cat magic-button/reports/report.json

after_script:
- >
Expand Down
22 changes: 18 additions & 4 deletions helpers_slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@

SLACK_MESSAGE_SIZE_LIMIT = 3001

# GitHub-generated addresses that never resolve to a real Slack profile:
# noreply@github.com (bare merge/squash commits) and the privacy-enabled
# form <id>+<username>@users.noreply.github.com (bots, and any user with
# "Keep my email addresses private" turned on - this is common, not rare).
NOREPLY_DOMAIN_SUFFIX = '@users.noreply.github.com'
NOREPLY_BARE_ADDRESS = 'noreply@github.com'


def init_app(slack_bot_token, approve_action_id, cancel_action_id):
app = App(token=slack_bot_token)
Expand Down Expand Up @@ -87,15 +94,22 @@ def gen_report(usernames, teams, channel, message, approval_code):
json.dump(report, outfile)


def is_noreply_email(email):
email = (email or '').strip().lower()
return email == NOREPLY_BARE_ADDRESS or email.endswith(NOREPLY_DOMAIN_SUFFIX)


def user_id_by_email(app, email):
try:
result = app.client.users_lookupByEmail(email=email)
return result['user']['id']
except SlackApiError as err:
if err.response['error'] == 'users_not_found':
return None

return None
error_code = err.response['error']
if error_code == 'users_not_found':
print(f'No Slack user found for email {email}')
else:
print(f'Slack lookup failed for email {email}: {error_code}')
return None


def is_message_longer_than_limit(message):
Expand Down
13 changes: 12 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
timezone = os.environ['TIMEZONE']
production_branches = os.environ['PRODUCTION_BRANCHES'].split()
slack_bot_token = os.environ["SLACK_BOT_TOKEN"]
# Not every commit is made by a person - squash merges, for instance, are committed by
# the SCM itself - so CI can tell us who triggered the build. Optional.
triggered_by_email = os.environ.get('TRIGGERED_BY_EMAIL', '').strip()

print(f'branches_to_promote: {branches_to_promote}')
print(f'production_branches: {production_branches}')
Expand All @@ -40,6 +43,11 @@
author_email = helpers_git.get_author_email_for_ref(current_commit_id)
author_slack_id = helpers_slack.user_id_by_email(app, author_email)
author_id = f'<@{author_slack_id}>' if author_slack_id is not None else author_email
triggered_by_id = None
if triggered_by_email and not helpers_slack.is_noreply_email(triggered_by_email):
triggered_by_slack_id = helpers_slack.user_id_by_email(app, triggered_by_email)
triggered_by_id = (f'<@{triggered_by_slack_id}>'
if triggered_by_slack_id is not None else triggered_by_email)
commit_msg = helpers_git.get_commit_message_for_ref(current_commit_id)

text_for_request = 'If approved will promote commit(s) below to branch '
Expand All @@ -49,7 +57,10 @@
details += f'Commit message: `{commit_msg}`\n'
details += f'Commit id: `{current_commit_id}`\n'
details += f'Committer: {commiter_id}\n'
details += f'Author: {author_id}\n\n'
details += f'Author: {author_id}\n'
if triggered_by_id is not None:
details += f'Triggered by: {triggered_by_id}\n'
details += '\n'
details += helpers_time.generate_time_based_message(production_branches, branches_to_promote, timezone)

# Generate separate diff blocks for every branch
Expand Down
Loading