Skip to content

Commit

Permalink
Migrate to Azure Container Apps
Browse files Browse the repository at this point in the history
  • Loading branch information
mbarton committed Sep 3, 2024
1 parent 2ee63a0 commit 16dd6e1
Show file tree
Hide file tree
Showing 13 changed files with 175 additions and 81 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Deploy on push to test ACA branch

on:
push:
branches:
- mbarton/azure-container-apps

permissions:
id-token: write
contents: read

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@main

- name: 'Login via Azure CLI'
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

- name: 'Run CI script'
env:
AZURE_REGISTRY_NAME: ${{ secrets.AZURE_REGISTRY_NAME }}
AZURE_CONFIG_STORAGE_ACCOUNT: ${{ secrets.AZURE_CONFIG_STORAGE_ACCOUNT }}
AZURE_CONFIG_FILE_SHARE: ${{ secrets.AZURE_CONFIG_FILE_SHARE }}
AZURE_STAGING_APP_NAME: ${{ secrets.AZURE_STAGING_APP_NAME }}
AZURE_RESOURCE_GROUP: ${{ secrets.AZURE_RESOURCE_GROUP }}
run: s/ci
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ WORKDIR /app/
# (Excludes any files/dirs matched by patterns in .dockerignore)
COPY . /app/

# Collect and compress static files into image
RUN python manage.py collectstatic --no-input
9 changes: 1 addition & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,7 @@ services:
image: e12-django:built # build image and save for use by other containers
depends_on:
- postgis
command: >
sh -c "python manage.py write_azure_pg_password_file &&
python manage.py collectstatic --noinput &&
python manage.py migrate &&
python manage.py seed --mode=seed_groups_and_permissions &&
echo $$DJANGO_STARTUP_COMMAND &&
$$DJANGO_STARTUP_COMMAND"
command: s/start-dev
restart: always

# PostgreSQL with PostGIS extension
Expand All @@ -61,4 +55,3 @@ services:
volumes:
caddy-data:
postgis-data:

24 changes: 22 additions & 2 deletions epilepsy12/apps.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import threading

from django.apps import AppConfig

from .management.commands.write_azure_pg_password_file import write_azure_pg_password_file

def periodically_update_azure_pg_password_file():
update_interval = 60 * 15 # 15 minutes

def _periodically_update_azure_pg_password_file():
write_azure_pg_password_file()

thread = threading.Timer(update_interval, _periodically_update_azure_pg_password_file)
thread.daemon = True
thread.start()

thread = threading.Timer(update_interval, _periodically_update_azure_pg_password_file)
thread.daemon = True
thread.start()

