Skip to content

Commit

Permalink
Add support for Configuration.pkg converters
Browse files Browse the repository at this point in the history
Signed-off-by: Alper Rifat Ulucinar <[email protected]>
  • Loading branch information
ulucinar committed May 26, 2023
1 parent b8634e8 commit aaf1c16
Show file tree
Hide file tree
Showing 18 changed files with 439 additions and 240 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,64 @@ import (
"fmt"
"strconv"

xpmetav1 "github.com/crossplane/crossplane/apis/pkg/meta/v1"
xpmetav1alpha1 "github.com/crossplane/crossplane/apis/pkg/meta/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

const (
// configuration migration steps follow any existing API migration steps
stepNewServiceScopedProvider = iota + stepAPIEnd + 1
stepPatchSkipDependencyResolution
stepConfigurationPackageDisableDepResolution
stepEditPackageLock
stepDeleteMonolithicProvider
stepActivateServiceScopedProviderRevision
stepEditConfigurations
stepEditConfigurationMetadata
stepEditConfigurationPackage
stepConfigurationPackageEnableDepResolution
)

const (
errConfigurationOutput = "failed to output configuration JSON merge document"
errConfigurationMetadataOutput = "failed to output configuration JSON merge document"
)

func (pg *PlanGenerator) convertConfigurationMetadata(o UnstructuredWithMetadata) (*UnstructuredWithMetadata, bool, error) {
isConverted := false
var conf metav1.Object
var err error
for _, confConv := range pg.registry.configurationConverters {
if confConv.re == nil || confConv.converter == nil || !confConv.re.MatchString(o.Object.GetName()) {
continue
}

conf, err = toConfigurationMetadata(o.Object)
if err != nil {
return nil, false, err
}
switch o.Object.GroupVersionKind().Version {
case "v1alpha1":
err = confConv.converter.ConfigurationMetadataV1Alpha1(conf.(*xpmetav1alpha1.Configuration))
default:
err = confConv.converter.ConfigurationMetadataV1(conf.(*xpmetav1.Configuration))
}
if err != nil {
return nil, false, errors.Wrapf(err, "failed to call converter on Configuration: %s", conf.GetName())
}
// TODO: if a configuration converter only converts a specific version,
// (or does not convert the given configuration),
// we will have a false positive. Better to compute and check
// a diff here.
isConverted = true
}
return &UnstructuredWithMetadata{
Object: ToSanitizedUnstructured(conf),
Metadata: o.Metadata,
}, isConverted, nil
}

func (pg *PlanGenerator) stepConfiguration(s step) *Step {
return pg.stepConfigurationWithSubStep(s, false)
}
Expand All @@ -53,7 +93,7 @@ func (pg *PlanGenerator) configurationSubStep(s step) string {
return pg.subSteps[s]
}

func (pg *PlanGenerator) stepConfigurationWithSubStep(s step, newSubStep bool) *Step {
func (pg *PlanGenerator) stepConfigurationWithSubStep(s step, newSubStep bool) *Step { // nolint:gocyclo // easy to follow all steps here
stepKey := strconv.Itoa(int(s))
if newSubStep {
stepKey = fmt.Sprintf("%s.%s", stepKey, pg.configurationSubStep(s))
Expand All @@ -66,41 +106,28 @@ func (pg *PlanGenerator) stepConfigurationWithSubStep(s step, newSubStep bool) *
switch s { // nolint:gocritic,exhaustive
case stepNewServiceScopedProvider:
setApplyStep("new-ssop", pg.Plan.Spec.stepMap[stepKey])
case stepPatchSkipDependencyResolution:
setPatchStep("skip-dependency-resolution", pg.Plan.Spec.stepMap[stepKey])
case stepConfigurationPackageDisableDepResolution:
setPatchStep("disable-dependency-resolution", pg.Plan.Spec.stepMap[stepKey])
case stepConfigurationPackageEnableDepResolution:
setPatchStep("enable-dependency-resolution", pg.Plan.Spec.stepMap[stepKey])
case stepEditConfigurationPackage:
setPatchStep("edit-configuration-package", pg.Plan.Spec.stepMap[stepKey])
case stepEditPackageLock:
setPatchStep("edit-package-lock", pg.Plan.Spec.stepMap[stepKey])
case stepDeleteMonolithicProvider:
setDeleteStep("delete-monolithic-provider", pg.Plan.Spec.stepMap[stepKey])
case stepActivateServiceScopedProviderRevision:
setPatchStep("activate-ssop", pg.Plan.Spec.stepMap[stepKey])
case stepEditConfigurations:
setPatchStep("edit-configurations", pg.Plan.Spec.stepMap[stepKey])
case stepEditConfigurationMetadata:
setPatchStep("edit-configuration-metadata", pg.Plan.Spec.stepMap[stepKey])
default:
panic(fmt.Sprintf(errInvalidStepFmt, s))
}
return pg.Plan.Spec.stepMap[stepKey]
}

func (pg *PlanGenerator) stepEditConfiguration(source UnstructuredWithMetadata, target *UnstructuredWithMetadata) error {
// set spec.SkipDependencyResolution: true for the configuration
s := pg.stepConfiguration(stepPatchSkipDependencyResolution)
source.Metadata.Path = fmt.Sprintf("%s/%s.yaml", s.Name, getVersionedName(source.Object))
s.Patch.Files = append(s.Patch.Files, source.Metadata.Path)
if err := pg.target.Put(UnstructuredWithMetadata{
Object: unstructured.Unstructured{
Object: addNameGVK(source.Object, map[string]any{
"spec": map[string]any{
"skipDependencyResolution": true,
},
}),
},
Metadata: source.Metadata,
}); err != nil {
return errors.Wrapf(err, errEditMonolithFmt, source.Metadata.Path)
}

s = pg.stepConfiguration(stepEditConfigurations)
func (pg *PlanGenerator) stepEditConfigurationMetadata(source UnstructuredWithMetadata, target *UnstructuredWithMetadata) error {
s := pg.stepConfiguration(stepEditConfigurationMetadata)
target.Metadata.Path = fmt.Sprintf("%s/%s.yaml", s.Name, getVersionedName(target.Object))
s.Patch.Files = append(s.Patch.Files, target.Metadata.Path)
patchMap, err := computeJSONMergePathDoc(source.Object, target.Object)
Expand All @@ -112,5 +139,5 @@ func (pg *PlanGenerator) stepEditConfiguration(source UnstructuredWithMetadata,
Object: addNameGVK(target.Object, patchMap),
},
Metadata: target.Metadata,
}), errConfigurationOutput)
}), errConfigurationMetadataOutput)
}
112 changes: 112 additions & 0 deletions pkg/migration/configurationpackage_steps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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"

