github.com/crossplane/upjet@v1.3.0/pkg/migration/plan_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  	"sort"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/pkg/errors"
    14  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    15  	"k8s.io/apimachinery/pkg/util/json"
    16  	"k8s.io/apimachinery/pkg/util/jsonmergepatch"
    17  	"k8s.io/apimachinery/pkg/util/rand"
    18  )
    19  
    20  type step int
    21  
    22  const (
    23  	errMarshalSourceForPatch = "failed to marshal source object for computing JSON merge patch"
    24  	errMarshalTargetForPatch = "failed to marshal target object for computing JSON merge patch"
    25  	errMergePatch            = "failed to compute the JSON merge patch document"
    26  	errMergePatchMap         = "failed to unmarshal the JSON merge patch document into map"
    27  	errInvalidStepFmt        = "invalid step ID: %d"
    28  )
    29  
    30  func setApplyStep(name string, s *Step) {
    31  	s.Name = name
    32  	s.Type = StepTypeApply
    33  	s.Apply = &ApplyStep{}
    34  }
    35  
    36  func setPatchStep(name string, s *Step) {
    37  	s.Name = name
    38  	s.Type = StepTypePatch
    39  	s.Patch = &PatchStep{}
    40  	s.Patch.Type = PatchTypeMerge
    41  }
    42  
    43  func setDeleteStep(name string, s *Step) {
    44  	s.Name = name
    45  	s.Type = StepTypeDelete
    46  	deletePolicy := FinalizerPolicyRemove
    47  	s.Delete = &DeleteStep{
    48  		Options: &DeleteOptions{
    49  			FinalizerPolicy: &deletePolicy,
    50  		},
    51  	}
    52  }
    53  
    54  func setExecStep(name string, s *Step) {
    55  	s.Name = name
    56  	s.Type = StepTypeExec
    57  	s.Exec = &ExecStep{
    58  		Command: "sh",
    59  	}
    60  }
    61  
    62  func (pg *PlanGenerator) commitSteps() { //nolint: gocyclo
    63  	if len(pg.Plan.Spec.stepMap) == 0 {
    64  		return
    65  	}
    66  	pg.Plan.Spec.Steps = make([]Step, 0, len(pg.Plan.Spec.stepMap))
    67  	keys := make([]string, 0, len(pg.Plan.Spec.stepMap))
    68  	for s := range pg.Plan.Spec.stepMap {
    69  		keys = append(keys, s)
    70  	}
    71  	// keys slice consist of the step keys of enabled migration steps.
    72  	// step keys are the string representation of integer or float64 numbers,
    73  	// which correspond to the step execution order (greater number executes later)
    74  	// therefore needs to be sorted according to their numeric values.
    75  	// otherwise, sorting the strings directly causes faulty behavior e.g "1" < "10" < "2"
    76  	// sorting will panic if a non-numeric step key is found in keys
    77  	sort.SliceStable(keys, func(i, j int) bool {
    78  		fi, err := strconv.ParseFloat(keys[i], 64)
    79  		if err != nil {
    80  			panic(err)
    81  		}
    82  		fj, err := strconv.ParseFloat(keys[j], 64)
    83  		if err != nil {
    84  			panic(err)
    85  		}
    86  		return fi < fj
    87  	})
    88  	addManualExecution := true
    89  	switch t := pg.source.(type) {
    90  	case *sources:
    91  		for _, source := range t.backends {
    92  			if _, ok := source.(*FileSystemSource); ok {
    93  				addManualExecution = false
    94  				break
    95  			}
    96  		}
    97  	case *FileSystemSource:
    98  		addManualExecution = false
    99  	}
   100  
   101  	if addManualExecution {
   102  		for _, s := range keys {
   103  			AddManualExecution(pg.Plan.Spec.stepMap[s])
   104  			pg.Plan.Spec.Steps = append(pg.Plan.Spec.Steps, *pg.Plan.Spec.stepMap[s])
   105  		}
   106  	} else {
   107  		for _, s := range keys {
   108  			pg.Plan.Spec.Steps = append(pg.Plan.Spec.Steps, *pg.Plan.Spec.stepMap[s])
   109  		}
   110  	}
   111  }
   112  
   113  // AddManualExecution sets the manual execution hint for
   114  // the specified step.
   115  func AddManualExecution(s *Step) {
   116  	switch s.Type {
   117  	case StepTypeExec:
   118  		s.ManualExecution = []string{fmt.Sprintf("%s %s %q", s.Exec.Command, s.Exec.Args[0], strings.Join(s.Exec.Args[1:], " "))}
   119  	case StepTypePatch:
   120  		for _, f := range s.Patch.Files {
   121  			s.ManualExecution = append(s.ManualExecution, fmt.Sprintf("kubectl patch --type='%s' -f %s --patch-file %s", s.Patch.Type, f, f))
   122  		}
   123  	case StepTypeApply:
   124  		for _, f := range s.Apply.Files {
   125  			s.ManualExecution = append(s.ManualExecution, fmt.Sprintf("kubectl apply -f %s", f))
   126  		}
   127  	case StepTypeDelete:
   128  		for _, r := range s.Delete.Resources {
   129  			s.ManualExecution = append(s.ManualExecution, fmt.Sprintf("kubectl delete %s %s", strings.Join([]string{r.Kind, r.Group}, "."), r.Name))
   130  		}
   131  	}
   132  }
   133  
   134  func (pg *PlanGenerator) stepEnabled(s step) bool {
   135  	for _, i := range pg.enabledSteps {
   136  		if i == s {
   137  			return true
   138  		}
   139  	}
   140  	return false
   141  }
   142  
   143  func computeJSONMergePathDoc(source, target unstructured.Unstructured) (map[string]any, error) {
   144  	sourceBuff, err := source.MarshalJSON()
   145  	if err != nil {
   146  		return nil, errors.Wrap(err, errMarshalSourceForPatch)
   147  	}
   148  	targetBuff, err := target.MarshalJSON()
   149  	if err != nil {
   150  		return nil, errors.Wrap(err, errMarshalTargetForPatch)
   151  	}
   152  	patch, err := jsonmergepatch.CreateThreeWayJSONMergePatch(sourceBuff, targetBuff, sourceBuff)
   153  	if err != nil {
   154  		return nil, errors.Wrap(err, errMergePatch)
   155  	}
   156  
   157  	var result map[string]any
   158  	return result, errors.Wrap(json.Unmarshal(patch, &result), errMergePatchMap)
   159  }
   160  
   161  func getQualifiedName(u unstructured.Unstructured) string {
   162  	namePrefix := u.GetName()
   163  	if len(namePrefix) == 0 {
   164  		namePrefix = fmt.Sprintf("%s%s", u.GetGenerateName(), rand.String(5))
   165  	}
   166  	gvk := u.GroupVersionKind()
   167  	return fmt.Sprintf("%s.%ss.%s", namePrefix, strings.ToLower(gvk.Kind), gvk.Group)
   168  }
   169  
   170  func getVersionedName(u unstructured.Unstructured) string {
   171  	v := u.GroupVersionKind().Version
   172  	qName := getQualifiedName(u)
   173  	if v == "" {
   174  		return qName
   175  	}
   176  	return fmt.Sprintf("%s_%s", qName, v)
   177  }