-
Notifications
You must be signed in to change notification settings - Fork 89
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add sample converter and plan generator
Signed-off-by: Alper Rifat Ulucinar <[email protected]>
- Loading branch information
Showing
6 changed files
with
312 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package migration | ||
|
||
import ( | ||
"github.com/crossplane/crossplane-runtime/pkg/fieldpath" | ||
"github.com/crossplane/crossplane-runtime/pkg/resource" | ||
"github.com/pkg/errors" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
"k8s.io/apimachinery/pkg/util/json" | ||
"k8s.io/client-go/kubernetes/scheme" | ||
) | ||
|
||
const ( | ||
errFromUnstructured = "failed to convert from unstructured.Unstructured to the managed resource type" | ||
errToUnstructured = "failed to convert from the managed resource type to unstructured.Unstructured" | ||
errRawExtensionUnmarshal = "failed to unmarshal runtime.RawExtension" | ||
|
||
errFmtPavedDelete = "failed to delete fieldpath %q from paved" | ||
errFmtNewObject = "failed to instantiate a new runtime.Object using scheme.Scheme for: %s" | ||
) | ||
|
||
func CopyInto(source any, target any, targetGVK schema.GroupVersionKind, skipFieldPaths ...string) (any, error) { | ||
u := ToUnstructured(source) | ||
paved := fieldpath.Pave(u.Object) | ||
skipFieldPaths = append(skipFieldPaths, "apiVersion", "kind") | ||
for _, p := range skipFieldPaths { | ||
if err := paved.DeleteField(p); err != nil { | ||
return nil, errors.Wrapf(err, errFmtPavedDelete, p) | ||
} | ||
} | ||
u.SetGroupVersionKind(targetGVK) | ||
return FromUnstructured(target, u), nil | ||
} | ||
|
||
func FromUnstructured(mg any, u unstructured.Unstructured) any { | ||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, mg); err != nil { | ||
panic(errors.Wrap(err, errFromUnstructured)) | ||
} | ||
return mg | ||
} | ||
|
||
func sanitizeResource(m map[string]any) map[string]any { | ||
delete(m, "status") | ||
if _, ok := m["metadata"]; !ok { | ||
return m | ||
} | ||
metadata := m["metadata"].(map[string]any) | ||
|
||
if v := metadata["creationTimestamp"]; v == nil { | ||
delete(metadata, "creationTimestamp") | ||
} | ||
if len(metadata) == 0 { | ||
delete(m, "metadata") | ||
} | ||
return m | ||
} | ||
|
||
func ToUnstructured(mg any) unstructured.Unstructured { | ||
m, err := runtime.DefaultUnstructuredConverter.ToUnstructured(mg) | ||
if err != nil { | ||
panic(errors.Wrap(err, errToUnstructured)) | ||
} | ||
return unstructured.Unstructured{ | ||
Object: sanitizeResource(m), | ||
} | ||
} | ||
|
||
func FromRawExtension(r runtime.RawExtension) (unstructured.Unstructured, error) { | ||
var m map[string]interface{} | ||
if err := json.Unmarshal(r.Raw, &m); err != nil { | ||
return unstructured.Unstructured{}, errors.Wrap(err, errRawExtensionUnmarshal) | ||
} | ||
return unstructured.Unstructured{ | ||
Object: m, | ||
}, nil | ||
} | ||
|
||
func ToManagedResource(gvk schema.GroupVersionKind, u unstructured.Unstructured) (resource.Managed, error) { | ||
obj, err := scheme.Scheme.New(gvk) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, errFmtNewObject, gvk) | ||
} | ||
return FromUnstructured(obj, u).(resource.Managed), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package migration | ||
|
||
type NoopTarget struct{} | ||
|
||
func (_ NoopTarget) Put(_ UnstructuredWithMetadata) error { | ||
return nil | ||
} | ||
|
||
func (_ NoopTarget) Patch(_ UnstructuredWithMetadata) error { | ||
return nil | ||
} | ||
|
||
func (_ NoopTarget) Delete(_ UnstructuredWithMetadata) error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
/* | ||
Copyright 2022 Upbound Inc. | ||
*/ | ||
|
||
package migration | ||
|
||
import ( | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
|
||
xpv1 "github.com/crossplane/crossplane/apis/apiextensions/v1" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
const ( | ||
errSourceHasNext = "failed to generate migration plan: Could not check next object from source" | ||
errSourceNext = "failed to generate migration plan: Could not get next object from source" | ||
errUnstructuredConvert = "failed to convert from unstructured object to v1.Composition" | ||
errUnstructuredMarshal = "failed to marshal unstructured object to JSON" | ||
errResourceMigrate = "failed to migrate resource" | ||
errCompositionMigrate = "failed to migrate the composition" | ||
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" | ||
errCompositionOutput = "failed to output migrated composition" | ||
errPlanGeneration = "failed to generate the migration plan" | ||
) | ||
|
||
type PlanGenerator struct { | ||
source Source | ||
target Target | ||
registry Registry | ||
} | ||
|
||
func NewPlanGenerator(source Source, target Target) PlanGenerator { | ||
return PlanGenerator{ | ||
source: source, | ||
target: target, | ||
registry: registry, | ||
} | ||
} | ||
|
||
func (pg *PlanGenerator) GeneratePlan() (*Plan, error) { | ||
if err := pg.convert(); err != nil { | ||
return nil, errors.Wrap(err, errPlanGeneration) | ||
} | ||
return &Plan{}, nil | ||
} | ||
|
||
func (pg *PlanGenerator) convert() error { //nolint: gocyclo | ||
for hasNext, err := pg.source.HasNext(); ; hasNext, err = pg.source.HasNext() { | ||
if err != nil { | ||
return errors.Wrap(err, errSourceHasNext) | ||
} | ||
if !hasNext { | ||
break | ||
} | ||
o, err := pg.source.Next() | ||
if err != nil { | ||
return errors.Wrap(err, errSourceNext) | ||
} | ||
switch gvk := o.Object.GroupVersionKind(); gvk { | ||
case xpv1.CompositionGroupVersionKind: | ||
target, err := pg.convertComposition(o) | ||
if err != nil { | ||
return errors.Wrap(err, errCompositionMigrate) | ||
} | ||
if err := pg.target.Put(*target); err != nil { | ||
return errors.Wrap(err, errCompositionOutput) | ||
} | ||
default: | ||
targets, err := pg.convertResource(gvk, o) | ||
if err != nil { | ||
return errors.Wrap(err, errResourceMigrate) | ||
} | ||
for _, tu := range targets { | ||
if err := pg.target.Put(tu); err != nil { | ||
return errors.Wrap(err, errResourceOutput) | ||
} | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (pg *PlanGenerator) convertResource(gvk schema.GroupVersionKind, o UnstructuredWithMetadata) ([]UnstructuredWithMetadata, error) { | ||
conv := pg.registry[gvk] | ||
if conv == nil { | ||
return []UnstructuredWithMetadata{o}, nil | ||
} | ||
mg, err := ToManagedResource(gvk, o.Object) | ||
if err != nil { | ||
return nil, errors.Wrap(err, errResourceMigrate) | ||
} | ||
resources, err := conv.Resources(mg) | ||
if err != nil { | ||
return nil, errors.Wrap(err, errResourceMigrate) | ||
} | ||
converted := make([]UnstructuredWithMetadata, 0, len(resources)) | ||
for _, mg := range resources { | ||
converted = append(converted, UnstructuredWithMetadata{ | ||
Object: ToUnstructured(mg), | ||
Metadata: o.Metadata, | ||
}) | ||
} | ||
return converted, nil | ||
} | ||
|
||
func (pg *PlanGenerator) convertComposition(o UnstructuredWithMetadata) (*UnstructuredWithMetadata, error) { | ||
c := xpv1.Composition{} | ||
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object.Object, &c); err != nil { | ||
return nil, errors.Wrap(err, errUnstructuredConvert) | ||
} | ||
var targetResources []*xpv1.ComposedTemplate | ||
for _, cmp := range c.Spec.Resources { | ||
u, err := FromRawExtension(cmp.Base) | ||
if err != nil { | ||
return nil, errors.Wrap(err, errCompositionMigrate) | ||
} | ||
gvk := u.GetObjectKind().GroupVersionKind() | ||
converted, err := pg.convertResource(gvk, UnstructuredWithMetadata{ | ||
Object: u, | ||
Metadata: o.Metadata, | ||
}) | ||
if err != nil { | ||
return nil, errors.Wrap(err, errComposedTemplateBase) | ||
} | ||
cmps := make([]*xpv1.ComposedTemplate, 0, len(converted)) | ||
for _, u := range converted { | ||
buff, err := u.Object.MarshalJSON() | ||
if err != nil { | ||
return nil, errors.Wrap(err, errUnstructuredMarshal) | ||
} | ||
c := cmp.DeepCopy() | ||
c.Base = runtime.RawExtension{ | ||
Raw: buff, | ||
} | ||
cmps = append(cmps, c) | ||
} | ||
conv := pg.registry[gvk] | ||
if conv != nil { | ||
if err := conv.ComposedTemplates(cmp, cmps...); err != nil { | ||
return nil, errors.Wrap(err, errComposedTemplateMigrate) | ||
} | ||
} | ||
targetResources = append(targetResources, cmps...) | ||
} | ||
c.Spec.Resources = make([]xpv1.ComposedTemplate, 0, len(targetResources)) | ||
for _, cmp := range targetResources { | ||
c.Spec.Resources = append(c.Spec.Resources, *cmp) | ||
} | ||
return &UnstructuredWithMetadata{ | ||
Object: ToUnstructured(&c), | ||
Metadata: o.Metadata, | ||
}, nil | ||
} |
Oops, something went wrong.