diff --git a/internal/controller/manifest/controller.go b/internal/controller/manifest/controller.go index ba5fca9e6f..3b0e1bdb87 100644 --- a/internal/controller/manifest/controller.go +++ b/internal/controller/manifest/controller.go @@ -36,7 +36,5 @@ func NewReconciler(mgr manager.Manager, declarativev2.WithCustomStateCheck(statecheck.NewManagerStateCheck(statefulChecker, deploymentChecker)), declarativev2.WithRemoteTargetCluster(lookup.ConfigResolver), manifest.WithClientCacheKey(), - declarativev2.WithPostRun{manifest.PostRunCreateCR}, - declarativev2.WithPreDelete{manifest.PreDeleteDeleteCR}, ) } diff --git a/internal/declarative/v2/options.go b/internal/declarative/v2/options.go index 018ccac59e..f87fd64010 100644 --- a/internal/declarative/v2/options.go +++ b/internal/declarative/v2/options.go @@ -49,9 +49,6 @@ type Options struct { CustomStateCheck StateCheck PostRenderTransforms []ObjectTransform - - PostRuns []PostRun - PreDeletes []PreDelete } type Option interface { @@ -112,36 +109,6 @@ func (o PostRenderTransformOption) Apply(options *Options) { options.PostRenderTransforms = append(options.PostRenderTransforms, o.ObjectTransforms...) } -// Hook defines a Hook into the declarative reconciliation -// skr is the runtime cluster -// kcp is the control-plane cluster -// obj is guaranteed to be the reconciled object and also to always preside in kcp. -type Hook func(ctx context.Context, skr Client, kcp client.Client, obj Object) error - -// WARNING: DO NOT USE THESE HOOKS IF YOU DO NOT KNOW THE RECONCILIATION LIFECYCLE OF THE DECLARATIVE API. -// IT CAN BREAK YOUR RECONCILIATION AND IF YOU ADJUST THE OBJECT, ALSO LEAD TO -// INVALID STATES. -type ( - // PostRun is executed after every successful render+reconciliation of the manifest. - PostRun Hook - // PreDelete is executed before any deletion of resources calculated from the status. - PreDelete Hook -) - -// WithPostRun applies PostRun. -type WithPostRun []PostRun - -func (o WithPostRun) Apply(options *Options) { - options.PostRuns = append(options.PostRuns, o...) -} - -// WithPreDelete applies PreDelete. -type WithPreDelete []PreDelete - -func (o WithPreDelete) Apply(options *Options) { - options.PreDeletes = append(options.PreDeletes, o...) -} - type WithSingletonClientCacheOption struct { ClientCache } diff --git a/internal/declarative/v2/reconciler.go b/internal/declarative/v2/reconciler.go index 6eae00eea8..63b3641391 100644 --- a/internal/declarative/v2/reconciler.go +++ b/internal/declarative/v2/reconciler.go @@ -6,10 +6,8 @@ import ( "fmt" "time" - "k8s.io/apimachinery/pkg/api/meta" apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/cli-runtime/pkg/resource" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -21,6 +19,8 @@ import ( "github.com/kyma-project/lifecycle-manager/api/v1beta2" "github.com/kyma-project/lifecycle-manager/internal" "github.com/kyma-project/lifecycle-manager/internal/manifest/labelsremoval" + "github.com/kyma-project/lifecycle-manager/internal/manifest/modulecr" + "github.com/kyma-project/lifecycle-manager/internal/manifest/status" "github.com/kyma-project/lifecycle-manager/internal/pkg/metrics" "github.com/kyma-project/lifecycle-manager/internal/pkg/resources" "github.com/kyma-project/lifecycle-manager/pkg/common" @@ -32,10 +32,8 @@ var ( ErrWarningResourceSyncStateDiff = errors.New("resource syncTarget state diff detected") ErrResourceSyncDiffInSameOCILayer = errors.New("resource syncTarget diff detected but in " + "same oci layer, prevent sync resource to be deleted") - ErrInstallationConditionRequiresUpdate = errors.New("installation condition needs an update") - ErrObjectHasEmptyState = errors.New("object has an empty state") - ErrRequeueRequired = errors.New("requeue required") - ErrAccessSecretNotFound = errors.New("access secret not found") + ErrRequeueRequired = errors.New("requeue required") + ErrAccessSecretNotFound = errors.New("access secret not found") ) const ( @@ -88,42 +86,6 @@ type Reconciler struct { managedLabelRemovalService ManagedLabelRemoval } -const waitingForResourcesMsg = "waiting for resources to become ready" - -type ConditionType string - -const ( - ConditionTypeResources ConditionType = "Resources" - ConditionTypeInstallation ConditionType = "Installation" -) - -type ConditionReason string - -const ( - ConditionReasonResourcesAreAvailable ConditionReason = "ResourcesAvailable" - ConditionReasonReady ConditionReason = "Ready" -) - -func newInstallationCondition(manifest *v1beta2.Manifest) apimetav1.Condition { - return apimetav1.Condition{ - Type: string(ConditionTypeInstallation), - Reason: string(ConditionReasonReady), - Status: apimetav1.ConditionFalse, - Message: "installation is ready and resources can be used", - ObservedGeneration: manifest.GetGeneration(), - } -} - -func newResourcesCondition(manifest *v1beta2.Manifest) apimetav1.Condition { - return apimetav1.Condition{ - Type: string(ConditionTypeResources), - Reason: string(ConditionReasonResourcesAreAvailable), - Status: apimetav1.ConditionFalse, - Message: "resources are parsed and ready for use", - ObservedGeneration: manifest.GetGeneration(), - } -} - //nolint:funlen,cyclop,gocognit // Declarative pkg will be removed soon func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { startTime := time.Now() @@ -146,7 +108,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return ctrl.Result{RequeueAfter: r.Success}, nil } - if err := r.initialize(manifest); err != nil { + if err := status.Initialize(manifest); err != nil { return r.finishReconcile(ctx, manifest, metrics.ManifestInit, manifestStatus, err) } @@ -224,7 +186,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return r.finishReconcile(ctx, manifest, metrics.ManifestPruneDiff, manifestStatus, err) } - if err := r.removeModuleCR(ctx, skrClient, manifest); err != nil { + if err := modulecr.NewClient(skrClient).RemoveModuleCR(ctx, r.Client, manifest); err != nil { if errors.Is(err, ErrRequeueRequired) { r.ManifestMetrics.RecordRequeueReason(metrics.ManifestPreDeleteEnqueueRequired, queue.IntendedRequeue) return ctrl.Result{Requeue: true}, nil @@ -260,7 +222,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu func (r *Reconciler) handleLabelsRemovalFinalizer(ctx context.Context, skrClient client.Client, manifest *v1beta2.Manifest, ) (ctrl.Result, error) { - defaultCR, err := getCR(ctx, skrClient, manifest) + defaultCR, err := modulecr.NewClient(skrClient).GetCR(ctx, manifest) if err != nil { return ctrl.Result{}, err } @@ -324,37 +286,10 @@ func (r *Reconciler) partialObjectMetadata(manifest *v1beta2.Manifest) *apimetav return objMeta } -func (r *Reconciler) initialize(manifest *v1beta2.Manifest) error { - status := manifest.GetStatus() - - for _, condition := range []apimetav1.Condition{ - newResourcesCondition(manifest), - newInstallationCondition(manifest), - } { - if meta.FindStatusCondition(status.Conditions, condition.Type) == nil { - meta.SetStatusCondition(&status.Conditions, condition) - } - } - - if status.Synced == nil { - status.Synced = []shared.Resource{} - } - - if status.State == "" { - manifest.SetStatus(status.WithState(shared.StateProcessing).WithErr(ErrObjectHasEmptyState)) - return ErrObjectHasEmptyState - } - - manifest.SetStatus(status) - - return nil -} - func (r *Reconciler) renderResources(ctx context.Context, skrClient Client, manifest *v1beta2.Manifest, spec *Spec, ) ([]*resource.Info, []*resource.Info, error) { - resourceCondition := newResourcesCondition(manifest) - status := manifest.GetStatus() + manifestStatus := manifest.GetStatus() var err error var target, current ResourceList @@ -362,65 +297,57 @@ func (r *Reconciler) renderResources(ctx context.Context, skrClient Client, mani converter := NewResourceToInfoConverter(ResourceInfoConverter(skrClient), apimetav1.NamespaceDefault) if target, err = r.renderTargetResources(ctx, skrClient, converter, manifest, spec); err != nil { - manifest.SetStatus(status.WithState(shared.StateError).WithErr(err)) + manifest.SetStatus(manifestStatus.WithState(shared.StateError).WithErr(err)) return nil, nil, err } - current, err = converter.ResourcesToInfos(status.Synced) + current, err = converter.ResourcesToInfos(manifestStatus.Synced) if err != nil { - manifest.SetStatus(status.WithState(shared.StateError).WithErr(err)) + manifest.SetStatus(manifestStatus.WithState(shared.StateError).WithErr(err)) return nil, nil, err } - - if !meta.IsStatusConditionTrue(status.Conditions, resourceCondition.Type) { - resourceCondition.Status = apimetav1.ConditionTrue - meta.SetStatusCondition(&status.Conditions, resourceCondition) - manifest.SetStatus(status.WithOperation(resourceCondition.Message)) - } - + status.UpdateResourcesCondition(manifest) return target, current, nil } -func (r *Reconciler) syncResources(ctx context.Context, clnt Client, manifest *v1beta2.Manifest, +func (r *Reconciler) syncResources(ctx context.Context, skrClient Client, manifest *v1beta2.Manifest, target []*resource.Info, ) error { - status := manifest.GetStatus() + manifestStatus := manifest.GetStatus() - if err := ConcurrentSSA(clnt, defaultFieldOwner).Run(ctx, target); err != nil { - manifest.SetStatus(status.WithState(shared.StateError).WithErr(err)) + if err := ConcurrentSSA(skrClient, defaultFieldOwner).Run(ctx, target); err != nil { + manifest.SetStatus(manifestStatus.WithState(shared.StateError).WithErr(err)) return err } - oldSynced := status.Synced + oldSynced := manifestStatus.Synced newSynced := NewInfoToResourceConverter().InfosToResources(target) - status.Synced = newSynced + manifestStatus.Synced = newSynced if hasDiff(oldSynced, newSynced) { if manifest.GetDeletionTimestamp().IsZero() { - manifest.SetStatus(status.WithState(shared.StateProcessing).WithOperation(ErrWarningResourceSyncStateDiff.Error())) - } else if status.State != shared.StateWarning { - manifest.SetStatus(status.WithState(shared.StateDeleting).WithOperation(ErrWarningResourceSyncStateDiff.Error())) + manifest.SetStatus(manifestStatus.WithState(shared.StateProcessing).WithOperation(ErrWarningResourceSyncStateDiff.Error())) + } else if manifestStatus.State != shared.StateWarning { + manifest.SetStatus(manifestStatus.WithState(shared.StateDeleting).WithOperation(ErrWarningResourceSyncStateDiff.Error())) } return ErrWarningResourceSyncStateDiff } - for i := range r.PostRuns { - if err := r.PostRuns[i](ctx, clnt, r.Client, manifest); err != nil { - manifest.SetStatus(status.WithState(shared.StateError).WithErr(err)) - return err - } + if err := modulecr.NewClient(skrClient).PostRunCreateCR(ctx, r.Client, manifest); err != nil { + manifest.SetStatus(manifestStatus.WithState(shared.StateError).WithErr(err)) + return err } if !manifest.GetDeletionTimestamp().IsZero() { - return r.setManifestState(manifest, shared.StateDeleting) + return status.SetManifestState(manifest, shared.StateDeleting) } - managerState, err := r.checkManagerState(ctx, clnt, target) + managerState, err := r.checkManagerState(ctx, skrClient, target) if err != nil { - manifest.SetStatus(status.WithState(shared.StateError).WithErr(err)) + manifest.SetStatus(manifestStatus.WithState(shared.StateError).WithErr(err)) return err } - return r.setManifestState(manifest, managerState) + return status.SetManifestState(manifest, managerState) } func hasDiff(oldResources []shared.Resource, newResources []shared.Resource) bool { @@ -456,44 +383,11 @@ func (r *Reconciler) checkManagerState(ctx context.Context, clnt Client, target return managerState, nil } -func (r *Reconciler) setManifestState(manifest *v1beta2.Manifest, newState shared.State) error { - status := manifest.GetStatus() - - if newState == shared.StateProcessing { - manifest.SetStatus(status.WithState(shared.StateProcessing).WithOperation(waitingForResourcesMsg)) - return ErrInstallationConditionRequiresUpdate - } - - installationCondition := newInstallationCondition(manifest) - if newState != status.State || !meta.IsStatusConditionTrue(status.Conditions, installationCondition.Type) { - installationCondition.Status = apimetav1.ConditionTrue - meta.SetStatusCondition(&status.Conditions, installationCondition) - - manifest.SetStatus(status.WithState(newState).WithOperation(installationCondition.Message)) - return ErrInstallationConditionRequiresUpdate - } - - return nil -} - -func (r *Reconciler) removeModuleCR(ctx context.Context, clnt Client, manifest *v1beta2.Manifest) error { - if !manifest.GetDeletionTimestamp().IsZero() { - for _, preDelete := range r.PreDeletes { - if err := preDelete(ctx, clnt, r.Client, manifest); err != nil { - // we do not set a status here since it will be deleting if timestamp is set. - manifest.SetStatus(manifest.GetStatus().WithErr(err)) - return err - } - } - } - return nil -} - func (r *Reconciler) renderTargetResources(ctx context.Context, skrClient client.Client, converter ResourceToInfoConverter, manifest *v1beta2.Manifest, spec *Spec, ) ([]*resource.Info, error) { if !manifest.GetDeletionTimestamp().IsZero() { - deleted, err := checkCRDeletion(ctx, skrClient, manifest) + deleted, err := modulecr.NewClient(skrClient).CheckCRDeletion(ctx, manifest) if err != nil { return nil, err } @@ -526,24 +420,6 @@ func (r *Reconciler) renderTargetResources(ctx context.Context, skrClient client return target, nil } -func checkCRDeletion(ctx context.Context, skrClient client.Client, manifestCR *v1beta2.Manifest) (bool, - error, -) { - if manifestCR.Spec.Resource == nil { - return true, nil - } - - resourceCR, err := getCR(ctx, skrClient, manifestCR) - if err != nil { - if util.IsNotFound(err) { - return true, nil - } - return false, fmt.Errorf("%w: failed to fetch default resource CR", err) - } - - return resourceCR == nil, nil -} - func (r *Reconciler) pruneDiff(ctx context.Context, clnt Client, manifest *v1beta2.Manifest, current, target []*resource.Info, spec *Spec, ) error { @@ -718,25 +594,3 @@ func (r *Reconciler) cleanUpMandatoryModuleMetrics(manifest *v1beta2.Manifest) { r.MandatoryModuleMetrics.CleanupMetrics(kymaName, moduleName) } } - -func getCR(ctx context.Context, skrClient client.Client, manifest *v1beta2.Manifest) (*unstructured.Unstructured, - error, -) { - resourceCR := &unstructured.Unstructured{} - name := manifest.Spec.Resource.GetName() - namespace := manifest.Spec.Resource.GetNamespace() - gvk := manifest.Spec.Resource.GroupVersionKind() - - resourceCR.SetGroupVersionKind(schema.GroupVersionKind{ - Group: gvk.Group, - Version: gvk.Version, - Kind: gvk.Kind, - }) - - if err := skrClient.Get(ctx, - client.ObjectKey{Name: name, Namespace: namespace}, resourceCR); err != nil { - return nil, fmt.Errorf("%w: failed to fetch default resource CR", err) - } - - return resourceCR, nil -} diff --git a/internal/manifest/custom_resource.go b/internal/manifest/modulecr/client.go similarity index 57% rename from internal/manifest/custom_resource.go rename to internal/manifest/modulecr/client.go index d4968862b4..110dd25299 100644 --- a/internal/manifest/custom_resource.go +++ b/internal/manifest/modulecr/client.go @@ -1,4 +1,4 @@ -package manifest +package modulecr import ( "context" @@ -6,6 +6,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -16,56 +18,74 @@ import ( "github.com/kyma-project/lifecycle-manager/pkg/util" ) -// PostRunCreateCR is a hook for creating the manifest default custom resource if not available in the cluster -// It is used to provide the controller with default data in the Runtime. -func PostRunCreateCR( - ctx context.Context, skr declarativev2.Client, kcp client.Client, obj declarativev2.Object, -) error { - manifest, ok := obj.(*v1beta2.Manifest) - if !ok { - return nil - } - if manifest.Spec.Resource == nil { - return nil +type Client struct { + client.Client +} + +func NewClient(client client.Client) *Client { + return &Client{ + client, } - if !manifest.GetDeletionTimestamp().IsZero() { - return nil +} + +func (c *Client) GetCR(ctx context.Context, manifest *v1beta2.Manifest) (*unstructured.Unstructured, + error, +) { + resourceCR := &unstructured.Unstructured{} + name := manifest.Spec.Resource.GetName() + namespace := manifest.Spec.Resource.GetNamespace() + gvk := manifest.Spec.Resource.GroupVersionKind() + + resourceCR.SetGroupVersionKind(schema.GroupVersionKind{ + Group: gvk.Group, + Version: gvk.Version, + Kind: gvk.Kind, + }) + + if err := c.Get(ctx, + client.ObjectKey{Name: name, Namespace: namespace}, resourceCR); err != nil { + return nil, fmt.Errorf("%w: failed to fetch default resource CR", err) } - resource := manifest.Spec.Resource.DeepCopy() - resource.SetLabels(collections.MergeMaps(resource.GetLabels(), map[string]string{ - shared.ManagedBy: shared.ManagedByLabelValue, - })) + return resourceCR, nil +} - err := skr.Create(ctx, resource, client.FieldOwner(declarativev2.CustomResourceManagerFinalizer)) - if err != nil && !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("failed to create resource: %w", err) +func (c *Client) CheckCRDeletion(ctx context.Context, manifestCR *v1beta2.Manifest) (bool, + error, +) { + if manifestCR.Spec.Resource == nil { + return true, nil } - oMeta := &apimetav1.PartialObjectMetadata{} - oMeta.SetName(obj.GetName()) - oMeta.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind()) - oMeta.SetNamespace(obj.GetNamespace()) - oMeta.SetFinalizers(obj.GetFinalizers()) + resourceCR, err := c.GetCR(ctx, manifestCR) + if err != nil { + if util.IsNotFound(err) { + return true, nil + } + return false, fmt.Errorf("%w: failed to fetch default resource CR", err) + } - if added := controllerutil.AddFinalizer(oMeta, declarativev2.CustomResourceManagerFinalizer); added { - if err := kcp.Patch( - ctx, oMeta, client.Apply, client.ForceOwnership, - client.FieldOwner(declarativev2.CustomResourceManagerFinalizer), - ); err != nil { - return fmt.Errorf("failed to patch resource: %w", err) + return resourceCR == nil, nil +} + +func (c *Client) RemoveModuleCR(ctx context.Context, kcp client.Client, manifest *v1beta2.Manifest) error { + if !manifest.GetDeletionTimestamp().IsZero() { + if err := c.preDeleteDeleteCR(ctx, kcp, manifest); err != nil { + // we do not set a status here since it will be deleting if timestamp is set. + manifest.SetStatus(manifest.GetStatus().WithErr(err)) + return err } - return declarativev2.ErrRequeueRequired } + return nil } -// PreDeleteDeleteCR is a hook for deleting the module CR if available in the cluster. +// preDeleteDeleteCR is a hook for deleting the module CR if available in the cluster. // It uses DeletePropagationBackground to delete module CR. // Only if module CR is not found (indicated by NotFound error), it continues to remove Manifest finalizer, // and we consider the CR removal successful. -func PreDeleteDeleteCR( - ctx context.Context, skr declarativev2.Client, kcp client.Client, obj declarativev2.Object, +func (c *Client) preDeleteDeleteCR( + ctx context.Context, kcp client.Client, obj declarativev2.Object, ) error { manifest, ok := obj.(*v1beta2.Manifest) if !ok { @@ -77,7 +97,7 @@ func PreDeleteDeleteCR( resource := manifest.Spec.Resource.DeepCopy() propagation := apimetav1.DeletePropagationBackground - err := skr.Delete(ctx, resource, &client.DeleteOptions{PropagationPolicy: &propagation}) + err := c.Delete(ctx, resource, &client.DeleteOptions{PropagationPolicy: &propagation}) if !util.IsNotFound(err) { return nil @@ -86,7 +106,7 @@ func PreDeleteDeleteCR( onCluster := manifest.DeepCopy() err = kcp.Get(ctx, client.ObjectKeyFromObject(obj), onCluster) if util.IsNotFound(err) { - return fmt.Errorf("PreDeleteDeleteCR: %w", err) + return fmt.Errorf("preDeleteDeleteCR: %w", err) } if err != nil { return fmt.Errorf("failed to fetch resource: %w", err) @@ -101,3 +121,47 @@ func PreDeleteDeleteCR( } return nil } + +// PostRunCreateCR is a hook for creating the manifest default custom resource if not available in the cluster +// It is used to provide the controller with default data in the Runtime. +func (c *Client) PostRunCreateCR( + ctx context.Context, kcp client.Client, obj declarativev2.Object, +) error { + manifest, ok := obj.(*v1beta2.Manifest) + if !ok { + return nil + } + if manifest.Spec.Resource == nil { + return nil + } + if !manifest.GetDeletionTimestamp().IsZero() { + return nil + } + + resource := manifest.Spec.Resource.DeepCopy() + resource.SetLabels(collections.MergeMaps(resource.GetLabels(), map[string]string{ + shared.ManagedBy: shared.ManagedByLabelValue, + })) + + err := c.Create(ctx, resource, client.FieldOwner(declarativev2.CustomResourceManagerFinalizer)) + if err != nil && !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("failed to create resource: %w", err) + } + + oMeta := &apimetav1.PartialObjectMetadata{} + oMeta.SetName(obj.GetName()) + oMeta.SetGroupVersionKind(obj.GetObjectKind().GroupVersionKind()) + oMeta.SetNamespace(obj.GetNamespace()) + oMeta.SetFinalizers(obj.GetFinalizers()) + + if added := controllerutil.AddFinalizer(oMeta, declarativev2.CustomResourceManagerFinalizer); added { + if err := kcp.Patch( + ctx, oMeta, client.Apply, client.ForceOwnership, + client.FieldOwner(declarativev2.CustomResourceManagerFinalizer), + ); err != nil { + return fmt.Errorf("failed to patch resource: %w", err) + } + return declarativev2.ErrRequeueRequired + } + return nil +} diff --git a/internal/manifest/status/condition.go b/internal/manifest/status/condition.go new file mode 100644 index 0000000000..012989b34c --- /dev/null +++ b/internal/manifest/status/condition.go @@ -0,0 +1,56 @@ +package status + +import ( + "k8s.io/apimachinery/pkg/api/meta" + apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kyma-project/lifecycle-manager/api/v1beta2" +) + +type ConditionType string + +const ( + ConditionTypeResources ConditionType = "Resources" + ConditionTypeModuleCR ConditionType = "ModuleCR" + ConditionTypeInstallation ConditionType = "Installation" +) + +type ConditionReason string + +const ( + ConditionReasonResourcesAreAvailable ConditionReason = "ResourcesAvailable" + ConditionReasonModuleCRWarning ConditionReason = "Warning" + ConditionReasonReady ConditionReason = "Ready" +) + +func initInstallationCondition(manifest *v1beta2.Manifest) apimetav1.Condition { + return apimetav1.Condition{ + Type: string(ConditionTypeInstallation), + Reason: string(ConditionReasonReady), + Status: apimetav1.ConditionFalse, + Message: "installation is ready and resources can be used", + ObservedGeneration: manifest.GetGeneration(), + } +} + +func initResourcesCondition(manifest *v1beta2.Manifest) apimetav1.Condition { + return apimetav1.Condition{ + Type: string(ConditionTypeResources), + Reason: string(ConditionReasonResourcesAreAvailable), + Status: apimetav1.ConditionFalse, + Message: "resources are parsed and ready for use", + ObservedGeneration: manifest.GetGeneration(), + } +} + +func UpdateResourcesCondition(manifest *v1beta2.Manifest) { + status := manifest.GetStatus() + resourceCondition := initResourcesCondition(manifest) + + if !meta.IsStatusConditionTrue(status.Conditions, resourceCondition.Type) { + resourceCondition.Status = apimetav1.ConditionTrue + meta.SetStatusCondition(&status.Conditions, resourceCondition) + manifest.SetStatus(status.WithOperation(resourceCondition.Message)) + } + +} diff --git a/internal/manifest/status/init.go b/internal/manifest/status/init.go new file mode 100644 index 0000000000..999d67ec62 --- /dev/null +++ b/internal/manifest/status/init.go @@ -0,0 +1,39 @@ +package status + +import ( + "errors" + + "k8s.io/apimachinery/pkg/api/meta" + apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kyma-project/lifecycle-manager/api/shared" + "github.com/kyma-project/lifecycle-manager/api/v1beta2" +) + +var ErrObjectHasEmptyState = errors.New("object has an empty state") + +func Initialize(manifest *v1beta2.Manifest) error { + status := manifest.GetStatus() + + for _, condition := range []apimetav1.Condition{ + initResourcesCondition(manifest), + initInstallationCondition(manifest), + } { + if meta.FindStatusCondition(status.Conditions, condition.Type) == nil { + meta.SetStatusCondition(&status.Conditions, condition) + } + } + + if status.Synced == nil { + status.Synced = []shared.Resource{} + } + + if status.State == "" { + manifest.SetStatus(status.WithState(shared.StateProcessing).WithErr(ErrObjectHasEmptyState)) + return ErrObjectHasEmptyState + } + + manifest.SetStatus(status) + + return nil +} diff --git a/internal/manifest/status/state.go b/internal/manifest/status/state.go new file mode 100644 index 0000000000..cc41d544f7 --- /dev/null +++ b/internal/manifest/status/state.go @@ -0,0 +1,35 @@ +package status + +import ( + "errors" + + "k8s.io/apimachinery/pkg/api/meta" + apimetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/kyma-project/lifecycle-manager/api/shared" + "github.com/kyma-project/lifecycle-manager/api/v1beta2" +) + +const waitingForResourcesMsg = "waiting for resources to become ready" + +var ErrInstallationConditionRequiresUpdate = errors.New("installation condition needs an update") + +func SetManifestState(manifest *v1beta2.Manifest, newState shared.State) error { + status := manifest.GetStatus() + + if newState == shared.StateProcessing { + manifest.SetStatus(status.WithState(shared.StateProcessing).WithOperation(waitingForResourcesMsg)) + return ErrInstallationConditionRequiresUpdate + } + + installationCondition := initInstallationCondition(manifest) + if newState != status.State || !meta.IsStatusConditionTrue(status.Conditions, installationCondition.Type) { + installationCondition.Status = apimetav1.ConditionTrue + meta.SetStatusCondition(&status.Conditions, installationCondition) + + manifest.SetStatus(status.WithState(newState).WithOperation(installationCondition.Message)) + return ErrInstallationConditionRequiresUpdate + } + + return nil +} diff --git a/tests/integration/controller/manifest/custom_resource_check/suite_test.go b/tests/integration/controller/manifest/custom_resource_check/suite_test.go index 93f2a1166f..6779499dd5 100644 --- a/tests/integration/controller/manifest/custom_resource_check/suite_test.go +++ b/tests/integration/controller/manifest/custom_resource_check/suite_test.go @@ -159,8 +159,7 @@ var _ = BeforeSuite(func() { func(_ context.Context, _ declarativev2.Object) (*declarativev2.ClusterInfo, error) { return &declarativev2.ClusterInfo{Config: authUser.Config()}, nil }, - ), manifest.WithClientCacheKey(), declarativev2.WithPostRun{manifest.PostRunCreateCR}, - declarativev2.WithPreDelete{manifest.PreDeleteDeleteCR}, + ), manifest.WithClientCacheKey(), declarativev2.WithCustomStateCheck(statecheck.NewManagerStateCheck(statefulChecker, deploymentChecker))) err = ctrl.NewControllerManagedBy(mgr). diff --git a/tests/integration/controller/manifest/suite_test.go b/tests/integration/controller/manifest/suite_test.go index 3ee5e72dfc..e9c4c45f71 100644 --- a/tests/integration/controller/manifest/suite_test.go +++ b/tests/integration/controller/manifest/suite_test.go @@ -156,8 +156,7 @@ var _ = BeforeSuite(func() { func(_ context.Context, _ declarativev2.Object) (*declarativev2.ClusterInfo, error) { return &declarativev2.ClusterInfo{Config: authUser.Config()}, nil }, - ), manifest.WithClientCacheKey(), declarativev2.WithPostRun{manifest.PostRunCreateCR}, - declarativev2.WithPreDelete{manifest.PreDeleteDeleteCR}, + ), manifest.WithClientCacheKey(), declarativev2.WithCustomStateCheck(declarativev2.NewExistsStateCheck())) err = ctrl.NewControllerManagedBy(mgr).