Skip to content

Commit

Permalink
feat: provisioning, deployment on GH actions
Browse files Browse the repository at this point in the history
* Improvement to the provisioning script
* Added example environment and configuration files
* Added Github Actions deployment workflows for dev, prod
  • Loading branch information
simon-20 committed Jul 18, 2024
1 parent fe8826e commit 5609ae1
Show file tree
Hide file tree
Showing 12 changed files with 453 additions and 16 deletions.
16 changes: 9 additions & 7 deletions .env-example
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
DATA_REGISTRATION=ckan-registry
DATA_REGISTRY_BASE_URL=https://iatiregistry.org/api/3/action/package_search

NUMBER_DOWNLOADER_THREADS=25
BLOB_STORAGE_BASE_PUBLIC_URL=http://127.0.0.1:10000/devstoreaccount1

NUMBER_DOWNLOADER_THREADS=1 # makes for easier testing locally

FORCE_REDOWNLOAD_AFTER_HOURS=24

REMOVE_LAST_GOOD_DOWNLOAD_AFTER_FAILING_HOURS=72

# Logs directory
# Log file
LOGFILE=

# Sample local setup - values read by docker compose (for simple Postgres DB
# creation), and used by the app
DB_NAME=bulk_data_service_db
DB_USER=bds

# Local setup - values read by docker compose, and used by the app
DB_PASS=
DB_HOST=
DB_PORT=
DB_PASS=pass
DB_HOST=localhost
DB_PORT=5255
DB_SSL_MODE=disable
DB_CONNECTION_TIMEOUT=30

Expand Down
118 changes: 118 additions & 0 deletions .github/workflows/build-and-deploy-job.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
name: Generic build and deploy (called by other workflows)

on:
workflow_call:
inputs:
APP_NAME:
required: true
type: string
TARGET_ENVIRONMENT:
required: true
type: string


jobs:
build-and-deploy:
runs-on: ubuntu-latest

env:
APP_NAME: ${{ inputs.APP_NAME }}
TARGET_ENVIRONMENT: ${{ inputs.TARGET_ENVIRONMENT }}

DOCKER_IMAGE_TAG: ${{ github.sha }}

# Needed as an environment variable for use of 'az' cmd in inline shell script
ACR_LOGIN_SERVER: ${{ secrets.ACR_LOGIN_SERVER }}
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}

steps:
- name: 'Generate/build derived environment variables'
run: |
echo "TARGET_ENVIRONMENT_UPPER=${TARGET_ENVIRONMENT^^}" >> ${GITHUB_ENV}
echo "CONTAINER_INSTANCE_BASE_NAME=aci-${APP_NAME}" >> ${GITHUB_ENV}
echo "RESOURCE_GROUP_BASE_NAME=rg-${APP_NAME}" >> ${GITHUB_ENV}
- name: 'Print calculated environment variables'
run: |
echo $TARGET_ENVIRONMENT_UPPER
echo $CONTAINER_INSTANCE_BASE_NAME
echo $RESOURCE_GROUP_BASE_NAME
- name: 'Checkout GitHub Action'
uses: actions/checkout@v4

- name: 'Login via Azure CLI'
uses: azure/login@v2
with:
creds: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'AZURE_CREDENTIALS')] }}

- name: 'Login to Docker Hub'
uses: docker/[email protected]
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_TOKEN }}

- name: 'Login to Azure Container Registry'
uses: azure/docker-login@v2
with:
login-server: ${{ env.ACR_LOGIN_SERVER }}
username: ${{ env.ACR_USERNAME }}
password: ${{ env.ACR_PASSWORD }}

- name: 'Build and push image'
run: |
IMAGE_NAME=$ACR_LOGIN_SERVER/$APP_NAME-$TARGET_ENVIRONMENT:$DOCKER_IMAGE_TAG
echo "IMAGE_NAME=$IMAGE_NAME" >> $GITHUB_ENV
docker build . -f Dockerfile -t $IMAGE_NAME
docker push $IMAGE_NAME
- name: 'Print IMAGE_NAME'
run: echo $IMAGE_NAME

