From e7325cacb8c05abdcf219be23291b228d1299449 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20D=C3=B6ll?= Date: Wed, 11 Dec 2024 12:36:20 +0000 Subject: [PATCH] wip: setup natsconfig --- api/v1alpha1/nats_config_types.go | 55 ++++++ api/v1alpha1/zz_generated.deepcopy.go | 97 ++++++++++ controllers/natsconfig_controller.go | 181 ++++++++++++++++++ controllers/natsoperator_controller.go | 98 ---------- examples/config.yaml | 5 + examples/system_account.yaml | 2 +- .../crd/bases/natz.zeiss.com_natsconfigs.yaml | 126 ++++++++++++ manifests/crd/kustomization.yaml | 1 + pkg/status/status.go | 29 +++ 9 files changed, 495 insertions(+), 99 deletions(-) create mode 100644 api/v1alpha1/nats_config_types.go create mode 100644 controllers/natsconfig_controller.go create mode 100644 examples/config.yaml create mode 100644 manifests/crd/bases/natz.zeiss.com_natsconfigs.yaml diff --git a/api/v1alpha1/nats_config_types.go b/api/v1alpha1/nats_config_types.go new file mode 100644 index 0000000..3c5237e --- /dev/null +++ b/api/v1alpha1/nats_config_types.go @@ -0,0 +1,55 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type ConfigPhase string + +const ( + ConfigPhaseNone ConfigPhase = "" + ConfigPhaseCreating ConfigPhase = "Creating" + ConfigPhaseActive ConfigPhase = "Active" + ConfigPhaseFailed ConfigPhase = "Failed" +) + +// NatsConfigSpec defines the desired state of NatsConfig +type NatsConfigSpec struct{} + +// NatsConfigStatus defines the observed state of NatsConfig +type NatsConfigStatus struct { + // Conditions is an array of conditions that the operator is currently in. + Conditions []metav1.Condition `json:"conditions,omitempty" optional:"true"` + // Phase is the current phase of the operator. + // + // +kubebuilder:validation:Enum={None,Pending,Creating,Synchronized,Failed} + Phase ConfigPhase `json:"phase"` + // ControlPaused is a flag that indicates if the operator is paused. + ControlPaused bool `json:"controlPaused,omitempty" optional:"true"` + // LastUpdate is the timestamp of the last update. + LastUpdate metav1.Time `json:"lastUpdate,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +type NatsConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NatsConfigSpec `json:"spec,omitempty"` + Status NatsConfigStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// NatsConfigList contains a list of NatsConfig +type NatsConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NatsConfig `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NatsConfig{}, &NatsConfigList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 186cd3a..dafd934 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -207,6 +207,103 @@ func (in *NatsAccountStatus) DeepCopy() *NatsAccountStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NatsConfig) DeepCopyInto(out *NatsConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatsConfig. +func (in *NatsConfig) DeepCopy() *NatsConfig { + if in == nil { + return nil + } + out := new(NatsConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NatsConfig) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NatsConfigList) DeepCopyInto(out *NatsConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NatsConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatsConfigList. +func (in *NatsConfigList) DeepCopy() *NatsConfigList { + if in == nil { + return nil + } + out := new(NatsConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NatsConfigList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NatsConfigSpec) DeepCopyInto(out *NatsConfigSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatsConfigSpec. +func (in *NatsConfigSpec) DeepCopy() *NatsConfigSpec { + if in == nil { + return nil + } + out := new(NatsConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NatsConfigStatus) DeepCopyInto(out *NatsConfigStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.LastUpdate.DeepCopyInto(&out.LastUpdate) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NatsConfigStatus. +func (in *NatsConfigStatus) DeepCopy() *NatsConfigStatus { + if in == nil { + return nil + } + out := new(NatsConfigStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NatsGateway) DeepCopyInto(out *NatsGateway) { *out = *in diff --git a/controllers/natsconfig_controller.go b/controllers/natsconfig_controller.go new file mode 100644 index 0000000..9a47d02 --- /dev/null +++ b/controllers/natsconfig_controller.go @@ -0,0 +1,181 @@ +package controllers + +import ( + "context" + "math" + "time" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + natsv1alpha1 "github.com/zeiss/natz-operator/api/v1alpha1" + "github.com/zeiss/natz-operator/pkg/status" + "github.com/zeiss/pkg/conv" + "github.com/zeiss/pkg/slices" + "github.com/zeiss/pkg/utilx" +) + +const ( + EventReasonConfigSynchronizeFailed EventReason = "ConfigSynchronizeFailed" + EventReasonConfigSynchronized EventReason = "configSynchronized" +) + +// NatsConfigReconciler reconciles a Natsconfig object +type NatsConfigReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +// NewNatsConfigReconciler ... +func NewNatsConfigReconciler(mgr ctrl.Manager) *NatsConfigReconciler { + return &NatsConfigReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor(EventRecorderLabel), + } +} + +//+kubebuilder:rbac:groups=natz.zeiss.com,resources=natsconfig,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=natz.zeiss.com,resources=natsconfig/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=natz.zeiss.com,resources=natsconfig/finalizers,verbs=update + +// Reconcile ... +func (r *NatsConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + config := &natsv1alpha1.NatsConfig{} + if err := r.Get(ctx, req.NamespacedName, config); err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + + return ctrl.Result{}, err + } + + if !config.ObjectMeta.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, config) + } + + // get latest version of the config + if err := r.Get(ctx, req.NamespacedName, config); err != nil { + return reconcile.Result{}, err + } + + return r.reconcileResources(ctx, config) +} + +func (r *NatsConfigReconciler) reconcileDelete(ctx context.Context, obj *natsv1alpha1.NatsConfig) (ctrl.Result, error) { + // Remove our finalizer from the list. + controllerutil.RemoveFinalizer(obj, natsv1alpha1.FinalizerName) + + if !obj.DeletionTimestamp.IsZero() { + // Remove our finalizer from the list. + controllerutil.RemoveFinalizer(obj, natsv1alpha1.FinalizerName) + + // Stop reconciliation as the object is being deleted. + return ctrl.Result{}, r.Update(ctx, obj) + } + + return ctrl.Result{Requeue: true}, nil +} + +func (r *NatsConfigReconciler) reconcileResources(ctx context.Context, config *natsv1alpha1.NatsConfig) (ctrl.Result, error) { + if err := r.reconcileStatus(ctx, config); err != nil { + return r.ManageError(ctx, config, err) + } + + // if err := r.reconcileconfig(ctx, config); err != nil { + // return r.ManageError(ctx, config, err) + // } + + return r.ManageSuccess(ctx, config) +} + +func (r *NatsConfigReconciler) reconcileConfig(ctx context.Context, config *natsv1alpha1.NatsConfig) error { + // if !controllerutil.ContainsFinalizer(config, natsv1alpha1.FinalizerName) { + // controllerutil.AddFinalizer(config, natsv1alpha1.FinalizerName) + // } + + // if !controllerutil.HasControllerReference(config) { + // if err := controllerutil.SetControllerReference(config, pk, r.Scheme); err != nil { + // return err + // } + // } + + return nil +} + +func (r *NatsConfigReconciler) reconcileStatus(ctx context.Context, config *natsv1alpha1.NatsConfig) error { + return nil +} + +// IsCreating ... +func (r *NatsConfigReconciler) IsCreating(obj *natsv1alpha1.NatsConfig) bool { + return utilx.Or(obj.Status.Conditions == nil, slices.Len(obj.Status.Conditions) == 0) +} + +// IsSynchronized ... +func (r *NatsConfigReconciler) IsSynchronized(obj *natsv1alpha1.NatsConfig) bool { + return obj.Status.Phase == natsv1alpha1.ConfigPhaseActive +} + +// ManageError ... +func (r *NatsConfigReconciler) ManageError(ctx context.Context, obj *natsv1alpha1.NatsConfig, err error) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + logger.Error(err, "reconciliation failed", "config", obj) + + status.SetNatzConfigCondition(obj, status.NewNatzConfigFailedCondition(obj, err)) + + if err := r.Client.Status().Update(ctx, obj); err != nil { + return ctrl.Result{Requeue: true, RequeueAfter: time.Second}, err + } + + r.Recorder.Event(obj, corev1.EventTypeWarning, conv.String(EventReasonConfigSynchronizeFailed), "config synchronization failed") + + var retryInterval time.Duration + + return reconcile.Result{ + RequeueAfter: time.Duration(math.Min(float64(retryInterval.Nanoseconds()*2), float64(time.Hour.Nanoseconds()*6))), + Requeue: true, + }, nil +} + +// ManageSuccess ... +func (r *NatsConfigReconciler) ManageSuccess(ctx context.Context, obj *natsv1alpha1.NatsConfig) (ctrl.Result, error) { + if r.IsSynchronized(obj) { + return ctrl.Result{}, nil + } + + status.SetNatzConfigCondition(obj, status.NewNatzConfigSynchronizedCondition(obj)) + + if r.IsCreating(obj) { + return ctrl.Result{Requeue: true}, nil + } + + if err := r.Client.Status().Update(ctx, obj); err != nil { + return ctrl.Result{}, err + } + + if !obj.ObjectMeta.DeletionTimestamp.IsZero() { + return ctrl.Result{Requeue: true}, nil + } + + r.Recorder.Event(obj, corev1.EventTypeNormal, conv.String(EventReasonConfigSynchronized), "config synchronized") + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *NatsConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&natsv1alpha1.NatsConfig{}). + Owns(&corev1.Secret{}). + Complete(r) +} diff --git a/controllers/natsoperator_controller.go b/controllers/natsoperator_controller.go index 30e7d21..6737323 100644 --- a/controllers/natsoperator_controller.go +++ b/controllers/natsoperator_controller.go @@ -98,11 +98,6 @@ func (r *NatsOperatorReconciler) reconcileResources(ctx context.Context, operato return err } - // err = r.reconcileSystemAccount(ctx, operator) - // if err != nil { - // return err - // } - err = r.reconcileServerConfig(ctx, operator) if err != nil { return err @@ -111,95 +106,6 @@ func (r *NatsOperatorReconciler) reconcileResources(ctx context.Context, operato return err } -// func (r *NatsOperatorReconciler) reconcileSystemAccount(ctx context.Context, operator *natsv1alpha1.NatsOperator) error { -// systemAccount := &natsv1alpha1.NatsAccount{} -// systemAccountName := client.ObjectKey{ -// Namespace: operator.Namespace, -// Name: fmt.Sprintf("%v-system", operator.Name), -// } - -// if err := r.Get(ctx, systemAccountName, systemAccount); !errors.IsNotFound(err) { -// return err -// } - -// systemAccount.Name = systemAccountName.Name -// systemAccount.Namespace = systemAccountName.Namespace - -// pk := &natsv1alpha1.NatsPrivateKey{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: fmt.Sprintf("%v-system-private-key", operator.Name), -// Namespace: operator.Namespace, -// }, -// Spec: natsv1alpha1.NatsPrivateKeySpec{ -// Type: "Account", -// }, -// } - -// _, err := controllerutil.CreateOrUpdate(ctx, r.Client, pk, func() error { -// return nil -// }) -// if err != nil { -// return err -// } - -// _, err = controllerutil.CreateOrUpdate(ctx, r.Client, systemAccount, func() error { -// systemAccount.Spec = natsv1alpha1.NatsAccountSpec{ -// PrivateKey: natsv1alpha1.NatsPrivateKeyReference{ -// Name: pk.ObjectMeta.Name, -// }, -// OperatorSigningKey: natsv1alpha1.NatsSigningKeyReference{ -// Name: operator.Spec.SigningKeys[0].Name, -// }, -// AllowUserNamespaces: []string{ -// operator.Namespace, -// }, -// Exports: []natsv1alpha1.Export{ -// { -// Name: "account-monitoring-services", -// Subject: "$SYS.REQ.ACCOUNT.*.*", -// Type: natsv1alpha1.Service, -// ResponseType: jwt.ResponseTypeStream, -// AccountTokenPosition: 4, -// Info: jwt.Info{ -// Description: `Request account specific monitoring services for: SUBSZ, CONNZ, LEAFZ, JSZ and INFO`, -// InfoURL: "https://docs.nats.io/nats-server/configuration/sys_accounts", -// }, -// }, -// { -// Name: "account-monitoring-streams", -// Subject: "$SYS.ACCOUNT.*.>", -// Type: natsv1alpha1.Stream, -// AccountTokenPosition: 3, -// Info: jwt.Info{ -// Description: `Account specific monitoring stream`, -// InfoURL: "https://docs.nats.io/nats-server/configuration/sys_accounts", -// }, -// }, -// }, -// Limits: natsv1alpha1.OperatorLimits{ -// NatsLimits: jwt.NatsLimits{ -// Subs: -1, -// Payload: -1, -// Data: -1, -// }, -// AccountLimits: jwt.AccountLimits{ -// Conn: -1, -// Exports: -1, -// WildcardExports: true, -// DisallowBearer: true, -// }, -// }, -// } - -// return nil -// }) -// if err != nil { -// return err -// } - -// return nil -// } - func (r *NatsOperatorReconciler) reconcileOperator(ctx context.Context, obj *natsv1alpha1.NatsOperator) error { pk := &corev1.Secret{} pkName := client.ObjectKey{ @@ -289,10 +195,6 @@ func (r *NatsOperatorReconciler) reconcileServerConfig(ctx context.Context, oper } func (r *NatsOperatorReconciler) reconcileStatus(ctx context.Context, operator *natsv1alpha1.NatsOperator) error { - log := log.FromContext(ctx) - - log.Info("reconcile status", "name", operator.Name, "namespace", operator.Namespace) - phase := utilx.IfElse( utilx.Empty(operator.Status.PublicKey) && utilx.Empty(operator.Status.JWT), natsv1alpha1.OperatorPhasePending, diff --git a/examples/config.yaml b/examples/config.yaml new file mode 100644 index 0000000..eef7dd0 --- /dev/null +++ b/examples/config.yaml @@ -0,0 +1,5 @@ +apiVersion: natz.zeiss.com/v1alpha1 +kind: NatsConfig +metadata: + name: nats-default-config +spec: diff --git a/examples/system_account.yaml b/examples/system_account.yaml index 0fa2ac4..79e729f 100644 --- a/examples/system_account.yaml +++ b/examples/system_account.yaml @@ -22,7 +22,7 @@ spec: privateKey: name: natsoperator-system-private-key signingKeys: - - name: natsoperator-system-signing-key+ + - name: natsoperator-system-signing-key exports: - name: account-monitoring-services subject: $SYS.REQ.ACCOUNT.*.* diff --git a/manifests/crd/bases/natz.zeiss.com_natsconfigs.yaml b/manifests/crd/bases/natz.zeiss.com_natsconfigs.yaml new file mode 100644 index 0000000..c8f332c --- /dev/null +++ b/manifests/crd/bases/natz.zeiss.com_natsconfigs.yaml @@ -0,0 +1,126 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.3 + name: natsconfigs.natz.zeiss.com +spec: + group: natz.zeiss.com + names: + kind: NatsConfig + listKind: NatsConfigList + plural: natsconfigs + singular: natsconfig + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NatsConfigSpec defines the desired state of NatsConfig + type: object + status: + description: NatsConfigStatus defines the observed state of NatsConfig + properties: + conditions: + description: Conditions is an array of conditions that the operator + is currently in. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + controlPaused: + description: ControlPaused is a flag that indicates if the operator + is paused. + type: boolean + lastUpdate: + description: LastUpdate is the timestamp of the last update. + format: date-time + type: string + phase: + description: Phase is the current phase of the operator. + enum: + - None + - Pending + - Creating + - Synchronized + - Failed + type: string + required: + - phase + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/manifests/crd/kustomization.yaml b/manifests/crd/kustomization.yaml index 716efb0..b1acf55 100644 --- a/manifests/crd/kustomization.yaml +++ b/manifests/crd/kustomization.yaml @@ -8,6 +8,7 @@ resources: - bases/natz.zeiss.com_natsgateways.yaml - bases/natz.zeiss.com_natssigningkeys.yaml - bases/natz.zeiss.com_natsprivatekeys.yaml + - bases/natz.zeiss.com_natsconfigs.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/pkg/status/status.go b/pkg/status/status.go index c1ae3d2..09afb27 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -57,6 +57,35 @@ func SetNatzUserCondition(obj *natsv1alpha1.NatsUser, condition metav1.Condition obj.Status.Conditions = SetCondition(condition, obj.Status.Conditions...) } +// SetNatzConfigCondition ... +func SetNatzConfigCondition(obj *natsv1alpha1.NatsConfig, condition metav1.Condition) { + obj.Status.Conditions = SetCondition(condition, obj.Status.Conditions...) +} + +// NewNatzConfigSynchronizedCondition creates the provisioning started condition in cluster conditions. +func NewNatzConfigSynchronizedCondition(obj *natsv1alpha1.NatsConfig) metav1.Condition { + return metav1.Condition{ + Type: natsv1alpha1.ConditionTypeSynchronized, + ObservedGeneration: obj.Generation, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Message: fmt.Sprintf("the config has successfully created: %s", obj.Name), + Reason: natsv1alpha1.ConditionReasonSynchronized, + } +} + +// NewNatzConfigFailedCondition creates the provisioning started condition in cluster conditions. +func NewNatzConfigFailedCondition(obj *natsv1alpha1.NatsConfig, err error) metav1.Condition { + return metav1.Condition{ + Type: natsv1alpha1.ConditionTypeFailed, + ObservedGeneration: obj.Generation, + Status: metav1.ConditionTrue, + LastTransitionTime: metav1.Now(), + Message: err.Error(), + Reason: natsv1alpha1.ConditionReasonFailed, + } +} + // NewPrivateKeySychronizedCondition creates the provisioning started condition in cluster conditions. func NewPrivateKeySychronizedCondition(obj *natsv1alpha1.NatsPrivateKey) metav1.Condition { return metav1.Condition{