"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

const (
errEditConfigurationPackageFmt = `failed to put the edited Configuration package: %s`
)

func (pg *PlanGenerator) convertConfigurationPackage(o UnstructuredWithMetadata) error {
pkg, err := toConfigurationPackageV1(o.Object)
if err != nil {
return err
}

// add step for disabling the dependency resolution
// for the configuration package
s := pg.stepConfiguration(stepConfigurationPackageDisableDepResolution)
p := fmt.Sprintf("%s/%s.yaml", s.Name, getVersionedName(o.Object))
s.Patch.Files = append(s.Patch.Files, p)
if err := pg.target.Put(UnstructuredWithMetadata{
Object: unstructured.Unstructured{
Object: addNameGVK(o.Object, map[string]any{
"spec": map[string]any{
"skipDependencyResolution": true,
},
}),
},
Metadata: Metadata{
Path: p,
},
}); err != nil {
return err
}

// add step for enabling the dependency resolution
// for the configuration package
s = pg.stepConfiguration(stepConfigurationPackageEnableDepResolution)
p = fmt.Sprintf("%s/%s.yaml", s.Name, getVersionedName(o.Object))
s.Patch.Files = append(s.Patch.Files, p)
if err := pg.target.Put(UnstructuredWithMetadata{
Object: unstructured.Unstructured{
Object: addNameGVK(o.Object, map[string]any{
"spec": map[string]any{
"skipDependencyResolution": false,
},
}),
},
Metadata: Metadata{
Path: p,
},
}); err != nil {
return err
}

// add the step for editing the configuration package
for _, pkgConv := range pg.registry.configurationPackageConverters {
if pkgConv.re == nil || pkgConv.converter == nil || !pkgConv.re.MatchString(pkg.Spec.Package) {
continue
}
err := pkgConv.converter.ConfigurationPackageV1(pkg)
if err != nil {
return errors.Wrapf(err, "failed to call converter on Configuration package: %s", pkg.Spec.Package)
}
// TODO: if a converter only converts a specific version,
// (or does not convert the given configuration),
// we will have a false positive. Better to compute and check
// a diff here.
target := &UnstructuredWithMetadata{
Object: ToSanitizedUnstructured(pkg),
Metadata: o.Metadata,
}
if err := pg.stepEditConfigurationPackage(o, target); err != nil {
return err
}
}
return nil
}

