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  }