github.com/crossplane/upjet@v1.3.0/pkg/migration/api_steps.go (about)

     1  // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io>
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package migration
     6  
     7  import (
     8  	"fmt"
     9  	"strconv"
    10  
    11  	v1 "github.com/crossplane/crossplane-runtime/apis/common/v1"
    12  	"github.com/crossplane/crossplane-runtime/pkg/meta"
    13  	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
    14  	"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
    15  	"github.com/pkg/errors"
    16  	corev1 "k8s.io/api/core/v1"
    17  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    18  )
    19  
    20  const (
    21  	stepPauseManaged step = iota
    22  	stepPauseComposites
    23  	stepCreateNewManaged
    24  	stepNewCompositions
    25  	stepEditComposites
    26  	stepEditClaims
    27  	stepDeletionPolicyOrphan
    28  	stepRemoveFinalizers
    29  	stepDeleteOldManaged
    30  	stepStartManaged
    31  	stepStartComposites
    32  	// this must be the last step
    33  	stepAPIEnd
    34  )
    35  
    36  func getAPIMigrationSteps() []step {
    37  	steps := make([]step, 0, stepAPIEnd)
    38  	for i := step(0); i < stepAPIEnd; i++ {
    39  		steps = append(steps, i)
    40  	}
    41  	return steps
    42  }
    43  
    44  func getAPIMigrationStepsFileSystemMode() []step {
    45  	return []step{
    46  		stepCreateNewManaged,
    47  		stepNewCompositions,
    48  		stepEditComposites,
    49  		stepEditClaims,
    50  		stepStartManaged,
    51  		stepStartComposites,
    52  		// this must be the last step
    53  		stepAPIEnd,
    54  	}
    55  }
    56  
    57  func (pg *PlanGenerator) addStepsForManagedResource(u *UnstructuredWithMetadata) error {
    58  	if u.Metadata.Category != CategoryManaged {
    59  		if _, ok, err := toManagedResource(pg.registry.scheme, u.Object); err != nil || !ok {
    60  			// not a managed resource or unable to determine
    61  			// whether it's a managed resource
    62  			return nil //nolint:nilerr
    63  		}
    64  	}
    65  	qName := getQualifiedName(u.Object)
    66  	if err := pg.stepPauseManagedResource(u, qName); err != nil {
    67  		return err
    68  	}
    69  	if err := pg.stepOrphanManagedResource(u, qName); err != nil {
    70  		return err
    71  	}
    72  	if err := pg.stepRemoveFinalizersManagedResource(u, qName); err != nil {
    73  		return err
    74  	}
    75  	pg.stepDeleteOldManagedResource(u)
    76  	orphaned, err := pg.stepOrhanMR(*u)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	if !orphaned {
    81  		return nil
    82  	}
    83  	_, err = pg.stepRevertOrhanMR(*u)
    84  	return err
    85  }
    86  
    87  func (pg *PlanGenerator) stepStartManagedResource(u *UnstructuredWithMetadata) error {
    88  	if !pg.stepEnabled(stepStartManaged) {
    89  		return nil
    90  	}
    91  	u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepStartManaged).Name, getQualifiedName(u.Object))
    92  	pg.stepAPI(stepStartManaged).Patch.Files = append(pg.stepAPI(stepStartManaged).Patch.Files, u.Metadata.Path)
    93  	return pg.pause(*u, false)
    94  }
    95  
    96  func (pg *PlanGenerator) stepPauseManagedResource(u *UnstructuredWithMetadata, qName string) error {
    97  	if !pg.stepEnabled(stepPauseManaged) {
    98  		return nil
    99  	}
   100  	u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepPauseManaged).Name, qName)
   101  	pg.stepAPI(stepPauseManaged).Patch.Files = append(pg.stepAPI(stepPauseManaged).Patch.Files, u.Metadata.Path)
   102  	return pg.pause(*u, true)
   103  }
   104  
   105  func (pg *PlanGenerator) stepPauseComposite(u *UnstructuredWithMetadata) error {
   106  	if !pg.stepEnabled(stepPauseComposites) {
   107  		return nil
   108  	}
   109  	u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepPauseComposites).Name, getQualifiedName(u.Object))
   110  	pg.stepAPI(stepPauseComposites).Patch.Files = append(pg.stepAPI(stepPauseComposites).Patch.Files, u.Metadata.Path)
   111  	return pg.pause(*u, true)
   112  }
   113  
   114  func (pg *PlanGenerator) stepOrphanManagedResource(u *UnstructuredWithMetadata, qName string) error {
   115  	if !pg.stepEnabled(stepDeletionPolicyOrphan) {
   116  		return nil
   117  	}
   118  	u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepDeletionPolicyOrphan).Name, qName)
   119  	pg.stepAPI(stepDeletionPolicyOrphan).Patch.Files = append(pg.stepAPI(stepDeletionPolicyOrphan).Patch.Files, u.Metadata.Path)
   120  	return errors.Wrap(pg.target.Put(UnstructuredWithMetadata{
   121  		Object: unstructured.Unstructured{
   122  			Object: addNameGVK(u.Object, map[string]any{
   123  				"spec": map[string]any{
   124  					"deletionPolicy": string(v1.DeletionOrphan),
   125  				},
   126  			}),
   127  		},
   128  		Metadata: u.Metadata,
   129  	}), errResourceOrphan)
   130  }
   131  
   132  func (pg *PlanGenerator) stepRemoveFinalizersManagedResource(u *UnstructuredWithMetadata, qName string) error {
   133  	if !pg.stepEnabled(stepRemoveFinalizers) {
   134  		return nil
   135  	}
   136  	u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepRemoveFinalizers).Name, qName)
   137  	pg.stepAPI(stepRemoveFinalizers).Patch.Files = append(pg.stepAPI(stepRemoveFinalizers).Patch.Files, u.Metadata.Path)
   138  	return pg.removeFinalizers(*u)
   139  }
   140  
   141  func (pg *PlanGenerator) stepDeleteOldManagedResource(u *UnstructuredWithMetadata) {
   142  	if !pg.stepEnabled(stepDeleteOldManaged) {
   143  		return
   144  	}
   145  	pg.stepAPI(stepDeleteOldManaged).Delete.Resources = append(pg.stepAPI(stepDeleteOldManaged).Delete.Resources,
   146  		Resource{
   147  			GroupVersionKind: FromGroupVersionKind(u.Object.GroupVersionKind()),
   148  			Name:             u.Object.GetName(),
   149  		})
   150  }
   151  
   152  func (pg *PlanGenerator) stepNewManagedResource(u *UnstructuredWithMetadata) error {
   153  	if !pg.stepEnabled(stepCreateNewManaged) {
   154  		return nil
   155  	}
   156  	meta.AddAnnotations(&u.Object, map[string]string{meta.AnnotationKeyReconciliationPaused: "true"})
   157  	u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepCreateNewManaged).Name, getQualifiedName(u.Object))
   158  	pg.stepAPI(stepCreateNewManaged).Apply.Files = append(pg.stepAPI(stepCreateNewManaged).Apply.Files, u.Metadata.Path)
   159  	if err := pg.target.Put(*u); err != nil {
   160  		return errors.Wrap(err, errResourceOutput)
   161  	}
   162  	return nil
   163  }
   164  
   165  func (pg *PlanGenerator) stepNewComposition(u *UnstructuredWithMetadata) error {
   166  	if !pg.stepEnabled(stepNewCompositions) {
   167  		return nil
   168  	}
   169  	u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepNewCompositions).Name, getQualifiedName(u.Object))
   170  	pg.stepAPI(stepNewCompositions).Apply.Files = append(pg.stepAPI(stepNewCompositions).Apply.Files, u.Metadata.Path)
   171  	if err := pg.target.Put(*u); err != nil {
   172  		return errors.Wrap(err, errCompositionOutput)
   173  	}
   174  	return nil
   175  }
   176  
   177  func (pg *PlanGenerator) stepStartComposites(composites []UnstructuredWithMetadata) error {
   178  	if !pg.stepEnabled(stepStartComposites) {
   179  		return nil
   180  	}
   181  	for _, u := range composites {
   182  		u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepStartComposites).Name, getQualifiedName(u.Object))
   183  		pg.stepAPI(stepStartComposites).Patch.Files = append(pg.stepAPI(stepStartComposites).Patch.Files, u.Metadata.Path)
   184  		if err := pg.pause(u, false); err != nil {
   185  			return errors.Wrap(err, errCompositeOutput)
   186  		}
   187  	}
   188  	return nil
   189  }
   190  
   191  func (pg *PlanGenerator) pause(u UnstructuredWithMetadata, isPaused bool) error {
   192  	return errors.Wrap(pg.target.Put(UnstructuredWithMetadata{
   193  		Object: unstructured.Unstructured{
   194  			Object: addNameGVK(u.Object, map[string]any{
   195  				"metadata": map[string]any{
   196  					"annotations": map[string]any{
   197  						meta.AnnotationKeyReconciliationPaused: strconv.FormatBool(isPaused),
   198  					},
   199  				},
   200  			}),
   201  		},
   202  		Metadata: Metadata{
   203  			Path: u.Metadata.Path,
   204  		},
   205  	}), errPause)
   206  }
   207  
   208  func (pg *PlanGenerator) removeFinalizers(u UnstructuredWithMetadata) error {
   209  	return errors.Wrap(pg.target.Put(UnstructuredWithMetadata{
   210  		Object: unstructured.Unstructured{
   211  			Object: addNameGVK(u.Object, map[string]any{
   212  				"metadata": map[string]any{
   213  					"finalizers": []any{},
   214  				},
   215  			}),
   216  		},
   217  		Metadata: Metadata{
   218  			Path: u.Metadata.Path,
   219  		},
   220  	}), errResourceRemoveFinalizer)
   221  }
   222  
   223  func (pg *PlanGenerator) stepEditComposites(composites []UnstructuredWithMetadata, convertedMap map[corev1.ObjectReference][]UnstructuredWithMetadata, convertedComposition map[string]string) error {
   224  	if !pg.stepEnabled(stepEditComposites) {
   225  		return nil
   226  	}
   227  	for _, u := range composites {
   228  		cp := composite.Unstructured{Unstructured: u.Object}
   229  		refs := cp.GetResourceReferences()
   230  		// compute new spec.resourceRefs so that the XR references the new MRs
   231  		newRefs := make([]corev1.ObjectReference, 0, len(refs))
   232  		for _, ref := range refs {
   233  			converted, ok := convertedMap[ref]
   234  			if !ok {
   235  				newRefs = append(newRefs, ref)
   236  				continue
   237  			}
   238  			for _, o := range converted {
   239  				gvk := o.Object.GroupVersionKind()
   240  				newRefs = append(newRefs, corev1.ObjectReference{
   241  					Kind:       gvk.Kind,
   242  					Name:       o.Object.GetName(),
   243  					APIVersion: gvk.GroupVersion().String(),
   244  				})
   245  			}
   246  		}
   247  		cp.SetResourceReferences(newRefs)
   248  		// compute new spec.compositionRef
   249  		if ref := cp.GetCompositionReference(); ref != nil && convertedComposition[ref.Name] != "" {
   250  			ref.Name = convertedComposition[ref.Name]
   251  			cp.SetCompositionReference(ref)
   252  		}
   253  		spec := u.Object.Object["spec"].(map[string]any)
   254  		u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepEditComposites).Name, getQualifiedName(u.Object))
   255  		pg.stepAPI(stepEditComposites).Patch.Files = append(pg.stepAPI(stepEditComposites).Patch.Files, u.Metadata.Path)
   256  		if err := pg.target.Put(UnstructuredWithMetadata{
   257  			Object: unstructured.Unstructured{
   258  				Object: addNameGVK(u.Object, map[string]any{
   259  					"spec": map[string]any{
   260  						keyResourceRefs:   spec[keyResourceRefs],
   261  						keyCompositionRef: spec[keyCompositionRef]},
   262  				}),
   263  			},
   264  			Metadata: u.Metadata,
   265  		}); err != nil {
   266  			return errors.Wrap(err, errCompositeOutput)
   267  		}
   268  	}
   269  	return nil
   270  }
   271  
   272  func (pg *PlanGenerator) stepEditClaims(claims []UnstructuredWithMetadata, convertedComposition map[string]string) error {
   273  	if !pg.stepEnabled(stepEditClaims) {
   274  		return nil
   275  	}
   276  	for _, u := range claims {
   277  		cm := claim.Unstructured{Unstructured: u.Object}
   278  		if ref := cm.GetCompositionReference(); ref != nil && convertedComposition[ref.Name] != "" {
   279  			ref.Name = convertedComposition[ref.Name]
   280  			cm.SetCompositionReference(ref)
   281  		}
   282  		u.Metadata.Path = fmt.Sprintf("%s/%s.yaml", pg.stepAPI(stepEditClaims).Name, getQualifiedName(u.Object))
   283  		pg.stepAPI(stepEditClaims).Patch.Files = append(pg.stepAPI(stepEditClaims).Patch.Files, u.Metadata.Path)
   284  		if err := pg.target.Put(UnstructuredWithMetadata{
   285  			Object: unstructured.Unstructured{
   286  				Object: addNameGVK(u.Object, map[string]any{
   287  					"spec": map[string]any{
   288  						keyCompositionRef: u.Object.Object["spec"].(map[string]any)[keyCompositionRef],
   289  					},
   290  				}),
   291  			},
   292  			Metadata: u.Metadata,
   293  		}); err != nil {
   294  			return errors.Wrap(err, errClaimOutput)
   295  		}
   296  	}
   297  	return nil
   298  }
   299  
   300  // NOTE: to cover different migration scenarios, we may use
   301  // "migration templates" instead of a static plan. But a static plan should be
   302  // fine as a start.
   303  func (pg *PlanGenerator) stepAPI(s step) *Step { //nolint:gocyclo // all steps under a single clause for readability
   304  	stepKey := strconv.Itoa(int(s))
   305  	if pg.Plan.Spec.stepMap[stepKey] != nil {
   306  		return pg.Plan.Spec.stepMap[stepKey]
   307  	}
   308  
   309  	pg.Plan.Spec.stepMap[stepKey] = &Step{}
   310  	switch s { //nolint:exhaustive
   311  	case stepPauseManaged:
   312  		setPatchStep("pause-managed", pg.Plan.Spec.stepMap[stepKey])
   313  
   314  	case stepPauseComposites:
   315  		setPatchStep("pause-composites", pg.Plan.Spec.stepMap[stepKey])
   316  
   317  	case stepCreateNewManaged:
   318  		setApplyStep("create-new-managed", pg.Plan.Spec.stepMap[stepKey])
   319  
   320  	case stepNewCompositions:
   321  		setApplyStep("new-compositions", pg.Plan.Spec.stepMap[stepKey])
   322  
   323  	case stepEditComposites:
   324  		setPatchStep("edit-composites", pg.Plan.Spec.stepMap[stepKey])
   325  
   326  	case stepEditClaims:
   327  		setPatchStep("edit-claims", pg.Plan.Spec.stepMap[stepKey])
   328  
   329  	case stepDeletionPolicyOrphan:
   330  		setPatchStep("deletion-policy-orphan", pg.Plan.Spec.stepMap[stepKey])
   331  
   332  	case stepRemoveFinalizers:
   333  		setPatchStep("remove-finalizers", pg.Plan.Spec.stepMap[stepKey])
   334  
   335  	case stepDeleteOldManaged:
   336  		setDeleteStep("delete-old-managed", pg.Plan.Spec.stepMap[stepKey])
   337  
   338  	case stepStartManaged:
   339  		setPatchStep("start-managed", pg.Plan.Spec.stepMap[stepKey])
   340  
   341  	case stepStartComposites:
   342  		setPatchStep("start-composites", pg.Plan.Spec.stepMap[stepKey])
   343  	default:
   344  		panic(fmt.Sprintf(errInvalidStepFmt, s))
   345  	}
   346  	return pg.Plan.Spec.stepMap[stepKey]
   347  }