- name: 'Delete existing container group'
uses: 'azure/CLI@v2'
with:
inlineScript: |
az -v
az container delete -y \
--name "${{ env.CONTAINER_INSTANCE_BASE_NAME }}-${{ env.TARGET_ENVIRONMENT }}" \
--resource-group "${{ env.RESOURCE_GROUP_BASE_NAME }}-${{ env.TARGET_ENVIRONMENT }}"
- name: 'Replace Env Vars and Secrets in ARM Yaml template'
env:
# Credentials for the app's resources
AZURE_STORAGE_CONNECTION_STRING: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'AZURE_STORAGE_CONNECTION_STRING')] }}

DB_HOST: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DB_HOST')] }}
DB_USER: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DB_USER')] }}
DB_PASS: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DB_PASS')] }}
DB_NAME: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DB_NAME')] }}
DB_PORT: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DB_PORT')] }}
DB_SSL_MODE: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DB_SSL_MODE')] }}
DB_CONNECTION_TIMEOUT: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DB_CONNECTION_TIMEOUT')] }}

LOG_WORKSPACE_ID: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'LOG_WORKSPACE_ID')] }}
LOG_WORKSPACE_KEY: ${{ secrets[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'LOG_WORKSPACE_KEY')] }}

# Variables which configure the app
DATA_REGISTRATION: ${{ vars[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DATA_REGISTRATION')] }}
DATA_REGISTRY_BASE_URL: ${{ vars[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'DATA_REGISTRY_BASE_URL')] }}
NUMBER_DOWNLOADER_THREADS: ${{ vars[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'NUMBER_DOWNLOADER_THREADS')] }}
FORCE_REDOWNLOAD_AFTER_HOURS: ${{ vars[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'FORCE_REDOWNLOAD_AFTER_HOURS')] }}
REMOVE_LAST_GOOD_DOWNLOAD_AFTER_FAILING_HOURS: ${{ vars[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'REMOVE_LAST_GOOD_DOWNLOAD_AFTER_FAILING_HOURS')] }}
ZIP_WORKING_DIR: ${{ vars[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'ZIP_WORKING_DIR')] }}
AZURE_STORAGE_BLOB_CONTAINER_NAME_IATI_XML: ${{ vars[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'AZURE_STORAGE_BLOB_CONTAINER_NAME_IATI_XML')] }}
AZURE_STORAGE_BLOB_CONTAINER_NAME_IATI_ZIP: ${{ vars[format('{0}_{1}', env.TARGET_ENVIRONMENT_UPPER, 'AZURE_STORAGE_BLOB_CONTAINER_NAME_IATI_ZIP')] }}

run: |
./azure-deployment/generate-manifest-from-template.sh
- name: 'Deploy group to Azure Container Instances'
uses: 'azure/CLI@v2'
with:
inlineScript: |
az -v
az container create --debug \
--resource-group "${{ env.RESOURCE_GROUP_BASE_NAME }}-${{ env.TARGET_ENVIRONMENT }}" \
--file ./azure-deployment/azure-resource-manager-deployment-manifest.yml
19 changes: 19 additions & 0 deletions .github/workflows/deploy-to-dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: Deploy Bulk Data Service to dev


on:
workflow_dispatch:
push:
paths-ignore:
- '.github/workflows/deploy-to-prod.yml'
branches:
- develop


jobs:
call-build-and-deploy:
uses: ./.github/workflows/build-and-deploy-job.yml
secrets: inherit
with:
APP_NAME: "bulk-data-service"
TARGET_ENVIRONMENT: "test"
16 changes: 16 additions & 0 deletions .github/workflows/deploy-to-prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Deploy Bulk Data Service to production


on:
workflow_dispatch:
release:
types: [published]


jobs:
call-build-and-deploy:
uses: ./.github/workflows/build-and-deploy-job.yml
secrets: inherit
with:
APP_NAME: "bulk-data-service"
TARGET_ENVIRONMENT: "test"
28 changes: 28 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Run Automated Tests

on:
workflow_dispatch:
push:
branches:
develop

jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup python
uses: actions/setup-python@v5
with:
python-version: 3.12
architecture: x64

