Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding composed.To() method #150

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions resource/composed/composed.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,34 @@ func New() *Unstructured {
return &Unstructured{unstructured.Unstructured{Object: make(map[string]any)}}
}

// To converts a unstructured composed resource to the provided object.
func To[T runtime.Object](un *Unstructured, obj T) error {

// Get known GVKs for the runtime object type
knownGVKs, _, err := Scheme.ObjectKinds(obj)
if err != nil {
return errors.Errorf("could not retrieve GVKs for the provided object: %v", err)
}

// Check if GVK is known as we should not try to convert it if it doesn't match
gvkMatches := false
for _, knownGVK := range knownGVKs {
if knownGVK == un.GetObjectKind().GroupVersionKind() {
gvkMatches = true
}
}

if !gvkMatches {
return errors.Errorf("GVK %v is not known by the scheme for the provided object type", un.GetObjectKind().GroupVersionKind())
}

err = runtime.DefaultUnstructuredConverter.FromUnstructured(un.Object, obj)
if err != nil {
return err
}
return nil
BigGold1310 marked this conversation as resolved.
Show resolved Hide resolved
}

// From creates a new unstructured composed resource from the supplied object.
func From(o runtime.Object) (*Unstructured, error) {
// If the supplied object is already unstructured content, avoid a JSON
Expand Down
148 changes: 148 additions & 0 deletions resource/composed/composed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package composed

import (
"errors"
"fmt"
"testing"

Expand Down Expand Up @@ -201,3 +202,150 @@ func TestFrom(t *testing.T) {
})
}
}

func ExampleTo() {
// Add all v1beta1 types to the scheme so that From can automatically
// determine their apiVersion and kind.
v1beta1.AddToScheme(Scheme)

// Create a unstructured object as we would receive by the function (observed/desired).
ub := &Unstructured{Unstructured: unstructured.Unstructured{Object: map[string]any{
"apiVersion": v1beta1.CRDGroupVersion.String(),
"kind": v1beta1.Bucket_Kind,
"metadata": map[string]any{
"name": "cool-bucket",
},
"spec": map[string]any{
"forProvider": map[string]any{
"region": "us-east-2",
},
},
"status": map[string]any{
"observedGeneration": float64(0),
},
}}}

// Create a strongly typed object from the unstructured object.
sb := &v1beta1.Bucket{}
err := To(ub, sb)
if err != nil {
panic(err)
}
// Now you have a strongly typed Bucket object.
objectLock := true
sb.Spec.ForProvider.ObjectLockEnabled = &objectLock
}

// Test the To function
func TestTo(t *testing.T) {
v1beta1.AddToScheme(Scheme)
type args struct {
un *Unstructured
obj runtime.Object
}
type want struct {
obj interface{}
err error
}

cases := map[string]struct {
reason string
args args
want want
}{
"SuccessfulConversion": {
reason: "A valid unstructured object should convert to a structured object without errors",
args: args{
un: &Unstructured{Unstructured: unstructured.Unstructured{Object: map[string]any{
"apiVersion": v1beta1.CRDGroupVersion.String(),
"kind": v1beta1.Bucket_Kind,
"metadata": map[string]any{
"name": "cool-bucket",
},
"spec": map[string]any{
"forProvider": map[string]any{
"region": "us-east-2",
},
},
"status": map[string]any{
"observedGeneration": float64(0),
},
}}},
obj: &v1beta1.Bucket{},
},
want: want{
obj: &v1beta1.Bucket{
TypeMeta: metav1.TypeMeta{
Kind: v1beta1.Bucket_Kind,
APIVersion: v1beta1.CRDGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "cool-bucket",
},
Spec: v1beta1.BucketSpec{
ForProvider: v1beta1.BucketParameters{
Region: ptr.To[string]("us-east-2"),
},
},
},
err: nil,
},
},
"InvalidGVK": {
reason: "An unstructured object with mismatched GVK should result in an error",
args: args{
un: &Unstructured{Unstructured: unstructured.Unstructured{Object: map[string]any{
"apiVersion": "test.example.io",
"kind": "Unknown",
"metadata": map[string]any{
"name": "cool-bucket",
},
"spec": map[string]any{
"forProvider": map[string]any{
"region": "us-east-2",
},
},
"status": map[string]any{
"observedGeneration": float64(0),
},
}}},
obj: &v1beta1.Bucket{},
},
want: want{
obj: &v1beta1.Bucket{},
err: errors.New("GVK /test.example.io, Kind=Unknown is not known by the scheme for the provided object type"),
},
},
}

for name, tc := range cases {
t.Run(name, func(t *testing.T) {
err := To(tc.args.un, tc.args.obj)

// Compare the resulting object with the expected one
if diff := cmp.Diff(tc.want.obj, tc.args.obj); diff != "" {
t.Errorf("\n%s\nTo(...): -want, +got:\n%s", tc.reason, diff)
}
// Compare the error with the expected error
if diff := cmp.Diff(tc.want.err, err, EquateErrors()); diff != "" {
t.Errorf("\n%s\nTo(...): -want error, +got error:\n%s", tc.reason, diff)
}
})
}
}

// EquateErrors returns true if the supplied errors are of the same type and
// produce identical strings. This mirrors the error comparison behaviour of
// https://github.com/go-test/deep,
//
// This differs from cmpopts.EquateErrors, which does not test for error strings
// and instead returns whether one error 'is' (in the errors.Is sense) the
// other.
func EquateErrors() cmp.Option {
return cmp.Comparer(func(a, b error) bool {
if a == nil || b == nil {
return a == nil && b == nil
}
return a.Error() == b.Error()
})
}
Loading