From ae092495e274e278115fd71579fa774a1f42526a Mon Sep 17 00:00:00 2001 From: Silas <69711739+SilasPeters@users.noreply.github.com> Date: Sat, 28 Sep 2024 23:04:26 +0200 Subject: [PATCH] chore: seperate staging deployments from production --- README.md | 17 ++---- ansible/.gitignore | 2 + ansible/default.nix | 1 + ansible/deploy.py | 139 +++++++++++++++++++++++++++++++------------- ansible/sample.env | 4 ++ 5 files changed, 113 insertions(+), 50 deletions(-) create mode 100644 ansible/sample.env diff --git a/README.md b/README.md index 9c2d7b62..8b439413 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ server to become Sticky's production server. The code in this repository depends on the following software: - [nix] -- A Discord webhook, which should be put in `ansible/.discord-webhook` +- Two Discord webhooks, which should be put in `ansible/.env` Furthermore, the Ansible playbooks assume a **vanilla Ubuntu 20.04 host** to be deployed on. @@ -157,16 +157,14 @@ performed, which are explained in detail in [this guide][deployment-new-producti ##### On your local terminal: 1. Install the Nix package manager via the steps on this page: https://nixos.org/download.html -1. Download the repository and enter the folder. +2. Download the repository and enter the folder. `$ git clone https://github.com/svsticky/sadserver` `$ cd sadserver/ansible` -1. Create a file `.discord-webhook` containing the webhook to be used for Discord notifications. Put the value of the -[`slack_notifications_webhook_url` secret] -(https://vault.bitwarden.com/#/vault?search=slack&itemId=c02392e6-728e-4bce-ae4e-ae900153afc9&cipherId=c02392e6-728e-4bce-ae4e-ae900153afc9) -in that file. You will need to login to bitwarden as `itcrowd@svsticky.nl` to read this secret. -Yes, the secret is still called `slack_notifications_webhook_url` because of legacy reasons, but you -should not just change the name because it is used in the ansible code. +3. Copy `sample.env` to `.env` and fill in the missing discord webhooks. +You will need to login to bitwarden as `itcrowd@svsticky.nl` to read this secret. +(If you find the `slack_notifications_webhook_url`, do _not_ change the name of +the secret for legacy reasons. Ansible's code is dependent on the name.) To install all required dependencies, run the following command to enter a nix shell. `$ nix-shell` @@ -209,9 +207,6 @@ Godspeed! [ansible/group_vars/all/websites.yml]:ansible/group_vars_example/all/websites.yml [Ansible Vault]:http://docs.ansible.com/ansible/playbooks_vault.html [inventory]:https://docs.ansible.com/ansible/intro_inventory.html - [slacktee]:https://github.com/course-hero/slacktee - [ansible]:https://github.com/ansible/ansible - [Bitwarden CLI]:https://help.bitwarden.com/article/cli/#download--install [deployment-new-production]:docs/deployment-new-production.md [IT Crowd]:mailto:itcrowd@svsticky.nl [deployment-guide]:#setting-up-the-staging-and-production-environment diff --git a/ansible/.gitignore b/ansible/.gitignore index ee7615bc..2c29b13a 100644 --- a/ansible/.gitignore +++ b/ansible/.gitignore @@ -24,3 +24,5 @@ tramp #-- SublimeText *.sublime-workspace #*.sublime-project + +.env diff --git a/ansible/default.nix b/ansible/default.nix index 112f4803..ee5f7f28 100644 --- a/ansible/default.nix +++ b/ansible/default.nix @@ -13,6 +13,7 @@ let types-requests pyaml types-pyyaml + python-dotenv ]); linuxOnlyTools = if pkgs.stdenv.isLinux then [ pkgs.ansible-lint ] else []; diff --git a/ansible/deploy.py b/ansible/deploy.py index af9cc9e5..a9fa91c9 100755 --- a/ansible/deploy.py +++ b/ansible/deploy.py @@ -9,10 +9,24 @@ import yaml from git.repo import Repo -from typing import Optional +from typing import Optional, List +from dotenv import load_dotenv import scripts.bitwarden as bitwarden +# Import .env file +load_dotenv() +discord_webhook_staging_deployments = os.getenv("DISCORD_WEBHOOK_STAGING_DEPLOYMENTS") +discord_webhook_production_deployments = os.getenv( + "DISCORD_WEBHOOK_STAGING_DEPLOYMENTS" +) + +if ( + discord_webhook_production_deployments is None + or discord_webhook_staging_deployments is None +): + print("At least one Discord webhook is missing. Please fill in the .env file") + @click.command() @click.option( @@ -90,11 +104,7 @@ def deploy( if check: arguments.append("--check") - if not tags is None: - arguments.append("--tags") - arguments.append(tags) - - # From-until logic: + # First determine all roles defined by from-until logic with open("main.yml", "r") as yaml_file: data = yaml.safe_load(yaml_file) @@ -114,15 +124,40 @@ def deploy( roles = roles[: roles.index(until_playbook) + 1] from_until = True - final_roles = ",".join(roles) if from_until: + from_until_roles = roles # Filtered by the from_until logic + else: + from_until_roles = [] + + # Then add all roles specified manually + if tags is not None: + manual_roles = [role.strip() for role in tags.split(",")] + else: + manual_roles = [] + + # Finally, pass down all roles specified, if any + specified_roles = from_until_roles + manual_roles + if specified_roles: arguments.append("--tags") - arguments.append(final_roles) + arguments.append(",".join(specified_roles)) arguments.append(playbook) + if host == "production": + discord_deployment_webhook = str(discord_webhook_production_deployments) + else: + discord_deployment_webhook = str(discord_webhook_staging_deployments) + if not check: - notify_deploy_start(playbook, host, user, branch, revision, roles) + notify_deploy_start( + playbook, + host, + user, + branch, + revision, + specified_roles, + discord_deployment_webhook, + ) print("Running the following playbook:") print(" ".join(arguments)) @@ -130,11 +165,27 @@ def deploy( try: subprocess.run(arguments, check=True, env=env) if not check: - notify_deploy_succes(playbook, host, branch, revision) + notify_deploy_succes( + playbook, + host, + user, + branch, + revision, + specified_roles, + discord_deployment_webhook, + ) except subprocess.CalledProcessError: if not check: - notify_deploy_failure(playbook, host, branch, revision) + notify_deploy_failure( + playbook, + host, + user, + branch, + revision, + specified_roles, + discord_deployment_webhook, + ) def current_branch_name() -> str: @@ -148,43 +199,66 @@ def current_git_revision() -> str: def notify_deploy_start( - playbook: str, host: str, user: str, git_branch: str, git_revision: str, roles: [str] + playbook: str, + host: str, + user: str, + git_branch: str, + git_revision: str, + roles: List[str], + discord_webhook: str, ) -> None: - roles_str = ', '.join(roles) + roles_str = ", ".join(roles) discord_notify( - f"*Deployment of playbook {playbook} in {host} environment started by {user}*\n" - + f'_Branch: {git_branch} - revision "{git_revision}"_\n' - + f'_Roles: {roles_str}', + f"**Deployment of playbook {playbook} to {host} started by {user}**\n" + + f'Branch: {git_branch} - revision "{git_revision}"\n' + + f"Roles: {roles_str}", ":construction:", "#46c4ff", + discord_webhook, ) def notify_deploy_succes( - playbook: str, host: str, git_branch: str, git_revision: str + playbook: str, + host: str, + user: str, + git_branch: str, + git_revision: str, + roles: List[str], + discord_webhook: str, ) -> None: + roles_str = ", ".join(roles) discord_notify( - f"*Deployment of playbook {playbook} in {host} environment succesfully completed*\n" - + f'_(branch: {git_branch} - revision "{git_revision}")_', + f"**Deployment of playbook {playbook} to {host}, started by {user}, succesfully completed**\n" + + f'Branch: {git_branch} - revision "{git_revision}"\n' + + f"Roles: {roles_str}", ":construction:", "good", + discord_webhook, ) def notify_deploy_failure( - playbook: str, host: str, git_branch: str, git_revision: str + playbook: str, + host: str, + user: str, + git_branch: str, + git_revision: str, + roles: List[str], + discord_webhook: str, ) -> None: + roles_str = ", ".join(roles) discord_notify( - f"*Deployment of playbook {playbook} in {host} environment FAILED!*\n" - + f'_(branch: {git_branch} - revision "{git_revision}")_', + f"**Deployment of playbook {playbook} to {host}, started by {user}, FAILED!**\n" + + f'Branch: {git_branch} - revision "{git_revision}"\n' + + f"Roles: {roles_str}", ":exclamation:", "danger", + discord_webhook, ) -def discord_notify(message: str, icon: str, color: str) -> None: - url = get_discord_webhook().strip() - +def discord_notify(message: str, icon: str, color: str, webhook_url: str) -> None: data = { "username": "Ansible", "attachments": [ @@ -201,7 +275,7 @@ def discord_notify(message: str, icon: str, color: str) -> None: } r = requests.post( - url, + webhook_url, json=data, ) @@ -234,18 +308,5 @@ def verify_on_latest_master(host: str) -> None: click.ClickException("There is uncommited in your working tree or staging area") -def get_discord_webhook() -> str: - webhook_filename = ".discord-webhook" - - if not os.path.exists(webhook_filename): - raise click.ClickException( - "Please create .discord-webhook with a webhook URL for deploy notifications" - ) - else: - # maybe should be a try-catch block here - with open(webhook_filename) as f: - return f.read() - - if __name__ == "__main__": deploy() diff --git a/ansible/sample.env b/ansible/sample.env new file mode 100644 index 00000000..c24c2091 --- /dev/null +++ b/ansible/sample.env @@ -0,0 +1,4 @@ +# These secrets can be found in our bitwarden under "Discord webhooks for sadserver deploy": +# https://vault.bitwarden.com/#/vault?search=slack&itemId=d6309cd5-2004-415d-931f-aae90107e62d +DISCORD_WEBHOOK_PRODUCTION_DEPLOYMENTS= +DISCORD_WEBHOOK_STAGING_DEPLOYMENTS=