- name: Install requirements-dev.txt
run: pip install -r requirements-dev.txt

- name: Run docker-compose
run: cd ./tests-local-environment; docker compose up -d

- name: Run automated tests
run: pytest
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ __pycache__

/web/index.html
/azure-deployment/azure-resource-manager-deployment-manifest.yml
/azure-deployment/secrets-for-manual-azure-deploy.env
/azure-deployment/manual-azure-deploy-secrets.env
/azure-deployment/manual-azure-deploy-variables.env
48 changes: 40 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Support | https://iatistandard.org/en/guidance/get-support/
## High-level requirements

* Python 3.12
* Postgres 16
* Postgres DB
* Azure storage account with blob storage enabled

## Running the app locally
Expand Down Expand Up @@ -47,7 +47,7 @@ The example file is preconfigured to work with the local docker compose setup.

#### 4. Install some version of `dotenv` (optional)

The `.env` file is used when running things locally to store environment variables that configure the apps mentioned above. To run the apps below, you can either source this file to get the environment variables into your current terminal context, or you can one of the various `dotenv` command line tools to import the environment on each run.
The `.env` file is used when running things locally to store environment variables that configure the apps mentioned above. Docker Compose will read this automatically, but when running the bulk data service app or `yoyo` directly, you need to get these variables into the shell environment: you can either source this file to get the environment variables into your current terminal context, or you can one of the various `dotenv` command line tools to import the environment on each run (using `dotenv` lets you quickly switch different `.env` files in and out, which can be useful for testing, debugging, etc).

### Running after first-time setup

Expand All @@ -65,6 +65,7 @@ Once the docker compose setup is running, start the bulk download app with:
dotenv run python src/iati_bulk_data_service.py -- --operation checker --single-run --run-for-n-datasets=50
```

*Note:* not all versions of `dotenv` require a `run` subcommand.

## Development on the app

Expand Down Expand Up @@ -143,9 +144,9 @@ The Bulk Data Service's database schema management is handled by [yoyo](https://
The following commands may be useful:

```
dotenv yoyo -- list # list available migrations
dotenv yoyo -- rollback # rollback, interactively
dotenv yoyo -- new # create file for a new migration
dotenv run yoyo -- list # list available migrations
dotenv run yoyo -- rollback # rollback, interactively
dotenv run yoyo -- new # create file for a new migration
```


Expand Down Expand Up @@ -180,15 +181,46 @@ This automated test environment is configured via the following files:

You can use the Mockoon GUI application to edit the mockoon server configuration file (`mockoon-registration-and-data-server-config.json`).

## Deployment
## Provisioning and Deployment

### Building the docker image
### Initial Provisioning

You can create an Azure-based instance of Bulk Data Service using the `azure-create-resources.sh` script. It must be run from the root of the repository, and it requires (i) the environment variable `BDS_DB_ADMIN_PASSWORD` to be set with the password for the database, and (ii) a single parameter which is the name of the environment/instance. For instance, the following command will create a dev instance:

```bash
BDS_DB_ADMIN_PASSWORD=passwordHere ./azure-provision/azure-create-resources.sh dev`
```
docker build . -t criati.azurecr.io/bulk-data-service-test

This will create a resource group on Azure called `rg-bulk-data-service-dev`, and then create and configure all the Azure resources needed for the Bulk Data Service within that resource group (except for the Container Instance, which is created/updated as part of the deploy stage).

At the end of its run, the `azure-create-resources.sh` script will print out various secrets which need to be added to Github Actions.

### Deployment - CI/CD

The application is setup to deploy to the dev instance when a PR is merged to
`develop`, and to production when a release is done on `main` branch.

Sometimes, when altering the CI/CD setup or otherwise debugging, it can be
useful to do things manually. The Bulk Data Service can be released to an Azure instance (e.g., a test instance) using the following command:

```bash
./azure-deployment/manual-azure-deploy-from-local.sh test
```

For this to work, you need to put the secrets you want to use in `azure-deployment/manual-azure-deploy-secrets.env` and the variables you want to use in `azure-deployment/manual-azure-deploy-variables.env`. These is an example of each of these files that can be used as a starting point.


