diff --git a/kubernetes-model-generator/openapi/generator/cmd/openapi.go b/kubernetes-model-generator/openapi/generator/cmd/openapi.go index 8817e1646c3..8ea07735cee 100644 --- a/kubernetes-model-generator/openapi/generator/cmd/openapi.go +++ b/kubernetes-model-generator/openapi/generator/cmd/openapi.go @@ -21,25 +21,7 @@ import ( "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/cmd/generated_openapi" "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/pkg/openapi" "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/pkg/openshift" - "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/pkg/parser" - "strings" - - networkattachmentdefinition "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" - openshiftbaremetaloperatorv1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" - openshiftclusterapiprovidermetal3v1beta1 "github.com/metal3-io/cluster-api-provider-metal3/api/v1beta1" - openshiftconfigv1 "github.com/openshift/api/config/v1" - openshiftcloudcredentialoperatorv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1" - openshiftclusternetworkoperatorv1 "github.com/openshift/cluster-network-operator/pkg/apis/network/v1" - openshiftclusternodetuningoperatorv1 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1" - openshifthivev1 "github.com/openshift/hive/apis/hive/v1" - openshiftinstallerv1 "github.com/openshift/installer/pkg/types" - operatorframeworkv1 "github.com/operator-framework/api/pkg/operators/v1" - operatorframeworkv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - olm "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" - prometheusoperatorv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "github.com/spf13/cobra" - "k8s.io/kube-openapi/pkg/common" - "k8s.io/kube-openapi/pkg/validation/spec" "time" ) @@ -55,57 +37,19 @@ func init() { var openApiRun = func(cobraCmd *cobra.Command, args []string) { startTime := time.Now() - fmt.Printf("OpenAPI JSON schema generation started...\n%s\n", strings.Join([]string{ - // Force imports so that modules are present in go.mod - networkattachmentdefinition.SchemeGroupVersion.String(), - olm.SchemeGroupVersion.String(), - openshiftbaremetaloperatorv1alpha1.GroupVersion.String(), - openshiftconfigv1.SchemeGroupVersion.String(), - openshiftcloudcredentialoperatorv1.GroupVersion.String(), - openshiftclusterapiprovidermetal3v1beta1.GroupVersion.String(), - openshiftclusternetworkoperatorv1.GroupVersion.String(), - openshiftclusternodetuningoperatorv1.SchemeGroupVersion.String(), - openshifthivev1.SchemeGroupVersion.String(), - "install.openshift.io/" + openshiftinstallerv1.InstallConfigVersion + " (" + strings.Join(openshiftinstallerv1.PlatformNames, ", ") + ")", - operatorframeworkv1alpha1.SchemeGroupVersion.String(), - operatorframeworkv1.GroupVersion.String(), - prometheusoperatorv1.SchemeGroupVersion.String(), - }, "\n")) + fmt.Println("OpenAPI JSON schema generation started...") var targetDirectory string if len(args) > 0 { targetDirectory = args[0] } else { targetDirectory = "." } - openApiGenerator := openapi.NewGenerator(targetDirectory, "openshift-generated") - openShiftModule := parser.NewModule(openshift.PackagePatterns...) - ///////////////////////////////////////////////////////////////////////////////// - // 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", openShiftModule.ApiName(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(openShiftModule.ApiName(k), v2Schema) - continue - } - } - openApiGenerator.PutDefinition(openShiftModule.ApiName(k), v.Schema) - } + openApiGenerator := openapi.NewGenerator( + targetDirectory, + "openshift-generated", + generated_openapi.GetOpenAPIDefinitions, + openshift.PackagePatterns..., + ) if err := openApiGenerator.WriteDefinitions(); err != nil { panic(fmt.Errorf("error writing OpenAPI schema: %w", err)) } diff --git a/kubernetes-model-generator/openapi/generator/cmd/supported-apis.go b/kubernetes-model-generator/openapi/generator/cmd/supported-apis.go new file mode 100644 index 00000000000..fb9cc1753a9 --- /dev/null +++ b/kubernetes-model-generator/openapi/generator/cmd/supported-apis.go @@ -0,0 +1,64 @@ +/** + * 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 main + +import ( + "fmt" + networkattachmentdefinition "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" + openshiftbaremetaloperatorv1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" + openshiftclusterapiprovidermetal3v1beta1 "github.com/metal3-io/cluster-api-provider-metal3/api/v1beta1" + openshiftconfigv1 "github.com/openshift/api/config/v1" + openshiftcloudcredentialoperatorv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1" + openshiftclusternetworkoperatorv1 "github.com/openshift/cluster-network-operator/pkg/apis/network/v1" + openshiftclusternodetuningoperatorv1 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1" + openshifthivev1 "github.com/openshift/hive/apis/hive/v1" + openshiftinstallerv1 "github.com/openshift/installer/pkg/types" + operatorframeworkv1 "github.com/operator-framework/api/pkg/operators/v1" + operatorframeworkv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" + olm "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" + prometheusoperatorv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" + "github.com/spf13/cobra" + "strings" +) + +var supportedApis = &cobra.Command{ + Use: "supported-apis", + Short: "Lists the APIs supported by this generator", + Run: supportedApisRun, +} + +func init() { + rootCmd.AddCommand(supportedApis) +} + +// Forces imports so that modules are present in go.mod +var supportedApisRun = func(cobraCmd *cobra.Command, args []string) { + fmt.Printf("This generator generates OpenAPI schemas for the following supported APIs:\n%s\n", strings.Join([]string{ + networkattachmentdefinition.SchemeGroupVersion.String(), + olm.SchemeGroupVersion.String(), + openshiftbaremetaloperatorv1alpha1.GroupVersion.String(), + openshiftconfigv1.SchemeGroupVersion.String(), + openshiftcloudcredentialoperatorv1.GroupVersion.String(), + openshiftclusterapiprovidermetal3v1beta1.GroupVersion.String(), + openshiftclusternetworkoperatorv1.GroupVersion.String(), + openshiftclusternodetuningoperatorv1.SchemeGroupVersion.String(), + openshifthivev1.SchemeGroupVersion.String(), + "install.openshift.io/" + openshiftinstallerv1.InstallConfigVersion + " (" + strings.Join(openshiftinstallerv1.PlatformNames, ", ") + ")", + operatorframeworkv1alpha1.SchemeGroupVersion.String(), + operatorframeworkv1.GroupVersion.String(), + prometheusoperatorv1.SchemeGroupVersion.String(), + }, "\n")) +} diff --git a/kubernetes-model-generator/openapi/generator/pkg/openapi/openapi-json.go b/kubernetes-model-generator/openapi/generator/pkg/openapi/openapi-json.go index 46caeb78caf..d2a13d7f7b8 100644 --- a/kubernetes-model-generator/openapi/generator/pkg/openapi/openapi-json.go +++ b/kubernetes-model-generator/openapi/generator/pkg/openapi/openapi-json.go @@ -20,31 +20,73 @@ package openapi import ( "encoding/json" "fmt" + "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/pkg/parser" + "k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/validation/spec" "os" "path/filepath" - "strings" ) -type JsonGenerator struct { +type JsonGenerator interface { + WriteDefinitions() error +} + +type jsonGenerator struct { name string targetDirectory string definitions map[string]spec.Schema + parserModule *parser.Module } -func NewGenerator(targetDirectory string, name string) *JsonGenerator { - return &JsonGenerator{ +func NewGenerator( + targetDirectory string, + name string, + getDefinitionsFunc func(callback common.ReferenceCallback) map[string]common.OpenAPIDefinition, + patterns ...string, +) JsonGenerator { + g := &jsonGenerator{ name: name, targetDirectory: targetDirectory, definitions: make(map[string]spec.Schema), + parserModule: parser.NewModule(patterns...), + } + defs := getDefinitionsFunc(g.refFunc) + // Ported and adapted from github.com/openshift/api/openapi/cmd/models-schema/main.go + // https://github.com/openshift/api/blob/b1f700bdd8d22c4033be4e4e9ef751d89ade42e8/openapi/cmd/models-schema/main.go#L30-L44 + for k, v := range defs { + g.addFabric8Info(k, &v) + // 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 { + g.definitions[g.parserModule.ApiName(k)] = v2Schema + continue + } + } + g.definitions[g.parserModule.ApiName(k)] = v.Schema } + return g } -func (g *JsonGenerator) PutDefinition(name string, schema spec.Schema) { - g.definitions[name] = schema +// Ported and adapted from github.com/openshift/api/openapi/cmd/models-schema/main.go +// https://github.com/openshift/api/blob/b1f700bdd8d22c4033be4e4e9ef751d89ade42e8/openapi/cmd/models-schema/main.go#L25-L27 +func (g *jsonGenerator) refFunc(name string) spec.Ref { + return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", g.parserModule.ApiName(name))) } -func (g *JsonGenerator) WriteDefinitions() error { +func (g *jsonGenerator) addFabric8Info(k string, v *common.OpenAPIDefinition) { + // Marc: Use gengo to complete information for the definition + fabric8Info := g.parserModule.ExtractInfo(k) + if v.Schema.ExtraProps == nil { + v.Schema.ExtraProps = make(map[string]interface{}) + } + v.Schema.ExtraProps["x-fabric8-info"] = fabric8Info +} + +func (g *jsonGenerator) WriteDefinitions() error { data, err := json.MarshalIndent(&spec.Swagger{ SwaggerProps: spec.SwaggerProps{ Definitions: g.definitions, @@ -66,19 +108,3 @@ func (g *JsonGenerator) WriteDefinitions() error { } 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 FriendlyName(name string) string { - 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 index 97c1dd1ab82..8c13d883fdb 100644 --- a/kubernetes-model-generator/openapi/generator/pkg/parser/parser.go +++ b/kubernetes-model-generator/openapi/generator/pkg/parser/parser.go @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +// Package parser provides functionality to extract information from Go types and packages using gengo. package parser import ( "fmt" "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/pkg/kubernetes" - "github.com/fabric8io/kubernetes-client/kubernetes-model-generator/openapi/generator/pkg/openapi" "k8s.io/gengo/v2/parser" "k8s.io/gengo/v2/types" "strings" @@ -73,17 +74,22 @@ func (oam *Module) ExtractInfo(definitionName string) *Fabric8Info { return fabric8Info } +// ApiName returns the completed definition name for the OpenAPI component +// The standard definition name is usually based on the module name + package name + type name. +// However, Kubernetes comment tags include augmented information such as the groupName and versionName. +// This method attempts to resolve the additional information from the gengo processed/parsed package and +// type information. func (oam *Module) ApiName(definitionName string) string { // Don't treat k8s.io types, json is expected to contain the full Go definition name instead of the group/version if strings.HasPrefix(definitionName, "k8s.io/") { - return openapi.FriendlyName(definitionName) + return FriendlyName(definitionName) } lastSeparator := strings.LastIndex(definitionName, ".") typeName := definitionName[lastSeparator+1:] pkg := oam.resolvePackage(definitionName) gn := groupName(pkg) if gn == "" { - return openapi.FriendlyName(definitionName) + return FriendlyName(definitionName) } groupParts := strings.Split(gn, ".") for i, j := 0, len(groupParts)-1; i < j; i, j = i+1, j-1 { @@ -171,3 +177,19 @@ func scope(typ *types.Type) string { } return scope } + +// 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 FriendlyName(name string) string { + 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/tools/generator/openapi.go b/kubernetes-model-generator/openapi/generator/tools/generator/openapi.go index ff048f3930b..6dae4483914 100644 --- a/kubernetes-model-generator/openapi/generator/tools/generator/openapi.go +++ b/kubernetes-model-generator/openapi/generator/tools/generator/openapi.go @@ -22,43 +22,13 @@ 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/openshift" - "strings" - - networkattachmentdefinition "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1" - openshiftbaremetaloperatorv1alpha1 "github.com/metal3-io/baremetal-operator/apis/metal3.io/v1alpha1" - openshiftclusterapiprovidermetal3v1beta1 "github.com/metal3-io/cluster-api-provider-metal3/api/v1beta1" - openshiftconfigv1 "github.com/openshift/api/config/v1" - openshiftcloudcredentialoperatorv1 "github.com/openshift/cloud-credential-operator/pkg/apis/cloudcredential/v1" - openshiftclusternetworkoperatorv1 "github.com/openshift/cluster-network-operator/pkg/apis/network/v1" - openshiftclusternodetuningoperatorv1 "github.com/openshift/cluster-node-tuning-operator/pkg/apis/tuned/v1" - openshifthivev1 "github.com/openshift/hive/apis/hive/v1" - openshiftinstallerv1 "github.com/openshift/installer/pkg/types" - operatorframeworkv1 "github.com/operator-framework/api/pkg/operators/v1" - operatorframeworkv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" - olm "github.com/operator-framework/operator-lifecycle-manager/pkg/package-server/apis/operators/v1" - prometheusoperatorv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" "k8s.io/kube-openapi/cmd/openapi-gen/args" "time" ) func main() { startTime := time.Now() - fmt.Printf("OpenAPI code generation started...\n%s\n", strings.Join([]string{ - // Force imports so that modules are present in go.mod - networkattachmentdefinition.SchemeGroupVersion.String(), - olm.SchemeGroupVersion.String(), - openshiftbaremetaloperatorv1alpha1.GroupVersion.String(), - openshiftconfigv1.SchemeGroupVersion.String(), - openshiftcloudcredentialoperatorv1.GroupVersion.String(), - openshiftclusterapiprovidermetal3v1beta1.GroupVersion.String(), - openshiftclusternetworkoperatorv1.GroupVersion.String(), - openshiftclusternodetuningoperatorv1.SchemeGroupVersion.String(), - openshifthivev1.SchemeGroupVersion.String(), - "install.openshift.io/" + openshiftinstallerv1.InstallConfigVersion + " (" + strings.Join(openshiftinstallerv1.PlatformNames, ", ") + ")", - operatorframeworkv1alpha1.SchemeGroupVersion.String(), - operatorframeworkv1.GroupVersion.String(), - prometheusoperatorv1.SchemeGroupVersion.String(), - }, "\n")) + fmt.Println("OpenAPI code generation started...") err := (&openapi.GoGenerator{ Args: args.Args{ OutputFile: "zz_generated.openapi.go", diff --git a/kubernetes-model-generator/openshift-model-storageversionmigrator/pom.xml b/kubernetes-model-generator/openshift-model-storageversionmigrator/pom.xml index 69ef1511cde..75cd4298966 100644 --- a/kubernetes-model-generator/openshift-model-storageversionmigrator/pom.xml +++ b/kubernetes-model-generator/openshift-model-storageversionmigrator/pom.xml @@ -56,8 +56,8 @@ - https://raw.githubusercontent.com/kubernetes-sigs/kube-storage-version-migrator/5c8923c5ff96ceb4435f66b986b5aec2dd0cbc22/manifests/storage_migration_crd.yaml - https://raw.githubusercontent.com/kubernetes-sigs/kube-storage-version-migrator/5c8923c5ff96ceb4435f66b986b5aec2dd0cbc22/manifests/storage_state_crd.yaml + https://raw.githubusercontent.com/kubernetes-sigs/kube-storage-version-migrator/${openapi.storageversionmigrator-revision}/manifests/storage_migration_crd.yaml + https://raw.githubusercontent.com/kubernetes-sigs/kube-storage-version-migrator/${openapi.storageversionmigrator-revision}/manifests/storage_state_crd.yaml io.fabric8.openshift.api.model.storageversionmigrator diff --git a/kubernetes-model-generator/openshift-model-whereabouts/pom.xml b/kubernetes-model-generator/openshift-model-whereabouts/pom.xml index 924aa0989a4..d591655233d 100644 --- a/kubernetes-model-generator/openshift-model-whereabouts/pom.xml +++ b/kubernetes-model-generator/openshift-model-whereabouts/pom.xml @@ -56,9 +56,9 @@ - https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/5bb9e1542cf987458db181bedee7c051a1538dc1/doc/crds/whereabouts.cni.cncf.io_ippools.yaml - https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/5bb9e1542cf987458db181bedee7c051a1538dc1/doc/crds/whereabouts.cni.cncf.io_nodeslicepools.yaml - https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/5bb9e1542cf987458db181bedee7c051a1538dc1/doc/crds/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml + https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/${openapi.whereabouts-revision}/doc/crds/whereabouts.cni.cncf.io_ippools.yaml + https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/${openapi.whereabouts-revision}/doc/crds/whereabouts.cni.cncf.io_nodeslicepools.yaml + https://raw.githubusercontent.com/k8snetworkplumbingwg/whereabouts/${openapi.whereabouts-revision}/doc/crds/whereabouts.cni.cncf.io_overlappingrangeipreservations.yaml io.fabric8.openshift.api.model.whereabouts diff --git a/kubernetes-model-generator/pom.xml b/kubernetes-model-generator/pom.xml index e3fab268511..716c8518905 100644 --- a/kubernetes-model-generator/pom.xml +++ b/kubernetes-model-generator/pom.xml @@ -79,9 +79,10 @@ false **/*KubernetesTest.java ${project.parent.basedir}/openapi/schemas/kubernetes-1.30.0.json - ${project.parent.basedir}/openapi/schemas/openshift-generated.json 4.17 + 5c8923c5ff96ceb4435f66b986b5aec2dd0cbc22 + 5bb9e1542cf987458db181bedee7c051a1538dc1 osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"