1/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package fieldmanager
18
19import (
20	"fmt"
21
22	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
23	"k8s.io/apimachinery/pkg/runtime"
24	"k8s.io/apimachinery/pkg/runtime/schema"
25	"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
26	"k8s.io/kube-openapi/pkg/util/proto"
27	"sigs.k8s.io/structured-merge-diff/v4/typed"
28	"sigs.k8s.io/structured-merge-diff/v4/value"
29)
30
31// TypeConverter allows you to convert from runtime.Object to
32// typed.TypedValue and the other way around.
33type TypeConverter interface {
34	ObjectToTyped(runtime.Object) (*typed.TypedValue, error)
35	TypedToObject(*typed.TypedValue) (runtime.Object, error)
36}
37
38// DeducedTypeConverter is a TypeConverter for CRDs that don't have a
39// schema. It does implement the same interface though (and create the
40// same types of objects), so that everything can still work the same.
41// CRDs are merged with all their fields being "atomic" (lists
42// included).
43//
44// Note that this is not going to be sufficient for converting to/from
45// CRDs that have a schema defined (we don't support that schema yet).
46// TODO(jennybuckley): Use the schema provided by a CRD if it exists.
47type DeducedTypeConverter struct{}
48
49var _ TypeConverter = DeducedTypeConverter{}
50
51// ObjectToTyped converts an object into a TypedValue with a "deduced type".
52func (DeducedTypeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
53	switch o := obj.(type) {
54	case *unstructured.Unstructured:
55		return typed.DeducedParseableType.FromUnstructured(o.UnstructuredContent())
56	default:
57		return typed.DeducedParseableType.FromStructured(obj)
58	}
59}
60
61// TypedToObject transforms the typed value into a runtime.Object. That
62// is not specific to deduced type.
63func (DeducedTypeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
64	return valueToObject(value.AsValue())
65}
66
67type typeConverter struct {
68	parser *internal.GvkParser
69}
70
71var _ TypeConverter = &typeConverter{}
72
73// NewTypeConverter builds a TypeConverter from a proto.Models. This
74// will automatically find the proper version of the object, and the
75// corresponding schema information.
76func NewTypeConverter(models proto.Models, preserveUnknownFields bool) (TypeConverter, error) {
77	parser, err := internal.NewGVKParser(models, preserveUnknownFields)
78	if err != nil {
79		return nil, err
80	}
81	return &typeConverter{parser: parser}, nil
82}
83
84func (c *typeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
85	gvk := obj.GetObjectKind().GroupVersionKind()
86	t := c.parser.Type(gvk)
87	if t == nil {
88		return nil, newNoCorrespondingTypeError(gvk)
89	}
90	switch o := obj.(type) {
91	case *unstructured.Unstructured:
92		return t.FromUnstructured(o.UnstructuredContent())
93	default:
94		return t.FromStructured(obj)
95	}
96}
97
98func (c *typeConverter) TypedToObject(value *typed.TypedValue) (runtime.Object, error) {
99	return valueToObject(value.AsValue())
100}
101
102func valueToObject(val value.Value) (runtime.Object, error) {
103	vu := val.Unstructured()
104	switch o := vu.(type) {
105	case map[string]interface{}:
106		return &unstructured.Unstructured{Object: o}, nil
107	default:
108		return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu)
109	}
110}
111
112type noCorrespondingTypeErr struct {
113	gvk schema.GroupVersionKind
114}
115
116func newNoCorrespondingTypeError(gvk schema.GroupVersionKind) error {
117	return &noCorrespondingTypeErr{gvk: gvk}
118}
119
120func (k *noCorrespondingTypeErr) Error() string {
121	return fmt.Sprintf("no corresponding type for %v", k.gvk)
122}
123
124func isNoCorrespondingTypeError(err error) bool {
125	if err == nil {
126		return false
127	}
128	_, ok := err.(*noCorrespondingTypeErr)
129	return ok
130}
131