From de93f9f572ccaf216d90c86c188e990009afc4f9 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Fri, 26 Apr 2019 13:46:32 -0700 Subject: [PATCH 1/2] Update sigs.k8s.io/smd to latest version --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 57811641b..835e4a1bf 100644 --- a/go.mod +++ b/go.mod @@ -33,5 +33,5 @@ require ( gopkg.in/yaml.v2 v2.2.1 k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92 - sigs.k8s.io/structured-merge-diff v0.0.0-20190416230737-b2ed7e1d99f6 + sigs.k8s.io/structured-merge-diff v0.0.0-20190426204423-ea680f03cc65 ) diff --git a/go.sum b/go.sum index 1ec701bcf..b76a17548 100644 --- a/go.sum +++ b/go.sum @@ -60,5 +60,5 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6 h1:4s3/R4+OYYYUKptXPhZKjQ04WJ6Eh k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92 h1:PgoMI/L1Nu5Vmvgm+vGheLuxKST8h6FMOqggyAFtHPc= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -sigs.k8s.io/structured-merge-diff v0.0.0-20190416230737-b2ed7e1d99f6 h1:HxGn4yiP+T5dFdwGo2WX/Jsi3OAjSuSkFLF/7QsshxI= -sigs.k8s.io/structured-merge-diff v0.0.0-20190416230737-b2ed7e1d99f6/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= +sigs.k8s.io/structured-merge-diff v0.0.0-20190426204423-ea680f03cc65 h1:xJNnO2qzHtgVCSPoGkkltSpyEX7D7IJw1TmbE3G/7lY= +sigs.k8s.io/structured-merge-diff v0.0.0-20190426204423-ea680f03cc65/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= From 62f1ac39d5ef09488ccbb7d04b5def26108d3e05 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Tue, 2 Apr 2019 10:46:12 -0700 Subject: [PATCH 2/2] Add parsing of openapi oneof/union semantic into smd --- pkg/schemaconv/smd.go | 111 ++++++++++++++++++++++++ pkg/schemaconv/testdata/new-schema.yaml | 61 +++++++++++++ pkg/schemaconv/testdata/swagger.json | 37 ++++++++ 3 files changed, 209 insertions(+) diff --git a/pkg/schemaconv/smd.go b/pkg/schemaconv/smd.go index 7a531923c..af9d9c357 100644 --- a/pkg/schemaconv/smd.go +++ b/pkg/schemaconv/smd.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "path" + "sort" "strings" "k8s.io/kube-openapi/pkg/util/proto" @@ -145,6 +146,109 @@ func (c *convert) makeRef(model proto.Schema) schema.TypeRef { return tr } +func makeUnions(extensions map[string]interface{}) ([]schema.Union, error) { + schemaUnions := []schema.Union{} + if iunions, ok := extensions["x-kubernetes-unions"]; ok { + unions, ok := iunions.([]interface{}) + if !ok { + return nil, fmt.Errorf(`"x-kubernetes-unions" should be a list, got %#v`, unions) + } + for _, iunion := range unions { + union, ok := iunion.(map[interface{}]interface{}) + if !ok { + return nil, fmt.Errorf(`"x-kubernetes-unions" items should be a map of string to unions, got %#v`, iunion) + } + unionMap := map[string]interface{}{} + for k, v := range union { + key, ok := k.(string) + if !ok { + return nil, fmt.Errorf(`"x-kubernetes-unions" has non-string key: %#v`, k) + } + unionMap[key] = v + } + schemaUnion, err := makeUnion(unionMap) + if err != nil { + return nil, err + } + schemaUnions = append(schemaUnions, schemaUnion) + } + } + + // Make sure we have no overlap between unions + fs := map[string]struct{}{} + for _, u := range schemaUnions { + if u.Discriminator != nil { + if _, ok := fs[*u.Discriminator]; ok { + return nil, fmt.Errorf("%v field appears multiple times in unions", *u.Discriminator) + } + fs[*u.Discriminator] = struct{}{} + } + for _, f := range u.Fields { + if _, ok := fs[f.FieldName]; ok { + return nil, fmt.Errorf("%v field appears multiple times in unions", f.FieldName) + } + fs[f.FieldName] = struct{}{} + } + } + + return schemaUnions, nil +} + +func makeUnion(extensions map[string]interface{}) (schema.Union, error) { + union := schema.Union{ + Fields: []schema.UnionField{}, + } + + if idiscriminator, ok := extensions["discriminator"]; ok { + discriminator, ok := idiscriminator.(string) + if !ok { + return schema.Union{}, fmt.Errorf(`"discriminator" must be a string, got: %#v`, idiscriminator) + } + union.Discriminator = &discriminator + } + + if ifields, ok := extensions["fields-discriminated"]; ok { + fields, ok := ifields.(map[interface{}]interface{}) + if !ok { + return schema.Union{}, fmt.Errorf(`"fields-discriminated" must be a map[string]string, got: %#v`, ifields) + } + // Needs sorted keys by field. + keys := []string{} + for ifield := range fields { + field, ok := ifield.(string) + if !ok { + return schema.Union{}, fmt.Errorf(`"fields-discriminated": field must be a string, got: %#v`, ifield) + } + keys = append(keys, field) + + } + sort.Strings(keys) + reverseMap := map[string]struct{}{} + for _, field := range keys { + value := fields[field] + discriminated, ok := value.(string) + if !ok { + return schema.Union{}, fmt.Errorf(`"fields-discriminated"/%v: value must be a string, got: %#v`, field, value) + } + union.Fields = append(union.Fields, schema.UnionField{ + FieldName: field, + DiscriminatedBy: discriminated, + }) + + // Check that we don't have the same discriminatedBy multiple times. + if _, ok := reverseMap[discriminated]; ok { + return schema.Union{}, fmt.Errorf("Multiple fields have the same discriminated name: %v", discriminated) + } + reverseMap[discriminated] = struct{}{} + } + } + + if union.Discriminator != nil && len(union.Fields) == 0 { + return schema.Union{}, fmt.Errorf("discriminator set to %v, but no fields in union", *union.Discriminator) + } + return union, nil +} + func (c *convert) VisitKind(k *proto.Kind) { a := c.top() a.Struct = &schema.Struct{} @@ -157,6 +261,13 @@ func (c *convert) VisitKind(k *proto.Kind) { }) } + unions, err := makeUnions(k.GetExtensions()) + if err != nil { + c.reportError(err.Error()) + return + } + a.Struct.Unions = unions + // TODO: Get element relationship when we start adding it to the spec. } diff --git a/pkg/schemaconv/testdata/new-schema.yaml b/pkg/schemaconv/testdata/new-schema.yaml index 42f2f1864..fa6afd912 100644 --- a/pkg/schemaconv/testdata/new-schema.yaml +++ b/pkg/schemaconv/testdata/new-schema.yaml @@ -929,6 +929,11 @@ types: - name: type type: scalar: string + unions: + - discriminator: type + fields: + - fieldName: rollingUpdate + discriminatedBy: RollingUpdate - name: io.k8s.api.apps.v1beta1.RollbackConfig struct: fields: @@ -6621,6 +6626,62 @@ types: - name: vsphereVolume type: namedType: io.k8s.api.core.v1.VsphereVirtualDiskVolumeSource + unions: + - fields: + - fieldName: awsElasticBlockStore + discriminatedBy: AWSElasticBlockStore + - fieldName: azureDisk + discriminatedBy: AzureDisk + - fieldName: azureFile + discriminatedBy: AzureFile + - fieldName: cephfs + discriminatedBy: CephFS + - fieldName: cinder + discriminatedBy: Cinder + - fieldName: configMap + discriminatedBy: ConfigMap + - fieldName: downwardAPI + discriminatedBy: DownwardAPI + - fieldName: emptyDir + discriminatedBy: EmptyDir + - fieldName: fc + discriminatedBy: FC + - fieldName: flexVolume + discriminatedBy: FlexVolume + - fieldName: flocker + discriminatedBy: Flocker + - fieldName: gcePersistentDisk + discriminatedBy: GCEPersistentDisk + - fieldName: gitRepo + discriminatedBy: GitRepo + - fieldName: glusterfs + discriminatedBy: Glusterfs + - fieldName: hostPath + discriminatedBy: HostPath + - fieldName: iscsi + discriminatedBy: ISCSI + - fieldName: nfs + discriminatedBy: NFS + - fieldName: persistentVolumeClaim + discriminatedBy: PersistentVolumeClaim + - fieldName: photonPersistentDisk + discriminatedBy: PhotonPersistentDisk + - fieldName: portworxVolume + discriminatedBy: PortworxVolume + - fieldName: projected + discriminatedBy: Projected + - fieldName: quobyte + discriminatedBy: Quobyte + - fieldName: rbd + discriminatedBy: RBD + - fieldName: scaleIO + discriminatedBy: ScaleIO + - fieldName: secret + discriminatedBy: Secret + - fieldName: storageos + discriminatedBy: StorageOS + - fieldName: vsphereVolume + discriminatedBy: VsphereVolume - name: io.k8s.api.core.v1.VolumeDevice struct: fields: diff --git a/pkg/schemaconv/testdata/swagger.json b/pkg/schemaconv/testdata/swagger.json index f843d0348..2a3c97e09 100644 --- a/pkg/schemaconv/testdata/swagger.json +++ b/pkg/schemaconv/testdata/swagger.json @@ -78578,6 +78578,12 @@ }, "io.k8s.api.apps.v1beta1.DeploymentStrategy": { "description": "DeploymentStrategy describes how to replace existing pods with new ones.", + "x-kubernetes-unions": [{ + "discriminator": "type", + "fields-discriminated": { + "rollingUpdate": "RollingUpdate" + } + }], "properties": { "rollingUpdate": { "description": "Rolling update config params. Present only if DeploymentStrategyType = RollingUpdate.", @@ -87522,6 +87528,37 @@ }, "io.k8s.api.core.v1.Volume": { "description": "Volume represents a named volume in a pod that may be accessed by any container in the pod.", + "x-kubernetes-unions": [{ + "fields-discriminated": { + "awsElasticBlockStore": "AWSElasticBlockStore", + "azureDisk": "AzureDisk", + "azureFile": "AzureFile", + "cephfs": "CephFS", + "cinder": "Cinder", + "configMap": "ConfigMap", + "downwardAPI": "DownwardAPI", + "emptyDir": "EmptyDir", + "fc": "FC", + "flexVolume": "FlexVolume", + "flocker": "Flocker", + "gcePersistentDisk": "GCEPersistentDisk", + "gitRepo": "GitRepo", + "glusterfs": "Glusterfs", + "hostPath": "HostPath", + "iscsi": "ISCSI", + "nfs": "NFS", + "persistentVolumeClaim": "PersistentVolumeClaim", + "photonPersistentDisk": "PhotonPersistentDisk", + "portworxVolume": "PortworxVolume", + "projected": "Projected", + "quobyte": "Quobyte", + "rbd": "RBD", + "scaleIO": "ScaleIO", + "secret": "Secret", + "storageos": "StorageOS", + "vsphereVolume": "VsphereVolume" + } + }], "required": [ "name" ],