Skip to content

Commit a1cdd64

Browse files
committed
feat: publish tagged images
1 parent da5ac25 commit a1cdd64

File tree

5 files changed

+133
-22
lines changed

5 files changed

+133
-22
lines changed

.github/workflows/build.yml

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ on:
44
push:
55
branches:
66
- main
7+
tags:
8+
- 'v*'
79
pull_request:
810
branches:
911
- main
@@ -36,23 +38,20 @@ jobs:
3638
registry: ${{ env.REGISTRY }}
3739
username: ${{ github.actor }}
3840
password: ${{ secrets.GHCR_PAT || secrets.GITHUB_TOKEN }}
39-
40-
- name: Generate Docker tags
41+
- name: Extract Docker metadata
4142
id: meta
42-
run: |
43-
IMAGE="${{ env.REGISTRY }}/${{ steps.image.outputs.name }}"
44-
SHA_SHORT=$(echo ${{ github.sha }} | cut -c1-7)
45-
46-
if [ "${{ github.event_name }}" = "pull_request" ]; then
47-
# PR builds: tag as pr-<number>
48-
TAGS="${IMAGE}:pr-${{ github.event.pull_request.number }},${IMAGE}:sha-${SHA_SHORT}"
49-
else
50-
# Push to main: tag as both 'main' and 'latest'
51-
TAGS="${IMAGE}:main,${IMAGE}:latest,${IMAGE}:sha-${SHA_SHORT}"
52-
fi
53-
54-
echo "tags=${TAGS}" >> $GITHUB_OUTPUT
55-
echo "primary_tag=$(echo ${TAGS} | cut -d',' -f1 | cut -d':' -f2)" >> $GITHUB_OUTPUT
43+
uses: docker/metadata-action@v5
44+
with:
45+
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.name }}
46+
tags: |
47+
type=ref,event=branch
48+
type=ref,event=tag
49+
type=ref,event=pr
50+
type=semver,pattern={{version}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
51+
type=semver,pattern={{major}}.{{minor}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
52+
type=semver,pattern={{major}},enable=${{ startsWith(github.ref, 'refs/tags/v') }}
53+
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}
54+
type=sha,format=short
5655
5756
- name: Build and push Docker image
5857
uses: docker/build-push-action@v5
@@ -62,11 +61,17 @@ jobs:
6261
platforms: linux/amd64
6362
push: true
6463
tags: ${{ steps.meta.outputs.tags }}
64+
labels: ${{ steps.meta.outputs.labels }}
6565
cache-from: type=gha
6666
cache-to: type=gha,mode=max
6767

6868
- name: Image summary
6969
run: |
70-
echo "### Docker Image Built 🐳" >> $GITHUB_STEP_SUMMARY
71-
echo "" >> $GITHUB_STEP_SUMMARY
72-
echo "**Tags:** ${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
70+
{
71+
echo "### Docker Image Built 🐳"
72+
echo
73+
echo "**Tags:**"
74+
while IFS= read -r tag; do
75+
printf -- "- %s\n" "$tag"
76+
done <<< "${{ steps.meta.outputs.tags }}"
77+
} >> "$GITHUB_STEP_SUMMARY"

Makefile

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
.PHONY: help setup lint format typecheck pre-commit build push clean
1+
.PHONY: help setup lint format typecheck pre-commit build push clean release
22

33
IMAGE_NAME := ghcr.io/eopf-explorer/data-pipeline
4-
TAG := v0
4+
TAG ?= v0
5+
MESSAGE ?=
6+
FLAGS ?=
57

68
help: ## Show this help message
79
@echo "🚀 EOPF GeoZarr Data Pipeline (Slim Branch)"
@@ -43,6 +45,10 @@ push: ## Push Docker image to registry
4345
docker push $(IMAGE_NAME):$(TAG)
4446
docker push $(IMAGE_NAME):latest
4547

48+
release: ## Cut and push a git tag (set TAG=vX[.Y[.Z]], optional MESSAGE="...", FLAGS="--force")
49+
@if [ "$(origin TAG)" = "default" ]; then echo "TAG is required, e.g. make release TAG=v0"; exit 1; fi
50+
@if [ -n "$(MESSAGE)" ]; then scripts/tag_release.sh $(FLAGS) "$(TAG)" "$(MESSAGE)"; else scripts/tag_release.sh $(FLAGS) "$(TAG)"; fi
51+
4652
clean: ## Clean generated files and caches
4753
@echo "Cleaning generated files..."
4854
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true

scripts/tag_release.sh

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
usage() {
5+
cat <<'EOF' >&2
6+
Usage: tag_release.sh [-f|--force] <tag> [message]
7+
8+
<tag> Annotated tag to create (semantic version, e.g. v1.2.0)
9+
[message] Optional tag message (defaults to "Release <tag>")
10+
11+
Options:
12+
-f, --force Move an existing tag locally and on origin (force-with-lease)
13+
-h, --help Show this help and exit
14+
EOF
15+
}
16+
17+
die() {
18+
printf 'Error: %s\n' "$1" >&2
19+
exit 1
20+
}
21+
22+
FORCE=0
23+
TAG=""
24+
25+
while [[ $# -gt 0 ]]; do
26+
case "$1" in
27+
-f|--force)
28+
FORCE=1
29+
shift
30+
;;
31+
-h|--help)
32+
usage
33+
exit 0
34+
;;
35+
--)
36+
shift
37+
break
38+
;;
39+
-*)
40+
die "unknown option: $1"
41+
;;
42+
*)
43+
TAG="$1"
44+
shift
45+
break
46+
;;
47+
esac
48+
done
49+
50+
[[ -n "$TAG" ]] || { usage; exit 1; }
51+
52+
MESSAGE=${*:-"Release ${TAG}"}
53+
54+
[[ "$TAG" =~ ^v[0-9]+(\.[0-9]+)*$ ]] || die "tag must follow semantic style (e.g. v1.2.0)."
55+
56+
if [[ -n $(git status --porcelain) ]]; then
57+
die "working tree has uncommitted changes."
58+
fi
59+
60+
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
61+
[[ "$CURRENT_BRANCH" == "main" ]] || die "releases must be created from main (current: $CURRENT_BRANCH)."
62+
63+
REMOTE=origin
64+
git config --get remote."$REMOTE".url >/dev/null 2>&1 || die "remote '$REMOTE' is not configured."
65+
66+
git fetch "$REMOTE" main --tags --quiet >/dev/null 2>&1 || die "failed to fetch $REMOTE/main."
67+
[[ $(git rev-parse HEAD) == $(git rev-parse "$REMOTE/main") ]] || die "local main is out of sync with $REMOTE/main."
68+
69+
if git rev-parse "$TAG" >/dev/null 2>&1 && [[ $FORCE -eq 0 ]]; then
70+
die "tag '$TAG' already exists locally. Use --force to move it."
71+
fi
72+
73+
if git ls-remote --tags "$REMOTE" "$TAG" | grep -q "refs/tags/$TAG$" && [[ $FORCE -eq 0 ]]; then
74+
die "tag '$TAG' already exists on $REMOTE. Use --force to replace it."
75+
fi
76+
77+
if [[ $FORCE -eq 1 ]]; then
78+
git tag -fa "$TAG" -m "$MESSAGE"
79+
git push "$REMOTE" "$TAG" --force-with-lease
80+
echo "Moved tag '$TAG' to $(git rev-parse --short HEAD) and pushed with force-with-lease."
81+
else
82+
git tag -a "$TAG" -m "$MESSAGE"
83+
git push "$REMOTE" "$TAG"
84+
echo "Created tag '$TAG' at $(git rev-parse --short HEAD) and pushed to $REMOTE."
85+
fi

