From aff4da51d76b9c12b450e213a01440b41c6cf7de Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 27 May 2026 13:19:13 -0400 Subject: [PATCH 1/7] Containerfile: move controller binaries to /usr/local/bin/ I know this is a common pattern, but when we have a full userspace and not just a fat binary in a scratch container, I find it icky. Closes: #36 Assisted-by: Pi (Claude Opus 4.6) --- Containerfile | 5 ++--- config/daemon/daemon.yaml | 2 +- config/manager/manager.yaml | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Containerfile b/Containerfile index e12e199..e6a92ad 100644 --- a/Containerfile +++ b/Containerfile @@ -15,7 +15,6 @@ RUN --mount=type=cache,id=gomod,target=/root/go/pkg/mod \ go build -o daemon ./cmd/daemon/ FROM quay.io/fedora/fedora-minimal:44 -WORKDIR / -COPY --from=builder /workspace/manager /workspace/daemon . +COPY --from=builder /workspace/manager /workspace/daemon /usr/local/bin/ USER 65532:65532 -ENTRYPOINT ["/manager"] +ENTRYPOINT ["manager"] diff --git a/config/daemon/daemon.yaml b/config/daemon/daemon.yaml index 0891bc6..1da7ddb 100644 --- a/config/daemon/daemon.yaml +++ b/config/daemon/daemon.yaml @@ -24,7 +24,7 @@ spec: containers: - name: daemon command: - - /daemon + - daemon image: controller:latest env: - name: NODE_NAME diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 11d3753..ccab453 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -66,7 +66,7 @@ spec: type: RuntimeDefault containers: - command: - - /manager + - manager args: - --leader-elect - --health-probe-bind-address=:8081 From 6b451e75a1418c37fde05a3cf73c8a4591f6709d Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 27 May 2026 13:20:16 -0400 Subject: [PATCH 2/7] e2e: use bink --label flag instead of post-join patching bink v0.1.1 added --label support to `bink node add`, so labels can be applied at join time rather than via a separate K8s API patch after the node is Ready. This simplifies AddNode and removes the dependency on the K8s client for labeling. Closes: #33 Assisted-by: Pi (Claude Opus 4.6) --- test/e2e/e2eutil/env.go | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/test/e2e/e2eutil/env.go b/test/e2e/e2eutil/env.go index 7880d9e..c5442a9 100644 --- a/test/e2e/e2eutil/env.go +++ b/test/e2e/e2eutil/env.go @@ -111,8 +111,8 @@ func WithLabel(key, value string) NodeOption { } // AddNode provisions a worker node via bink, waits for it to be Ready, -// labels it with LabelE2ETest, and registers cleanup to remove it. -// Returns the node name. +// and returns the node name. The node is labeled with LabelE2ETest +// (and any extra labels from WithLabel). func (e *Env) AddNode(t *testing.T, opts ...NodeOption) string { t.Helper() @@ -123,8 +123,12 @@ func (e *Env) AddNode(t *testing.T, opts ...NodeOption) string { nodeName := e.generateNodeName(t) - // Provision the node. + // Provision the node with labels applied at join time. args := []string{"node", "add", nodeName, "--cluster-name", e.clusterName, "--control-plane", "controller"} + args = append(args, "--label", LabelE2ETest+"="+e.testID) + for k, v := range cfg.labels { + args = append(args, "--label", k+"="+v) + } if cfg.memory > 0 { args = append(args, "--memory", fmt.Sprintf("%d", cfg.memory)) } @@ -141,24 +145,6 @@ func (e *Env) AddNode(t *testing.T, opts ...NodeOption) string { // Wait for Ready. waitForNodeReady(t, e.Client, nodeName) - // Label with test ID (replace with --label` once available: - // https://github.com/alicefr/bink/issues/23). - var node corev1.Node - if err := e.Client.Get(context.Background(), client.ObjectKey{Name: nodeName}, &node); err != nil { - t.Fatalf("getting node %q for labeling: %v", nodeName, err) - } - patch := client.StrategicMergeFrom(node.DeepCopy()) - if node.Labels == nil { - node.Labels = map[string]string{} - } - node.Labels[LabelE2ETest] = e.testID - for k, v := range cfg.labels { - node.Labels[k] = v - } - if err := e.Client.Patch(context.Background(), &node, patch); err != nil { - t.Fatalf("labeling node %q: %v", nodeName, err) - } - return nodeName } From 2abe881a76b97e9b4f9fcd82f9f1a31241129a97 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 27 May 2026 13:20:40 -0400 Subject: [PATCH 3/7] config: add example BootcNodePool with full options Replace the kubebuilder stub samples with a realistic BootcNodePool example showcasing all spec fields: nodeSelector, tag-based image ref, rollout settings, soft-reboot disruption policy, and pull secret. The BootcNode sample is removed since those objects are operator-managed, not user-created. Closes: #39 Assisted-by: Pi (Claude Opus 4.6) --- config/samples/bootc_v1alpha1_bootcnode.yaml | 9 -------- .../samples/bootc_v1alpha1_bootcnodepool.yaml | 22 ++++++++++++++----- config/samples/kustomization.yaml | 3 --- 3 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 config/samples/bootc_v1alpha1_bootcnode.yaml diff --git a/config/samples/bootc_v1alpha1_bootcnode.yaml b/config/samples/bootc_v1alpha1_bootcnode.yaml deleted file mode 100644 index d83d491..0000000 --- a/config/samples/bootc_v1alpha1_bootcnode.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: node.bootc.dev/v1alpha1 -kind: BootcNode -metadata: - labels: - app.kubernetes.io/name: bootc-operator - app.kubernetes.io/managed-by: kustomize - name: bootcnode-sample -spec: - # TODO(user): Add fields here diff --git a/config/samples/bootc_v1alpha1_bootcnodepool.yaml b/config/samples/bootc_v1alpha1_bootcnodepool.yaml index 8f4c885..47cffa3 100644 --- a/config/samples/bootc_v1alpha1_bootcnodepool.yaml +++ b/config/samples/bootc_v1alpha1_bootcnodepool.yaml @@ -1,9 +1,21 @@ +# This sample pool targets all worker nodes. All optional fields are shown +# commented out with default or example values. apiVersion: node.bootc.dev/v1alpha1 kind: BootcNodePool metadata: - labels: - app.kubernetes.io/name: bootc-operator - app.kubernetes.io/managed-by: kustomize - name: bootcnodepool-sample + name: workers spec: - # TODO(user): Add fields here + nodeSelector: + matchLabels: + node-role.kubernetes.io/worker: "" + image: + ref: ghcr.io/alicefr/bink/node:latest + # rollout: + # maxUnavailable: 1 # default + # paused: false # default + # drainTimeoutSeconds: 300 # example; default: no timeout + # disruption: + # rebootPolicy: AllowSoftReboot # example; default: RebootOnly + # pullSecretRef: # example; default: none + # name: my-pull-secret + # namespace: bootc-operator diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 2e4d43d..e1f578c 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -1,5 +1,2 @@ -## Append samples of your project ## resources: - bootc_v1alpha1_bootcnodepool.yaml -- bootc_v1alpha1_bootcnode.yaml -# +kubebuilder:scaffold:manifestskustomizesamples From fdb2ffe78c3aed931deaa23ec462edd9c3c88c59 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 27 May 2026 13:20:56 -0400 Subject: [PATCH 4/7] ci: Publish container images to GHCR Push the e2e-tested operator image to ghcr.io/jlebon/bootc-operator on every push to main (:latest + :$GITHUB_SHA) and on version tags (:$TAG). Closes: #38 Assisted-by: Pi (Claude Opus 4.6) --- .github/workflows/ci.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 58ef01d..02e66f9 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -3,6 +3,7 @@ name: CI on: push: branches: [main] + tags: ["v*"] pull_request: branches: [main] @@ -38,6 +39,11 @@ jobs: e2e: runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: read + packages: write + env: + IMAGE: ghcr.io/${{ github.repository }} steps: - name: Checkout uses: actions/checkout@v6 @@ -81,3 +87,18 @@ jobs: - name: Run e2e tests run: make buildimg deploy-bink e2e V=1 + + - name: Push to GHCR + if: github.event_name == 'push' + run: | + podman login -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} ghcr.io + podman push bootc-operator:dev ${{ env.IMAGE }}:dev + podman push bootc-operator:dev ${{ env.IMAGE }}:${{ github.sha }} + + if [[ "${{ github.ref }}" == refs/tags/v* ]]; then + podman push bootc-operator:dev ${{ env.IMAGE }}:${{ github.ref_name }} + fi + + if [[ "${{ github.ref }}" == refs/heads/main ]]; then + podman push bootc-operator:dev ${{ env.IMAGE }}:latest + fi From 6ed59c7f684e2e62938eb4d19134c9a37f5162a6 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 27 May 2026 14:21:16 -0400 Subject: [PATCH 5/7] Use published GHCR image in manifests Now that we publish to ghcr.io/jlebon/bootc-operator, reference it in the default manifests instead of the kubebuilder placeholder. Assisted-by: Pi (Claude Opus 4.6) --- config/daemon/daemon.yaml | 2 +- config/manager/manager.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/daemon/daemon.yaml b/config/daemon/daemon.yaml index 1da7ddb..16f5234 100644 --- a/config/daemon/daemon.yaml +++ b/config/daemon/daemon.yaml @@ -25,7 +25,7 @@ spec: - name: daemon command: - daemon - image: controller:latest + image: ghcr.io/jlebon/bootc-operator:latest env: - name: NODE_NAME valueFrom: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index ccab453..dc18e6b 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -70,7 +70,7 @@ spec: args: - --leader-elect - --health-probe-bind-address=:8081 - image: controller:latest + image: ghcr.io/jlebon/bootc-operator:latest name: manager ports: - containerPort: 8081 From 12421c8862764440bb1cec5cc428b4af4e89a226 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 27 May 2026 15:38:50 -0400 Subject: [PATCH 6/7] ci: add Dependabot for Go modules and GitHub Actions Add Dependabot configuration for Go modules and GitHub Actions with weekly scheduling, minor/patch grouping, and cooldown. Add an auto-merge workflow for non-major Dependabot PRs. This pretty much copies what we have set up in chunkah. Closes: #32 Assisted-by: Pi (Claude Opus 4.6) --- .github/dependabot.yml | 36 ++++++++++++++++++++++++++++++++ .github/workflows/dependabot.yml | 26 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ddac962 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,36 @@ +version: 2 + +updates: + # Go modules + - package-ecosystem: gomod + directory: / + schedule: + interval: weekly + day: monday + groups: + go-deps: + patterns: + - "*" + update-types: + - minor + - patch + open-pull-requests-limit: 5 + commit-message: + prefix: "deps" + cooldown: + default-days: 7 + + # GitHub Actions + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + groups: + actions: + patterns: + - "*" + commit-message: + prefix: "ci" + cooldown: + default-days: 7 diff --git a/.github/workflows/dependabot.yml b/.github/workflows/dependabot.yml new file mode 100644 index 0000000..c9146bb --- /dev/null +++ b/.github/workflows/dependabot.yml @@ -0,0 +1,26 @@ +name: Dependabot + +on: pull_request + +permissions: {} + +jobs: + automerge: + runs-on: ubuntu-latest + permissions: + contents: write # Required to merge PR + pull-requests: write # Required to enable auto-merge for PR + if: github.event.pull_request.user.login == 'dependabot[bot]' + steps: + - name: Fetch Dependabot metadata + id: metadata + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Enable auto-merge for minor/patch updates + if: steps.metadata.outputs.update-type != 'version-update:semver-major' + run: gh pr merge --auto --rebase "$PR_URL" + env: + PR_URL: ${{ github.event.pull_request.html_url }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 04f1b2507b1508feb9764e8211549a29492ff142 Mon Sep 17 00:00:00 2001 From: Jonathan Lebon Date: Wed, 27 May 2026 15:38:54 -0400 Subject: [PATCH 7/7] ci: add zizmor scanning and harden workflows Now that we build and publish images which are referenced in the manifest, let's harden the CI workflows using zizmor. This also matches what we did in chunkah: https://github.com/coreos/chunkah/pull/122 Assisted-by: Pi (Claude Opus 4.6) --- .github/workflows/ci.yaml | 40 ++++++++++++++++++++++++------------ .github/workflows/zizmor.yml | 24 ++++++++++++++++++++++ 2 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/zizmor.yml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 02e66f9..fdf1d42 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,6 +10,8 @@ on: env: BINK_VERSION: v0.1.1 +permissions: {} + concurrency: group: ci-${{ github.head_ref || github.ref }} cancel-in-progress: true @@ -17,15 +19,19 @@ concurrency: jobs: unit: runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod - cache: true + cache: true # zizmor: ignore[cache-poisoning] - name: Unit tests run: make unit @@ -46,7 +52,9 @@ jobs: IMAGE: ghcr.io/${{ github.repository }} steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false - name: Download bink release run: | @@ -55,10 +63,10 @@ jobs: sudo chmod +x /usr/local/bin/bink - name: Set up Go - uses: actions/setup-go@v6 + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 with: go-version-file: go.mod - cache: true + cache: true # zizmor: ignore[cache-poisoning] - name: Set up KVM run: sudo chmod 666 /dev/kvm @@ -90,15 +98,21 @@ jobs: - name: Push to GHCR if: github.event_name == 'push' + env: + ACTOR: ${{ github.actor }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SHA: ${{ github.sha }} + REF: ${{ github.ref }} + REF_NAME: ${{ github.ref_name }} run: | - podman login -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} ghcr.io - podman push bootc-operator:dev ${{ env.IMAGE }}:dev - podman push bootc-operator:dev ${{ env.IMAGE }}:${{ github.sha }} + podman login -u "${ACTOR}" -p "${GH_TOKEN}" ghcr.io + podman push bootc-operator:dev "${IMAGE}":dev + podman push bootc-operator:dev "${IMAGE}":"${SHA}" - if [[ "${{ github.ref }}" == refs/tags/v* ]]; then - podman push bootc-operator:dev ${{ env.IMAGE }}:${{ github.ref_name }} + if [[ "${REF}" == refs/tags/v* ]]; then + podman push bootc-operator:dev "${IMAGE}":"${REF_NAME}" fi - if [[ "${{ github.ref }}" == refs/heads/main ]]; then - podman push bootc-operator:dev ${{ env.IMAGE }}:latest + if [[ "${REF}" == refs/heads/main ]]; then + podman push bootc-operator:dev "${IMAGE}":latest fi diff --git a/.github/workflows/zizmor.yml b/.github/workflows/zizmor.yml new file mode 100644 index 0000000..7ec0495 --- /dev/null +++ b/.github/workflows/zizmor.yml @@ -0,0 +1,24 @@ +name: Zizmor + +on: + push: + branches: [main] + pull_request: + branches: ["**"] + +permissions: {} + +jobs: + zizmor: + name: Scan GHA workflows + runs-on: ubuntu-24.04 + permissions: + security-events: write # Required to upload SARIF files + steps: + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Run zizmor + uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6