Skip to content

Commit 88b4246

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

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

workflows/README.md

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

100100
## Configuration
101101

102+
### Docker Image Versions
103+
104+
CI publishes images to `ghcr.io/eopf-explorer/data-pipeline` with these automatic tags:
105+
106+
- `main`, `latest`, and `sha-<short>` from merges to `main`.
107+
- `vX.Y.Z`, `vX.Y`, `vX`, and `sha-<short>` from annotated tags like `v1.2.0`.
108+
- `pr-<number>` and `sha-<short>` for pull-request builds.
109+
110+
**Releasing a new tag**
111+
1. After merging to `main`, run `make release TAG=v1.2.0 [MESSAGE="Release v1.2.0"]`. The helper ensures `main` is clean and in sync before tagging.
112+
2. If you must reuse a tag, run `make release TAG=v1.2.0 FLAGS="--force"` to move it deliberately.
113+
3. Wait for GitHub Actions to finish, then update `pipeline_image_version` in the overlays to the new tag.
114+
102115
### S3 Storage
103116

104117
- **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)