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 }