Compare commits

...
Author SHA1 Message Date
claude-botandClaude Opus 5 fe6de18048 fix(mosquitto): propagate the alpine-jq base tag instead of hardcoding it
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/push/build-ci-images Pipeline was successful
ci/woodpecker/pr/pr-review Pipeline was successful
Review point on #449, and a real flaw rather than a nit.

The Containerfile pinned `FROM alpine-jq:2026-07-30`. The pipeline step
already retriggers on changes to ci/images/alpine-jq/**, so a base rebuild
— a CVE patch, say — WOULD rebuild this image, but against the old pinned
tag. That is worse than not rebuilding: the step goes green while shipping
a stale base, so the fix looks applied and is not.

Now ARG BASE_TAG, passed by the pipeline as the same date value used for
this image own tags, with depends_on: [build-alpine-jq] guaranteeing the
base for that date is pushed first. The two images therefore always move
together. The `latest` default applies only to a manual docker build
outside CI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:55:23 +02:00
claude-botandClaude Opus 5 3f5387979c fix(mosquitto): drop runtime apk add from the 7 bootstrap Jobs
Last of the runtime `apk add` cleanups. Each of the seven credential
bootstrap Jobs ran `apk add --no-cache curl jq` on every execution, making
them depend on package-index availability at run time.

Adds ci/images/alpine-curl-jq, LAYERED `FROM alpine-jq` rather than
repeating that image's build. jq is therefore installed in exactly one
Containerfile, the shared layers are pulled once, and the relationship
between the two images is explicit instead of implied by duplicated
instructions. The pipeline step uses depends_on: [build-alpine-jq] and also
triggers on changes to the base's path, so the base is built before
anything is layered onto it.

Kept curl rather than switching to busybox wget, deliberately. wget has
--header and --post-data but NO --ca-certificate, so reaching the
Kubernetes API with the ServiceAccount CA needs the CA appended to the
system trust bundle first. That does work — verified in-cluster: TLS
validated and the request reached RBAC, returning 403 rather than a
certificate error — but it changes error semantics versus `curl -f` and
busybox sets its own Content-Type for --post-data. These Jobs mint
credentials, and the existence check they branch on decides whether a
Secret gets created at all, so their logic was left untouched: the diff is
one deleted line and one image change per Job.

Rationale recorded once at the top of the file rather than repeated seven
times.

Verified all seven Jobs render with the new image and no apk add:

  mosquitto-admin-credentials-bootstrap      -> alpine-curl-jq:2026-07-30
  mosquitto-credentials-bootstrap            -> alpine-curl-jq:2026-07-30
  mosquitto-health-credentials-bootstrap     -> alpine-curl-jq:2026-07-30
  mosquitto-open3e-credentials-bootstrap     -> alpine-curl-jq:2026-07-30
  mosquitto-shellies-credentials-bootstrap   -> alpine-curl-jq:2026-07-30
  mosquitto-sml2mqtt-credentials-bootstrap   -> alpine-curl-jq:2026-07-30
  mosquitto-zigbee2mqtt-credentials-bootstrap-> alpine-curl-jq:2026-07-30

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 14:55:23 +02:00
3 changed files with 93 additions and 14 deletions
+40
View File
@@ -217,3 +217,43 @@ steps:
--dockerfile Containerfile \
--destination gitea.renner.uno/gitea_admin/alpine-openssl:$TAG \
--destination gitea.renner.uno/gitea_admin/alpine-openssl:latest
# Runtime image, not CI tooling: alpine-jq PLUS curl, LAYERED on that image
# rather than repeating its build, so jq lives in exactly one Containerfile and
# the shared layers are pulled once. Consumed by the mosquitto bootstrap Jobs,
# which talk to the Kubernetes API and build JSON bodies. Replaces an
# `apk add curl jq` that ran on every Job execution.
#
# depends_on build-alpine-jq (not []) because this FROMs it: the base must be
# built and pushed before it is layered on. Combined with --build-arg
# BASE_TAG=$TAG below, that means this image is ALWAYS layered on the base
# built in the SAME pipeline run, so a base change (e.g. a CVE patch)
# propagates automatically instead of silently rebuilding against a stale
# hardcoded tag.
- name: build-alpine-curl-jq
image: gcr.io/kaniko-project/executor:v1.23.2-debug
directory: ci/images/alpine-curl-jq
depends_on: [build-alpine-jq]
when:
- event: manual
- event: push
path:
- "ci/images/alpine-curl-jq/**"
- "ci/images/alpine-jq/**"
- ".woodpecker/build-ci-images.yml"
environment:
GITEA_USER: gitea_admin
GITEA_TOKEN:
from_secret: ci_images_token
commands:
- mkdir -p /kaniko/.docker
- AUTH=$(printf '%s:%s' "$GITEA_USER" "$GITEA_TOKEN" | base64 | tr -d '\n')
- printf '{"auths":{"gitea.renner.uno":{"auth":"%s"}}}' "$AUTH" > /kaniko/.docker/config.json
- TAG=$(date +%Y-%m-%d)
- |
/kaniko/executor \
--context . \
--dockerfile Containerfile \
--build-arg BASE_TAG=$TAG \
--destination gitea.renner.uno/gitea_admin/alpine-curl-jq:$TAG \
--destination gitea.renner.uno/gitea_admin/alpine-curl-jq:latest
+29
View File
@@ -0,0 +1,29 @@
# LAYERED on alpine-jq rather than repeating its build. jq is installed in
# exactly one place (ci/images/alpine-jq/Containerfile); this image is that plus
# curl, and shares every layer below it — so consumers that pull both get the
# common layers once.
#
# BASE_TAG is supplied by the pipeline as the SAME `date +%Y-%m-%d` value used for
# this image's own tags, and build-alpine-curl-jq depends_on build-alpine-jq, so
# the base for today's date is always pushed before this is layered onto it.
#
# Deliberately NOT a hardcoded dated tag. With one, a rebuild of alpine-jq — a CVE
# patch, say — would never reach this image: the pipeline's when.path already
# retriggers this step when the base's directory changes, but it would rebuild
# against the OLD pinned tag. That is worse than not rebuilding at all, because
# the step goes green while shipping a stale base. Passing the tag through makes
# the two images move together.
#
# The `latest` default applies only to a manual `docker build` outside the
# pipeline; CI always passes BASE_TAG explicitly.
ARG BASE_TAG=latest
FROM gitea.renner.uno/gitea_admin/alpine-jq:${BASE_TAG}
# curl, not busybox wget, on purpose. busybox wget has --header and --post-data
# but NO --ca-certificate, so talking to the Kubernetes API with the
# ServiceAccount CA needs the CA appended to the system trust bundle first.
# That works (verified: TLS validated, request reached RBAC and returned 403),
# but it changes error semantics versus `curl -f` and busybox sets its own
# Content-Type for --post-data. The consumers here mint Secrets, so keeping curl
# means their scripts are untouched apart from deleting the apk add line.
RUN apk add --no-cache curl
@@ -1,4 +1,21 @@
---
# ALL SEVEN bootstrap Jobs in this file run
# gitea.renner.uno/gitea_admin/alpine-curl-jq (built by
# .woodpecker/build-ci-images.yml from ci/images/alpine-curl-jq/, itself layered
# FROM alpine-jq so jq is installed in exactly one place). They previously used
# alpine:3.21 and ran `apk add --no-cache curl jq` on every execution, which made
# each Job depend on package-index availability at run time. Tools belong in the
# image; the scripts below are otherwise unchanged.
#
# curl rather than busybox wget on purpose: busybox wget has --header and
# --post-data but no --ca-certificate, so reaching the Kubernetes API with the
# ServiceAccount CA would need the CA appended to the system trust bundle, and
# its error semantics and Content-Type handling differ from `curl -f`. These Jobs
# mint credentials, so their logic was left alone.
#
# Dated tag, matching the ci-ansible / alpine-jq convention: a change to what
# these Jobs run is a reviewable diff here rather than a silent rebuild.
---
# Generates mosquitto-credentials on first deploy. The password is created
# in-cluster, lives only in the Secret object, and is never written to git
# or seen by a human. The Mosquitto broker hashes it via mosquitto_passwd
@@ -55,13 +72,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-credentials
@@ -123,13 +139,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-admin-credentials
@@ -190,13 +205,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-shellies-credentials
@@ -256,13 +270,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-sml2mqtt-credentials
@@ -324,13 +337,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-open3e-credentials
@@ -392,13 +404,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-health-credentials
@@ -461,13 +472,12 @@ spec:
restartPolicy: OnFailure
containers:
- name: bootstrap
image: alpine:3.21
image: gitea.renner.uno/gitea_admin/alpine-curl-jq:2026-07-30
command:
- sh
- -c
- |
set -eu
apk add --no-cache curl jq >/dev/null
NS=home-assistant
NAME=mosquitto-zigbee2mqtt-credentials