diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4910b91 --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +# Add env variables here +TESTING= +DEBUG= +SLACK_TOKEN= +SLACK_CHANNEL= +ENVIRONMENT= diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d9f2c3b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,33 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "" +labels: "" +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: + +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. + +**Acceptance Criteria** +Add acceptance criteria here. + +**Documentation** +Add documentation as required diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..f007c50 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,26 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "" +labels: "" +assignees: "" +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. + +**Acceptance Criteria** +Add acceptance criteria here. + +**Documentation** + +- [ ] Add Documentation as required diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..5c45a34 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,29 @@ +#[ISSUE_ID] + + + + +### Please complete the following steps and check these boxes before filing your PR: + +### Types of changes + + + +- [ ] Bug fix (a change which fixes an issue) +- [ ] New feature (a change which adds functionality) + +### Short description of what this resolves: + + + + +### Checklist: + + + + +- [ ] I have performed a self-review of my own code. +- [ ] The code follows the style guidelines of this project. +- [ ] I have documented my code wherever required. +- [ ] The changes requires a change to the documentation. +- [ ] I have updated the documentation based on the my changes. diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 0000000..dd390cf --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -0,0 +1,16 @@ +name: pre-commit + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: "3.9" + - uses: pre-commit/action@v2.0.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..684d8f2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,161 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +big-query-credentials.json +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..72097ce --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,40 @@ +exclude: "^\ + (third-party/.*)\ + " + +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: check-added-large-files # prevents giant files from being committed. + - id: check-case-conflict # checks for files that would conflict in case-insensitive filesystems. + - id: check-merge-conflict # checks for files that contain merge conflict strings. + - id: check-yaml # checks yaml files for parseable syntax. + - id: detect-private-key # detects the presence of private keys. + - id: end-of-file-fixer # ensures that a file is either empty, or ends with one newline. + - id: fix-byte-order-marker # removes utf-8 byte order marker. + - id: mixed-line-ending # replaces or checks mixed line ending. + - id: requirements-txt-fixer # sorts entries in requirements.txt. + - id: trailing-whitespace # trims trailing whitespace. + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + files: \.(js|ts|jsx|tsx|css|less|html|json|markdown|md|yaml|yml)$ + + - repo: https://github.com/psf/black + rev: 24.2.0 + hooks: + - id: black + + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + args: [--profile=black] + + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v17.0.6 + hooks: + - id: clang-format diff --git a/README.md b/README.md new file mode 100644 index 0000000..64c0719 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Slack-Alert-Service + +This repository supports a generic Slack alert service. We use a trigger pub/sub to send budget alert notifications and an HTTP trigger to alert the team about Exotel call permissions on the Slack channel. + +## Installation + +### Prerequisite + +1. pyenv +2. python 3.11 + +### Steps + +1. Clone the repository + ```sh + git clone https://github.com/DostEducation/slack-alert-service.git + ``` +2. Switch to project folder and setup the virtual environment + ```sh + cd slack-alert-service + python -m venv venv + ``` +3. Activate the virtual environment + ```sh + source ./venv/bin/activate + ``` +4. Install the dependencies: + ```sh + pip install -r requirements.txt + ``` +5. Set up your .env file by copying .env.example + ```sh + cp .env.example .env + ``` +6. Add/update variables in your `.env` file for your environment. +7. Run the following command to get started with pre-commit + ```sh + pre-commit install + ``` +8. Start the server by following command: + + ```sh + functions_framework --target=handle_alerts --debug + ``` diff --git a/app/helpers/notification_helper.py b/app/helpers/notification_helper.py new file mode 100644 index 0000000..c0e1dfd --- /dev/null +++ b/app/helpers/notification_helper.py @@ -0,0 +1,8 @@ +from app import services + + +def notify(slack_block): + """Notify on slack.""" + notification_service = services.SlackService() + send_alert = notification_service.send_alert(slack_block) + return send_alert diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..9ec610b --- /dev/null +++ b/app/services/__init__.py @@ -0,0 +1 @@ +from .slack_service import * diff --git a/app/services/slack_service.py b/app/services/slack_service.py new file mode 100644 index 0000000..f8dcd92 --- /dev/null +++ b/app/services/slack_service.py @@ -0,0 +1,26 @@ +from ssl import SSLContext +import slack +import config +from utils import logger + +class SlackService: + def __init__(self): + self.token = config.SLACK_TOKEN + self.sslcert = SSLContext() + self.channel_name = config.SLACK_CHANNEL + self.slack_client = slack.WebClient( + token=self.token, + ssl=self.sslcert, + ) + + def send_alert(self, slack_block): + try: + response = self.slack_client.chat_postMessage( + channel=self.channel_name, + blocks=slack_block["blocks"], + ) + logger.info(f"Alert Sent: {response}") + return True + except Exception as e: + logger.error(f"Exception occurred:{e}") + return False diff --git a/config.py b/config.py new file mode 100644 index 0000000..91e0829 --- /dev/null +++ b/config.py @@ -0,0 +1,13 @@ +import os + +ENVIRONMENT = os.environ.get("ENVIRONMENT", "development") + +if ENVIRONMENT == "development": + from dotenv import load_dotenv + # Load environment variables from .env file + load_dotenv() + +TESTING = os.environ.get("TESTING") +DEBUG = os.environ.get("DEBUG") +SLACK_TOKEN = os.environ.get("SLACK_TOKEN") +SLACK_CHANNEL = os.environ.get("SLACK_CHANNEL") diff --git a/main.py b/main.py new file mode 100644 index 0000000..187c476 --- /dev/null +++ b/main.py @@ -0,0 +1,21 @@ +import json +import functions_framework +from app.helpers import notification_helper +from utils import logger + +# Endpoint of the `slack-alert-service` cloud function. +@functions_framework.http +def handle_alerts(req): + try: + if not req.method == "POST": + return "Method not allowed" + + # Retrieving data from the request + jsonData = req.get_json() + slack_block = jsonData.get("slack_block") + notification_helper.notify(slack_block) + except Exception as e: + error_message = "Error while handling request for slack alert service" + logger.error(f"{error_message}: {e}") + return {"message": f"{error_message}"}, 500 + return (json.dumps({"Success": True}), 200, {"ContentType": "application/json"}) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3aa2e7d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +functions-framework==3.2.0 +google-cloud-logging==3.5.0 +pre-commit==3.6.2 +slackclient==2.9.4 +Werkzeug==2.3.7 diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..dedfb26 --- /dev/null +++ b/utils/__init__.py @@ -0,0 +1 @@ +from .loggingutils import * diff --git a/utils/loggingutils.py b/utils/loggingutils.py new file mode 100644 index 0000000..af3eb04 --- /dev/null +++ b/utils/loggingutils.py @@ -0,0 +1,17 @@ +import logging + +from google.cloud import logging as gcloud_logging + +import config + +logger = logging.getLogger() +logging.basicConfig(level=logging.DEBUG) + +if config.ENVIRONMENT == "development": + log_handler = logger.handlers[0] + logger.addHandler(log_handler) +else: + log_client = gcloud_logging.Client() + log_client.setup_logging() + log_handler = log_client.get_default_handler() + logger.addHandler(log_handler)