Skip to content

Commit d856d13

Browse files
committed
try to verify contents of the images via a label
1 parent a605023 commit d856d13

File tree

4 files changed

+124
-33
lines changed

4 files changed

+124
-33
lines changed

.github/workflows/_image-factory.yml

Lines changed: 84 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ jobs:
2626
runs-on: ubuntu-latest
2727
outputs:
2828
run-build: ${{ steps.check.outputs.run }}
29+
core-sha: ${{ steps.stamp.outputs.core }}
30+
eo-sha: ${{ steps.stamp.outputs.eo }}
31+
hub-sha: ${{ steps.stamp.outputs.hub }}
32+
cli-sha: ${{ steps.stamp.outputs.cli }}
33+
expected-manifest: ${{ steps.stamp.outputs.manifest }}
2934
steps:
3035
- uses: actions/checkout@v4
36+
3137
- id: check
3238
run: |
3339
if [[ "${{ github.event_name }}" == "repository_dispatch" ]]; then
@@ -38,6 +44,25 @@ jobs:
3844
else echo "run=false" >> $GITHUB_OUTPUT; fi
3945
else echo "run=true" >> $GITHUB_OUTPUT; fi
4046
47+
- id: stamp
48+
run: |
49+
CORE="${{ inputs.repo-core-ref || (github.event.client_payload.source_repo == 'mapchete' && github.event.client_payload.sha) || 'main' }}"
50+
EO="${{ inputs.repo-eo-ref || (github.event.client_payload.source_repo == 'mapchete-eo' && github.event.client_payload.sha) || 'main' }}"
51+
HUB="${{ inputs.repo-hub-ref || (github.event.client_payload.source_repo == 'mapchete-hub' && github.event.client_payload.sha) || 'main' }}"
52+
CLI="${{ inputs.repo-cli-ref || (github.event.client_payload.source_repo == 'mapchete-hub-cli' && github.event.client_payload.sha) || 'main' }}"
53+
54+
if [[ "${{ inputs.image-name }}" == "mapchete" ]]; then
55+
MANIFEST="${CORE}|${EO}|${HUB}|none"
56+
else
57+
MANIFEST="none|none|${HUB}|${CLI}"
58+
fi
59+
60+
echo "manifest=$MANIFEST" >> $GITHUB_OUTPUT
61+
echo "core=$CORE" >> $GITHUB_OUTPUT
62+
echo "eo=$EO" >> $GITHUB_OUTPUT
63+
echo "hub=$HUB" >> $GITHUB_OUTPUT
64+
echo "cli=$CLI" >> $GITHUB_OUTPUT
65+
4166
build:
4267
needs: prepare
4368
if: needs.prepare.outputs.run-build == 'true'
@@ -59,30 +84,67 @@ jobs:
5984
load: true
6085
tags: local-test-image:latest
6186
build-args: |
62-
REPO_CORE_REF=${{ inputs.repo-core-ref || (github.event.client_payload.source_repo == 'mapchete' && github.event.client_payload.sha) || 'main' }}
63-
REPO_EO_REF=${{ inputs.repo-eo-ref || (github.event.client_payload.source_repo == 'mapchete-eo' && github.event.client_payload.sha) || 'main' }}
64-
REPO_HUB_REF=${{ inputs.repo-hub-ref || (github.event.client_payload.source_repo == 'mapchete-hub' && github.event.client_payload.sha) || 'main' }}
65-
REPO_CLI_REF=${{ inputs.repo-cli-ref || (github.event.client_payload.source_repo == 'mapchete-hub-cli' && github.event.client_payload.sha) || 'main' }}
87+
REPO_CORE_REF=${{ needs.prepare.outputs.core-sha }}
88+
REPO_EO_REF=${{ needs.prepare.outputs.eo-sha }}
89+
REPO_HUB_REF=${{ needs.prepare.outputs.hub-sha }}
90+
REPO_CLI_REF=${{ needs.prepare.outputs.cli-sha }}
91+
92+
- name: Verify Build & Sync Check
93+
id: uv_check
94+
run: |
95+
# 1. Verify Git Sync Integrity
96+
ACTUAL_MANIFEST=$(docker run --rm local-test-image:latest cat /app/build_manifest)
97+
EXPECTED_MANIFEST="${{ needs.prepare.outputs.expected-manifest }}"
98+
if [ "$ACTUAL_MANIFEST" != "$EXPECTED_MANIFEST" ]; then
99+
echo "❌ Git SHA Mismatch! CI expected $EXPECTED_MANIFEST but Docker built $ACTUAL_MANIFEST"
100+
exit 1
101+
fi
102+
103+
# 2. Extract UV Fingerprint
104+
UV_HASH=$(docker run --rm local-test-image:latest cat /app/uv_fingerprint)
105+
echo "uv-hash=$UV_HASH" >> $GITHUB_OUTPUT
106+
107+
# 3. Compare with Remote Registry Label
108+
REMOTE_TAG="dev"
109+
if [[ "${{ github.event_name }}" == "repository_dispatch" && "${{ github.event.client_payload.image_tag }}" == "latest" ]]; then REMOTE_TAG="latest"; fi
110+
111+
REMOTE_HASH=$(docker manifest inspect ghcr.io/mapchete/${{ inputs.image-name }}:$REMOTE_TAG 2>/dev/null | jq -r '.config.labels["org.mapchete.uv_lock_hash"]' || echo "none")
112+
113+
if [[ "$UV_HASH" == "$REMOTE_HASH" && "${{ github.ref_type }}" != "tag" ]]; then
114+
echo "changed=false" >> $GITHUB_OUTPUT
115+
echo "✅ Content Match: $UV_HASH matches registry. Skipping push."
116+
else
117+
echo "changed=true" >> $GITHUB_OUTPUT
118+
echo "🚀 Content Change: New fingerprint $UV_HASH"
119+
fi
66120
67121
- name: Integration Tests
68-
if: inputs.image-name == 'mapchete'
122+
if: steps.uv_check.outputs.changed == 'true'
123+
id: tests
69124
run: |
70-
docker run --rm \
71-
-e AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \
72-
-e AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \
73-
-e CDSE_S3_ACCESS_KEY=${{ secrets.CDSE_S3_ACCESS_KEY }} \
74-
-e CDSE_S3_SECRET_KEY=${{ secrets.CDSE_S3_SECRET_KEY }} \
75-
-e AWS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \
76-
local-test-image:latest \
77-
/bin/bash -c "cd deps/mapchete-eo && uv pip install -e .[test] && uv run pytest"
125+
if docker run --rm local-test-image:latest ls -d deps/mapchete-eo/tests >/dev/null 2>&1; then
126+
echo "found=true" >> $GITHUB_OUTPUT
127+
docker run --rm \
128+
-e AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} \
129+
-e AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} \
130+
-e CDSE_S3_ACCESS_KEY=${{ secrets.CDSE_S3_ACCESS_KEY }} \
131+
-e CDSE_S3_SECRET_KEY=${{ secrets.CDSE_S3_SECRET_KEY }} \
132+
-e AWS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \
133+
local-test-image:latest \
134+
/bin/bash -c "cd deps/mapchete-eo && uv pip install -e .[test] && uv run pytest"
135+
else
136+
echo "found=false" >> $GITHUB_OUTPUT
137+
fi
78138
79139
- uses: docker/login-action@v3
140+
if: steps.uv_check.outputs.changed == 'true'
80141
with:
81142
registry: ghcr.io
82143
username: ${{ github.actor }}
83144
password: ${{ secrets.GITHUB_TOKEN }}
84145

