Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 0 additions & 22 deletions .github/workflows/build.yml

This file was deleted.

107 changes: 107 additions & 0 deletions .github/workflows/callable-build-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
name: "Task: Build"
on:
workflow_call:
inputs:
override_sha:
description: 'Optionally force checkout of a specific sha'
default: ''
type: string
push:
description: 'To push or not to push'
required: true
type: boolean
outputs:
container_image_digest:
description: "The sha256 digest of the built container image"
value: ${{ jobs.docker.outputs.digest }}

permissions:
packages: write
contents: read
attestations: write
id-token: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
docker:
runs-on: ubuntu-24.04
outputs:
digest: ${{ steps.push.outputs.digest }}
steps:
- uses: actions/checkout@v4
if: ${{ inputs.override_sha != '' }}
with:
fetch-depth: 1
ref: ${{ inputs.override_sha }}

- uses: actions/checkout@v4
if: ${{ inputs.override_sha == '' }}
with:
fetch-depth: 1

- name: Configuration
id: config
run: |
REPOSITORY_OWNER=$(tr "[:upper:]" "[:lower:]" <<< "${{ github.repository_owner }}")
echo "REPOSITORY_OWNER=${REPOSITORY_OWNER}" >> "$GITHUB_OUTPUT"

REPOSITORY_NAME=$(tr "[:upper:]" "[:lower:]" <<< "${{ github.event.repository.name }}")
echo "REPOSITORY_NAME=${REPOSITORY_NAME}" >> "$GITHUB_OUTPUT"

TARGET_IMAGE="ghcr.io/${REPOSITORY_OWNER}/${REPOSITORY_NAME}"
echo "TARGET_IMAGE=${TARGET_IMAGE}" >> "$GITHUB_OUTPUT"

- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ steps.config.outputs.REPOSITORY_OWNER }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
context: workflow
images: ${{ steps.config.outputs.TARGET_IMAGE }}
tags: |-
${{ inputs.override_sha == '' && 'type=ref,event=branch' || '' }}
${{ inputs.override_sha == '' && 'type=ref,event=tag' || '' }}
${{ inputs.override_sha == '' && 'type=ref,event=pr' || '' }}
${{ inputs.override_sha == '' && '# skip raw sha' || format('type=raw,value=sha-{0}', inputs.override_sha) }}
labels: |-
${{ inputs.override_sha != '' && 'org.opencontainers.image.version=unknown' }}
${{ inputs.override_sha != '' && format('org.opencontainers.image.revision={0}', inputs.override_sha) }}
flavor: |
latest=false

- name: Build and push Docker image
id: push
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: ${{ inputs.push }}
platforms: "linux/amd64"
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

- name: Generate artifact attestation
uses: actions/attest-build-provenance@v2
if: ${{ inputs.push }}
with:
subject-name: ${{ steps.config.outputs.TARGET_IMAGE }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: ${{ inputs.push }}

- name: Diff containers
run: |
wget https://github.com/reproducible-containers/diffoci/releases/download/v0.1.7/diffoci-v0.1.7.linux-amd64 -O diffoci
chmod +x diffoci
./diffoci diff --semantic ghcr.io/bracketsoftware/lamp-server:pr-3 ghcr.io/bidmcdigitalpsychiatry/lamp-server:2023.7.27
98 changes: 98 additions & 0 deletions .github/workflows/callable-deploy-ecs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: "Task: Deploy"
on:
workflow_call:
inputs:
env:
description: Target environment of deployment. (dev, stg, prod)
required: true
type: string
container_digest:
description: 'The container sha256 digest to deploy'
required: true
type: string

permissions:
contents: 'read'
id-token: 'write'

concurrency:
group: ${{ inputs.env }}
cancel-in-progress: false

jobs:
deploy:
runs-on: ubuntu-24.04
environment: ${{ inputs.env }}
steps:
- uses: actions/checkout@v4

- name: Configuration
id: config
run: |
REPOSITORY_OWNER=$(tr "[:upper:]" "[:lower:]" <<< "${{ github.repository_owner }}")
echo "REPOSITORY_OWNER=${REPOSITORY_OWNER}" >> "$GITHUB_OUTPUT"

REPOSITORY_NAME=$(tr "[:upper:]" "[:lower:]" <<< "${{ github.event.repository.name }}")
echo "REPOSITORY_NAME=${REPOSITORY_NAME}" >> "$GITHUB_OUTPUT"

TARGET_IMAGE="ghcr.io/${REPOSITORY_OWNER}/${REPOSITORY_NAME}"
echo "TARGET_IMAGE=${TARGET_IMAGE}" >> "$GITHUB_OUTPUT"

TARGET_IMAGE_W_DIGEST="${TARGET_IMAGE}@${{ inputs.container_digest }}"
echo "TARGET_IMAGE_W_DIGEST=${TARGET_IMAGE_W_DIGEST}" >> "$GITHUB_OUTPUT"

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.IAM_ROLE_ARN }}
aws-region: us-east-2

