Skip to content

Commit

Permalink
Merge pull request #692 from DFE-Digital/docker-initcontainer
Browse files Browse the repository at this point in the history
Docker initcontainer
  • Loading branch information
DrizzlyOwl authored Feb 7, 2025
2 parents 6e2fff0 + f68199c commit 08de881
Show file tree
Hide file tree
Showing 11 changed files with 146 additions and 40 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ docker-compose.yml
docker-compose.*.yml
docker/
!docker/web-docker-entrypoint.sh
!docker/init-docker-entrypoint.sh

### Docs
docs
Expand Down
72 changes: 62 additions & 10 deletions .github/workflows/deploy-to-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.event.inputs.environment }}

env:
IMAGE_NAME: fiat-app

jobs:
set-env:
name: Determine environment
runs-on: ubuntu-22.04
outputs:
environment: ${{ steps.var.outputs.environment }}
branch: ${{ steps.var.outputs.branch }}
release: ${{steps.var.outputs.release}}
release: ${{ steps.var.outputs.release }}
image-name: ${{ steps.var.outputs.image-name }}
steps:
- if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
name: Get branch name for push/dispatch event
Expand All @@ -47,6 +51,7 @@ jobs:
echo "environment=${ENVIRONMENT,,}" >> $GITHUB_OUTPUT
echo "branch=$GIT_BRANCH" >> $GITHUB_OUTPUT
echo "release=${RELEASE,,}" >> $GITHUB_OUTPUT
echo "image-name=${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT
create-tag:
if: needs.set-env.outputs.environment == 'production'
Expand Down Expand Up @@ -80,32 +85,79 @@ jobs:
core.setFailed(error.message);
}
deploy-image:
build:
name: Build
needs: [ set-env ]
permissions:
id-token: write
contents: read
packages: write
name: Deploy to environment
needs: [ set-env ]
uses: DFE-Digital/deploy-azure-container-apps-action/.github/workflows/[email protected]
uses: DFE-Digital/deploy-azure-container-apps-action/.github/workflows/[email protected]
strategy:
matrix:
stage: [
"final",
"initcontainer"
]
include:
- stage: "final"
tag-prefix: ""
- stage: "initcontainer"
tag-prefix: "init-"
with:
docker-image-name: 'fiat-app'
environment: ${{ needs.set-env.outputs.environment }}
docker-image-name: ${{ needs.set-env.outputs.image-name }}
docker-build-file-name: 'docker/Dockerfile'
docker-build-target: ${{ matrix.stage }}
docker-tag-prefix: ${{ matrix.tag-prefix }}

import:
name: Import
needs: [ set-env, build ]
permissions:
id-token: write
uses: DFE-Digital/deploy-azure-container-apps-action/.github/workflows/[email protected]
strategy:
matrix:
stage: [
"final",
"initcontainer"
]
include:
- stage: "final"
tag-prefix: ""
- stage: "initcontainer"
tag-prefix: "init-"
with:
environment: ${{ needs.set-env.outputs.environment }}
annotate-release: true
docker-image-name: ${{ needs.set-env.outputs.image-name }}
docker-tag-prefix: ${{ matrix.tag-prefix }}
secrets:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
azure-acr-client-id: ${{ secrets.ACR_CLIENT_ID }}
azure-acr-name: ${{ secrets.ACR_NAME }}

deploy:
name: Deploy
needs: [ set-env, import ]
permissions:
id-token: write
uses: DFE-Digital/deploy-azure-container-apps-action/.github/workflows/[email protected]
with:
environment: ${{ needs.set-env.outputs.environment }}
docker-image-name: ${{ needs.set-env.outputs.image-name }}
annotate-release: true
secrets:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
azure-acr-name: ${{ secrets.ACR_NAME }}
azure-aca-client-id: ${{ secrets.ACA_CLIENT_ID }}
azure-aca-name: ${{ secrets.ACA_CONTAINERAPP_NAME }}
azure-aca-resource-group: ${{ secrets.ACA_RESOURCE_GROUP }}

run-automated-ui-tests:
name: Run automated UI tests
uses: ./.github/workflows/test-automated-ui.yml
needs: [ deploy-image, set-env ]
needs: [ deploy, set-env ]
if: ${{ needs.set-env.outputs.environment == 'development' }}
with:
environment: ${{ needs.set-env.outputs.environment }}
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ on:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
stage: [
"final",
"initcontainer"
]
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -23,4 +29,5 @@ jobs:
secrets: github_token=${{ secrets.GITHUB_TOKEN }}
cache-from: type=gha
cache-to: type=gha
target: ${{ matrix.stage }}
push: false
13 changes: 10 additions & 3 deletions .github/workflows/docker-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@ name: Scan Docker image

on:
push:
branches: main
branches: [ main ]

