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.
This commit is contained in:
Joris Coenen
2022-06-15 17:46:56 +02:00
parent 0796b9c5e2
commit 6c20db47d6
5 changed files with 121 additions and 11 deletions

View File

@@ -12,8 +12,6 @@ spec:
scope: Namespaced scope: Namespaced
versions: versions:
- name: v1 - name: v1
served: true
storage: true
schema: schema:
openAPIV3Schema: openAPIV3Schema:
description: OnePasswordItem is the Schema for the onepassworditems API description: OnePasswordItem is the Schema for the onepassworditems API
@@ -38,8 +36,43 @@ spec:
type: object type: object
status: status:
description: OnePasswordItemStatus defines the observed state of OnePasswordItem 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: object
type: type:
description: 'Kubernetes secret type. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types'
type: string type: string
type: object type: object
served: true
storage: true
subresources:
status: {}

View File

@@ -11,11 +11,34 @@ type OnePasswordItemSpec struct {
ItemPath string `json:"itemPath,omitempty"` 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 // OnePasswordItemStatus defines the observed state of OnePasswordItem
type OnePasswordItemStatus struct { 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 // 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 // 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 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

View File

@@ -1,3 +1,4 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated // +build !ignore_autogenerated
// Code generated by operator-sdk. DO NOT EDIT. // Code generated by operator-sdk. DO NOT EDIT.
@@ -14,7 +15,7 @@ func (in *OnePasswordItem) DeepCopyInto(out *OnePasswordItem) {
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec out.Spec = in.Spec
out.Status = in.Status in.Status.DeepCopyInto(&out.Status)
return return
} }
@@ -36,6 +37,23 @@ func (in *OnePasswordItem) DeepCopyObject() runtime.Object {
return nil 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OnePasswordItemList) DeepCopyInto(out *OnePasswordItemList) { func (in *OnePasswordItemList) DeepCopyInto(out *OnePasswordItemList) {
*out = *in *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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OnePasswordItemStatus) DeepCopyInto(out *OnePasswordItemStatus) { func (in *OnePasswordItemStatus) DeepCopyInto(out *OnePasswordItemStatus) {
*out = *in *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 return
} }

View File

@@ -96,10 +96,11 @@ func (r *ReconcileOnePasswordItem) Reconcile(request reconcile.Request) (reconci
} }
// Handles creation or updating secrets for deployment if needed // Handles creation or updating secrets for deployment if needed
if err := r.HandleOnePasswordItem(onepassworditem, request); err != nil { err := r.HandleOnePasswordItem(onepassworditem, request)
return reconcile.Result{}, err 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 one password finalizer exists then we must cleanup associated secrets
if utils.ContainsString(onepassworditem.ObjectMeta.Finalizers, finalizer) { 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) 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)
}

View File

@@ -45,8 +45,7 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
if autoRestart != "" { if autoRestart != "" {
_, err := utils.StringToBool(autoRestart) _, err := utils.StringToBool(autoRestart)
if err != nil { if err != nil {
log.Error(err, "Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName) return fmt.Errorf("Error parsing %v annotation on Secret %v. Must be true or false. Defaulting to false.", RestartDeploymentsAnnotation, secretName)
return err
} }
secretAnnotations[RestartDeploymentsAnnotation] = autoRestart secretAnnotations[RestartDeploymentsAnnotation] = autoRestart
} }
@@ -75,7 +74,10 @@ func CreateKubernetesSecretFromItem(kubeClient kubernetesClient.Client, secretNa
currentSecret.ObjectMeta.Annotations = secretAnnotations currentSecret.ObjectMeta.Annotations = secretAnnotations
currentSecret.ObjectMeta.Labels = labels currentSecret.ObjectMeta.Labels = labels
currentSecret.Data = secret.Data 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])) log.Info(fmt.Sprintf("Secret with name %v and version %v already exists", secret.Name, secret.Annotations[VersionAnnotation]))