workflows/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,21 @@ geozarr-jflnj Failed 10h
9999

100100
## Configuration
101101

102+
### Docker Image Versions
103+
104+
CI now publishes images to `ghcr.io/eopf-explorer/data-pipeline` with the following tags:
105+
106+
- `main`, `latest`, and `sha-<short>` whenever a change lands on `main`.
107+
- `vX.Y.Z`, `vX.Y`, `vX`, and `sha-<short>` from annotated semantic tags (for example `v1.2.0`).
108+
- `pr-<number>` and `sha-<short>` for every pull-request build.
109+
110+
**Releasing a new tag**
111+
1. Merge to `main`, then run `make release TAG=v1.2.0 [MESSAGE="Release v1.2.0"]` from a clean repo. The helper ensures `main` is clean and in sync before tagging.
112+
2. Need to reuse an existing tag? Add `FLAGS="--force"`.
113+
3. After GitHub Actions completes, production keeps running the new `latest` image. To pin a specific tag:
114+
- **Staging first (preferred):** set `pipeline_image_version` in `workflows/overlays/staging/kustomization.yaml` to the tag (for example `v1.2.0`) and run `kubectl apply -k workflows/overlays/staging`. If it passes validation, make the same edit in `workflows/overlays/production/kustomization.yaml` and apply it.
115+
- **Ship now:** set the tag directly in `workflows/overlays/production/kustomization.yaml` and apply. Staging already tracks the latest `main`; bump it too if you want staging and production on the same image.
116+
102117
### S3 Storage
103118

104119
- **Endpoint**: `https://s3.de.io.cloud.ovh.net` (OVH Frankfurt)

workflows/base/workflowtemplate.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ spec:
3131
- name: s3_output_prefix
3232
value: tests-output
3333
- name: pipeline_image_version
34-
value: feature-align-data-model
34+
value: latest
3535
# Optional conversion parameter overrides (empty = use collection defaults)
3636
- name: override_groups
3737
value: ""

0 commit comments

Comments
 (0)