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
68 changes: 48 additions & 20 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,33 @@ jobs:
- name: Test
run: make test

VersionCheck:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# The check compares against the base branch or the previous tag.
fetch-depth: 0

# Gated on a pull request and on a tag, the two events that can publish
# something carrying a version. Pushes to master and to feature branches
# fall through and the job passes.
#
# The condition sits on the step rather than the job: a skipped job also
# skips everything that needs it, which would take the whole build with it.
- name: Check chart and app versions
if: github.event_name == 'pull_request' || startsWith(github.ref, 'refs/tags/')
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
./scripts/check-version.sh pr "origin/${{ github.base_ref }}"
else
./scripts/check-version.sh tag "${{ github.ref_name }}"
fi

Build:
needs: Test
needs: [Test, VersionCheck]
runs-on: ubuntu-latest
permissions:
contents: read
Expand All @@ -47,6 +72,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# The Makefile derives the version from `git describe`, which needs
# the tags and the history behind them.
fetch-depth: 0

- name: Set up QEMU
uses: docker/setup-qemu-action@v3
Expand All @@ -64,28 +93,27 @@ jobs:

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
#
# master -> :latest
# v0.5.0 -> :0.5.0, :0.5 and :latest (the default `latest=auto` flavor
# moves `latest` onto every semver tag)
# PR -> :pr-N, built but not pushed
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=ref,event=pr
type=raw,value=latest,enable={{is_default_branch}}

- uses: actions/github-script@v6
id: tags
with:
script: |
tags = `${{ join(steps.meta.outputs.tags, ',') }}`
result = []
tags.split("\n").forEach(function (item) {
short_tag = item.trim().split(":")[1];
if (short_tag != "latest") {
result.push(short_tag);
}
})
return result.join("-");
result-encoding: string
- name: Get result
run: echo "${{steps.set-result.outputs.result}}"
# The build context has no .git, so `git describe` has to run here and be
# handed to the image as a build argument.
- name: Resolve the binary version
id: version
run: echo "version=$(git describe --tags --always --dirty)" >> "$GITHUB_OUTPUT"

# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
Expand All @@ -95,10 +123,10 @@ jobs:
context: .
file: Dockerfile
platforms: linux/amd64,linux/arm64
build-args: |
VERSION=${{ steps.version.outputs.version }}
push: ${{ github.event_name != 'pull_request' || github.event.inputs.push == 'true' }}
tags: |
${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest
${{ secrets.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:latest-${{ steps.tags.outputs.result }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

# - name: Docker Hub Description
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ jobs:
with:
go-version-file: go.mod

# Before any artifact is produced: the tag has to agree with the chart.
- name: Check chart and app versions
run: ./scripts/check-version.sh tag "${{ github.ref_name }}"

- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
Expand Down
5 changes: 0 additions & 5 deletions .gitpod.yml

This file was deleted.

8 changes: 3 additions & 5 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,9 @@ checksum:
name_template: "checksums.txt"

changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
# Let GitHub generate the notes from the merged pull requests, the same list
# the release page offers, instead of a flat commit dump.
use: github-native

release:
github:
Expand Down
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ FROM docker.io/golang:1.26 AS builder
RUN apt-get update && apt-get install -y make && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY go.mod main.go Makefile ./
RUN make build
# The build context has no .git, so the Makefile's git describe finds nothing;
# the version has to be passed in.
ARG VERSION=dev
RUN make build VERSION=$VERSION

FROM alpine:3.23
RUN apk --no-cache add ca-certificates
Expand Down
37 changes: 30 additions & 7 deletions build-multiarch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,38 @@ buildah login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD $DOCKER_RE

podman run --rm --events-backend=file --cgroup-manager=cgroupfs --privileged docker://multiarch/qemu-user-static --reset -p yes

VERSION="latest"
IMAGE="byjg/static-httpserver"

# The version stamped into the binary. The build context has no .git, so the
# Makefile's git describe finds nothing and it has to be passed in, the same way
# the GitHub workflow does it.
VERSION=$(git describe --tags --always --dirty 2>/dev/null || echo dev)

# Image tags follow the same scheme as the workflow: sitting on a release tag
# publishes :X.Y.Z, :X.Y and :latest, anything else publishes :latest alone.
RELEASE_TAG=$(git describe --exact-match --tags 2>/dev/null || true)
if [ -n "$RELEASE_TAG" ]; then
SEMVER=${RELEASE_TAG#v}
TAGS=("$SEMVER" "${SEMVER%.*}" "latest")
else
TAGS=("latest")
fi

echo "Building $IMAGE version $VERSION, tags: ${TAGS[*]}"

MANIFEST="$IMAGE:${TAGS[0]}"

buildah manifest create byjg/static-httpserver:$VERSION
# A manifest left behind by an earlier run makes create fail.
buildah manifest rm "$MANIFEST" 2>/dev/null || true
buildah manifest create "$MANIFEST"

buildah bud --arch arm64 --os linux --iidfile /tmp/iid-arm64 -f Dockerfile -t byjg/static-httpserver:$VERSION-arm64 .
buildah bud --arch amd64 --os linux --iidfile /tmp/iid-amd64 -f Dockerfile -t byjg/static-httpserver:$VERSION-amd64 .
buildah bud --arch arm64 --os linux --build-arg VERSION="$VERSION" --iidfile /tmp/iid-arm64 -f Dockerfile -t "$IMAGE:${TAGS[0]}-arm64" .
buildah bud --arch amd64 --os linux --build-arg VERSION="$VERSION" --iidfile /tmp/iid-amd64 -f Dockerfile -t "$IMAGE:${TAGS[0]}-amd64" .

buildah manifest add byjg/static-httpserver:$VERSION --arch arm64 --os linux --variant v8 $(cat /tmp/iid-arm64)
buildah manifest add byjg/static-httpserver:$VERSION --arch amd64 --os linux --os=linux $(cat /tmp/iid-amd64)
buildah manifest add "$MANIFEST" --arch arm64 --os linux --variant v8 "$(cat /tmp/iid-arm64)"
buildah manifest add "$MANIFEST" --arch amd64 --os linux "$(cat /tmp/iid-amd64)"

buildah manifest push --all --format v2s2 byjg/static-httpserver:$VERSION docker://byjg/static-httpserver:$VERSION
for tag in "${TAGS[@]}"; do
buildah manifest push --all --format v2s2 "$MANIFEST" "docker://$IMAGE:$tag"
done

4 changes: 2 additions & 2 deletions helm/static-httpserver/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.3.0
version: 0.3.1

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "latest"
appVersion: "0.5.1"
148 changes: 148 additions & 0 deletions scripts/check-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/usr/bin/env bash
#
# Refuses to build anything publishable when a change was made without the
# matching version bump.
#
# Two failures this guards against:
# - a chart version that did not move, which makes the published .tgz
# overwrite the previous one and drop it from the repo index entirely;
# - an appVersion that disagrees with the release tag, which points the chart
# at an image tag that was never built.
#
set -euo pipefail

CHART_FILE="helm/static-httpserver/Chart.yaml"

# Everything that ends up inside the image. Touching it has to move appVersion.
APP_PATHS="main.go go.mod Makefile Dockerfile html/"
# The chart itself. Touching it has to move the chart version. Chart.yaml is
# excluded: bumping either version edits it, and that must not count as a chart
# change or an appVersion bump would demand a chart bump alongside it.
CHART_PATHS="helm/"

usage() {
cat <<'EOF'
Usage:
scripts/check-version.sh pr <base-ref> compare against the PR base branch
scripts/check-version.sh tag <tag-name> compare against the previous tag

Examples:
scripts/check-version.sh pr origin/master
scripts/check-version.sh tag v0.6.0
EOF
}

# field_at <git-ref, or empty for the working tree> <field name>
field_at() {
local ref="$1" field="$2" content
if [ -z "$ref" ]; then
content=$(cat "$CHART_FILE")
else
content=$(git show "$ref:$CHART_FILE")
fi
printf '%s\n' "$content" |
sed -nE "s/^${field}:[[:space:]]*\"?([^\"[:space:]]+)\"?.*/\1/p" |
head -1
}

# changed_under <newline separated file list> <space separated path prefixes>
changed_under() {
local files="$1" prefix
for prefix in $2; do
if printf '%s\n' "$files" | grep -qE "^${prefix}"; then
return 0
fi
done
return 1
}

STATUS=0
fail() {
echo "FAIL: $*" >&2
STATUS=1
}

MODE="${1:-}"
REF="${2:-}"
if [ -z "$MODE" ] || [ -z "$REF" ]; then
usage
exit 1
fi

case "$MODE" in
pr)
CHANGED=$(git diff --name-only "$REF...HEAD")
echo "Files changed against $REF:"
printf '%s\n' "$CHANGED" | sed 's/^/ /'
echo

CHART_CHANGES=$(printf '%s\n' "$CHANGED" | grep -E "^${CHART_PATHS}" | grep -vxF "$CHART_FILE" || true)
APP_CHANGED=no
changed_under "$CHANGED" "$APP_PATHS" && APP_CHANGED=yes

# An application change moves appVersion, appVersion lives in the chart, and
# the chart is republished on every merge to master. So it lands as a new
# .tgz and needs a chart version of its own, exactly as a template change
# does: whenever either side moves, the chart version moves with it.
if [ -n "$CHART_CHANGES" ] || [ "$APP_CHANGED" = yes ]; then
OLD=$(field_at "$REF" version)
NEW=$(field_at "" version)
if [ "$OLD" = "$NEW" ]; then
if [ -n "$CHART_CHANGES" ]; then
WHY="the chart changed"
else
WHY="the application changed, which republishes the chart"
fi
fail "$WHY, but the chart version is still $NEW. Bump 'version' in $CHART_FILE."
else
echo "OK: chart version $OLD -> $NEW"
fi
else
echo "SKIP: neither the chart nor the application changed, chart version not required to move"
fi

if [ "$APP_CHANGED" = yes ]; then
OLD=$(field_at "$REF" appVersion)
NEW=$(field_at "" appVersion)
if [ "$OLD" = "$NEW" ]; then
fail "the application changed but appVersion is still $NEW. Bump 'appVersion' in $CHART_FILE."
else
echo "OK: appVersion $OLD -> $NEW"
fi
else
echo "SKIP: application untouched, appVersion not required to move"
fi
;;

tag)
WANT="${REF#v}"

APP=$(field_at "" appVersion)
if [ "$APP" != "$WANT" ]; then
fail "tag $REF expects appVersion $WANT, but $CHART_FILE says $APP."
else
echo "OK: appVersion $APP matches tag $REF"
fi

# The tag is already checked out, so the previous tag is the one before it.
PREV=$(git describe --abbrev=0 --tags HEAD^ 2>/dev/null || true)
if [ -z "$PREV" ]; then
echo "SKIP: no previous tag to compare the chart version against"
else
OLD=$(field_at "$PREV" version)
NEW=$(field_at "" version)
if [ "$OLD" = "$NEW" ]; then
fail "chart version is still $NEW since $PREV. A published chart version is immutable: bump 'version' in $CHART_FILE or the new .tgz replaces the old one and $OLD disappears from the repo index."
else
echo "OK: chart version $OLD ($PREV) -> $NEW"
fi
fi
;;

*)
usage
exit 1
;;
esac

exit "$STATUS"
Loading