diff --git a/k8-operator/api/v1alpha1/infisicaldynamicsecret_types.go b/k8-operator/api/v1alpha1/infisicaldynamicsecret_types.go index 142cb671f..a55e215a3 100644 --- a/k8-operator/api/v1alpha1/infisicaldynamicsecret_types.go +++ b/k8-operator/api/v1alpha1/infisicaldynamicsecret_types.go @@ -65,10 +65,10 @@ type InfisicalDynamicSecretSpec struct { // InfisicalDynamicSecretStatus defines the observed state of InfisicalDynamicSecret. type InfisicalDynamicSecretStatus struct { - Lease *InfisicalDynamicSecretLease `json:"lease,omitempty"` - - DynamicSecretID string `json:"dynamicSecretId,omitempty"` + Conditions []metav1.Condition `json:"conditions"` + Lease *InfisicalDynamicSecretLease `json:"lease,omitempty"` + DynamicSecretID string `json:"dynamicSecretId,omitempty"` // The MaxTTL can be null, if it's null, there's no max TTL and we should never have to renew. MaxTTL string `json:"maxTTL,omitempty"` } diff --git a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go index d30811f71..bad990bc4 100644 --- a/k8-operator/api/v1alpha1/zz_generated.deepcopy.go +++ b/k8-operator/api/v1alpha1/zz_generated.deepcopy.go @@ -354,6 +354,13 @@ func (in *InfisicalDynamicSecretSpec) DeepCopy() *InfisicalDynamicSecretSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *InfisicalDynamicSecretStatus) DeepCopyInto(out *InfisicalDynamicSecretStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } if in.Lease != nil { in, out := &in.Lease, &out.Lease *out = new(InfisicalDynamicSecretLease) diff --git a/k8-operator/config/crd/bases/secrets.infisical.com_infisicaldynamicsecrets.yaml b/k8-operator/config/crd/bases/secrets.infisical.com_infisicaldynamicsecrets.yaml index 7595709a1..c1cb7255d 100644 --- a/k8-operator/config/crd/bases/secrets.infisical.com_infisicaldynamicsecrets.yaml +++ b/k8-operator/config/crd/bases/secrets.infisical.com_infisicaldynamicsecrets.yaml @@ -203,6 +203,74 @@ spec: description: InfisicalDynamicSecretStatus defines the observed state of InfisicalDynamicSecret. properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + 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. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + 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 dynamicSecretId: type: string lease: @@ -228,6 +296,8 @@ spec: description: The MaxTTL can be null, if it's null, there's no max TTL and we should never have to renew. type: string + required: + - conditions type: object type: object served: true diff --git a/k8-operator/controllers/infisicaldynamicsecret/conditions.go b/k8-operator/controllers/infisicaldynamicsecret/conditions.go new file mode 100644 index 000000000..9620167e1 --- /dev/null +++ b/k8-operator/controllers/infisicaldynamicsecret/conditions.go @@ -0,0 +1,173 @@ +package controllers + +import ( + "context" + "fmt" + + "github.com/Infisical/infisical/k8-operator/api/v1alpha1" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func (r *InfisicalDynamicSecretReconciler) SetReconcileAutoRedeploymentStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, numDeployments int, errorToConditionOn error) { + if infisicalDynamicSecret.Status.Conditions == nil { + infisicalDynamicSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn == nil { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/AutoRedeployReady", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: fmt.Sprintf("Infisical has found %v deployments which are ready to be auto redeployed when dynamic secret lease changes", numDeployments), + }) + } else { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/AutoRedeployReady", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: fmt.Sprintf("Failed reconcile deployments because: %v", errorToConditionOn), + }) + } + + err := r.Client.Status().Update(ctx, infisicalDynamicSecret) + if err != nil { + logger.Error(err, "Could not set condition for AutoRedeployReady") + } +} + +func (r *InfisicalDynamicSecretReconciler) SetAuthenticatedStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) { + if infisicalDynamicSecret.Status.Conditions == nil { + infisicalDynamicSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn == nil { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/Authenticated", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: "Infisical has successfully authenticated with the Infisical API", + }) + } else { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/Authenticated", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: fmt.Sprintf("Failed to authenticate with Infisical API because: %v", errorToConditionOn), + }) + } + + err := r.Client.Status().Update(ctx, infisicalDynamicSecret) + if err != nil { + logger.Error(err, "Could not set condition for Authenticated") + } +} + +func (r *InfisicalDynamicSecretReconciler) SetLeaseRenewalStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) { + if infisicalDynamicSecret.Status.Conditions == nil { + infisicalDynamicSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn == nil { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/LeaseRenewal", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: "Infisical has successfully renewed the lease", + }) + } else { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/LeaseRenewal", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: fmt.Sprintf("Failed to renew the lease because: %v", errorToConditionOn), + }) + } + + err := r.Client.Status().Update(ctx, infisicalDynamicSecret) + if err != nil { + logger.Error(err, "Could not set condition for LeaseRenewal") + } +} + +func (r *InfisicalDynamicSecretReconciler) SetCreatedLeaseStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) { + if infisicalDynamicSecret.Status.Conditions == nil { + infisicalDynamicSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn == nil { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/LeaseCreated", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: "Infisical has successfully created the lease", + }) + } else { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/LeaseCreated", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: fmt.Sprintf("Failed to create the lease because: %v", errorToConditionOn), + }) + } + + err := r.Client.Status().Update(ctx, infisicalDynamicSecret) + if err != nil { + logger.Error(err, "Could not set condition for LeaseCreated") + } +} + +func (r *InfisicalDynamicSecretReconciler) SetRevokedLeaseStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) { + if infisicalDynamicSecret.Status.Conditions == nil { + infisicalDynamicSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn == nil { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/LeaseRevoked", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: "Infisical has successfully revoked the lease", + }) + } else { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/LeaseRevoked", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: fmt.Sprintf("Failed to revoke the lease because: %v", errorToConditionOn), + }) + } + + err := r.Client.Status().Update(ctx, infisicalDynamicSecret) + if err != nil { + logger.Error(err, "Could not set condition for LeaseRevoked") + } +} + +func (r *InfisicalDynamicSecretReconciler) SetReconcileStatus(ctx context.Context, logger logr.Logger, infisicalDynamicSecret *v1alpha1.InfisicalDynamicSecret, errorToConditionOn error) { + if infisicalDynamicSecret.Status.Conditions == nil { + infisicalDynamicSecret.Status.Conditions = []metav1.Condition{} + } + + if errorToConditionOn == nil { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/Reconcile", + Status: metav1.ConditionTrue, + Reason: "OK", + Message: "Infisical has successfully reconciled the InfisicalDynamicSecret", + }) + } else { + meta.SetStatusCondition(&infisicalDynamicSecret.Status.Conditions, metav1.Condition{ + Type: "secrets.infisical.com/Reconcile", + Status: metav1.ConditionFalse, + Reason: "Error", + Message: fmt.Sprintf("Failed to reconcile the InfisicalDynamicSecret because: %v", errorToConditionOn), + }) + } + + err := r.Client.Status().Update(ctx, infisicalDynamicSecret) + if err != nil { + logger.Error(err, "Could not set condition for Reconcile") + } +} diff --git a/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_controller.go b/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_controller.go index 8e1738eae..9be907e96 100644 --- a/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_controller.go +++ b/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_controller.go @@ -30,16 +30,20 @@ type InfisicalDynamicSecretReconciler struct { BaseLogger logr.Logger } -// +kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicaldynamicsecrets,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicaldynamicsecrets/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicaldynamicsecrets/finalizers,verbs=update - var infisicalDynamicSecretsResourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables) func (r *InfisicalDynamicSecretReconciler) GetLogger(req ctrl.Request) logr.Logger { return r.BaseLogger.WithValues("infisicaldynamicsecret", req.NamespacedName) } +// +kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicaldynamicsecrets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicaldynamicsecrets/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicaldynamicsecrets/finalizers,verbs=update +// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;delete +// +kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;delete +// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update +// +kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch + func (r *InfisicalDynamicSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := r.GetLogger(req) @@ -82,6 +86,7 @@ func (r *InfisicalDynamicSecretReconciler) Reconcile(ctx context.Context, req ct } err := r.HandleLeaseRevocation(ctx, logger, infisicalDynamicSecretCRD) + r.SetRevokedLeaseStatus(ctx, logger, &infisicalDynamicSecretCRD, err) if infisicalDynamicSecretsResourceVariablesMap != nil { if rv, ok := infisicalDynamicSecretsResourceVariablesMap[string(infisicalDynamicSecretCRD.GetUID())]; ok { @@ -128,7 +133,7 @@ func (r *InfisicalDynamicSecretReconciler) Reconcile(ctx context.Context, req ct } nextReconcile, err := r.ReconcileInfisicalDynamicSecret(ctx, logger, infisicalDynamicSecretCRD) - // r.SetSuccessfullyReconciledConditions(ctx, &infisicalDynamicSecretCRD, err) + r.SetReconcileStatus(ctx, logger, &infisicalDynamicSecretCRD, err) if err == nil && nextReconcile.Seconds() >= 5 { requeueTime = nextReconcile @@ -141,7 +146,8 @@ func (r *InfisicalDynamicSecretReconciler) Reconcile(ctx context.Context, req ct }, nil } - _, err = controllerhelpers.ReconcileDeploymentsWithManagedSecrets(ctx, r.Client, logger, infisicalDynamicSecretCRD.Spec.ManagedSecretReference) + numDeployments, err := controllerhelpers.ReconcileDeploymentsWithManagedSecrets(ctx, r.Client, logger, infisicalDynamicSecretCRD.Spec.ManagedSecretReference) + r.SetReconcileAutoRedeploymentStatus(ctx, logger, &infisicalDynamicSecretCRD, numDeployments, err) if err != nil { logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime)) diff --git a/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_helper.go b/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_helper.go index c5e2703c9..f049d6883 100644 --- a/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_helper.go +++ b/k8-operator/controllers/infisicaldynamicsecret/infisicaldynamicsecret_helper.go @@ -270,6 +270,7 @@ func (r *InfisicalDynamicSecretReconciler) HandleLeaseRevocation(ctx context.Con logger.Info("Authenticating for lease revocation") authDetails, err := r.handleAuthentication(ctx, infisicalDynamicSecret, infisicalClient) + r.SetAuthenticatedStatus(ctx, logger, &infisicalDynamicSecret, err) if err != nil { return fmt.Errorf("unable to authenticate for lease revocation [err=%s]", err) @@ -334,6 +335,7 @@ func (r *InfisicalDynamicSecretReconciler) ReconcileInfisicalDynamicSecret(ctx c if authDetails.AuthStrategy == "" { logger.Info("No authentication strategy found. Attempting to authenticate") authDetails, err = r.handleAuthentication(ctx, infisicalDynamicSecret, infisicalClient) + r.SetAuthenticatedStatus(ctx, logger, &infisicalDynamicSecret, err) if err != nil { return nextReconcile, fmt.Errorf("unable to authenticate [err=%s]", err) @@ -368,7 +370,10 @@ func (r *InfisicalDynamicSecretReconciler) ReconcileInfisicalDynamicSecret(ctx c } if infisicalDynamicSecret.Status.Lease == nil { - r.CreateDynamicSecretLease(ctx, logger, infisicalClient, &infisicalDynamicSecret, destination) + err := r.CreateDynamicSecretLease(ctx, logger, infisicalClient, &infisicalDynamicSecret, destination) + r.SetCreatedLeaseStatus(ctx, logger, &infisicalDynamicSecret, err) + + return defaultNextReconcile, err // Short requeue after creation } else { now := time.Now() leaseExpiresAt := infisicalDynamicSecret.Status.Lease.ExpiresAt.Time @@ -403,6 +408,7 @@ func (r *InfisicalDynamicSecretReconciler) ReconcileInfisicalDynamicSecret(ctx c maxTTLThreshold)) err := r.CreateDynamicSecretLease(ctx, logger, infisicalClient, &infisicalDynamicSecret, destination) + r.SetCreatedLeaseStatus(ctx, logger, &infisicalDynamicSecret, err) return defaultNextReconcile, err // Short requeue after creation } } @@ -411,6 +417,7 @@ func (r *InfisicalDynamicSecretReconciler) ReconcileInfisicalDynamicSecret(ctx c if now.After(leaseExpiresAt) { logger.Info("Lease has expired, creating new lease...") err = r.CreateDynamicSecretLease(ctx, logger, infisicalClient, &infisicalDynamicSecret, destination) + r.SetCreatedLeaseStatus(ctx, logger, &infisicalDynamicSecret, err) return defaultNextReconcile, err // Short requeue after creation } @@ -421,10 +428,12 @@ func (r *InfisicalDynamicSecretReconciler) ReconcileInfisicalDynamicSecret(ctx c renewalThreshold)) err = r.RenewDynamicSecretLease(ctx, logger, infisicalClient, &infisicalDynamicSecret, destination) + r.SetLeaseRenewalStatus(ctx, logger, &infisicalDynamicSecret, err) if err == constants.ErrInvalidLease { logger.Info("Failed to renew expired lease, creating new lease...") err = r.CreateDynamicSecretLease(ctx, logger, infisicalClient, &infisicalDynamicSecret, destination) + r.SetCreatedLeaseStatus(ctx, logger, &infisicalDynamicSecret, err) } return defaultNextReconcile, err // Short requeue after renewal/creation