Skip to content

Commit

Permalink
Merge pull request #68 from DFE-Digital/200086-build-api-deployment
Browse files Browse the repository at this point in the history
200086 build api deployment
  • Loading branch information
DrizzlyOwl authored Feb 13, 2025
1 parent f37cf0b commit a352a9b
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 26 deletions.
89 changes: 66 additions & 23 deletions .github/workflows/build-and-push-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@ on:
concurrency:
group: ${{ github.workflow }}-${{ github.event.inputs.environment }}

env:
IMAGE_NAME: complete-app

jobs:
set-env:
name: Determine environment
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
outputs:
environment: ${{ steps.var.outputs.environment }}
release: ${{ steps.var.outputs.release }}
image-name: ${{ steps.var.outputs.image-name }}
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -32,6 +36,7 @@ jobs:
RELEASE=${ENVIRONMENT,,}-`date +%Y-%m-%d`.${{ github.run_number }}
echo "environment=${ENVIRONMENT,,}" >> $GITHUB_OUTPUT
echo "release=${RELEASE}" >> $GITHUB_OUTPUT
echo "image-name=${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT
validate-packages:
runs-on: windows-latest
Expand All @@ -45,32 +50,70 @@ jobs:
with:
environment: ${{ needs.set-env.outputs.environment }}

deploy-image:
build-import-deploy:
name: Build, Import, Deploy
needs: [ set-env, validate-packages ]
runs-on: ubuntu-24.04
environment: ${{ needs.set-env.outputs.environment }}
permissions:
id-token: write
contents: read
packages: write
name: Deploy Container
needs: [ set-env, validate-packages ]
uses: DFE-Digital/deploy-azure-container-apps-action/.github/workflows/build-push-deploy.yml@v4.1.0
with:
docker-tag-prefix: dotnet-
docker-image-name: 'complete-app'
docker-build-file-name: './Dockerfile'
environment: ${{ needs.set-env.outputs.environment }}
annotate-release: false
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 }}
azure-aca-client-id: ${{ secrets.ACA_CLIENT_ID }}
azure-aca-name: ${{ secrets.ACA_CONTAINERAPP_NAME }}
azure-aca-resource-group: ${{ secrets.ACA_RESOURCE_GROUP }}
id-token: write
strategy:
matrix:
image: [
"web",
"api"
]
include:
- image: "web"
tag-prefix: "dotnet-"
aca-name-secret: "ACA_CONTAINERAPP_NAME"
- image: "api"
tag-prefix: "dotnet-api-"
aca-name-secret: "ACA_CONTAINERAPP_API_NAME"
steps:
- uses: DFE-Digital/deploy-azure-container-apps-action/.github/actions/build@v5.0.0
with:
build-file-name: ./Dockerfile.${{ matrix.image }}
build-args: CI=true
build-target: ${{ matrix.stage }}
image-name: ${{ needs.set-env.outputs.image-name }}
tag-prefix: ${{ matrix.tag-prefix }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- uses: DFE-Digital/deploy-azure-container-apps-action/.github/actions/import@v5.0.0
with:
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 }}
image-name: ${{ needs.set-env.outputs.image-name }}
tag-prefix: ${{ matrix.tag-prefix }}
github-token: ${{ secrets.GITHUB_TOKEN }}
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID || '' }}
AZURE_SUBSCRIPTION: ${{ secrets.AZURE_SUBSCRIPTION_ID || '' }}
AZURE_ACR_CLIENT_ID: ${{ secrets.ACR_CLIENT_ID || '' }}

- uses: DFE-Digital/deploy-azure-container-apps-action/.github/actions/deploy@v5.0.0
with:
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
azure-subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
azure-aca-client-id: ${{ secrets.ACA_CLIENT_ID }}
azure-aca-name: ${{ secrets[matrix.aca-name-secret] }}
azure-aca-resource-group: ${{ secrets.ACA_RESOURCE_GROUP }}
azure-acr-name: ${{ secrets.ACR_NAME }}
annotate-release: ${{ matrix.image == 'web' }}
image-name: ${{ needs.set-env.outputs.image-name }}
tag-prefix: ${{ matrix.tag-prefix }}
env:
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID || '' }}
AZURE_SUBSCRIPTION: ${{ secrets.AZURE_SUBSCRIPTION_ID || '' }}
AZURE_ACA_CLIENT_ID: ${{ secrets.ACA_CLIENT_ID || '' }}