85146
- id: meta
147+
if: steps.uv_check.outputs.changed == 'true'
86148
uses: docker/metadata-action@v5
87149
with:
88150
images: ghcr.io/mapchete/${{ inputs.image-name }}
@@ -91,45 +153,32 @@ jobs:
91153
tags: |
92154
type=raw,value=${{ inputs.image-tag }},enable=${{ inputs.image-tag != '' }}
93155
type=ref,event=tag
94-
95-
# BUILD LATEST:
96-
# - Only on push to main or 'latest' dispatch
97156
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || (github.event_name == 'repository_dispatch' && github.event.client_payload.image_tag == 'latest') }}
98-
99-
# BUILD DEV:
100-
# - On PRs
101-
# - On push to main
102-
# - On 'dev' dispatch
103-
# - On 'latest' dispatch (as requested)
104157
type=raw,value=dev,enable=${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/main' || (github.event_name == 'repository_dispatch' && (github.event.client_payload.image_tag == 'dev' || github.event.client_payload.image_tag == 'latest')) }}
105-
106158
type=ref,event=pr
107159
108160
- id: push
161+
if: steps.uv_check.outputs.changed == 'true'
109162
uses: docker/build-push-action@v5
110163
with:
111164
context: ${{ inputs.image-name }}/
112165
push: true
113166
tags: ${{ steps.meta.outputs.tags }}
114167
labels: ${{ steps.meta.outputs.labels }}
115168
build-args: |
116-
REPO_CORE_REF=${{ inputs.repo-core-ref || (github.event.client_payload.source_repo == 'mapchete' && github.event.client_payload.sha) || 'main' }}
117-
REPO_EO_REF=${{ inputs.repo-eo-ref || (github.event.client_payload.source_repo == 'mapchete-eo' && github.event.client_payload.sha) || 'main' }}
118-
REPO_HUB_REF=${{ inputs.repo-hub-ref || (github.event.client_payload.source_repo == 'mapchete-hub' && github.event.client_payload.sha) || 'main' }}
119-
REPO_CLI_REF=${{ inputs.repo-cli-ref || (github.event.client_payload.source_repo == 'mapchete-hub-cli' && github.event.client_payload.sha) || 'main' }}
169+
REPO_CORE_REF=${{ needs.prepare.outputs.core-sha }}
170+
REPO_EO_REF=${{ needs.prepare.outputs.eo-sha }}
171+
REPO_HUB_REF=${{ needs.prepare.outputs.hub-sha }}
172+
REPO_CLI_REF=${{ needs.prepare.outputs.cli-sha }}
173+
IMAGE_FINGERPRINT=${{ steps.uv_check.outputs.uv-hash }}
120174
121175
- uses: actions/attest-build-provenance@v2
122-
if: steps.push.outputs.digest != ''
176+
if: steps.uv_check.outputs.changed == 'true' && steps.push.outputs.digest != ''
123177
with:
124178
subject-name: ghcr.io/mapchete/${{ inputs.image-name }}
125179
subject-digest: ${{ steps.push.outputs.digest }}
126180
push-to-registry: true
127181

128-
- name: Update Version File
129-
if: github.event_name == 'repository_dispatch' && github.event.client_payload.type == 'version'
130-
run: |
131-
echo "${{ github.event.client_payload.source_repo }}: ${{ github.event.client_payload.image_tag }}" > ${{ inputs.image-name }}/VERSION.txt
132-
133182
- name: Create or Update Pull Request
134183
if: github.event_name == 'repository_dispatch' && github.event.client_payload.type == 'version'
135184
uses: peter-evans/create-pull-request@v6
@@ -141,6 +190,8 @@ jobs:
141190
body: |
142191
Automated update triggered by `${{ github.event.client_payload.source_repo }}`.
143192
- **New Version:** `${{ github.event.client_payload.image_tag }}`
193+
- **UV Environment Hash:** `${{ steps.uv_check.outputs.uv-hash }}`
194+
- **Sync Integrity Check:** PASSED ✅
144195
base: main
145196
delete-branch: true
146197
assignees: "scartography"

mapchete/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,28 @@ RUN uv init --name factory-build --lib . && \
2929
uv lock && \
3030
uv sync --frozen --no-dev
3131

32+
# Generate the fingerprint based on the resolved lockfile
33+
RUN sha256sum uv.lock | cut -d' ' -f1 > /app/uv_fingerprint
34+
# Also keep the manifest for the CI sync check
35+
RUN CORE_SHA=$(git -C deps/mapchete rev-parse HEAD 2>/dev/null || echo "none") && \
36+
EO_SHA=$(git -C deps/mapchete-eo rev-parse HEAD 2>/dev/null || echo "none") && \
37+
HUB_SHA=$(git -C deps/mapchete-hub rev-parse HEAD 2>/dev/null || echo "none") && \
38+
echo "${CORE_SHA}|${EO_SHA}|${HUB_SHA}}" > /app/build_manifest
39+
3240
FROM ghcr.io/osgeo/gdal:ubuntu-small-3.12.0
3341

42+
ARG IMAGE_FINGERPRINT=unknown
43+
3444
WORKDIR /app
3545
COPY --from=builder /app/.venv /app/.venv
3646
COPY --from=builder /app/deps /app/deps
3747
COPY . .
3848
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
3949

50+
COPY --from=builder /app/uv_fingerprint /app/uv_fingerprint
51+
COPY --from=builder /app/build_manifest /app/build_manifest
52+
LABEL org.mapchete.uv_lock_hash=$IMAGE_FINGERPRINT
53+
4054
ENV PATH="/app/.venv/bin:$PATH"
4155

4256
CMD ["mapchete", "--help"]

mhub-cli-full/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,25 @@ RUN uv init --name factory-build --lib . && \
1818
uv lock && \
1919
uv sync --frozen --no-dev
2020

21+
# Generate the fingerprint based on the resolved lockfile
22+
RUN sha256sum uv.lock | cut -d' ' -f1 > /app/uv_fingerprint
23+
# Also keep the manifest for the CI sync check
24+
RUN mkdir -p /app && \
25+
CLI_SHA=$(git -C deps/mapchete-hub-cli rev-parse HEAD 2>/dev/null || echo "none") && \
26+
echo "${CLI_SHA}" > /app/build_manifest
27+
2128
FROM ghcr.io/osgeo/gdal:ubuntu-small-3.12.0
2229

30+
ARG IMAGE_FINGERPRINT=unknown
31+
2332
WORKDIR /app
2433
COPY --from=builder /app/.venv /app/.venv
2534
COPY --from=builder /app/deps /app/deps
2635
COPY . .
2736

37+
COPY --from=builder /app/uv_fingerprint /app/uv_fingerprint
38+
COPY --from=builder /app/build_manifest /app/build_manifest
39+
LABEL org.mapchete.image_fingerprint=$IMAGE_FINGERPRINT
40+
2841
ENV PATH="/app/.venv/bin:$PATH"
2942
ENTRYPOINT ["mhub"]

mhub-cli-light/Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,25 @@ RUN uv init --name factory-build --lib . && \
1818
uv lock && \
1919
uv sync --frozen --no-dev
2020

21+
# Generate the fingerprint based on the resolved lockfile
22+
RUN sha256sum uv.lock | cut -d' ' -f1 > /app/uv_fingerprint
23+
# Also keep the manifest for the CI sync check
24+
RUN mkdir -p /app && \
25+
CLI_SHA=$(git -C deps/mapchete-hub-cli rev-parse HEAD 2>/dev/null || echo "none") && \
26+
echo "${CLI_SHA}" > /app/build_manifest
27+
2128
FROM ghcr.io/osgeo/gdal:ubuntu-small-3.12.0
2229

30+
ARG IMAGE_FINGERPRINT=unknown
31+
2332
WORKDIR /app
2433
COPY --from=builder /app/.venv /app/.venv
2534
COPY --from=builder /app/deps /app/deps
2635
COPY . .
2736

37+
COPY --from=builder /app/uv_fingerprint /app/uv_fingerprint
38+
COPY --from=builder /app/build_manifest /app/build_manifest
39+
LABEL org.mapchete.image_fingerprint=$IMAGE_FINGERPRINT
40+
2841
ENV PATH="/app/.venv/bin:$PATH"
2942
ENTRYPOINT ["mhub"]

0 commit comments

Comments
 (0)