v1.40.0

Upgrade OPM version to v1.55.0 in the Makefile

Update the OPM version in your Makefile to v1.55.0:

-const opmVersion = "v1.23.0"
+const opmVersion = "v1.55.0"
-       curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\
+       curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.55.0/$${OS}-$${ARCH}-opm ;\

See #6953 for more details.

Add a devcontainer for Go-based operators

Create the devcontainer configuration in the root of the repository under .devcontainer.

  1. Create a new directory called .devcontainer in the root of your project.
  2. Copy the contents of the testdata/go/v4/memcached-operator/.devcontainer available in the Operator SDK repository for the tag release v1.40.0.

See #6928 for more details.

Add new GitHub actions for Go-based operators

Add the actions configuration in the .github/workflows directory. The new actions are:

  • lint.yaml: Lint the code using golangci-lint
  • test.yaml: Run the tests using go test
  • test-e2e.yaml: Run the e2e tests using go test You can obtain this configuration to be added to your project by looking at the files available in testdata/go/v4/memcached-operator/.github/workflows for this release. testdata/go/v4/memcached-operator/.github/workflows

See #6928 for more details.

Update your project to properly support TLS certificates for webhooks and metrics server

  1. Update the main.go file in your project to support TLS certificates for webhooks and metrics server.
  • Add the new flag definitions to accept custom certificate file paths and names:

    func main() {
        ...
        var metricsCertPath, metricsCertName, metricsCertKey string
        var webhookCertPath, webhookCertName, webhookCertKey string
        ...
        flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.")
        flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.")
        flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.")
        flag.StringVar(&metricsCertPath, "metrics-cert-path", "", "The directory that contains the metrics server certificate.")
        flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.")
        flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.")
    
  • After this conditional check:

    if !enableHTTP2 {
        tlsOpts = append(tlsOpts, disableHTTP2)
    }
    

    Insert the following code to configure certificate watchers for webhooks and metrics:

    var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher
    webhookTLSOpts := tlsOpts
    
    if len(webhookCertPath) > 0 {
        setupLog.Info("Initializing webhook certificate watcher using provided certificates",
            "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey)
    
        var err error
        webhookCertWatcher, err = certwatcher.New(
            filepath.Join(webhookCertPath, webhookCertName),
            filepath.Join(webhookCertPath, webhookCertKey),
        )
        if err != nil {
            setupLog.Error(err, "Failed to initialize webhook certificate watcher")
            os.Exit(1)
        }
    
        webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) {
            config.GetCertificate = webhookCertWatcher.GetCertificate
        })
    }
    
  • Update the webhook server TLS options:

    Replace:

    TLSOpts: tlsOpts,
    

    With:

    TLSOpts: webhookTLSOpts,
    
  • Before initializing the manager, configure the metrics certificate watcher if metrics certs are provided:

    if len(metricsCertPath) > 0 {
        setupLog.Info("Initializing metrics certificate watcher using provided certificates",
            "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey)
    
        var err error
        metricsCertWatcher, err = certwatcher.New(
            filepath.Join(metricsCertPath, metricsCertName),
            filepath.Join(metricsCertPath, metricsCertKey),
        )
        if err != nil {
            setupLog.Error(err, "Failed to initialize metrics certificate watcher")
            os.Exit(1)
        }
    
        metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) {
            config.GetCertificate = metricsCertWatcher.GetCertificate
        })
    }
    
    mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
        ...
    })
    
  • Before calling AddHealthzCheck, ensure the certificate watchers are registered with the manager:

    if metricsCertWatcher != nil {
        setupLog.Info("Adding metrics certificate watcher to manager")
        if err := mgr.Add(metricsCertWatcher); err != nil {
            setupLog.Error(err, "Unable to add metrics certificate watcher to manager")
            os.Exit(1)
        }
    }
    
    if webhookCertWatcher != nil {
        setupLog.Info("Adding webhook certificate watcher to manager")
        if err := mgr.Add(webhookCertWatcher); err != nil {
            setupLog.Error(err, "Unable to add webhook certificate watcher to manager")
            os.Exit(1)
        }
    }
    
    if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
        setupLog.Error(err, "Unable to set up health check")
        os.Exit(1)
    }
    

Note that you can use as reference the main.go file available in the Operator SDK repository for the tag release v1.40.0 to see how the code should look like, see: testdata/go/v4/memcached-operator/cmd/main.go

  1. Add the new certificates in the config/certmanager directory:
  1. Update the config/default/kustomization.yaml to allow work with the new options:

Under patches ensure that you have:

patches:
  ...
  # Uncomment the patches line if you enable Metrics and CertManager
  # [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.
  # This patch will protect the metrics with certManager self-signed certs.
  - path: cert_metrics_manager_patch.yaml
    target:
      kind: Deployment

  # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
  # crd/kustomization.yaml
  - path: manager_webhook_patch.yaml
    target:
      kind: Deployment
...

Under the replacements section, replace:

  - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
    kind: Certificate
    group: cert-manager.io
    version: v1
    name: serving-cert # this name should match the one in certificate.yaml
    fieldPath: .metadata.namespace # namespace of the certificate CR
  targets:
    - select:
        kind: ValidatingWebhookConfiguration
      fieldPaths:
        - .metadata.annotations.[cert-manager.io/inject-ca-from]
      options:
        delimiter: '/'
        index: 0
        create: true
    - select:
        kind: MutatingWebhookConfiguration
      fieldPaths:
        - .metadata.annotations.[cert-manager.io/inject-ca-from]
      options:
        delimiter: '/'
        index: 0
        create: true
    - select:
        kind: CustomResourceDefinition
      fieldPaths:
        - .metadata.annotations.[cert-manager.io/inject-ca-from]
      options:
        delimiter: '/'
        index: 0
        create: true

With: the code from Kubebuilder samples testdata/project-v4/config/default/kustomization.yaml

NOTE: You can see the complete file in the repository for the tag release v1.40.0: testdata/go/v4/memcached-operator/config/default/kustomization.yaml

  1. Add the new file to allow patch the certs for the metrics: testdata/go/v4/memcached-operator/config/default/cert_metrics_manager_patch.yaml
  2. Replace the content of config/default/manager_webhook_patch.yaml with: testdata/go/v4/memcached-operator/config/default/config/default/manager_webhook_patch.yaml
  3. Update the config/manager/manager.yaml to include the ports and volumes to allow the patch to work properly:
...
         env:
         - name: MEMCACHED_IMAGE
           value: memcached:1.4.36-alpine
+        ports: []
...
...
           requests:
             cpu: 10m
             memory: 64Mi
+        volumeMounts: []
+      volumes: []
  serviceAccountName: controller-manager
  terminationGracePeriodSeconds: 10
...

See #6928 for more details.

Update your project to properly support TLS for Prometheus scraping

Changes required under the hood config/prometheus/

    1. Update the config/prometheus/kutomization.yaml add at the bottom:
      # [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus
      # to securely reference certificates created and managed by cert-manager.
      # Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml
      # to mount the "metrics-server-cert" secret in the Manager Deployment.
      #patches:
      #  - path: monitor_tls_patch.yaml
      #    target:
      #      kind: ServiceMonitor

See #6928 for more details.

Update your project to properly support CA injection for CRDs with conversion webhooks

Changes required under the hood config/crd/

    1. Update the config/crd/kustomization.yaml for the file to include the new marker +kubebuilder:scaffold:crdkustomizewebhookpatch for the tool be able to inject the path for any new CRD that is created with the --conversion flag.
    1. Ensure that under the patches section you have only patches for the CRDs which are created with the --conversion flag.
    1. Remove the files prefixed with cainjection_<kind>.yaml. You should have only the files prefixed with webhookpatch_<kind>.yaml for the CRDs that have the --conversion flag. (example)

Changes required under the hood config/default/

    1. Update the config/default/kustomization.yaml for the file to include the new marker +kubebuilder:scaffold:crdkustomizecainjectionns for the tool be able to inject for any new CRD that is created with the --conversion flag as well to have commented the default replacement. For further information see an example in Kubebuilder testdata samples testdata/project-v4/config/default/kustomization.yaml.

NOTE: You can see the complete file in the repository for the tag release v1.40.0: testdata/go/v4/memcached-operator/config/default/kustomization.yaml

See #6928 for more details.

Use .Named("<Kind>") in SetupWithManager for controller registration

To improve clarity and avoid naming collisions in multi-group Go-based operator projects, each controller’s SetupWithManager call now includes an explicit .Named("<Kind>") declaration.

Example change:

func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
  return ctrl.NewControllerManagedBy(mgr).
    For(&appsv1.Deployment{}).
    Named("apps-deployment").
    Complete(r)
  }

