diff --git a/.github/scripts/announce_pr_on_slack.py b/.github/scripts/announce_pr_on_slack.py new file mode 100644 index 00000000..71ae75f2 --- /dev/null +++ b/.github/scripts/announce_pr_on_slack.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +""" +GitHub Actions script to send Slack notifications for new pull requests. +""" + +import os +import sys +from typing import Tuple + +import httpx + + +def send_slack_message( + slack_team: str, slack_service: str, slack_token: str, message: str +) -> bool: + """Send a message to Slack channel.""" + + url = ( + f"https://hooks.slack.com/services/T{slack_team}/B{slack_service}/{slack_token}" + ) + + headers = { + "Content-Type": "application/json", + } + + data = {"text": message} + + try: + with httpx.Client() as client: + response = client.post(url, headers=headers, json=data) + response.raise_for_status() + + result = response.text + if "ok" in result: + print("✅ Slack message sent successfully") + return True + else: + print(f"❌ Slack API error: {result}") + return False + + except httpx.HTTPError as e: + print(f"❌ Request error: {e}") + return False + + +def ensure_environment_variables_are_present() -> ( + Tuple[str, str, str, str, str, str, str, str] +): + """ + Ensures that all necessary environment variables are present for the application to run. + + This function checks for the presence of required environment variables related to Slack bot token, + Pull Request (PR) details, and repository name. It also validates that the Slack channel is set. + + Raises: + SystemExit: If any of the required environment variables are missing. + + Returns: + A tuple containing the values of the following environment variables: + - SLACK_TOKEN: The token for the Slack bot. + - SLACK_TEAM: The ID of the Slack team. + - SLACK_SERVICE: The ID of the Slack service. + - PR_NUMBER: The number of the Pull Request. + - PR_TITLE: The title of the Pull Request. + - PR_URL: The URL of the Pull Request. + - PR_AUTHOR: The author of the Pull Request. + - REPO_NAME: The name of the repository. + """ + # Get environment variables + slack_token = os.getenv("SLACK_TOKEN") + slack_team = os.getenv("SLACK_TEAM") + slack_service = os.getenv("SLACK_SERVICE") + pr_number = os.getenv("PR_NUMBER") + pr_title = os.getenv("PR_TITLE") + pr_url = os.getenv("PR_URL") + pr_author = os.getenv("PR_AUTHOR") + repo_name = os.getenv("REPO_NAME") + + # Validate required environment variables + if not slack_token: + print("❌ SLACK_TOKEN environment variable is required") + sys.exit(1) + + if not slack_team: + print("❌ SLACK_TEAM environment variable is required") + sys.exit(1) + + if not slack_service: + print("❌ SLACK_SERVICE environment variable is required") + sys.exit(1) + + if not all([pr_number, pr_title, pr_url, pr_author, repo_name]): + print( + "❌ Missing required PR information (PR_NUMBER, PR_TITLE, PR_URL, PR_AUTHOR, REPO_NAME)" + ) + sys.exit(1) + + # Since we're validating these variables, we can assert they're not None + assert pr_number is not None + assert pr_title is not None + assert pr_url is not None + assert pr_author is not None + assert repo_name is not None + + return ( + slack_token, + slack_team, + slack_service, + pr_number, + pr_title, + pr_url, + pr_author, + repo_name, + ) + + +def main() -> None: + """Main function to process PR and send Slack notification.""" + + ( + slack_token, + slack_team, + slack_service, + pr_number, + pr_title, + pr_url, + pr_author, + repo_name, + ) = ensure_environment_variables_are_present() + + print(f"Processing PR #{pr_number}") + + # Create Slack message + message = ( + f":mega: Oyez! Oyez! Oyez!\n" + f"Hello Team. Please, review the opened PR #{pr_number} in {repo_name}\n" + f"*{pr_title}* by @{pr_author}\n" + f":pull-request-opened: {pr_url}" + ) + + # Send to Slack + success = send_slack_message(slack_service, slack_team, slack_token, message) + + if not success: + sys.exit(1) + + print("✅ Process completed successfully") + + +if __name__ == "__main__": + main() + +# Made with Bob diff --git a/.github/scripts/announce_release_on_slack.py b/.github/scripts/announce_release_on_slack.py new file mode 100755 index 00000000..a54ac6de --- /dev/null +++ b/.github/scripts/announce_release_on_slack.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python3 + +import logging +import os +import sys + +import httpx +from github import Github + + +def ensure_environment_variables_are_present() -> None: + required_env_vars = ( + "GITHUB_RELEASE_TAG", + "GITHUB_TOKEN", + "SLACK_TOKEN", + "SLACK_SERVICE", + "SLACK_TEAM", + ) + + for env_var in required_env_vars: + if env_var not in os.environ: + logging.fatal(f"❌ A required environment variable is missing: {env_var}") + sys.exit(1) + + +def get_gh_release_info_text_with_token(release_tag: str, access_token: str) -> str: + gh = Github(access_token) + repo_name = "instana/python-sensor" + repo = gh.get_repo(repo_name) + release = repo.get_release(release_tag) + + logging.info("GH Release fetched successfully %s", release) + + msg = ( + f":mega: Oyez! Oyez! Oyez!\n" + f":package: A new version of the Python Tracer has been released.\n" + f"Name: Instana Python Tracer {release.title}\n" + f"Tag: {release.tag_name}\n" + f"Created at: {release.created_at}\n" + f"Published at: {release.published_at}\n" + f"{release.body}\n" + ) + + logging.info(msg) + return msg + + +def post_on_slack_channel( + slack_team: str, slack_service: str, slack_token: str, message_text: str +) -> None: + """Send a message to Slack channel.""" + + url = ( + f"https://hooks.slack.com/services/T{slack_team}/B{slack_service}/{slack_token}" + ) + + headers = { + "Content-Type": "application/json", + } + body = {"text": message_text} + + with httpx.Client() as client: + response = client.post(url, headers=headers, json=body) + response.raise_for_status() + + result = response.text + if "ok" in result: + print("✅ Slack message sent successfully") + else: + print(f"❌ Slack API error: {result}") + + +def main() -> None: + # Setting this globally to DEBUG will also debug PyGithub, + # which will produce even more log output + logging.basicConfig(level=logging.INFO) + ensure_environment_variables_are_present() + + msg = get_gh_release_info_text_with_token( + os.environ["GITHUB_RELEASE_TAG"], os.environ["GITHUB_TOKEN"] + ) + + post_on_slack_channel( + os.environ["SLACK_TEAM"], + os.environ["SLACK_SERVICE"], + os.environ["SLACK_TOKEN"], + msg, + ) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/opened-pr-notification-on-slack.yml b/.github/workflows/opened-pr-notification-on-slack.yml new file mode 100644 index 00000000..33b23d7f --- /dev/null +++ b/.github/workflows/opened-pr-notification-on-slack.yml @@ -0,0 +1,41 @@ +name: PR Slack Notification + +permissions: + contents: read + pull-requests: read + +on: + pull_request: + types: [opened, reopened, review_requested] + +jobs: + notify-slack: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history to access commit messages + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + + - name: Install dependencies + run: | + pip install httpx + + - name: Send Slack notification + env: + SLACK_TOKEN: ${{ secrets.RUPY_PR_ANNOUNCEMENT_TOKEN }} + SLACK_SERVICE: ${{ secrets.RUPY_PR_ANNOUNCEMENT_CHANNEL_ID }} + SLACK_TEAM: ${{ secrets.RUPY_TOWN_CRIER_SERVICE_ID }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.number }} + PR_TITLE: ${{ github.event.pull_request.title }} + PR_URL: ${{ github.event.pull_request.html_url }} + PR_AUTHOR: ${{ github.event.pull_request.user.login }} + REPO_NAME: ${{ github.repository }} + run: python .github/scripts/announce_pr_on_slack.py diff --git a/.github/workflows/release-notification-on-slack.yml b/.github/workflows/release-notification-on-slack.yml index 186466e1..3952dc8e 100644 --- a/.github/workflows/release-notification-on-slack.yml +++ b/.github/workflows/release-notification-on-slack.yml @@ -9,26 +9,57 @@ on: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#release release: - types: [published] + types: [published, released] jobs: - build: - name: Slack Post + notify-slack: runs-on: ubuntu-latest + steps: - - name: 'Checkout the needed file only ./bin/announce_release_on_slack.py' - uses: actions/checkout@v3 - - run: | - if [[ ${{ github.event_name == 'workflow_dispatch' }} == true ]]; then - export GITHUB_RELEASE_TAG=${{ inputs.github_ref }} - else # release event - export GITHUB_RELEASE_TAG=$(basename ${GITHUB_REF}) - fi - echo "New release published ${GITHUB_RELEASE_TAG}" - pip3 install PyGithub - echo $PWD - ls -lah - ./bin/announce_release_on_slack.py - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} - SLACK_CHANNEL_ID_RELEASES: ${{ secrets.SLACK_CHANNEL_ID_RELEASES }} + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history to access commit messages + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.13' + + - name: Install dependencies + run: | + pip install httpx PyGithub + + # Set environment variables safely + - name: Set event name + id: set-event-name + env: + EVENT_NAME: ${{ github.event_name }} + run: echo "EVENT_NAME=$EVENT_NAME" >> $GITHUB_ENV + + # Handle workflow_dispatch event + - name: Set GitHub ref for workflow dispatch + if: ${{ github.event_name == 'workflow_dispatch' }} + env: + INPUT_REF: ${{ inputs.github_ref }} + run: echo "GITHUB_RELEASE_TAG=$INPUT_REF" >> $GITHUB_ENV + + # Handle release event + - name: Set GitHub ref for release event + if: ${{ github.event_name != 'workflow_dispatch' }} + env: + GH_REF: ${{ github.ref }} + run: | + REF_NAME=$(basename "$GH_REF") + echo "GITHUB_RELEASE_TAG=$REF_NAME" >> $GITHUB_ENV + + # Send notification using the safely set environment variables + - name: Send Slack notification + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SLACK_TOKEN: ${{ secrets.RUPY_TRACER_RELEASES_TOKEN }} + SLACK_SERVICE: ${{ secrets.RUPY_TRACER_RELEASES_CHANNEL_ID }} + SLACK_TEAM: ${{ secrets.RUPY_TOWN_CRIER_SERVICE_ID }} + run: | + echo "New release published ${GITHUB_RELEASE_TAG}" + python .github/scripts/announce_release_on_slack.py + \ No newline at end of file diff --git a/bin/announce_release_on_slack.py b/bin/announce_release_on_slack.py deleted file mode 100755 index 2c6625dd..00000000 --- a/bin/announce_release_on_slack.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 - -import json -import logging -import os -import requests -import sys - -from github import Github - - -def ensure_environment_variables_are_present(): - required_env_vars = ('GITHUB_RELEASE_TAG', 'GITHUB_TOKEN', - 'SLACK_BOT_TOKEN', 'SLACK_CHANNEL_ID_RELEASES') - - for v in required_env_vars: - if not os.environ.get(v): - logging.fatal("A required environment variable is missing: %s", v) - sys.exit(1) - - -def get_gh_release_info_text_with_token(release_tag, access_token): - g = Github(access_token) - repo_name = "instana/python-sensor" - repo = g.get_repo(repo_name) - release = repo.get_release(release_tag) - - logging.info("GH Release fetched successfully %s", release) - - msg = ( - f":mega: :package: A new version is released in {repo_name}\n" - f"Name: {release.title}\n" - f"Tag: {release.tag_name}\n" - f"Created at: {release.created_at}\n" - f"Published at: {release.published_at}\n" - f"{release.body}\n") - - logging.info(msg) - return msg - - -def post_on_slack_channel(slack_token, slack_channel_id, message_text): - api_url = "https://slack.com/api/chat.postMessage" - - headers = {"Authorization": f"Bearer {slack_token}", - "Content-Type": "application/json"} - body = {"channel": slack_channel_id, "text": message_text} - - response = requests.post(api_url, headers=headers, data=json.dumps(body)) - response_data = json.loads(response.text) - - if response_data["ok"]: - logging.info("Message sent successfully!") - else: - logging.fatal("Error sending message: %s", response_data['error']) - - -def main(): - # Setting this globally to DEBUG will also debug PyGithub, - # which will produce even more log output - logging.basicConfig(level=logging.INFO) - ensure_environment_variables_are_present() - - msg = get_gh_release_info_text_with_token(os.environ['GITHUB_RELEASE_TAG'], - os.environ['GITHUB_TOKEN']) - - post_on_slack_channel(os.environ['SLACK_BOT_TOKEN'], - os.environ['SLACK_CHANNEL_ID_RELEASES'], - msg) - - -if __name__ == "__main__": - main()