- name: Download task definition
run: |
aws ecs describe-task-definition \
--task-definition ${{ vars.ECS_TASK_DEF_FAMILY }} \
--query taskDefinition \
> task.json

# Remove Ignored Properties

echo "$( jq 'del(.compatibilities)' task.json )" > task.json
echo "$( jq 'del(.taskDefinitionArn)' task.json )" > task.json
echo "$( jq 'del(.requiresAttributes)' task.json )" > task.json
echo "$( jq 'del(.revision)' task.json )" > task.json
echo "$( jq 'del(.status)' task.json )" > task.json
echo "$( jq 'del(.registeredAt)' task.json )" > task.json
echo "$( jq 'del(.registeredBy)' task.json )" > task.json

# Update Image
echo "$( jq --arg image "${{ steps.config.outputs.TARGET_IMAGE_W_DIGEST }}" '.containerDefinitions |= map((select(.name == "server") | .image) |= $image)' task.json )" > task.json
cat task.json

- name: Deploy Amazon ECS task definition
id: ecs-deploy
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: task.json
service: ${{ vars.ECS_SERVICE }}
cluster: ${{ vars.ECS_CLUSTER }}
wait-for-service-stability: true
propagate-tags: SERVICE

- name: Verify deploy
id: check-deployment
run: |
TASK_DEF_EXPECTED=${{ steps.ecs-deploy.outputs.task-definition-arn }}
TASK_DEF_CURRENT=$(
aws ecs describe-services \
--cluster ${{ vars.ECS_CLUSTER }} \
--services ${{ vars.ECS_SERVICE }} \
--query services[0].deployments[0].taskDefinition \
| jq -r "."
)
echo "Task Arn - Current: $TASK_DEF_CURRENT"
echo "Task Arn - Expected: $TASK_DEF_EXPECTED"
if [ "$TASK_DEF_CURRENT" != "$TASK_DEF_EXPECTED" ]; then
echo "Current task arn does not match the expected task arn."
echo "The deployment may have been rolled back or been deposed by a more recent deployment attempt"
exit 1
fi
90 changes: 90 additions & 0 deletions .github/workflows/manual-deploy-sha.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: 'Manual Deploy: Git Sha'
on:
workflow_dispatch:
inputs:
sha:
type: string
required: true
description: Git sha (long format) to build and deploy
env:
type: environment
default: dev

concurrency:
group: ${{ github.workflow }}-${{ inputs.sha }}
cancel-in-progress: false

jobs:
# This job builds a new container and deploys it to the target environment. It
# is unsafe to deploy such arbitrary builds directly to production without
# first trialing them in lower environments.

validate:
name: "Validate Inputs"
runs-on: ubuntu-24.04
steps:
- name: Verify not production
if: ${{ inputs.env == 'prod' }}
run: |
cat << EOF
#---------------------------------------------------------------------------------
# ERROR: Cannot deploy arbitrary sha to production
#---------------------------------------------------------------------------------
#
# DETAILS:
#
# This job builds a new container and deploys it to the target environment. It
# is unsafe to deploy such arbitrary builds directly to production without
# first trialing them in lower environments.
#
# WORKAROUND:
#
# If you truely must release a specific sha,
#
# 1) Use this workflow to build and deploy the sha to staging
# 2) Deploy the assigned tag from that deployment using the
# "Manual Deploy: Docker Tag" workflow
# Note: You must cite a DOCKER tag
# Which should look like "sha-9e14d6f3da3c3c3f7ea73b74dec8c931365745e4"
#
#---------------------------------------------------------------------------------
EOF
exit 1
- name: Verify sha is hex
run: |
if [[ ! "${{ inputs.sha }}" =~ ^[0-9A-Fa-f]+$ ]]; then
echo "sha must be hexidecimal"; exit 1
fi

length=$(expr length "${{ inputs.sha }}")
if [ "$length" != "40"]; then
echo "sha must be all 40 characters"; exit 1
fi

- uses: actions/checkout@v4
with:
ref: ${{ inputs.sha }}

- name: Verify commit exists
run: |
git cat-file commit ${{ inputs.sha }}

build-docker:
name: "Build"
uses: ./.github/workflows/callable-build-docker.yml
secrets: inherit
with:
override_sha: ${{ inputs.sha }}
push: true
needs:
- validate

deploy-ecs:
name: "Deploy container to ECS"
uses: ./.github/workflows/callable-deploy-ecs.yml
secrets: inherit
with:
env: ${{ inputs.env }}
container_digest: ${{ needs.build-docker.outputs.container_image_digest }}
needs:
- build-docker
Loading
Loading