Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 37 additions & 16 deletions .github/workflows/helm-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,28 +117,49 @@ jobs:
echo "Schema validation passed for ${{ matrix.platform }}"

ingestor-multiarch:
# Guard: the chart's PINNED ingestor digest must be a multi-arch index
# (linux/amd64 + linux/arm64). Greenfield installs spawn the ingestor from this
# pinned digest (before image-refresh ticks), so an amd64-only pin breaks data
# ingestion on arm64 hosts (Apple Silicon, Graviton) with ImagePullBackOff. This
# would have caught #160 (which pinned the amd64-only v0.3.1). See client#186.
name: Pinned ingestor digest is multi-arch
# Guard: the ingestor image the cluster spawns must be a multi-arch index
# (linux/amd64 + linux/arm64), or arm64 hosts (Apple Silicon, Graviton)
# fail data ingestion with "no match for platform" / ImagePullBackOff.
# jobs-manager spawns ingestion Jobs by the floating tag
# `images.ingestor.tag` (imagePullPolicy=Always) by DEFAULT, so that tag
# must be multi-arch. If an operator opts into pinning via
# `images.ingestor.digest` (empty by default), the pinned digest must be
# multi-arch too. This would have caught #160 (the amd64-only v0.3.1 pin).
# See client#186.
name: Spawned ingestor image is multi-arch
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Assert images.ingestor.digest supports linux/amd64 + linux/arm64
- name: Assert the ingestor tag (and pinned digest, if any) is multi-arch
run: |
repo=$(yq '.images.ingestor.repository' client/values.yaml)
if [ -z "$repo" ] || [ "$repo" = "null" ]; then repo="ghcr.io/tracebloc/ingestor"; fi
tag=$(yq '.images.ingestor.tag' client/values.yaml)
digest=$(yq '.images.ingestor.digest' client/values.yaml)
echo "Pinned ingestor digest: $digest"
if [ -z "$digest" ] || [ "$digest" = "null" ]; then
echo "::error::images.ingestor.digest is empty — it must be a pinned multi-arch digest."; exit 1

assert_multiarch() {
ref="$1"; label="$2"
echo "Inspecting $label: $ref"
plats=$(docker buildx imagetools inspect "$ref" 2>&1 \
| awk '/Platform:/{print $2}' | grep -v '^unknown' | sort -u)
echo "Platforms: $(echo "$plats" | paste -sd' ' -)"
echo "$plats" | grep -qx 'linux/arm64' || { echo "::error::$label ($ref) is NOT multi-arch (no linux/arm64). arm64 installs (Apple Silicon, Graviton) would fail ingestion with 'no match for platform' / ImagePullBackOff. See client#186 / #160."; exit 1; }
echo "$plats" | grep -qx 'linux/amd64' || { echo "::error::$label ($ref) is missing linux/amd64."; exit 1; }
echo "OK — $label is multi-arch (amd64 + arm64)."
}

# The floating tag is the default spawn target — always validate it.
if [ -z "$tag" ] || [ "$tag" = "null" ]; then
echo "::error::images.ingestor.tag is empty — the chart must define a floating tag to spawn by."; exit 1
fi
assert_multiarch "${repo}:${tag}" "floating tag"

# A pinned digest is opt-in (empty by default). When set, it must be multi-arch too.
if [ -n "$digest" ] && [ "$digest" != "null" ]; then
assert_multiarch "${repo}@${digest}" "pinned digest"
else
echo "images.ingestor.digest empty (default) — spawning by floating tag; no pinned digest to check."
fi
plats=$(docker buildx imagetools inspect "ghcr.io/tracebloc/ingestor@$digest" 2>&1 \
| awk '/Platform:/{print $2}' | grep -v '^unknown' | sort -u)
echo "Platforms: $(echo "$plats" | paste -sd' ' -)"
echo "$plats" | grep -qx 'linux/arm64' || { echo "::error::Pinned ingestor digest is NOT multi-arch (no linux/arm64). arm64 installs (Apple Silicon, Graviton) would fail ingestion with 'no match for platform' / ImagePullBackOff. Pin a multi-arch :0.x index. See client#186 / #160."; exit 1; }
echo "$plats" | grep -qx 'linux/amd64' || { echo "::error::Pinned ingestor digest is missing linux/amd64."; exit 1; }
echo "OK — pinned ingestor digest is multi-arch (amd64 + arm64)."

# Installer script tests (bats + Pester) + the cross-distro prerequisite matrix
# live in their own workflow: .github/workflows/installer-tests.yaml
Expand Down
2 changes: 1 addition & 1 deletion client/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ apiVersion: v2
name: client
description: A unified Helm chart for tracebloc on AKS, EKS, bare-metal, and OpenShift
type: application
version: 1.5.1
version: 1.6.0
appVersion: "1.5.1"
keywords:
- tracebloc
Expand Down
25 changes: 9 additions & 16 deletions client/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -145,27 +145,20 @@ mysql-pvc
{{- $imgs := default dict .Values.images -}}
{{- $jm := default dict $imgs.jobsManager -}}
{{- $pm := default dict $imgs.podsMonitor -}}
{{- $in := default dict $imgs.ingestor -}}
{{/*
Per-image pin signals (each one means "skip auto-refresh for this image"):
* jobs-manager / pods-monitor: digest set (non-empty) — same signal as
the deployment uses to switch imagePullPolicy to IfNotPresent.
* ingestor: explicit `autoRefresh: false` flag — asymmetric because
ingestor.digest must be non-empty for jobs-manager to work, so we
can't use digest-presence as the signal there.
Per-image pin signal (means "skip auto-refresh for this image"):
jobs-manager / pods-monitor are pinned when `digest` is set (non-empty) —
the same signal the deployment uses to switch imagePullPolicy to
IfNotPresent. The ingestor is no longer refreshed by this CronJob (it is
spawned by jobs-manager from a floating tag — see the
image-refresh-cronjob.yaml header and submit_ingestion_run in
client-runtime), so the CronJob exists only to refresh the two class-1
images: when BOTH are pinned there is nothing left for it to do.
*/}}
{{- $jmPinned := $jm.digest -}}
{{- $pmPinned := $pm.digest -}}
{{/*
Can't use `default true $in.autoRefresh` here — Go templates treat
the bool `false` as falsy, so `default true false` returns `true`
and flips the pin state on the explicit-disable case. Instead test
for the literal `false` directly; absence (nil) and explicit `true`
both fall through to "not pinned".
*/}}
{{- $inPinned := eq $in.autoRefresh false -}}
{{- if not $ir.enabled -}}
{{- else if and (and $jmPinned $pmPinned) $inPinned -}}
{{- else if and $jmPinned $pmPinned -}}
{{- else -}}
true
{{- end -}}
Expand Down
Loading
Loading