diff --git a/.github/workflows/daily.yaml b/.github/workflows/daily.yaml index 35b4f08..6fac62d 100644 --- a/.github/workflows/daily.yaml +++ b/.github/workflows/daily.yaml @@ -2,6 +2,11 @@ name: Daily Collection on: workflow_dispatch: + inputs: + slack_channel: + description: Slack channel to post the error message to if the builds fail. + required: false + default: "sdv-alerts-debug" schedule: - cron: '0 0 * * *' @@ -28,3 +33,22 @@ jobs: env: PYDRIVE_CREDENTIALS: ${{ secrets.PYDRIVE_CREDENTIALS }} BIGQUERY_CREDENTIALS: ${{ secrets.BIGQUERY_CREDENTIALS }} + + alert: + needs: [collect] + runs-on: ubuntu-latest + if: failure() + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Install slack dependencies + run: | + python -m pip install --upgrade pip + python -m pip install invoke + python -m pip install .[dev] + - name: Slack alert if failure + run: python -m github_analytics.slack_utils -r ${{ github.run_id }} -c ${{ github.event.inputs.slack_channel || 'sdv-alerts' }} + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} diff --git a/.github/workflows/dryrun.yaml b/.github/workflows/dryrun.yaml index 6953831..bdf0f72 100644 --- a/.github/workflows/dryrun.yaml +++ b/.github/workflows/dryrun.yaml @@ -1,12 +1,18 @@ name: Health-check Dry Run on: - - workflow_dispatch - - push - - pull_request + workflow_dispatch: + inputs: + slack_channel: + description: Slack channel to post the error message to if the builds fail. + required: false + default: "sdv-alerts-debug" + + push: + pull_request: jobs: - collect: + dry_run: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -29,3 +35,22 @@ jobs: env: PYDRIVE_CREDENTIALS: ${{ secrets.PYDRIVE_CREDENTIALS }} BIGQUERY_CREDENTIALS: ${{ secrets.BIGQUERY_CREDENTIALS }} + + alert: + needs: [dry_run] + runs-on: ubuntu-latest + if: failure() + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.13' + - name: Install slack dependencies + run: | + python -m pip install --upgrade pip + python -m pip install invoke + python -m pip install .[dev] + - name: Slack alert if failure + run: python -m github_analytics.slack_utils -r ${{ github.run_id }} -c ${{ github.event.inputs.slack_channel || 'sdv-alerts' }} + env: + SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} diff --git a/download_analytics/slack_utils.py b/download_analytics/slack_utils.py new file mode 100644 index 0000000..2cdb4c9 --- /dev/null +++ b/download_analytics/slack_utils.py @@ -0,0 +1,100 @@ +"""Utility functions for Slack integration.""" + +import argparse +import os + +from slack_sdk import WebClient + +GITHUB_URL_PREFIX = 'https://github.com/datacebo/download-analytics/actions/runs/' +DEFAULT_SLACK_CHANNEL = 'sdv-alerts-debug' + + +def _get_slack_client(): + """Create an authenticated Slack client. + + Returns: + WebClient: + An authenticated Slack WebClient instance. + """ + token = os.getenv('SLACK_TOKEN') + client = WebClient(token=token) + return client + + +def post_slack_message(channel, text): + """Post a message to a Slack channel. + + Args: + channel (str): + The name of the channel to post to. + text (str): + The message to send to the channel. + + Returns: + SlackResponse: + Response from Slack API call + """ + client = _get_slack_client() + response = client.chat_postMessage(channel=channel, text=text) + if not response['ok']: + error = response.get('error', 'unknown_error') + msg = f'{error} occured trying to post message to {channel}' + raise RuntimeError(msg) + + return response + + +def post_slack_message_in_thread(channel, text, thread_ts): + """Post a message as a threaded reply in a Slack channel. + + Args: + channel (str): + The name of the channel to post to. + text (str): + The message to send as a reply in the thread. + thread_ts (str): + The timestamp of the message that starts the thread. + + Returns: + SlackResponse: + Response from Slack API call. + """ + client = _get_slack_client() + response = client.chat_postMessage(channel=channel, text=text, thread_ts=thread_ts) + if not response['ok']: + error = response.get('error', 'unknown_error') + msg = f'{error} occurred trying to post threaded message to {channel}' + raise RuntimeError(msg) + + return response + + +def send_alert(args): + """Send an alert message to a slack channel.""" + url = GITHUB_URL_PREFIX + args.run_id + message = ( + f'Download Analytics build failed :fire: :dumpster-fire: :fire: See errors <{url}|here>' + ) + post_slack_message(args.channel, message) + + +def get_parser(): + """Get the parser.""" + parser = argparse.ArgumentParser(description='Function to alert when a Github workflow fails.') + parser.add_argument('-r', '--run-id', type=str, help='The id of the github run.') + parser.add_argument( + '-c', + '--channel', + type=str, + help='The slack channel to post to.', + default=DEFAULT_SLACK_CHANNEL, + ) + parser.set_defaults(action=send_alert) + + return parser + + +if __name__ == '__main__': + parser = get_parser() + args = parser.parse_args() + args.action(args) diff --git a/pyproject.toml b/pyproject.toml index e28e030..ae5c259 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,8 @@ include = ['download_analytics', 'download_analytics.*'] [project.optional-dependencies] dev = [ "ruff>=0.9.8", - "invoke" + "invoke", + "slack-sdk>=3.34,<4.0", ] [tool.ruff]