From 6c20db47d6171bdcfe5e5e0ff834571f4d5e7c4f Mon Sep 17 00:00:00 2001 From: Joris Coenen Date: Wed, 15 Jun 2022 17:46:56 +0200 Subject: [PATCH] Add Status field to OnePasswordItem resource This makes it easier to see whehter the controller succeeded in creating the Kubernetes secret for a OnePasswordItem. If something failed, the `ready` field will be `false` and the `OnePasswordItemReady` condition will have a `status` of `False` with the error messages in the `message` field. --- .../onepassword.com_onepassworditems_crd.yaml | 39 +++++++++++++++++-- .../onepassword/v1/onepasswordsecret_types.go | 25 +++++++++++- .../onepassword/v1/zz_generated.deepcopy.go | 32 ++++++++++++++- .../onepassworditem_controller.go | 28 +++++++++++-- .../kubernetes_secrets_builder.go | 8 ++-- 5 files changed, 121 insertions(+), 11 deletions(-) diff --git a/deploy/crds/onepassword.com_onepassworditems_crd.yaml b/deploy/crds/onepassword.com_onepassworditems_crd.yaml index 2a8dc9e..3515c4b 100644 --- a/deploy/crds/onepassword.com_onepassworditems_crd.yaml +++ b/deploy/crds/onepassword.com_onepassworditems_crd.yaml @@ -12,8 +12,6 @@ spec: scope: Namespaced versions: - name: v1 - served: true - storage: true schema: openAPIV3Schema: description: OnePasswordItem is the Schema for the onepassworditems API @@ -38,8 +36,43 @@ spec: type: object status: description: OnePasswordItemStatus defines the observed state of OnePasswordItem + properties: + conditions: + description: 'Important: Run "operator-sdk generate k8s" to regenerate + code after modifying this file Add custom validation using kubebuilder + tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html' + items: + properties: + lastTransitionTime: + description: Last time the condition transit from one status + to another. + format: date-time + type: string + message: + description: Human-readable message indicating details about + last transition. + type: string + status: + description: Status of the condition, one of True, False, Unknown. + type: string + type: + description: Type of job condition, Completed. + type: string + required: + - status + - type + type: object + type: array + ready: + description: True when the Kubernetes secret is ready for use. + type: boolean + required: + - conditions type: object type: - description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types' type: string type: object + served: true + storage: true + subresources: + status: {} diff --git a/pkg/apis/onepassword/v1/onepasswordsecret_types.go b/pkg/apis/onepassword/v1/onepasswordsecret_types.go index a2e9ba3..3f4389e 100644 --- a/pkg/apis/onepassword/v1/onepasswordsecret_types.go +++ b/pkg/apis/onepassword/v1/onepasswordsecret_types.go @@ -11,11 +11,34 @@ type OnePasswordItemSpec struct { ItemPath string `json:"itemPath,omitempty"` } +type OnePasswordItemConditionType string + +const ( + // OnePasswordItemReady means the Kubernetes secret is ready for use. + OnePasswordItemReady OnePasswordItemConditionType = "Ready" +) + +type OnePasswordItemCondition struct { + // Type of job condition, Completed. + Type OnePasswordItemConditionType `json:"type"` + // Status of the condition, one of True, False, Unknown. + Status metav1.ConditionStatus `json:"status"` + // Last time the condition transit from one status to another. + // +optional + LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"` + // Human-readable message indicating details about last transition. + // +optional + Message string `json:"message,omitempty"` +} + // OnePasswordItemStatus defines the observed state of OnePasswordItem type OnePasswordItemStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html + Conditions []OnePasswordItemCondition `json:"conditions"` + + // True when the Kubernetes secret is ready for use. + Ready *bool `json:"ready,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/apis/onepassword/v1/zz_generated.deepcopy.go b/pkg/apis/onepassword/v1/zz_generated.deepcopy.go index 1ecff98..158a327 100644 --- a/pkg/apis/onepassword/v1/zz_generated.deepcopy.go +++ b/pkg/apis/onepassword/v1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated // Code generated by operator-sdk. DO NOT EDIT. @@ -14,7 +15,7 @@ func (in *OnePasswordItem) DeepCopyInto(out *OnePasswordItem) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = in.Spec - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) return } @@ -36,6 +37,23 @@ func (in *OnePasswordItem) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OnePasswordItemCondition) DeepCopyInto(out *OnePasswordItemCondition) { + *out = *in + in.LastTransitionTime.DeepCopyInto(&out.LastTransitionTime) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OnePasswordItemCondition. +func (in *OnePasswordItemCondition) DeepCopy() *OnePasswordItemCondition { + if in == nil { + return nil + } + out := new(OnePasswordItemCondition) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OnePasswordItemList) DeepCopyInto(out *OnePasswordItemList) { *out = *in @@ -88,6 +106,18 @@ func (in *OnePasswordItemSpec) DeepCopy() *OnePasswordItemSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OnePasswordItemStatus) DeepCopyInto(out *OnePasswordItemStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]OnePasswordItemCondition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Ready != nil { + in, out := &in.Ready, &out.Ready + *out = new(bool) + **out = **in + } return } diff --git a/pkg/controller/onepassworditem/onepassworditem_controller.go b/pkg/controller/onepassworditem/onepassworditem_controller.go index 613aa41..2ca870b 100644 --- a/pkg/controller/onepassworditem/onepassworditem_controller.go +++ b/pkg/controller/onepassworditem/onepassworditem_controller.go @@ -96,10 +96,11 @@ func (r *ReconcileOnePasswordItem) Reconcile(request reconcile.Request) (reconci } // Handles creation or updating secrets for deployment if needed - if err := r.HandleOnePasswordItem(onepassworditem, request); err != nil { - return reconcile.Result{}, err + err := r.HandleOnePasswordItem(onepassworditem, request) + if updateStatusErr := r.updateStatus(onepassworditem, err); updateStatusErr != nil { + return reconcile.Result{}, fmt.Errorf("cannot update status: %s", updateStatusErr) } - return reconcile.Result{}, nil + return reconcile.Result{}, err } // If one password finalizer exists then we must cleanup associated secrets if utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) { @@ -169,3 +170,24 @@ func (r *ReconcileOnePasswordItem) HandleOnePasswordItem(resource *onepasswordv1 return kubeSecrets.CreateKubernetesSecretFromItem(r.kubeClient, secretName, resource.Namespace, item, autoRestart, labels, secretType, ownerRef) } + +func (r *ReconcileOnePasswordItem) updateStatus(resource *onepasswordv1.OnePasswordItem, err error) error { + condition := onepasswordv1.OnePasswordItemCondition{ + Type: onepasswordv1.OnePasswordItemReady, + Status: metav1.ConditionTrue, + } + if err != nil { + condition.Message = err.Error() + condition.Status = metav1.ConditionFalse + } + + ready := err == nil + if resource.Status.Ready == nil || ready != *resource.Status.Ready { + condition.LastTransitionTime = metav1.Now() + } + + resource.Status.Ready = &ready + + resource.Status.Conditions = []onepasswordv1.OnePasswordItemCondition{condition} + return r.kubeClient.Status().Update(context.Background(), resource) +} diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index b7587c7..64fe064 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -45,8 +45,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa if autoRestart != "" { _, err := utils.StringToBool(autoRestart) if err != nil { - log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName) - return err + return fmt.Errorf("Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName) } secretAnnotations[RestartDeploymentsAnnotation] = autoRestart } @@ -75,7 +74,10 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa currentSecret.ObjectMeta.Annotations = secretAnnotations currentSecret.ObjectMeta.Labels = labels currentSecret.Data = secret.Data - return kubeClient.Update(context.Background(), currentSecret) + if err := kubeClient.Update(context.Background(), currentSecret); err != nil { + return fmt.Errorf("Kubernetes secret update failed: %w", err) + } + return nil } log.Info(fmt.Sprintf("Secret with name %v and version %v already exists", secret.Name, secret.Annotations[VersionAnnotation]))