diff --git a/README.md b/README.md index 9906c87..2f85176 100644 --- a/README.md +++ b/README.md @@ -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 `+@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 diff --git a/helpers_git.py b/helpers_git.py index bc82d73..4e6c3e2 100644 --- a/helpers_git.py +++ b/helpers_git.py @@ -1,6 +1,18 @@ import subprocess import helpers_slack +# GitHub-generated addresses that never resolve to a real Slack profile: +# noreply@github.com (bare merge/squash commits) and the privacy-enabled +# form +@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 is_noreply_email(email): + email = (email or '').strip().lower() + return email == NOREPLY_BARE_ADDRESS or email.endswith(NOREPLY_DOMAIN_SUFFIX) + def resolve_git_ref_to_sha1(ref_name): print(f'Resolving {ref_name} to Git SHA1...') diff --git a/helpers_slack.py b/helpers_slack.py index 46dd6c9..a29d2ba 100644 --- a/helpers_slack.py +++ b/helpers_slack.py @@ -92,10 +92,12 @@ def user_id_by_email(app, email): 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): diff --git a/main.py b/main.py index 4185920..8a1bfa7 100644 --- a/main.py +++ b/main.py @@ -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', '') print(f'branches_to_promote: {branches_to_promote}') print(f'production_branches: {production_branches}') @@ -40,6 +43,13 @@ 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 + triggered_by_valid = (triggered_by_email and triggered_by_email.strip() + and not helpers_git.is_noreply_email(triggered_by_email)) + if triggered_by_valid: + 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 ' @@ -49,7 +59,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