Skip to content

feature/performance #645

feature/performance

feature/performance #645

Workflow file for this run

name: CI
on:
# Trigger the workflow on push or pull request,
# but only for the master/develop branches
push:
branches:
- master
- develop
pull_request:
branches:
- master
- develop
workflow_dispatch:
env:
REGISTRY_LOGIN_SERVER: ${{ secrets.ACR_LOGIN_SERVER }}
REGISTRY_USERNAME: ${{ secrets.ACR_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.ACR_PASSWORD }}
jobs:
build:
runs-on: ubuntu-latest
env:
DOCKER_BUILDKIT: '1'
steps:
- uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- name: Pull static images
run: docker-compose -f docker-compose.dev.yml pull
- id: cache-docker
uses: actions/cache@v2
with:
path: /tmp/docker-registry
key: docker-registry-buildkit-${{ hashFiles('Dockerfile') }}
- run: docker run -d -p 5000:5000 --restart=always --name registry -v /tmp/docker-registry:/var/lib/registry registry:2 && npx wait-on tcp:5000
- run: docker build . -t iati-standard-website_web:latest --cache-from=localhost:5000/iati-standard-website_web --build-arg BUILDKIT_INLINE_CACHE=1
- run: docker tag iati-standard-website_web:latest localhost:5000/iati-standard-website_web && docker push localhost:5000/iati-standard-website_web || true
if: steps.cache.outputs.cache-hit != 'true'
- name: Setup docker-compose
run: |
docker container stop registry
docker-compose -f docker-compose.dev.yml up -d
while ! echo exit | nc localhost 5000; do sleep 10; done
- name: flake8 lint
run: docker-compose -f docker-compose.dev.yml exec -T web make flake8
- name: pylint
run: docker-compose -f docker-compose.dev.yml exec -T web make pylint
- name: pydocstyle lint
run: docker-compose -f docker-compose.dev.yml exec -T web make pydocstyle
- name: pytest
run: docker-compose -f docker-compose.dev.yml exec -T web make test
deploy_dev:
needs: [build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
env:
DOCKER_BUILDKIT: '1'
TAG: ${{ github.sha }}
STAGE: dev
NAME: iati-website
steps:
- uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- id: cache-docker
uses: actions/cache@v2
with:
path: /tmp/docker-registry
key: docker-registry-buildkit-${{ hashFiles('Dockerfile') }}
- run: docker run -d -p 5000:5000 --restart=always --name registry -v /tmp/docker-registry:/var/lib/registry registry:2 && npx wait-on tcp:5000
- run: docker build . -t iati-standard-website_web:latest --cache-from=localhost:5000/iati-standard-website_web --build-arg BUILDKIT_INLINE_CACHE=1
- name: 'Login via Azure CLI'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: "Login to azure"
uses: azure/docker-login@v1
with:
login-server: ${{ env.REGISTRY_LOGIN_SERVER }}
username: ${{ env.REGISTRY_USERNAME }}
password: ${{ env.REGISTRY_PASSWORD }}
- name: "Build and push image"
run: |
docker build -f Dockerfile_deploy . -t ${{ env.REGISTRY_LOGIN_SERVER }}/${{env.STAGE}}-${{env.NAME}}:${{ env.TAG }}
docker push ${{ env.REGISTRY_LOGIN_SERVER }}/${{env.STAGE}}-${{env.NAME}}:${{ env.TAG }}
- name: "Check if blue is running"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
if echo $(az group exists --name "${{ env.STAGE }}-${{ env.NAME }}-blue") | grep -q "true"; then
echo "NEW_COLOUR=green" >> $GITHUB_ENV
echo "OLD_COLOUR=blue" >> $GITHUB_ENV
else
echo "NEW_COLOUR=blue" >> $GITHUB_ENV
echo "OLD_COLOUR=green" >> $GITHUB_ENV
fi
- name: "Create new resource group"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: az group create --location uksouth --name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}"
- name: "Deploy new Azure VM"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
az vm create \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--size Standard_B2s \
--image UbuntuLTS \
--ssh-key-values ${{ secrets.DEV_PUB_KEYS }} && \
export NEW_IP=$(az vm list-ip-addresses --name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" --resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" --query [].virtualMachine.network[].publicIpAddresses[][].ipAddress --output tsv) && \
echo "NEW_IP=$NEW_IP" >> $GITHUB_ENV && \
az postgres server firewall-rule update \
--resource-group "${{ secrets.DEV_PSQL_RESOURCE_GROUP }}" \
--server-name "${{ secrets.DEV_PSQL_NAME }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--start-ip-address $NEW_IP \
--end-ip-address $NEW_IP && \
az network nsg rule create \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--nsg-name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}NSG" \
--name AllowPort5000 \
--priority 1010 \
--access Allow \
--protocol Tcp \
--destination-port-ranges 5000 && \
az vm run-command invoke \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--command-id RunShellScript \
--scripts "sudo snap install core snapd && sudo snap install docker" && \
az vm run-command invoke \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--command-id RunShellScript \
--scripts "\
docker login -u '${{ env.REGISTRY_USERNAME }}' -p '${{ env.REGISTRY_PASSWORD }}' ${{ env.REGISTRY_LOGIN_SERVER }} && \
docker pull '${{ env.REGISTRY_LOGIN_SERVER }}/${{env.STAGE}}-${{env.NAME}}:${{ env.TAG }}' && \
docker run --restart always --name website -d -p 5000:5000 \
--log-driver 'json-file' \
--log-opt max-size=100m \
--log-opt max-file=3 \
-e DJANGO_SETTINGS_MODULE='iati.settings.dev' \
-e SECRET_KEY='${{ secrets.DEV_SECRET_KEY }}' \
-e DATABASE_NAME='${{ secrets.DEV_DATABASE_NAME }}' \
-e DATABASE_USER='${{ secrets.DEV_DATABASE_USER }}' \
-e DATABASE_PASS='${{ secrets.DEV_DATABASE_PASS }}' \
-e DATABASE_HOST='${{ secrets.DEV_DATABASE_HOST }}' \
-e DATABASE_PORT='${{ secrets.DEV_DATABASE_PORT }}' \
-e AZURE_ACCOUNT_NAME='${{ secrets.DEV_AZURE_ACCOUNT_NAME }}' \
-e AZURE_ACCOUNT_KEY='${{ secrets.DEV_AZURE_ACCOUNT_KEY }}' \
-e AZURE_CONTAINER='${{ secrets.DEV_AZURE_CONTAINER }}' \
-e ZENDESK_CAPTCHA_FIELD_ID='${{ secrets.ZENDESK_CAPTCHA_FIELD_ID }}' \
-e ZENDESK_SUSPICIOUS_FIELD_ID='${{ secrets.ZENDESK_SUSPICIOUS_FIELD_ID }}' \
-e RECAPTCHA_PUBLIC_KEY='${{ secrets.RECAPTCHA_PUBLIC_KEY }}' \
-e RECAPTCHA_PRIVATE_KEY='${{ secrets.RECAPTCHA_PRIVATE_KEY }}' \
-e GITHUB_TOKEN='${{ secrets.SSOT_GITHUB_TOKEN }}' \
'${{ env.REGISTRY_LOGIN_SERVER }}/${{env.STAGE}}-${{env.NAME}}:${{ env.TAG }}'" && \
az vm run-command invoke \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--command-id RunShellScript \
--scripts "\
echo '#!/bin/sh\n/snap/bin/docker exec -i website sh -c '\''python3 manage.py publish_scheduled_pages && python3 manage.py updatestatistics'\''' > /etc/cron.hourly/hourly-cron && \
chmod +x /etc/cron.hourly/hourly-cron" && \
az resource tag \
--tags env="$STAGE" avgCPU=true ACU=true ACUvalue=300 \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--resource-type "Microsoft.Compute/virtualMachines"
- name: "Wait for 5 minutes"
run: sleep 300
- name: "Check new Azure Container Instance success"
id: new_has_succeeded
continue-on-error: true
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
if echo $(az vm run-command invoke -g "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" -n "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" --command-id RunShellScript --scripts "docker logs website" -o tsv --query value[0].message) | grep -q "Booting worker with pid"; then
echo "Success"
else
echo "Failure"
exit 1
fi
- if: steps.new_has_succeeded.outcome == 'success'
name: "Update NGINX VM backend"
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: |
ssh-agent -a $SSH_AUTH_SOCK > /dev/null && \
ssh-add - <<< "${{ secrets.DEV_NGINX_KEY }}" && \
sed -i 's/XX.XX.XX.XX/'${{ env.NEW_IP }}'/g' config/nginx/website_dev.conf && \
scp -o StrictHostKeyChecking=no config/nginx/website_dev.conf azureuser@${{ secrets.DEV_NGINX_IP }}:/etc/nginx/conf.d/website.conf && \
ssh -o StrictHostKeyChecking=no azureuser@${{ secrets.DEV_NGINX_IP }} /home/azureuser/restart_nginx.sh
- if: steps.new_has_succeeded.outcome == 'success'
name: "Delete previous VM resource group"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
az group delete --yes --no-wait \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.OLD_COLOUR }}"
- if: steps.new_has_succeeded.outcome != 'success'
name: "Delete failed VM resource group"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
az group delete --yes --no-wait \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}"
deploy_prod:
needs: [build]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
env:
DOCKER_BUILDKIT: '1'
TAG: ${{ github.sha }}
STAGE: prod
NAME: iati-website
steps:
- uses: actions/checkout@v2
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}
- id: cache-docker
uses: actions/cache@v2
with:
path: /tmp/docker-registry
key: docker-registry-buildkit-${{ hashFiles('Dockerfile') }}
- run: docker run -d -p 5000:5000 --restart=always --name registry -v /tmp/docker-registry:/var/lib/registry registry:2 && npx wait-on tcp:5000
- run: docker build . -t iati-standard-website_web:latest --cache-from=localhost:5000/iati-standard-website_web --build-arg BUILDKIT_INLINE_CACHE=1
- name: 'Login via Azure CLI'
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: "Login to azure"
uses: azure/docker-login@v1
with:
login-server: ${{ env.REGISTRY_LOGIN_SERVER }}
username: ${{ env.REGISTRY_USERNAME }}
password: ${{ env.REGISTRY_PASSWORD }}
- name: "Build and push image"
run: |
docker build -f Dockerfile_deploy . -t ${{ env.REGISTRY_LOGIN_SERVER }}/${{env.STAGE}}-${{env.NAME}}:${{ env.TAG }}
docker push ${{ env.REGISTRY_LOGIN_SERVER }}/${{env.STAGE}}-${{env.NAME}}:${{ env.TAG }}
- name: "Check if blue is running"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
if echo $(az group exists --name "${{ env.STAGE }}-${{ env.NAME }}-blue") | grep -q "true"; then
echo "NEW_COLOUR=green" >> $GITHUB_ENV
echo "OLD_COLOUR=blue" >> $GITHUB_ENV
else
echo "NEW_COLOUR=blue" >> $GITHUB_ENV
echo "OLD_COLOUR=green" >> $GITHUB_ENV
fi
- name: "Create new resource group"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: az group create --location uksouth --name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}"
- name: "Deploy new Azure VM"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
az vm create \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--size Standard_B2s \
--image UbuntuLTS \
--ssh-key-values ${{ secrets.DEV_PUB_KEYS }} && \
export NEW_IP=$(az vm list-ip-addresses --name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" --resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" --query [].virtualMachine.network[].publicIpAddresses[][].ipAddress --output tsv) && \
echo "NEW_IP=$NEW_IP" >> $GITHUB_ENV && \
az postgres server firewall-rule update \
--resource-group "${{ secrets.PROD_PSQL_RESOURCE_GROUP }}" \
--server-name "${{ secrets.PROD_PSQL_NAME }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--start-ip-address $NEW_IP \
--end-ip-address $NEW_IP && \
az network nsg rule create \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--nsg-name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}NSG" \
--name AllowPort5000 \
--priority 1010 \
--access Allow \
--protocol Tcp \
--destination-port-ranges 5000 && \
az vm run-command invoke \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--command-id RunShellScript \
--scripts "sudo snap install core snapd && sudo snap install docker" && \
az vm run-command invoke \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--command-id RunShellScript \
--scripts "\
docker login -u '${{ env.REGISTRY_USERNAME }}' -p '${{ env.REGISTRY_PASSWORD }}' ${{ env.REGISTRY_LOGIN_SERVER }} && \
docker pull '${{ env.REGISTRY_LOGIN_SERVER }}/${{env.STAGE}}-${{env.NAME}}:${{ env.TAG }}' && \
docker run --restart always --name website -d -p 5000:5000 \
--log-driver 'json-file' \
--log-opt max-size=100m \
--log-opt max-file=3 \
-e DJANGO_SETTINGS_MODULE='iati.settings.production' \
-e SECRET_KEY='${{ secrets.PROD_SECRET_KEY }}' \
-e DATABASE_NAME='${{ secrets.PROD_DATABASE_NAME }}' \
-e DATABASE_USER='${{ secrets.PROD_DATABASE_USER }}' \
-e DATABASE_PASS='${{ secrets.PROD_DATABASE_PASS }}' \
-e DATABASE_HOST='${{ secrets.PROD_DATABASE_HOST }}' \
-e DATABASE_PORT='${{ secrets.PROD_DATABASE_PORT }}' \
-e APPLICATIONINSIGHTS_CONNECTION_STRING='${{ secrets.PROD_APPLICATIONINSIGHTS_CONNECTION_STRING }}' \
-e AZURE_ACCOUNT_NAME='${{ secrets.PROD_AZURE_ACCOUNT_NAME }}' \
-e AZURE_ACCOUNT_KEY='${{ secrets.PROD_AZURE_ACCOUNT_KEY }}' \
-e AZURE_CONTAINER='${{ secrets.PROD_AZURE_CONTAINER }}' \
-e ZENDESK_CAPTCHA_FIELD_ID='${{ secrets.ZENDESK_CAPTCHA_FIELD_ID }}' \
-e ZENDESK_SUSPICIOUS_FIELD_ID='${{ secrets.ZENDESK_SUSPICIOUS_FIELD_ID }}' \
-e RECAPTCHA_PUBLIC_KEY='${{ secrets.RECAPTCHA_PUBLIC_KEY }}' \
-e RECAPTCHA_PRIVATE_KEY='${{ secrets.RECAPTCHA_PRIVATE_KEY }}' \
-e GITHUB_TOKEN='${{ secrets.SSOT_GITHUB_TOKEN }}' \
'${{ env.REGISTRY_LOGIN_SERVER }}/${{env.STAGE}}-${{env.NAME}}:${{ env.TAG }}'" && \
az vm run-command invoke \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--command-id RunShellScript \
--scripts "\
echo '#!/bin/sh\n/snap/bin/docker exec -i website sh -c '\''python3 manage.py publish_scheduled_pages && python3 manage.py updatestatistics'\''' > /etc/cron.hourly/hourly-cron && \
chmod +x /etc/cron.hourly/hourly-cron" && \
az resource tag \
--tags env="$STAGE" avgCPU=true ACU=true ACUvalue=300 \
--resource-group "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" \
--resource-type "Microsoft.Compute/virtualMachines"
- name: "Wait for 5 minutes"
run: sleep 300
- name: "Check new Azure Container Instance success"
id: new_has_succeeded
continue-on-error: true
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
if echo $(az vm run-command invoke -g "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" -n "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}" --command-id RunShellScript --scripts "docker logs website" -o tsv --query value[0].message) | grep -q "Booting worker with pid"; then
echo "Success"
else
echo "Failure"
exit 1
fi
- if: steps.new_has_succeeded.outcome == 'success'
name: "Update NGINX VM backend"
env:
SSH_AUTH_SOCK: /tmp/ssh_agent.sock
run: |
ssh-agent -a $SSH_AUTH_SOCK > /dev/null && \
ssh-add - <<< "${{ secrets.PROD_NGINX_KEY }}" && \
sed -i 's/XX.XX.XX.XX/'${{ env.NEW_IP }}'/g' config/nginx/website_prod.conf && \
scp -o StrictHostKeyChecking=no config/nginx/website_prod.conf azureuser@${{ secrets.PROD_NGINX_IP }}:/etc/nginx/conf.d/website.conf && \
ssh -o StrictHostKeyChecking=no azureuser@${{ secrets.PROD_NGINX_IP }} /home/azureuser/restart_nginx.sh
- if: steps.new_has_succeeded.outcome == 'success'
name: "Delete previous VM resource group"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
az group delete --yes --no-wait \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.OLD_COLOUR }}"
- if: steps.new_has_succeeded.outcome != 'success'
name: "Delete failed VM resource group"
uses: "azure/[email protected]"
with:
azcliversion: 2.30.0
inlineScript: |
az group delete --yes --no-wait \
--name "${{ env.STAGE }}-${{ env.NAME }}-${{ env.NEW_COLOUR }}"