Skip to content
Open
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
66 changes: 37 additions & 29 deletions .github/workflows/test-chart.yml
Original file line number Diff line number Diff line change
@@ -1,41 +1,35 @@
name: Test Chart

permissions:
contents: read

on:
pull_request:
paths-ignore:
- 'docs/**'
- '**/*.md'

permissions: {}

jobs:
test-e2e:
name: Run test chart
permissions:
contents: read
name: Run on Ubuntu
runs-on: ubuntu-latest
env:
IMG: controller:latest
steps:
- name: Clone the code
uses: actions/checkout@v6
Comment thread
afritzler marked this conversation as resolved.
with:
persist-credentials: false

- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod

- name: Install Helm
run: |
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

- name: Verify Helm installation
run: helm version

- name: Lint Helm Chart
run: |
helm lint ./dist/chart

- name: Install the latest version of kind
run: |
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)
chmod +x ./kind
Comment on lines 31 to 33
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

What is the official recommended way to install kind in CI with version pinning and checksum verification?

💡 Result:

Official, CI-friendly way (with version pinning + checksum verification) is to download the exact kind binary release asset for your OS/arch at a pinned version, also download the matching ${binary}.sha256sum asset, then verify with sha256sum -c before marking the binary executable. The kind project publishes stable binaries and they are “generally strongly recommended for CI usage”. [1][2] Example (Linux amd64; adapt OS/arch and filenames): 1) Pick a pinned kind version, e.g. v0.31.0. 2) Download the binary and its checksum, then verify: VERSION=v0.31.0 OS="$(uname -s | tr '[:upper:]' '[:lower:]')" # should map to github asset naming ARCH="$(uname -m)" case "$ARCH" in x86_64) ARCH_SUFFIX=amd64;; aarch64) ARCH_SUFFIX=arm64;; *) echo "unsupported arch: $ARCH"; exit 1;; esac BINARY="kind-${OS}-${ARCH_SUFFIX}" BASE_URL="https://kind.sigs.k8s.io/dl/${VERSION}" curl -sSLo "./kind" "${BASE_URL}/${BINARY}" curl -sSLo "./${BINARY}.sha256sum" "${BASE_URL}/${BINARY}.sha256sum" # Verify integrity (expects the sha256 file format to include the filename) sha256sum --check "./${BINARY}.sha256sum" chmod +x./kind sudo mv./kind /usr/local/bin/kind Why this works: - kind release assets include per-binary .sha256sum files alongside the binaries. [3][4] - A widely used install implementation pattern in the ecosystem is: download kind-linux-${arch} and kind-linux-${arch}.sha256sum, then run sha256sum -c/--check against the checksum file before moving the binary into place. [5] Notes: - If you use Kubernetes-in-kind node images, you can further pin node images by digest (the kind docs recommend using sha256 shasums for those images). [6][7]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,120p' .github/workflows/test-chart.yml | nl -ba | sed -n '20,60p'

Repository: ironcore-dev/metal-operator

Length of output: 114


🏁 Script executed:

#!/bin/bash
set -euo pipefail
# Show numbered lines around the reported location (use cat -n since nl isn't available)
cat -n .github/workflows/test-chart.yml | sed -n '20,60p'

Repository: ironcore-dev/metal-operator

Length of output: 1454


Pin kind version and verify its SHA256 checksum in CI

In .github/workflows/test-chart.yml (lines 28-32), the workflow downloads kind from .../dl/latest/... and installs it without any checksum verification, making CI behavior drift and weakening binary integrity. Download a pinned kind release binary and its matching ${binary}.sha256sum from https://kind.sigs.k8s.io/dl/<version>/, run sha256sum --check on the checksum file, then install the verified binary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/test-chart.yml around lines 29 - 31, Replace the current
unpinned curl download (the line that runs curl -Lo ./kind
https://kind.sigs.k8s.io/dl/latest/kind-linux-$(go env GOARCH)) with a pinned
release URL (e.g. https://kind.sigs.k8s.io/dl/<VERSION>/kind-linux-$(go env
GOARCH)), also download the corresponding <binary>.sha256sum file from the same
<VERSION> directory, run sha256sum --check <binary>.sha256sum to verify
integrity, and only then keep the existing chmod +x ./kind to install the
verified binary; ensure the workflow errors out if the checksum verification
fails.

sudo mv ./kind /usr/local/bin/kind

Expand All @@ -47,26 +41,40 @@ jobs:

- name: Prepare metal-operator
run: |
go mod download
make docker-build CONTROLLER_IMG=metal-operator:v0.1.0 METALPROBE_IMG=metal-probe:v0.1.0
kind load docker-image metal-operator:v0.1.0 metal-probe:v0.1.0
go mod tidy
make docker-build
kind load docker-image $IMG

- name: Install Helm
run: make install-helm

- name: Install cert-manager via Helm
- name: Lint Helm Chart
run: |
helm lint ./dist/chart


- name: Install cert-manager via Helm (wait for readiness)
run: |
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager --namespace cert-manager --create-namespace --set installCRDs=true
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true \
--wait \
--timeout 300s
Comment thread
afritzler marked this conversation as resolved.