create-tag:
name: Tag and release
needs: [ set-env, deploy-image ]
needs: [ set-env, build-import-deploy ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -104,7 +147,7 @@ jobs:
cypress-tests:
name: Run Cypress Tests
if: needs.set-env.outputs.environment == 'test' || needs.set-env.outputs.environment == 'development'
needs: [ deploy-image, set-env ]
needs: [ set-env, build-import-deploy ]
uses: ./.github/workflows/cypress.yml
with:
environment: ${{ needs.set-env.outputs.environment }}
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ on:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
image: [
"web",
"api"
]
steps:
- name: Checkout code
uses: actions/checkout@v4
Expand All @@ -24,7 +30,9 @@ jobs:
uses: docker/build-push-action@v6
id: build
with:
file: './Dockerfile.${{ matrix.image }}'
secrets: github_token=${{ secrets.GITHUB_TOKEN }}
build-args: CI=true
cache-from: type=gha
cache-to: type=gha
push: false
14 changes: 11 additions & 3 deletions .github/workflows/docker-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@ name: Docker

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

jobs:
scan:
name: Scan for CVEs
runs-on: ubuntu-latest
strategy:
matrix:
image: [
"web",
"api"
]
outputs:
image: ${{ steps.build.outputs.imageid }}
steps:
Expand All @@ -22,18 +28,20 @@ jobs:
id: build
with:
secrets: github_token=${{ secrets.GITHUB_TOKEN }}
file: './Dockerfile.${{ matrix.image }}'
load: true
build-args: CI=true
cache-from: type=gha
cache-to: type=gha
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.image }}-${{ github.ref_name }}.tar ${{ steps.build.outputs.imageid }}

- name: Scan Docker image for CVEs
uses: aquasecurity/trivy-action@0.24.0
with:
input: ${{ github.ref_name }}.tar
input: ${{ matrix.image }}-${{ github.ref_name }}.tar
format: 'sarif'
output: 'trivy-results.sarif'
limit-severities-for-sarif: true
Expand Down
30 changes: 30 additions & 0 deletions Dockerfile.api
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Set the major version of dotnet
ARG DOTNET_VERSION=8.0

# Build the app using the dotnet SDK
FROM "mcr.microsoft.com/dotnet/sdk:${DOTNET_VERSION}-azurelinux3.0" AS build
ARG CI
ENV CI=${CI}
WORKDIR /build
COPY ./Dfe.Complete.sln /build
COPY ./Directory.Build.props /build
COPY ./src/ /build/src
COPY ./script/docker-entrypoint.sh /app/docker-entrypoint.sh

# Mount GitHub Token as a Docker secret so that NuGet Feed can be accessed
RUN --mount=type=secret,id=github_token dotnet nuget add source --username USERNAME --password $(cat /run/secrets/github_token) --store-password-in-clear-text --name github "https://nuget.pkg.github.com/DFE-Digital/index.json"
RUN ["dotnet", "restore", "Dfe.Complete.sln"]
WORKDIR /build/src/Api/Dfe.Complete.Api/
RUN ["dotnet", "build", "--no-restore", "-c", "Release"]
RUN ["dotnet", "publish", "--no-build", "-o", "/app"]

# Build a runtime environment
FROM "mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION}-azurelinux3.0" AS final
WORKDIR /app
LABEL org.opencontainers.image.source="https://github.com/DFE-Digital/complete-api"
LABEL org.opencontainers.image.description="Complete Conversions Transfers and Changes - API"

COPY --from=build /app /app
RUN ["chmod", "+x", "./docker-entrypoint.sh"]

USER $APP_UID
1 change: 1 addition & 0 deletions Dockerfile → Dockerfile.web
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ RUN ["dotnet", "publish", "--no-build", "-o", "/app"]
FROM "mcr.microsoft.com/dotnet/aspnet:${DOTNET_VERSION}-azurelinux3.0" AS base
WORKDIR /app
LABEL org.opencontainers.image.source="https://github.com/DFE-Digital/complete-api"
LABEL org.opencontainers.image.description="Complete Conversions Transfers and Changes - App"

