Upgrade to Operator SDK 1.41.1 (#211)

* Add missing improvements from Operator SDK 1.34.1

These were not mentioned in the upgrade documentation for version 1.34.x (https://sdk.operatorframework.io/docs/upgrading-sdk-version/v1.34.0/), but I've found them by compating the release with the previous one (https://github.com/operator-framework/operator-sdk/compare/v1.33.0...v1.34.1).

* Upgrade to Operator SDK 1.36.0

Source of upgrade steps: https://sdk.operatorframework.io/docs/upgrading-sdk-version/v1.36.0/
Key differences:
- Go packages `k8s.io/*` are already at a version higher than the one in the upgrade.
- `ENVTEST_K8S_VERSION` is at a version higher than the one in the upgrade
- We didn't have the golangci-lint make command before, thus we only needed to add things.

* Upgrade to Operator SDK 1.38.0

Source of upgrade steps: https://sdk.operatorframework.io/docs/upgrading-sdk-version/v1.38.0/

* Upgrade to Operator SDK 1.39.0

Source of upgrade steps: https://sdk.operatorframework.io/docs/upgrading-sdk-version/v1.39.0/

* Upgrade to Operator SDK 1.40.0

Source of upgrade steps: https://sdk.operatorframework.io/docs/upgrading-sdk-version/v1.40.0/

I didn't do the "Add app.kubernetes.io/name label to your manifests" since it seems that we have it already, and it's customized.

* Address lint errors

* Update golangci-lint version used to support Go 1.24

* Improve workflows

- Make workflow targets more specific.
- Make build workflow only build (i.e. remove test part of it).
- Rearrange steps and improve naming for build workflow.

* Add back deleted test

Initially the test has been removed due to lint saying that it was duplicate code, but it falsely errored since the values are different.

* Improve code and add missing upgrade pieces

* Upgrade to Operator SDK 1.41.1

Source of upgrade steps: https://sdk.operatorframework.io/docs/upgrading-sdk-version/v1.41.0/

Upgrading to 1.41.1 from 1.40.0 doesn't have any migration steps.

Key elements:
- Upgrade to golangci-lint v2
- Made the manifests using the updated controller tools

* Address linter errors

golanci-lint v2 seems to be more robust than the previous one, which is beneficial. Thus, we address the linter errors thrown by v2 and improve our code even further.

* Add Makefile improvements

These were brought in by comparing the Makefile of a freshly created operator using the latest operator-sdk with ours.

* Add missing default kustomization for 1.40.0 upgrade

* Bring default kustomization to latest version

This is done by putting the file's content from a newly-generated operator.

* Switch metrics-bind-address default value back to 8080

This ensures that the upgrade is backwards-compatible.

* Add webhook-related scaffolding

This enables us to easily add support for webhooks by running `operator-sdk create webhook` whenever we want to add them.

* Fix typo
This commit is contained in:
Eduard Filip
2025-07-14 19:32:30 +02:00
committed by GitHub
parent 54eed0c81c
commit cabc020cc6
64 changed files with 1317 additions and 686 deletions

View File

@@ -59,9 +59,9 @@ type DeploymentReconciler struct {
OpAnnotationRegExp *regexp.Regexp
}
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=apps,resources=deployments/finalizers,verbs=update
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=deployments/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=apps,resources=deployments/finalizers,verbs=update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
@@ -91,12 +91,12 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return ctrl.Result{}, nil
}
//If the deployment is not being deleted
if deployment.ObjectMeta.DeletionTimestamp.IsZero() {
// If the deployment is not being deleted
if deployment.DeletionTimestamp.IsZero() {
// Adds a finalizer to the deployment if one does not exist.
// This is so we can handle cleanup of associated secrets properly
if !utils.ContainsString(deployment.ObjectMeta.Finalizers, finalizer) {
deployment.ObjectMeta.Finalizers = append(deployment.ObjectMeta.Finalizers, finalizer)
if !utils.ContainsString(deployment.Finalizers, finalizer) {
deployment.Finalizers = append(deployment.Finalizers, finalizer)
if err = r.Update(ctx, deployment); err != nil {
return reconcile.Result{}, err
}
@@ -114,7 +114,7 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
}
// The deployment has been marked for deletion. If the one password
// finalizer is found there are cleanup tasks to perform
if utils.ContainsString(deployment.ObjectMeta.Finalizers, finalizer) {
if utils.ContainsString(deployment.Finalizers, finalizer) {
secretName := annotations[op.NameAnnotation]
if err = r.cleanupKubernetesSecretForDeployment(ctx, secretName, deployment); err != nil {
@@ -133,13 +133,14 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request)
func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&appsv1.Deployment{}).
Named("onepassword-deployment").
Complete(r)
}
func (r *DeploymentReconciler) cleanupKubernetesSecretForDeployment(ctx context.Context, secretName string, deletedDeployment *appsv1.Deployment) error {
kubernetesSecret := &corev1.Secret{}
kubernetesSecret.ObjectMeta.Name = secretName
kubernetesSecret.ObjectMeta.Namespace = deletedDeployment.Namespace
kubernetesSecret.Name = secretName
kubernetesSecret.Namespace = deletedDeployment.Namespace
if len(secretName) == 0 {
return nil
@@ -185,7 +186,7 @@ func (r *DeploymentReconciler) areMultipleDeploymentsUsingSecret(ctx context.Con
}
func (r *DeploymentReconciler) removeOnePasswordFinalizerFromDeployment(ctx context.Context, deployment *appsv1.Deployment) error {
deployment.ObjectMeta.Finalizers = utils.RemoveString(deployment.ObjectMeta.Finalizers, finalizer)
deployment.Finalizers = utils.RemoveString(deployment.Finalizers, finalizer)
return r.Update(ctx, deployment)
}

View File

@@ -82,10 +82,7 @@ var _ = Describe("Deployment controller", func() {
time.Sleep(time.Millisecond * 100)
Eventually(func() bool {
err := k8sClient.Get(ctx, secretKey, createdSecret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(createdSecret.Data).Should(Equal(item1.SecretData))
}
@@ -190,10 +187,7 @@ var _ = Describe("Deployment controller", func() {
updatedSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, secretKey, updatedSecret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(updatedSecret.Data).Should(Equal(item2.SecretData))
})
@@ -247,10 +241,7 @@ var _ = Describe("Deployment controller", func() {
updatedSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, secretKey, updatedSecret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(updatedSecret.Data).Should(Equal(item1.SecretData))
})

View File

@@ -57,18 +57,18 @@ type OnePasswordItemReconciler struct {
OpClient opclient.Client
}
//+kubebuilder:rbac:groups=onepassword.com,resources=onepassworditems,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=onepassword.com,resources=onepassworditems/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=onepassword.com,resources=onepassworditems/finalizers,verbs=update
// +kubebuilder:rbac:groups=onepassword.com,resources=onepassworditems,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=onepassword.com,resources=onepassworditems/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=onepassword.com,resources=onepassworditems/finalizers,verbs=update
//+kubebuilder:rbac:groups="",resources=pods,verbs=get
//+kubebuilder:rbac:groups="",resources=pods;services;services/finalizers;endpoints;persistentvolumeclaims;events;configmaps;secrets;namespaces,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=daemonsets;deployments;replicasets;statefulsets,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=apps,resources=replicasets;deployments,verbs=get
//+kubebuilder:rbac:groups=apps,resourceNames=onepassword-connect-operator,resources=deployments/finalizers,verbs=update
//+kubebuilder:rbac:groups=onepassword.com,resources=*,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create
//+kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;create;update
// +kubebuilder:rbac:groups="",resources=pods,verbs=get
// +kubebuilder:rbac:groups="",resources=pods;services;services/finalizers;endpoints;persistentvolumeclaims;events;configmaps;secrets;namespaces,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=daemonsets;deployments;replicasets;statefulsets,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=replicasets;deployments,verbs=get
// +kubebuilder:rbac:groups=apps,resourceNames=onepassword-connect-operator,resources=deployments/finalizers,verbs=update
// +kubebuilder:rbac:groups=onepassword.com,resources=*,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=monitoring.coreos.com,resources=servicemonitors,verbs=get;create
// +kubebuilder:rbac:groups=coordination.k8s.io,resources=leases,verbs=get;list;create;update
// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
@@ -93,11 +93,11 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ
}
// If the deployment is not being deleted
if onepassworditem.ObjectMeta.DeletionTimestamp.IsZero() {
if onepassworditem.DeletionTimestamp.IsZero() {
// Adds a finalizer to the deployment if one does not exist.
// This is so we can handle cleanup of associated secrets properly
if !utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) {
onepassworditem.ObjectMeta.Finalizers = append(onepassworditem.ObjectMeta.Finalizers, finalizer)
if !utils.ContainsString(onepassworditem.Finalizers, finalizer) {
onepassworditem.Finalizers = append(onepassworditem.Finalizers, finalizer)
if err = r.Update(ctx, onepassworditem); err != nil {
return ctrl.Result{}, err
}
@@ -117,7 +117,7 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ
return ctrl.Result{}, err
}
// If one password finalizer exists then we must cleanup associated secrets
if utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) {
if utils.ContainsString(onepassworditem.Finalizers, finalizer) {
// Delete associated kubernetes secret
if err = r.cleanupKubernetesSecret(ctx, onepassworditem); err != nil {
@@ -125,7 +125,7 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ
}
// Remove finalizer now that cleanup is complete
if err = r.removeFinalizer(ctx, onepassworditem); err != nil {
if err = r.removeOnePasswordFinalizerFromOnePasswordItem(ctx, onepassworditem); err != nil {
return ctrl.Result{}, err
}
}
@@ -136,21 +136,14 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ
func (r *OnePasswordItemReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&onepasswordv1.OnePasswordItem{}).
Named("onepassworditem").
Complete(r)
}
func (r *OnePasswordItemReconciler) removeFinalizer(ctx context.Context, onePasswordItem *onepasswordv1.OnePasswordItem) error {
onePasswordItem.ObjectMeta.Finalizers = utils.RemoveString(onePasswordItem.ObjectMeta.Finalizers, finalizer)
if err := r.Update(ctx, onePasswordItem); err != nil {
return err
}
return nil
}
func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(ctx context.Context, onePasswordItem *onepasswordv1.OnePasswordItem) error {
kubernetesSecret := &corev1.Secret{}
kubernetesSecret.ObjectMeta.Name = onePasswordItem.Name
kubernetesSecret.ObjectMeta.Namespace = onePasswordItem.Namespace
kubernetesSecret.Name = onePasswordItem.Name
kubernetesSecret.Namespace = onePasswordItem.Namespace
if err := r.Delete(ctx, kubernetesSecret); err != nil {
if !errors.IsNotFound(err) {
@@ -160,12 +153,12 @@ func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(ctx context.Context,
return nil
}
func (r *OnePasswordItemReconciler) removeOnePasswordFinalizerFromOnePasswordItem(ctx context.Context, opSecret *onepasswordv1.OnePasswordItem) error {
opSecret.ObjectMeta.Finalizers = utils.RemoveString(opSecret.ObjectMeta.Finalizers, finalizer)
return r.Update(ctx, opSecret)
func (r *OnePasswordItemReconciler) removeOnePasswordFinalizerFromOnePasswordItem(ctx context.Context, onePasswordItem *onepasswordv1.OnePasswordItem) error {
onePasswordItem.Finalizers = utils.RemoveString(onePasswordItem.Finalizers, finalizer)
return r.Update(ctx, onePasswordItem)
}
func (r *OnePasswordItemReconciler) handleOnePasswordItem(ctx context.Context, resource *onepasswordv1.OnePasswordItem, req ctrl.Request) error {
func (r *OnePasswordItemReconciler) handleOnePasswordItem(ctx context.Context, resource *onepasswordv1.OnePasswordItem, _ ctrl.Request) error {
secretName := resource.GetName()
labels := resource.Labels
secretType := resource.Type

View File

@@ -3,6 +3,7 @@ package controller
import (
"context"
"fmt"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
@@ -60,20 +61,14 @@ var _ = Describe("OnePasswordItem controller", func() {
created := &onepasswordv1.OnePasswordItem{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, created)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
By("Creating the K8s secret successfully")
createdSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, createdSecret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(createdSecret.Data).Should(Equal(item1.SecretData))
@@ -101,10 +96,7 @@ var _ = Describe("OnePasswordItem controller", func() {
updatedSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, updatedSecret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(updatedSecret.Data).Should(Equal(newDataByte))
@@ -175,20 +167,14 @@ var _ = Describe("OnePasswordItem controller", func() {
created := &onepasswordv1.OnePasswordItem{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, created)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
By("Creating the K8s secret successfully")
createdSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, createdSecret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(createdSecret.Data).Should(Equal(expectedData))
@@ -297,10 +283,7 @@ var _ = Describe("OnePasswordItem controller", func() {
secret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, secret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(secret.Type).Should(Equal(v1.SecretType(customType)))
})
@@ -344,10 +327,7 @@ var _ = Describe("OnePasswordItem controller", func() {
createdSecret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, createdSecret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
Expect(createdSecret.Data).Should(HaveKeyWithValue("server.crt", fileContent))
@@ -381,20 +361,14 @@ var _ = Describe("OnePasswordItem controller", func() {
secret := &v1.Secret{}
Eventually(func() bool {
err := k8sClient.Get(ctx, key, secret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeTrue())
By("Failing to update K8s secret")
Eventually(func() bool {
secret.Type = v1.SecretTypeBasicAuth
err := k8sClient.Update(ctx, secret)
if err != nil {
return false
}
return true
return err == nil
}, timeout, interval).Should(BeFalse())
})

View File

@@ -26,6 +26,7 @@ package controller
import (
"context"
"os"
"path/filepath"
"regexp"
"testing"
@@ -46,7 +47,7 @@ import (
onepasswordcomv1 "github.com/1Password/onepassword-operator/api/v1"
"github.com/1Password/onepassword-operator/pkg/mocks"
"github.com/1Password/onepassword-operator/pkg/onepassword/model"
//+kubebuilder:scaffold:imports
// +kubebuilder:scaffold:imports
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
@@ -155,6 +156,11 @@ var _ = BeforeSuite(func() {
ErrorIfCRDPathMissing: true,
}
// Retrieve the first found binary directory to allow running tests from IDEs
if getFirstFoundEnvTestBinaryDir() != "" {
testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir()
}
var err error
// cfg is defined in this file globally.
cfg, err = testEnv.Start()
@@ -164,7 +170,7 @@ var _ = BeforeSuite(func() {
err = onepasswordcomv1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
//+kubebuilder:scaffold:scheme
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
Expect(err).NotTo(HaveOccurred())
@@ -210,3 +216,26 @@ var _ = AfterSuite(func() {
err := testEnv.Stop()
Expect(err).NotTo(HaveOccurred())
})
// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path.
// ENVTEST-based tests depend on specific binaries, usually located in paths set by
// controller-runtime. When running tests directly (e.g., via an IDE) without using
// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured.
//
// This function streamlines the process by finding the required binaries, similar to
// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are
// properly set up, run 'make setup-envtest' beforehand.
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 ""
}