- name: Wait for cert-manager to be ready
run: |
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-cainjector
kubectl wait --namespace cert-manager --for=condition=available --timeout=300s deployment/cert-manager-webhook
# TODO: Uncomment if Prometheus is enabled
# - name: Install Prometheus Operator CRDs
# run: |
# helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# helm repo update
# helm install prometheus-crds prometheus-community/prometheus-operator-crds

- name: Install Helm chart for project
- name: Deploy manager via Helm
run: |
helm install my-release ./dist/chart --create-namespace --namespace metal-operator-system
make helm-deploy

- name: Check Helm release status
run: |
helm status my-release --namespace metal-operator-system
make helm-status
49 changes: 48 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.

.PHONY: helm
helm: manifests kubebuilder
"$(KUBEBUILDER)" edit --plugins=helm/v1-alpha
"$(KUBEBUILDER)" edit --plugins=helm/v2-alpha

##@ Dependencies

Expand Down Expand Up @@ -420,3 +420,50 @@ kind-delete: ## Destroys the "metal" kind cluster.
.PHONY: tilt-up
tilt-up: $(ENVTEST) $(KUSTOMIZE) kind-create ## start tilt and build kind cluster if needed
EXP_CLUSTER_RESOURCE_SET=true tilt up

##@ Helm Deployment

## Helm binary to use for deploying the chart
HELM ?= helm
## Namespace to deploy the Helm release
HELM_NAMESPACE ?= metal-operator-system
## Name of the Helm release
HELM_RELEASE ?= metal-operator
## Path to the Helm chart directory
HELM_CHART_DIR ?= dist/chart
## Additional arguments to pass to helm commands
HELM_EXTRA_ARGS ?=

