Skip to content

Commit

Permalink
Add support for converting Crossplane Configurations
Browse files Browse the repository at this point in the history
- Add the migration.Executor interface

Signed-off-by: Alper Rifat Ulucinar <ulucinar@users.noreply.github.com>
  • Loading branch information
ulucinar committed May 24, 2023
1 parent 7eb5f5a commit 7247294
Show file tree
Hide file tree
Showing 7 changed files with 233 additions and 29 deletions.
27 changes: 27 additions & 0 deletions pkg/migration/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ package migration

import (
"github.com/crossplane/crossplane-runtime/pkg/fieldpath"
"github.com/crossplane/crossplane-runtime/pkg/resource"
xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -27,6 +29,7 @@ import (

const (
errFromUnstructured = "failed to convert from unstructured.Unstructured to the managed resource type"
errFromUnstructuredConf = "failed to convert from unstructured.Unstructured to Crossplane Configuration metadata"
errToUnstructured = "failed to convert from the managed resource type to unstructured.Unstructured"
errRawExtensionUnmarshal = "failed to unmarshal runtime.RawExtension"

Expand Down Expand Up @@ -165,3 +168,27 @@ func addNameGVK(u unstructured.Unstructured, target map[string]any) map[string]a
target["metadata"] = m
return target
}

func toManagedResource(c runtime.ObjectCreater, u unstructured.Unstructured) (resource.Managed, bool, error) {
gvk := u.GroupVersionKind()
if gvk == xpv1.CompositionGroupVersionKind {
return nil, false, nil
}
obj, err := c.New(gvk)
if err != nil {
return nil, false, errors.Wrapf(err, errFmtNewObject, gvk)
}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil {
return nil, false, errors.Wrap(err, errFromUnstructured)
}
mg, ok := obj.(resource.Managed)
return mg, ok, nil
}

func toConfiguration(u unstructured.Unstructured) (*xpmetav1.Configuration, error) {
conf := &xpmetav1.Configuration{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, conf); err != nil {
return nil, errors.Wrap(err, errFromUnstructuredConf)
}
return conf, nil
}
31 changes: 31 additions & 0 deletions pkg/migration/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2023 Upbound Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package migration

import "fmt"

type errUnsupportedStepType struct {
planStep Step
}

func (e errUnsupportedStepType) Error() string {
return fmt.Sprintf("executor does not support steps of type %q in step: %s", e.planStep.Type, e.planStep.Name)
}

func NewUnsupportedStepTypeError(s Step) error {
return errUnsupportedStepType{
planStep: s,
}
}
24 changes: 24 additions & 0 deletions pkg/migration/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package migration
import (
"github.com/crossplane/crossplane-runtime/pkg/resource"
xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
)

// ResourceConverter converts a managed resource from
Expand Down Expand Up @@ -69,6 +70,14 @@ type PatchSetConverter interface {
PatchSets(psMap map[string]*xpv1.PatchSet) error
}

// ConfigurationConverter converts a Crossplane Configuration's metadata.
type ConfigurationConverter interface {
// Configuration takes a Crossplane Configuration metadata, converts it,
// and stores the converted metadata in its argument. Returns any errors
// encountered during the conversion.
Configuration(configuration *xpmetav1.Configuration) error
}

// Source is a source for reading resource manifests
type Source interface {
// HasNext returns `true` if the Source implementation has a next manifest
Expand All @@ -88,3 +97,18 @@ type Target interface {
// Delete deletes a resource manifest from this Target
Delete(o UnstructuredWithMetadata) error
}

// Executor is a migration plan executor.
type Executor interface {
// Init initializes an executor using the supplied executor specific
// configuration data.
Init(config any) error
// Step asks the executor to execute the next step passing any available
// context from the previous step, and returns any new context to be passed
// to the next step if there exists one.
Step(s Step, ctx any) (any, error)
// Destroy is called when all the steps have been executed,
// or a step has returned an error, and we would like to stop
// executing the plan.
Destroy() error
}
45 changes: 30 additions & 15 deletions pkg/migration/plan_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/claim"
"github.com/crossplane/crossplane-runtime/pkg/resource/unstructured/composite"
xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand All @@ -45,7 +46,8 @@ const (
errCompositePause = "failed to pause composite resource"
errCompositesEdit = "failed to edit composite resources"
errCompositesStart = "failed to start composite resources"
errCompositionMigrate = "failed to migrate the composition"
errCompositionMigrateFmt = "failed to migrate the composition: %s"
errConfigurationMigrateFmt = "failed to migrate the configuration: %s"
errComposedTemplateBase = "failed to migrate the base of a composed template"
errComposedTemplateMigrate = "failed to migrate the composed templates of the composition"
errResourceOutput = "failed to output migrated resource"
Expand Down Expand Up @@ -197,17 +199,25 @@ func (pg *PlanGenerator) convert() error { //nolint: gocyclo
return errors.Wrap(err, errSourceNext)
}
switch gvk := o.Object.GroupVersionKind(); gvk {
case xpmetav1.ConfigurationGroupVersionKind:
target, converted, err := pg.convertConfiguration(o)
if err != nil {
return errors.Wrapf(err, errConfigurationMigrateFmt, o.Object.GetName())
}
if converted {
fmt.Printf("converted configuration: %v\n", target)
}
case xpv1.CompositionGroupVersionKind:
target, converted, err := pg.convertComposition(o)
if err != nil {
return errors.Wrap(err, errCompositionMigrate)
return errors.Wrapf(err, errCompositionMigrateFmt, o.Object.GetName())
}
if converted {
migratedName := fmt.Sprintf("%s-migrated", o.Object.GetName())
convertedComposition[o.Object.GetName()] = migratedName
target.Object.SetName(migratedName)
if err := pg.stepNewComposition(target); err != nil {
return errors.Wrap(err, errCompositionMigrate)
return errors.Wrapf(err, errCompositionMigrateFmt, o.Object.GetName())
}
}
default:
Expand Down Expand Up @@ -314,20 +324,25 @@ func assertMetadataName(parentName string, resources []resource.Managed) {
}
}

func toManagedResource(c runtime.ObjectCreater, u unstructured.Unstructured) (resource.Managed, bool, error) {
gvk := u.GroupVersionKind()
if gvk == xpv1.CompositionGroupVersionKind {
return nil, false, nil
}
obj, err := c.New(gvk)
func (pg *PlanGenerator) convertConfiguration(o UnstructuredWithMetadata) (*UnstructuredWithMetadata, bool, error) {
conf, err := toConfiguration(o.Object)
if err != nil {
return nil, false, errors.Wrapf(err, errFmtNewObject, gvk)
return nil, false, err
}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil {
return nil, false, errors.Wrap(err, errFromUnstructured)
isConverted := false
for _, confConv := range pg.registry.configurationConverters {
if confConv.re == nil || confConv.converter == nil || !confConv.re.MatchString(o.Object.GetName()) {
continue
}
if err := confConv.converter.Configuration(conf); err != nil {
return nil, false, errors.Wrapf(err, "failed to call converter on Configuration: %s", conf.GetName())
}
isConverted = true
}
mg, ok := obj.(resource.Managed)
return mg, ok, nil
return &UnstructuredWithMetadata{
Object: ToSanitizedUnstructured(conf),
Metadata: o.Metadata,
}, isConverted, nil
}

func (pg *PlanGenerator) convertComposition(o UnstructuredWithMetadata) (*UnstructuredWithMetadata, bool, error) { // nolint:gocyclo
Expand All @@ -344,7 +359,7 @@ func (pg *PlanGenerator) convertComposition(o UnstructuredWithMetadata) (*Unstru
for _, cmp := range comp.Spec.Resources {
u, err := FromRawExtension(cmp.Base)
if err != nil {
return nil, false, errors.Wrap(err, errCompositionMigrate)
return nil, false, errors.Wrapf(err, errCompositionMigrateFmt, o.Object.GetName())
}
gvk := u.GroupVersionKind()
converted, ok, err := pg.convertResource(UnstructuredWithMetadata{
Expand Down
32 changes: 30 additions & 2 deletions pkg/migration/plan_generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
xpresource "github.com/crossplane/crossplane-runtime/pkg/resource"
"github.com/crossplane/crossplane-runtime/pkg/test"
v1 "github.com/crossplane/crossplane/apis/apiextensions/v1"
xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
"github.com/google/go-cmp/cmp"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -95,7 +96,7 @@ func TestGeneratePlan(t *testing.T) {
re: AllCompositions,
converter: &testConverter{},
},
}),
}, nil),
},
want: want{
migrationPlanPath: "testdata/plan/generated/migration_plan.yaml",
Expand All @@ -112,6 +113,20 @@ func TestGeneratePlan(t *testing.T) {
},
},
},
"PlanWithConfiguration": {
fields: fields{
source: newTestSource(map[string]Metadata{
"testdata/plan/configuration.yaml": {}}),
target: newTestTarget(),
registry: getRegistryWithConverters(nil, nil, []configurationConverter{
{
re: AllConfigurations,
converter: &testConverter{},
},
}),
},
want: want{},
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
Expand Down Expand Up @@ -239,6 +254,16 @@ func (f *testTarget) Delete(o UnstructuredWithMetadata) error {

type testConverter struct{}

func (f *testConverter) Configuration(c *xpmetav1.Configuration) error {
c.Spec.DependsOn = []xpmetav1.Dependency{
{
Provider: ptrFromString("xpkg.upbound.io/upbound/provider-aws-eks"),
Version: ">=v0.17.0",
},
}
return nil
}

func (f *testConverter) PatchSets(psMap map[string]*v1.PatchSet) error {
psMap["ps1"].Patches[0].ToFieldPath = ptrFromString(`spec.forProvider.tags["key3"]`)
psMap["ps6"].Patches[0].ToFieldPath = ptrFromString(`spec.forProvider.tags["key4"]`)
Expand All @@ -249,14 +274,17 @@ func ptrFromString(s string) *string {
return &s
}

func getRegistryWithConverters(converters map[schema.GroupVersionKind]delegatingConverter, psConverters []patchSetConverter) *Registry {
func getRegistryWithConverters(converters map[schema.GroupVersionKind]delegatingConverter, psConverters []patchSetConverter, confConverters []configurationConverter) *Registry {
scheme := runtime.NewScheme()
scheme.AddKnownTypeWithName(fake.MigrationSourceGVK, &fake.MigrationSourceObject{})
scheme.AddKnownTypeWithName(fake.MigrationTargetGVK, &fake.MigrationTargetObject{})
r := NewRegistry(scheme)
for _, c := range psConverters {
r.RegisterPatchSetConverter(c.re, c.converter)
}
for _, c := range confConverters {
r.RegisterConfigurationConverter(c.re, c.converter)
}
for gvk, d := range converters {
r.RegisterConversionFunctions(gvk, d.rFn, d.cmpFn, nil)
}
Expand Down
Loading

0 comments on commit 7247294

Please sign in to comment.