mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 03:11:40 +00:00
Merge pull request #114439 from alexzielenski/apiserver/smd/conversion-smaller
add direct construction of TypeConverter from OpenAPI
This commit is contained in:
commit
d475085776
@ -493,7 +493,9 @@ func addTypeMetaProperties(s *spec.Schema, v2 bool) {
|
|||||||
func (b *builder) buildListSchema(v2 bool) *spec.Schema {
|
func (b *builder) buildListSchema(v2 bool) *spec.Schema {
|
||||||
name := definitionPrefix + util.ToRESTFriendlyName(fmt.Sprintf("%s/%s/%s", b.group, b.version, b.kind))
|
name := definitionPrefix + util.ToRESTFriendlyName(fmt.Sprintf("%s/%s/%s", b.group, b.version, b.kind))
|
||||||
doc := fmt.Sprintf("List of %s. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md", b.plural)
|
doc := fmt.Sprintf("List of %s. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md", b.plural)
|
||||||
s := new(spec.Schema).WithDescription(fmt.Sprintf("%s is a list of %s", b.listKind, b.kind)).
|
s := new(spec.Schema).
|
||||||
|
Typed("object", "").
|
||||||
|
WithDescription(fmt.Sprintf("%s is a list of %s", b.listKind, b.kind)).
|
||||||
WithRequired("items").
|
WithRequired("items").
|
||||||
SetProperty("items", *spec.ArrayProperty(spec.RefSchema(refForOpenAPIVersion(name, v2))).WithDescription(doc)).
|
SetProperty("items", *spec.ArrayProperty(spec.RefSchema(refForOpenAPIVersion(name, v2))).WithDescription(doc)).
|
||||||
SetProperty("metadata", *spec.RefSchema(refForOpenAPIVersion(listMetaSchemaRef, v2)).WithDescription(swaggerPartialObjectMetadataListDescriptions["metadata"]))
|
SetProperty("metadata", *spec.RefSchema(refForOpenAPIVersion(listMetaSchemaRef, v2)).WithDescription(swaggerPartialObjectMetadataListDescriptions["metadata"]))
|
||||||
|
@ -43,19 +43,3 @@ var fakeTypeConverter = func() internal.TypeConverter {
|
|||||||
}
|
}
|
||||||
return typeConverter
|
return typeConverter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var testTypeConverter = func() internal.TypeConverter {
|
|
||||||
data, err := ioutil.ReadFile(filepath.Join("testdata", "swagger.json"))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
spec := spec.Swagger{}
|
|
||||||
if err := json.Unmarshal(data, &spec); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
typeConverter, err := internal.NewTypeConverter(&spec, false)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return typeConverter
|
|
||||||
}()
|
|
||||||
|
@ -21,9 +21,10 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilopenapi "k8s.io/apiserver/pkg/util/openapi"
|
"k8s.io/kube-openapi/pkg/schemaconv"
|
||||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
smdschema "sigs.k8s.io/structured-merge-diff/v4/schema"
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/value"
|
"sigs.k8s.io/structured-merge-diff/v4/value"
|
||||||
)
|
)
|
||||||
@ -36,29 +37,35 @@ type TypeConverter interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type typeConverter struct {
|
type typeConverter struct {
|
||||||
parser *managedfields.GvkParser
|
parser map[schema.GroupVersionKind]*typed.ParseableType
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ TypeConverter = &typeConverter{}
|
var _ TypeConverter = &typeConverter{}
|
||||||
|
|
||||||
// NewTypeConverter builds a TypeConverter from a proto.Models. This
|
// NewTypeConverter builds a TypeConverter from a spec.Swagger. This
|
||||||
// will automatically find the proper version of the object, and the
|
// will automatically find the proper version of the object, and the
|
||||||
// corresponding schema information.
|
// corresponding schema information.
|
||||||
func NewTypeConverter(openapiSpec *spec.Swagger, preserveUnknownFields bool) (TypeConverter, error) {
|
func NewTypeConverter(openapiSpec *spec.Swagger, preserveUnknownFields bool) (TypeConverter, error) {
|
||||||
models, err := utilopenapi.ToProtoModels(openapiSpec)
|
pointerDefs := map[string]*spec.Schema{}
|
||||||
if err != nil {
|
for k, v := range openapiSpec.Definitions {
|
||||||
return nil, err
|
vCopy := v
|
||||||
|
pointerDefs[k] = &vCopy
|
||||||
}
|
}
|
||||||
parser, err := managedfields.NewGVKParser(models, preserveUnknownFields)
|
|
||||||
|
typeSchema, err := schemaconv.ToSchemaFromOpenAPI(pointerDefs, preserveUnknownFields)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("failed to convert models to schema: %v", err)
|
||||||
}
|
}
|
||||||
return &typeConverter{parser: parser}, nil
|
|
||||||
|
typeParser := typed.Parser{Schema: smdschema.Schema{Types: typeSchema.Types}}
|
||||||
|
tr := indexModels(&typeParser, pointerDefs)
|
||||||
|
|
||||||
|
return &typeConverter{parser: tr}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
|
func (c *typeConverter) ObjectToTyped(obj runtime.Object) (*typed.TypedValue, error) {
|
||||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||||
t := c.parser.Type(gvk)
|
t := c.parser[gvk]
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return nil, NewNoCorrespondingTypeError(gvk)
|
return nil, NewNoCorrespondingTypeError(gvk)
|
||||||
}
|
}
|
||||||
@ -110,3 +117,86 @@ func valueToObject(val value.Value) (runtime.Object, error) {
|
|||||||
return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu)
|
return nil, fmt.Errorf("failed to convert value to unstructured for type %T", vu)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func indexModels(
|
||||||
|
typeParser *typed.Parser,
|
||||||
|
openAPISchemas map[string]*spec.Schema,
|
||||||
|
) map[schema.GroupVersionKind]*typed.ParseableType {
|
||||||
|
tr := map[schema.GroupVersionKind]*typed.ParseableType{}
|
||||||
|
for modelName, model := range openAPISchemas {
|
||||||
|
gvkList := parseGroupVersionKind(model.Extensions)
|
||||||
|
if len(gvkList) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedType := typeParser.Type(modelName)
|
||||||
|
for _, gvk := range gvkList {
|
||||||
|
if len(gvk.Kind) > 0 {
|
||||||
|
tr[schema.GroupVersionKind(gvk)] = &parsedType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get and parse GroupVersionKind from the extension. Returns empty if it doesn't have one.
|
||||||
|
func parseGroupVersionKind(extensions map[string]interface{}) []schema.GroupVersionKind {
|
||||||
|
gvkListResult := []schema.GroupVersionKind{}
|
||||||
|
|
||||||
|
// Get the extensions
|
||||||
|
gvkExtension, ok := extensions["x-kubernetes-group-version-kind"]
|
||||||
|
if !ok {
|
||||||
|
return []schema.GroupVersionKind{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gvk extension must be a list of at least 1 element.
|
||||||
|
gvkList, ok := gvkExtension.([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return []schema.GroupVersionKind{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, gvk := range gvkList {
|
||||||
|
var group, version, kind string
|
||||||
|
|
||||||
|
// gvk extension list must be a map with group, version, and
|
||||||
|
// kind fields
|
||||||
|
if gvkMap, ok := gvk.(map[interface{}]interface{}); ok {
|
||||||
|
group, ok = gvkMap["group"].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
version, ok = gvkMap["version"].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kind, ok = gvkMap["kind"].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if gvkMap, ok := gvk.(map[string]interface{}); ok {
|
||||||
|
group, ok = gvkMap["group"].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
version, ok = gvkMap["version"].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kind, ok = gvkMap["kind"].(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
gvkListResult = append(gvkListResult, schema.GroupVersionKind{
|
||||||
|
Group: group,
|
||||||
|
Version: version,
|
||||||
|
Kind: kind,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return gvkListResult
|
||||||
|
}
|
||||||
|
@ -14,22 +14,25 @@ See the License for the specific language governing permissions and
|
|||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package internal_test
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
smdschema "sigs.k8s.io/structured-merge-diff/v4/schema"
|
||||||
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
"sigs.k8s.io/structured-merge-diff/v4/typed"
|
||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/internal"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTypeConverter(t *testing.T) {
|
func TestTypeConverter(t *testing.T) {
|
||||||
dtc := internal.NewDeducedTypeConverter()
|
dtc := NewDeducedTypeConverter()
|
||||||
|
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name string
|
name string
|
||||||
@ -106,7 +109,7 @@ spec:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testObjectToTyped(t *testing.T, tc internal.TypeConverter, y string) {
|
func testObjectToTyped(t *testing.T, tc TypeConverter, y string) {
|
||||||
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
|
||||||
if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil {
|
if err := yaml.Unmarshal([]byte(y), &obj.Object); err != nil {
|
||||||
t.Fatalf("Failed to parse yaml object: %v", err)
|
t.Fatalf("Failed to parse yaml object: %v", err)
|
||||||
@ -169,3 +172,146 @@ spec:
|
|||||||
}
|
}
|
||||||
result = *r
|
result = *r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIndexModels(t *testing.T) {
|
||||||
|
myDefs := map[string]*spec.Schema{
|
||||||
|
// Show empty GVK extension is ignored
|
||||||
|
"def0": {
|
||||||
|
VendorExtensible: spec.VendorExtensible{
|
||||||
|
Extensions: spec.Extensions{
|
||||||
|
"x-kubernetes-group-version-kind": []interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Show nil GVK is ignored
|
||||||
|
"def0.0": {
|
||||||
|
VendorExtensible: spec.VendorExtensible{
|
||||||
|
Extensions: spec.Extensions{
|
||||||
|
"x-kubernetes-group-version-kind": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Show this is ignored
|
||||||
|
"def0.1": {},
|
||||||
|
// Show allows binding a single GVK
|
||||||
|
"def1": {
|
||||||
|
VendorExtensible: spec.VendorExtensible{
|
||||||
|
Extensions: spec.Extensions{
|
||||||
|
"x-kubernetes-group-version-kind": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "mygroup",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "MyKind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Show allows bindings with two versions
|
||||||
|
"def2": {
|
||||||
|
VendorExtensible: spec.VendorExtensible{
|
||||||
|
Extensions: spec.Extensions{
|
||||||
|
"x-kubernetes-group-version-kind": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "mygroup",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "MyOtherKind",
|
||||||
|
},
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "mygroup",
|
||||||
|
"version": "v2",
|
||||||
|
"kind": "MyOtherKind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Show that we can mix and match GVKs from other definitions, and
|
||||||
|
// that both map[interface{}]interface{} and map[string]interface{}
|
||||||
|
// are allowed
|
||||||
|
"def3": {
|
||||||
|
VendorExtensible: spec.VendorExtensible{
|
||||||
|
Extensions: spec.Extensions{
|
||||||
|
"x-kubernetes-group-version-kind": []interface{}{
|
||||||
|
map[string]interface{}{
|
||||||
|
"group": "mygroup",
|
||||||
|
"version": "v3",
|
||||||
|
"kind": "MyKind",
|
||||||
|
},
|
||||||
|
map[interface{}]interface{}{
|
||||||
|
"group": "mygroup",
|
||||||
|
"version": "v3",
|
||||||
|
"kind": "MyOtherKind",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
myTypes := []smdschema.TypeDef{
|
||||||
|
{
|
||||||
|
Name: "def0",
|
||||||
|
Atom: smdschema.Atom{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "def0.1",
|
||||||
|
Atom: smdschema.Atom{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "def0.2",
|
||||||
|
Atom: smdschema.Atom{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "def1",
|
||||||
|
Atom: smdschema.Atom{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "def2",
|
||||||
|
Atom: smdschema.Atom{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "def3",
|
||||||
|
Atom: smdschema.Atom{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := typed.Parser{Schema: smdschema.Schema{Types: myTypes}}
|
||||||
|
gvkIndex := indexModels(&parser, myDefs)
|
||||||
|
|
||||||
|
require.Len(t, gvkIndex, 5)
|
||||||
|
|
||||||
|
resultNames := map[schema.GroupVersionKind]string{}
|
||||||
|
for k, v := range gvkIndex {
|
||||||
|
require.NotNil(t, v.TypeRef.NamedType)
|
||||||
|
resultNames[k] = *v.TypeRef.NamedType
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, resultNames, map[schema.GroupVersionKind]string{
|
||||||
|
{
|
||||||
|
Group: "mygroup",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "MyKind",
|
||||||
|
}: "def1",
|
||||||
|
{
|
||||||
|
Group: "mygroup",
|
||||||
|
Version: "v1",
|
||||||
|
Kind: "MyOtherKind",
|
||||||
|
}: "def2",
|
||||||
|
{
|
||||||
|
Group: "mygroup",
|
||||||
|
Version: "v2",
|
||||||
|
Kind: "MyOtherKind",
|
||||||
|
}: "def2",
|
||||||
|
{
|
||||||
|
Group: "mygroup",
|
||||||
|
Version: "v3",
|
||||||
|
Kind: "MyKind",
|
||||||
|
}: "def3",
|
||||||
|
{
|
||||||
|
Group: "mygroup",
|
||||||
|
Version: "v3",
|
||||||
|
Kind: "MyOtherKind",
|
||||||
|
}: "def3",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user