Skip to content

Commit

Permalink
chore: seperate staging deployments from production
Browse files Browse the repository at this point in the history
  • Loading branch information
SilasPeters committed Sep 30, 2024
1 parent 20dede9 commit ae09249
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 50 deletions.
17 changes: 6 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 `[email protected]` 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 `[email protected]` 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`
Expand Down Expand Up @@ -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:[email protected]
[deployment-guide]:#setting-up-the-staging-and-production-environment
Expand Down
2 changes: 2 additions & 0 deletions ansible/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ tramp
#-- SublimeText
*.sublime-workspace
#*.sublime-project

.env
1 change: 1 addition & 0 deletions ansible/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ let
types-requests
pyaml
types-pyyaml
python-dotenv
]);

linuxOnlyTools = if pkgs.stdenv.isLinux then [ pkgs.ansible-lint ] else [];
Expand Down
139 changes: 100 additions & 39 deletions ansible/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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)

Expand All @@ -114,27 +124,68 @@ 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))

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:
Expand All @@ -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": [
Expand All @@ -201,7 +275,7 @@ def discord_notify(message: str, icon: str, color: str) -> None:
}

r = requests.post(
url,
webhook_url,
json=data,
)

Expand Down Expand Up @@ -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()
4 changes: 4 additions & 0 deletions ansible/sample.env
Original file line number Diff line number Diff line change
@@ -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=

0 comments on commit ae09249

Please sign in to comment.