mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-13 13:55:41 +00:00
Merge pull request #118879 from atiratree/fix-openapi-apiservices
make apiservices.apiregistration.k8s.io discoverable in openapi/v3
This commit is contained in:
commit
40956a7977
15
api/discovery/apis__apiregistration.k8s.io.json
Normal file
15
api/discovery/apis__apiregistration.k8s.io.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"kind": "APIGroup",
|
||||
"name": "apiregistration.k8s.io",
|
||||
"preferredVersion": {
|
||||
"groupVersion": "apiregistration.k8s.io/v1",
|
||||
"version": "v1"
|
||||
},
|
||||
"versions": [
|
||||
{
|
||||
"groupVersion": "apiregistration.k8s.io/v1",
|
||||
"version": "v1"
|
||||
}
|
||||
]
|
||||
}
|
38
api/discovery/apis__apiregistration.k8s.io__v1.json
Normal file
38
api/discovery/apis__apiregistration.k8s.io__v1.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"apiVersion": "v1",
|
||||
"groupVersion": "apiregistration.k8s.io/v1",
|
||||
"kind": "APIResourceList",
|
||||
"resources": [
|
||||
{
|
||||
"categories": [
|
||||
"api-extensions"
|
||||
],
|
||||
"kind": "APIService",
|
||||
"name": "apiservices",
|
||||
"namespaced": false,
|
||||
"singularName": "apiservice",
|
||||
"storageVersionHash": "InPBPD7+PqM=",
|
||||
"verbs": [
|
||||
"create",
|
||||
"delete",
|
||||
"deletecollection",
|
||||
"get",
|
||||
"list",
|
||||
"patch",
|
||||
"update",
|
||||
"watch"
|
||||
]
|
||||
},
|
||||
{
|
||||
"kind": "APIService",
|
||||
"name": "apiservices/status",
|
||||
"namespaced": false,
|
||||
"singularName": "",
|
||||
"verbs": [
|
||||
"get",
|
||||
"patch",
|
||||
"update"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
2796
api/openapi-spec/v3/apis__apiregistration.k8s.io__v1_openapi.json
Normal file
2796
api/openapi-spec/v3/apis__apiregistration.k8s.io__v1_openapi.json
Normal file
File diff suppressed because it is too large
Load Diff
158
api/openapi-spec/v3/apis__apiregistration.k8s.io_openapi.json
Normal file
158
api/openapi-spec/v3/apis__apiregistration.k8s.io_openapi.json
Normal file
@ -0,0 +1,158 @@
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup": {
|
||||
"description": "APIGroup contains the name, the supported versions, and the preferred version of a group.",
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"default": "",
|
||||
"description": "name is the name of the group.",
|
||||
"type": "string"
|
||||
},
|
||||
"preferredVersion": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery"
|
||||
}
|
||||
],
|
||||
"default": {},
|
||||
"description": "preferredVersion is the version preferred by the API server, which probably is the storage version."
|
||||
},
|
||||
"serverAddressByClientCIDRs": {
|
||||
"description": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"versions": {
|
||||
"description": "versions are the versions supported in this group.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"versions"
|
||||
],
|
||||
"type": "object",
|
||||
"x-kubernetes-group-version-kind": [
|
||||
{
|
||||
"group": "",
|
||||
"kind": "APIGroup",
|
||||
"version": "v1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"io.k8s.apimachinery.pkg.apis.meta.v1.GroupVersionForDiscovery": {
|
||||
"description": "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.",
|
||||
"properties": {
|
||||
"groupVersion": {
|
||||
"default": "",
|
||||
"description": "groupVersion specifies the API group and version in the form \"group/version\"",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"default": "",
|
||||
"description": "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"groupVersion",
|
||||
"version"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.apimachinery.pkg.apis.meta.v1.ServerAddressByClientCIDR": {
|
||||
"description": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.",
|
||||
"properties": {
|
||||
"clientCIDR": {
|
||||
"default": "",
|
||||
"description": "The CIDR with which clients can match their IP to figure out the server address that they should use.",
|
||||
"type": "string"
|
||||
},
|
||||
"serverAddress": {
|
||||
"default": "",
|
||||
"description": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"clientCIDR",
|
||||
"serverAddress"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"BearerToken": {
|
||||
"description": "Bearer Token authentication",
|
||||
"in": "header",
|
||||
"name": "authorization",
|
||||
"type": "apiKey"
|
||||
}
|
||||
}
|
||||
},
|
||||
"info": {
|
||||
"title": "Kubernetes",
|
||||
"version": "unversioned"
|
||||
},
|
||||
"openapi": "3.0.0",
|
||||
"paths": {
|
||||
"/apis/apiregistration.k8s.io/": {
|
||||
"get": {
|
||||
"description": "get information of a group",
|
||||
"operationId": "getApiregistrationAPIGroup",
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup"
|
||||
}
|
||||
},
|
||||
"application/vnd.kubernetes.protobuf": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup"
|
||||
}
|
||||
},
|
||||
"application/yaml": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.apis.meta.v1.APIGroup"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "OK"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"apiregistration"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -440,6 +440,8 @@ func (s *APIAggregator) PrepareRun() (preparedAPIAggregator, error) {
|
||||
openAPIV3Aggregator, err := openapiv3aggregator.BuildAndRegisterAggregator(
|
||||
specDownloaderV3,
|
||||
s.GenericAPIServer.NextDelegate(),
|
||||
s.GenericAPIServer.Handler.GoRestfulContainer,
|
||||
s.openAPIConfig,
|
||||
s.GenericAPIServer.Handler.NonGoRestfulMux)
|
||||
if err != nil {
|
||||
return preparedAPIAggregator{}, err
|
||||
|
@ -25,9 +25,13 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/server/mux"
|
||||
"k8s.io/apiserver/pkg/server/routes"
|
||||
"k8s.io/klog/v2"
|
||||
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
@ -73,12 +77,29 @@ func (s *specProxier) GetAPIServiceNames() []string {
|
||||
}
|
||||
|
||||
// BuildAndRegisterAggregator registered OpenAPI aggregator handler. This function is not thread safe as it only being called on startup.
|
||||
func BuildAndRegisterAggregator(downloader Downloader, delegationTarget server.DelegationTarget, pathHandler common.PathHandlerByGroupVersion) (SpecProxier, error) {
|
||||
func BuildAndRegisterAggregator(downloader Downloader, delegationTarget server.DelegationTarget, aggregatorService *restful.Container, openAPIConfig *common.Config, pathHandler common.PathHandlerByGroupVersion) (SpecProxier, error) {
|
||||
s := &specProxier{
|
||||
apiServiceInfo: map[string]*openAPIV3APIServiceInfo{},
|
||||
downloader: downloader,
|
||||
}
|
||||
|
||||
if aggregatorService != nil && openAPIConfig != nil {
|
||||
// Make native types exposed by aggregator available to the aggregated
|
||||
// OpenAPI (normal handle is disabled by skipOpenAPIInstallation option)
|
||||
aggregatorLocalServiceName := "k8s_internal_local_kube_aggregator_types"
|
||||
v3Mux := mux.NewPathRecorderMux(aggregatorLocalServiceName)
|
||||
_ = routes.OpenAPI{
|
||||
Config: openAPIConfig,
|
||||
}.InstallV3(aggregatorService, v3Mux)
|
||||
|
||||
s.AddUpdateAPIService(v3Mux, &v1.APIService{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: aggregatorLocalServiceName,
|
||||
},
|
||||
})
|
||||
s.UpdateAPIServiceSpec(aggregatorLocalServiceName)
|
||||
}
|
||||
|
||||
i := 1
|
||||
for delegate := delegationTarget; delegate != nil; delegate = delegate.NextDelegate() {
|
||||
handler := delegate.UnprotectedHandler()
|
||||
|
@ -23,15 +23,20 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/emicklei/go-restful/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/server/mux"
|
||||
"k8s.io/component-base/metrics/legacyregistry"
|
||||
"k8s.io/component-base/metrics/testutil"
|
||||
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
|
||||
openapicommon "k8s.io/kube-openapi/pkg/common"
|
||||
"k8s.io/kube-openapi/pkg/handler3"
|
||||
kubeopenapispec "k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
type testV3APIService struct {
|
||||
@ -89,7 +94,7 @@ func TestV2APIService(t *testing.T) {
|
||||
downloader := Downloader{}
|
||||
pathHandler := mux.NewPathRecorderMux("aggregator_test")
|
||||
var serveHandler http.Handler = pathHandler
|
||||
specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), pathHandler)
|
||||
specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), nil, nil, pathHandler)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -133,7 +138,7 @@ func TestV3APIService(t *testing.T) {
|
||||
|
||||
pathHandler := mux.NewPathRecorderMux("aggregator_test")
|
||||
var serveHandler http.Handler = pathHandler
|
||||
specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), pathHandler)
|
||||
specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), nil, nil, pathHandler)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -170,6 +175,54 @@ func TestV3APIService(t *testing.T) {
|
||||
assert.ElementsMatch(t, []string{openAPIV2Converter, apiService.Name}, apiServiceNames)
|
||||
}
|
||||
|
||||
func TestV3RootAPIService(t *testing.T) {
|
||||
ws := new(restful.WebService)
|
||||
{
|
||||
ws.Path("/apis/apiregistration.k8s.io/v1")
|
||||
ws.Doc("API at/apis/apiregistration.k8s.io/v1 ")
|
||||
ws.Consumes("*/*")
|
||||
ws.Produces("application/json")
|
||||
ws.ApiVersion("apiregistration.k8s.io/v1")
|
||||
routeBuilder := ws.GET("apiservices").
|
||||
To(func(request *restful.Request, response *restful.Response) {}).
|
||||
Doc("list or watch objects of kind APIService").
|
||||
Operation("listAPIService").
|
||||
Produces("application/json").
|
||||
Returns(http.StatusOK, "OK", v1.APIService{}).
|
||||
Writes(v1.APIService{})
|
||||
ws.Route(routeBuilder)
|
||||
}
|
||||
openapiConfig := genericapiserver.DefaultOpenAPIV3Config(getTestAPIServiceOpenAPIDefinitions, openapinamer.NewDefinitionNamer(runtime.NewScheme()))
|
||||
|
||||
downloader := Downloader{}
|
||||
goRestfulContainer := restful.NewContainer()
|
||||
goRestfulContainer.Add(ws)
|
||||
pathHandler := mux.NewPathRecorderMux("aggregator_test")
|
||||
var serveHandler http.Handler = pathHandler
|
||||
specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), goRestfulContainer, openapiConfig, pathHandler)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
expectedSpecJSON := []byte(`{"openapi":"3.0.0","info":{"title":"Generic API Server"},"paths":{"/apis/apiregistration.k8s.io/v1/apiservices":{"get":{"tags":["apiregistration_v1"],"description":"list or watch objects of kind APIService","operationId":"listApiregistrationV1APIService","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService"}}}}}}}},"components":{"schemas":{"io.k8s.kube-aggregator.pkg.apis.apiregistration.v1.APIService":{"description":"APIService represents a server for a particular GroupVersion. Name must be \"version.group\".","type":"object"}}}}`)
|
||||
|
||||
data := sendReq(t, serveHandler, "/openapi/v3")
|
||||
groupVersionList := handler3.OpenAPIV3Discovery{}
|
||||
if err := json.Unmarshal(data, &groupVersionList); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path, ok := groupVersionList.Paths["apis/apiregistration.k8s.io/v1"]
|
||||
if !ok {
|
||||
t.Error("Expected apiregistration.k8s.io/v1 to be in group version list")
|
||||
}
|
||||
gotSpecJSON := sendReq(t, serveHandler, path.ServerRelativeURL)
|
||||
if bytes.Compare(gotSpecJSON, expectedSpecJSON) != 0 {
|
||||
t.Errorf("Spec mismatch, expected %s, got %s", expectedSpecJSON, gotSpecJSON)
|
||||
}
|
||||
|
||||
apiServiceNames := specProxier.GetAPIServiceNames()
|
||||
assert.ElementsMatch(t, []string{"k8s_internal_local_kube_aggregator_types", openAPIV2Converter}, apiServiceNames)
|
||||
}
|
||||
|
||||
func TestOpenAPIRequestMetrics(t *testing.T) {
|
||||
metrics.Register()
|
||||
metrics.Reset()
|
||||
@ -178,7 +231,7 @@ func TestOpenAPIRequestMetrics(t *testing.T) {
|
||||
|
||||
pathHandler := mux.NewPathRecorderMux("aggregator_metrics_test")
|
||||
var serveHandler http.Handler = pathHandler
|
||||
specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), pathHandler)
|
||||
specProxier, err := BuildAndRegisterAggregator(downloader, genericapiserver.NewEmptyDelegate(), nil, nil, pathHandler)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
@ -239,3 +292,20 @@ func sendReq(t *testing.T, handler http.Handler, path string) []byte {
|
||||
handler.ServeHTTP(writer, req)
|
||||
return writer.data
|
||||
}
|
||||
|
||||
func getTestAPIServiceOpenAPIDefinitions(_ openapicommon.ReferenceCallback) map[string]openapicommon.OpenAPIDefinition {
|
||||
return map[string]openapicommon.OpenAPIDefinition{
|
||||
"k8s.io/kube-aggregator/pkg/apis/apiregistration/v1.APIService": buildTestAPIServiceOpenAPIDefinition(),
|
||||
}
|
||||
}
|
||||
|
||||
func buildTestAPIServiceOpenAPIDefinition() openapicommon.OpenAPIDefinition {
|
||||
return openapicommon.OpenAPIDefinition{
|
||||
Schema: kubeopenapispec.Schema{
|
||||
SchemaProps: kubeopenapispec.SchemaProps{
|
||||
Description: "APIService represents a server for a particular GroupVersion. Name must be \"version.group\".",
|
||||
Type: []string{"object"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user