mirror of
https://github.com/kubernetes/client-go.git
synced 2025-06-25 14:41:53 +00:00
Merge pull request #108992 from alexzielenski/cache-busting-client-go
client-go: OpenAPI v3 support Kubernetes-commit: 656dc213ce43f1ecfa7f54eb1f01864468f8f0e2
This commit is contained in:
commit
11ca265357
@ -33,6 +33,8 @@ import (
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/openapi"
|
||||
cachedopenapi "k8s.io/client-go/openapi/cached"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
@ -56,6 +58,9 @@ type CachedDiscoveryClient struct {
|
||||
invalidated bool
|
||||
// fresh is true if all used cache files were ours
|
||||
fresh bool
|
||||
|
||||
// caching openapi v3 client which wraps the delegate's client
|
||||
openapiClient openapi.Client
|
||||
}
|
||||
|
||||
var _ discovery.CachedDiscoveryInterface = &CachedDiscoveryClient{}
|
||||
@ -233,6 +238,21 @@ func (d *CachedDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return d.delegate.OpenAPISchema()
|
||||
}
|
||||
|
||||
// OpenAPIV3 retrieves and parses the OpenAPIV3 specs exposed by the server
|
||||
func (d *CachedDiscoveryClient) OpenAPIV3() openapi.Client {
|
||||
// Must take lock since Invalidate call may modify openapiClient
|
||||
d.mutex.Lock()
|
||||
defer d.mutex.Unlock()
|
||||
|
||||
if d.openapiClient == nil {
|
||||
// Delegate is discovery client created with special HTTP client which
|
||||
// respects E-Tag cache responses to serve cache from disk.
|
||||
d.openapiClient = cachedopenapi.NewClient(d.delegate.OpenAPIV3())
|
||||
}
|
||||
|
||||
return d.openapiClient
|
||||
}
|
||||
|
||||
// Fresh is supposed to tell the caller whether or not to retry if the cache
|
||||
// fails to find something (false = retry, true = no need to retry).
|
||||
func (d *CachedDiscoveryClient) Fresh() bool {
|
||||
@ -250,6 +270,7 @@ func (d *CachedDiscoveryClient) Invalidate() {
|
||||
d.ourFiles = map[string]struct{}{}
|
||||
d.fresh = true
|
||||
d.invalidated = true
|
||||
d.openapiClient = nil
|
||||
}
|
||||
|
||||
// NewCachedDiscoveryClientForConfig creates a new DiscoveryClient for the given config, and wraps
|
||||
|
@ -20,19 +20,24 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
openapi_v2 "github.com/google/gnostic/openapiv2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/openapi"
|
||||
"k8s.io/client-go/rest"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
testutil "k8s.io/client-go/util/testing"
|
||||
)
|
||||
|
||||
func TestCachedDiscoveryClient_Fresh(t *testing.T) {
|
||||
@ -123,6 +128,83 @@ func TestNewCachedDiscoveryClient_PathPerm(t *testing.T) {
|
||||
assert.NoError(err)
|
||||
}
|
||||
|
||||
// Tests that schema instances returned by openapi cached and returned after
|
||||
// successive calls
|
||||
func TestOpenAPIDiskCache(t *testing.T) {
|
||||
// Create discovery cache dir (unused)
|
||||
discoCache, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
os.RemoveAll(discoCache)
|
||||
defer os.RemoveAll(discoCache)
|
||||
|
||||
// Create http cache dir
|
||||
httpCache, err := ioutil.TempDir("", "")
|
||||
require.NoError(t, err)
|
||||
os.RemoveAll(httpCache)
|
||||
defer os.RemoveAll(httpCache)
|
||||
|
||||
// Start test OpenAPI server
|
||||
fakeServer, err := testutil.NewFakeOpenAPIV3Server("../../testdata")
|
||||
require.NoError(t, err)
|
||||
defer fakeServer.HttpServer.Close()
|
||||
|
||||
require.Greater(t, len(fakeServer.ServedDocuments), 0)
|
||||
|
||||
client, err := NewCachedDiscoveryClientForConfig(
|
||||
&rest.Config{Host: fakeServer.HttpServer.URL},
|
||||
discoCache,
|
||||
httpCache,
|
||||
1*time.Nanosecond,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
openapiClient := client.OpenAPIV3()
|
||||
|
||||
// Ensure initial Paths call hits server
|
||||
_, err = openapiClient.Paths()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, fakeServer.RequestCounters["/openapi/v3"])
|
||||
|
||||
// Ensure Paths call does hits server again
|
||||
// This is expected since openapiClient is the same instance, so Paths()
|
||||
// should be cached in memory.
|
||||
paths, err := openapiClient.Paths()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, fakeServer.RequestCounters["/openapi/v3"])
|
||||
|
||||
require.Greater(t, len(paths), 0)
|
||||
i := 0
|
||||
for k, v := range paths {
|
||||
i++
|
||||
|
||||
_, err = v.Schema()
|
||||
assert.NoError(t, err)
|
||||
|
||||
path := "/openapi/v3/" + strings.TrimPrefix(k, "/")
|
||||
assert.Equal(t, 1, fakeServer.RequestCounters[path])
|
||||
|
||||
// Ensure schema call is served from memory
|
||||
_, err = v.Schema()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, fakeServer.RequestCounters[path])
|
||||
|
||||
client.Invalidate()
|
||||
|
||||
// Refetch the schema from a new openapi client to try to force a new
|
||||
// http request
|
||||
newPaths, err := client.OpenAPIV3().Paths()
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure schema call is still served from disk
|
||||
_, err = newPaths[k].Schema()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1+i, fakeServer.RequestCounters["/openapi/v3"])
|
||||
assert.Equal(t, 1, fakeServer.RequestCounters[path])
|
||||
}
|
||||
}
|
||||
|
||||
type fakeDiscoveryClient struct {
|
||||
groupCalls int
|
||||
resourceCalls int
|
||||
@ -207,3 +289,7 @@ func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
c.openAPICalls = c.openAPICalls + 1
|
||||
return &openapi_v2.Document{}, nil
|
||||
}
|
||||
|
||||
func (d *fakeDiscoveryClient) OpenAPIV3() openapi.Client {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
@ -29,6 +29,8 @@ import (
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/openapi"
|
||||
cachedopenapi "k8s.io/client-go/openapi/cached"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
@ -49,6 +51,7 @@ type memCacheClient struct {
|
||||
groupToServerResources map[string]*cacheEntry
|
||||
groupList *metav1.APIGroupList
|
||||
cacheValid bool
|
||||
openapiClient openapi.Client
|
||||
}
|
||||
|
||||
// Error Constants
|
||||
@ -143,6 +146,18 @@ func (d *memCacheClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return d.delegate.OpenAPISchema()
|
||||
}
|
||||
|
||||
func (d *memCacheClient) OpenAPIV3() openapi.Client {
|
||||
// Must take lock since Invalidate call may modify openapiClient
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
if d.openapiClient == nil {
|
||||
d.openapiClient = cachedopenapi.NewClient(d.delegate.OpenAPIV3())
|
||||
}
|
||||
|
||||
return d.openapiClient
|
||||
}
|
||||
|
||||
func (d *memCacheClient) Fresh() bool {
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
@ -160,6 +175,7 @@ func (d *memCacheClient) Invalidate() {
|
||||
d.cacheValid = false
|
||||
d.groupToServerResources = nil
|
||||
d.groupList = nil
|
||||
d.openapiClient = nil
|
||||
}
|
||||
|
||||
// refreshLocked refreshes the state of cache. The caller must hold d.lock for
|
||||
|
@ -23,9 +23,14 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
errorsutil "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/discovery/fake"
|
||||
"k8s.io/client-go/rest"
|
||||
testutil "k8s.io/client-go/util/testing"
|
||||
)
|
||||
|
||||
type resourceMapEntry struct {
|
||||
@ -390,3 +395,60 @@ func TestPartialRetryableFailure(t *testing.T) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that schema instances returned by openapi cached and returned after
|
||||
// successive calls
|
||||
func TestOpenAPIMemCache(t *testing.T) {
|
||||
fakeServer, err := testutil.NewFakeOpenAPIV3Server("../../testdata")
|
||||
require.NoError(t, err)
|
||||
defer fakeServer.HttpServer.Close()
|
||||
|
||||
require.Greater(t, len(fakeServer.ServedDocuments), 0)
|
||||
|
||||
client := NewMemCacheClient(
|
||||
discovery.NewDiscoveryClientForConfigOrDie(
|
||||
&rest.Config{Host: fakeServer.HttpServer.URL},
|
||||
),
|
||||
)
|
||||
openapiClient := client.OpenAPIV3()
|
||||
|
||||
paths, err := openapiClient.Paths()
|
||||
require.NoError(t, err)
|
||||
|
||||
for k, v := range paths {
|
||||
original, err := v.Schema()
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
pathsAgain, err := openapiClient.Paths()
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
schemaAgain, err := pathsAgain[k].Schema()
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.True(t, reflect.ValueOf(paths).Pointer() == reflect.ValueOf(pathsAgain).Pointer())
|
||||
assert.True(t, reflect.ValueOf(original).Pointer() == reflect.ValueOf(schemaAgain).Pointer())
|
||||
|
||||
// Invalidate and try again. This time pointers should not be equal
|
||||
client.Invalidate()
|
||||
|
||||
pathsAgain, err = client.OpenAPIV3().Paths()
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
schemaAgain, err = pathsAgain[k].Schema()
|
||||
if !assert.NoError(t, err) {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.True(t, reflect.ValueOf(paths).Pointer() != reflect.ValueOf(pathsAgain).Pointer())
|
||||
assert.True(t, reflect.ValueOf(original).Pointer() != reflect.ValueOf(schemaAgain).Pointer())
|
||||
assert.Equal(t, original, schemaAgain)
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import (
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/openapi"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
@ -46,7 +47,8 @@ const (
|
||||
// defaultRetries is the number of times a resource discovery is repeated if an api group disappears on the fly (e.g. CustomResourceDefinitions).
|
||||
defaultRetries = 2
|
||||
// protobuf mime type
|
||||
mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf"
|
||||
openAPIV2mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf"
|
||||
|
||||
// defaultTimeout is the maximum amount of time per request when no timeout has been set on a RESTClient.
|
||||
// Defaults to 32s in order to have a distinguishable length of time, relative to other timeouts that exist.
|
||||
defaultTimeout = 32 * time.Second
|
||||
@ -60,6 +62,7 @@ type DiscoveryInterface interface {
|
||||
ServerResourcesInterface
|
||||
ServerVersionInterface
|
||||
OpenAPISchemaInterface
|
||||
OpenAPIV3SchemaInterface
|
||||
}
|
||||
|
||||
// CachedDiscoveryInterface is a DiscoveryInterface with cache invalidation and freshness.
|
||||
@ -121,6 +124,10 @@ type OpenAPISchemaInterface interface {
|
||||
OpenAPISchema() (*openapi_v2.Document, error)
|
||||
}
|
||||
|
||||
type OpenAPIV3SchemaInterface interface {
|
||||
OpenAPIV3() openapi.Client
|
||||
}
|
||||
|
||||
// DiscoveryClient implements the functions that discover server-supported API groups,
|
||||
// versions and resources.
|
||||
type DiscoveryClient struct {
|
||||
@ -399,9 +406,9 @@ func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// OpenAPISchema fetches the open api schema using a rest client and parses the proto.
|
||||
// OpenAPISchema fetches the open api v2 schema using a rest client and parses the proto.
|
||||
func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", mimePb).Do(context.TODO()).Raw()
|
||||
data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", openAPIV2mimePb).Do(context.TODO()).Raw()
|
||||
if err != nil {
|
||||
if errors.IsForbidden(err) || errors.IsNotFound(err) || errors.IsNotAcceptable(err) {
|
||||
// single endpoint not found/registered in old server, try to fetch old endpoint
|
||||
@ -422,6 +429,10 @@ func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return document, nil
|
||||
}
|
||||
|
||||
func (d *DiscoveryClient) OpenAPIV3() openapi.Client {
|
||||
return openapi.NewClient(d.restClient)
|
||||
}
|
||||
|
||||
// withRetries retries the given recovery function in case the groups supported by the server change after ServerGroup() returns.
|
||||
func withRetries(maxRetries int, f func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
|
||||
var result []*metav1.APIResourceList
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
openapi_v2 "github.com/google/gnostic/openapiv2"
|
||||
openapi_v3 "github.com/google/gnostic/openapiv3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
golangproto "google.golang.org/protobuf/proto"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -36,6 +37,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
testutil "k8s.io/client-go/util/testing"
|
||||
)
|
||||
|
||||
func TestGetServerVersion(t *testing.T) {
|
||||
@ -535,6 +537,14 @@ func openapiSchemaFakeServer(t *testing.T) (*httptest.Server, error) {
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func openapiV3SchemaFakeServer(t *testing.T) (*httptest.Server, map[string]*openapi_v3.Document, error) {
|
||||
res, err := testutil.NewFakeOpenAPIV3Server("testdata")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return res.HttpServer, res.ServedDocuments, nil
|
||||
}
|
||||
|
||||
func TestGetOpenAPISchema(t *testing.T) {
|
||||
server, err := openapiSchemaFakeServer(t)
|
||||
if err != nil {
|
||||
@ -552,6 +562,45 @@ func TestGetOpenAPISchema(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOpenAPISchemaV3(t *testing.T) {
|
||||
server, testV3Specs, err := openapiV3SchemaFakeServer(t)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error starting fake server: %v", err)
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
openapiClient := client.OpenAPIV3()
|
||||
paths, err := openapiClient.Paths()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting openapi: %v", err)
|
||||
}
|
||||
|
||||
for k, v := range paths {
|
||||
actual, err := v.Schema()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := testV3Specs[k]
|
||||
if !golangproto.Equal(expected, actual) {
|
||||
t.Fatalf("expected \n%v\n\ngot:\n%v", expected, actual)
|
||||
}
|
||||
|
||||
// Ensure that fetching schema once again does not return same instance
|
||||
actualAgain, err := v.Schema()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if reflect.ValueOf(actual).Pointer() == reflect.ValueOf(actualAgain).Pointer() {
|
||||
t.Fatal("expected schema not to be cached")
|
||||
} else if !golangproto.Equal(actual, actualAgain) {
|
||||
t.Fatal("expected schema values to be equal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOpenAPISchemaForbiddenFallback(t *testing.T) {
|
||||
server, err := openapiSchemaDeprecatedFakeServer(http.StatusForbidden, t)
|
||||
if err != nil {
|
||||
|
@ -26,6 +26,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/openapi"
|
||||
kubeversion "k8s.io/client-go/pkg/version"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/testing"
|
||||
@ -154,6 +155,10 @@ func (c *FakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return &openapi_v2.Document{}, nil
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) OpenAPIV3() openapi.Client {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate with API server
|
||||
// by this client implementation.
|
||||
func (c *FakeDiscovery) RESTClient() restclient.Interface {
|
||||
|
1
discovery/testdata/apis/batch/v1.json
vendored
Normal file
1
discovery/testdata/apis/batch/v1.json
vendored
Normal file
File diff suppressed because one or more lines are too long
1
discovery/testdata/apis/batch/v1beta1.json
vendored
Normal file
1
discovery/testdata/apis/batch/v1beta1.json
vendored
Normal file
File diff suppressed because one or more lines are too long
4
go.mod
4
go.mod
@ -9,8 +9,11 @@ require (
|
||||
github.com/Azure/go-autorest/autorest v0.11.18
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.13
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible // indirect
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible
|
||||
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.5 // indirect
|
||||
github.com/go-openapi/swag v0.19.14 // indirect
|
||||
github.com/gogo/protobuf v1.3.2
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da
|
||||
github.com/golang/protobuf v1.5.2
|
||||
@ -21,6 +24,7 @@ require (
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7
|
||||
github.com/imdario/mergo v0.3.5
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.7.0
|
||||
|
15
go.sum
15
go.sum
@ -54,7 +54,9 @@ github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBp
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
@ -75,6 +77,8 @@ github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk=
|
||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
@ -99,9 +103,14 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg
|
||||
github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE=
|
||||
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
|
||||
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
|
||||
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@ -187,6 +196,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
|
||||
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
@ -202,6 +213,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
@ -212,6 +225,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
|
54
openapi/cached/client.go
Normal file
54
openapi/cached/client.go
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 cached
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/client-go/openapi"
|
||||
)
|
||||
|
||||
type client struct {
|
||||
delegate openapi.Client
|
||||
|
||||
once sync.Once
|
||||
result map[string]openapi.GroupVersion
|
||||
err error
|
||||
}
|
||||
|
||||
func NewClient(other openapi.Client) openapi.Client {
|
||||
return &client{
|
||||
delegate: other,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Paths() (map[string]openapi.GroupVersion, error) {
|
||||
c.once.Do(func() {
|
||||
uncached, err := c.delegate.Paths()
|
||||
if err != nil {
|
||||
c.err = err
|
||||
return
|
||||
}
|
||||
|
||||
result := make(map[string]openapi.GroupVersion, len(uncached))
|
||||
for k, v := range uncached {
|
||||
result[k] = newGroupVersion(v)
|
||||
}
|
||||
c.result = result
|
||||
})
|
||||
return c.result, c.err
|
||||
}
|
45
openapi/cached/groupversion.go
Normal file
45
openapi/cached/groupversion.go
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 cached
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
openapi_v3 "github.com/google/gnostic/openapiv3"
|
||||
"k8s.io/client-go/openapi"
|
||||
)
|
||||
|
||||
type groupversion struct {
|
||||
delegate openapi.GroupVersion
|
||||
once sync.Once
|
||||
doc *openapi_v3.Document
|
||||
err error
|
||||
}
|
||||
|
||||
func newGroupVersion(delegate openapi.GroupVersion) *groupversion {
|
||||
return &groupversion{
|
||||
delegate: delegate,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *groupversion) Schema() (*openapi_v3.Document, error) {
|
||||
g.once.Do(func() {
|
||||
g.doc, g.err = g.delegate.Schema()
|
||||
})
|
||||
|
||||
return g.doc, g.err
|
||||
}
|
64
openapi/client.go
Normal file
64
openapi/client.go
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/kube-openapi/pkg/handler3"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Paths() (map[string]GroupVersion, error)
|
||||
}
|
||||
|
||||
type client struct {
|
||||
// URL includes the `hash` query param to take advantage of cache busting
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func NewClient(restClient rest.Interface) Client {
|
||||
return &client{
|
||||
restClient: restClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Paths() (map[string]GroupVersion, error) {
|
||||
data, err := c.restClient.Get().
|
||||
AbsPath("/openapi/v3").
|
||||
Do(context.TODO()).
|
||||
Raw()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
discoMap := &handler3.OpenAPIV3Discovery{}
|
||||
err = json.Unmarshal(data, discoMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create GroupVersions for each element of the result
|
||||
result := map[string]GroupVersion{}
|
||||
for k, v := range discoMap.Paths {
|
||||
result[k] = newGroupVersion(c, v)
|
||||
}
|
||||
return result, nil
|
||||
}
|
59
openapi/groupversion.go
Normal file
59
openapi/groupversion.go
Normal file
@ -0,0 +1,59 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
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 (
|
||||
"context"
|
||||
|
||||
openapi_v3 "github.com/google/gnostic/openapiv3"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"k8s.io/kube-openapi/pkg/handler3"
|
||||
)
|
||||
|
||||
const openAPIV3mimePb = "application/com.github.proto-openapi.spec.v3@v1.0+protobuf"
|
||||
|
||||
type GroupVersion interface {
|
||||
Schema() (*openapi_v3.Document, error)
|
||||
}
|
||||
|
||||
type groupversion struct {
|
||||
client *client
|
||||
item handler3.OpenAPIV3DiscoveryGroupVersion
|
||||
}
|
||||
|
||||
func newGroupVersion(client *client, item handler3.OpenAPIV3DiscoveryGroupVersion) *groupversion {
|
||||
return &groupversion{client: client, item: item}
|
||||
}
|
||||
|
||||
func (g *groupversion) Schema() (*openapi_v3.Document, error) {
|
||||
data, err := g.client.restClient.Get().
|
||||
RequestURI(g.item.ServerRelativeURL).
|
||||
SetHeader("Accept", openAPIV3mimePb).
|
||||
Do(context.TODO()).
|
||||
Raw()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
document := &openapi_v3.Document{}
|
||||
if err := proto.Unmarshal(data, document); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return document, nil
|
||||
}
|
@ -27,6 +27,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
. "k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/openapi"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
|
||||
@ -417,6 +418,10 @@ func (*fakeFailingDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (c *fakeFailingDiscovery) OpenAPIV3() openapi.Client {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
type fakeCachedDiscoveryInterface struct {
|
||||
invalidateCalls int
|
||||
fresh bool
|
||||
@ -490,6 +495,10 @@ func (c *fakeCachedDiscoveryInterface) OpenAPISchema() (*openapi_v2.Document, er
|
||||
return &openapi_v2.Document{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) OpenAPIV3() openapi.Client {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
var (
|
||||
aGroup = metav1.APIGroup{
|
||||
Name: "a",
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/openapi"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
)
|
||||
@ -296,3 +297,7 @@ func (c *fakeDiscoveryClient) ServerVersion() (*version.Info, error) {
|
||||
func (c *fakeDiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return &openapi_v2.Document{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeDiscoveryClient) OpenAPIV3() openapi.Client {
|
||||
panic("implement me")
|
||||
}
|
||||
|
174
util/testing/fake_openapi_handler.go
Normal file
174
util/testing/fake_openapi_handler.go
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
Copyright 2021 The Kubernetes Authors.
|
||||
|
||||
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 testing
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
openapi_v3 "github.com/google/gnostic/openapiv3"
|
||||
"k8s.io/kube-openapi/pkg/handler3"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
)
|
||||
|
||||
type FakeOpenAPIServer struct {
|
||||
HttpServer *httptest.Server
|
||||
ServedDocuments map[string]*openapi_v3.Document
|
||||
RequestCounters map[string]int
|
||||
}
|
||||
|
||||
// Creates a mock OpenAPIV3 server as it would be on a standing kubernetes
|
||||
// API server.
|
||||
//
|
||||
// specsPath - Give a path to some test data organized so that each GroupVersion
|
||||
// has its own OpenAPI V3 JSON file.
|
||||
// i.e. apps/v1beta1 is stored in <specsPath>/apps/v1beta1.json
|
||||
func NewFakeOpenAPIV3Server(specsPath string) (*FakeOpenAPIServer, error) {
|
||||
mux := &testMux{
|
||||
counts: map[string]int{},
|
||||
}
|
||||
server := httptest.NewServer(mux)
|
||||
|
||||
openAPIVersionedService, err := handler3.NewOpenAPIService(nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = openAPIVersionedService.RegisterOpenAPIV3VersionedService("/openapi/v3", mux)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
grouped := make(map[string][]byte)
|
||||
var testV3Specs = make(map[string]*openapi_v3.Document)
|
||||
|
||||
addSpec := func(path string) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
vals, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(specsPath, path)
|
||||
if err == nil {
|
||||
grouped[rel[:(len(rel)-len(filepath.Ext(rel)))]] = vals
|
||||
}
|
||||
}
|
||||
|
||||
filepath.WalkDir(specsPath, func(path string, d fs.DirEntry, err error) error {
|
||||
if filepath.Ext(path) != ".json" || d.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
addSpec(path)
|
||||
return nil
|
||||
})
|
||||
|
||||
for gv, jsonSpec := range grouped {
|
||||
spec := &spec3.OpenAPI{}
|
||||
err = json.Unmarshal(jsonSpec, spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gnosticSpec, err := openapi_v3.ParseDocument(jsonSpec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
testV3Specs[gv] = gnosticSpec
|
||||
openAPIVersionedService.UpdateGroupVersion(gv, spec)
|
||||
}
|
||||
|
||||
return &FakeOpenAPIServer{
|
||||
HttpServer: server,
|
||||
ServedDocuments: testV3Specs,
|
||||
RequestCounters: mux.counts,
|
||||
}, nil
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Tiny Test HTTP Mux
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Implements the mux interface used by handler3 for registering the OpenAPI
|
||||
// handlers
|
||||
|
||||
type testMux struct {
|
||||
lock sync.Mutex
|
||||
prefixMap map[string]http.Handler
|
||||
pathMap map[string]http.Handler
|
||||
counts map[string]int
|
||||
}
|
||||
|
||||
func (t *testMux) Handle(path string, handler http.Handler) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if t.pathMap == nil {
|
||||
t.pathMap = make(map[string]http.Handler)
|
||||
}
|
||||
t.pathMap[path] = handler
|
||||
}
|
||||
|
||||
func (t *testMux) HandlePrefix(path string, handler http.Handler) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if t.prefixMap == nil {
|
||||
t.prefixMap = make(map[string]http.Handler)
|
||||
}
|
||||
t.prefixMap[path] = handler
|
||||
}
|
||||
|
||||
func (t *testMux) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
|
||||
if t.counts == nil {
|
||||
t.counts = make(map[string]int)
|
||||
}
|
||||
|
||||
if val, exists := t.counts[req.URL.Path]; exists {
|
||||
t.counts[req.URL.Path] = val + 1
|
||||
} else {
|
||||
t.counts[req.URL.Path] = 1
|
||||
}
|
||||
|
||||
if handler, ok := t.pathMap[req.URL.Path]; ok {
|
||||
handler.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
for k, v := range t.prefixMap {
|
||||
if strings.HasPrefix(req.URL.Path, k) {
|
||||
v.ServeHTTP(w, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
Loading…
Reference in New Issue
Block a user