jobs:
scan:
runs-on: ubuntu-latest
strategy:
matrix:
stage: [
"final",
"initcontainer"
]
outputs:
image: ${{ steps.build.outputs.imageid }}
steps:
Expand All @@ -25,15 +31,16 @@ jobs:
load: true
cache-from: type=gha
cache-to: type=gha
target: ${{ matrix.stage }}
push: false

- name: Export docker image as tar
run: docker save -o ${{ github.ref_name }}.tar ${{ steps.build.outputs.imageid }}
run: docker save -o ${{ matrix.stage }}-${{ github.ref_name }}.tar ${{ steps.build.outputs.imageid }}

- name: Scan Docker image for CVEs
uses: aquasecurity/[email protected]
with:
input: ${{ github.ref_name }}.tar
input: ${{ matrix.stage }}-${{ github.ref_name }}.tar
format: 'sarif'
output: 'trivy-results.sarif'
limit-severities-for-sarif: true
Expand Down
20 changes: 12 additions & 8 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,20 @@ RUN ["dotnet", "build", "DfE.FindInformationAcademiesTrusts", "--no-restore", "-

RUN ["dotnet", "publish", "DfE.FindInformationAcademiesTrusts", "--no-build", "-o", "/app"]

### Install SQL tools to allow migrations to be run ###
FROM "mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION}-azurelinux3.0" AS base
RUN curl "https://packages.microsoft.com/config/rhel/9/prod.repo" | tee /etc/yum.repos.d/mssql-release.repo
ENV ACCEPT_EULA=Y
RUN ["tdnf", "update", "-y"]
RUN ["tdnf", "install", "-y", "mssql-tools18"]
RUN ["tdnf", "clean", "all"]
### Entity Framework: Migration Runner ###
FROM "mcr.microsoft.com/mssql-tools" AS initcontainer

# - Copy and configure sql migration script
WORKDIR /app
COPY ./DfE.FindInformationAcademiesTrusts.Data.FiatDb/Migrations/FiatDbMigrationScript.sql /app/sql/FiatDbMigrationScript.sql
RUN ["touch", "/app/sql/FiatDbMigrationScriptOutput.txt"]

# - Copy and configure docker entrypoint script
COPY ./docker/init-docker-entrypoint.sh /app/migratedb
RUN ["chmod", "+x", "/app/migratedb"]

### Build a runtime environment ###
FROM base AS runtime
FROM "mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION}-azurelinux3.0" AS final
WORKDIR /app
LABEL org.opencontainers.image.source="https://github.com/DFE-Digital/find-information-about-academies-and-trusts"

Expand Down
26 changes: 26 additions & 0 deletions docker/init-docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/bash

# exit on failures
set -e
set -o pipefail

# Apply migrations to FIAT db
ConnectionStrings__DefaultConnection=${ConnectionStrings__DefaultConnection:?}

declare -A mssqlconn

for keyvaluepair in $(echo "$ConnectionStrings__DefaultConnection" | sed "s/ //g; s/;/ /g")
do
IFS=" " read -r -a ARR <<< "${keyvaluepair//=/ }"
mssqlconn[${ARR[0]}]=${ARR[1]}
done

echo "Running FIAT database migrations ..."
until /opt/mssql-tools/bin/sqlcmd -S "${mssqlconn[Server]}" -U "${mssqlconn[UserId]}" -P "${mssqlconn[Password]}" -d "${mssqlconn[Database]}" -C -I -i /app/sql/FiatDbMigrationScript.sql -o /app/sql/FiatDbMigrationScriptOutput.txt
do
cat /app/sql/FiatDbMigrationScriptOutput.txt
echo "Retrying FIAT database migrations ..."
sleep 5
done

echo "Done!"
19 changes: 0 additions & 19 deletions docker/web-docker-entrypoint.sh
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,4 @@
set -e
set -o pipefail

# Apply migrations to FIAT db
ConnectionStrings__DefaultConnection=${ConnectionStrings__DefaultConnection:?}

declare -A mssqlconn

for keyvaluepair in $(echo "$ConnectionStrings__DefaultConnection" | sed "s/ //g; s/;/ /g")
do
IFS=" " read -r -a ARR <<< "${keyvaluepair//=/ }"
mssqlconn[${ARR[0]}]=${ARR[1]}
done

echo "Running FIAT database migrations ..."
until /opt/mssql-tools18/bin/sqlcmd -S "${mssqlconn[Server]}" -U "${mssqlconn[UserId]}" -P "${mssqlconn[Password]}" -d "${mssqlconn[Database]}" -C -I -i /app/sql/FiatDbMigrationScript.sql -o /app/sql/FiatDbMigrationScriptOutput.txt
do
cat /app/sql/FiatDbMigrationScriptOutput.txt
echo "Retrying FIAT database migrations ..."
sleep 5
done

exec "$@"
3 changes: 3 additions & 0 deletions terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ No resources.
| <a name="input_enable_dns_zone"></a> [enable\_dns\_zone](#input\_enable\_dns\_zone) | Conditionally create a DNS zone | `bool` | n/a | yes |
| <a name="input_enable_event_hub"></a> [enable\_event\_hub](#input\_enable\_event\_hub) | Send Azure Container App logs to an Event Hub sink | `bool` | `false` | no |
| <a name="input_enable_health_insights_api"></a> [enable\_health\_insights\_api](#input\_enable\_health\_insights\_api) | Deploys a Function App that exposes the last 3 HTTP Web Tests via an API endpoint. 'enable\_app\_insights\_integration' and 'enable\_monitoring' must be set to 'true'. | `bool` | `false` | no |
| <a name="input_enable_init_container"></a> [enable\_init\_container](#input\_enable\_init\_container) | Deploy an Init Container. Init containers run before the primary app container and are used to perform initialization tasks such as downloading data or preparing the environment | `bool` | `false` | no |
| <a name="input_enable_logstash_consumer"></a> [enable\_logstash\_consumer](#input\_enable\_logstash\_consumer) | Create an Event Hub consumer group for Logstash | `bool` | `false` | no |
| <a name="input_enable_monitoring"></a> [enable\_monitoring](#input\_enable\_monitoring) | Create App Insights monitoring groups for the container app | `bool` | n/a | yes |
| <a name="input_enable_mssql_database"></a> [enable\_mssql\_database](#input\_enable\_mssql\_database) | Set to true to create an Azure SQL server/database, with a private endpoint within the virtual network | `bool` | n/a | yes |
Expand All @@ -206,6 +207,8 @@ No resources.
| <a name="input_health_insights_api_cors_origins"></a> [health\_insights\_api\_cors\_origins](#input\_health\_insights\_api\_cors\_origins) | List of hostnames that are permitted to contact the Health insights API | `list(string)` | <pre>[<br/> "*"<br/>]</pre> | no |
| <a name="input_health_insights_api_ipv4_allow_list"></a> [health\_insights\_api\_ipv4\_allow\_list](#input\_health\_insights\_api\_ipv4\_allow\_list) | List of IPv4 addresses that are permitted to contact the Health insights API | `list(string)` | `[]` | no |
| <a name="input_image_name"></a> [image\_name](#input\_image\_name) | Image name | `string` | n/a | yes |
| <a name="input_init_container_command"></a> [init\_container\_command](#input\_init\_container\_command) | Container command for the Init Container | `list(any)` | `[]` | no |
| <a name="input_init_container_image"></a> [init\_container\_image](#input\_init\_container\_image) | Image name for the Init Container. Leave blank to use the same Container image from the primary app | `string` | `""` | no |
| <a name="input_key_vault_access_ipv4"></a> [key\_vault\_access\_ipv4](#input\_key\_vault\_access\_ipv4) | List of IPv4 Addresses that are permitted to access the Key Vault | `list(string)` | n/a | yes |
| <a name="input_monitor_email_receivers"></a> [monitor\_email\_receivers](#input\_monitor\_email\_receivers) | A list of email addresses that will receive alerts from App Insights | `list(string)` | n/a | yes |
| <a name="input_monitor_endpoint_healthcheck"></a> [monitor\_endpoint\_healthcheck](#input\_monitor\_endpoint\_healthcheck) | Specify a route that should be monitored for a 200 OK status | `string` | n/a | yes |
Expand Down
4 changes: 4 additions & 0 deletions terraform/container-apps-hosting.tf
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,8 @@ module "azure_container_apps_hosting" {
existing_logic_app_workflow = local.existing_logic_app_workflow
existing_network_watcher_name = local.existing_network_watcher_name
existing_network_watcher_resource_group_name = local.existing_network_watcher_resource_group_name

enable_init_container = local.enable_init_container
init_container_image = local.init_container_image
init_container_command = local.init_container_command
}
3 changes: 3 additions & 0 deletions terraform/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,7 @@ locals {
cdn_frontdoor_vdp_destination_hostname = var.cdn_frontdoor_vdp_destination_hostname
dns_alias_records = var.dns_alias_records
monitor_http_availability_fqdn = var.monitor_http_availability_fqdn
enable_init_container = var.enable_init_container
init_container_image = var.init_container_image
init_container_command = var.init_container_command
}
18 changes: 18 additions & 0 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,21 @@ variable "monitor_http_availability_fqdn" {
type = string
default = ""
}

variable "enable_init_container" {
description = "Deploy an Init Container. Init containers run before the primary app container and are used to perform initialization tasks such as downloading data or preparing the environment"
type = bool
default = false
}

variable "init_container_image" {
description = "Image name for the Init Container. Leave blank to use the same Container image from the primary app"
type = string
default = ""
}

variable "init_container_command" {
description = "Container command for the Init Container"
type = list(any)
default = []
}

0 comments on commit 08de881

Please sign in to comment.