Skip to content

Commit 31b3490

Browse files
aniruddha2000janiskemper
authored andcommitted
Fix untar of old cluster stacks
Signed-off-by: Aniruddha Basak <[email protected]>
1 parent 3b5eef5 commit 31b3490

File tree

7 files changed

+275
-20
lines changed

7 files changed

+275
-20
lines changed

api/v1alpha1/clusteraddon_types.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ const (
2828
ClusterAddonFinalizer = "clusteraddon.clusterstack.x-k8s.io"
2929
)
3030

31+
const StageAnnotation = "ClusterAddonStage"
32+
33+
type StageAnnotationValue string
34+
35+
const (
36+
StageCreated = StageAnnotationValue("created")
37+
StageUpgraded = StageAnnotationValue("upgraded")
38+
)
39+
3140
// StagePhase defines the status of helm chart in the cluster addon.
3241
type StagePhase string
3342

@@ -134,6 +143,26 @@ func (r *ClusterAddon) SetStagePhase(helmChartName string, action clusteraddon.A
134143
}
135144
}
136145

146+
func (r *ClusterAddon) SetStageAnnotations(value StageAnnotationValue) {
147+
if r.Annotations == nil {
148+
r.Annotations = make(map[string]string, 0)
149+
}
150+
_, found := r.Annotations[StageAnnotation]
151+
if !found {
152+
r.Annotations[StageAnnotation] = string(value)
153+
}
154+
}
155+
156+
// HasAnnotation returns a bool if passed in annotation exists.
157+
func (r *ClusterAddon) HasStageAnnotation(value StageAnnotationValue) bool {
158+
val, found := r.Annotations[StageAnnotation]
159+
if found && val == string(value) {
160+
return true
161+
}
162+
163+
return false
164+
}
165+
137166
// GetConditions returns the observations of the operational state of the ClusterAddon resource.
138167
func (r *ClusterAddon) GetConditions() clusterv1.Conditions {
139168
return r.Status.Conditions

internal/controller/clusteraddon_controller.go

Lines changed: 220 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,8 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
212212
return reconcile.Result{}, fmt.Errorf("failed to get cluster addon config path: %w", err)
213213
}
214214

215+
logger := log.FromContext(ctx)
216+
215217
// Check whether current Helm chart has been applied in the workload cluster. If not, then we need to apply the helm chart (again).
216218
// the spec.clusterStack is only set after a Helm chart from a ClusterStack has been applied successfully.
217219
// If it is not set, the Helm chart has never been applied.
@@ -250,6 +252,8 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
250252
conditions.MarkTrue(clusterAddon, csov1alpha1.HelmChartAppliedCondition)
251253
}
252254

255+
clusterAddon.SetStageAnnotations(csov1alpha1.StageCreated)
256+
clusterAddon.Spec.Hook = ""
253257
clusterAddon.Spec.ClusterStack = cluster.Spec.Topology.Class
254258
clusterAddon.Status.Ready = true
255259
return ctrl.Result{}, nil
@@ -271,11 +275,14 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
271275
// set condition that helm chart has been applied successfully
272276
conditions.MarkTrue(clusterAddon, csov1alpha1.HelmChartAppliedCondition)
273277
}
278+
279+
clusterAddon.Spec.Hook = ""
280+
clusterAddon.SetStageAnnotations(csov1alpha1.StageCreated)
274281
clusterAddon.Status.Ready = true
275282
return ctrl.Result{}, nil
276283

277284
} else {
278-
in.addonStagesInput, err = r.getAddonStagesInput(clusterAddon, in.restConfig, in.clusterAddonChartPath)
285+
in.addonStagesInput, err = r.getAddonStagesInput(in.restConfig, in.clusterAddonChartPath)
279286
if err != nil {
280287
return reconcile.Result{}, fmt.Errorf("failed to get addon stages input: %w", err)
281288
}
@@ -305,7 +312,7 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
305312
in.oldDestinationClusterAddonChartDir = strings.TrimSuffix(oldRelease.ClusterAddonChartPath(), ".tgz")
306313

307314
if err := unTarContent(oldRelease.ClusterAddonChartPath(), in.oldDestinationClusterAddonChartDir); err != nil {
308-
return reconcile.Result{}, fmt.Errorf("failed to untar cluster addon chart: %q: %w", oldRelease.ClusterAddonChartPath(), err)
315+
return reconcile.Result{}, fmt.Errorf("failed to untar old cluster stack cluster addon chart: %q: %w", oldRelease.ClusterAddonChartPath(), err)
309316
}
310317
}
311318

