diff --git a/internal/controller/clusteraddon_controller.go b/internal/controller/clusteraddon_controller.go index bd007e6e5..f8044c5af 100644 --- a/internal/controller/clusteraddon_controller.go +++ b/internal/controller/clusteraddon_controller.go @@ -346,13 +346,16 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re } // Helm chart has been applied successfully - // clusterAddon.Spec.Version = metadata.Versions.Components.ClusterAddon conditions.MarkTrue(clusterAddon, csov1alpha1.HelmChartAppliedCondition) - clusterAddon.Spec.ClusterStack = cluster.Spec.Topology.Class + // remove the status resource if hook is finished + clusterAddon.Status.Resources = make([]*csov1alpha1.Resource, 0) // remove the helm chart status from the status. clusterAddon.Status.HelmChartStatus = make(map[string]csov1alpha1.HelmChartStatusConditions) + + // update the latest cluster class + clusterAddon.Spec.ClusterStack = cluster.Spec.Topology.Class clusterAddon.Status.Ready = true return ctrl.Result{}, nil @@ -372,7 +375,6 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re if shouldRequeue { return reconcile.Result{RequeueAfter: 20 * time.Second}, nil } - } if clusterAddon.Spec.Hook == "AfterControlPlaneInitialized" || clusterAddon.Spec.Hook == "BeforeClusterUpgrade" { @@ -386,6 +388,9 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re // remove the helm chart status from the status. clusterAddon.Status.HelmChartStatus = make(map[string]csov1alpha1.HelmChartStatusConditions) + // remove the status resource if hook is finished + clusterAddon.Status.Resources = make([]*csov1alpha1.Resource, 0) + // store the release kubernetes version and current hook clusterAddon.Status.KubernetesVersion = releaseAsset.Meta.Versions.Kubernetes clusterAddon.Status.CurrentHook = clusterAddon.Spec.Hook @@ -520,7 +525,7 @@ func (r *ClusterAddonReconciler) executeStage(ctx context.Context, stage cluster check: switch in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] { - case csov1alpha1.None: + case csov1alpha1.None, csov1alpha1.WaitingForPreCondition: // If WaitForPreCondition is mentioned. if !reflect.DeepEqual(stage.WaitForPreCondition, clusteraddon.WaitForCondition{}) { // Evaluate the condition. @@ -566,6 +571,9 @@ check: } logger.V(1).Info("finished applying helm chart", "clusterStack", in.clusterAddon.Spec.ClusterStack, "helm chart", stage.HelmChartName, "hook", in.clusterAddon.Spec.Hook) + // remove status resource if applied successfully + in.clusterAddon.Status.Resources = make([]*csov1alpha1.Resource, 0) + in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] = csov1alpha1.WaitingForPostCondition goto check @@ -589,6 +597,9 @@ check: } logger.V(1).Info("finished deleting helm chart", "clusterStack", in.clusterAddon.Spec.ClusterStack, "helm chart", stage.HelmChartName, "hook", in.clusterAddon.Spec.Hook) + // remove status resource if deleted successfully + in.clusterAddon.Status.Resources = make([]*csov1alpha1.Resource, 0) + in.clusterAddon.Status.HelmChartStatus[stage.HelmChartName] = csov1alpha1.WaitingForPostCondition goto check } @@ -708,11 +719,13 @@ func helmTemplateAndApplyNewClusterStack(ctx context.Context, in templateAndAppl return false, fmt.Errorf("failed to template new helm chart: %w", err) } - shouldRequeue, err := in.kubeClient.ApplyNewClusterStack(ctx, oldHelmTemplate, newHelmTemplate) + newResources, shouldRequeue, err := in.kubeClient.ApplyNewClusterStack(ctx, oldHelmTemplate, newHelmTemplate) if err != nil { return false, fmt.Errorf("failed to apply objects from cluster addon Helm chart: %w", err) } + in.clusterAddon.Status.Resources = newResources + return shouldRequeue, nil } @@ -728,11 +741,13 @@ func helmTemplateAndDeleteNewClusterStack(ctx context.Context, in templateAndApp return false, fmt.Errorf("failed to template new helm chart: %w", err) } - shouldRequeue, err := in.kubeClient.DeleteNewClusterStack(ctx, newHelmTemplate) + newResources, shouldRequeue, err := in.kubeClient.DeleteNewClusterStack(ctx, newHelmTemplate) if err != nil { return false, fmt.Errorf("failed to delete objects from cluster addon Helm chart: %w", err) } + in.clusterAddon.Status.Resources = newResources + return shouldRequeue, nil } diff --git a/pkg/kube/fake/kube.go b/pkg/kube/fake/kube.go index de4f94cf6..a6e216320 100644 --- a/pkg/kube/fake/kube.go +++ b/pkg/kube/fake/kube.go @@ -50,10 +50,10 @@ func (*kube) Delete(_ context.Context, _ []byte, _ []*csov1alpha1.Resource) (_ [ return nil, false, nil } -func (*kube) ApplyNewClusterStack(_ context.Context, _, _ []byte) (_ bool, _ error) { - return false, nil +func (*kube) ApplyNewClusterStack(_ context.Context, _, _ []byte) (_ []*csov1alpha1.Resource, _ bool, _ error) { + return nil, false, nil } -func (*kube) DeleteNewClusterStack(_ context.Context, _ []byte) (_ bool, _ error) { - return false, nil +func (*kube) DeleteNewClusterStack(_ context.Context, _ []byte) (_ []*csov1alpha1.Resource, _ bool, _ error) { + return nil, false, nil } diff --git a/pkg/kube/kube.go b/pkg/kube/kube.go index 67d7799e9..77dccacfb 100644 --- a/pkg/kube/kube.go +++ b/pkg/kube/kube.go @@ -40,8 +40,8 @@ type Client interface { Apply(ctx context.Context, template []byte, oldResources []*csov1alpha1.Resource, shouldDelete bool) (newResources []*csov1alpha1.Resource, shouldRequeue bool, err error) Delete(ctx context.Context, template []byte, oldResources []*csov1alpha1.Resource) (newResources []*csov1alpha1.Resource, shouldRequeue bool, err error) - ApplyNewClusterStack(ctx context.Context, oldTemplate, newTemplate []byte) (shouldRequeue bool, err error) - DeleteNewClusterStack(ctx context.Context, template []byte) (shouldRequeue bool, err error) + ApplyNewClusterStack(ctx context.Context, oldTemplate, newTemplate []byte) (newResources []*csov1alpha1.Resource, shouldRequeue bool, err error) + DeleteNewClusterStack(ctx context.Context, template []byte) (newResources []*csov1alpha1.Resource, shouldRequeue bool, err error) } // Factory creates new fake kube client factories. @@ -67,24 +67,26 @@ func (*factory) NewClient(namespace string, resCfg *rest.Config) Client { } } -func (k *kube) ApplyNewClusterStack(ctx context.Context, oldTemplate, newTemplate []byte) (shouldRequeue bool, err error) { +func (k *kube) ApplyNewClusterStack(ctx context.Context, oldTemplate, newTemplate []byte) (newResources []*csov1alpha1.Resource, shouldRequeue bool, err error) { logger := log.FromContext(ctx) oldObjects, err := parseK8sYaml(oldTemplate) if err != nil { - return false, fmt.Errorf("failed to parse old cluster stack template: %w", err) + return nil, false, fmt.Errorf("failed to parse old cluster stack template: %w", err) } newObjects, err := parseK8sYaml(newTemplate) if err != nil { - return false, fmt.Errorf("failed to parse new cluster stack template: %w", err) + return nil, false, fmt.Errorf("failed to parse new cluster stack template: %w", err) } for _, newObject := range newObjects { if err := setLabel(newObject, ObjectLabelKeyOwned, ObjectLabelValueOwned); err != nil { - return false, fmt.Errorf("error setting label: %w", err) + return nil, false, fmt.Errorf("error setting label: %w", err) } + resource := csov1alpha1.NewResourceFromUnstructured(newObject) + // call the function and get dynamic.ResourceInterface // getDynamicResourceInterface dr, err := getDynamicResourceInterface(k.Namespace, k.RestConfig, newObject.GroupVersionKind()) @@ -97,50 +99,68 @@ func (k *kube) ApplyNewClusterStack(ctx context.Context, oldTemplate, newTemplat if _, err := dr.Apply(ctx, newObject.GetName(), newObject, metav1.ApplyOptions{FieldManager: "kubectl", Force: true}); err != nil { reterr := fmt.Errorf("failed to apply object: %w", err) + resource.Error = reterr.Error() + resource.Status = csov1alpha1.ResourceStatusNotSynced logger.Error(reterr, "failed to apply object", "obj", newObject.GetObjectKind().GroupVersionKind(), "name", newObject.GetName(), "namespace", newObject.GetNamespace()) shouldRequeue = true } + + resource.Status = csov1alpha1.ResourceStatusSynced + newResources = append(newResources, resource) } for _, object := range resourcesToBeDeletedFromUnstructuredObjects(oldObjects, newObjects) { + resource := csov1alpha1.NewResourceFromUnstructured(object) + dr, err := getDynamicResourceInterface(k.Namespace, k.RestConfig, object.GroupVersionKind()) if err != nil { - return false, fmt.Errorf("failed to get dynamic resource interface: %w", err) + return nil, false, fmt.Errorf("failed to get dynamic resource interface: %w", err) } if err := dr.Delete(ctx, object.GetName(), metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { reterr := fmt.Errorf("failed to delete object: %w", err) logger.Error(reterr, "failed to delete object", "obj", object.GroupVersionKind(), "namespacedName", fmt.Sprintf("%s/%s", object.GetNamespace(), object.GetName())) // append resource to status and requeue again to be able to retry deletion + resource.Status = csov1alpha1.ResourceStatusNotSynced + resource.Error = reterr.Error() + newResources = append(newResources, resource) shouldRequeue = true } } - return shouldRequeue, nil + return newResources, shouldRequeue, nil } -func (k *kube) DeleteNewClusterStack(ctx context.Context, template []byte) (shouldRequeue bool, err error) { +func (k *kube) DeleteNewClusterStack(ctx context.Context, template []byte) (newResources []*csov1alpha1.Resource, shouldRequeue bool, err error) { objects, err := parseK8sYaml(template) if err != nil { - return false, fmt.Errorf("failed to parse new cluster stack template: %w", err) + return nil, false, fmt.Errorf("failed to parse new cluster stack template: %w", err) } for _, object := range objects { + resource := csov1alpha1.NewResourceFromUnstructured(object) + if err := setLabel(object, ObjectLabelKeyOwned, ObjectLabelValueOwned); err != nil { - return false, fmt.Errorf("error setting label: %w", err) + return nil, false, fmt.Errorf("error setting label: %w", err) } dr, err := getDynamicResourceInterface(k.Namespace, k.RestConfig, object.GroupVersionKind()) if err != nil { - return false, fmt.Errorf("failed to get dynamic resource interface: %w", err) + return nil, false, fmt.Errorf("failed to get dynamic resource interface: %w", err) } - if err := dr.Delete(ctx, object.GetName(), metav1.DeleteOptions{}); err != nil { - return true, fmt.Errorf("failed to delete object %q: %w", object.GetObjectKind().GroupVersionKind(), err) + if err := dr.Delete(ctx, object.GetName(), metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) { + reterr := fmt.Errorf("failed to delete object %q: %w", object.GetObjectKind().GroupVersionKind(), err) + resource.Status = csov1alpha1.ResourceStatusNotSynced + resource.Error = reterr.Error() + shouldRequeue = true } + + resource.Status = csov1alpha1.ResourceStatusSynced + newResources = append(newResources, resource) } - return shouldRequeue, nil + return newResources, shouldRequeue, nil } func (k *kube) Apply(ctx context.Context, template []byte, oldResources []*csov1alpha1.Resource, shouldDelete bool) (newResources []*csov1alpha1.Resource, shouldRequeue bool, err error) { @@ -192,6 +212,7 @@ func (k *kube) Apply(ctx context.Context, template []byte, oldResources []*csov1 newResources = append(newResources, resource) } + // TODO: cleanup shouldDelete if shouldDelete { // make a diff between new objs and oldResources to find out // a) if an object is in oldResources and synced and not in new objs, then delete should be attempted diff --git a/pkg/kube/mocks/Client.go b/pkg/kube/mocks/Client.go index 07bca7f13..a4cd02192 100644 --- a/pkg/kube/mocks/Client.go +++ b/pkg/kube/mocks/Client.go @@ -49,27 +49,36 @@ func (_m *Client) Apply(ctx context.Context, template []byte, oldResources []*v1 } // ApplyNewClusterStack provides a mock function with given fields: ctx, oldTemplate, newTemplate -func (_m *Client) ApplyNewClusterStack(ctx context.Context, oldTemplate []byte, newTemplate []byte) (bool, error) { +func (_m *Client) ApplyNewClusterStack(ctx context.Context, oldTemplate []byte, newTemplate []byte) ([]*v1alpha1.Resource, bool, error) { ret := _m.Called(ctx, oldTemplate, newTemplate) - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []byte, []byte) (bool, error)); ok { + var r0 []*v1alpha1.Resource + var r1 bool + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []byte, []byte) ([]*v1alpha1.Resource, bool, error)); ok { return rf(ctx, oldTemplate, newTemplate) } - if rf, ok := ret.Get(0).(func(context.Context, []byte, []byte) bool); ok { + if rf, ok := ret.Get(0).(func(context.Context, []byte, []byte) []*v1alpha1.Resource); ok { r0 = rf(ctx, oldTemplate, newTemplate) } else { - r0 = ret.Get(0).(bool) + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*v1alpha1.Resource) + } } - if rf, ok := ret.Get(1).(func(context.Context, []byte, []byte) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, []byte, []byte) bool); ok { r1 = rf(ctx, oldTemplate, newTemplate) } else { - r1 = ret.Error(1) + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(context.Context, []byte, []byte) error); ok { + r2 = rf(ctx, oldTemplate, newTemplate) + } else { + r2 = ret.Error(2) } - return r0, r1 + return r0, r1, r2 } // Delete provides a mock function with given fields: ctx, template, oldResources @@ -106,27 +115,36 @@ func (_m *Client) Delete(ctx context.Context, template []byte, oldResources []*v } // DeleteNewClusterStack provides a mock function with given fields: ctx, template -func (_m *Client) DeleteNewClusterStack(ctx context.Context, template []byte) (bool, error) { +func (_m *Client) DeleteNewClusterStack(ctx context.Context, template []byte) ([]*v1alpha1.Resource, bool, error) { ret := _m.Called(ctx, template) - var r0 bool - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, []byte) (bool, error)); ok { + var r0 []*v1alpha1.Resource + var r1 bool + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, []byte) ([]*v1alpha1.Resource, bool, error)); ok { return rf(ctx, template) } - if rf, ok := ret.Get(0).(func(context.Context, []byte) bool); ok { + if rf, ok := ret.Get(0).(func(context.Context, []byte) []*v1alpha1.Resource); ok { r0 = rf(ctx, template) } else { - r0 = ret.Get(0).(bool) + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*v1alpha1.Resource) + } } - if rf, ok := ret.Get(1).(func(context.Context, []byte) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, []byte) bool); ok { r1 = rf(ctx, template) } else { - r1 = ret.Error(1) + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(context.Context, []byte) error); ok { + r2 = rf(ctx, template) + } else { + r2 = ret.Error(2) } - return r0, r1 + return r0, r1, r2 } // NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.