From 7bde8167be30296ef0a0de1d8d09c1f8cb90e4e5 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Mon, 30 Sep 2024 09:33:49 +0200 Subject: [PATCH] feat: openapi generator with complete information inferring Signed-off-by: Marc Nuri --- .../openapi/generator/cmd/openapi.go | 49 ++++++- .../openapi/generator/go.mod | 26 ++-- .../openapi/generator/go.sum | 40 +++-- .../openapi/generator/pkg/openapi/openapi.go | 94 ++++++++++++ .../openapi/generator/pkg/parser/parser.go | 138 ++++++++++++++++++ .../schema/generator/GeneratorSettings.java | 18 +++ 6 files changed, 339 insertions(+), 26 deletions(-) create mode 100644 kubernetes-model-generator/openapi/generator/pkg/openapi/openapi.go create mode 100644 kubernetes-model-generator/openapi/generator/pkg/parser/parser.go diff --git a/kubernetes-model-generator/openapi/generator/cmd/openapi.go b/kubernetes-model-generator/openapi/generator/cmd/openapi.go index 69d1db56e4f..b8d53683fcf 100644 --- a/kubernetes-model-generator/openapi/generator/cmd/openapi.go +++ b/kubernetes-model-generator/openapi/generator/cmd/openapi.go @@ -15,7 +15,15 @@ */ package main -import "github.com/spf13/cobra" +import ( + "fmt" + "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/pkg/openapi" + "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/pkg/parser" + "github.com/openshift/api/openapi/generated_openapi" + "github.com/spf13/cobra" + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/validation/spec" +) var openApi = &cobra.Command{ Use: "open-api [targetDirectory]", @@ -28,5 +36,44 @@ func init() { } var openApiRun = func(cobraCmd *cobra.Command, args []string) { + var targetDirectory string + if len(args) > 0 { + targetDirectory = args[0] + } else { + targetDirectory = "." + } + openApiGenerator := openapi.NewGenerator(targetDirectory, "openshift-generated") + openApiGenerator.PutPackageMapping("github.com/openshift/api", "openshift.io") + openShiftModule := parser.NewModule("github.com/openshift/api") + ///////////////////////////////////////////////////////////////////////////////// + // Ported from github.com/openshift/api/openapi/cmd/models-schema/main.go + refFunc := func(name string) spec.Ref { + return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", openApiGenerator.FriendlyName(name))) + } + defs := generated_openapi.GetOpenAPIDefinitions(refFunc) + for k, v := range defs { + // Marc: Use gengo to complete information for the definition + fabric8Info := openShiftModule.ExtractInfo(k) + if v.Schema.ExtraProps == nil { + v.Schema.ExtraProps = make(map[string]interface{}) + } + v.Schema.ExtraProps["x-fabric8-info"] = fabric8Info + // Replace top-level schema with v2 if a v2 schema is embedded + // so that the output of this program is always in OpenAPI v2. + // This is done by looking up an extension that marks the embedded v2 + // schema, and, if the v2 schema is found, make it the resulting schema for + // the type. + if schema, ok := v.Schema.Extensions[common.ExtensionV2Schema]; ok { + if v2Schema, isOpenAPISchema := schema.(spec.Schema); isOpenAPISchema { + openApiGenerator.PutDefinition(openApiGenerator.FriendlyName(k), v2Schema) + continue + } + } + openApiGenerator.PutDefinition(openApiGenerator.FriendlyName(k), v.Schema) + } + + if err := openApiGenerator.WriteDefinitions(); err != nil { + panic(fmt.Errorf("error writing OpenAPI schema: %w", err)) + } } diff --git a/kubernetes-model-generator/openapi/generator/go.mod b/kubernetes-model-generator/openapi/generator/go.mod index 78d3ff272f1..e8e58189643 100644 --- a/kubernetes-model-generator/openapi/generator/go.mod +++ b/kubernetes-model-generator/openapi/generator/go.mod @@ -17,10 +17,17 @@ require ( sigs.k8s.io/kustomize/api v0.17.2 ) +require ( + github.com/spf13/cobra v1.8.1 + k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 + k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 +) + require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect @@ -37,18 +44,19 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - golang.org/x/net v0.24.0 // indirect - golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/protobuf v1.33.0 // indirect + golang.org/x/mod v0.20.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/tools v0.24.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.120.1 // indirect - k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect - k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect sigs.k8s.io/kustomize/kyaml v0.17.1 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect diff --git a/kubernetes-model-generator/openapi/generator/go.sum b/kubernetes-model-generator/openapi/generator/go.sum index 192e78bf81e..dbc73a08978 100644 --- a/kubernetes-model-generator/openapi/generator/go.sum +++ b/kubernetes-model-generator/openapi/generator/go.sum @@ -9,8 +9,8 @@ github.com/getkin/kin-openapi v0.125.0 h1:jyQCyf2qXS1qvs2U00xQzkGCqYPhEhZDmSmVt6 github.com/getkin/kin-openapi v0.125.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= @@ -85,34 +85,40 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= -golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= -golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -132,14 +138,16 @@ k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 h1:Q8Z7VlGhcJgBHJHYugJ/K/7iB8a2eSxCyxdVjJp+lLY= -k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4= +k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38 h1:1dWzkmJrrprYvjGwh9kEUxmcUV/CtNU8QM7h1FLWQOo= +k8s.io/kube-openapi v0.0.0-20240903163716-9e1beecbcb38/go.mod h1:coRQXBK9NxO98XUv3ZD6AK3xzHCxV6+b7lrquKwaKzA= k8s.io/metrics v0.30.2 h1:zj4kIPTCfEbY0RHEogpA7QtlItU7xaO11+Gz1zVDxlc= k8s.io/metrics v0.30.2/go.mod h1:GpoO5XTy/g8CclVLtgA5WTrr2Cy5vCsqr5Xa/0ETWIk= -k8s.io/utils v0.0.0-20240423183400-0849a56e8f22 h1:ao5hUqGhsqdm+bYbjH/pRkCs0unBGe9UyDahzs9zQzQ= -k8s.io/utils v0.0.0-20240423183400-0849a56e8f22/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/gateway-api v1.1.0 h1:DsLDXCi6jR+Xz8/xd0Z1PYl2Pn0TyaFMOPPZIj4inDM= sigs.k8s.io/gateway-api v1.1.0/go.mod h1:ZH4lHrL2sDi0FHZ9jjneb8kKnGzFWyrTya35sWUTrRs= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= diff --git a/kubernetes-model-generator/openapi/generator/pkg/openapi/openapi.go b/kubernetes-model-generator/openapi/generator/pkg/openapi/openapi.go new file mode 100644 index 00000000000..50cbd47b878 --- /dev/null +++ b/kubernetes-model-generator/openapi/generator/pkg/openapi/openapi.go @@ -0,0 +1,94 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package openapi + +import ( + "encoding/json" + "fmt" + "k8s.io/kube-openapi/pkg/validation/spec" + "os" + "path/filepath" + "strings" +) + +type Generator struct { + name string + targetDirectory string + definitions map[string]spec.Schema + packageMappings map[string]string +} + +func NewGenerator(targetDirectory string, name string) *Generator { + return &Generator{ + name: name, + targetDirectory: targetDirectory, + definitions: make(map[string]spec.Schema), + packageMappings: make(map[string]string), + } +} + +func (g *Generator) PutDefinition(name string, schema spec.Schema) { + g.definitions[name] = schema +} + +func (g *Generator) PutPackageMapping(name, target string) { + g.packageMappings[name] = target +} + +func (g *Generator) WriteDefinitions() error { + data, err := json.MarshalIndent(&spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Definitions: g.definitions, + Info: &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "Kubernetes", + Version: "0.0.0", + }, + }, + Swagger: "2.0", + }, + }, "", " ") + if err != nil { + return fmt.Errorf("error serializing OpenAPI schema: %w", err) + } + err = os.WriteFile(filepath.Join(g.targetDirectory, g.name+".json"), data, 0644) + if err != nil { + return fmt.Errorf("error writing OpenAPI schema: %w", err) + } + return nil +} + +// FriendlyName returns an OpenAPI friendly name for the given name. +// From vendor/k8s.io/apiserver/pkg/endpoints/openapi/openapi.go +// https://github.com/kubernetes/apiserver/blob/60d1ca672541e1b30b558e32e53cad7c172345a6/pkg/endpoints/openapi/openapi.go#L136-L147 +func (g *Generator) FriendlyName(name string) string { + for k, v := range g.packageMappings { + if strings.HasPrefix(name, k) { + name = strings.Replace(name, k, v, 1) + break + } + } + nameParts := strings.Split(name, "/") + // Reverse first part. e.g., io.k8s... instead of k8s.io... + if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { + parts := strings.Split(nameParts[0], ".") + for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { + parts[i], parts[j] = parts[j], parts[i] + } + nameParts[0] = strings.Join(parts, ".") + } + return strings.Join(nameParts, ".") +} diff --git a/kubernetes-model-generator/openapi/generator/pkg/parser/parser.go b/kubernetes-model-generator/openapi/generator/pkg/parser/parser.go new file mode 100644 index 00000000000..e445466251f --- /dev/null +++ b/kubernetes-model-generator/openapi/generator/pkg/parser/parser.go @@ -0,0 +1,138 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package parser + +import ( + "fmt" + "k8s.io/gengo/v2/parser" + "k8s.io/gengo/v2/types" + "strings" +) + +const genClient = "+genclient" +const genClientPrefix = genClient + ":" +const groupNamePrefix = "+groupName=" + +var listType = types.ParseFullyQualifiedName("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta") + +type Module struct { + Name string + parser *parser.Parser + universe *types.Universe +} + +type Fabric8Info struct { + // https://github.com/kubernetes/community/blob/495011674de058660011593e6c6c842c83a1fd24/contributors/devel/sig-architecture/api-conventions.md#types-kinds + Type string + Group string + Version string + Kind string + Scope string +} + +func NewModule(name string) *Module { + p := parser.New() + err := p.LoadPackages(name) + if err != nil { + panic(fmt.Sprintf("error loading packages: %v", err)) + } + universe, err := p.NewUniverse() + if err != nil { + panic(fmt.Sprintf("error creating universe: %v", err)) + } + return &Module{ + Name: name, + parser: p, + universe: &universe, + } +} + +func (oam *Module) ExtractInfo(definitionName string) *Fabric8Info { + pkg := oam.resolvePackage(definitionName) + typ := oam.universe.Type(types.ParseFullyQualifiedName(definitionName)) + fabric8Info := &Fabric8Info{} + fabric8Info.Type = resolveType(typ) + fabric8Info.Group = groupName(pkg) + fabric8Info.Version = pkg.Name + fabric8Info.Kind = typ.Name.Name + fabric8Info.Scope = scope(typ) + return fabric8Info +} + +func (oam *Module) ApiName(definitionName string) string { + if strings.Index(definitionName, oam.Name) != 0 { + return definitionName + } + lastSeparator := strings.LastIndex(definitionName, ".") + typeName := definitionName[lastSeparator+1:] + pkg := oam.resolvePackage(definitionName) + groupName := groupName(pkg) + groupParts := strings.Split(groupName, ".") + for i, j := 0, len(groupParts)-1; i < j; i, j = i+1, j-1 { + groupParts[i], groupParts[j] = groupParts[j], groupParts[i] + } + return strings.Join(groupParts, ".") + "." + typeName +} + +func (oam *Module) resolvePackage(definitionName string) *types.Package { + lastSeparator := strings.LastIndex(definitionName, ".") + packageName := definitionName[:lastSeparator] + _, err := oam.parser.LoadPackagesTo(oam.universe, packageName) + if err != nil { + panic(fmt.Sprintf("error loading packages: %v", err)) + } + pkg := oam.universe.Package(packageName) + if pkg == nil { + panic(fmt.Sprintf("package %s not found", packageName)) + } + return pkg +} + +func groupName(pkg *types.Package) string { + for _, c := range pkg.Comments { + if strings.HasPrefix(c, groupNamePrefix) { + return strings.TrimPrefix(c, groupNamePrefix) + } + } + return "" +} + +func resolveType(typ *types.Type) string { + // Check if the type is a top-level type (Object) + for _, c := range append(typ.CommentLines, typ.SecondClosestCommentLines...) { + if strings.TrimSpace(c) == genClient { + return "object" + } + } + // Check if the type is a list + for _, m := range typ.Members { + if m.Type.Name == listType { + return "list" + } + } + return "nested" +} + +func scope(typ *types.Type) string { + scope := "Namespaced" + for _, c := range append(typ.CommentLines, typ.SecondClosestCommentLines...) { + if strings.Contains(c, genClientPrefix+"nonNamespaced") { + scope = "Clustered" + break + } + } + return scope +} diff --git a/kubernetes-model-generator/openapi/maven-plugin/src/main/java/io/fabric8/kubernetes/schema/generator/GeneratorSettings.java b/kubernetes-model-generator/openapi/maven-plugin/src/main/java/io/fabric8/kubernetes/schema/generator/GeneratorSettings.java index 4eb59e05d91..24206399d62 100644 --- a/kubernetes-model-generator/openapi/maven-plugin/src/main/java/io/fabric8/kubernetes/schema/generator/GeneratorSettings.java +++ b/kubernetes-model-generator/openapi/maven-plugin/src/main/java/io/fabric8/kubernetes/schema/generator/GeneratorSettings.java @@ -37,6 +37,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -213,6 +214,23 @@ private static Map computeApiVersions(OpenAPI openAPI) { } } } + // TODO: use new x-fabric8-info + openAPI.getComponents().getSchemas().entrySet().stream() + .filter(e -> e.getValue().getExtensions() != null) + .filter(e -> e.getValue().getExtensions().containsKey("x-fabric8-info")) + .forEach(e -> { + final Map fabric8Info = (Map) e.getValue().getExtensions().get("x-fabric8-info"); + // Consider only Kubernetes object and list types (top-level resources) + if (!Objects.equals(fabric8Info.get("Type"), "nested")) { + apiVersions.putIfAbsent(e.getKey(), + ApiVersion.builder() + .group(fabric8Info.get("Group").toString()) + .version(fabric8Info.get("Version").toString()) + .namespaced(Objects.equals(fabric8Info.get("Scope").toString(), "Namespaced")) + .build() + ); + } + }); // api machinery + core components always tagged as core v1 // TODO: see if we want to omit the addition of group and version altogether in the future openAPI.getComponents().getSchemas().entrySet().stream()