github.com/crossplane/upjet@v1.3.0/pkg/config/conversion/conversions.go (about) 1 // SPDX-FileCopyrightText: 2023 The Crossplane Authors <https://crossplane.io> 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 5 package conversion 6 7 import ( 8 "github.com/crossplane/crossplane-runtime/pkg/fieldpath" 9 "github.com/crossplane/crossplane-runtime/pkg/resource" 10 "github.com/pkg/errors" 11 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 12 "k8s.io/apimachinery/pkg/runtime" 13 ) 14 15 const ( 16 // AllVersions denotes that a Conversion is applicable for all versions 17 // of an API with which the Conversion is registered. It can be used for 18 // both the conversion source or target API versions. 19 AllVersions = "*" 20 ) 21 22 // Conversion is the interface for the API version converters. 23 // Conversion implementations registered for a source, target 24 // pair are called in chain so Conversion implementations can be modular, e.g., 25 // a Conversion implementation registered for a specific source and target 26 // versions does not have to contain all the needed API conversions between 27 // these two versions. 28 type Conversion interface { 29 // Applicable should return true if this Conversion is applicable while 30 // converting the API of the `src` object to the API of the `dst` object. 31 Applicable(src, dst runtime.Object) bool 32 } 33 34 // PavedConversion is an optimized Conversion between two fieldpath.Paved 35 // objects. PavedConversion implementations for a specific source and target 36 // version pair are chained together and the source and the destination objects 37 // are paved once at the beginning of the chained PavedConversion.ConvertPaved 38 // calls. The target fieldpath.Paved object is then converted into the original 39 // resource.Terraformed object at the end of the chained calls. This prevents 40 // the intermediate conversions between fieldpath.Paved and 41 // the resource.Terraformed representations of the same object, and the 42 // fieldpath.Paved representation is convenient for writing generic 43 // Conversion implementations not bound to a specific type. 44 type PavedConversion interface { 45 Conversion 46 // ConvertPaved converts from the `src` paved object to the `dst` 47 // paved object and returns `true` if the conversion has been done, 48 // `false` otherwise, together with any errors encountered. 49 ConvertPaved(src, target *fieldpath.Paved) (bool, error) 50 } 51 52 // ManagedConversion defines a Conversion from a specific source 53 // resource.Managed type to a target one. Generic Conversion 54 // implementations may prefer to implement the PavedConversion interface. 55 // Implementations of ManagedConversion can do type assertions to 56 // specific source and target types, and so, they are expected to be 57 // strongly typed. 58 type ManagedConversion interface { 59 Conversion 60 // ConvertManaged converts from the `src` managed resource to the `dst` 61 // managed resource and returns `true` if the conversion has been done, 62 // `false` otherwise, together with any errors encountered. 63 ConvertManaged(src, target resource.Managed) (bool, error) 64 } 65 66 type baseConversion struct { 67 sourceVersion string 68 targetVersion string 69 } 70 71 func newBaseConversion(sourceVersion, targetVersion string) baseConversion { 72 return baseConversion{ 73 sourceVersion: sourceVersion, 74 targetVersion: targetVersion, 75 } 76 } 77 78 func (c *baseConversion) Applicable(src, dst runtime.Object) bool { 79 return (c.sourceVersion == AllVersions || c.sourceVersion == src.GetObjectKind().GroupVersionKind().Version) && 80 (c.targetVersion == AllVersions || c.targetVersion == dst.GetObjectKind().GroupVersionKind().Version) 81 } 82 83 type fieldCopy struct { 84 baseConversion 85 sourceField string 86 targetField string 87 } 88 89 func (f *fieldCopy) ConvertPaved(src, target *fieldpath.Paved) (bool, error) { 90 if !f.Applicable(&unstructured.Unstructured{Object: src.UnstructuredContent()}, 91 &unstructured.Unstructured{Object: target.UnstructuredContent()}) { 92 return false, nil 93 } 94 v, err := src.GetValue(f.sourceField) 95 // TODO: the field might actually exist in the schema and 96 // missing in the object. Or, it may not exist in the schema. 97 // For a field that does not exist in the schema, we had better error. 98 if fieldpath.IsNotFound(err) { 99 return false, nil 100 } 101 if err != nil { 102 return false, errors.Wrapf(err, "failed to get the field %q from the conversion source object", f.sourceField) 103 } 104 return true, errors.Wrapf(target.SetValue(f.targetField, v), "failed to set the field %q of the conversion target object", f.targetField) 105 } 106 107 // NewFieldRenameConversion returns a new Conversion that implements a 108 // field renaming conversion from the specified `sourceVersion` to the specified 109 // `targetVersion` of an API. The field's name in the `sourceVersion` is given 110 // with the `sourceField` parameter and its name in the `targetVersion` is 111 // given with `targetField` parameter. 112 func NewFieldRenameConversion(sourceVersion, sourceField, targetVersion, targetField string) Conversion { 113 return &fieldCopy{ 114 baseConversion: newBaseConversion(sourceVersion, targetVersion), 115 sourceField: sourceField, 116 targetField: targetField, 117 } 118 } 119 120 type customConverter func(src, target resource.Managed) error 121 122 type customConversion struct { 123 baseConversion 124 customConverter customConverter 125 } 126 127 func (cc *customConversion) ConvertManaged(src, target resource.Managed) (bool, error) { 128 if !cc.Applicable(src, target) || cc.customConverter == nil { 129 return false, nil 130 } 131 return true, errors.Wrap(cc.customConverter(src, target), "failed to apply the converter function") 132 } 133 134 // NewCustomConverter returns a new Conversion from the specified 135 // `sourceVersion` of an API to the specified `targetVersion` and invokes 136 // the specified converter function to perform the conversion on the 137 // managed resources. 138 func NewCustomConverter(sourceVersion, targetVersion string, converter func(src, target resource.Managed) error) Conversion { 139 return &customConversion{ 140 baseConversion: newBaseConversion(sourceVersion, targetVersion), 141 customConverter: converter, 142 } 143 }