Skip to content

Commit 57bd941

Browse files
committed
fix(cozy-lib,harbor,mariadb): digest-pin cleanup-hook kubectl image and route it through cozy-lib.images-registry
The post-delete cleanup Jobs in the harbor and mariadb charts hardcoded `docker.io/clastix/kubectl:v1.32` — a moving tag pulled directly from Docker Hub. This broke build reproducibility (the tag can be re-pushed), was a supply-chain risk, and could not be satisfied on air-gapped / mirrored clusters, where it failed at the worst moment: during uninstall, when the post-delete hook runs and stalls release teardown. Add a canonical `cozy-lib.image` helper that prefixes a registry-relative image path with `cozy-lib.images-registry` when set and returns it unchanged (no leading "/") when empty, so standard installs resolve from the default registry and mirrored installs resolve internally. Consume it from both cleanup hooks with the image digest-pinned to the immutable multi-arch index sha256:b9ef7d8dbe65bcc81a46c09b8dc7543103055021c4f43287bf59e92a8f4fe05c (the same digest already pinned for postgres-operator's webhook hook). Keep the pin fresh with a custom Renovate manager: the repo disables the helm-values manager (enabledManagers), and the ref is built at render time by the helper rather than being a Dockerfile/go.mod dependency, so the built-in managers cannot see it. A regex manager over the hook templates tracks clastix/kubectl by tag+digest — mirroring the existing extra/etcd manager — so the supply-chain pin does not silently age. Also harden `cozy-lib.images-registry` against a nil .Values._cluster so the helper never crashes the render — relevant precisely because the hook renders during teardown. Helm unit tests assert the rendered image (routed, digest-pinned, no leading slash) by pattern rather than an exact digest, so a Renovate bump keeps them green. Closes #3088 Assisted-By: Claude <[email protected]> Signed-off-by: Алексей Артамонов <[email protected]>
1 parent 4783add commit 57bd941

6 files changed

Lines changed: 140 additions & 3 deletions

File tree

‎.github/renovate.json‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424
"managerFilePatterns": ["/^packages/extra/etcd/templates/etcd-cluster\\.yaml$/"],
2525
"matchStrings": ["image:\\s+(?<depName>quay\\.io/coreos/etcd):(?<currentValue>v[0-9]+\\.[0-9]+\\.[0-9]+)"],
2626
"datasourceTemplate": "docker"
27+
},
28+
{
29+
"customType": "regex",
30+
"description": "Track and keep fresh the clastix/kubectl image digest-pinned in the post-delete cleanup hooks. The ref is assembled at render time by the cozy-lib.image helper (routed through cozy-lib.images-registry so mirrored / air-gapped installs resolve it), so it is neither a Dockerfile nor a go.mod dependency and the built-in managers cannot see it. The helm-values manager is disabled repo-wide (see enabledManagers above), so without this regex manager the digest — a supply-chain pin — would silently age.",
31+
"managerFilePatterns": ["/^packages/.+/templates/hooks/.+\\.yaml$/"],
32+
"matchStrings": ["clastix/kubectl:(?<currentValue>v[0-9]+\\.[0-9]+(?:\\.[0-9]+)?)@(?<currentDigest>sha256:[a-f0-9]+)"],
33+
"datasourceTemplate": "docker",
34+
"depNameTemplate": "docker.io/clastix/kubectl"
2735
}
2836
],
2937
"packageRules": [

‎packages/apps/harbor/templates/hooks/cleanup.yaml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@ spec:
2727
type: RuntimeDefault
2828
containers:
2929
- name: cleanup
30-
image: docker.io/clastix/kubectl:v1.32
30+
# Digest-pinned (immutable) and routed through the cluster images
31+
# registry via cozy-lib.image, so mirrored / air-gapped installs
32+
# resolve it and an upstream retag cannot change what runs at
33+
# uninstall. The tag+digest is refreshed by the "clastix/kubectl"
34+
# custom Renovate manager in .github/renovate.json (the helm-values
35+
# manager is disabled repo-wide), so this pin does not silently age.
36+
image: {{ include "cozy-lib.image" (list "clastix/kubectl:v1.32@sha256:b9ef7d8dbe65bcc81a46c09b8dc7543103055021c4f43287bf59e92a8f4fe05c" $) }}
3137
securityContext:
3238
allowPrivilegeEscalation: false
3339
readOnlyRootFilesystem: true

‎packages/apps/harbor/tests/cleanup_hook_test.yaml‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,45 @@ tests:
7474
path: spec.template.spec.containers[0].command[2]
7575
pattern: kubectl delete[^\n]*-l acme.cert-manager.io/http01-solver=true
7676

77+
- it: pins the cleanup image by digest and routes it through the images registry
78+
documentIndex: 0
79+
asserts:
80+
# The image must be digest-pinned (immutable) rather than a moving tag,
81+
# and must NOT hardcode docker.io so a mirrored/air-gapped registry can be
82+
# prefixed via cozy-lib.image. Asserted by pattern (not an exact digest)
83+
# so a Renovate tag/digest bump does not break the test.
84+
- matchRegex:
85+
path: spec.template.spec.containers[0].image
86+
pattern: ^clastix/kubectl:v[0-9]+\.[0-9]+(\.[0-9]+)?@sha256:[0-9a-f]{64}$
87+
- notMatchRegex:
88+
path: spec.template.spec.containers[0].image
89+
pattern: docker\.io
90+
91+
- it: prefixes the cleanup image with the cluster images registry when set
92+
documentIndex: 0
93+
set:
94+
_cluster:
95+
images-registry: registry.example.com
96+
asserts:
97+
# A mirrored / air-gapped install resolves the image from the internal
98+
# registry; the digest pin is preserved.
99+
- matchRegex:
100+
path: spec.template.spec.containers[0].image
101+
pattern: ^registry\.example\.com/clastix/kubectl:v[0-9]+\.[0-9]+(\.[0-9]+)?@sha256:[0-9a-f]{64}$
102+
103+
- it: leaves no leading slash when the images registry is empty (default)
104+
documentIndex: 0
105+
asserts:
106+
# With images-registry defaulting to "", the rendered ref must still be a
107+
# valid image reference (no leading "/") so a standard install resolves it.
108+
# The "^clastix" anchor proves there is no leading slash and no registry.
109+
- matchRegex:
110+
path: spec.template.spec.containers[0].image
111+
pattern: ^clastix/kubectl:v[0-9]+\.[0-9]+(\.[0-9]+)?@sha256:[0-9a-f]{64}$
112+
- notMatchRegex:
113+
path: spec.template.spec.containers[0].image
114+
pattern: ^/
115+
77116
- it: sets resource requests and limits on the cleanup container
78117
documentIndex: 0
79118
asserts:

‎packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@ spec:
2626
type: RuntimeDefault
2727
containers:
2828
- name: cleanup
29-
image: docker.io/clastix/kubectl:v1.32
29+
# Digest-pinned (immutable) and routed through the cluster images
30+
# registry via cozy-lib.image, so mirrored / air-gapped installs
31+
# resolve it and an upstream retag cannot change what runs at
32+
# uninstall. The tag+digest is refreshed by the "clastix/kubectl"
33+
# custom Renovate manager in .github/renovate.json (the helm-values
34+
# manager is disabled repo-wide), so this pin does not silently age.
35+
image: {{ include "cozy-lib.image" (list "clastix/kubectl:v1.32@sha256:b9ef7d8dbe65bcc81a46c09b8dc7543103055021c4f43287bf59e92a8f4fe05c" $) }}
3036
securityContext:
3137
allowPrivilegeEscalation: false
3238
readOnlyRootFilesystem: true
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
suite: mariadb post-delete cleanup hook
2+
templates:
3+
- templates/hooks/cleanup-pvc.yaml
4+
5+
release:
6+
name: mariadb-test
7+
namespace: tenant-root
8+
9+
tests:
10+
- it: renders the cleanup Job as a post-delete hook
11+
documentIndex: 0
12+
asserts:
13+
- equal:
14+
path: kind
15+
value: Job
16+
- equal:
17+
path: metadata.name
18+
value: mariadb-test-cleanup
19+
- equal:
20+
path: metadata.annotations["helm.sh/hook"]
21+
value: post-delete
22+
23+
- it: pins the cleanup image by digest and routes it through the images registry
24+
documentIndex: 0
25+
asserts:
26+
# The image must be digest-pinned (immutable) rather than a moving tag,
27+
# and must NOT hardcode docker.io so a mirrored/air-gapped registry can be
28+
# prefixed via cozy-lib.image. Asserted by pattern (not an exact digest)
29+
# so a Renovate tag/digest bump does not break the test.
30+
- matchRegex:
31+
path: spec.template.spec.containers[0].image
32+
pattern: ^clastix/kubectl:v[0-9]+\.[0-9]+(\.[0-9]+)?@sha256:[0-9a-f]{64}$
33+
- notMatchRegex:
34+
path: spec.template.spec.containers[0].image
35+
pattern: docker\.io
36+
37+
- it: prefixes the cleanup image with the cluster images registry when set
38+
documentIndex: 0
39+
set:
40+
_cluster:
41+
images-registry: registry.example.com
42+
asserts:
43+
# A mirrored / air-gapped install resolves the image from the internal
44+
# registry; the digest pin is preserved.
45+
- matchRegex:
46+
path: spec.template.spec.containers[0].image
47+
pattern: ^registry\.example\.com/clastix/kubectl:v[0-9]+\.[0-9]+(\.[0-9]+)?@sha256:[0-9a-f]{64}$
48+
49+
- it: leaves no leading slash when the images registry is empty (default)
50+
documentIndex: 0
51+
asserts:
52+
# With images-registry defaulting to "", the rendered ref must still be a
53+
# valid image reference (no leading "/") so a standard install resolves it.
54+
# The "^clastix" anchor proves there is no leading slash and no registry.
55+
- matchRegex:
56+
path: spec.template.spec.containers[0].image
57+
pattern: ^clastix/kubectl:v[0-9]+\.[0-9]+(\.[0-9]+)?@sha256:[0-9a-f]{64}$
58+
- notMatchRegex:
59+
path: spec.template.spec.containers[0].image
60+
pattern: ^/

‎packages/library/cozy-lib/templates/_cozyconfig.tpl‎

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,25 @@ Get the images registry.
2424
Usage: {{ include "cozy-lib.images-registry" . }}
2525
*/}}
2626
{{- define "cozy-lib.images-registry" -}}
27-
{{- (index .Values._cluster "images-registry") | default "" }}
27+
{{- (index (.Values._cluster | default dict) "images-registry") | default "" }}
28+
{{- end }}
29+
30+
{{/*
31+
Build a fully-qualified image reference routed through the cluster images
32+
registry. Pass the registry-relative image path (repository + tag, ideally
33+
digest-pinned) and the global context.
34+
Usage: {{ include "cozy-lib.image" (list "clastix/kubectl:v1.32@sha256:..." $) }}
35+
When images-registry is set (mirrored / air-gapped installs) it is prefixed as
36+
"<registry>/<image>"; when empty (the default on a standard install) the image
37+
is returned unchanged so it resolves from its default registry — importantly
38+
with no leading "/", which would otherwise be an invalid reference.
39+
*/}}
40+
{{- define "cozy-lib.image" -}}
41+
{{- include "cozy-lib.checkInput" . }}
42+
{{- $image := index . 0 }}
43+
{{- $ctx := index . 1 }}
44+
{{- $registry := include "cozy-lib.images-registry" $ctx }}
45+
{{- with $registry }}{{ . }}/{{ end }}{{ $image }}
2846
{{- end }}
2947

3048
{{/*

0 commit comments

Comments
 (0)