diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b6faf24..d68dd1f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,7 +9,6 @@ on: env: BINK_COMMIT: c7c4013e1275f07fe6ee90be92b61d256117460f - RELEASED_OPERATOR_TAG: v0.1.1 permissions: contents: read @@ -115,8 +114,6 @@ jobs: - name: Deploy to bink cluster run: make deploy-bink - env: - RELEASED_OPERATOR_IMG: ghcr.io/${{ github.repository }}:${{ env.RELEASED_OPERATOR_TAG }} - name: Gather deploy logs if: failure() @@ -124,8 +121,6 @@ jobs: - name: Run e2e tests run: make e2e V=1 - env: - RELEASED_OPERATOR_IMG: ghcr.io/${{ github.repository }}:${{ env.RELEASED_OPERATOR_TAG }} - name: Upload logs if: always() diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml deleted file mode 100644 index 4a9fa18..0000000 --- a/.github/workflows/helm.yaml +++ /dev/null @@ -1,111 +0,0 @@ -name: Helm - -on: - push: - branches: [main, release-*] - pull_request: - branches: [main, release-*] - -env: - BINK_COMMIT: c7c4013e1275f07fe6ee90be92b61d256117460f - -permissions: - contents: read - -concurrency: - group: helm-${{ github.head_ref || github.ref }} - cancel-in-progress: true - -jobs: - build-bink: - uses: $/.github/workflows/build-bink.yaml - with: - bink-commit: c7c4013e1275f07fe6ee90be92b61d256117460f - - build-operator: - uses: $/.github/workflows/build-operator.yaml - - helm-e2e: - runs-on: ubuntu-latest - needs: [build-bink, build-operator] - timeout-minutes: 30 - permissions: - contents: read - env: - BINK_NODE_DISK_IMAGE: ghcr.io/bootc-dev/bink/node@sha256:d3077c99027db0c196d5c6531ef07b8955be827651b9e3d2bc4b8e7c6ebf5612 - steps: - - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - - name: Download bink binary - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: bink - path: /usr/local/bin - - - name: Make bink executable - run: chmod +x /usr/local/bin/bink - - - name: Set up Go - uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 - with: - go-version-file: go.mod - cache: true # zizmor: ignore[cache-poisoning] - - - name: Set up bink environment - uses: $/.github/actions/setup-bink-env - - - name: Download operator image - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: operator-image - - - name: Load operator image - run: podman load -i operator-image.tar - - - name: Start bink cluster - run: make start-bink - - - name: Build update images - run: make build-update-image - - - name: Push operator image to bink registry - run: podman push --tls-verify=false bootc-operator:dev localhost:5000/bootc-operator:dev - - - name: Generate Helm chart - run: make helm - - - name: Install Helm charts - run: | - helm dependency build ./chart/bootc-operator - helm install bootc-operator ./chart/bootc-operator \ - --kubeconfig $(pwd)/kubeconfig-e2e \ - --create-namespace --namespace bootc-operator \ - --set image.repository=registry.cluster.local:5000/bootc-operator \ - --set image.tag=dev \ - --set image.pullPolicy=Always \ - --set-json 'controller.extraArgs=["--allow-insecure-registry"]' - - - name: Verify controller is running - run: | - kubectl --kubeconfig $(pwd)/kubeconfig-e2e -n bootc-operator \ - rollout status deployment/bootc-operator-controller --timeout=3m - kubectl --kubeconfig $(pwd)/kubeconfig-e2e -n bootc-operator \ - get pods - - - name: Run e2e smoke tests - run: make e2e RUN='TestCRDSmoke|TestControllerMembership' V=1 - - - name: Gather logs on failure - if: failure() - run: make gather-bink - - - name: Upload logs - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: helm-e2e-logs - path: _output/logs/ - if-no-files-found: ignore diff --git a/Makefile b/Makefile index 432ceb6..12ec7ed 100644 --- a/Makefile +++ b/Makefile @@ -17,13 +17,10 @@ BINK_NODE_DISK_IMAGE ?= ghcr.io/bootc-dev/bink/node:v$(DEFAULT_KUBE_MINOR)-fedor BINK_LOCAL_REGISTRY_NODE_IMAGE ?= registry.cluster.local:5000/node E2E_REGISTRY_USER ?= e2e-user E2E_REGISTRY_PASSWORD ?= e2e-password -# Released operator for upgrade testing (set to run TestOperatorUpgrade). -# RELEASED_OPERATOR_TAG: GitHub release tag to download install.yaml from. -# RELEASED_OPERATOR_IMG: container image to use (defaults to upstream, override for downstream). -# Example: RELEASED_OPERATOR_TAG=v0.1.0 -# RELEASED_OPERATOR_IMG=ghcr.io/bootc-dev/bootc-operator:v0.1.0 -RELEASED_OPERATOR_TAG ?= -RELEASED_OPERATOR_IMG ?= +# Released operator for upgrade testing (TestOperatorUpgrade). +# Override RELEASED_OPERATOR_IMG for downstream images. +RELEASED_OPERATOR_TAG ?= v0.2.1 +RELEASED_OPERATOR_IMG ?= ghcr.io/bootc-dev/bootc-operator:$(RELEASED_OPERATOR_TAG) IMG_BINK_RELEASED ?= registry.cluster.local:5000/bootc-operator-released:latest # YEAR defines the year value used for substituting the YEAR placeholder in the boilerplate header. YEAR ?= $(shell date +%Y) @@ -245,17 +242,19 @@ start-bink: seed-node-image ## Start a bink cluster (idempotent). kubectl --kubeconfig $(KUBECONFIG_BINK) wait --for=condition=Ready node/controller --timeout=5m .PHONY: deploy-bink -deploy-bink: start-bink build-update-image $(if $(RELEASED_OPERATOR_IMG),push-released-operator-image) kustomize ## Deploy to a bink cluster (requires: buildimg). +deploy-bink: start-bink build-update-image $(if $(RELEASED_OPERATOR_IMG),push-released-operator-image) helm ## Deploy to a bink cluster (requires: buildimg). podman push --tls-verify=false $(IMG) localhost:5000/bootc-operator-e2e:latest - # On re-deploy, restart the rollout to force a re-pull of the :latest tag. - # On fresh deploy, skip the restart -- the pod is already pulling the correct image. - @existed=$$(kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator get deploy bootc-operator-controller-manager -o name 2>/dev/null || true) && \ - $(MAKE) deploy KUBECONFIG=$(abspath $(KUBECONFIG_BINK)) IMG=$(IMG_BINK) \ - MANAGER_EXTRA_ARGS='"--allow-insecure-registry","--tag-resolution-interval=10s"' && \ - if [ -n "$$existed" ]; then \ - kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout restart deployment/bootc-operator-controller-manager; \ - fi - kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout status deployment/bootc-operator-controller-manager --timeout=3m + helm dependency build ./chart/bootc-operator + img="$(IMG_BINK)" && \ + helm upgrade --install bootc-operator ./chart/bootc-operator \ + --kubeconfig $(abspath $(KUBECONFIG_BINK)) \ + --create-namespace --namespace bootc-operator \ + --set-string image.repository="$${img%:*}" \ + --set-string image.tag="$${img##*:}" \ + --set-string image.pullPolicy=Always \ + --set-json 'controller.extraArgs=["--allow-insecure-registry","--tag-resolution-interval=10s"]' + kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout restart deployment/bootc-operator-controller + kubectl --kubeconfig $(KUBECONFIG_BINK) -n bootc-operator rollout status deployment/bootc-operator-controller --timeout=3m .PHONY: gather-bink gather-bink: ## Gather diagnostic logs from the bink cluster. diff --git a/test/e2e/bootcnode_test.go b/test/e2e/bootcnode_test.go index bb7667a..aa6fe40 100644 --- a/test/e2e/bootcnode_test.go +++ b/test/e2e/bootcnode_test.go @@ -961,7 +961,7 @@ func TestControllerRecovery(t *testing.T) { Should(poolAllUpdated(1, env.NodeImageUpdateDigest())) } -// scaleController scales the operator's controller-manager Deployment to the +// scaleController scales the operator's controller Deployment to the // given replica count and waits until its available replicas match. It is used // to simulate controller outages in recovery tests. func scaleController(t *testing.T, env *e2eutil.Env, ctx context.Context, replicas int32) { @@ -970,7 +970,7 @@ func scaleController(t *testing.T, env *e2eutil.Env, ctx context.Context, replic g := NewWithT(t) deployKey := client.ObjectKey{ Namespace: testutil.OperatorNamespaceName, - Name: "bootc-operator-controller-manager", + Name: "bootc-operator-controller", } var deploy appsv1.Deployment diff --git a/test/e2e/upgrade_test.go b/test/e2e/upgrade_test.go index 5458f65..9017248 100644 --- a/test/e2e/upgrade_test.go +++ b/test/e2e/upgrade_test.go @@ -3,14 +3,12 @@ package e2e import ( - "bytes" "context" - "fmt" - "io" - "net/http" + "encoding/json" "os" "os/exec" "path/filepath" + "strings" "testing" "time" @@ -19,18 +17,14 @@ import ( corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" - utilyaml "k8s.io/apimachinery/pkg/util/yaml" "sigs.k8s.io/controller-runtime/pkg/client" - sigsyaml "sigs.k8s.io/yaml" bootcv1alpha1 "github.com/bootc-dev/bootc-operator/api/v1alpha1" "github.com/bootc-dev/bootc-operator/test/e2e/e2eutil" testutil "github.com/bootc-dev/bootc-operator/test/util" ) -const ( - releaseManifestURL = "https://github.com/bootc-dev/bootc-operator/releases/download/%s/install.yaml" -) +const helmReleaseName = "bootc-operator" var operatorCRDNames = []string{ "bootcnodepools.node.bootc.dev", @@ -38,9 +32,9 @@ var operatorCRDNames = []string{ } // TestOperatorUpgrade installs the released version of the operator -// from its published manifest, verifies basic functionality, then -// upgrades by re-applying the current manifests (including CRDs) on -// top and verifies the operator still works. +// via Helm, verifies basic functionality, then upgrades by running +// helm upgrade with the current image and verifies the operator still +// works. func TestOperatorUpgrade(t *testing.T) { e2eutil.Providers(t, "bink") @@ -60,21 +54,29 @@ func TestOperatorUpgrade(t *testing.T) { env := e2eutil.New(t) ctx := context.Background() - // Capture the current operator image before we replace it. + repoRoot := findRepoRoot(t) + chartPath := filepath.Join(repoRoot, "chart", "bootc-operator") + + // Capture the current operator image and extra args before we + // replace it. var origDeploy appsv1.Deployment g.Expect(env.Client.Get(ctx, operatorDeployKey(), &origDeploy)).To(Succeed()) currentImg := containerImage(t, origDeploy.Spec.Template.Spec.Containers, "manager") - currentArgs := containerArgs(t, origDeploy.Spec.Template.Spec.Containers, "manager") + extraArgs := controllerExtraArgs(t, origDeploy.Spec.Template.Spec.Containers) t.Logf("Current operator image: %s", currentImg) t.Logf("Released operator tag: %s, image: %s", releaseTag, releasedImg) - // Phase 1: Delete the current operator and install the released - // version from its published manifest. - installReleasedOperator(t, g, ctx, env, releaseTag, releasedImg) + // Phase 1: Uninstall the current operator and re-install with + // the released image via Helm. + uninstallOperator(t, g, ctx, env) + helmInstallOperator(t, chartPath, releasedImg, extraArgs) + waitForOperatorReady(t, g, ctx, env.Client) + + t.Logf("Released operator %s installed via Helm", releaseTag) t.Cleanup(func() { t.Logf("Restoring operator to current version...") - applyCurrentManifests(t, currentImg, currentArgs) + helmUpgradeOperator(t, chartPath, currentImg, extraArgs) waitForOperatorReady(t, g, ctx, env.Client) t.Logf("Operator restored") }) @@ -89,11 +91,11 @@ func TestOperatorUpgrade(t *testing.T) { t.Logf("Node %q is Idle with released operator", nodeName) - // Phase 3: Upgrade by applying the current manifests on top. - applyCurrentManifests(t, currentImg, currentArgs) + // Phase 3: Upgrade via helm upgrade to the current version. + helmUpgradeOperator(t, chartPath, currentImg, extraArgs) waitForOperatorReady(t, g, ctx, env.Client) - t.Logf("Upgraded operator to current version via manifest apply") + t.Logf("Upgraded operator to current version via Helm") // Verify the pre-existing pool and node survived the upgrade. testutil.WaitForNodeIdle(t, g, ctx, env.Client, nodeName, 3*time.Minute) @@ -124,43 +126,18 @@ func TestOperatorUpgrade(t *testing.T) { Should(poolAllUpdated(1, env.NodeImageUpdateDigest())) } -// installReleasedOperator deletes the current operator, downloads the -// released install manifest, patches the image reference, and applies -// it. -func installReleasedOperator( +// uninstallOperator removes the operator via Helm uninstall, then +// deletes CRDs (which Helm does not manage on uninstall). Custom +// resources are deleted first while the operator is still running so +// it can handle finalizer removal. +func uninstallOperator( t *testing.T, g Gomega, ctx context.Context, env *e2eutil.Env, - releaseTag, releasedImg string, ) { t.Helper() - deleteCurrentOperator(t, g, ctx, env) - - manifest := downloadReleaseManifest(t, releaseTag) - patchedManifest := patchManifestImage(t, manifest, releasedImg, nil) - - kubectlApply(t, patchedManifest) - waitForOperatorReady(t, g, ctx, env.Client) - - t.Logf("Released operator %s installed from manifest", releaseTag) -} - -// deleteCurrentOperator deletes custom resources (while the operator -// is still running so it can handle finalizer removal), then removes -// the operator Deployment, DaemonSet, and CRDs. -func deleteCurrentOperator( - t *testing.T, - g Gomega, - ctx context.Context, - env *e2eutil.Env, -) { - t.Helper() - - // Delete CRs first while the operator is running so it can - // remove finalizers. If CRs with finalizers remain when the - // controller is gone, CRD deletion hangs indefinitely. t.Logf("Deleting custom resources...") g.Expect(env.Client.DeleteAllOf(ctx, &bootcv1alpha1.BootcNodePool{})).To(Succeed()) g.Expect(env.Client.DeleteAllOf(ctx, &bootcv1alpha1.BootcNode{})).To(Succeed()) @@ -175,28 +152,16 @@ func deleteCurrentOperator( }).WithTimeout(2*time.Minute).Should(Succeed(), "expected all custom resources to be deleted") - var deploy appsv1.Deployment - g.Expect(env.Client.Get(ctx, operatorDeployKey(), &deploy)).To(Succeed()) - var ds appsv1.DaemonSet - g.Expect(env.Client.Get(ctx, operatorDaemonSetKey(), &ds)).To(Succeed()) - - t.Logf("Deleting operator deployment, daemonset, and CRDs...") - g.Expect(env.Client.Delete(ctx, &deploy)).To(Succeed()) - g.Expect(env.Client.Delete(ctx, &ds)).To(Succeed()) - - for _, crdName := range operatorCRDNames { - var crd apiextensionsv1.CustomResourceDefinition - if err := env.Client.Get(ctx, client.ObjectKey{Name: crdName}, &crd); err == nil { - g.Expect(env.Client.Delete(ctx, &crd)).To(Succeed()) - } - } + t.Logf("Uninstalling Helm release...") + helmRun(t, "uninstall", helmReleaseName, + "--namespace", testutil.OperatorNamespaceName) g.Eventually(func(g Gomega) { var d appsv1.Deployment err := env.Client.Get(ctx, operatorDeployKey(), &d) g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) - var dset appsv1.DaemonSet - err = env.Client.Get(ctx, operatorDaemonSetKey(), &dset) + var ds appsv1.DaemonSet + err = env.Client.Get(ctx, operatorDaemonSetKey(), &ds) g.Expect(apierrors.IsNotFound(err)).To(BeTrue()) for _, crdName := range operatorCRDNames { var crd apiextensionsv1.CustomResourceDefinition @@ -206,207 +171,99 @@ func deleteCurrentOperator( }).WithTimeout(2*time.Minute).Should(Succeed(), "expected operator resources to be deleted") - t.Logf("Operator deployment, daemonset, and CRDs deleted") + t.Logf("Operator uninstalled") } -// downloadReleaseManifest fetches install.yaml from a GitHub release. -func downloadReleaseManifest(t *testing.T, tag string) []byte { +// helmInstallOperator installs the operator via helm install. +func helmInstallOperator(t *testing.T, chartPath, img string, extraArgs []string) { t.Helper() - - url := fmt.Sprintf(releaseManifestURL, tag) - t.Logf("Downloading release manifest from %s", url) - - resp, err := http.Get(url) //nolint:gosec - if err != nil { - t.Fatalf("downloading release manifest: %v", err) - } - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode != http.StatusOK { - t.Fatalf("downloading release manifest: HTTP %d", resp.StatusCode) - } - - data, err := io.ReadAll(resp.Body) - if err != nil { - t.Fatalf("reading release manifest body: %v", err) + args := []string{ + "install", helmReleaseName, chartPath, + "--namespace", testutil.OperatorNamespaceName, + "--create-namespace", } - - return data + args = append(args, helmImageArgs(t, img, extraArgs)...) + helmRun(t, args...) } -// patchManifestImage replaces container image references in -// Deployment (manager) and DaemonSet (daemon) documents. -func patchManifestImage( - t *testing.T, - manifest []byte, - img string, - managerArgs []string, -) []byte { +// helmUpgradeOperator upgrades the operator via helm upgrade. +func helmUpgradeOperator(t *testing.T, chartPath, img string, extraArgs []string) { t.Helper() - - var out bytes.Buffer - reader := utilyaml.NewDocumentDecoder(io.NopCloser(bytes.NewReader(manifest))) - defer func() { _ = reader.Close() }() - - first := true - for { - buf := make([]byte, len(manifest)+256) - n, err := reader.Read(buf) - if err != nil { - if err == io.EOF { - break - } - t.Fatalf("reading YAML document: %v", err) - } - doc := buf[:n] - - var obj map[string]interface{} - if err := sigsyaml.Unmarshal(doc, &obj); err != nil { - t.Fatalf("unmarshaling YAML document: %v", err) - } - - kind, _ := obj["kind"].(string) - switch kind { - case "Deployment": - setContainerImage(t, obj, "manager", img) - if len(managerArgs) > 0 { - setContainerArgs(t, obj, "manager", managerArgs) - } - case "DaemonSet": - setContainerImage(t, obj, "daemon", img) - } - - patched, err := sigsyaml.Marshal(obj) - if err != nil { - t.Fatalf("marshaling patched %s: %v", kind, err) - } - - if !first { - out.WriteString("---\n") - } - out.Write(patched) - first = false + args := []string{ + "upgrade", helmReleaseName, chartPath, + "--namespace", testutil.OperatorNamespaceName, } - - return out.Bytes() + args = append(args, helmImageArgs(t, img, extraArgs)...) + helmRun(t, args...) } -// setContainerImage patches the image field of a named container in a -// Deployment or DaemonSet manifest map. -func setContainerImage(t *testing.T, obj map[string]interface{}, containerName, img string) { +// helmImageArgs returns the --set flags for image and controller args. +func helmImageArgs(t *testing.T, img string, extraArgs []string) []string { t.Helper() - - spec, _ := obj["spec"].(map[string]interface{}) - template, _ := spec["template"].(map[string]interface{}) - podSpec, _ := template["spec"].(map[string]interface{}) - containers, _ := podSpec["containers"].([]interface{}) - - for _, c := range containers { - container, _ := c.(map[string]interface{}) - if name, _ := container["name"].(string); name == containerName { - container["image"] = img - return - } + repo, tag := splitImage(img) + args := []string{ + "--set-string", "image.repository=" + repo, + "--set-string", "image.tag=" + tag, + "--set-string", "image.pullPolicy=Always", } - t.Fatalf("container %q not found in %s", containerName, obj["kind"]) -} - -// setContainerArgs replaces the args of a named container in a -// Deployment or DaemonSet manifest map. -func setContainerArgs( - t *testing.T, - obj map[string]interface{}, - containerName string, - args []string, -) { - t.Helper() - - spec, _ := obj["spec"].(map[string]interface{}) - template, _ := spec["template"].(map[string]interface{}) - podSpec, _ := template["spec"].(map[string]interface{}) - containers, _ := podSpec["containers"].([]interface{}) - - for _, c := range containers { - container, _ := c.(map[string]interface{}) - if name, _ := container["name"].(string); name == containerName { - ifaceArgs := make([]interface{}, len(args)) - for i, a := range args { - ifaceArgs[i] = a - } - container["args"] = ifaceArgs - return + if len(extraArgs) > 0 { + j, err := json.Marshal(extraArgs) + if err != nil { + t.Fatalf("marshaling extraArgs: %v", err) } + args = append(args, "--set-json", "controller.extraArgs="+string(j)) } - t.Fatalf("container %q not found in %s", containerName, obj["kind"]) + return args } -// kubectlApply applies the given manifest bytes via kubectl. -func kubectlApply(t *testing.T, manifest []byte) { +// helmRun executes a helm command, adding --kubeconfig if set. +func helmRun(t *testing.T, args ...string) { t.Helper() - kubeconfigPath := os.Getenv("KUBECONFIG") - args := []string{"apply", "--server-side", "-f", "-"} - if kubeconfigPath != "" { - args = append([]string{"--kubeconfig", kubeconfigPath}, args...) + subcmd := args[0] + if kc := os.Getenv("KUBECONFIG"); kc != "" { + args = append([]string{"--kubeconfig", kc}, args...) } - cmd := exec.Command("kubectl", args...) - cmd.Stdin = bytes.NewReader(manifest) - + cmd := exec.Command("helm", args...) out, err := cmd.CombinedOutput() if err != nil { - t.Fatalf("kubectl apply failed: %v\n%s", err, out) + t.Fatalf("helm %s failed: %v\n%s", subcmd, err, out) } - - t.Logf("kubectl apply:\n%s", out) + t.Logf("helm %s:\n%s", subcmd, out) } -// applyCurrentManifests runs the equivalent of "make deploy" for the -// current version: kustomize build + image patch + kubectl apply. -func applyCurrentManifests( - t *testing.T, - img string, - managerArgs []string, -) { - t.Helper() - - repoRoot := findRepoRoot(t) - - kustomize := filepath.Join(repoRoot, "bin", "kustomize") - if _, err := os.Stat(kustomize); err != nil { - kustomize = "kustomize" +// splitImage splits a container image reference into repository and tag. +func splitImage(ref string) (repo, tag string) { + lastSlash := strings.LastIndex(ref, "/") + lastColon := strings.LastIndex(ref, ":") + if lastColon > lastSlash { + return ref[:lastColon], ref[lastColon+1:] } - - cmd := exec.Command(kustomize, "build", filepath.Join(repoRoot, "config", "default")) - manifest, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("kustomize build failed: %v\n%s", err, manifest) - } - - manifest = patchManifestImage(t, manifest, img, managerArgs) - kubectlApply(t, manifest) + return ref, "latest" } -// findRepoRoot walks up from the test directory to find the repository -// root (containing go.mod). -func findRepoRoot(t *testing.T) string { +// controllerExtraArgs returns the controller's extra arguments, +// filtering out the flags that the Helm chart always includes. +func controllerExtraArgs(t *testing.T, containers []corev1.Container) []string { t.Helper() - - dir, err := os.Getwd() - if err != nil { - t.Fatalf("getting working directory: %v", err) - } - - for { - if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { - return dir + for _, c := range containers { + if c.Name != "manager" { + continue } - parent := filepath.Dir(dir) - if parent == dir { - t.Fatal("could not find repository root (go.mod)") + var extra []string + for _, arg := range c.Args { + switch { + case arg == "--leader-elect", + strings.HasPrefix(arg, "--health-probe-bind-address="): + default: + extra = append(extra, arg) + } } - dir = parent + return extra } + t.Fatalf("container %q not found in pod spec", "manager") + return nil } // waitForOperatorReady polls until the operator deployment has one available and up-to-date replica. @@ -431,7 +288,7 @@ func waitForOperatorReady( func operatorDeployKey() client.ObjectKey { return client.ObjectKey{ Namespace: testutil.OperatorNamespaceName, - Name: "bootc-operator-controller-manager", + Name: "bootc-operator-controller", } } @@ -455,13 +312,24 @@ func containerImage(t *testing.T, containers []corev1.Container, name string) st return "" } -func containerArgs(t *testing.T, containers []corev1.Container, name string) []string { +// findRepoRoot walks up from the test directory to find the repository +// root (containing go.mod). +func findRepoRoot(t *testing.T) string { t.Helper() - for _, c := range containers { - if c.Name == name { - return c.Args + + dir, err := os.Getwd() + if err != nil { + t.Fatalf("getting working directory: %v", err) + } + + for { + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + return dir } + parent := filepath.Dir(dir) + if parent == dir { + t.Fatal("could not find repository root (go.mod)") + } + dir = parent } - t.Fatalf("container %q not found in pod spec", name) - return nil }