@@ -397,7 +404,39 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
397404
}
398405
}
399406

407+
logger.Info("the hook is here", "hook", in.clusterAddon.Spec.Hook)
408+
400409
if clusterAddon.Spec.Hook == "AfterControlPlaneInitialized" || clusterAddon.Spec.Hook == "BeforeClusterUpgrade" {
410+
if clusterAddon.Spec.Hook == "BeforeClusterUpgrade" {
411+
// create the list of old release objects
412+
oldClusterStackObjectList, err := r.getOldReleaseObjects(ctx, in, clusterAddonConfig, oldRelease)
413+
if err != nil {
414+
return reconcile.Result{}, fmt.Errorf("failed to get old cluster stack object list from helm charts: %w", err)
415+
}
416+
logger.Info("here is the old cluster stack object list", "list", oldClusterStackObjectList)
417+
418+
newClusterStackObjectList, err := r.getNewReleaseObjects(ctx, in, clusterAddonConfig)
419+
if err != nil {
420+
return reconcile.Result{}, fmt.Errorf("failed to get new cluster stack object list from helm charts: %w", err)
421+
}
422+
423+
logger.Info("here in the clean up begins", "currentList", newClusterStackObjectList)
424+
shouldReque, err := r.cleanUpResources(ctx, in, oldClusterStackObjectList, newClusterStackObjectList)
425+
logger.Info("here in the clean up done", "currentList", newClusterStackObjectList, "reque", shouldReque)
426+
if err != nil {
427+
return reconcile.Result{}, fmt.Errorf("failed to clean up resources: %w", err)
428+
}
429+
if shouldReque {
430+
return reconcile.Result{RequeueAfter: 20 * time.Second}, nil
431+
}
432+
433+
// set upgrade annotation once done
434+
clusterAddon.SetStageAnnotations(csov1alpha1.StageUpgraded)
435+
}
436+
437+
// if upgrade annotation is not present add the create annotation
438+
clusterAddon.SetStageAnnotations(csov1alpha1.StageCreated)
439+
401440
clusterAddon.Spec.ClusterStack = cluster.Spec.Topology.Class
402441
}
403442

@@ -419,6 +458,153 @@ func (r *ClusterAddonReconciler) Reconcile(ctx context.Context, req reconcile.Re
419458
return ctrl.Result{}, nil
420459
}
421460

