Skip to content

Commit

Permalink
Add discord notification to deployment script (#199)
Browse files Browse the repository at this point in the history
* Add discord notification to deployment script

* Improve deployment script when handling discord notification url

* Use only Python stdlib in the deployment script

* Update deployment script

* Fix bugs in deployment script
  • Loading branch information
ricardogsilva authored Aug 23, 2024
1 parent ea23967 commit 0772d00
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 38 deletions.
121 changes: 83 additions & 38 deletions deployments/staging/hook-scripts/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# This script is to be run by the `webhook` server whenever there
# is a push to the arpav-ppcv-backend repository.
#
# In order to simplify deployment, this script only uses stuff from the Python
# standard library.
# NOTE: IN ORDER TO SIMPLIFY DEPLOYMENT, THIS SCRIPT SHALL ONLY USE STUFF FROM THE
# PYTHON STANDARD LIBRARY

import argparse
import dataclasses
Expand All @@ -13,12 +13,15 @@
import os
import shlex
import shutil
import urllib.request
from pathlib import Path
from subprocess import run
from typing import (
Optional,
Protocol,
Sequence,
)
from subprocess import run
from urllib.error import HTTPError

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -146,10 +149,12 @@ def handle(self) -> None:
@dataclasses.dataclass
class _StartCompose:
env_file_db_service: Path
env_file_legacy_db_service: Path
env_file_webapp_service: Path
env_file_frontend_service: Path
env_file_martin_service: Path
env_file_prefect_db_service: Path
env_file_prefect_server_service: Path
env_file_prefect_static_worker_service: Path
compose_files_fragment: str
name: str = "start docker compose"

Expand All @@ -163,10 +168,12 @@ def handle(self) -> None:
env={
**os.environ,
"ARPAV_PPCV_DEPLOYMENT_ENV_FILE_DB_SERVICE": self.env_file_db_service,
"ARPAV_PPCV_DEPLOYMENT_ENV_FILE_LEGACY_DB_SERVICE": self.env_file_legacy_db_service, # noqa
"ARPAV_PPCV_DEPLOYMENT_ENV_FILE_WEBAPP_SERVICE": self.env_file_webapp_service, # noqa
"ARPAV_PPCV_DEPLOYMENT_ENV_FILE_FRONTEND_SERVICE": self.env_file_frontend_service, # noqa
"ARPAV_PPCV_DEPLOYMENT_ENV_FILE_MARTIN_SERVICE": self.env_file_martin_service, # noqa
"ARPAV_PPCV_DEPLOYMENT_ENV_FILE_PREFECT_DB_SERVICE": self.env_file_prefect_db_service, # noqa
"ARPAV_PPCV_DEPLOYMENT_ENV_FILE_PREFECT_SERVER_SERVICE": self.env_file_prefect_server_service, # noqa
"ARPAV_PPCV_DEPLOYMENT_ENV_FILE_PREFECT_STATIC_WORKER_SERVICE": self.env_file_prefect_static_worker_service, # noqa
},
check=True,
)
Expand Down Expand Up @@ -205,39 +212,39 @@ def handle(self) -> None:


@dataclasses.dataclass
class _RunLegacyMigrations:
webapp_service_name: str
name: str = "run legacy DB migrations"
class _SendDiscordChannelNotification:
webhook_url: str
content: str
name: str = "send a notification to a discord channel"

def handle(self) -> None:
print("Upgrading legacy database...")
run(
shlex.split(
f"docker exec {self.webapp_service_name} poetry run "
f"arpav-ppcv django-admin migrate"
),
check=True,
)


@dataclasses.dataclass
class _CollectLegacyStaticFiles:
webapp_service_name: str
name: str = "collect legacy static files"
print("Sending discord channel notification...")
request = urllib.request.Request(self.webhook_url, method="POST")
request.add_header("Content-Type", "application/json")

def handle(self) -> None:
print("Collecting legacy static files...")
run(
shlex.split(
f"docker exec {self.webapp_service_name} poetry run "
f"arpav-ppcv django-admin collectstatic --no-input"
),
check=True,
)
# the discord server blocks the default user-agent sent by urllib, the
# one sent by httpx works, so we just use that
request.add_header("User-Agent", "python-httpx/0.27.0")
try:
with urllib.request.urlopen(
request, data=json.dumps({"content": self.content}).encode("utf-8")
) as response:
if 200 <= response.status <= 299:
print("notification sent")
else:
print(
f"notification response was not successful: {response.status}"
)
except HTTPError:
print("sending notification failed")