This ensures controller names are unique and consistent across different APIs in multi-group scenarios, which improves controller lifecycle management and logging.

See #6928 for more details.

ENVTEST version automation and improved test binary discovery

The SDK now automates the setup of ENVTEST for Go-based operators by dynamically deriving the required versions from go.mod rather than requiring manual updates in the Makefile.

  1. Update the Makefile:
  • The variables ENVTEST_VERSION and ENVTEST_K8S_VERSION are now computed using go list:
    ENVTEST_VERSION := $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}')
    ENVTEST_K8S_VERSION := $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}')
    
  • A new target setup-envtest was introduced to automatically install the binaries:
    .PHONY: setup-envtest
    setup-envtest:
      @$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \
        echo "Error setting up envtest"; exit 1; }
    
  • The test target now depends on setup-envtest to ensure binaries are ready before running tests.
  1. Update the suite_test.go files for controllers and webhooks: In each internal/controller/suite_test.go and internal/webhook/<version>/webhook/suite_test.go file:
  • A new helper function getFirstFoundEnvTestBinaryDir() was added:
    func getFirstFoundEnvTestBinaryDir() string {
      basePath := filepath.Join("..", "..", "..", "bin", "k8s")
      entries, err := os.ReadDir(basePath)
      if err != nil {
          logf.Log.Error(err, "Failed to read directory", "path", basePath)
          return ""
      }
      for _, entry := range entries {
          if entry.IsDir() {
              return filepath.Join(basePath, entry.Name())
          }
      }
      return ""
    }
    
  • testEnv.BinaryAssetsDirectory now uses this helper to locate installed ENVTEST binaries:
    testEnv = &envtest.Environment{
      BinaryAssetsDirectory: getFirstFoundEnvTestBinaryDir(),
      ...
    }
    

See #6928 for more details.

Replace exportloopref with copyloopvar in .golangci.yaml

The exportloopref linter has been deprecated in recent versions of GolangCI-Lint. It is now replaced with the more accurate and actively maintained copyloopvar linter.

Update your .golangci.yaml file by replacing:

- exportloopref

With:

- copyloopvar

See #6928 for more details.

Add lint-config target to Makefile to verify linter configuration

The target uses the config verify subcommand provided by golangci-lint:

.PHONY: lint-config
lint-config: golangci-lint ## Verify golangci-lint linter configuration
	$(GOLANGCI_LINT) config verify

See #6928 for more details.

Upgrade to Go 1.23 and Kubernetes v0.32.1 dependencies

  1. Update your go.mod to reflect the new versions:
go 1.23

require (
  github.com/onsi/ginkgo/v2 v2.22.0
  github.com/onsi/gomega v1.36.1
  k8s.io/api v0.32.1
  k8s.io/apimachinery v0.32.1
  k8s.io/client-go v0.32.1
  k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
  sigs.k8s.io/controller-runtime v0.20.4
)
  1. Update the Go toolchain in your Dockerfile to match:
FROM golang:1.23 AS builder

See #6928 for more details.

You must change your webhooks implementation to be able to use controller-runtime v0.20.0+

If you have no webhooks, you can skip this migration. Otherwise, ensure that you check the described steps to update your project in the release notes of Kubebuilder v4.3.0 release: https://github.com/kubernetes-sigs/kubebuilder/releases/tag/v4.3.0

See #6928 for more details.

Add app.kubernetes.io/name label to your manifests

The Operator SDK now adds the app.kubernetes.io/name label to scaffolded Kubernetes manifests such as Deployments, Services, and RBAC resources. This label aligns with Kubernetes labeling conventions and improves compatibility with observability and automation tools.

If upgrading from a previous version, you may want to add the following label manually to your existing manifests:

metadata:
  labels:
    app.kubernetes.io/name: <your-app-name>

See #6928 for more details.

With you wish manually add those roles to your project

See the permissions and RBAC generate as an example to know how properly create those files for each CRD you have in your project by looking at the sample in the repository for the tag release v1.40.0: testdata/go/v4/memcached-operator/config/rbac

See #6928 for more details.

With you wish manually add those roles to your project

See the permissions and RBAC generate as an example to know how properly create those files for each CRD you have in your project by looking at the sample in the repository for the tag release v1.40.0: testdata/go/v4/memcached-operator/config/rbac

See #6928 for more details.

Last modified June 2, 2025: Release v1.40.0 (#6956) (c975e3a0)