class Epilepsy12Config(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'epilepsy12'
default_auto_field = "django.db.models.BigAutoField"
name = "epilepsy12"

def ready(self) -> None:
import epilepsy12.signals

periodically_update_azure_pg_password_file()

return super().ready()
44 changes: 16 additions & 28 deletions epilepsy12/management/commands/write_azure_pg_password_file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import requests

from django.core.management.base import BaseCommand
from azure.identity import DefaultAzureCredential

# https://www.postgresql.org/docs/current/libpq-pgpass.html
#
Expand All @@ -10,36 +11,23 @@
# Django specifically does not allow you to update settings at
# runtime so we store the connection details in a separate file.

class Command(BaseCommand):
help = "Generate a Postgres password file to use an Azure managed identity to authenticate with the database."

def fetch_azure_token(self):
url = 'http://169.254.169.254/metadata/identity/oauth2/token'

params = {
'api-version': '2018-02-01',
'resource': 'https://ossrdbms-aad.database.windows.net'
}

headers = {
'Metadata': 'true'
}
def write_azure_pg_password_file():
password_file = os.environ.get('E12_POSTGRES_DB_PASSWORD_FILE')

resp = requests.get(url=url, params=params, headers=headers)
data = resp.json()
if not password_file:
return

return data['access_token']
password = DefaultAzureCredential().get_token("https://ossrdbms-aad.database.windows.net").token

def handle(self, *args, **options):
password_file = os.environ.get('E12_POSTGRES_DB_PASSWORD_FILE')
with open(password_file, "w") as f:
f.write(f"*:*:*:*:{password}")

# libpg silently ignores the file if it's readable by anyone else
os.chmod(password_file, 0o600)

if not password_file:
return

password = self.fetch_azure_token()
class Command(BaseCommand):
help = "Generate a Postgres password file to use an Azure managed identity to authenticate with the database."

with open(password_file, "w") as f:
f.write(f"*:*:*:*:{password}")

# libpg silently ignores the file if it's readable by anyone else
os.chmod(password_file, 0o600)
def handle(self, *args, **options):
write_azure_pg_password_file()
32 changes: 32 additions & 0 deletions rcpch-audit-engine/build_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os
import json
import subprocess
import logging


logger = logging.getLogger(__name__)
_build_info = None

def get_build_info_from_dot_git_folder():
try:
result = subprocess.run('s/get-build-info', stdout = subprocess.PIPE)
return json.loads(result.stdout)
except:
logger.exception("Error getting git data from repository")
return {
"active_git_branch": "[branch name not found]",
"latest_git_commit": "[latest commit hash not found]",
}

def get_build_info(request=None):
global _build_info

if not _build_info:
try:
with open("build_info.json", "r") as f:
_build_info = json.load(f)
except:
# Running in dev
_build_info = get_build_info_from_dot_git_folder()

return _build_info
42 changes: 0 additions & 42 deletions rcpch-audit-engine/git_context_processor.py

This file was deleted.

6 changes: 5 additions & 1 deletion rcpch-audit-engine/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import os
from pathlib import Path

from dotenv import load_dotenv

# third party imports
from django.core.management.utils import get_random_secret_key

Expand All @@ -26,6 +28,8 @@

logger = logging.getLogger(__name__)

load_dotenv('envs/.env')

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent

Expand Down Expand Up @@ -166,7 +170,7 @@
"django.contrib.auth.context_processors.auth",
"django.contrib.messages.context_processors.messages",
"django_auto_logout.context_processors.auto_logout_client", # auto logout
"rcpch-audit-engine.git_context_processor.get_active_branch_and_commit",
"rcpch-audit-engine.build_info.get_build_info",
]
},
},
Expand Down
2 changes: 2 additions & 0 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ phonenumbers==8.13.43
psycopg2-binary==2.9.9
whitenoise==6.7.0
openpyxl==3.1.5
python-dotenv==1.0.1
azure-identity==1.17.1

## graphing
plotly==5.23.0
Expand Down
34 changes: 34 additions & 0 deletions s/ci
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash -e

az acr login --name ${AZURE_REGISTRY_NAME}

# Download .env file (--query avoids outputting all the info about the blob in our public logs)
az storage file download \
--auth-mode login \
--enable-file-backup-request-intent \
--account-name ${AZURE_CONFIG_STORAGE_ACCOUNT} \
--share-name ${AZURE_CONFIG_FILE_SHARE} \
--query 'size' \
--path .env --dest envs/.env

# Burn in build info (git hash, branch etc) into the image
s/get-build-info > build_info.json

docker compose build

# Tests (against local Postgres)
docker compose up -d
s/test
docker compose down

# Push to Azure
azure_tag="${AZURE_REGISTRY_NAME}.azurecr.io/e12-django:${GITHUB_SHA}"
docker tag e12-django:built ${azure_tag}
docker push ${azure_tag}

# Deploy to Azure Container Apps
az containerapp revision copy \
--name ${AZURE_STAGING_APP_NAME} \
--resource-group ${AZURE_RESOURCE_GROUP} \
--image ${azure_tag} \
--query 'properties.provisioningState'
10 changes: 10 additions & 0 deletions s/get-build-info
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/bash

git_hash=$(git rev-parse HEAD)
branch_name=$(git rev-parse --abbrev-ref HEAD)

if [[ "${git_hash}" == 'HEAD' ]]; then
git_hash='unknown'
fi

echo "{\"latest_git_commit\":\"${git_hash}\",\"active_git_branch\":\"${branch_name}\"}"
6 changes: 6 additions & 0 deletions s/start-dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash -e

python manage.py collectstatic --noinput
python manage.py migrate
python manage.py seed --mode=seed_groups_and_permissions
python manage.py runserver 0.0.0.0:8000
12 changes: 12 additions & 0 deletions s/start-prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash -e

python manage.py write_azure_pg_password_file
python manage.py migrate
python manage.py seed --mode=seed_groups_and_permissions

gunicorn \
--bind=0.0.0.0:8000 \
--timeout 600 \
--access-logfile '-' \
--access-logformat '%({x-forwarded-for}i)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' \
rcpch-audit-engine.wsgi

0 comments on commit 16dd6e1

Please sign in to comment.