def perform_deployment(
*, raw_request_payload: str, deployment_root: Path, confirmed: bool = False
*,
raw_request_payload: str,
deployment_root: Path,
discord_webhook_url: Optional[str] = None,
confirmed: bool = False,
):
if not confirmed:
print("Performing a dry-run")
Expand All @@ -249,12 +256,15 @@ def perform_deployment(
clone_destination = Path("/tmp/arpav-ppcv-backend")
deployment_env_files = {
"db_service": deployment_root / "environment-files/db-service.env",
"legacy_db_service": (
deployment_root / "environment-files/legacy-db-service.env"
),
"webapp_service": deployment_root / "environment-files/webapp-service.env",
"frontend_service": deployment_root / "environment-files/frontend-service.env",
"martin_service": deployment_root / "environment-files/martin-service.env",
"prefect_db_service": deployment_root
/ "environment-files/prefect-db-service.env",
"prefect_server_service": deployment_root
/ "environment-files/prefect-server-service.env",
"prefect_static_worker_service": deployment_root
/ "environment-files/prefect-static-worker-service.env",
}
webapp_service_name = "arpav-ppcv-staging-webapp-1"
deployment_steps = [
Expand All @@ -272,24 +282,40 @@ def perform_deployment(
),
_StartCompose(
env_file_db_service=deployment_env_files["db_service"],
env_file_legacy_db_service=deployment_env_files["legacy_db_service"],
env_file_webapp_service=deployment_env_files["webapp_service"],
env_file_frontend_service=deployment_env_files["frontend_service"],
env_file_martin_service=deployment_env_files["martin_service"],
env_file_prefect_db_service=deployment_env_files["prefect_db_service"],
env_file_prefect_server_service=deployment_env_files[
"prefect_server_service"
],
env_file_prefect_static_worker_service=deployment_env_files[
"prefect_static_worker_service"
],
compose_files_fragment=compose_files,
),
_RunMigrations(webapp_service_name=webapp_service_name),
_CompileTranslations(webapp_service_name=webapp_service_name),
_RunLegacyMigrations(webapp_service_name=webapp_service_name),
_CollectLegacyStaticFiles(webapp_service_name=webapp_service_name),
]
if discord_webhook_url is not None:
deployment_steps.append(
_SendDiscordChannelNotification(
webhook_url=discord_webhook_url,
content=(
"A new deployment of ARPAV-PPCV to staging environment has finished"
),
)
)
for step in deployment_steps:
print(f"Running step: {step.name!r}...")
if confirmed:
step.handle()


if __name__ == "__main__":
discord_notification_env_var_name = (
"ARPAV_PPCV_DEPLOYMENT_STATUS_DISCORD_WEBHOOK_URL"
)
parser = argparse.ArgumentParser()
parser.add_argument(
"deployment_root", help="Root directory of the deployment", type=Path
Expand All @@ -303,17 +329,36 @@ def perform_deployment(
"in dry-run mode, just showing what steps would be performed"
),
)
parser.add_argument(
"--send-discord-notification",
action="store_true",
help=(
f"Send a notification to the discord channel. This only works if "
f"the {discord_notification_env_var_name} environment variable is set"
),
)
parser.add_argument(
"--verbose",
action="store_true",
help="Turn on debug logging level",
)
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.WARNING)
webhook_url = None
if (notification_url := os.getenv(discord_notification_env_var_name)) is not None:
if args.send_discord_notification:
webhook_url = notification_url
else:
if args.send_discord_notification:
logger.warning(
f"Not sending discord notification because "
f"{discord_notification_env_var_name} is not set"
)
try:
perform_deployment(
raw_request_payload=args.payload,
deployment_root=args.deployment_root,
discord_webhook_url=webhook_url,
confirmed=args.confirm,
)
except RuntimeError as err:
Expand Down
2 changes: 2 additions & 0 deletions deployments/staging/hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
# ensure the deployment script does not run in dry-mode
- source: string
name: '--confirm'
- source: string
name: '--send-discord-notification'
command-working-directory: /home/arpav/webhooks/hook-scripts
trigger-rule:
match:
Expand Down
1 change: 1 addition & 0 deletions deployments/staging/webhook.service
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

[Service]
Environment="WEBHOOK_SECRET=change-me"
Environment="ARPAV_PPCV_DEPLOYMENT_STATUS_DISCORD_WEBHOOK_URL=change-me"
ExecStart=/usr/bin/webhook -template -port 9001 -hooks /home/arpav/webhooks/hooks.yaml
User=arpav
Group=arpav

0 comments on commit 0772d00

Please sign in to comment.