### Manually building the docker image (to test/develop the deployment setup)

You can build the docker image using the following command, replacing `INSTANCE_NAME` with the relevant instance:

```bash
docker build . -t criati.azurecr.io/bulk-data-service-INSTANCE_NAME
```

To run it locally:

```bash
docker container run --env-file=.env-docker "criati.azurecr.io/bulk-data-service-dev" --operation checker --single-run --run-for-n-datasets 20
```

Expand Down
73 changes: 73 additions & 0 deletions azure-deployment/azure-resource-manager-deployment-template.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: "aci-#APP_NAME#-#TARGET_ENVIRONMENT#"
apiVersion: "2021-10-01"
location: "uksouth"
properties: # Properties of container group
imageRegistryCredentials: # Credentials to pull a private image
- server: "#ACR_LOGIN_SERVER#"
username: "#ACR_USERNAME#"
password: "#ACR_PASSWORD#"
restartPolicy: "Never"
osType: "Linux"
diagnostics:
logAnalytics:
workspaceId: "#LOG_WORKSPACE_ID#"
workspaceKey: "#LOG_WORKSPACE_KEY#"
containers:
- name: "#APP_NAME#-#TARGET_ENVIRONMENT#"
properties: # Properties of an instance
resources: # Resource requirements of the instance
requests:
memoryInGB: 4
cpu: 1
image: "#ACR_LOGIN_SERVER#/#APP_NAME#-#TARGET_ENVIRONMENT#"
ports:
- port: 9090
command:
- "/usr/local/bin/python"
- "src/iati_bulk_data_service.py"
- "--operation"
- "checker"
environmentVariables:
- name: DATA_REGISTRATION
value: "#DATA_REGISTRATION#"
- name: DATA_REGISTRY_BASE_URL
value: "#DATA_REGISTRY_BASE_URL#"
- name: BLOB_STORAGE_BASE_PUBLIC_URL
value: "https://sabulkdataservice#TARGET_ENVIRONMENT#.blob.core.windows.net"
- name: NUMBER_DOWNLOADER_THREADS
value: "#NUMBER_DOWNLOADER_THREADS#"
- name: FORCE_REDOWNLOAD_AFTER_HOURS
value: "#FORCE_REDOWNLOAD_AFTER_HOURS#"
- name: REMOVE_LAST_GOOD_DOWNLOAD_AFTER_FAILING_HOURS
value: "#REMOVE_LAST_GOOD_DOWNLOAD_AFTER_FAILING_HOURS#"
- name: LOGFILE
value: ""
- name: ZIP_WORKING_DIR
value: "#ZIP_WORKING_DIR#"
- name: AZURE_STORAGE_BLOB_CONTAINER_NAME_IATI_XML
value: "#AZURE_STORAGE_BLOB_CONTAINER_NAME_IATI_XML#"
- name: AZURE_STORAGE_BLOB_CONTAINER_NAME_IATI_ZIP
value: "#AZURE_STORAGE_BLOB_CONTAINER_NAME_IATI_ZIP#"

- name: AZURE_STORAGE_CONNECTION_STRING
secureValue: "#AZURE_STORAGE_CONNECTION_STRING#"
- name: DB_HOST
secureValue: "#APP_NAME#-db-#TARGET_ENVIRONMENT#.postgres.database.azure.com"
- name: DB_PORT
secureValue: "#DB_PORT#"
- name: DB_USER
secureValue: "#DB_USER#"
- name: DB_PASS
secureValue: "#DB_PASS#"
- name: DB_NAME
secureValue: "#DB_NAME#"
- name: DB_SSL_MODE
secureValue: "#DB_SSL_MODE#"
- name: DB_CONNECTION_TIMEOUT
secureValue: "#DB_CONNECTION_TIMEOUT#"

ipAddress:
type: "public"
dnsNameLabel: "#APP_NAME#-#TARGET_ENVIRONMENT#"
ports:
- port: 9090
Loading

0 comments on commit 5609ae1

Please sign in to comment.