Skip to content
Merged
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
63 changes: 9 additions & 54 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# Checks this repo: lints every workflow and template, and runs the shared
# workflows against the small projects in tests/. The deploy, release and
# Terraform workflows need AWS, so only actionlint checks them.
# Checks this repo: lints every workflow and template, and builds a small
# image with the shared image workflow. The deploy and release workflows need
# AWS, so only actionlint checks them.
name: CI

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
push:
branches: [main]

Expand All @@ -18,61 +19,15 @@ permissions:
jobs:
actionlint:
name: actionlint
if: github.event.pull_request.draft != true
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checking out the code
uses: actions/checkout@v7

- name: Linting the workflows and templates
run: |
bash <(curl -sSfL https://raw.githubusercontent.com/rhysd/actionlint/v1.7.12/scripts/download-actionlint.bash) 1.7.12
./actionlint -color .github/workflows/*.yml

# Lints the templates as a repo holds them: in .github/workflows,
# with the Deploy template saved as deploy.yml, and calling the
# shared workflows of this commit, so a wrong input name fails here.
dir="$RUNNER_TEMP/templates"
mkdir -p "$dir/.github/workflows"
cp .github/workflows/{go,python,image,deploy-ecs,release,terraform}.yml "$dir/.github/workflows/"
for f in workflow-templates/*.yml; do
sed 's#probe-lab/.github/.github/workflows/\(.*\)\.yml@v1#./.github/workflows/\1.yml#' "$f" \
> "$dir/.github/workflows/template-$(basename "$f")"
done
cp "$dir/.github/workflows/template-service-deploy.yml" "$dir/.github/workflows/deploy.yml"
git -C "$dir" init -q
cd "$dir" && "$GITHUB_WORKSPACE/actionlint" -color .github/workflows/template-*.yml .github/workflows/deploy.yml

go:
uses: ./.github/workflows/go.yml
with:
runner: ubuntu-latest
working-directory: tests/go
clickhouse: true
golangci-lint: v2.15
lint-command: just lint && golangci-lint run ./...

python:
uses: ./.github/workflows/python.yml
with:
runner: ubuntu-latest
working-directory: tests/python
clickhouse: true
- name: Setting up just
uses: extractions/setup-just@v4

image:
uses: ./.github/workflows/image.yml
with:
runner: ubuntu-latest
service: fixture
context: tests/go
dockerfile: tests/go/Dockerfile

ci-ok:
name: ci-ok
if: always()
needs: [actionlint, go, python, image]
runs-on: ubuntu-latest
steps:
- name: Checking that every job passed
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
run: exit 1
- name: Linting the workflows and templates
run: just lint
117 changes: 35 additions & 82 deletions .github/workflows/deploy-ecs.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Deploys the image of one commit to one ECS service. A person starts it with
# a repo's Deploy workflow. A merge deploys only when its pull request has
# the label that pr-label names. See the README for an example call.
# Deploys the image of one commit to one ECS service. Two events start it in
# the caller: a person pressing the Deploy button (workflow_dispatch), and the
# CI run of a merge (push), where the deploy is the last job, after the image
# job. The button deploys without a check. A merge deploys only when its
# pull request has the label deploy. See the README for an example call.
#
# It deploys only a commit on the default branch whose image
# <service>-sha<commit> is in ECR. It never builds an image: while CI still
# builds it, the deploy waits up to image-wait-minutes. The caller grants
# contents: read and id-token: write, and pull-requests: read for pr-label.
# <service>-sha<commit> is in ECR. It never builds an image and never waits
# for one. The caller grants contents: read, id-token: write and
# pull-requests: read.
name: Deploy to ECS

on:
Expand All @@ -19,12 +21,6 @@ on:
description: The commit to deploy. Empty means the newest on the default branch.
type: string
default: ""
pr-label:
description: >-
Deploys only if the merged pull request of the commit has this
label, for example deploy. Empty means deploy without a check.
type: string
default: ""
cluster:
description: The ECS cluster, for example prod-use1-web-ecs-cluster.
type: string
Expand Down Expand Up @@ -53,35 +49,6 @@ on:
tasks that something else starts.
type: boolean
default: false
environment:
type: string
default: production
wait-minutes:
description: How long to wait for the service to be stable.
type: number
default: 15
image-wait-minutes:
description: >-
How long to wait for the image of the commit while CI still builds
it. 0 means no wait.
type: number
default: 10
smoke-url:
description: A URL that must answer with a 2xx status after the deploy. Empty means no smoke check.
type: string
default: ""
aws-region:
type: string
default: us-east-1
aws-role:
type: string
default: arn:aws:iam::019120760881:role/prod-use1-github-oidc-role
ecr-registry:
type: string
default: 019120760881.dkr.ecr.us-east-1.amazonaws.com
ecr-repository:
type: string
default: probelab
outputs:
commit:
description: The full commit that was deployed.
Expand All @@ -93,12 +60,19 @@ on:
description: The new task definition.
value: ${{ jobs.deploy.outputs.task-definition-arn }}

env:
AWS_REGION: us-east-1
AWS_ROLE: arn:aws:iam::019120760881:role/prod-use1-github-oidc-role
ECR_REGISTRY: 019120760881.dkr.ecr.us-east-1.amazonaws.com
ECR_REPOSITORY: probelab
SERVICE_WAIT_MINUTES: 15

jobs:
# A separate job, so that a merge without the label creates no deployment
# in GitHub.
# in GitHub. The button skips it.
label:
name: Check the pull request label
if: inputs.pr-label != ''
if: github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
Expand All @@ -109,8 +83,8 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
SHA: ${{ inputs.commit || github.sha }}
LABEL: ${{ inputs.pr-label }}
SHA: ${{ github.sha }}
LABEL: deploy
run: |
set -euo pipefail
prs=$(gh api "repos/$REPO/commits/$SHA/pulls" | jq -r --arg label "$LABEL" \
Expand All @@ -131,7 +105,7 @@ jobs:
# Deploys hold prod credentials, so they never run on a self-hosted runner.
runs-on: ubuntu-latest
timeout-minutes: 45
environment: ${{ inputs.environment }}
environment: production
# One rollout at a time for each service. A newer deploy waits, and an
# older deploy that still waits is dropped.
concurrency:
Expand All @@ -148,7 +122,7 @@ jobs:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
DEFAULT_BRANCH: ${{ github.event.repository.default_branch || 'main' }}
COMMIT: ${{ inputs.commit }}
COMMIT: ${{ inputs.commit || (github.event_name == 'push' && github.sha) || '' }}
run: |
set -euo pipefail
sha=$(gh api "repos/$REPO/commits/${COMMIT:-$DEFAULT_BRANCH}" --jq .sha)
Expand All @@ -164,34 +138,29 @@ jobs:
- name: Configuring AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-region: ${{ inputs.aws-region }}
role-to-assume: ${{ inputs.aws-role }}
aws-region: ${{ env.AWS_REGION }}
role-to-assume: ${{ env.AWS_ROLE }}

# CI pushes the image after the tests pass. A deploy started right
# after a merge waits for it.
# The image job of the same CI run pushed it before this job started. A
# person who presses the button on a commit without an image gets the
# error at once.
- name: Finding the image
id: image
env:
REPOSITORY: ${{ inputs.ecr-repository }}
IMAGE: ${{ inputs.ecr-registry }}/${{ inputs.ecr-repository }}
REPOSITORY: ${{ env.ECR_REPOSITORY }}
IMAGE: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}
SERVICE: ${{ inputs.service }}
SHA: ${{ steps.commit.outputs.sha }}
WAIT_MINUTES: ${{ inputs.image-wait-minutes }}
run: |
tag="$SERVICE-sha${SHA::7}"
deadline=$((SECONDS + WAIT_MINUTES * 60))
until aws ecr describe-images --repository-name "$REPOSITORY" --image-ids imageTag="$tag" > /dev/null 2> ecr-error.txt; do
if ! grep -q ImageNotFoundException ecr-error.txt; then
if ! aws ecr describe-images --repository-name "$REPOSITORY" --image-ids imageTag="$tag" > /dev/null 2> ecr-error.txt; then
if grep -q ImageNotFoundException ecr-error.txt; then
echo "::error::There is no image $tag. Check that the CI run of this commit on the default branch passed."
else
echo "::error::$(cat ecr-error.txt)"
exit 1
fi
if (( SECONDS >= deadline )); then
echo "::error::There is no image $tag after $WAIT_MINUTES minutes. Check that the CI run of this commit on the default branch passed."
exit 1
fi
echo "$tag is not in ECR yet. CI may still be running. Checking again in 20 s."
sleep 20
done
exit 1
fi
echo "image=$IMAGE:$tag" >> "$GITHUB_OUTPUT"

# Takes the newest task definition, so a change that Terraform made to
Expand Down Expand Up @@ -230,7 +199,7 @@ jobs:
cluster: ${{ inputs.cluster }}
service: ${{ inputs.ecs-service }}
wait-for-service-stability: true
wait-for-minutes: ${{ inputs.wait-minutes }}
wait-for-minutes: ${{ env.SERVICE_WAIT_MINUTES }}
propagate-tags: SERVICE
enable-ecs-managed-tags: true

Expand All @@ -241,19 +210,3 @@ jobs:
with:
task-definition: ${{ steps.render.outputs.task-definition }}
cluster: ${{ inputs.cluster }}

- name: Checking that the service answers
if: inputs.smoke-url != '' && !inputs.task-definition-only
env:
URL: ${{ inputs.smoke-url }}
run: |
for attempt in $(seq 1 10); do
if curl -fsS -o /dev/null -m 10 "$URL"; then
echo "$URL answered."
exit 0
fi
echo "Attempt $attempt: $URL did not answer. Trying again in 6 s."
sleep 6
done
echo "::error::$URL did not answer after the deploy. Roll back with the Deploy workflow and an older commit."
exit 1
Loading
Loading