diff --git a/deploy/crds/onepassword.com_onepassworditems_crd.yaml b/deploy/crds/onepassword.com_onepassworditems_crd.yaml index 2a8dc9e..64b8534 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,41 @@ 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 + 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..c056fd0 100644 --- a/pkg/apis/onepassword/v1/onepasswordsecret_types.go +++ b/pkg/apis/onepassword/v1/onepasswordsecret_types.go @@ -11,11 +11,31 @@ 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"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -26,7 +46,9 @@ type OnePasswordItemStatus struct { type OnePasswordItem struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Type string `json:"type,omitempty"` + + // Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types + Type string `json:"type,omitempty"` Spec OnePasswordItemSpec `json:"spec,omitempty"` Status OnePasswordItemStatus `json:"status,omitempty"` diff --git a/pkg/apis/onepassword/v1/zz_generated.deepcopy.go b/pkg/apis/onepassword/v1/zz_generated.deepcopy.go index 1ecff98..024797c 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,13 @@ 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]) + } + } return } diff --git a/pkg/controller/onepassworditem/onepassworditem_controller.go b/pkg/controller/onepassworditem/onepassworditem_controller.go index 613aa41..7a17462 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,34 @@ 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 { + existingCondition := findCondition(resource.Status.Conditions, onepasswordv1.OnePasswordItemReady) + updatedCondition := existingCondition + if err != nil { + updatedCondition.Message = err.Error() + updatedCondition.Status = metav1.ConditionFalse + } else { + updatedCondition.Message = "" + updatedCondition.Status = metav1.ConditionTrue + } + + if existingCondition.Status != updatedCondition.Status { + updatedCondition.LastTransitionTime = metav1.Now() + } + + resource.Status.Conditions = []onepasswordv1.OnePasswordItemCondition{updatedCondition} + return r.kubeClient.Status().Update(context.Background(), resource) +} + +func findCondition(conditions []onepasswordv1.OnePasswordItemCondition, t onepasswordv1.OnePasswordItemConditionType) onepasswordv1.OnePasswordItemCondition { + for _, c := range conditions { + if c.Type == t { + return c + } + } + return onepasswordv1.OnePasswordItemCondition{ + Type: t, + Status: metav1.ConditionUnknown, + } +} diff --git a/pkg/kubernetessecrets/kubernetes_secrets_builder.go b/pkg/kubernetessecrets/kubernetes_secrets_builder.go index 47987cc..ccc3a81 100644 --- a/pkg/kubernetessecrets/kubernetes_secrets_builder.go +++ b/pkg/kubernetessecrets/kubernetes_secrets_builder.go @@ -44,8 +44,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 } @@ -83,7 +82,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]))