func (pg *PlanGenerator) stepEditConfigurationPackage(source UnstructuredWithMetadata, t *UnstructuredWithMetadata) error {
s := pg.stepConfigurationWithSubStep(stepEditConfigurationPackage, true)
t.Metadata.Path = fmt.Sprintf("%s/%s.yaml", s.Name, getVersionedName(t.Object))
s.Patch.Files = append(s.Patch.Files, t.Metadata.Path)
patchMap, err := computeJSONMergePathDoc(source.Object, t.Object)
if err != nil {
return err
}
return errors.Wrap(pg.target.Put(UnstructuredWithMetadata{
Object: unstructured.Unstructured{
Object: addNameGVK(t.Object, patchMap),
},
Metadata: t.Metadata,
}), errEditConfigurationPackageFmt)
}
29 changes: 19 additions & 10 deletions pkg/migration/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ 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"
errFromUnstructuredProvider = "failed to convert from unstructured.Unstructured to Crossplane Provider package"
errFromUnstructured = "failed to convert from unstructured.Unstructured to the managed resource type"
errFromUnstructuredConfMeta = "failed to convert from unstructured.Unstructured to Crossplane Configuration metadata"
errFromUnstructuredConfPackage = "failed to convert from unstructured.Unstructured to Crossplane Configuration package"
errFromUnstructuredProvider = "failed to convert from unstructured.Unstructured to Crossplane Provider package"
// errFromUnstructuredLock = "failed to convert from unstructured.Unstructured to Crossplane package lock"
errToUnstructured = "failed to convert from the managed resource type to unstructured.Unstructured"
errRawExtensionUnmarshal = "failed to unmarshal runtime.RawExtension"
Expand Down Expand Up @@ -195,30 +196,38 @@ func toManagedResource(c runtime.ObjectCreater, u unstructured.Unstructured) (re
return mg, ok, nil
}

func toConfigurationV1(u unstructured.Unstructured) (*xpmetav1.Configuration, error) {
func toConfigurationPackageV1(u unstructured.Unstructured) (*xppkgv1.Configuration, error) {
conf := &xppkgv1.Configuration{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, conf); err != nil {
return nil, errors.Wrap(err, errFromUnstructuredConfPackage)
}
return conf, nil
}

func toConfigurationMetadataV1(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 nil, errors.Wrap(err, errFromUnstructuredConfMeta)
}
return conf, nil
}

func toConfigurationV1Alpha1(u unstructured.Unstructured) (*xpmetav1alpha1.Configuration, error) {
func toConfigurationMetadataV1Alpha1(u unstructured.Unstructured) (*xpmetav1alpha1.Configuration, error) {
conf := &xpmetav1alpha1.Configuration{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, conf); err != nil {
return nil, errors.Wrap(err, errFromUnstructuredConf)
return nil, errors.Wrap(err, errFromUnstructuredConfMeta)
}
return conf, nil
}

func toConfiguration(u unstructured.Unstructured) (metav1.Object, error) {
func toConfigurationMetadata(u unstructured.Unstructured) (metav1.Object, error) {
var conf metav1.Object
var err error
switch u.GroupVersionKind().Version {
case "v1alpha1":
conf, err = toConfigurationV1Alpha1(u)
conf, err = toConfigurationMetadataV1Alpha1(u)
default:
conf, err = toConfigurationV1(u)
conf, err = toConfigurationMetadataV1(u)
}
return conf, err
}
Expand Down
21 changes: 15 additions & 6 deletions pkg/migration/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,25 @@ type PatchSetConverter interface {
PatchSets(psMap map[string]*xpv1.PatchSet) error
}

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

// ConfigurationPackageConverter converts a Crossplane configuration package.
type ConfigurationPackageConverter interface {
// ConfigurationPackageV1 takes a Crossplane Configuration v1 package,
// converts it possibly to multiple packages and returns
// the converted configuration package.
// Returns any errors encountered during the conversion.
ConfigurationPackageV1(pkg *xppkgv1.Configuration) error
}

// ProviderPackageConverter converts a Crossplane provider package.
Expand Down
Loading

0 comments on commit aaf1c16

Please sign in to comment.