461+
func (r *ClusterAddonReconciler) getNewReleaseObjects(ctx context.Context, in templateAndApplyClusterAddonInput, clusterAddonConfig clusteraddon.ClusterAddonConfig) ([]*csov1alpha1.Resource, error) {
462+
var (
463+
newBuildTemplate []byte
464+
resources []*csov1alpha1.Resource
465+
)
466+
467+
for _, stage := range clusterAddonConfig.AddonStages[in.clusterAddon.Spec.Hook] {
468+
if _, err := os.Stat(filepath.Join(in.newDestinationClusterAddonChartDir, stage.HelmChartName, release.OverwriteYaml)); err == nil {
469+
newBuildTemplate, err = buildTemplateFromClusterAddonValues(ctx, filepath.Join(in.newDestinationClusterAddonChartDir, stage.HelmChartName, release.OverwriteYaml), in.cluster, r.Client, true)
470+
if err != nil {
471+
return nil, fmt.Errorf("failed to build template from new cluster addon values of the latest cluster stack: %w", err)
472+
}
473+
}
474+
475+
helmTemplate, err := helmTemplateClusterAddon(filepath.Join(in.newDestinationClusterAddonChartDir, stage.HelmChartName), newBuildTemplate)
476+
if err != nil {
477+
return nil, fmt.Errorf("failed to template new helm chart of the latest cluster stack: %w", err)
478+
}
479+
480+
resource, err := kube.GetResourcesFromHelmTemplate(helmTemplate)
481+
if err != nil {
482+
return nil, fmt.Errorf("failed to get resources form old cluster stack helm template of the latest cluster stack: %w", err)
483+
}
484+
485+
if stage.Action == clusteraddon.Apply {
486+
resources = append(resources, resource...)
487+
} else {
488+
resources = removeResourcesFromCurrentListOfObjects(resources, resource)
489+
}
490+
}
491+
492+
return resources, nil
493+
}
494+
495+
// getOldReleaseObjects returns the old cluster stack objects in the workload cluster.
496+
func (r *ClusterAddonReconciler) getOldReleaseObjects(ctx context.Context, in templateAndApplyClusterAddonInput, clusterAddonConfig clusteraddon.ClusterAddonConfig, oldRelease release.Release) ([]*csov1alpha1.Resource, error) {
497+
// clusteraddon.yaml
498+
clusterAddonConfigPath, err := r.getClusterAddonConfigPath(in.clusterAddon.Spec.ClusterStack)
499+
if err != nil {
500+
return nil, fmt.Errorf("failed to get old cluster stack cluster addon config path: %w", err)
501+
}
502+
503+
if _, err := os.Stat(clusterAddonConfigPath); err != nil {
504+
if !os.IsNotExist(err) {
505+
return nil, fmt.Errorf("failed to verify the clusteraddon.yaml on old cluster stack release path %q with error: %w", clusterAddonConfigPath, err)
506+
}
507+
508+
// this is the old way
509+
buildTemplate, err := buildTemplateFromClusterAddonValues(ctx, oldRelease.ClusterAddonValuesPath(), in.cluster, r.Client, false)
510+
if err != nil {
511+
return nil, fmt.Errorf("failed to build template from the old cluster stack cluster addon values: %w", err)
512+
}
513+
514+
helmTemplate, err := helmTemplateClusterAddon(oldRelease.ClusterAddonChartPath(), buildTemplate)
515+
if err != nil {
516+
return nil, fmt.Errorf("failed to template helm chart: %w", err)
517+
}
518+
519+
resources, err := kube.GetResourcesFromHelmTemplate(helmTemplate)
520+
if err != nil {
521+
return nil, fmt.Errorf("failed to get resources form old cluster stack helm template: %w", err)
522+
}
523+
524+
return resources, nil
525+
}
526+
527+
// this is the new way
528+
// Read all the helm charts in new the un-tared cluster addon.
529+
530+
var (
531+
newBuildTemplate []byte
532+
resources []*csov1alpha1.Resource
533+
534+
hook string
535+
)
536+
537+
if in.clusterAddon.HasStageAnnotation(csov1alpha1.StageCreated) {
538+
hook = "AfterControlPlaneInitialized"
539+
} else {
540+
hook = "BeforeClusterUpgrade"
541+
}
542+
543+
for _, stage := range clusterAddonConfig.AddonStages[hook] {
544+
if _, err := os.Stat(filepath.Join(in.oldDestinationClusterAddonChartDir, stage.HelmChartName, release.OverwriteYaml)); err == nil {
545+
newBuildTemplate, err = buildTemplateFromClusterAddonValues(ctx, filepath.Join(in.oldDestinationClusterAddonChartDir, stage.HelmChartName, release.OverwriteYaml), in.cluster, r.Client, true)
546+
if err != nil {
547+
return nil, fmt.Errorf("failed to build template from new cluster addon values: %w", err)
548+
}
549+
}
550+
551+
helmTemplate, err := helmTemplateClusterAddon(filepath.Join(in.oldDestinationClusterAddonChartDir, stage.HelmChartName), newBuildTemplate)
552+
if err != nil {
553+
return nil, fmt.Errorf("failed to template new helm chart: %w", err)
554+
}
555+
556+
resource, err := kube.GetResourcesFromHelmTemplate(helmTemplate)
557+
if err != nil {
558+
return nil, fmt.Errorf("failed to get resources form old cluster stack helm template: %w", err)
559+
}
560+
561+
if stage.Action == clusteraddon.Apply {
562+
resources = append(resources, resource...)
563+
} else {
564+
resources = removeResourcesFromCurrentListOfObjects(resources, resource)
565+
}
566+
}
567+
568+
return resources, nil
569+
}
570+
571+
func (r *ClusterAddonReconciler) cleanUpResources(ctx context.Context, in templateAndApplyClusterAddonInput, oldList, newList []*csov1alpha1.Resource) (shouldRequeue bool, err error) {
572+
// Create a map of items in the new slice for faster lookup
573+
newMap := make(map[*csov1alpha1.Resource]bool)
574+
for _, item := range newList {
575+
newMap[item] = true
576+
}
577+
578+
// Find extra objects in the old slice
579+
var extraResources []*csov1alpha1.Resource
580+
for _, item := range oldList {
581+
if !newMap[item] {
582+
extraResources = append(extraResources, item)
583+
}
584+
}
585+
logger := log.FromContext(ctx)
586+
587+
logger.Info("diff in resources", "diff", extraResources)
588+
589+
for _, resource := range extraResources {
590+
if resource.Namespace == "" {
591+
resource.Namespace = clusterAddonNamespace
592+
}
593+
dr, err := kube.GetDynamicResourceInterface(resource.Namespace, in.restConfig, resource.GroupVersionKind())
594+
if err != nil {
595+
return false, fmt.Errorf("failed to get dynamic resource interface: %w", err)
596+
}
597+
598+
if err := dr.Delete(ctx, resource.Name, metav1.DeleteOptions{}); err != nil && !apierrors.IsNotFound(err) {
599+
reterr := fmt.Errorf("failed to delete object %q: %w", resource.GroupVersionKind(), err)
600+
resource.Error = reterr.Error()
601+
shouldRequeue = true
602+
}
603+
}
604+
605+
return shouldRequeue, nil
606+
}
607+
422608
func (r *ClusterAddonReconciler) getClusterAddonConfigPath(clusterClassName string) (string, error) {
423609
// path to the clusteraddon config /tmp/cluster-stacks/docker-ferrol-1-27-v1/clusteraddon.yaml
424610
// if present then new way of cluster stack otherwise old way.
@@ -455,7 +641,7 @@ type addonStagesInput struct {
455641
oldDestinationClusterAddonChartDir string
456642
}
457643

458-
func (r *ClusterAddonReconciler) getAddonStagesInput(clusterAddon *csov1alpha1.ClusterAddon, restConfig *rest.Config, clusterAddonChart string) (addonStagesInput, error) {
644+
func (r *ClusterAddonReconciler) getAddonStagesInput(restConfig *rest.Config, clusterAddonChart string) (addonStagesInput, error) {
459645
var (
460646
addonStages addonStagesInput
461647
err error
@@ -476,7 +662,7 @@ func (r *ClusterAddonReconciler) getAddonStagesInput(clusterAddon *csov1alpha1.C
476662
// dst - /tmp/cluster-stacks/docker-ferrol-1-27-v1/docker-ferrol-1-27-cluster-addon-v1/
477663
addonStages.newDestinationClusterAddonChartDir = strings.TrimSuffix(clusterAddonChart, ".tgz")
478664
if err := unTarContent(clusterAddonChart, addonStages.newDestinationClusterAddonChartDir); err != nil {
479-
return addonStagesInput{}, fmt.Errorf("failed to untar cluster addon chart: %q: %w", clusterAddonChart, err)
665+
return addonStagesInput{}, fmt.Errorf("failed to untar new cluster stack cluster addon chart: %q: %w", clusterAddonChart, err)
480666
}
481667

482668
// Read all the helm charts in the un-tared cluster addon.
@@ -517,6 +703,7 @@ func (r *ClusterAddonReconciler) templateAndApplyClusterAddonHelmChart(ctx conte
517703
}
518704

519705
in.clusterAddon.Status.Resources = newResources
706+
520707
return shouldRequeue, nil
521708
}
522709

@@ -600,7 +787,7 @@ check:
600787
} else {
601788
// Delete part
602789
logger.V(1).Info("starting to delete helm chart", "clusterStack", in.clusterAddon.Spec.ClusterStack, "helm chart", stage.HelmChartName, "hook", in.clusterAddon.Spec.Hook)
603-
shouldRequeue, err = helmTemplateAndDeleteNewClusterStack(ctx, in, stage.HelmChartName)
790+
shouldRequeue, err = r.helmTemplateAndDeleteNewClusterStack(ctx, in, stage.HelmChartName)
604791
if err != nil {
605792
return false, fmt.Errorf("failed to delete helm chart: %w", err)
606793
}
@@ -729,6 +916,7 @@ func (r *ClusterAddonReconciler) templateAndApplyNewClusterStackAddonHelmChart(c
729916
if in.oldDestinationClusterAddonChartDir != "" {
730917
oldClusterStackSubDirPath := filepath.Join(in.oldDestinationClusterAddonChartDir, helmChartName)
731918

919+
// we skip helm templating if last cluster stack don't follow the new convention.
732920
if _, err := os.Stat(filepath.Join(oldClusterStackSubDirPath, release.OverwriteYaml)); err == nil {
733921
oldBuildTemplate, err = buildTemplateFromClusterAddonValues(ctx, filepath.Join(oldClusterStackSubDirPath, release.OverwriteYaml), in.cluster, r.Client, true)
734922
if err != nil {
@@ -761,12 +949,13 @@ func (r *ClusterAddonReconciler) templateAndApplyNewClusterStackAddonHelmChart(c
761949
return false, fmt.Errorf("failed to apply objects from cluster addon Helm chart: %w", err)
762950
}
763951

952+
// This is for the current stage objects and will be removed once done.
764953
in.clusterAddon.Status.Resources = newResources
765954

766955
return shouldRequeue, nil
767956
}
768957

769-
func helmTemplateAndDeleteNewClusterStack(ctx context.Context, in templateAndApplyClusterAddonInput, helmChartName string) (bool, error) {
958+
func (r *ClusterAddonReconciler) helmTemplateAndDeleteNewClusterStack(ctx context.Context, in templateAndApplyClusterAddonInput, helmChartName string) (bool, error) {
770959
var (
771960
buildTemplate []byte
772961
err error
@@ -778,12 +967,13 @@ func helmTemplateAndDeleteNewClusterStack(ctx context.Context, in templateAndApp
778967
return false, fmt.Errorf("failed to template new helm chart: %w", err)
779968
}
780969

781-
newResources, shouldRequeue, err := in.kubeClient.DeleteNewClusterStack(ctx, newHelmTemplate)
970+
deletedResources, shouldRequeue, err := in.kubeClient.DeleteNewClusterStack(ctx, newHelmTemplate)
782971
if err != nil {
783972
return false, fmt.Errorf("failed to delete objects from cluster addon Helm chart: %w", err)
784973
}
785974

786-
in.clusterAddon.Status.Resources = newResources
975+
// This is for the current stage objects and will be removed once done.
976+
in.clusterAddon.Status.Resources = deletedResources
787977

788978
return shouldRequeue, nil
789979
}
@@ -1096,6 +1286,10 @@ func unTarContent(src, dst string) error {
10961286
}
10971287
case tar.TypeReg:
10981288
// Create regular files
1289+
if err := os.MkdirAll(filepath.Dir(targetPath), os.ModePerm); err != nil {
1290+
return fmt.Errorf("%q: creating directory: %w", filepath.Dir(targetPath), err)
1291+
}
1292+
10991293
outputFile, err := os.Create(filepath.Clean(targetPath))
11001294
if err != nil {
11011295
return fmt.Errorf("%q: creating file: %w", targetPath, err)
@@ -1118,3 +1312,21 @@ func unTarContent(src, dst string) error {
11181312

11191313
return nil
11201314
}
1315+
1316+
func removeResourcesFromCurrentListOfObjects(baseList []*csov1alpha1.Resource, itemsToRemove []*csov1alpha1.Resource) []*csov1alpha1.Resource {
1317+
// Create a map of items to remove for faster lookup
1318+
itemsMap := make(map[*csov1alpha1.Resource]bool)
1319+
for _, item := range itemsToRemove {
1320+
itemsMap[item] = true
1321+
}
1322+
1323+
// Create a new list without the items to remove
1324+
var newList []*csov1alpha1.Resource
1325+
for _, item := range baseList {
1326+
if !itemsMap[item] {
1327+
newList = append(newList, item)
1328+
}
1329+
}
1330+
1331+
return newList
1332+
}

0 commit comments

Comments
 (0)