mirror of https://github.com/knative/pkg.git
Add a simple ToUnstructured method for converting our types to unstructured. (#900)
This adds a ToUnstructured method to complement the FromUnstructured method we have had for some time. The ToUnstructured method is effectively scoped to Knative-style types since we expect it to implement both kmeta.Accessor and kmeta.OwnerRefable in order to ensure that the TypeMeta is always properly populated.
This commit is contained in:
parent
47b250654a
commit
9f41433241
|
@ -18,16 +18,49 @@ package duck
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"knative.dev/pkg/kmeta"
|
||||
)
|
||||
|
||||
// Marshallable is implementated by the Unstructured K8s types.
|
||||
type Marshalable interface {
|
||||
MarshalJSON() ([]byte, error)
|
||||
// OneOfOurs is the union of our Accessor interface and the OwnerRefable interface
|
||||
// that is implemented by our resources that implement the kmeta.Accessor.
|
||||
type OneOfOurs interface {
|
||||
kmeta.Accessor
|
||||
kmeta.OwnerRefable
|
||||
}
|
||||
|
||||
// ToUnstructured takes an instance of a OneOfOurs compatible type and
|
||||
// converts it to unstructured.Unstructured. We take OneOfOurs in place
|
||||
// or runtime.Object because sometimes we get resources that do not have their
|
||||
// TypeMeta populated but that is required for unstructured.Unstructured to
|
||||
// deserialize things, so we leverage our content-agnostic GroupVersionKind()
|
||||
// method to populate this as-needed (in a copy, so that we don't modify the
|
||||
// informer's copy, if that is what we are passed).
|
||||
func ToUnstructured(desired OneOfOurs) (*unstructured.Unstructured, error) {
|
||||
// If the TypeMeta is not populated, then unmarshalling will fail, so ensure
|
||||
// the TypeMeta is populated. See also EnsureTypeMeta.
|
||||
if gvk := desired.GroupVersionKind(); gvk.Version == "" || gvk.Kind == "" {
|
||||
gvk = desired.GetGroupVersionKind()
|
||||
desired = desired.DeepCopyObject().(OneOfOurs)
|
||||
desired.SetGroupVersionKind(gvk)
|
||||
}
|
||||
|
||||
// Convert desired to unstructured.Unstructured
|
||||
b, err := json.Marshal(desired)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ud := &unstructured.Unstructured{}
|
||||
if err := json.Unmarshal(b, ud); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ud, nil
|
||||
}
|
||||
|
||||
// FromUnstructured takes unstructured object from (say from client-go/dynamic) and
|
||||
// converts it into our duck types.
|
||||
func FromUnstructured(obj Marshalable, target interface{}) error {
|
||||
func FromUnstructured(obj json.Marshaler, target interface{}) error {
|
||||
// Use the unstructured marshaller to ensure it's proper JSON
|
||||
raw, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
|
|
|
@ -17,18 +17,20 @@ limitations under the License.
|
|||
package duck
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"encoding/json"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
. "knative.dev/pkg/testing"
|
||||
)
|
||||
|
||||
func TestFromUnstructuredFooable(t *testing.T) {
|
||||
tcs := []struct {
|
||||
name string
|
||||
in Marshalable
|
||||
in json.Marshaler
|
||||
want FooStatus
|
||||
wantError error
|
||||
}{{
|
||||
|
@ -70,23 +72,86 @@ func TestFromUnstructuredFooable(t *testing.T) {
|
|||
want: FooStatus{},
|
||||
wantError: nil,
|
||||
}}
|
||||
|
||||
for _, tc := range tcs {
|
||||
raw, err := json.Marshal(tc.in)
|
||||
if err != nil {
|
||||
panic("failed to marshal")
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
raw, err := json.Marshal(tc.in)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal: %v", err)
|
||||
}
|
||||
|
||||
t.Logf("Marshalled : %s", string(raw))
|
||||
t.Logf("Marshalled: %s", string(raw))
|
||||
|
||||
got := Foo{}
|
||||
err = FromUnstructured(tc.in, &got)
|
||||
if err != tc.wantError {
|
||||
t.Errorf("Unexpected error for %q: %v", string(tc.name), err)
|
||||
continue
|
||||
}
|
||||
got := Foo{}
|
||||
if err := FromUnstructured(tc.in, &got); err != tc.wantError {
|
||||
t.Fatalf("FromUnstructured() = %v", err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tc.want, got.Status) {
|
||||
t.Errorf("Decode(%q) want: %+v\ngot: %+v", string(tc.name), tc.want, got)
|
||||
}
|
||||
if !cmp.Equal(tc.want, got.Status) {
|
||||
t.Errorf("ToUnstructured (-want, +got) = %s", cmp.Diff(tc.want, got.Status))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToUnstructured(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in OneOfOurs
|
||||
want *unstructured.Unstructured
|
||||
wantError error
|
||||
}{{
|
||||
name: "missing TypeMeta",
|
||||
in: &Resource{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "blah",
|
||||
},
|
||||
},
|
||||
want: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "pkg.knative.dev/v2",
|
||||
"kind": "Resource",
|
||||
"metadata": map[string]interface{}{
|
||||
"creationTimestamp": nil,
|
||||
"name": "blah",
|
||||
},
|
||||
"spec": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "with TypeMeta",
|
||||
in: &Resource{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "blah",
|
||||
},
|
||||
},
|
||||
want: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"creationTimestamp": nil,
|
||||
"name": "blah",
|
||||
},
|
||||
"spec": map[string]interface{}{},
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := ToUnstructured(tc.in)
|
||||
if err != tc.wantError {
|
||||
t.Fatalf("ToUnstructured() = %v", err)
|
||||
}
|
||||
|
||||
if !cmp.Equal(tc.want, got) {
|
||||
t.Errorf("ToUnstructured (-want, +got) = %s", cmp.Diff(tc.want, got))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"knative.dev/pkg/apis"
|
||||
)
|
||||
|
||||
|
@ -49,6 +50,11 @@ type ResourceSpec struct {
|
|||
FieldThatsImmutableWithDefault string `json:"fieldThatsImmutableWithDefault,omitempty"`
|
||||
}
|
||||
|
||||
// GetGroupVersionKind returns the GroupVersionKind.
|
||||
func (r *Resource) GetGroupVersionKind() schema.GroupVersionKind {
|
||||
return SchemeGroupVersion.WithKind("Resource")
|
||||
}
|
||||
|
||||
// GetUntypedSpec returns the spec of the resource.
|
||||
func (r *Resource) GetUntypedSpec() interface{} {
|
||||
return r.Spec
|
||||
|
|
Loading…
Reference in New Issue