Make release image tags truly immutable
This commit is contained in:
@@ -72,6 +72,9 @@
|
|||||||
- Hardened the all-in-one release path with commit-SHA image tags, OCI build
|
- Hardened the all-in-one release path with commit-SHA image tags, OCI build
|
||||||
labels, previous-image preservation, automatic/manual rollback, an isolated
|
labels, previous-image preservation, automatic/manual rollback, an isolated
|
||||||
fresh-install smoke and dependency-cache-safe build metadata.
|
fresh-install smoke and dependency-cache-safe build metadata.
|
||||||
|
- Bound immutable release tags to both commit and dependency profile
|
||||||
|
(`<sha>-ai` or `<sha>-gis`) and reuse matching images instead of rebuilding
|
||||||
|
or overwriting an existing release tag.
|
||||||
- Made production startup reject known-default PostGIS passwords and apply the
|
- Made production startup reject known-default PostGIS passwords and apply the
|
||||||
configured upload limit consistently to nginx and FastAPI.
|
configured upload limit consistently to nginx and FastAPI.
|
||||||
|
|
||||||
|
|||||||
@@ -15,15 +15,20 @@ def test_build_identity_does_not_invalidate_dependency_layers() -> None:
|
|||||||
assert build_identity > source_copy
|
assert build_identity > source_copy
|
||||||
assert 'org.opencontainers.image.revision="${GEOINTEL_BUILD_SHA}"' in dockerfile
|
assert 'org.opencontainers.image.revision="${GEOINTEL_BUILD_SHA}"' in dockerfile
|
||||||
assert 'org.opencontainers.image.created="${GEOINTEL_BUILD_TIME}"' in dockerfile
|
assert 'org.opencontainers.image.created="${GEOINTEL_BUILD_TIME}"' in dockerfile
|
||||||
|
assert 'io.geointel.ai.enabled="${GEOINTEL_INSTALL_AI}"' in dockerfile
|
||||||
|
|
||||||
|
|
||||||
def test_release_deploy_preserves_immutable_and_previous_images() -> None:
|
def test_release_deploy_preserves_immutable_and_previous_images() -> None:
|
||||||
script = (ROOT / "deploy" / "unraid" / "deploy-release.sh").read_text(encoding="utf-8")
|
script = (ROOT / "deploy" / "unraid" / "deploy-release.sh").read_text(encoding="utf-8")
|
||||||
|
|
||||||
assert 'GEOINTEL_RELEASE_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:${GEOINTEL_BUILD_SHA}"' in script
|
assert 'GEOINTEL_RELEASE_VARIANT="ai"' in script
|
||||||
|
assert 'GEOINTEL_RELEASE_VARIANT="gis"' in script
|
||||||
|
assert 'GEOINTEL_RELEASE_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:${GEOINTEL_BUILD_SHA}-${GEOINTEL_RELEASE_VARIANT}"' in script
|
||||||
assert 'GEOINTEL_PREVIOUS_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:previous"' in script
|
assert 'GEOINTEL_PREVIOUS_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:previous"' in script
|
||||||
assert 'docker tag "$current_image_id" "${GEOINTEL_IMAGE_REPOSITORY}:${current_revision}"' in script
|
|
||||||
assert 'docker tag "$current_image_id" "$GEOINTEL_PREVIOUS_IMAGE"' in script
|
assert 'docker tag "$current_image_id" "$GEOINTEL_PREVIOUS_IMAGE"' in script
|
||||||
|
assert 'if docker image inspect "$GEOINTEL_RELEASE_IMAGE"' in script
|
||||||
|
assert "Immutable release tag has conflicting metadata" in script
|
||||||
|
assert "Reusing existing immutable image" in script
|
||||||
assert "rollback_previous()" in script
|
assert "rollback_previous()" in script
|
||||||
assert "Deployed immutable image" in script
|
assert "Deployed immutable image" in script
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,8 @@ ENV GEOINTEL_BUILD_SHA="${GEOINTEL_BUILD_SHA}" \
|
|||||||
LABEL org.opencontainers.image.title="GeoIntel" \
|
LABEL org.opencontainers.image.title="GeoIntel" \
|
||||||
org.opencontainers.image.description="GeoIntel workbench for Belgium and the Belgian North Sea" \
|
org.opencontainers.image.description="GeoIntel workbench for Belgium and the Belgian North Sea" \
|
||||||
org.opencontainers.image.revision="${GEOINTEL_BUILD_SHA}" \
|
org.opencontainers.image.revision="${GEOINTEL_BUILD_SHA}" \
|
||||||
org.opencontainers.image.created="${GEOINTEL_BUILD_TIME}"
|
org.opencontainers.image.created="${GEOINTEL_BUILD_TIME}" \
|
||||||
|
io.geointel.ai.enabled="${GEOINTEL_INSTALL_AI}"
|
||||||
|
|
||||||
VOLUME ["/var/lib/postgresql/data", "/app/storage"]
|
VOLUME ["/var/lib/postgresql/data", "/app/storage"]
|
||||||
|
|
||||||
|
|||||||
@@ -81,10 +81,11 @@ password is empty or one of the documented defaults.
|
|||||||
|
|
||||||
The repository deploy scripts run the same flow automatically. They validate
|
The repository deploy scripts run the same flow automatically. They validate
|
||||||
the Compose reference, preserve the current image as
|
the Compose reference, preserve the current image as
|
||||||
`geointel-all-in-one:previous`, build an immutable commit-SHA tag plus `latest`,
|
`geointel-all-in-one:previous`, build an immutable `<commit-sha>-ai` or
|
||||||
install the DockerMan metadata and start the SHA-tagged image. A failed start,
|
`<commit-sha>-gis` tag plus `latest`, install the DockerMan metadata and start
|
||||||
live migration smoke or browser/API smoke automatically attempts the previous
|
the immutable image. An existing matching tag is reused, never rebuilt. A
|
||||||
image without changing the configured PostGIS or storage paths.
|
failed start, live migration smoke or browser/API smoke automatically attempts
|
||||||
|
the previous image without changing the configured PostGIS or storage paths.
|
||||||
|
|
||||||
`scripts/deploy_tower.sh` and `scripts/deploy_tower.ps1` source the remote
|
`scripts/deploy_tower.sh` and `scripts/deploy_tower.ps1` source the remote
|
||||||
`.env` before building the image. That means `GEOINTEL_INSTALL_AI=true` in
|
`.env` before building the image. That means `GEOINTEL_INSTALL_AI=true` in
|
||||||
@@ -270,7 +271,7 @@ bash deploy/unraid/rollback-dockerman-container.sh
|
|||||||
For an older retained commit, select its immutable tag explicitly:
|
For an older retained commit, select its immutable tag explicitly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GEOINTEL_ROLLBACK_IMAGE=geointel-all-in-one:<commit-sha> \
|
GEOINTEL_ROLLBACK_IMAGE=geointel-all-in-one:<commit-sha>-ai \
|
||||||
bash deploy/unraid/rollback-dockerman-container.sh
|
bash deploy/unraid/rollback-dockerman-container.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ GEOINTEL_INSTALL_AI="${GEOINTEL_INSTALL_AI:-false}"
|
|||||||
GEOINTEL_BUILD_SHA="$(git rev-parse HEAD)"
|
GEOINTEL_BUILD_SHA="$(git rev-parse HEAD)"
|
||||||
GEOINTEL_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
GEOINTEL_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
GEOINTEL_IMAGE_REPOSITORY="${GEOINTEL_IMAGE_REPOSITORY:-geointel-all-in-one}"
|
GEOINTEL_IMAGE_REPOSITORY="${GEOINTEL_IMAGE_REPOSITORY:-geointel-all-in-one}"
|
||||||
GEOINTEL_RELEASE_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:${GEOINTEL_BUILD_SHA}"
|
if [ "$GEOINTEL_INSTALL_AI" = "true" ]; then
|
||||||
|
GEOINTEL_RELEASE_VARIANT="ai"
|
||||||
|
else
|
||||||
|
GEOINTEL_RELEASE_VARIANT="gis"
|
||||||
|
fi
|
||||||
|
GEOINTEL_RELEASE_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:${GEOINTEL_BUILD_SHA}-${GEOINTEL_RELEASE_VARIANT}"
|
||||||
GEOINTEL_PREVIOUS_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:previous"
|
GEOINTEL_PREVIOUS_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:previous"
|
||||||
FRONTEND_URL="${FRONTEND_URL:-http://127.0.0.1:${GEOINTEL_FRONTEND_PORT:-1202}}"
|
FRONTEND_URL="${FRONTEND_URL:-http://127.0.0.1:${GEOINTEL_FRONTEND_PORT:-1202}}"
|
||||||
|
|
||||||
@@ -62,25 +67,36 @@ docker compose -f docker-compose.unraid.yml config >/dev/null
|
|||||||
|
|
||||||
current_image_id="$(docker inspect --format '{{.Image}}' geointel 2>/dev/null || true)"
|
current_image_id="$(docker inspect --format '{{.Image}}' geointel 2>/dev/null || true)"
|
||||||
if [ -n "$current_image_id" ] && docker image inspect "$current_image_id" >/dev/null 2>&1; then
|
if [ -n "$current_image_id" ] && docker image inspect "$current_image_id" >/dev/null 2>&1; then
|
||||||
current_revision="$(
|
|
||||||
docker image inspect \
|
|
||||||
--format '{{index .Config.Labels "org.opencontainers.image.revision"}}' \
|
|
||||||
"$current_image_id" 2>/dev/null || true
|
|
||||||
)"
|
|
||||||
if [ -n "$current_revision" ] && [ "$current_revision" != "<no value>" ] && [ "$current_revision" != "unknown" ]; then
|
|
||||||
docker tag "$current_image_id" "${GEOINTEL_IMAGE_REPOSITORY}:${current_revision}"
|
|
||||||
fi
|
|
||||||
docker tag "$current_image_id" "$GEOINTEL_PREVIOUS_IMAGE"
|
docker tag "$current_image_id" "$GEOINTEL_PREVIOUS_IMAGE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker build \
|
if docker image inspect "$GEOINTEL_RELEASE_IMAGE" >/dev/null 2>&1; then
|
||||||
--build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" \
|
stored_revision="$(
|
||||||
--build-arg GEOINTEL_BUILD_SHA="$GEOINTEL_BUILD_SHA" \
|
docker image inspect \
|
||||||
--build-arg GEOINTEL_BUILD_TIME="$GEOINTEL_BUILD_TIME" \
|
--format '{{index .Config.Labels "org.opencontainers.image.revision"}}' \
|
||||||
-f deploy/unraid/Dockerfile.all-in-one \
|
"$GEOINTEL_RELEASE_IMAGE"
|
||||||
-t "$GEOINTEL_RELEASE_IMAGE" \
|
)"
|
||||||
-t "${GEOINTEL_IMAGE_REPOSITORY}:latest" \
|
stored_ai="$(
|
||||||
.
|
docker image inspect \
|
||||||
|
--format '{{index .Config.Labels "io.geointel.ai.enabled"}}' \
|
||||||
|
"$GEOINTEL_RELEASE_IMAGE"
|
||||||
|
)"
|
||||||
|
if [ "$stored_revision" != "$GEOINTEL_BUILD_SHA" ] || [ "$stored_ai" != "$GEOINTEL_INSTALL_AI" ]; then
|
||||||
|
echo "Immutable release tag has conflicting metadata: ${GEOINTEL_RELEASE_IMAGE}" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
echo "Reusing existing immutable image ${GEOINTEL_RELEASE_IMAGE}."
|
||||||
|
docker tag "$GEOINTEL_RELEASE_IMAGE" "${GEOINTEL_IMAGE_REPOSITORY}:latest"
|
||||||
|
else
|
||||||
|
docker build \
|
||||||
|
--build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI" \
|
||||||
|
--build-arg GEOINTEL_BUILD_SHA="$GEOINTEL_BUILD_SHA" \
|
||||||
|
--build-arg GEOINTEL_BUILD_TIME="$GEOINTEL_BUILD_TIME" \
|
||||||
|
-f deploy/unraid/Dockerfile.all-in-one \
|
||||||
|
-t "$GEOINTEL_RELEASE_IMAGE" \
|
||||||
|
-t "${GEOINTEL_IMAGE_REPOSITORY}:latest" \
|
||||||
|
.
|
||||||
|
fi
|
||||||
|
|
||||||
if ! start_image "$GEOINTEL_RELEASE_IMAGE"; then
|
if ! start_image "$GEOINTEL_RELEASE_IMAGE"; then
|
||||||
rollback_previous || true
|
rollback_previous || true
|
||||||
|
|||||||
@@ -45,6 +45,10 @@
|
|||||||
immutable commit images plus a `previous` rollback target, adding automatic
|
immutable commit images plus a `previous` rollback target, adding automatic
|
||||||
rollback and an isolated fresh-install smoke, and failing closed on default
|
rollback and an isolated fresh-install smoke, and failing closed on default
|
||||||
database secrets or inconsistent upload limits.
|
database secrets or inconsistent upload limits.
|
||||||
|
- Tower proved cached apt/PyTorch layers, a fresh install on isolated paths and
|
||||||
|
a healthy rollback against the retained database/storage. The release tag
|
||||||
|
contract was then tightened to immutable commit-plus-profile tags so repeated
|
||||||
|
deploys reuse rather than overwrite the same build identity.
|
||||||
|
|
||||||
- Froze the RC geography as all Belgian land plus the separately labelled
|
- Froze the RC geography as all Belgian land plus the separately labelled
|
||||||
territorial sea, EEZ and continental shelf.
|
territorial sea, EEZ and continental shelf.
|
||||||
|
|||||||
Reference in New Issue
Block a user