.PHONY: install-helm
install-helm: ## Install the latest version of Helm.
@command -v $(HELM) >/dev/null 2>&1 || { \
echo "Installing Helm..." && \
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

.PHONY: helm-deploy
helm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.
$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \
--namespace $(HELM_NAMESPACE) \
--create-namespace \
--set manager.image.repository=$${IMG%:*} \
--set manager.image.tag=$${IMG##*:} \
Comment on lines +445 to +450
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fail fast when IMG is unset in helm-deploy.

Right now, empty IMG values flow into string-splitting and can produce unclear Helm failures.

Suggested guard
 .PHONY: helm-deploy
 helm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.
+	`@test` -n "$$IMG" || { echo "IMG is required (example: IMG=ghcr.io/org/controller:tag make helm-deploy)"; exit 1; }
 	$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \
 		--namespace $(HELM_NAMESPACE) \
 		--create-namespace \
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
helm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.
$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \
--namespace $(HELM_NAMESPACE) \
--create-namespace \
--set manager.image.repository=$${IMG%:*} \
--set manager.image.tag=$${IMG##*:} \
helm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.
`@test` -n "$$IMG" || { echo "IMG is required (example: IMG=ghcr.io/org/controller:tag make helm-deploy)"; exit 1; }
$(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \
--namespace $(HELM_NAMESPACE) \
--create-namespace \
--set manager.image.repository=$${IMG%:*} \
--set manager.image.tag=$${IMG##*:} \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 445 - 450, The helm-deploy target currently uses the
IMG Make variable without checking it; add a guard at the start of the
helm-deploy recipe to fail fast if IMG is empty by printing an error and exiting
non-zero. Specifically, in the helm-deploy target, detect if the IMG variable is
unset/empty (e.g., test -z "$(IMG)") and if so echo a clear error like "IMG is
required for helm-deploy" and exit 1 before any use of ${IMG%:*} or ${IMG##*:};
otherwise continue to the existing $(HELM) upgrade --install invocation.

--wait \
--timeout 5m \
$(HELM_EXTRA_ARGS)

.PHONY: helm-uninstall
helm-uninstall: ## Uninstall the Helm release from the K8s cluster.
$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)

.PHONY: helm-status
helm-status: ## Show Helm release status.
$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)

.PHONY: helm-history
helm-history: ## Show Helm release history.
$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)

.PHONY: helm-rollback
helm-rollback: ## Rollback to previous Helm release.
$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
4 changes: 3 additions & 1 deletion PROJECT
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ layout:
- go.kubebuilder.io/v4
plugins:
autoupdate.kubebuilder.io/v1-alpha: {}
helm.kubebuilder.io/v1-alpha: {}
helm.kubebuilder.io/v2-alpha:
manifests: dist/install.yaml
output: dist
projectName: metal-operator
repo: github.com/ironcore-dev/metal-operator
resources:
Expand Down
6 changes: 6 additions & 0 deletions config/manager/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: controller
newName: controller
newTag: latest
resources:
- manager.yaml
11 changes: 9 additions & 2 deletions dist/chart/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
apiVersion: v2
name: metal-operator
description: A Helm chart to distribute the project metal-operator
description: A Helm chart to distribute metal-operator
type: application

version: 0.1.0
appVersion: "0.1.0"
icon: "https://example.com/icon.png"

keywords:
- kubernetes
- operator

annotations:
kubebuilder.io/generated-by: kubebuilder
15 changes: 15 additions & 0 deletions dist/chart/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Thank you for installing {{ .Chart.Name }}.

Your release is named {{ .Release.Name }}.

The controller and CRDs have been installed in namespace {{ .Release.Namespace }}.

To verify the installation:

kubectl get pods -n {{ .Release.Namespace }}
kubectl get customresourcedefinitions

To learn more about the release, try:

$ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}
$ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}
93 changes: 53 additions & 40 deletions dist/chart/templates/_helpers.tpl
Original file line number Diff line number Diff line change
@@ -1,50 +1,63 @@
{{- define "chart.name" -}}
{{- if .Chart }}
{{- if .Chart.Name }}
{{- .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- else if .Values.nameOverride }}
{{ .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
metal-operator
{{- end }}
{{/*
Expand the name of the chart.
*/}}
{{- define "metal-operator.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "metal-operator.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
metal-operator
{{- end }}
{{- end }}


{{- define "chart.labels" -}}
{{- if .Chart.Version -}}
helm.sh/chart: {{ .Chart.Version | quote }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
app.kubernetes.io/name: {{ include "chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}


{{- define "chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{/*
Namespace for generated references.
Always uses the Helm release namespace.
*/}}
{{- define "metal-operator.namespaceName" -}}
{{- .Release.Namespace }}
{{- end }}


{{- define "chart.hasMutatingWebhooks" -}}
{{- $hasMutating := false }}
{{- range . }}
{{- if eq .type "mutating" }}
$hasMutating = true }}{{- end }}
{{/*
Resource name with proper truncation for Kubernetes 63-character limit.
Takes a dict with:
- .suffix: Resource name suffix (e.g., "metrics", "webhook")
- .context: Template context (root context with .Values, .Release, etc.)
Dynamically calculates safe truncation to ensure total name length <= 63 chars.
*/}}
{{- define "metal-operator.resourceName" -}}
{{- $fullname := include "metal-operator.fullname" .context }}
{{- $suffix := .suffix }}
{{- $maxLen := sub 62 (len $suffix) | int }}
{{- if gt (len $fullname) $maxLen }}
{{- printf "%s-%s" (trunc $maxLen $fullname | trimSuffix "-") $suffix | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" $fullname $suffix | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{ $hasMutating }}}}{{- end }}


{{- define "chart.hasValidatingWebhooks" -}}
{{- $hasValidating := false }}
{{- range . }}
{{- if eq .type "validating" }}
$hasValidating = true }}{{- end }}
{{/*
ServiceAccount name to use.
If serviceAccount.enable is false and serviceAccount.name is set, use that name.
Otherwise, use the standard resourceName helper with "controller-manager" suffix.
*/}}
{{- define "metal-operator.serviceAccountName" -}}
{{- if and (not (.Values.serviceAccount.enable | default true)) .Values.serviceAccount.name }}
{{- .Values.serviceAccount.name }}
{{- else }}
{{- include "metal-operator.resourceName" (dict "suffix" "controller-manager" "context" .) }}
{{- end }}
{{- end }}
{{ $hasValidating }}}}{{- end }}
20 changes: 20 additions & 0 deletions dist/chart/templates/cert-manager/metrics-certs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{- if and .Values.certManager.enable .Values.metrics.enable .Values.metrics.secure }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "metal-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "metal-operator.resourceName" (dict "suffix" "metrics-certs" "context" $) }}
namespace: {{ .Release.Namespace }}
spec:
dnsNames:
- {{ include "metal-operator.resourceName" (dict "suffix" "controller-manager-metrics-service" "context" $) }}.{{ .Release.Namespace }}.svc
- {{ include "metal-operator.resourceName" (dict "suffix" "controller-manager-metrics-service" "context" $) }}.{{ .Release.Namespace }}.svc.cluster.local
issuerRef:
kind: Issuer
name: {{ include "metal-operator.resourceName" (dict "suffix" "selfsigned-issuer" "context" $) }}
secretName: metrics-server-cert
{{- end }}
14 changes: 14 additions & 0 deletions dist/chart/templates/cert-manager/selfsigned-issuer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{- if .Values.certManager.enable }}
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "metal-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "metal-operator.resourceName" (dict "suffix" "selfsigned-issuer" "context" $) }}
namespace: {{ .Release.Namespace }}
spec:
selfSigned: {}
{{- end }}
20 changes: 20 additions & 0 deletions dist/chart/templates/cert-manager/serving-cert.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{{- if .Values.certManager.enable }}
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "metal-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "metal-operator.resourceName" (dict "suffix" "serving-cert" "context" $) }}
namespace: {{ .Release.Namespace }}
spec:
dnsNames:
- {{ include "metal-operator.resourceName" (dict "suffix" "webhook-service" "context" $) }}.{{ .Release.Namespace }}.svc
- {{ include "metal-operator.resourceName" (dict "suffix" "webhook-service" "context" $) }}.{{ .Release.Namespace }}.svc.cluster.local
issuerRef:
kind: Issuer
name: {{ include "metal-operator.resourceName" (dict "suffix" "selfsigned-issuer" "context" $) }}
secretName: webhook-server-cert
{{- end }}
Loading
Loading