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 }