From efbe96e93ae46c4c6cb10c784633bddbfb10c391 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Sun, 8 Jun 2025 10:28:15 -0500 Subject: [PATCH 1/4] Use global context --- cmd/main.go | 20 ++++++----- internal/controller/deployment_controller.go | 30 ++++++++-------- .../controller/deployment_controller_test.go | 9 +++-- .../controller/onepassworditem_controller.go | 34 +++++++++---------- .../kubernetes_secrets_builder.go | 8 ++--- .../kubernetes_secrets_builder_test.go | 22 +++++++----- pkg/mocks/mocksecretserver.go | 9 ++--- pkg/onepassword/client/client.go | 13 +++---- pkg/onepassword/client/connect/connect.go | 9 ++--- .../client/connect/connect_test.go | 9 ++--- pkg/onepassword/client/sdk/sdk.go | 20 +++++------ pkg/onepassword/client/sdk/sdk_test.go | 8 ++--- pkg/onepassword/connect_setup.go | 26 +++++++------- pkg/onepassword/connect_setup_test.go | 10 +++--- pkg/onepassword/items.go | 19 ++++++----- pkg/onepassword/secret_update_handler.go | 32 ++++++++--------- pkg/onepassword/secret_update_handler_test.go | 8 ++--- 17 files changed, 150 insertions(+), 136 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 3ee68f7..bdfd60b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -25,6 +25,7 @@ SOFTWARE. package main import ( + "context" "errors" "flag" "fmt" @@ -112,6 +113,9 @@ func main() { printVersion() + // Create a root context that will be cancelled on termination signals + ctx := ctrl.SetupSignalHandler() + watchNamespace, err := getWatchNamespace() if err != nil { setupLog.Error(err, "unable to get WatchNamespace, "+ @@ -152,7 +156,7 @@ func main() { } // Setup One Password Client - opClient, err := opclient.NewClient(version.OperatorVersion) + opClient, err := opclient.NewClient(ctx, version.OperatorVersion) if err != nil { setupLog.Error(err, "unable to create 1Password client") os.Exit(1) @@ -182,10 +186,10 @@ func main() { //Setup 1PasswordConnect if shouldManageConnect() { setupLog.Info("Automated Connect Management Enabled") - go func() { + go func(ctx context.Context) { connectStarted := false for connectStarted == false { - err := op.SetupConnect(mgr.GetClient(), deploymentNamespace) + err := op.SetupConnect(ctx, mgr.GetClient(), deploymentNamespace) // Cache Not Started is an acceptable error. Retry until cache is started. if err != nil && !errors.Is(err, &cache.ErrCacheNotStarted{}) { setupLog.Error(err, "") @@ -195,7 +199,7 @@ func main() { connectStarted = true } } - }() + }(ctx) } else { setupLog.Info("Automated Connect Management Disabled") } @@ -204,20 +208,20 @@ func main() { updatedSecretsPoller := op.NewManager(mgr.GetClient(), opClient, shouldAutoRestartDeployments()) done := make(chan bool) ticker := time.NewTicker(getPollingIntervalForUpdatingSecrets()) - go func() { + go func(ctx context.Context) { for { select { case <-done: ticker.Stop() return case <-ticker.C: - err := updatedSecretsPoller.UpdateKubernetesSecretsTask() + err := updatedSecretsPoller.UpdateKubernetesSecretsTask(ctx) if err != nil { setupLog.Error(err, "error running update kubernetes secret task") } } } - }() + }(ctx) if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") @@ -229,7 +233,7 @@ func main() { } setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + if err := mgr.Start(ctx); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) } diff --git a/internal/controller/deployment_controller.go b/internal/controller/deployment_controller.go index db3b2f4..56aa443 100644 --- a/internal/controller/deployment_controller.go +++ b/internal/controller/deployment_controller.go @@ -76,7 +76,7 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) reqLogger.V(logs.DebugLevel).Info("Reconciling Deployment") deployment := &appsv1.Deployment{} - err := r.Get(context.Background(), req.NamespacedName, deployment) + err := r.Get(ctx, req.NamespacedName, deployment) if err != nil { if errors.IsNotFound(err) { return reconcile.Result{}, nil @@ -96,12 +96,12 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) // 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 err = r.Update(context.Background(), deployment); err != nil { + if err = r.Update(ctx, deployment); err != nil { return reconcile.Result{}, err } } // Handles creation or updating secrets for deployment if needed - if err = r.handleApplyingDeployment(deployment, deployment.Namespace, annotations, req); err != nil { + if err = r.handleApplyingDeployment(ctx, deployment, deployment.Namespace, annotations, req); err != nil { return ctrl.Result{}, err } return ctrl.Result{}, nil @@ -111,12 +111,12 @@ func (r *DeploymentReconciler) Reconcile(ctx context.Context, req ctrl.Request) if utils.ContainsString(deployment.ObjectMeta.Finalizers, finalizer) { secretName := annotations[op.NameAnnotation] - if err = r.cleanupKubernetesSecretForDeployment(secretName, deployment); err != nil { + if err = r.cleanupKubernetesSecretForDeployment(ctx, secretName, deployment); err != nil { return ctrl.Result{}, err } // Remove the finalizer from the deployment so deletion of deployment can be completed - if err = r.removeOnePasswordFinalizerFromDeployment(deployment); err != nil { + if err = r.removeOnePasswordFinalizerFromDeployment(ctx, deployment); err != nil { return reconcile.Result{}, err } } @@ -130,7 +130,7 @@ func (r *DeploymentReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *DeploymentReconciler) cleanupKubernetesSecretForDeployment(secretName string, deletedDeployment *appsv1.Deployment) error { +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 @@ -140,14 +140,14 @@ func (r *DeploymentReconciler) cleanupKubernetesSecretForDeployment(secretName s } updatedSecrets := map[string]*corev1.Secret{secretName: kubernetesSecret} - multipleDeploymentsUsingSecret, err := r.areMultipleDeploymentsUsingSecret(updatedSecrets, *deletedDeployment) + multipleDeploymentsUsingSecret, err := r.areMultipleDeploymentsUsingSecret(ctx, updatedSecrets, *deletedDeployment) if err != nil { return err } // Only delete the associated kubernetes secret if it is not being used by other deployments if !multipleDeploymentsUsingSecret { - if err = r.Delete(context.Background(), kubernetesSecret); err != nil { + if err = r.Delete(ctx, kubernetesSecret); err != nil { if !errors.IsNotFound(err) { return err } @@ -156,13 +156,13 @@ func (r *DeploymentReconciler) cleanupKubernetesSecretForDeployment(secretName s return nil } -func (r *DeploymentReconciler) areMultipleDeploymentsUsingSecret(updatedSecrets map[string]*corev1.Secret, deletedDeployment appsv1.Deployment) (bool, error) { +func (r *DeploymentReconciler) areMultipleDeploymentsUsingSecret(ctx context.Context, updatedSecrets map[string]*corev1.Secret, deletedDeployment appsv1.Deployment) (bool, error) { deployments := &appsv1.DeploymentList{} opts := []client.ListOption{ client.InNamespace(deletedDeployment.Namespace), } - err := r.List(context.Background(), deployments, opts...) + err := r.List(ctx, deployments, opts...) if err != nil { logDeployment.Error(err, "Failed to list kubernetes deployments") return false, err @@ -178,12 +178,12 @@ func (r *DeploymentReconciler) areMultipleDeploymentsUsingSecret(updatedSecrets return false, nil } -func (r *DeploymentReconciler) removeOnePasswordFinalizerFromDeployment(deployment *appsv1.Deployment) error { +func (r *DeploymentReconciler) removeOnePasswordFinalizerFromDeployment(ctx context.Context, deployment *appsv1.Deployment) error { deployment.ObjectMeta.Finalizers = utils.RemoveString(deployment.ObjectMeta.Finalizers, finalizer) - return r.Update(context.Background(), deployment) + return r.Update(ctx, deployment) } -func (r *DeploymentReconciler) handleApplyingDeployment(deployment *appsv1.Deployment, namespace string, annotations map[string]string, request reconcile.Request) error { +func (r *DeploymentReconciler) handleApplyingDeployment(ctx context.Context, deployment *appsv1.Deployment, namespace string, annotations map[string]string, request reconcile.Request) error { reqLog := logDeployment.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) secretName := annotations[op.NameAnnotation] @@ -195,7 +195,7 @@ func (r *DeploymentReconciler) handleApplyingDeployment(deployment *appsv1.Deplo return nil } - item, err := op.GetOnePasswordItemByPath(r.OpClient, annotations[op.ItemPathAnnotation]) + item, err := op.GetOnePasswordItemByPath(ctx, r.OpClient, annotations[op.ItemPathAnnotation]) if err != nil { return fmt.Errorf("Failed to retrieve item: %v", err) } @@ -212,5 +212,5 @@ func (r *DeploymentReconciler) handleApplyingDeployment(deployment *appsv1.Deplo UID: deployment.GetUID(), } - return kubeSecrets.CreateKubernetesSecretFromItem(r.Client, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, ownerRef) + return kubeSecrets.CreateKubernetesSecretFromItem(ctx, r.Client, secretName, namespace, item, annotations[op.RestartDeploymentsAnnotation], secretLabels, secretType, ownerRef) } diff --git a/internal/controller/deployment_controller_test.go b/internal/controller/deployment_controller_test.go index 85364d0..7703fb8 100644 --- a/internal/controller/deployment_controller_test.go +++ b/internal/controller/deployment_controller_test.go @@ -24,14 +24,13 @@ const ( ) var _ = Describe("Deployment controller", func() { - var ctx context.Context + ctx := context.Background() var deploymentKey types.NamespacedName var secretKey types.NamespacedName var deploymentResource *appsv1.Deployment createdSecret := &v1.Secret{} makeDeployment := func() { - ctx = context.Background() deploymentKey = types.NamespacedName{ Name: deploymentName, @@ -93,13 +92,13 @@ var _ = Describe("Deployment controller", func() { cleanK8sResources := func() { // failed test runs that don't clean up leave resources behind. - err := k8sClient.DeleteAllOf(context.Background(), &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace)) + err := k8sClient.DeleteAllOf(ctx, &onepasswordv1.OnePasswordItem{}, client.InNamespace(namespace)) Expect(err).ToNot(HaveOccurred()) - err = k8sClient.DeleteAllOf(context.Background(), &v1.Secret{}, client.InNamespace(namespace)) + err = k8sClient.DeleteAllOf(ctx, &v1.Secret{}, client.InNamespace(namespace)) Expect(err).ToNot(HaveOccurred()) - err = k8sClient.DeleteAllOf(context.Background(), &appsv1.Deployment{}, client.InNamespace(namespace)) + err = k8sClient.DeleteAllOf(ctx, &appsv1.Deployment{}, client.InNamespace(namespace)) Expect(err).ToNot(HaveOccurred()) } diff --git a/internal/controller/onepassworditem_controller.go b/internal/controller/onepassworditem_controller.go index d38e55b..7e68ebf 100644 --- a/internal/controller/onepassworditem_controller.go +++ b/internal/controller/onepassworditem_controller.go @@ -82,7 +82,7 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ reqLogger.V(logs.DebugLevel).Info("Reconciling OnePasswordItem") onepassworditem := &onepasswordv1.OnePasswordItem{} - err := r.Get(context.Background(), req.NamespacedName, onepassworditem) + err := r.Get(ctx, req.NamespacedName, onepassworditem) if err != nil { if errors.IsNotFound(err) { return ctrl.Result{}, nil @@ -96,14 +96,14 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ // 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 err = r.Update(context.Background(), onepassworditem); err != nil { + if err = r.Update(ctx, onepassworditem); err != nil { return ctrl.Result{}, err } } // Handles creation or updating secrets for deployment if needed - err = r.handleOnePasswordItem(onepassworditem, req) - if updateStatusErr := r.updateStatus(onepassworditem, err); updateStatusErr != nil { + err = r.handleOnePasswordItem(ctx, onepassworditem, req) + if updateStatusErr := r.updateStatus(ctx, onepassworditem, err); updateStatusErr != nil { return ctrl.Result{}, fmt.Errorf("cannot update status: %s", updateStatusErr) } return ctrl.Result{}, err @@ -112,12 +112,12 @@ func (r *OnePasswordItemReconciler) Reconcile(ctx context.Context, req ctrl.Requ if utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) { // Delete associated kubernetes secret - if err = r.cleanupKubernetesSecret(onepassworditem); err != nil { + if err = r.cleanupKubernetesSecret(ctx, onepassworditem); err != nil { return ctrl.Result{}, err } // Remove finalizer now that cleanup is complete - if err = r.removeFinalizer(onepassworditem); err != nil { + if err = r.removeFinalizer(ctx, onepassworditem); err != nil { return ctrl.Result{}, err } } @@ -131,20 +131,20 @@ func (r *OnePasswordItemReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func (r *OnePasswordItemReconciler) removeFinalizer(onePasswordItem *onepasswordv1.OnePasswordItem) error { +func (r *OnePasswordItemReconciler) removeFinalizer(ctx context.Context, onePasswordItem *onepasswordv1.OnePasswordItem) error { onePasswordItem.ObjectMeta.Finalizers = utils.RemoveString(onePasswordItem.ObjectMeta.Finalizers, finalizer) - if err := r.Update(context.Background(), onePasswordItem); err != nil { + if err := r.Update(ctx, onePasswordItem); err != nil { return err } return nil } -func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(onePasswordItem *onepasswordv1.OnePasswordItem) error { +func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(ctx context.Context, onePasswordItem *onepasswordv1.OnePasswordItem) error { kubernetesSecret := &corev1.Secret{} kubernetesSecret.ObjectMeta.Name = onePasswordItem.Name kubernetesSecret.ObjectMeta.Namespace = onePasswordItem.Namespace - if err := r.Delete(context.Background(), kubernetesSecret); err != nil { + if err := r.Delete(ctx, kubernetesSecret); err != nil { if !errors.IsNotFound(err) { return err } @@ -152,18 +152,18 @@ func (r *OnePasswordItemReconciler) cleanupKubernetesSecret(onePasswordItem *one return nil } -func (r *OnePasswordItemReconciler) removeOnePasswordFinalizerFromOnePasswordItem(opSecret *onepasswordv1.OnePasswordItem) error { +func (r *OnePasswordItemReconciler) removeOnePasswordFinalizerFromOnePasswordItem(ctx context.Context, opSecret *onepasswordv1.OnePasswordItem) error { opSecret.ObjectMeta.Finalizers = utils.RemoveString(opSecret.ObjectMeta.Finalizers, finalizer) - return r.Update(context.Background(), opSecret) + return r.Update(ctx, opSecret) } -func (r *OnePasswordItemReconciler) handleOnePasswordItem(resource *onepasswordv1.OnePasswordItem, req ctrl.Request) error { +func (r *OnePasswordItemReconciler) handleOnePasswordItem(ctx context.Context, resource *onepasswordv1.OnePasswordItem, req ctrl.Request) error { secretName := resource.GetName() labels := resource.Labels secretType := resource.Type autoRestart := resource.Annotations[op.RestartDeploymentsAnnotation] - item, err := op.GetOnePasswordItemByPath(r.OpClient, resource.Spec.ItemPath) + item, err := op.GetOnePasswordItemByPath(ctx, r.OpClient, resource.Spec.ItemPath) if err != nil { return fmt.Errorf("Failed to retrieve item: %v", err) } @@ -180,10 +180,10 @@ func (r *OnePasswordItemReconciler) handleOnePasswordItem(resource *onepasswordv UID: resource.GetUID(), } - return kubeSecrets.CreateKubernetesSecretFromItem(r.Client, secretName, resource.Namespace, item, autoRestart, labels, secretType, ownerRef) + return kubeSecrets.CreateKubernetesSecretFromItem(ctx, r.Client, secretName, resource.Namespace, item, autoRestart, labels, secretType, ownerRef) } -func (r *OnePasswordItemReconciler) updateStatus(resource *onepasswordv1.OnePasswordItem, err error) error { +func (r *OnePasswordItemReconciler) updateStatus(ctx context.Context, resource *onepasswordv1.OnePasswordItem, err error) error { existingCondition := findCondition(resource.Status.Conditions, onepasswordv1.OnePasswordItemReady) updatedCondition := existingCondition if err != nil { @@ -199,7 +199,7 @@ func (r *OnePasswordItemReconciler) updateStatus(resource *onepasswordv1.OnePass } resource.Status.Conditions = []onepasswordv1.OnePasswordItemCondition{updatedCondition} - return r.Status().Update(context.Background(), resource) + return r.Status().Update(ctx, resource) } func findCondition(conditions []onepasswordv1.OnePasswordItemCondition, t onepasswordv1.OnePasswordItemConditionType) onepasswordv1.OnePasswordItemCondition { diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index d7982fd..429d93e 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -33,7 +33,7 @@ var ErrCannotUpdateSecretType = errs.New("Cannot change secret type. Secret type var log = logf.Log -func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretName, namespace string, item *model.Item, autoRestart string, labels map[string]string, secretType string, ownerRef *metav1.OwnerReference) error { +func CreateKubernetesSecretFromItem(ctx context.Context, kubeClient kubernetesClient.Client, secretName, namespace string, item *model.Item, autoRestart string, labels map[string]string, secretType string, ownerRef *metav1.OwnerReference) error { itemVersion := fmt.Sprint(item.Version) secretAnnotations := map[string]string{ VersionAnnotation: itemVersion, @@ -52,10 +52,10 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa secret := BuildKubernetesSecretFromOnePasswordItem(secretName, namespace, secretAnnotations, labels, secretType, *item, ownerRef) currentSecret := &corev1.Secret{} - err := kubeClient.Get(context.Background(), types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) + err := kubeClient.Get(ctx, types.NamespacedName{Name: secret.Name, Namespace: secret.Namespace}, currentSecret) if err != nil && errors.IsNotFound(err) { log.Info(fmt.Sprintf("Creating Secret %v at namespace '%v'", secret.Name, secret.Namespace)) - return kubeClient.Create(context.Background(), secret) + return kubeClient.Create(ctx, secret) } else if err != nil { return err } @@ -81,7 +81,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa currentSecret.ObjectMeta.Annotations = secretAnnotations currentSecret.ObjectMeta.Labels = labels currentSecret.Data = secret.Data - if err := kubeClient.Update(context.Background(), currentSecret); err != nil { + if err := kubeClient.Update(ctx, currentSecret); err != nil { return fmt.Errorf("Kubernetes secret update failed: %w", err) } return nil diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go b/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go index c7128b1..a3e8039 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder_test.go @@ -17,6 +17,7 @@ import ( const restartDeploymentAnnotation = "false" func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { + ctx := context.Background() secretName := "test-secret-name" namespace := "test" @@ -30,12 +31,12 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { secretLabels := map[string]string{} secretType := "" - err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) + err := CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) if err != nil { t.Errorf("Unexpected error: %v", err) } createdSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) + err = kubeClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) if err != nil { t.Errorf("Secret was not created: %v", err) @@ -45,6 +46,7 @@ func TestCreateKubernetesSecretFromOnePasswordItem(t *testing.T) { } func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) { + ctx := context.Background() secretName := "test-secret-name" namespace := "test" @@ -64,12 +66,12 @@ func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) { Name: "test-deployment", UID: types.UID("test-uid"), } - err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef) + err := CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, ownerRef) if err != nil { t.Errorf("Unexpected error: %v", err) } createdSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) + err = kubeClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) // Check owner references. gotOwnerRefs := createdSecret.ObjectMeta.OwnerReferences @@ -90,6 +92,7 @@ func TestKubernetesSecretFromOnePasswordItemOwnerReferences(t *testing.T) { } func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { + ctx := context.Background() secretName := "test-secret-update" namespace := "test" @@ -103,7 +106,7 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { secretLabels := map[string]string{} secretType := "" - err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) + err := CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) if err != nil { t.Errorf("Unexpected error: %v", err) @@ -115,12 +118,12 @@ func TestUpdateKubernetesSecretFromOnePasswordItem(t *testing.T) { newItem.Version = 456 newItem.VaultID = "hfnjvi6aymbsnfc2xeeoheizda" newItem.ID = "h46bb3jddvay7nxopfhvlwg35q" - err = CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil) + err = CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &newItem, restartDeploymentAnnotation, secretLabels, secretType, nil) if err != nil { t.Errorf("Unexpected error: %v", err) } updatedSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, updatedSecret) + err = kubeClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, updatedSecret) if err != nil { t.Errorf("Secret was not found: %v", err) @@ -205,6 +208,7 @@ func TestBuildKubernetesSecretFixesInvalidLabels(t *testing.T) { } func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) { + ctx := context.Background() secretName := "tls-test-secret-name" namespace := "test" @@ -218,12 +222,12 @@ func TestCreateKubernetesTLSSecretFromOnePasswordItem(t *testing.T) { secretLabels := map[string]string{} secretType := "kubernetes.io/tls" - err := CreateKubernetesSecretFromItem(kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) + err := CreateKubernetesSecretFromItem(ctx, kubeClient, secretName, namespace, &item, restartDeploymentAnnotation, secretLabels, secretType, nil) if err != nil { t.Errorf("Unexpected error: %v", err) } createdSecret := &corev1.Secret{} - err = kubeClient.Get(context.Background(), types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) + err = kubeClient.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, createdSecret) if err != nil { t.Errorf("Secret was not created: %v", err) diff --git a/pkg/mocks/mocksecretserver.go b/pkg/mocks/mocksecretserver.go index c6c3463..4fa091f 100644 --- a/pkg/mocks/mocksecretserver.go +++ b/pkg/mocks/mocksecretserver.go @@ -1,6 +1,7 @@ package mocks import ( + "context" "github.com/stretchr/testify/mock" "github.com/1Password/onepassword-operator/pkg/onepassword/model" @@ -10,7 +11,7 @@ type TestClient struct { mock.Mock } -func (tc *TestClient) GetItemByID(vaultID, itemID string) (*model.Item, error) { +func (tc *TestClient) GetItemByID(ctx context.Context, vaultID, itemID string) (*model.Item, error) { args := tc.Called(vaultID, itemID) if args.Get(0) == nil { return nil, args.Error(1) @@ -18,12 +19,12 @@ func (tc *TestClient) GetItemByID(vaultID, itemID string) (*model.Item, error) { return args.Get(0).(*model.Item), args.Error(1) } -func (tc *TestClient) GetItemsByTitle(vaultID, itemTitle string) ([]model.Item, error) { +func (tc *TestClient) GetItemsByTitle(ctx context.Context, vaultID, itemTitle string) ([]model.Item, error) { args := tc.Called(vaultID, itemTitle) return args.Get(0).([]model.Item), args.Error(1) } -func (tc *TestClient) GetFileContent(vaultID, itemID, fileID string) ([]byte, error) { +func (tc *TestClient) GetFileContent(ctx context.Context, vaultID, itemID, fileID string) ([]byte, error) { args := tc.Called(vaultID, itemID, fileID) if args.Get(0) == nil { return nil, args.Error(1) @@ -31,7 +32,7 @@ func (tc *TestClient) GetFileContent(vaultID, itemID, fileID string) ([]byte, er return args.Get(0).([]byte), args.Error(1) } -func (tc *TestClient) GetVaultsByTitle(title string) ([]model.Vault, error) { +func (tc *TestClient) GetVaultsByTitle(ctx context.Context, title string) ([]model.Vault, error) { args := tc.Called(title) return args.Get(0).([]model.Vault), args.Error(1) } diff --git a/pkg/onepassword/client/client.go b/pkg/onepassword/client/client.go index 7147381..10ec58b 100644 --- a/pkg/onepassword/client/client.go +++ b/pkg/onepassword/client/client.go @@ -1,6 +1,7 @@ package client import ( + "context" "errors" "fmt" "os" @@ -12,14 +13,14 @@ import ( // Client is an interface for interacting with 1Password items and vaults. type Client interface { - GetItemByID(vaultID, itemID string) (*model.Item, error) - GetItemsByTitle(vaultID, itemTitle string) ([]model.Item, error) - GetFileContent(vaultID, itemID, fileID string) ([]byte, error) - GetVaultsByTitle(title string) ([]model.Vault, error) + GetItemByID(ctx context.Context, vaultID, itemID string) (*model.Item, error) + GetItemsByTitle(ctx context.Context, vaultID, itemTitle string) ([]model.Item, error) + GetFileContent(ctx context.Context, vaultID, itemID, fileID string) ([]byte, error) + GetVaultsByTitle(ctx context.Context, title string) ([]model.Vault, error) } // NewClient creates a new 1Password client based on the provided configuration. -func NewClient(integrationVersion string) (Client, error) { +func NewClient(ctx context.Context, integrationVersion string) (Client, error) { connectHost, _ := os.LookupEnv("OP_CONNECT_HOST") connectToken, _ := os.LookupEnv("OP_CONNECT_TOKEN") serviceAccountToken, _ := os.LookupEnv("OP_SERVICE_ACCOUNT_TOKEN") @@ -30,7 +31,7 @@ func NewClient(integrationVersion string) (Client, error) { if serviceAccountToken != "" { fmt.Printf("Using Service Account Token") - return sdk.NewClient(sdk.Config{ + return sdk.NewClient(ctx, sdk.Config{ ServiceAccountToken: serviceAccountToken, IntegrationName: "1password-operator", IntegrationVersion: integrationVersion, diff --git a/pkg/onepassword/client/connect/connect.go b/pkg/onepassword/client/connect/connect.go index e45df48..efa49ed 100644 --- a/pkg/onepassword/client/connect/connect.go +++ b/pkg/onepassword/client/connect/connect.go @@ -1,6 +1,7 @@ package connect import ( + "context" "fmt" "github.com/1Password/connect-sdk-go/connect" @@ -27,7 +28,7 @@ func NewClient(config Config) *Connect { } } -func (c *Connect) GetItemByID(vaultID, itemID string) (*model.Item, error) { +func (c *Connect) GetItemByID(ctx context.Context, vaultID, itemID string) (*model.Item, error) { connectItem, err := c.client.GetItemByUUID(itemID, vaultID) if err != nil { return nil, fmt.Errorf("1password Connect error: %w", err) @@ -38,7 +39,7 @@ func (c *Connect) GetItemByID(vaultID, itemID string) (*model.Item, error) { return &item, nil } -func (c *Connect) GetItemsByTitle(vaultID, itemTitle string) ([]model.Item, error) { +func (c *Connect) GetItemsByTitle(ctx context.Context, vaultID, itemTitle string) ([]model.Item, error) { // Get all items in the vault with the specified title connectItems, err := c.client.GetItemsByTitle(itemTitle, vaultID) if err != nil { @@ -55,7 +56,7 @@ func (c *Connect) GetItemsByTitle(vaultID, itemTitle string) ([]model.Item, erro return items, nil } -func (c *Connect) GetFileContent(vaultID, itemID, fileID string) ([]byte, error) { +func (c *Connect) GetFileContent(ctx context.Context, vaultID, itemID, fileID string) ([]byte, error) { bytes, err := c.client.GetFileContent(&onepassword.File{ ContentPath: fmt.Sprintf("/v1/vaults/%s/items/%s/files/%s/content", vaultID, itemID, fileID), }) @@ -66,7 +67,7 @@ func (c *Connect) GetFileContent(vaultID, itemID, fileID string) ([]byte, error) return bytes, nil } -func (c *Connect) GetVaultsByTitle(vaultQuery string) ([]model.Vault, error) { +func (c *Connect) GetVaultsByTitle(ctx context.Context, vaultQuery string) ([]model.Vault, error) { connectVaults, err := c.client.GetVaultsByTitle(vaultQuery) if err != nil { return nil, fmt.Errorf("1password Connect error: %w", err) diff --git a/pkg/onepassword/client/connect/connect_test.go b/pkg/onepassword/client/connect/connect_test.go index ef21b90..eafb481 100644 --- a/pkg/onepassword/client/connect/connect_test.go +++ b/pkg/onepassword/client/connect/connect_test.go @@ -1,6 +1,7 @@ package connect import ( + "context" "errors" "testing" @@ -48,7 +49,7 @@ func TestConnect_GetItemByID(t *testing.T) { for description, tc := range testCases { t.Run(description, func(t *testing.T) { client := &Connect{client: tc.mockClient()} - item, err := client.GetItemByID("vault-id", "item-id") + item, err := client.GetItemByID(context.Background(), "vault-id", "item-id") tc.check(t, item, err) }) } @@ -110,7 +111,7 @@ func TestConnect_GetItemsByTitle(t *testing.T) { for description, tc := range testCases { t.Run(description, func(t *testing.T) { client := &Connect{client: tc.mockClient()} - items, err := client.GetItemsByTitle("vault-id", "item-title") + items, err := client.GetItemsByTitle(context.Background(), "vault-id", "item-title") tc.check(t, items, err) }) } @@ -152,7 +153,7 @@ func TestConnect_GetFileContent(t *testing.T) { for description, tc := range testCases { t.Run(description, func(t *testing.T) { client := &Connect{client: tc.mockClient()} - content, err := client.GetFileContent("vault-id", "item-id", "file-id") + content, err := client.GetFileContent(context.Background(), "vault-id", "item-id", "file-id") tc.check(t, content, err) }) } @@ -224,7 +225,7 @@ func TestConnect_GetVaultsByTitle(t *testing.T) { for description, tc := range testCases { t.Run(description, func(t *testing.T) { client := &Connect{client: tc.mockClient()} - vault, err := client.GetVaultsByTitle(VaultTitleEmployee) + vault, err := client.GetVaultsByTitle(context.Background(), VaultTitleEmployee) tc.check(t, vault, err) }) } diff --git a/pkg/onepassword/client/sdk/sdk.go b/pkg/onepassword/client/sdk/sdk.go index b6a89ee..588d147 100644 --- a/pkg/onepassword/client/sdk/sdk.go +++ b/pkg/onepassword/client/sdk/sdk.go @@ -20,8 +20,8 @@ type SDK struct { client *sdk.Client } -func NewClient(config Config) (*SDK, error) { - client, err := sdk.NewClient(context.Background(), +func NewClient(ctx context.Context, config Config) (*SDK, error) { + client, err := sdk.NewClient(ctx, sdk.WithServiceAccountToken(config.ServiceAccountToken), sdk.WithIntegrationInfo(config.IntegrationName, config.IntegrationVersion), ) @@ -34,8 +34,8 @@ func NewClient(config Config) (*SDK, error) { }, nil } -func (s *SDK) GetItemByID(vaultID, itemID string) (*model.Item, error) { - sdkItem, err := s.client.Items().Get(context.Background(), vaultID, itemID) +func (s *SDK) GetItemByID(ctx context.Context, vaultID, itemID string) (*model.Item, error) { + sdkItem, err := s.client.Items().Get(ctx, vaultID, itemID) if err != nil { return nil, fmt.Errorf("1password sdk error: %w", err) } @@ -45,9 +45,9 @@ func (s *SDK) GetItemByID(vaultID, itemID string) (*model.Item, error) { return &item, nil } -func (s *SDK) GetItemsByTitle(vaultID, itemTitle string) ([]model.Item, error) { +func (s *SDK) GetItemsByTitle(ctx context.Context, vaultID, itemTitle string) ([]model.Item, error) { // Get all items in the vault - sdkItems, err := s.client.Items().List(context.Background(), vaultID) + sdkItems, err := s.client.Items().List(ctx, vaultID) if err != nil { return nil, fmt.Errorf("1password sdk error: %w", err) } @@ -65,8 +65,8 @@ func (s *SDK) GetItemsByTitle(vaultID, itemTitle string) ([]model.Item, error) { return items, nil } -func (s *SDK) GetFileContent(vaultID, itemID, fileID string) ([]byte, error) { - bytes, err := s.client.Items().Files().Read(context.Background(), vaultID, itemID, sdk.FileAttributes{ +func (s *SDK) GetFileContent(ctx context.Context, vaultID, itemID, fileID string) ([]byte, error) { + bytes, err := s.client.Items().Files().Read(ctx, vaultID, itemID, sdk.FileAttributes{ ID: fileID, }) if err != nil { @@ -76,9 +76,9 @@ func (s *SDK) GetFileContent(vaultID, itemID, fileID string) ([]byte, error) { return bytes, nil } -func (s *SDK) GetVaultsByTitle(title string) ([]model.Vault, error) { +func (s *SDK) GetVaultsByTitle(ctx context.Context, title string) ([]model.Vault, error) { // List all vaults - sdkVaults, err := s.client.Vaults().List(context.Background()) + sdkVaults, err := s.client.Vaults().List(ctx) if err != nil { return nil, fmt.Errorf("1password sdk error: %w", err) } diff --git a/pkg/onepassword/client/sdk/sdk_test.go b/pkg/onepassword/client/sdk/sdk_test.go index 5455c5b..91c2a21 100644 --- a/pkg/onepassword/client/sdk/sdk_test.go +++ b/pkg/onepassword/client/sdk/sdk_test.go @@ -54,7 +54,7 @@ func TestSDK_GetItemByID(t *testing.T) { ItemsAPI: tc.mockItemAPI(), }, } - item, err := client.GetItemByID("vault-id", "item-id") + item, err := client.GetItemByID(context.Background(), "vault-id", "item-id") tc.check(t, item, err) }) } @@ -123,7 +123,7 @@ func TestSDK_GetItemsByTitle(t *testing.T) { ItemsAPI: tc.mockItemAPI(), }, } - items, err := client.GetItemsByTitle("vault-id", "item-title") + items, err := client.GetItemsByTitle(context.Background(), "vault-id", "item-title") tc.check(t, items, err) }) } @@ -185,7 +185,7 @@ func TestSDK_GetFileContent(t *testing.T) { ItemsAPI: tc.mockItemAPI(), }, } - content, err := client.GetFileContent("vault-id", "item-id", "file-id") + content, err := client.GetFileContent(context.Background(), "vault-id", "item-id", "file-id") tc.check(t, content, err) }) } @@ -262,7 +262,7 @@ func TestSDK_GetVaultsByTitle(t *testing.T) { VaultsAPI: tc.mockVaultAPI(), }, } - vault, err := client.GetVaultsByTitle(VaultTitleEmployee) + vault, err := client.GetVaultsByTitle(context.Background(), VaultTitleEmployee) tc.check(t, vault, err) }) } diff --git a/pkg/onepassword/connect_setup.go b/pkg/onepassword/connect_setup.go index 16d2d0c..57bf057 100644 --- a/pkg/onepassword/connect_setup.go +++ b/pkg/onepassword/connect_setup.go @@ -18,13 +18,13 @@ var logConnectSetup = logf.Log.WithName("ConnectSetup") var deploymentPath = "../config/connect/deployment.yaml" var servicePath = "../config/connect/service.yaml" -func SetupConnect(kubeClient client.Client, deploymentNamespace string) error { - err := setupService(kubeClient, servicePath, deploymentNamespace) +func SetupConnect(ctx context.Context, kubeClient client.Client, deploymentNamespace string) error { + err := setupService(ctx, kubeClient, servicePath, deploymentNamespace) if err != nil { return err } - err = setupDeployment(kubeClient, deploymentPath, deploymentNamespace) + err = setupDeployment(ctx, kubeClient, deploymentPath, deploymentNamespace) if err != nil { return err } @@ -32,27 +32,27 @@ func SetupConnect(kubeClient client.Client, deploymentNamespace string) error { return nil } -func setupDeployment(kubeClient client.Client, deploymentPath string, deploymentNamespace string) error { +func setupDeployment(ctx context.Context, kubeClient client.Client, deploymentPath string, deploymentNamespace string) error { existingDeployment := &appsv1.Deployment{} // check if deployment has already been created - err := kubeClient.Get(context.Background(), types.NamespacedName{Name: "onepassword-connect", Namespace: deploymentNamespace}, existingDeployment) + err := kubeClient.Get(ctx, types.NamespacedName{Name: "onepassword-connect", Namespace: deploymentNamespace}, existingDeployment) if err != nil { if errors.IsNotFound(err) { logConnectSetup.Info("No existing Connect deployment found. Creating Deployment") - return createDeployment(kubeClient, deploymentPath, deploymentNamespace) + return createDeployment(ctx, kubeClient, deploymentPath, deploymentNamespace) } } return err } -func createDeployment(kubeClient client.Client, deploymentPath string, deploymentNamespace string) error { +func createDeployment(ctx context.Context, kubeClient client.Client, deploymentPath string, deploymentNamespace string) error { deployment, err := getDeploymentToCreate(deploymentPath, deploymentNamespace) if err != nil { return err } - err = kubeClient.Create(context.Background(), deployment) + err = kubeClient.Create(ctx, deployment) if err != nil { return err } @@ -78,21 +78,21 @@ func getDeploymentToCreate(deploymentPath string, deploymentNamespace string) (* return deployment, nil } -func setupService(kubeClient client.Client, servicePath string, deploymentNamespace string) error { +func setupService(ctx context.Context, kubeClient client.Client, servicePath string, deploymentNamespace string) error { existingService := &corev1.Service{} //check if service has already been created - err := kubeClient.Get(context.Background(), types.NamespacedName{Name: "onepassword-connect", Namespace: deploymentNamespace}, existingService) + err := kubeClient.Get(ctx, types.NamespacedName{Name: "onepassword-connect", Namespace: deploymentNamespace}, existingService) if err != nil { if errors.IsNotFound(err) { logConnectSetup.Info("No existing Connect service found. Creating Service") - return createService(kubeClient, servicePath, deploymentNamespace) + return createService(ctx, kubeClient, servicePath, deploymentNamespace) } } return err } -func createService(kubeClient client.Client, servicePath string, deploymentNamespace string) error { +func createService(ctx context.Context, kubeClient client.Client, servicePath string, deploymentNamespace string) error { f, err := os.Open(servicePath) if err != nil { return err @@ -108,7 +108,7 @@ func createService(kubeClient client.Client, servicePath string, deploymentNames return err } - err = kubeClient.Create(context.Background(), service) + err = kubeClient.Create(ctx, service) if err != nil { return err } diff --git a/pkg/onepassword/connect_setup_test.go b/pkg/onepassword/connect_setup_test.go index f0e5864..8e47df4 100644 --- a/pkg/onepassword/connect_setup_test.go +++ b/pkg/onepassword/connect_setup_test.go @@ -15,6 +15,7 @@ import ( var defaultNamespacedName = types.NamespacedName{Name: "onepassword-connect", Namespace: "default"} func TestServiceSetup(t *testing.T) { + ctx := context.Background() // Register operator types with the runtime scheme. s := scheme.Scheme @@ -25,7 +26,7 @@ func TestServiceSetup(t *testing.T) { // Create a fake client to mock API calls. client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build() - err := setupService(client, "../../config/connect/service.yaml", defaultNamespacedName.Namespace) + err := setupService(ctx, client, "../../config/connect/service.yaml", defaultNamespacedName.Namespace) if err != nil { t.Errorf("Error Setting Up Connect: %v", err) @@ -33,13 +34,14 @@ func TestServiceSetup(t *testing.T) { // check that service was created service := &corev1.Service{} - err = client.Get(context.TODO(), defaultNamespacedName, service) + err = client.Get(ctx, defaultNamespacedName, service) if err != nil { t.Errorf("Error Setting Up Connect service: %v", err) } } func TestDeploymentSetup(t *testing.T) { + ctx := context.Background() // Register operator types with the runtime scheme. s := scheme.Scheme @@ -50,7 +52,7 @@ func TestDeploymentSetup(t *testing.T) { // Create a fake client to mock API calls. client := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build() - err := setupDeployment(client, "../../config/connect/deployment.yaml", defaultNamespacedName.Namespace) + err := setupDeployment(ctx, client, "../../config/connect/deployment.yaml", defaultNamespacedName.Namespace) if err != nil { t.Errorf("Error Setting Up Connect: %v", err) @@ -58,7 +60,7 @@ func TestDeploymentSetup(t *testing.T) { // check that deployment was created deployment := &appsv1.Deployment{} - err = client.Get(context.TODO(), defaultNamespacedName, deployment) + err = client.Get(ctx, defaultNamespacedName, deployment) if err != nil { t.Errorf("Error Setting Up Connect deployment: %v", err) } diff --git a/pkg/onepassword/items.go b/pkg/onepassword/items.go index eb121a3..6b4a983 100644 --- a/pkg/onepassword/items.go +++ b/pkg/onepassword/items.go @@ -1,6 +1,7 @@ package onepassword import ( + "context" "fmt" "strings" @@ -12,28 +13,28 @@ import ( var logger = logf.Log.WithName("retrieve_item") -func GetOnePasswordItemByPath(opClient opclient.Client, path string) (*model.Item, error) { +func GetOnePasswordItemByPath(ctx context.Context, opClient opclient.Client, path string) (*model.Item, error) { vaultIdentifier, itemIdentifier, err := ParseVaultAndItemFromPath(path) if err != nil { return nil, err } - vaultID, err := getVaultID(opClient, vaultIdentifier) + vaultID, err := getVaultID(ctx, opClient, vaultIdentifier) if err != nil { return nil, fmt.Errorf("failed to 'getVaultID' for vaultIdentifier='%s': %w", vaultIdentifier, err) } - itemID, err := getItemID(opClient, vaultID, itemIdentifier) + itemID, err := getItemID(ctx, opClient, vaultID, itemIdentifier) if err != nil { return nil, fmt.Errorf("faild to 'getItemID' for vaultID='%s' and itemIdentifier='%s': %w", vaultID, itemIdentifier, err) } - item, err := opClient.GetItemByID(vaultID, itemID) + item, err := opClient.GetItemByID(ctx, vaultID, itemID) if err != nil { return nil, fmt.Errorf("faield to 'GetItemByID' for vaultID='%s' and itemID='%s': %w", vaultID, itemID, err) } for _, file := range item.Files { - _, err := opClient.GetFileContent(vaultID, itemID, file.ID) + _, err := opClient.GetFileContent(ctx, vaultID, itemID, file.ID) if err != nil { return nil, err } @@ -50,9 +51,9 @@ func ParseVaultAndItemFromPath(path string) (string, string, error) { return "", "", fmt.Errorf("%q is not an acceptable path for One Password item. Must be of the format: `vaults/{vault_id}/items/{item_id}`", path) } -func getVaultID(client opclient.Client, vaultIdentifier string) (string, error) { +func getVaultID(ctx context.Context, client opclient.Client, vaultIdentifier string) (string, error) { if !IsValidClientUUID(vaultIdentifier) { - vaults, err := client.GetVaultsByTitle(vaultIdentifier) + vaults, err := client.GetVaultsByTitle(ctx, vaultIdentifier) if err != nil { return "", err } @@ -75,9 +76,9 @@ func getVaultID(client opclient.Client, vaultIdentifier string) (string, error) return vaultIdentifier, nil } -func getItemID(client opclient.Client, vaultId, itemIdentifier string) (string, error) { +func getItemID(ctx context.Context, client opclient.Client, vaultId, itemIdentifier string) (string, error) { if !IsValidClientUUID(itemIdentifier) { - items, err := client.GetItemsByTitle(vaultId, itemIdentifier) + items, err := client.GetItemsByTitle(ctx, vaultId, itemIdentifier) if err != nil { return "", err } diff --git a/pkg/onepassword/secret_update_handler.go b/pkg/onepassword/secret_update_handler.go index d7f0156..1042740 100644 --- a/pkg/onepassword/secret_update_handler.go +++ b/pkg/onepassword/secret_update_handler.go @@ -37,23 +37,23 @@ type SecretUpdateHandler struct { shouldAutoRestartDeploymentsGlobal bool } -func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask() error { - updatedKubernetesSecrets, err := h.updateKubernetesSecrets() +func (h *SecretUpdateHandler) UpdateKubernetesSecretsTask(ctx context.Context) error { + updatedKubernetesSecrets, err := h.updateKubernetesSecrets(ctx) if err != nil { return err } - return h.restartDeploymentsWithUpdatedSecrets(updatedKubernetesSecrets) + return h.restartDeploymentsWithUpdatedSecrets(ctx, updatedKubernetesSecrets) } -func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecretsByNamespace map[string]map[string]*corev1.Secret) error { +func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(ctx context.Context, updatedSecretsByNamespace map[string]map[string]*corev1.Secret) error { // No secrets to update. Exit if len(updatedSecretsByNamespace) == 0 || updatedSecretsByNamespace == nil { return nil } deployments := &appsv1.DeploymentList{} - err := h.client.List(context.Background(), deployments) + err := h.client.List(ctx, deployments) if err != nil { log.Error(err, "Failed to list kubernetes deployments") return err @@ -63,7 +63,7 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret return nil } - setForAutoRestartByNamespaceMap, err := h.getIsSetForAutoRestartByNamespaceMap() + setForAutoRestartByNamespaceMap, err := h.getIsSetForAutoRestartByNamespaceMap(ctx) if err != nil { return err } @@ -78,7 +78,7 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret } for _, secret := range updatedDeploymentSecrets { if isSecretSetForAutoRestart(secret, deployment, setForAutoRestartByNamespaceMap) { - h.restartDeployment(deployment) + h.restartDeployment(ctx, deployment) continue } } @@ -89,21 +89,21 @@ func (h *SecretUpdateHandler) restartDeploymentsWithUpdatedSecrets(updatedSecret return nil } -func (h *SecretUpdateHandler) restartDeployment(deployment *appsv1.Deployment) { +func (h *SecretUpdateHandler) restartDeployment(ctx context.Context, deployment *appsv1.Deployment) { log.Info(fmt.Sprintf("Deployment %q at namespace %q references an updated secret. Restarting", deployment.GetName(), deployment.Namespace)) if deployment.Spec.Template.Annotations == nil { deployment.Spec.Template.Annotations = map[string]string{} } deployment.Spec.Template.Annotations[RestartAnnotation] = time.Now().String() - err := h.client.Update(context.Background(), deployment) + err := h.client.Update(ctx, deployment) if err != nil { log.Error(err, "Problem restarting deployment") } } -func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]*corev1.Secret, error) { +func (h *SecretUpdateHandler) updateKubernetesSecrets(ctx context.Context) (map[string]map[string]*corev1.Secret, error) { secrets := &corev1.SecretList{} - err := h.client.List(context.Background(), secrets) + err := h.client.List(ctx, secrets) if err != nil { log.Error(err, "Failed to list kubernetes secrets") return nil, err @@ -121,7 +121,7 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]* OnePasswordItemPath := h.getPathFromOnePasswordItem(secret) - item, err := GetOnePasswordItemByPath(h.opClient, OnePasswordItemPath) + item, err := GetOnePasswordItemByPath(ctx, h.opClient, OnePasswordItemPath) if err != nil { log.Error(err, "failed to retrieve 1Password item at path \"%s\" for secret \"%s\"", secret.Annotations[ItemPathAnnotation], secret.Name) continue @@ -135,7 +135,7 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]* log.V(logs.DebugLevel).Info(fmt.Sprintf("Secret '%v' has been updated in 1Password but is set to be ignored. Updates to an ignored secret will not trigger an update to a kubernetes secret or a rolling restart.", secret.GetName())) secret.Annotations[VersionAnnotation] = itemVersion secret.Annotations[ItemPathAnnotation] = itemPathString - if err := h.client.Update(context.Background(), &secret); err != nil { + if err := h.client.Update(ctx, &secret); err != nil { log.Error(err, "failed to update secret %s annotations to version %d: %s", secret.Name, itemVersion, err) continue } @@ -146,7 +146,7 @@ func (h *SecretUpdateHandler) updateKubernetesSecrets() (map[string]map[string]* secret.Annotations[ItemPathAnnotation] = itemPathString secret.Data = kubeSecrets.BuildKubernetesSecretData(item.Fields, item.Files) log.V(logs.DebugLevel).Info(fmt.Sprintf("New secret path: %v and version: %v", secret.Annotations[ItemPathAnnotation], secret.Annotations[VersionAnnotation])) - if err := h.client.Update(context.Background(), &secret); err != nil { + if err := h.client.Update(ctx, &secret); err != nil { log.Error(err, "failed to update secret %s to version %d: %s", secret.Name, itemVersion, err) continue } @@ -177,9 +177,9 @@ func isUpdatedSecret(secretName string, updatedSecrets map[string]*corev1.Secret return false } -func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap() (map[string]bool, error) { +func (h *SecretUpdateHandler) getIsSetForAutoRestartByNamespaceMap(ctx context.Context) (map[string]bool, error) { namespaces := &corev1.NamespaceList{} - err := h.client.List(context.Background(), namespaces) + err := h.client.List(ctx, namespaces) if err != nil { log.Error(err, "Failed to list kubernetes namespaces") return nil, err diff --git a/pkg/onepassword/secret_update_handler_test.go b/pkg/onepassword/secret_update_handler_test.go index f6dad7f..9568b39 100644 --- a/pkg/onepassword/secret_update_handler_test.go +++ b/pkg/onepassword/secret_update_handler_test.go @@ -787,7 +787,7 @@ var tests = []testUpdateSecretTask{ func TestUpdateSecretHandler(t *testing.T) { for _, testData := range tests { t.Run(testData.testName, func(t *testing.T) { - + ctx := context.Background() // Register operator types with the runtime scheme. s := scheme.Scheme s.AddKnownTypes(appsv1.SchemeGroupVersion, testData.existingDeployment) @@ -822,7 +822,7 @@ func TestUpdateSecretHandler(t *testing.T) { shouldAutoRestartDeploymentsGlobal: testData.globalAutoRestartEnabled, } - err := h.UpdateKubernetesSecretsTask() + err := h.UpdateKubernetesSecretsTask(ctx) assert.Equal(t, testData.expectedError, err) @@ -835,7 +835,7 @@ func TestUpdateSecretHandler(t *testing.T) { // Check if Secret has been created and has the correct data secret := &corev1.Secret{} - err = cl.Get(context.TODO(), types.NamespacedName{Name: expectedSecretName, Namespace: namespace}, secret) + err = cl.Get(ctx, types.NamespacedName{Name: expectedSecretName, Namespace: namespace}, secret) if testData.expectedResultSecret == nil { assert.Error(t, err) @@ -849,7 +849,7 @@ func TestUpdateSecretHandler(t *testing.T) { //check if deployment has been restarted deployment := &appsv1.Deployment{} - err = cl.Get(context.TODO(), types.NamespacedName{Name: testData.existingDeployment.Name, Namespace: namespace}, deployment) + err = cl.Get(ctx, types.NamespacedName{Name: testData.existingDeployment.Name, Namespace: namespace}, deployment) _, ok := deployment.Spec.Template.Annotations[RestartAnnotation] if ok { From 04c1fc12363284280158ee5217ca2f0e2603b3c0 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 13 Jun 2025 16:15:41 -0500 Subject: [PATCH 2/4] Update USAGEGUIDE.md --- USAGEGUIDE.md | 232 +++++++++++++------------------------------------- 1 file changed, 60 insertions(+), 172 deletions(-) diff --git a/USAGEGUIDE.md b/USAGEGUIDE.md index 956c09c..b94a9e1 100644 --- a/USAGEGUIDE.md +++ b/USAGEGUIDE.md @@ -5,143 +5,50 @@ ## Table of Contents -- [Configuration Options](#configuration-options) -- [Prerequisites](#prerequisites) -- [Deploying 1Password Connect to Kubernetes](#deploying-1password-connect-to-kubernetes) -- [Kubernetes Operator Deployment With Connect](#kubernetes-operator-deployment-with-connect) -- [Kubernetes Operator Deployment With Service Account](#kubernetes-operator-deployment-with-service-account) -- [Usage](#usage) -- [Configuring Automatic Rolling Restarts of Deployments](#configuring-automatic-rolling-restarts-of-deployments) -- [Development](#development) +1. [Prerequisites](#prerequisites) +2. [Configuration Options](#configuration-options) +3. [Use Kubernetes Operator with Service Account](#use-kubernetes-operator-with-service-account) + - [Create a Service Account](#1-create-a-service-account) + - [Create a Kubernetes secret](#2-create-a-kubernetes-secret-for-the-service-account) + - [Deploy the Operator](#3-deploy-the-operator) +4. [Use Kubernetes Operator with Connect](#use-kubernetes-operator-with-connect) + - [Deploy with Helm](#1-deploy-with-helm) + - [Deploy manually](#2-deploy-manually) +5. [Logging level](#logging-level) +6. [Usage examples](#usage-examples) +7. [How 1Password Items Map to Kubernetes Secrets](#how-1password-items-map-to-kubernetes-secrets) +8. [Configuring Automatic Rolling Restarts of Deployments](#configuring-automatic-rolling-restarts-of-deployments) +9. [Development](#development) + +--- ## Prerequisites - [1Password Command Line Tool Installed](https://1password.com/downloads/command-line/) - [`kubectl` installed](https://kubernetes.io/docs/tasks/tools/install-kubectl/) - [`docker` installed](https://docs.docker.com/get-docker/) -- [A `1password-credentials.json` file generated and a 1Password Connect API Token issued for the K8s Operator integration](https://developer.1password.com/docs/connect/get-started/#step-1-set-up-a-secrets-automation-workflow) + +--- ## Configuration options There are 2 ways 1Password Operator can talk to 1Password servers: - **Connect**: It uses the 1Password Connect API to access items in 1Password. - **Service Account**: It uses [1Password SDK](https://developer.1password.com/docs/sdks/) and [Service Account](https://developer.1password.com/docs/service-accounts) to access items in 1Password. -## Deploying 1Password Connect to Kubernetes +--- -If 1Password Connect is already running, you can skip this step. +## Use Kubernetes Operator with Service Account -There are options to deploy 1Password Connect: - -- [Deploy with Helm](#deploy-with-helm) -- [Deploy using the Connect Operator](#deploy-using-the-connect-operator) - -### Deploy with Helm - -The 1Password Connect Helm Chart helps to simplify the deployment of 1Password Connect and the 1Password Connect Kubernetes Operator to Kubernetes. - -[The 1Password Connect Helm Chart can be found here.](https://github.com/1Password/connect-helm-charts) - -### Deploy using the Connect Operator - -This guide will provide a quickstart option for deploying a default configuration of 1Password Connect via starting the deploying the 1Password Connect Operator, however, it is recommended that you instead deploy your own manifest file if customization of the 1Password Connect deployment is desired. - -Encode the `1password-credentials.json` file you generated in the prerequisite steps and save it to a file named `op-session`: - -```bash -cat 1password-credentials.json | base64 | \ - tr '/+' '_-' | tr -d '=' | tr -d '\n' > op-session -``` - -Create a Kubernetes secret from the op-session file: - -```bash -kubectl create secret generic op-credentials --from-file=op-session -``` - -Add the following environment variable to the onepassword-connect-operator container in `/config/manager/manager.yaml`: - -```yaml -- name: MANAGE_CONNECT - value: "true" -``` - -Adding this environment variable will have the operator automatically deploy a default configuration of 1Password Connect to the current namespace. - -## Kubernetes Operator Deployment with Connect - -#### Create Kubernetes Secret for OP_CONNECT_TOKEN #### - -Create a Connect token for the operator and save it as a Kubernetes Secret: - -```bash -kubectl create secret generic onepassword-token --from-literal=token="" -``` - -If you do not have a token for the operator, you can generate a token and save it to Kubernetes with the following command: - -```bash -kubectl create secret generic onepassword-token --from-literal=token=$(op create connect token op-k8s-operator --vault ) -``` - -**Deploying the Operator** - -An sample Deployment yaml can be found at `/config/manager/manager.yaml`. - -To further configure the 1Password Kubernetes Operator the following Environment variables can be set in the operator yaml: - -- **OP_CONNECT_HOST** *(required)*: Specifies the host name within Kubernetes in which to access the 1Password Connect. -- **WATCH_NAMESPACE:** *(default: watch all namespaces)*: Comma separated list of what Namespaces to watch for changes. -- **POLLING_INTERVAL** *(default: 600)*: The number of seconds the 1Password Kubernetes Operator will wait before checking for updates from 1Password Connect. -- **MANAGE_CONNECT** *(default: false)*: If set to true, on deployment of the operator, a default configuration of the OnePassword Connect Service will be deployed to the current namespace. -- **AUTO_RESTART** (default: false): If set to true, the operator will restart any deployment using a secret from 1Password Connect. This can be overwritten by namespace, deployment, or individual secret. More details on AUTO_RESTART can be found in the ["Configuring Automatic Rolling Restarts of Deployments"](#configuring-automatic-rolling-restarts-of-deployments) section. - -You can also set the logging level by setting `--zap-log-level` as an arg on the containers to either `debug`, `info` or `error`. (Note: the default value is `debug`.) - -Example: -```yaml -. -. -. -containers: - - command: - - /manager - args: - - --leader-elect - - --zap-log-level=info - image: 1password/onepassword-operator:latest -. -. -. -``` -To deploy the operator, simply run the following command: - -```shell -make deploy -``` - -**Undeploy Operator** - -``` -make undeploy -``` - -## Kubernetes Operator Deployment with Service Account - -#### Create Kubernetes Secret for OP_SERVICE_ACCOUNT_TOKEN #### - -Create a Service Account token for the operator and save it as a Kubernetes Secret: +### 1. [Create a service account](https://developer.1password.com/docs/service-accounts/get-started#create-a-service-account) +### 2. Create a Kubernetes secret for the Service Account +- Set `OP_SERVICE_ACCOUNT_TOKEN` environment variable to the service account token you created in the previous step. This token will be used by the operator to access 1Password items. +- Create Kubernetes secret: ```bash kubectl create secret generic onepassword-service-account-token --from-literal=token="$OP_SERVICE_ACCOUNT_TOKEN" ``` -If you do not have a token for the operator, you can generate a token and save it to Kubernetes with the following command: - -```bash -kubectl create secret generic onepassword-service-account-token --from-literal=token=$(op service-account create my-service-account --vault Dev:read_items --vault Test:read_items,write_items) -``` - -**Deploying the Operator** +### 3. Deploy the Operator An sample Deployment yaml can be found at `/config/manager/manager.yaml`. To use Operator with Service Account, you need to set the `OP_SERVICE_ACCOUNT_TOKEN` environment variable in the `/config/manager/manager.yaml`. And remove `OP_CONNECT_TOKEN` and `OP_CONNECT_HOST` environment variables. @@ -153,24 +60,6 @@ To further configure the 1Password Kubernetes Operator the following Environment - **POLLING_INTERVAL** *(default: 600)*: The number of seconds the 1Password Kubernetes Operator will wait before checking for updates from 1Password. - **AUTO_RESTART** (default: false): If set to true, the operator will restart any deployment using a secret from 1Password. This can be overwritten by namespace, deployment, or individual secret. More details on AUTO_RESTART can be found in the ["Configuring Automatic Rolling Restarts of Deployments"](#configuring-automatic-rolling-restarts-of-deployments) section. -You can also set the logging level by setting `--zap-log-level` as an arg on the containers to either `debug`, `info` or `error`. (Note: the default value is `debug`.) - -Example: -```yaml -. -. -. -containers: - - command: - - /manager - args: - - --leader-elect - - --zap-log-level=info - image: 1password/onepassword-operator:latest -. -. -. -``` To deploy the operator, simply run the following command: ```shell @@ -183,59 +72,56 @@ make deploy make undeploy ``` -## Usage +--- -To create a Kubernetes Secret from a 1Password item, create a yaml file with the following +## Use Kubernetes Operator with Connect +### 1. [Deploy with Helm](https://developer.1password.com/docs/k8s/operator/?deployment-type=helm#helm-step-1) +### 2. [Deploy manually](https://developer.1password.com/docs/k8s/operator/?deployment-type=manual#manual-step-1) + +To further configure the 1Password Kubernetes Operator the following Environment variables can be set in the operator yaml: + +- **OP_CONNECT_HOST** *(required)*: Specifies the host name within Kubernetes in which to access the 1Password Connect. +- **WATCH_NAMESPACE:** *(default: watch all namespaces)*: Comma separated list of what Namespaces to watch for changes. +- **POLLING_INTERVAL** *(default: 600)*: The number of seconds the 1Password Kubernetes Operator will wait before checking for updates from 1Password Connect. +- **MANAGE_CONNECT** *(default: false)*: If set to true, on deployment of the operator, a default configuration of the OnePassword Connect Service will be deployed to the current namespace. +- **AUTO_RESTART** (default: false): If set to true, the operator will restart any deployment using a secret from 1Password Connect. This can be overwritten by namespace, deployment, or individual secret. More details on AUTO_RESTART can be found in the ["Configuring Automatic Rolling Restarts of Deployments"](#configuring-automatic-rolling-restarts-of-deployments) section. + +--- + +## Logging level +You can set the logging level by setting `--zap-log-level` as an arg on the containers to either `debug`, `info` or `error`. The default value is `debug`. + +Example: ```yaml -apiVersion: onepassword.com/v1 -kind: OnePasswordItem -metadata: - name: #this name will also be used for naming the generated kubernetes secret -spec: - itemPath: "vaults//items/" +.... +containers: + - command: + - /manager + args: + - --leader-elect + - --zap-log-level=info + image: 1password/onepassword-operator:latest +.... ``` -Deploy the OnePasswordItem to Kubernetes: +--- -```bash -kubectl apply -f .yaml -``` +## [Usage examples](https://developer.1password.com/docs/k8s/operator/?deployment-type=manual#usage-examples) -To test that the Kubernetes Secret check that the following command returns a secret: +--- -```bash -kubectl get secret -``` - -**Note:** Deleting the `OnePasswordItem` that you've created will automatically delete the created Kubernetes Secret. - -To create a single Kubernetes Secret for a deployment, add the following annotations to the deployment metadata: - -```yaml -apiVersion: apps/v1 -kind: Deployment -metadata: - name: deployment-example - annotations: - operator.1password.io/item-path: "vaults//items/" - operator.1password.io/item-name: "" -``` - -Applying this yaml file will create a Kubernetes Secret with the name `` and contents from the location specified at the specified Item Path. +## How 1Password Items Map to Kubernetes Secrets The contents of the Kubernetes secret will be key-value pairs in which the keys are the fields of the 1Password item and the values are the corresponding values stored in 1Password. In case of fields that store files, the file's contents will be used as the value. Within an item, if both a field storing a file and a field of another type have the same name, the file field will be ignored and the other field will take precedence. -**Note:** Deleting the Deployment that you've created will automatically delete the created Kubernetes Secret only if the deployment is still annotated with `operator.1password.io/item-path` and `operator.1password.io/item-name` and no other deployment is using the secret. +Deleting the Deployment that you've created will automatically delete the created Kubernetes Secret only if the deployment is still annotated with `operator.1password.io/item-path` and `operator.1password.io/item-name` and no other deployment is using the secret. If a 1Password Item that is linked to a Kubernetes Secret is updated within the POLLING_INTERVAL the associated Kubernetes Secret will be updated. However, if you do not want a specific secret to be updated you can add the tag `operator.1password.io:ignore-secret` to the item stored in 1Password. While this tag is in place, any updates made to an item will not trigger an update to the associated secret in Kubernetes. ---- - -**NOTE** If multiple 1Password vaults/items have the same `title` when using a title in the access path, the desired action will be performed on the oldest vault/item. @@ -302,6 +188,8 @@ metadata: If the value is not set, the auto restart settings on the deployment will be used. +--- + ## Development ### How it works From ef7361b3c1a1a79389339ff170d68c3e2c89a9a7 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 13 Jun 2025 16:22:18 -0500 Subject: [PATCH 3/4] Bump go version to 1.24 --- Dockerfile | 2 +- go.mod | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 207ceb3..385d016 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.22 as builder +FROM golang:1.24 as builder ARG TARGETOS ARG TARGETARCH diff --git a/go.mod b/go.mod index d383404..bf42587 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,8 @@ module github.com/1Password/onepassword-operator -go 1.22.0 +go 1.24 -toolchain go1.24.1 +toolchain go1.24.4 require ( github.com/1Password/connect-sdk-go v1.5.3 From 83b294690ae8699c82bd48daeb043bd5d6d6ea40 Mon Sep 17 00:00:00 2001 From: Volodymyr Zotov Date: Fri, 13 Jun 2025 16:23:45 -0500 Subject: [PATCH 4/4] Revert version change, as should be done in the release MR --- version/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version/version.go b/version/version.go index c46e83f..3557c0f 100644 --- a/version/version.go +++ b/version/version.go @@ -1,6 +1,6 @@ package version var ( - OperatorVersion = "1.9.0" + OperatorVersion = "1.8.1" OperatorSDKVersion = "1.34.1" )