COPY --from=build /app /app
COPY --from=assets /app /app/wwwroot
Expand Down
1 change: 1 addition & 0 deletions terraform/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ No resources.
| <a name="input_container_port"></a> [container\_port](#input\_container\_port) | Container port | `number` | `8080` | no |
| <a name="input_container_scale_http_concurrency"></a> [container\_scale\_http\_concurrency](#input\_container\_scale\_http\_concurrency) | When the number of concurrent HTTP requests exceeds this value, then another replica is added. Replicas continue to add to the pool up to the max-replicas amount. | `number` | `10` | no |
| <a name="input_container_secret_environment_variables"></a> [container\_secret\_environment\_variables](#input\_container\_secret\_environment\_variables) | Container secret environment variables | `map(string)` | n/a | yes |
| <a name="input_custom_container_apps"></a> [custom\_container\_apps](#input\_custom\_container\_apps) | Custom container apps, by default deployed within the container app environment managed by this module. | <pre>map(object({<br/> container_app_environment_id = optional(string, "")<br/> resource_group_name = optional(string, "")<br/> revision_mode = optional(string, "Single")<br/> container_port = optional(number, 0)<br/> ingress = optional(object({<br/> external_enabled = optional(bool, true)<br/> target_port = optional(number, null)<br/> traffic_weight = object({<br/> percentage = optional(number, 100)<br/> })<br/> cdn_frontdoor_custom_domain = optional(string, "")<br/> cdn_frontdoor_origin_fqdn_override = optional(string, "")<br/> cdn_frontdoor_origin_host_header_override = optional(string, "")<br/> enable_cdn_frontdoor_health_probe = optional(bool, false)<br/> cdn_frontdoor_health_probe_protocol = optional(string, "")<br/> cdn_frontdoor_health_probe_interval = optional(number, 120)<br/> cdn_frontdoor_health_probe_request_type = optional(string, "")<br/> cdn_frontdoor_health_probe_path = optional(string, "")<br/> cdn_frontdoor_forwarding_protocol_override = optional(string, "")<br/> }), null)<br/> identity = optional(list(object({<br/> type = string<br/> identity_ids = list(string)<br/> })), [])<br/> secrets = optional(list(object({<br/> name = string<br/> value = string<br/> })), [])<br/> registry = optional(object({<br/> server = optional(string, "")<br/> username = optional(string, "")<br/> password_secret_name = optional(string, "")<br/> identity = optional(string, "")<br/> }), null),<br/> image = string<br/> cpu = number<br/> memory = number<br/> command = list(string)<br/> liveness_probes = optional(list(object({<br/> interval_seconds = number<br/> transport = string<br/> port = number<br/> path = optional(string, null)<br/> })), [])<br/> env = optional(list(object({<br/> name = string<br/> value = optional(string, null)<br/> secretRef = optional(string, null)<br/> })), [])<br/> min_replicas = number<br/> max_replicas = number<br/> }))</pre> | `{}` | no |
| <a name="input_dns_alias_records"></a> [dns\_alias\_records](#input\_dns\_alias\_records) | DNS ALIAS records to add to the DNS Zone | <pre>map(<br/> object({<br/> ttl : optional(number, 300),<br/> target_resource_id : string<br/> })<br/> )</pre> | `{}` | no |
| <a name="input_dns_mx_records"></a> [dns\_mx\_records](#input\_dns\_mx\_records) | DNS MX records to add to the DNS Zone | <pre>map(<br/> object({<br/> ttl : optional(number, 300),<br/> records : list(<br/> object({<br/> preference : number,<br/> exchange : string<br/> })<br/> )<br/> })<br/> )</pre> | `{}` | no |
| <a name="input_dns_ns_records"></a> [dns\_ns\_records](#input\_dns\_ns\_records) | DNS NS records to add to the DNS Zone | <pre>map(<br/> object({<br/> ttl : optional(number, 300),<br/> records : list(string)<br/> })<br/> )</pre> | n/a | yes |
Expand Down
1 change: 1 addition & 0 deletions terraform/container-apps-hosting.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ module "azure_container_apps_hosting" {
enable_health_insights_api = local.enable_health_insights_api
health_insights_api_cors_origins = local.health_insights_api_cors_origins
health_insights_api_ipv4_allow_list = local.health_insights_api_ipv4_allow_list
custom_container_apps = local.custom_container_apps

existing_container_app_environment = local.existing_container_app_environment
existing_virtual_network = local.existing_virtual_network
Expand Down
1 change: 1 addition & 0 deletions terraform/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ locals {
health_insights_api_ipv4_allow_list = var.health_insights_api_ipv4_allow_list
monitor_http_availability_verb = var.monitor_http_availability_verb
monitor_http_availability_fqdn = var.monitor_http_availability_fqdn
custom_container_apps = var.custom_container_apps
}
58 changes: 58 additions & 0 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,61 @@ variable "monitor_http_availability_fqdn" {
type = string
default = ""
}

variable "custom_container_apps" {
description = "Custom container apps, by default deployed within the container app environment managed by this module."
type = map(object({
container_app_environment_id = optional(string, "")
resource_group_name = optional(string, "")
revision_mode = optional(string, "Single")
container_port = optional(number, 0)
ingress = optional(object({
external_enabled = optional(bool, true)
target_port = optional(number, null)
traffic_weight = object({
percentage = optional(number, 100)
})
cdn_frontdoor_custom_domain = optional(string, "")
cdn_frontdoor_origin_fqdn_override = optional(string, "")
cdn_frontdoor_origin_host_header_override = optional(string, "")
enable_cdn_frontdoor_health_probe = optional(bool, false)
cdn_frontdoor_health_probe_protocol = optional(string, "")
cdn_frontdoor_health_probe_interval = optional(number, 120)
cdn_frontdoor_health_probe_request_type = optional(string, "")
cdn_frontdoor_health_probe_path = optional(string, "")
cdn_frontdoor_forwarding_protocol_override = optional(string, "")
}), null)
identity = optional(list(object({
type = string
identity_ids = list(string)
})), [])
secrets = optional(list(object({
name = string
value = string
})), [])
registry = optional(object({
server = optional(string, "")
username = optional(string, "")
password_secret_name = optional(string, "")
identity = optional(string, "")
}), null),
image = string
cpu = number
memory = number
command = list(string)
liveness_probes = optional(list(object({
interval_seconds = number
transport = string
port = number
path = optional(string, null)
})), [])
env = optional(list(object({
name = string
value = optional(string, null)
secretRef = optional(string, null)
})), [])
min_replicas = number
max_replicas = number
}))
default = {}
}

0 comments on commit a352a9b

Please sign in to comment.