Upgrade the operator to use Operator SDK v1.33.0 (#182)

* Move controller package inside internal directory

Based on the go/v4 project structure, the following changed:
- Pakcage `controllers` is now named `controller`
- Package `controller` now lives inside new `internal` directory

* Move main.go in cmd directory

Based on the new go/v4 project structure, `main.go` now lives in the `cmd` directory.

* Change package import in main.go

* Update go mod dependencies

Update the dependencies based on the versions obtained by creating a new operator project using `kubebuilder init --domain onepassword.com --plugins=go/v4`.

This is based on the migration steps provided to go from go/v3 to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4)

* Update vendor

* Adjust code for breaking changes from pkg update

sigs.k8s.io/controller-runtime package had breaking changes from v0.14.5 to v0.16.3. This commit brings the changes needed to achieve the same things using the new functionality avaialble.

* Adjust paths to connect yaml files

Since `main.go` is now in `cmd` directory, the paths to the files for deploying Connect have to be adjusted based on the new location `main.go` is executed from.

* Update files based on new structure and scaffolding

These changes are made based on the new project structure and scaffolding obtained when using the new go/v4 project structure.

These were done based on the migration steps mentioned when migrating to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4).

* Update config files

These updates are made based on the Kustomize v4 syntax.

This is part of the upgrate to go/v4 (https://book.kubebuilder.io/migration/migration_guide_gov3_to_gov4)

* Update dependencies and GO version

* Update vendor

* Update Kubernetes tools versions

* Update operator version in Makefile

Now the version in the Makefile matches the version of the operator

* Update Operator SDK version in version.go

* Adjust generated deepcopy

It seems that the +build tag is no longer needed based on the latest generated scaffolding, therefore it's removed.

* Update copyright year

* Bring back missing changes from migration

Some customization in Makefile was lost during the migration process. Specifically, the namespace customization for `make deploy` command.

Also, we push changes to kustomization.yaml for making the deploy process smoother.

* Add RBAC perms for coordination.k8s.io

It seems that with the latest changes to Kubernetes and Kustomize, we need to add additional RBAC to the service account used so that it can properly access the `leases` resource.

* Optimize Dockerfile

Dockerfile had a step for caching dependencies (go mod download). However, this is already done by the vendor directory, which we include. Therefore, this step can be removed to make the image build time faster.
This commit is contained in:
Eduard Filip
2024-01-25 14:21:31 +01:00
committed by GitHub
parent 8fc852a4dd
commit f72e5243b0
1356 changed files with 86780 additions and 43671 deletions

View File

@@ -1,51 +0,0 @@
/*
Copyright 2022 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 util
import (
"reflect"
"k8s.io/kube-openapi/pkg/schemamutation"
"k8s.io/kube-openapi/pkg/validation/spec"
)
// wrapRefs wraps OpenAPI V3 Schema refs that contain sibling elements.
// AllOf is used to wrap the Ref to prevent references from having sibling elements
// Please see https://github.com/kubernetes/kubernetes/issues/106387#issuecomment-967640388
func WrapRefs(schema *spec.Schema) *spec.Schema {
walker := schemamutation.Walker{
SchemaCallback: func(schema *spec.Schema) *spec.Schema {
orig := schema
clone := func() {
if orig == schema {
schema = new(spec.Schema)
*schema = *orig
}
}
if schema.Ref.String() != "" && !reflect.DeepEqual(*schema, spec.Schema{SchemaProps: spec.SchemaProps{Ref: schema.Ref}}) {
clone()
refSchema := new(spec.Schema)
refSchema.Ref = schema.Ref
schema.Ref = spec.Ref{}
schema.AllOf = []spec.Schema{*refSchema}
}
return schema
},
RefCallback: schemamutation.RefCallbackNoop,
}
return walker.WalkSchema(schema)
}

View File

@@ -14,31 +14,29 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// Package cache provides a cache mechanism based on etags to lazily
// Package cached provides a cache mechanism based on etags to lazily
// build, and/or cache results from expensive operation such that those
// operations are not repeated unnecessarily. The operations can be
// created as a tree, and replaced dynamically as needed.
//
// All the operations in this module are thread-safe.
//
// # Dependencies and types of caches
//
// This package uses a source/transform/sink model of caches to build
// the dependency tree, and can be used as follows:
// - [NewSource]: A source cache that recomputes the content every time.
// - [NewStaticSource]: A source cache that always produces the
// - [Func]: A source cache that recomputes the content every time.
// - [Once]: A source cache that always produces the
// same content, it is only called once.
// - [NewTransformer]: A cache that transforms data from one format to
// - [Transform]: A cache that transforms data from one format to
// another. It's only refreshed when the source changes.
// - [NewMerger]: A cache that aggregates multiple caches into one.
// - [Merge]: A cache that aggregates multiple caches in a map into one.
// It's only refreshed when the source changes.
// - [Replaceable]: A cache adapter that can be atomically
// replaced with a new one, and saves the previous results in case an
// error pops-up.
//
// # Atomicity
//
// Most of the operations are not atomic/thread-safe, except for
// [Replaceable.Replace] which can be performed while the objects
// are being read.
// - [MergeList]: A cache that aggregates multiple caches in a list into one.
// It's only refreshed when the source changes.
// - [Atomic]: A cache adapter that atomically replaces the source with a new one.
// - [LastSuccess]: A cache adapter that caches the last successful and returns
// it if the next call fails. It extends [Atomic].
//
// # Etags
//
@@ -54,113 +52,146 @@ package cached
import (
"fmt"
"sync"
"sync/atomic"
)
// Result is the content returned from a call to a cache. It can either
// be created with [NewResultOK] if the call was a success, or
// [NewResultErr] if the call resulted in an error.
// Value is wrapping a value behind a getter for lazy evaluation.
type Value[T any] interface {
Get() (value T, etag string, err error)
}
// Result is wrapping T and error into a struct for cases where a tuple is more
// convenient or necessary in Golang.
type Result[T any] struct {
Data T
Etag string
Err error
Value T
Etag string
Err error
}
// NewResultOK creates a new [Result] for a successful operation.
func NewResultOK[T any](data T, etag string) Result[T] {
return Result[T]{
Data: data,
Etag: etag,
}
func (r Result[T]) Get() (T, string, error) {
return r.Value, r.Etag, r.Err
}
// NewResultErr creates a new [Result] when an error has happened.
func NewResultErr[T any](err error) Result[T] {
return Result[T]{
Err: err,
}
// Func wraps a (thread-safe) function as a Value[T].
func Func[T any](fn func() (T, string, error)) Value[T] {
return valueFunc[T](fn)
}
// Result can be treated as a [Data] if necessary.
func (r Result[T]) Get() Result[T] {
return r
type valueFunc[T any] func() (T, string, error)
func (c valueFunc[T]) Get() (T, string, error) {
return c()
}
// Data is a cache that performs an action whose result data will be
// cached. It also returns an "etag" identifier to version the cache, so
// that the caller can know if they have the most recent version of the
// cache (and can decide to cache some operation based on that).
//
// The [NewMerger] and [NewTransformer] automatically handle
// that for you by checking if the etag is updated before calling the
// merging or transforming function.
type Data[T any] interface {
// Returns the cached data, as well as an "etag" to identify the
// version of the cache, or an error if something happened.
Get() Result[T]
// Static returns constant values.
func Static[T any](value T, etag string) Value[T] {
return Result[T]{Value: value, Etag: etag}
}
// T is the source type, V is the destination type.
type merger[K comparable, T, V any] struct {
mergeFn func(map[K]Result[T]) Result[V]
caches map[K]Data[T]
cacheResults map[K]Result[T]
result Result[V]
}
// NewMerger creates a new merge cache, a cache that merges the result
// of other caches. The function only gets called if any of the
// dependency has changed.
// Merge merges a of cached values. The merge function only gets called if any of
// the dependency has changed.
//
// If any of the dependency returned an error before, or any of the
// dependency returned an error this time, or if the mergeFn failed
// before, then the function is reran.
//
// The caches and results are mapped by K so that associated data can be
// retrieved. The map of dependencies can not be modified after
// creation, and a new merger should be created (and probably replaced
// using a [Replaceable]).
// before, then the function is run again.
//
// Note that this assumes there is no "partial" merge, the merge
// function will remerge all the dependencies together everytime. Since
// the list of dependencies is constant, there is no way to save some
// partial merge information either.
func NewMerger[K comparable, T, V any](mergeFn func(results map[K]Result[T]) Result[V], caches map[K]Data[T]) Data[V] {
return &merger[K, T, V]{
mergeFn: mergeFn,
caches: caches,
//
// Also note that Golang map iteration is not stable. If the mergeFn
// depends on the order iteration to be stable, it will need to
// implement its own sorting or iteration order.
func Merge[K comparable, T, V any](mergeFn func(results map[K]Result[T]) (V, string, error), caches map[K]Value[T]) Value[V] {
list := make([]Value[T], 0, len(caches))
// map from index to key
indexes := make(map[int]K, len(caches))
i := 0
for k := range caches {
list = append(list, caches[k])
indexes[i] = k
i++
}
return MergeList(func(results []Result[T]) (V, string, error) {
if len(results) != len(indexes) {
panic(fmt.Errorf("invalid result length %d, expected %d", len(results), len(indexes)))
}
m := make(map[K]Result[T], len(results))
for i := range results {
m[indexes[i]] = results[i]
}
return mergeFn(m)
}, list)
}
// MergeList merges a list of cached values. The function only gets called if
// any of the dependency has changed.
//
// The benefit of ListMerger over the basic Merger is that caches are
// stored in an ordered list so the order of the cache will be
// preserved in the order of the results passed to the mergeFn.
//
// If any of the dependency returned an error before, or any of the
// dependency returned an error this time, or if the mergeFn failed
// before, then the function is reran.
//
// Note that this assumes there is no "partial" merge, the merge
// function will remerge all the dependencies together everytime. Since
// the list of dependencies is constant, there is no way to save some
// partial merge information either.
func MergeList[T, V any](mergeFn func(results []Result[T]) (V, string, error), delegates []Value[T]) Value[V] {
return &listMerger[T, V]{
mergeFn: mergeFn,
delegates: delegates,
}
}
func (c *merger[K, T, V]) prepareResults() map[K]Result[T] {
cacheResults := make(map[K]Result[T], len(c.caches))
for key, cache := range c.caches {
cacheResults[key] = cache.Get()
type listMerger[T, V any] struct {
lock sync.Mutex
mergeFn func([]Result[T]) (V, string, error)
delegates []Value[T]
cache []Result[T]
result Result[V]
}
func (c *listMerger[T, V]) prepareResultsLocked() []Result[T] {
cacheResults := make([]Result[T], len(c.delegates))
ch := make(chan struct {
int
Result[T]
}, len(c.delegates))
for i := range c.delegates {
go func(index int) {
value, etag, err := c.delegates[index].Get()
ch <- struct {
int
Result[T]
}{index, Result[T]{Value: value, Etag: etag, Err: err}}
}(i)
}
for i := 0; i < len(c.delegates); i++ {
res := <-ch
cacheResults[res.int] = res.Result
}
return cacheResults
}
// Rerun if:
// - The last run resulted in an error
// - Any of the dependency previously returned an error
// - Any of the dependency just returned an error
// - Any of the dependency's etag changed
func (c *merger[K, T, V]) needsRunning(results map[K]Result[T]) bool {
if c.cacheResults == nil {
func (c *listMerger[T, V]) needsRunningLocked(results []Result[T]) bool {
if c.cache == nil {
return true
}
if c.result.Err != nil {
return true
}
if len(results) != len(c.cacheResults) {
panic(fmt.Errorf("invalid number of results: %v (expected %v)", len(results), len(c.cacheResults)))
if len(results) != len(c.cache) {
panic(fmt.Errorf("invalid number of results: %v (expected %v)", len(results), len(c.cache)))
}
for key, oldResult := range c.cacheResults {
newResult, ok := results[key]
if !ok {
panic(fmt.Errorf("unknown cache entry: %v", key))
}
for i, oldResult := range c.cache {
newResult := results[i]
if newResult.Etag != oldResult.Etag || newResult.Err != nil || oldResult.Err != nil {
return true
}
@@ -168,97 +199,92 @@ func (c *merger[K, T, V]) needsRunning(results map[K]Result[T]) bool {
return false
}
func (c *merger[K, T, V]) Get() Result[V] {
cacheResults := c.prepareResults()
if c.needsRunning(cacheResults) {
c.cacheResults = cacheResults
c.result = c.mergeFn(c.cacheResults)
func (c *listMerger[T, V]) Get() (V, string, error) {
c.lock.Lock()
defer c.lock.Unlock()
cacheResults := c.prepareResultsLocked()
if c.needsRunningLocked(cacheResults) {
c.cache = cacheResults
c.result.Value, c.result.Etag, c.result.Err = c.mergeFn(c.cache)
}
return c.result
return c.result.Value, c.result.Etag, c.result.Err
}
type transformerCacheKeyType struct{}
// NewTransformer creates a new cache that transforms the result of
// another cache. The transformFn will only be called if the source
// cache has updated the output, otherwise, the cached result will be
// returned.
// Transform the result of another cached value. The transformFn will only be called
// if the source has updated, otherwise, the result will be returned.
//
// If the dependency returned an error before, or it returns an error
// this time, or if the transformerFn failed before, the function is
// reran.
func NewTransformer[T, V any](transformerFn func(Result[T]) Result[V], source Data[T]) Data[V] {
return NewMerger(func(caches map[transformerCacheKeyType]Result[T]) Result[V] {
cache, ok := caches[transformerCacheKeyType{}]
if len(caches) != 1 || !ok {
panic(fmt.Errorf("invalid cache for transformer cache: %v", caches))
func Transform[T, V any](transformerFn func(T, string, error) (V, string, error), source Value[T]) Value[V] {
return MergeList(func(delegates []Result[T]) (V, string, error) {
if len(delegates) != 1 {
panic(fmt.Errorf("invalid cache for transformer cache: %v", delegates))
}
return transformerFn(cache)
}, map[transformerCacheKeyType]Data[T]{
{}: source,
return transformerFn(delegates[0].Value, delegates[0].Etag, delegates[0].Err)
}, []Value[T]{source})
}
// Once calls Value[T].Get() lazily and only once, even in case of an error result.
func Once[T any](d Value[T]) Value[T] {
return &once[T]{
data: d,
}
}
type once[T any] struct {
once sync.Once
data Value[T]
result Result[T]
}
func (c *once[T]) Get() (T, string, error) {
c.once.Do(func() {
c.result.Value, c.result.Etag, c.result.Err = c.data.Get()
})
return c.result.Value, c.result.Etag, c.result.Err
}
// NewSource creates a new cache that generates some data. This
// will always be called since we don't know the origin of the data and
// if it needs to be updated or not.
func NewSource[T any](sourceFn func() Result[T]) Data[T] {
c := source[T](sourceFn)
return &c
// Replaceable extends the Value[T] interface with the ability to change the
// underlying Value[T] after construction.
type Replaceable[T any] interface {
Value[T]
Store(Value[T])
}
type source[T any] func() Result[T]
func (c *source[T]) Get() Result[T] {
return (*c)()
// Atomic wraps a Value[T] as an atomic value that can be replaced. It implements
// Replaceable[T].
type Atomic[T any] struct {
value atomic.Pointer[Value[T]]
}
// NewStaticSource creates a new cache that always generates the
// same data. This will only be called once (lazily).
func NewStaticSource[T any](staticFn func() Result[T]) Data[T] {
return &static[T]{
fn: staticFn,
var _ Replaceable[[]byte] = &Atomic[[]byte]{}
func (x *Atomic[T]) Store(val Value[T]) { x.value.Store(&val) }
func (x *Atomic[T]) Get() (T, string, error) { return (*x.value.Load()).Get() }
// LastSuccess calls Value[T].Get(), but hides errors by returning the last
// success if there has been any.
type LastSuccess[T any] struct {
Atomic[T]
success atomic.Pointer[Result[T]]
}
var _ Replaceable[[]byte] = &LastSuccess[[]byte]{}
func (c *LastSuccess[T]) Get() (T, string, error) {
success := c.success.Load()
value, etag, err := c.Atomic.Get()
if err == nil {
if success == nil {
c.success.CompareAndSwap(nil, &Result[T]{Value: value, Etag: etag, Err: err})
}
return value, etag, err
}
}
type static[T any] struct {
fn func() Result[T]
result *Result[T]
}
func (c *static[T]) Get() Result[T] {
if c.result == nil {
result := c.fn()
c.result = &result
if success != nil {
return success.Value, success.Etag, success.Err
}
return *c.result
}
// Replaceable is a cache that carries the result even when the
// cache is replaced. The cache can be replaced atomically (without any
// lock held). This is the type that should typically be stored in
// structs.
type Replaceable[T any] struct {
cache atomic.Pointer[Data[T]]
result *Result[T]
}
// Get retrieves the data from the underlying source. [Replaceable]
// implements the [Data] interface itself. This is a pass-through
// that calls the most recent underlying cache. If the cache fails but
// previously had returned a success, that success will be returned
// instead. If the cache fails but we never returned a success, that
// failure is returned.
func (c *Replaceable[T]) Get() Result[T] {
result := (*c.cache.Load()).Get()
if result.Err != nil && c.result != nil && c.result.Err == nil {
return *c.result
}
c.result = &result
return *c.result
}
// Replace changes the cache in a thread-safe way.
func (c *Replaceable[T]) Replace(cache Data[T]) {
c.cache.Swap(&cache)
return value, etag, err
}

View File

@@ -22,7 +22,6 @@ import (
"github.com/emicklei/go-restful/v3"
"k8s.io/kube-openapi/pkg/openapiconv"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@@ -172,43 +171,6 @@ type OpenAPIV3Config struct {
DefaultSecurity []map[string][]string
}
// ConvertConfigToV3 converts a Config object to an OpenAPIV3Config object
func ConvertConfigToV3(config *Config) *OpenAPIV3Config {
if config == nil {
return nil
}
v3Config := &OpenAPIV3Config{
Info: config.Info,
IgnorePrefixes: config.IgnorePrefixes,
GetDefinitions: config.GetDefinitions,
GetOperationIDAndTags: config.GetOperationIDAndTags,
GetOperationIDAndTagsFromRoute: config.GetOperationIDAndTagsFromRoute,
GetDefinitionName: config.GetDefinitionName,
Definitions: config.Definitions,
SecuritySchemes: make(spec3.SecuritySchemes),
DefaultSecurity: config.DefaultSecurity,
DefaultResponse: openapiconv.ConvertResponse(config.DefaultResponse, []string{"application/json"}),
CommonResponses: make(map[int]*spec3.Response),
ResponseDefinitions: make(map[string]*spec3.Response),
}
if config.SecurityDefinitions != nil {
for s, securityScheme := range *config.SecurityDefinitions {
v3Config.SecuritySchemes[s] = openapiconv.ConvertSecurityScheme(securityScheme)
}
}
for k, commonResponse := range config.CommonResponses {
v3Config.CommonResponses[k] = openapiconv.ConvertResponse(&commonResponse, []string{"application/json"})
}
for k, responseDefinition := range config.ResponseDefinitions {
v3Config.ResponseDefinitions[k] = openapiconv.ConvertResponse(&responseDefinition, []string{"application/json"})
}
return v3Config
}
type typeInfo struct {
name string
format string

View File

@@ -30,9 +30,10 @@ import (
"time"
"github.com/golang/protobuf/proto"
openapi_v3 "github.com/google/gnostic/openapiv3"
openapi_v3 "github.com/google/gnostic-models/openapiv3"
"github.com/google/uuid"
"github.com/munnerz/goautoneg"
"k8s.io/klog/v2"
"k8s.io/kube-openapi/pkg/cached"
"k8s.io/kube-openapi/pkg/common"
@@ -73,38 +74,38 @@ type timedSpec struct {
// This type is protected by the lock on OpenAPIService.
type openAPIV3Group struct {
specCache cached.Replaceable[*spec3.OpenAPI]
pbCache cached.Data[timedSpec]
jsonCache cached.Data[timedSpec]
specCache cached.LastSuccess[*spec3.OpenAPI]
pbCache cached.Value[timedSpec]
jsonCache cached.Value[timedSpec]
}
func newOpenAPIV3Group() *openAPIV3Group {
o := &openAPIV3Group{}
o.jsonCache = cached.NewTransformer[*spec3.OpenAPI](func(result cached.Result[*spec3.OpenAPI]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
json, err := json.Marshal(result.Data)
o.jsonCache = cached.Transform[*spec3.OpenAPI](func(spec *spec3.OpenAPI, etag string, err error) (timedSpec, string, error) {
if err != nil {
return cached.NewResultErr[timedSpec](err)
return timedSpec{}, "", err
}
return cached.NewResultOK(timedSpec{spec: json, lastModified: time.Now()}, computeETag(json))
json, err := json.Marshal(spec)
if err != nil {
return timedSpec{}, "", err
}
return timedSpec{spec: json, lastModified: time.Now()}, computeETag(json), nil
}, &o.specCache)
o.pbCache = cached.NewTransformer(func(result cached.Result[timedSpec]) cached.Result[timedSpec] {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
}
proto, err := ToV3ProtoBinary(result.Data.spec)
o.pbCache = cached.Transform(func(ts timedSpec, etag string, err error) (timedSpec, string, error) {
if err != nil {
return cached.NewResultErr[timedSpec](err)
return timedSpec{}, "", err
}
return cached.NewResultOK(timedSpec{spec: proto, lastModified: result.Data.lastModified}, result.Etag)
proto, err := ToV3ProtoBinary(ts.spec)
if err != nil {
return timedSpec{}, "", err
}
return timedSpec{spec: proto, lastModified: ts.lastModified}, etag, nil
}, o.jsonCache)
return o
}
func (o *openAPIV3Group) UpdateSpec(openapi cached.Data[*spec3.OpenAPI]) {
o.specCache.Replace(openapi)
func (o *openAPIV3Group) UpdateSpec(openapi cached.Value[*spec3.OpenAPI]) {
o.specCache.Store(openapi)
}
// OpenAPIService is the service responsible for serving OpenAPI spec. It has
@@ -114,7 +115,7 @@ type OpenAPIService struct {
mutex sync.Mutex
v3Schema map[string]*openAPIV3Group
discoveryCache cached.Replaceable[timedSpec]
discoveryCache cached.LastSuccess[timedSpec]
}
func computeETag(data []byte) string {
@@ -137,20 +138,20 @@ func NewOpenAPIService() *OpenAPIService {
o := &OpenAPIService{}
o.v3Schema = make(map[string]*openAPIV3Group)
// We're not locked because we haven't shared the structure yet.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
o.discoveryCache.Store(o.buildDiscoveryCacheLocked())
return o
}
func (o *OpenAPIService) buildDiscoveryCacheLocked() cached.Data[timedSpec] {
caches := make(map[string]cached.Data[timedSpec], len(o.v3Schema))
func (o *OpenAPIService) buildDiscoveryCacheLocked() cached.Value[timedSpec] {
caches := make(map[string]cached.Value[timedSpec], len(o.v3Schema))
for gvName, group := range o.v3Schema {
caches[gvName] = group.jsonCache
}
return cached.NewMerger(func(results map[string]cached.Result[timedSpec]) cached.Result[timedSpec] {
return cached.Merge(func(results map[string]cached.Result[timedSpec]) (timedSpec, string, error) {
discovery := &OpenAPIV3Discovery{Paths: make(map[string]OpenAPIV3DiscoveryGroupVersion)}
for gvName, result := range results {
if result.Err != nil {
return cached.NewResultErr[timedSpec](result.Err)
return timedSpec{}, "", result.Err
}
discovery.Paths[gvName] = OpenAPIV3DiscoveryGroupVersion{
ServerRelativeURL: constructServerRelativeURL(gvName, result.Etag),
@@ -158,9 +159,9 @@ func (o *OpenAPIService) buildDiscoveryCacheLocked() cached.Data[timedSpec] {
}
j, err := json.Marshal(discovery)
if err != nil {
return cached.NewResultErr[timedSpec](err)
return timedSpec{}, "", err
}
return cached.NewResultOK(timedSpec{spec: j, lastModified: time.Now()}, computeETag(j))
return timedSpec{spec: j, lastModified: time.Now()}, computeETag(j), nil
}, caches)
}
@@ -171,32 +172,32 @@ func (o *OpenAPIService) getSingleGroupBytes(getType string, group string) ([]by
if !ok {
return nil, "", time.Now(), fmt.Errorf("Cannot find CRD group %s", group)
}
result := cached.Result[timedSpec]{}
switch getType {
case subTypeJSON:
result = v.jsonCache.Get()
ts, etag, err := v.jsonCache.Get()
return ts.spec, etag, ts.lastModified, err
case subTypeProtobuf, subTypeProtobufDeprecated:
result = v.pbCache.Get()
ts, etag, err := v.pbCache.Get()
return ts.spec, etag, ts.lastModified, err
default:
return nil, "", time.Now(), fmt.Errorf("Invalid accept clause %s", getType)
}
return result.Data.spec, result.Etag, result.Data.lastModified, result.Err
}
// UpdateGroupVersionLazy adds or updates an existing group with the new cached.
func (o *OpenAPIService) UpdateGroupVersionLazy(group string, openapi cached.Data[*spec3.OpenAPI]) {
func (o *OpenAPIService) UpdateGroupVersionLazy(group string, openapi cached.Value[*spec3.OpenAPI]) {
o.mutex.Lock()
defer o.mutex.Unlock()
if _, ok := o.v3Schema[group]; !ok {
o.v3Schema[group] = newOpenAPIV3Group()
// Since there is a new item, we need to re-build the cache map.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
o.discoveryCache.Store(o.buildDiscoveryCacheLocked())
}
o.v3Schema[group].UpdateSpec(openapi)
}
func (o *OpenAPIService) UpdateGroupVersion(group string, openapi *spec3.OpenAPI) {
o.UpdateGroupVersionLazy(group, cached.NewResultOK(openapi, uuid.New().String()))
o.UpdateGroupVersionLazy(group, cached.Static(openapi, uuid.New().String()))
}
func (o *OpenAPIService) DeleteGroupVersion(group string) {
@@ -204,19 +205,19 @@ func (o *OpenAPIService) DeleteGroupVersion(group string) {
defer o.mutex.Unlock()
delete(o.v3Schema, group)
// Rebuild the merge cache map since the items have changed.
o.discoveryCache.Replace(o.buildDiscoveryCacheLocked())
o.discoveryCache.Store(o.buildDiscoveryCacheLocked())
}
func (o *OpenAPIService) HandleDiscovery(w http.ResponseWriter, r *http.Request) {
result := o.discoveryCache.Get()
if result.Err != nil {
klog.Errorf("Error serving discovery: %s", result.Err)
ts, etag, err := o.discoveryCache.Get()
if err != nil {
klog.Errorf("Error serving discovery: %s", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Etag", strconv.Quote(result.Etag))
w.Header().Set("Etag", strconv.Quote(etag))
w.Header().Set("Content-Type", "application/json")
http.ServeContent(w, r, "/openapi/v3", result.Data.lastModified, bytes.NewReader(result.Data.spec))
http.ServeContent(w, r, "/openapi/v3", ts.lastModified, bytes.NewReader(ts.spec))
}
func (o *OpenAPIService) HandleGroupVersion(w http.ResponseWriter, r *http.Request) {

View File

@@ -22,3 +22,4 @@ var UseOptimizedJSONUnmarshalingV3 bool = true
// Used by tests to selectively disable experimental JSON marshaler
var UseOptimizedJSONMarshaling bool = true
var UseOptimizedJSONMarshalingV3 bool = true

View File

@@ -1,322 +0,0 @@
/*
Copyright 2022 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 openapiconv
import (
"strings"
klog "k8s.io/klog/v2"
builderutil "k8s.io/kube-openapi/pkg/builder3/util"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
)
var OpenAPIV2DefPrefix = "#/definitions/"
var OpenAPIV3DefPrefix = "#/components/schemas/"
// ConvertV2ToV3 converts an OpenAPI V2 object into V3.
// Certain references may be shared between the V2 and V3 objects in the conversion.
func ConvertV2ToV3(v2Spec *spec.Swagger) *spec3.OpenAPI {
v3Spec := &spec3.OpenAPI{
Version: "3.0.0",
Info: v2Spec.Info,
ExternalDocs: ConvertExternalDocumentation(v2Spec.ExternalDocs),
Paths: ConvertPaths(v2Spec.Paths),
Components: ConvertComponents(v2Spec.SecurityDefinitions, v2Spec.Definitions, v2Spec.Responses, v2Spec.Produces),
}
return v3Spec
}
func ConvertExternalDocumentation(v2ED *spec.ExternalDocumentation) *spec3.ExternalDocumentation {
if v2ED == nil {
return nil
}
return &spec3.ExternalDocumentation{
ExternalDocumentationProps: spec3.ExternalDocumentationProps{
Description: v2ED.Description,
URL: v2ED.URL,
},
}
}
func ConvertComponents(v2SecurityDefinitions spec.SecurityDefinitions, v2Definitions spec.Definitions, v2Responses map[string]spec.Response, produces []string) *spec3.Components {
components := &spec3.Components{}
if v2Definitions != nil {
components.Schemas = make(map[string]*spec.Schema)
}
for s, schema := range v2Definitions {
components.Schemas[s] = ConvertSchema(&schema)
}
if v2SecurityDefinitions != nil {
components.SecuritySchemes = make(spec3.SecuritySchemes)
}
for s, securityScheme := range v2SecurityDefinitions {
components.SecuritySchemes[s] = ConvertSecurityScheme(securityScheme)
}
if v2Responses != nil {
components.Responses = make(map[string]*spec3.Response)
}
for r, response := range v2Responses {
components.Responses[r] = ConvertResponse(&response, produces)
}
return components
}
func ConvertSchema(v2Schema *spec.Schema) *spec.Schema {
if v2Schema == nil {
return nil
}
v3Schema := spec.Schema{
VendorExtensible: v2Schema.VendorExtensible,
SchemaProps: v2Schema.SchemaProps,
SwaggerSchemaProps: v2Schema.SwaggerSchemaProps,
ExtraProps: v2Schema.ExtraProps,
}
if refString := v2Schema.Ref.String(); refString != "" {
if idx := strings.Index(refString, OpenAPIV2DefPrefix); idx != -1 {
v3Schema.Ref = spec.MustCreateRef(OpenAPIV3DefPrefix + refString[idx+len(OpenAPIV2DefPrefix):])
} else {
klog.Errorf("Error: Swagger V2 Ref %s does not contain #/definitions\n", refString)
}
}
if v2Schema.Properties != nil {
v3Schema.Properties = make(map[string]spec.Schema)
for key, property := range v2Schema.Properties {
v3Schema.Properties[key] = *ConvertSchema(&property)
}
}
if v2Schema.Items != nil {
v3Schema.Items = &spec.SchemaOrArray{
Schema: ConvertSchema(v2Schema.Items.Schema),
Schemas: ConvertSchemaList(v2Schema.Items.Schemas),
}
}
if v2Schema.AdditionalProperties != nil {
v3Schema.AdditionalProperties = &spec.SchemaOrBool{
Schema: ConvertSchema(v2Schema.AdditionalProperties.Schema),
Allows: v2Schema.AdditionalProperties.Allows,
}
}
if v2Schema.AdditionalItems != nil {
v3Schema.AdditionalItems = &spec.SchemaOrBool{
Schema: ConvertSchema(v2Schema.AdditionalItems.Schema),
Allows: v2Schema.AdditionalItems.Allows,
}
}
return builderutil.WrapRefs(&v3Schema)
}
func ConvertSchemaList(v2SchemaList []spec.Schema) []spec.Schema {
if v2SchemaList == nil {
return nil
}
v3SchemaList := []spec.Schema{}
for _, s := range v2SchemaList {
v3SchemaList = append(v3SchemaList, *ConvertSchema(&s))
}
return v3SchemaList
}
func ConvertSecurityScheme(v2securityScheme *spec.SecurityScheme) *spec3.SecurityScheme {
if v2securityScheme == nil {
return nil
}
securityScheme := &spec3.SecurityScheme{
VendorExtensible: v2securityScheme.VendorExtensible,
SecuritySchemeProps: spec3.SecuritySchemeProps{
Description: v2securityScheme.Description,
Type: v2securityScheme.Type,
Name: v2securityScheme.Name,
In: v2securityScheme.In,
},
}
if v2securityScheme.Flow != "" {
securityScheme.Flows = make(map[string]*spec3.OAuthFlow)
securityScheme.Flows[v2securityScheme.Flow] = &spec3.OAuthFlow{
OAuthFlowProps: spec3.OAuthFlowProps{
AuthorizationUrl: v2securityScheme.AuthorizationURL,
TokenUrl: v2securityScheme.TokenURL,
Scopes: v2securityScheme.Scopes,
},
}
}
return securityScheme
}
func ConvertPaths(v2Paths *spec.Paths) *spec3.Paths {
if v2Paths == nil {
return nil
}
paths := &spec3.Paths{
VendorExtensible: v2Paths.VendorExtensible,
}
if v2Paths.Paths != nil {
paths.Paths = make(map[string]*spec3.Path)
}
for k, v := range v2Paths.Paths {
paths.Paths[k] = ConvertPathItem(v)
}
return paths
}
func ConvertPathItem(v2pathItem spec.PathItem) *spec3.Path {
path := &spec3.Path{
Refable: v2pathItem.Refable,
PathProps: spec3.PathProps{
Get: ConvertOperation(v2pathItem.Get),
Put: ConvertOperation(v2pathItem.Put),
Post: ConvertOperation(v2pathItem.Post),
Delete: ConvertOperation(v2pathItem.Delete),
Options: ConvertOperation(v2pathItem.Options),
Head: ConvertOperation(v2pathItem.Head),
Patch: ConvertOperation(v2pathItem.Patch),
},
VendorExtensible: v2pathItem.VendorExtensible,
}
for _, param := range v2pathItem.Parameters {
path.Parameters = append(path.Parameters, ConvertParameter(param))
}
return path
}
func ConvertOperation(v2Operation *spec.Operation) *spec3.Operation {
if v2Operation == nil {
return nil
}
operation := &spec3.Operation{
VendorExtensible: v2Operation.VendorExtensible,
OperationProps: spec3.OperationProps{
Description: v2Operation.Description,
ExternalDocs: ConvertExternalDocumentation(v2Operation.OperationProps.ExternalDocs),
Tags: v2Operation.Tags,
Summary: v2Operation.Summary,
Deprecated: v2Operation.Deprecated,
OperationId: v2Operation.ID,
},
}
for _, param := range v2Operation.Parameters {
if param.ParamProps.Name == "body" && param.ParamProps.Schema != nil {
operation.OperationProps.RequestBody = &spec3.RequestBody{
RequestBodyProps: spec3.RequestBodyProps{},
}
if v2Operation.Consumes != nil {
operation.RequestBody.Content = make(map[string]*spec3.MediaType)
}
for _, consumer := range v2Operation.Consumes {
operation.RequestBody.Content[consumer] = &spec3.MediaType{
MediaTypeProps: spec3.MediaTypeProps{
Schema: ConvertSchema(param.ParamProps.Schema),
},
}
}
} else {
operation.Parameters = append(operation.Parameters, ConvertParameter(param))
}
}
operation.Responses = &spec3.Responses{ResponsesProps: spec3.ResponsesProps{
Default: ConvertResponse(v2Operation.Responses.Default, v2Operation.Produces),
},
VendorExtensible: v2Operation.Responses.VendorExtensible,
}
if v2Operation.Responses.StatusCodeResponses != nil {
operation.Responses.StatusCodeResponses = make(map[int]*spec3.Response)
}
for k, v := range v2Operation.Responses.StatusCodeResponses {
operation.Responses.StatusCodeResponses[k] = ConvertResponse(&v, v2Operation.Produces)
}
return operation
}
func ConvertResponse(v2Response *spec.Response, produces []string) *spec3.Response {
if v2Response == nil {
return nil
}
response := &spec3.Response{
Refable: ConvertRefableResponse(v2Response.Refable),
VendorExtensible: v2Response.VendorExtensible,
ResponseProps: spec3.ResponseProps{
Description: v2Response.Description,
},
}
if v2Response.Schema != nil {
if produces != nil {
response.Content = make(map[string]*spec3.MediaType)
}
for _, producer := range produces {
response.ResponseProps.Content[producer] = &spec3.MediaType{
MediaTypeProps: spec3.MediaTypeProps{
Schema: ConvertSchema(v2Response.Schema),
},
}
}
}
return response
}
func ConvertParameter(v2Param spec.Parameter) *spec3.Parameter {
param := &spec3.Parameter{
Refable: ConvertRefableParameter(v2Param.Refable),
VendorExtensible: v2Param.VendorExtensible,
ParameterProps: spec3.ParameterProps{
Name: v2Param.Name,
Description: v2Param.Description,
In: v2Param.In,
Required: v2Param.Required,
Schema: ConvertSchema(v2Param.Schema),
AllowEmptyValue: v2Param.AllowEmptyValue,
},
}
// Convert SimpleSchema into Schema
if param.Schema == nil {
param.Schema = &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{v2Param.Type},
Format: v2Param.Format,
UniqueItems: v2Param.UniqueItems,
},
}
}
return param
}
func ConvertRefableParameter(refable spec.Refable) spec.Refable {
if refable.Ref.String() != "" {
return spec.Refable{Ref: spec.MustCreateRef(strings.Replace(refable.Ref.String(), "#/parameters/", "#/components/parameters/", 1))}
}
return refable
}
func ConvertRefableResponse(refable spec.Refable) spec.Refable {
if refable.Ref.String() != "" {
return spec.Refable{Ref: spec.MustCreateRef(strings.Replace(refable.Ref.String(), "#/responses/", "#/components/responses/", 1))}
}
return refable
}

View File

@@ -1,519 +0,0 @@
/*
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 schemamutation
import (
"k8s.io/kube-openapi/pkg/validation/spec"
)
// Walker runs callback functions on all references of an OpenAPI spec,
// replacing the values when visiting corresponding types.
type Walker struct {
// SchemaCallback will be called on each schema, taking the original schema,
// and before any other callbacks of the Walker.
// If the schema needs to be mutated, DO NOT mutate it in-place,
// always create a copy, mutate, and return it.
SchemaCallback func(schema *spec.Schema) *spec.Schema
// RefCallback will be called on each ref.
// If the ref needs to be mutated, DO NOT mutate it in-place,
// always create a copy, mutate, and return it.
RefCallback func(ref *spec.Ref) *spec.Ref
}
type SchemaCallbackFunc func(schema *spec.Schema) *spec.Schema
type RefCallbackFunc func(ref *spec.Ref) *spec.Ref
var SchemaCallBackNoop SchemaCallbackFunc = func(schema *spec.Schema) *spec.Schema {
return schema
}
var RefCallbackNoop RefCallbackFunc = func(ref *spec.Ref) *spec.Ref {
return ref
}
// ReplaceReferences rewrites the references without mutating the input.
// The output might share data with the input.
func ReplaceReferences(walkRef func(ref *spec.Ref) *spec.Ref, sp *spec.Swagger) *spec.Swagger {
walker := &Walker{RefCallback: walkRef, SchemaCallback: SchemaCallBackNoop}
return walker.WalkRoot(sp)
}
func (w *Walker) WalkSchema(schema *spec.Schema) *spec.Schema {
if schema == nil {
return nil
}
orig := schema
clone := func() {
if orig == schema {
schema = &spec.Schema{}
*schema = *orig
}
}
// Always run callback on the whole schema first
// so that SchemaCallback can take the original schema as input.
schema = w.SchemaCallback(schema)
if r := w.RefCallback(&schema.Ref); r != &schema.Ref {
clone()
schema.Ref = *r
}
definitionsCloned := false
for k, v := range schema.Definitions {
if s := w.WalkSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
clone()
schema.Definitions = make(spec.Definitions, len(orig.Definitions))
for k2, v2 := range orig.Definitions {
schema.Definitions[k2] = v2
}
}
schema.Definitions[k] = *s
}
}
propertiesCloned := false
for k, v := range schema.Properties {
if s := w.WalkSchema(&v); s != &v {
if !propertiesCloned {
propertiesCloned = true
clone()
schema.Properties = make(map[string]spec.Schema, len(orig.Properties))
for k2, v2 := range orig.Properties {
schema.Properties[k2] = v2
}
}
schema.Properties[k] = *s
}
}
patternPropertiesCloned := false
for k, v := range schema.PatternProperties {
if s := w.WalkSchema(&v); s != &v {
if !patternPropertiesCloned {
patternPropertiesCloned = true
clone()
schema.PatternProperties = make(map[string]spec.Schema, len(orig.PatternProperties))
for k2, v2 := range orig.PatternProperties {
schema.PatternProperties[k2] = v2
}
}
schema.PatternProperties[k] = *s
}
}
allOfCloned := false
for i := range schema.AllOf {
if s := w.WalkSchema(&schema.AllOf[i]); s != &schema.AllOf[i] {
if !allOfCloned {
allOfCloned = true
clone()
schema.AllOf = make([]spec.Schema, len(orig.AllOf))
copy(schema.AllOf, orig.AllOf)
}
schema.AllOf[i] = *s
}
}
anyOfCloned := false
for i := range schema.AnyOf {
if s := w.WalkSchema(&schema.AnyOf[i]); s != &schema.AnyOf[i] {
if !anyOfCloned {
anyOfCloned = true
clone()
schema.AnyOf = make([]spec.Schema, len(orig.AnyOf))
copy(schema.AnyOf, orig.AnyOf)
}
schema.AnyOf[i] = *s
}
}
oneOfCloned := false
for i := range schema.OneOf {
if s := w.WalkSchema(&schema.OneOf[i]); s != &schema.OneOf[i] {
if !oneOfCloned {
oneOfCloned = true
clone()
schema.OneOf = make([]spec.Schema, len(orig.OneOf))
copy(schema.OneOf, orig.OneOf)
}
schema.OneOf[i] = *s
}
}
if schema.Not != nil {
if s := w.WalkSchema(schema.Not); s != schema.Not {
clone()
schema.Not = s
}
}
if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil {
if s := w.WalkSchema(schema.AdditionalProperties.Schema); s != schema.AdditionalProperties.Schema {
clone()
schema.AdditionalProperties = &spec.SchemaOrBool{Schema: s, Allows: schema.AdditionalProperties.Allows}
}
}
if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil {
if s := w.WalkSchema(schema.AdditionalItems.Schema); s != schema.AdditionalItems.Schema {
clone()
schema.AdditionalItems = &spec.SchemaOrBool{Schema: s, Allows: schema.AdditionalItems.Allows}
}
}
if schema.Items != nil {
if schema.Items.Schema != nil {
if s := w.WalkSchema(schema.Items.Schema); s != schema.Items.Schema {
clone()
schema.Items = &spec.SchemaOrArray{Schema: s}
}
} else {
itemsCloned := false
for i := range schema.Items.Schemas {
if s := w.WalkSchema(&schema.Items.Schemas[i]); s != &schema.Items.Schemas[i] {
if !itemsCloned {
clone()
schema.Items = &spec.SchemaOrArray{
Schemas: make([]spec.Schema, len(orig.Items.Schemas)),
}
itemsCloned = true
copy(schema.Items.Schemas, orig.Items.Schemas)
}
schema.Items.Schemas[i] = *s
}
}
}
}
return schema
}
func (w *Walker) walkParameter(param *spec.Parameter) *spec.Parameter {
if param == nil {
return nil
}
orig := param
cloned := false
clone := func() {
if !cloned {
cloned = true
param = &spec.Parameter{}
*param = *orig
}
}
if r := w.RefCallback(&param.Ref); r != &param.Ref {
clone()
param.Ref = *r
}
if s := w.WalkSchema(param.Schema); s != param.Schema {
clone()
param.Schema = s
}
if param.Items != nil {
if r := w.RefCallback(&param.Items.Ref); r != &param.Items.Ref {
param.Items.Ref = *r
}
}
return param
}
func (w *Walker) walkParameters(params []spec.Parameter) ([]spec.Parameter, bool) {
if params == nil {
return nil, false
}
orig := params
cloned := false
clone := func() {
if !cloned {
cloned = true
params = make([]spec.Parameter, len(params))
copy(params, orig)
}
}
for i := range params {
if s := w.walkParameter(&params[i]); s != &params[i] {
clone()
params[i] = *s
}
}
return params, cloned
}
func (w *Walker) walkResponse(resp *spec.Response) *spec.Response {
if resp == nil {
return nil
}
orig := resp
cloned := false
clone := func() {
if !cloned {
cloned = true
resp = &spec.Response{}
*resp = *orig
}
}
if r := w.RefCallback(&resp.Ref); r != &resp.Ref {
clone()
resp.Ref = *r
}
if s := w.WalkSchema(resp.Schema); s != resp.Schema {
clone()
resp.Schema = s
}
return resp
}
func (w *Walker) walkResponses(resps *spec.Responses) *spec.Responses {
if resps == nil {
return nil
}
orig := resps
cloned := false
clone := func() {
if !cloned {
cloned = true
resps = &spec.Responses{}
*resps = *orig
}
}
if r := w.walkResponse(resps.ResponsesProps.Default); r != resps.ResponsesProps.Default {
clone()
resps.Default = r
}
responsesCloned := false
for k, v := range resps.ResponsesProps.StatusCodeResponses {
if r := w.walkResponse(&v); r != &v {
if !responsesCloned {
responsesCloned = true
clone()
resps.ResponsesProps.StatusCodeResponses = make(map[int]spec.Response, len(orig.StatusCodeResponses))
for k2, v2 := range orig.StatusCodeResponses {
resps.ResponsesProps.StatusCodeResponses[k2] = v2
}
}
resps.ResponsesProps.StatusCodeResponses[k] = *r
}
}
return resps
}
func (w *Walker) walkOperation(op *spec.Operation) *spec.Operation {
if op == nil {
return nil
}
orig := op
cloned := false
clone := func() {
if !cloned {
cloned = true
op = &spec.Operation{}
*op = *orig
}
}
parametersCloned := false
for i := range op.Parameters {
if s := w.walkParameter(&op.Parameters[i]); s != &op.Parameters[i] {
if !parametersCloned {
parametersCloned = true
clone()
op.Parameters = make([]spec.Parameter, len(orig.Parameters))
copy(op.Parameters, orig.Parameters)
}
op.Parameters[i] = *s
}
}
if r := w.walkResponses(op.Responses); r != op.Responses {
clone()
op.Responses = r
}
return op
}
func (w *Walker) walkPathItem(pathItem *spec.PathItem) *spec.PathItem {
if pathItem == nil {
return nil
}
orig := pathItem
cloned := false
clone := func() {
if !cloned {
cloned = true
pathItem = &spec.PathItem{}
*pathItem = *orig
}
}
if p, changed := w.walkParameters(pathItem.Parameters); changed {
clone()
pathItem.Parameters = p
}
if op := w.walkOperation(pathItem.Get); op != pathItem.Get {
clone()
pathItem.Get = op
}
if op := w.walkOperation(pathItem.Head); op != pathItem.Head {
clone()
pathItem.Head = op
}
if op := w.walkOperation(pathItem.Delete); op != pathItem.Delete {
clone()
pathItem.Delete = op
}
if op := w.walkOperation(pathItem.Options); op != pathItem.Options {
clone()
pathItem.Options = op
}
if op := w.walkOperation(pathItem.Patch); op != pathItem.Patch {
clone()
pathItem.Patch = op
}
if op := w.walkOperation(pathItem.Post); op != pathItem.Post {
clone()
pathItem.Post = op
}
if op := w.walkOperation(pathItem.Put); op != pathItem.Put {
clone()
pathItem.Put = op
}
return pathItem
}
func (w *Walker) walkPaths(paths *spec.Paths) *spec.Paths {
if paths == nil {
return nil
}
orig := paths
cloned := false
clone := func() {
if !cloned {
cloned = true
paths = &spec.Paths{}
*paths = *orig
}
}
pathsCloned := false
for k, v := range paths.Paths {
if p := w.walkPathItem(&v); p != &v {
if !pathsCloned {
pathsCloned = true
clone()
paths.Paths = make(map[string]spec.PathItem, len(orig.Paths))
for k2, v2 := range orig.Paths {
paths.Paths[k2] = v2
}
}
paths.Paths[k] = *p
}
}
return paths
}
func (w *Walker) WalkRoot(swagger *spec.Swagger) *spec.Swagger {
if swagger == nil {
return nil
}
orig := swagger
cloned := false
clone := func() {
if !cloned {
cloned = true
swagger = &spec.Swagger{}
*swagger = *orig
}
}
parametersCloned := false
for k, v := range swagger.Parameters {
if p := w.walkParameter(&v); p != &v {
if !parametersCloned {
parametersCloned = true
clone()
swagger.Parameters = make(map[string]spec.Parameter, len(orig.Parameters))
for k2, v2 := range orig.Parameters {
swagger.Parameters[k2] = v2
}
}
swagger.Parameters[k] = *p
}
}
responsesCloned := false
for k, v := range swagger.Responses {
if r := w.walkResponse(&v); r != &v {
if !responsesCloned {
responsesCloned = true
clone()
swagger.Responses = make(map[string]spec.Response, len(orig.Responses))
for k2, v2 := range orig.Responses {
swagger.Responses[k2] = v2
}
}
swagger.Responses[k] = *r
}
}
definitionsCloned := false
for k, v := range swagger.Definitions {
if s := w.WalkSchema(&v); s != &v {
if !definitionsCloned {
definitionsCloned = true
clone()
swagger.Definitions = make(spec.Definitions, len(orig.Definitions))
for k2, v2 := range orig.Definitions {
swagger.Definitions[k2] = v2
}
}
swagger.Definitions[k] = *s
}
}
if swagger.Paths != nil {
if p := w.walkPaths(swagger.Paths); p != swagger.Paths {
clone()
swagger.Paths = p
}
}
return swagger
}

View File

@@ -32,6 +32,9 @@ type Encoding struct {
// MarshalJSON is a custom marshal function that knows how to encode Encoding as JSON
func (e *Encoding) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.EncodingProps)
if err != nil {
return nil, err
@@ -43,6 +46,16 @@ func (e *Encoding) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (e *Encoding) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
EncodingProps encodingPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.EncodingProps = encodingPropsOmitZero(e.EncodingProps)
return opts.MarshalNext(enc, x)
}
func (e *Encoding) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)
@@ -82,3 +95,11 @@ type EncodingProps struct {
// AllowReserved determines whether the parameter value SHOULD allow reserved characters, as defined by RFC3986
AllowReserved bool `json:"allowReserved,omitempty"`
}
type encodingPropsOmitZero struct {
ContentType string `json:"contentType,omitempty"`
Headers map[string]*Header `json:"headers,omitempty"`
Style string `json:"style,omitempty"`
Explode bool `json:"explode,omitzero"`
AllowReserved bool `json:"allowReserved,omitzero"`
}

View File

@@ -36,6 +36,9 @@ type Example struct {
// MarshalJSON is a custom marshal function that knows how to encode RequestBody as JSON
func (e *Example) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.Refable)
if err != nil {
return nil, err
@@ -50,6 +53,17 @@ func (e *Example) MarshalJSON() ([]byte, error) {
}
return swag.ConcatJSON(b1, b2, b3), nil
}
func (e *Example) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
ExampleProps `json:",inline"`
spec.Extensions
}
x.Ref = e.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.ExampleProps = e.ExampleProps
return opts.MarshalNext(enc, x)
}
func (e *Example) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {

View File

@@ -39,6 +39,9 @@ type ExternalDocumentationProps struct {
// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (e *ExternalDocumentation) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(e)
}
b1, err := json.Marshal(e.ExternalDocumentationProps)
if err != nil {
return nil, err
@@ -50,6 +53,16 @@ func (e *ExternalDocumentation) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (e *ExternalDocumentation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
ExternalDocumentationProps `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.ExternalDocumentationProps = e.ExternalDocumentationProps
return opts.MarshalNext(enc, x)
}
func (e *ExternalDocumentation) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, e)

View File

@@ -35,6 +35,18 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
func(o *OpenAPI, c fuzz.Continue) {
c.FuzzNoCustom(o)
o.Version = "3.0.0"
for i, val := range o.SecurityRequirement {
if val == nil {
o.SecurityRequirement[i] = make(map[string][]string)
}
for k, v := range val {
if v == nil {
val[k] = make([]string, 0)
}
}
}
},
func(r *interface{}, c fuzz.Continue) {
switch c.Intn(3) {
@@ -169,6 +181,21 @@ var OpenAPIV3FuzzFuncs []interface{} = []interface{}{
c.Fuzz(&v.ResponseProps)
c.Fuzz(&v.VendorExtensible)
},
func(v *Operation, c fuzz.Continue) {
c.FuzzNoCustom(v)
// Do not fuzz null values into the array.
for i, val := range v.SecurityRequirement {
if val == nil {
v.SecurityRequirement[i] = make(map[string][]string)
}
for k, v := range val {
if v == nil {
val[k] = make([]string, 0)
}
}
}
},
func(v *spec.Extensions, c fuzz.Continue) {
numChildren := c.Intn(5)
for i := 0; i < numChildren; i++ {

View File

@@ -36,6 +36,9 @@ type Header struct {
// MarshalJSON is a custom marshal function that knows how to encode Header as JSON
func (h *Header) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(h)
}
b1, err := json.Marshal(h.Refable)
if err != nil {
return nil, err
@@ -51,6 +54,18 @@ func (h *Header) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (h *Header) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
HeaderProps headerPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Ref = h.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(h.Extensions)
x.HeaderProps = headerPropsOmitZero(h.HeaderProps)
return opts.MarshalNext(enc, x)
}
func (h *Header) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, h)
@@ -109,3 +124,19 @@ type HeaderProps struct {
// Examples of the header
Examples map[string]*Example `json:"examples,omitempty"`
}
// Marshaling structure only, always edit along with corresponding
// struct (or compilation will fail).
type headerPropsOmitZero struct {
Description string `json:"description,omitempty"`
Required bool `json:"required,omitzero"`
Deprecated bool `json:"deprecated,omitzero"`
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
Style string `json:"style,omitempty"`
Explode bool `json:"explode,omitzero"`
AllowReserved bool `json:"allowReserved,omitzero"`
Schema *spec.Schema `json:"schema,omitzero"`
Content map[string]*MediaType `json:"content,omitempty"`
Example interface{} `json:"example,omitempty"`
Examples map[string]*Example `json:"examples,omitempty"`
}

View File

@@ -35,6 +35,9 @@ type MediaType struct {
// MarshalJSON is a custom marshal function that knows how to encode MediaType as JSON
func (m *MediaType) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(m)
}
b1, err := json.Marshal(m.MediaTypeProps)
if err != nil {
return nil, err
@@ -46,6 +49,16 @@ func (m *MediaType) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (e *MediaType) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
MediaTypeProps mediaTypePropsOmitZero `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(e.Extensions)
x.MediaTypeProps = mediaTypePropsOmitZero(e.MediaTypeProps)
return opts.MarshalNext(enc, x)
}
func (m *MediaType) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, m)
@@ -84,3 +97,10 @@ type MediaTypeProps struct {
// A map between a property name and its encoding information. The key, being the property name, MUST exist in the schema as a property. The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded
Encoding map[string]*Encoding `json:"encoding,omitempty"`
}
type mediaTypePropsOmitZero struct {
Schema *spec.Schema `json:"schema,omitzero"`
Example interface{} `json:"example,omitempty"`
Examples map[string]*Example `json:"examples,omitempty"`
Encoding map[string]*Encoding `json:"encoding,omitempty"`
}

View File

@@ -35,6 +35,9 @@ type Operation struct {
// MarshalJSON is a custom marshal function that knows how to encode Operation as JSON
func (o *Operation) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(o)
}
b1, err := json.Marshal(o.OperationProps)
if err != nil {
return nil, err
@@ -46,6 +49,16 @@ func (o *Operation) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (o *Operation) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
spec.Extensions
OperationProps operationPropsOmitZero `json:",inline"`
}
x.Extensions = internal.SanitizeExtensions(o.Extensions)
x.OperationProps = operationPropsOmitZero(o.OperationProps)
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON hydrates this items instance with the data from JSON
func (o *Operation) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
@@ -95,3 +108,17 @@ type OperationProps struct {
// Servers contains an alternative server array to service this operation
Servers []*Server `json:"servers,omitempty"`
}
type operationPropsOmitZero struct {
Tags []string `json:"tags,omitempty"`
Summary string `json:"summary,omitempty"`
Description string `json:"description,omitempty"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
OperationId string `json:"operationId,omitempty"`
Parameters []*Parameter `json:"parameters,omitempty"`
RequestBody *RequestBody `json:"requestBody,omitzero"`
Responses *Responses `json:"responses,omitzero"`
Deprecated bool `json:"deprecated,omitzero"`
SecurityRequirement []map[string][]string `json:"security,omitempty"`
Servers []*Server `json:"servers,omitempty"`
}

View File

@@ -36,6 +36,9 @@ type Parameter struct {
// MarshalJSON is a custom marshal function that knows how to encode Parameter as JSON
func (p *Parameter) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.Refable)
if err != nil {
return nil, err
@@ -51,6 +54,18 @@ func (p *Parameter) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (p *Parameter) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
ParameterProps parameterPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Ref = p.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(p.Extensions)
x.ParameterProps = parameterPropsOmitZero(p.ParameterProps)
return opts.MarshalNext(enc, x)
}
func (p *Parameter) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, p)
@@ -114,3 +129,19 @@ type ParameterProps struct {
// Examples of the parameter's potential value. Each example SHOULD contain a value in the correct format as specified in the parameter encoding
Examples map[string]*Example `json:"examples,omitempty"`
}
type parameterPropsOmitZero struct {
Name string `json:"name,omitempty"`
In string `json:"in,omitempty"`
Description string `json:"description,omitempty"`
Required bool `json:"required,omitzero"`
Deprecated bool `json:"deprecated,omitzero"`
AllowEmptyValue bool `json:"allowEmptyValue,omitzero"`
Style string `json:"style,omitempty"`
Explode bool `json:"explode,omitzero"`
AllowReserved bool `json:"allowReserved,omitzero"`
Schema *spec.Schema `json:"schema,omitzero"`
Content map[string]*MediaType `json:"content,omitempty"`
Example interface{} `json:"example,omitempty"`
Examples map[string]*Example `json:"examples,omitempty"`
}

View File

@@ -35,15 +35,41 @@ type Paths struct {
// MarshalJSON is a custom marshal function that knows how to encode Paths as JSON
func (p *Paths) MarshalJSON() ([]byte, error) {
b1, err := json.Marshal(p.Paths)
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.VendorExtensible)
if err != nil {
return nil, err
}
b2, err := json.Marshal(p.VendorExtensible)
pths := make(map[string]*Path)
for k, v := range p.Paths {
if strings.HasPrefix(k, "/") {
pths[k] = v
}
}
b2, err := json.Marshal(pths)
if err != nil {
return nil, err
}
return swag.ConcatJSON(b1, b2), nil
concated := swag.ConcatJSON(b1, b2)
return concated, nil
}
func (p *Paths) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
m := make(map[string]any, len(p.Extensions)+len(p.Paths))
for k, v := range p.Extensions {
if internal.IsExtensionKey(k) {
m[k] = v
}
}
for k, v := range p.Paths {
if strings.HasPrefix(k, "/") {
m[k] = v
}
}
return opts.MarshalNext(enc, m)
}
// UnmarshalJSON hydrates this items instance with the data from JSON
@@ -144,6 +170,9 @@ type Path struct {
// MarshalJSON is a custom marshal function that knows how to encode Path as JSON
func (p *Path) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(p)
}
b1, err := json.Marshal(p.Refable)
if err != nil {
return nil, err
@@ -159,6 +188,18 @@ func (p *Path) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (p *Path) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
spec.Extensions
PathProps
}
x.Ref = p.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(p.Extensions)
x.PathProps = p.PathProps
return opts.MarshalNext(enc, x)
}
func (p *Path) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, p)

View File

@@ -36,6 +36,9 @@ type RequestBody struct {
// MarshalJSON is a custom marshal function that knows how to encode RequestBody as JSON
func (r *RequestBody) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.Refable)
if err != nil {
return nil, err
@@ -51,6 +54,18 @@ func (r *RequestBody) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (r *RequestBody) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
RequestBodyProps requestBodyPropsOmitZero `json:",inline"`
spec.Extensions
}
x.Ref = r.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(r.Extensions)
x.RequestBodyProps = requestBodyPropsOmitZero(r.RequestBodyProps)
return opts.MarshalNext(enc, x)
}
func (r *RequestBody) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
@@ -77,6 +92,12 @@ type RequestBodyProps struct {
Required bool `json:"required,omitempty"`
}
type requestBodyPropsOmitZero struct {
Description string `json:"description,omitempty"`
Content map[string]*MediaType `json:"content,omitempty"`
Required bool `json:"required,omitzero"`
}
func (r *RequestBody) UnmarshalNextJSON(opts jsonv2.UnmarshalOptions, dec *jsonv2.Decoder) error {
var x struct {
spec.Extensions

View File

@@ -37,6 +37,9 @@ type Responses struct {
// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (r *Responses) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.ResponsesProps)
if err != nil {
return nil, err
@@ -48,6 +51,25 @@ func (r *Responses) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (r Responses) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type ArbitraryKeys map[string]interface{}
var x struct {
ArbitraryKeys
Default *Response `json:"default,omitzero"`
}
x.ArbitraryKeys = make(map[string]any, len(r.Extensions)+len(r.StatusCodeResponses))
for k, v := range r.Extensions {
if internal.IsExtensionKey(k) {
x.ArbitraryKeys[k] = v
}
}
for k, v := range r.StatusCodeResponses {
x.ArbitraryKeys[strconv.Itoa(k)] = v
}
x.Default = r.Default
return opts.MarshalNext(enc, x)
}
func (r *Responses) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
@@ -179,6 +201,9 @@ type Response struct {
// MarshalJSON is a custom marshal function that knows how to encode Response as JSON
func (r *Response) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.Refable)
if err != nil {
return nil, err
@@ -194,6 +219,18 @@ func (r *Response) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (r Response) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
spec.Extensions
ResponseProps `json:",inline"`
}
x.Ref = r.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(r.Extensions)
x.ResponseProps = r.ResponseProps
return opts.MarshalNext(enc, x)
}
func (r *Response) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)
@@ -247,6 +284,9 @@ type Link struct {
// MarshalJSON is a custom marshal function that knows how to encode Link as JSON
func (r *Link) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(r)
}
b1, err := json.Marshal(r.Refable)
if err != nil {
return nil, err
@@ -262,6 +302,18 @@ func (r *Link) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (r *Link) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
spec.Extensions
LinkProps `json:",inline"`
}
x.Ref = r.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(r.Extensions)
x.LinkProps = r.LinkProps
return opts.MarshalNext(enc, x)
}
func (r *Link) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, r)

View File

@@ -20,6 +20,8 @@ import (
"encoding/json"
"github.com/go-openapi/swag"
"k8s.io/kube-openapi/pkg/internal"
jsonv2 "k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json"
"k8s.io/kube-openapi/pkg/validation/spec"
)
@@ -32,6 +34,9 @@ type SecurityScheme struct {
// MarshalJSON is a custom marshal function that knows how to encode SecurityScheme as JSON
func (s *SecurityScheme) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.SecuritySchemeProps)
if err != nil {
return nil, err
@@ -47,6 +52,18 @@ func (s *SecurityScheme) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2, b3), nil
}
func (s *SecurityScheme) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
Ref string `json:"$ref,omitempty"`
SecuritySchemeProps `json:",inline"`
spec.Extensions
}
x.Ref = s.Refable.Ref.String()
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.SecuritySchemeProps = s.SecuritySchemeProps
return opts.MarshalNext(enc, x)
}
// UnmarshalJSON hydrates this items instance with the data from JSON
func (s *SecurityScheme) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &s.SecuritySchemeProps); err != nil {

View File

@@ -41,6 +41,9 @@ type ServerProps struct {
// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (s *Server) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.ServerProps)
if err != nil {
return nil, err
@@ -52,6 +55,16 @@ func (s *Server) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (s *Server) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
ServerProps `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.ServerProps = s.ServerProps
return opts.MarshalNext(enc, x)
}
func (s *Server) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, s)
@@ -96,6 +109,9 @@ type ServerVariableProps struct {
// MarshalJSON is a custom marshal function that knows how to encode Responses as JSON
func (s *ServerVariable) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(s)
}
b1, err := json.Marshal(s.ServerVariableProps)
if err != nil {
return nil, err
@@ -107,6 +123,16 @@ func (s *ServerVariable) MarshalJSON() ([]byte, error) {
return swag.ConcatJSON(b1, b2), nil
}
func (s *ServerVariable) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
var x struct {
ServerVariableProps `json:",inline"`
spec.Extensions
}
x.Extensions = internal.SanitizeExtensions(s.Extensions)
x.ServerVariableProps = s.ServerVariableProps
return opts.MarshalNext(enc, x)
}
func (s *ServerVariable) UnmarshalJSON(data []byte) error {
if internal.UseOptimizedJSONUnmarshalingV3 {
return jsonv2.Unmarshal(data, s)

View File

@@ -36,6 +36,8 @@ type OpenAPI struct {
Servers []*Server `json:"servers,omitempty"`
// Components hold various schemas for the specification
Components *Components `json:"components,omitempty"`
// SecurityRequirement holds a declaration of which security mechanisms can be used across the API
SecurityRequirement []map[string][]string `json:"security,omitempty"`
// ExternalDocs holds additional external documentation
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
}
@@ -48,3 +50,26 @@ func (o *OpenAPI) UnmarshalJSON(data []byte) error {
}
return json.Unmarshal(data, &p)
}
func (o *OpenAPI) MarshalJSON() ([]byte, error) {
if internal.UseOptimizedJSONMarshalingV3 {
return internal.DeterministicMarshal(o)
}
type OpenAPIWithNoFunctions OpenAPI
p := (*OpenAPIWithNoFunctions)(o)
return json.Marshal(&p)
}
func (o *OpenAPI) MarshalNextJSON(opts jsonv2.MarshalOptions, enc *jsonv2.Encoder) error {
type OpenAPIOmitZero struct {
Version string `json:"openapi"`
Info *spec.Info `json:"info"`
Paths *Paths `json:"paths,omitzero"`
Servers []*Server `json:"servers,omitempty"`
Components *Components `json:"components,omitzero"`
SecurityRequirement []map[string][]string `json:"security,omitempty"`
ExternalDocs *ExternalDocumentation `json:"externalDocs,omitzero"`
}
x := (*OpenAPIOmitZero)(o)
return opts.MarshalNext(enc, x)
}

View File

@@ -21,7 +21,7 @@ import (
"sort"
"strings"
openapi_v2 "github.com/google/gnostic/openapiv2"
openapi_v2 "github.com/google/gnostic-models/openapiv2"
"gopkg.in/yaml.v2"
)

View File

@@ -21,7 +21,7 @@ import (
"reflect"
"strings"
openapi_v3 "github.com/google/gnostic/openapiv3"
openapi_v3 "github.com/google/gnostic-models/openapiv3"
"gopkg.in/yaml.v3"
)

View File

@@ -1,502 +0,0 @@
/*
Copyright 2022 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 spec
import (
"github.com/go-openapi/jsonreference"
"github.com/google/go-cmp/cmp"
fuzz "github.com/google/gofuzz"
)
var SwaggerFuzzFuncs []interface{} = []interface{}{
func(v *Responses, c fuzz.Continue) {
c.FuzzNoCustom(v)
if v.Default != nil {
// Check if we hit maxDepth and left an incomplete value
if v.Default.Description == "" {
v.Default = nil
v.StatusCodeResponses = nil
}
}
// conversion has no way to discern empty statusCodeResponses from
// nil, since "default" is always included in the map.
// So avoid empty responses list
if len(v.StatusCodeResponses) == 0 {
v.StatusCodeResponses = nil
}
},
func(v *Operation, c fuzz.Continue) {
c.FuzzNoCustom(v)
if v != nil {
// force non-nil
v.Responses = &Responses{}
c.Fuzz(v.Responses)
v.Schemes = nil
if c.RandBool() {
v.Schemes = append(v.Schemes, "http")
}
if c.RandBool() {
v.Schemes = append(v.Schemes, "https")
}
if c.RandBool() {
v.Schemes = append(v.Schemes, "ws")
}
if c.RandBool() {
v.Schemes = append(v.Schemes, "wss")
}
// Gnostic unconditionally makes security values non-null
// So do not fuzz null values into the array.
for i, val := range v.Security {
if val == nil {
v.Security[i] = make(map[string][]string)
}
for k, v := range val {
if v == nil {
val[k] = make([]string, 0)
}
}
}
}
},
func(v map[int]Response, c fuzz.Continue) {
n := 0
c.Fuzz(&n)
if n == 0 {
// Test that fuzzer is not at maxDepth so we do not
// end up with empty elements
return
}
// Prevent negative numbers
num := c.Intn(4)
for i := 0; i < num+2; i++ {
val := Response{}
c.Fuzz(&val)
val.Description = c.RandString() + "x"
v[100*(i+1)+c.Intn(100)] = val
}
},
func(v map[string]PathItem, c fuzz.Continue) {
n := 0
c.Fuzz(&n)
if n == 0 {
// Test that fuzzer is not at maxDepth so we do not
// end up with empty elements
return
}
num := c.Intn(5)
for i := 0; i < num+2; i++ {
val := PathItem{}
c.Fuzz(&val)
// Ref params are only allowed in certain locations, so
// possibly add a few to PathItems
numRefsToAdd := c.Intn(5)
for i := 0; i < numRefsToAdd; i++ {
theRef := Parameter{}
c.Fuzz(&theRef.Refable)
val.Parameters = append(val.Parameters, theRef)
}
v["/"+c.RandString()] = val
}
},
func(v *SchemaOrArray, c fuzz.Continue) {
*v = SchemaOrArray{}
// gnostic parser just doesn't support more
// than one Schema here
v.Schema = &Schema{}
c.Fuzz(&v.Schema)
},
func(v *SchemaOrBool, c fuzz.Continue) {
*v = SchemaOrBool{}
if c.RandBool() {
v.Allows = c.RandBool()
} else {
v.Schema = &Schema{}
v.Allows = true
c.Fuzz(&v.Schema)
}
},
func(v map[string]Response, c fuzz.Continue) {
n := 0
c.Fuzz(&n)
if n == 0 {
// Test that fuzzer is not at maxDepth so we do not
// end up with empty elements
return
}
// Response definitions are not allowed to
// be refs
for i := 0; i < c.Intn(5)+1; i++ {
resp := &Response{}
c.Fuzz(resp)
resp.Ref = Ref{}
resp.Description = c.RandString() + "x"
// Response refs are not vendor extensible by gnostic
resp.VendorExtensible.Extensions = nil
v[c.RandString()+"x"] = *resp
}
},
func(v *Header, c fuzz.Continue) {
if v != nil {
c.FuzzNoCustom(v)
// descendant Items of Header may not be refs
cur := v.Items
for cur != nil {
cur.Ref = Ref{}
cur = cur.Items
}
}
},
func(v *Ref, c fuzz.Continue) {
*v = Ref{}
v.Ref, _ = jsonreference.New("http://asd.com/" + c.RandString())
},
func(v *Response, c fuzz.Continue) {
*v = Response{}
if c.RandBool() {
v.Ref = Ref{}
v.Ref.Ref, _ = jsonreference.New("http://asd.com/" + c.RandString())
} else {
c.Fuzz(&v.VendorExtensible)
c.Fuzz(&v.Schema)
c.Fuzz(&v.ResponseProps)
v.Headers = nil
v.Ref = Ref{}
n := 0
c.Fuzz(&n)
if n != 0 {
// Test that fuzzer is not at maxDepth so we do not
// end up with empty elements
num := c.Intn(4)
for i := 0; i < num; i++ {
if v.Headers == nil {
v.Headers = make(map[string]Header)
}
hdr := Header{}
c.Fuzz(&hdr)
if hdr.Type == "" {
// hit maxDepth, just abort trying to make haders
v.Headers = nil
break
}
v.Headers[c.RandString()+"x"] = hdr
}
} else {
v.Headers = nil
}
}
v.Description = c.RandString() + "x"
// Gnostic parses empty as nil, so to keep avoid putting empty
if len(v.Headers) == 0 {
v.Headers = nil
}
},
func(v **Info, c fuzz.Continue) {
// Info is never nil
*v = &Info{}
c.FuzzNoCustom(*v)
(*v).Title = c.RandString() + "x"
},
func(v *Extensions, c fuzz.Continue) {
// gnostic parser only picks up x- vendor extensions
numChildren := c.Intn(5)
for i := 0; i < numChildren; i++ {
if *v == nil {
*v = Extensions{}
}
(*v)["x-"+c.RandString()] = c.RandString()
}
},
func(v *Swagger, c fuzz.Continue) {
c.FuzzNoCustom(v)
if v.Paths == nil {
// Force paths non-nil since it does not have omitempty in json tag.
// This means a perfect roundtrip (via json) is impossible,
// since we can't tell the difference between empty/unspecified paths
v.Paths = &Paths{}
c.Fuzz(v.Paths)
}
v.Swagger = "2.0"
// Gnostic support serializing ID at all
// unavoidable data loss
v.ID = ""
v.Schemes = nil
if c.RandUint64()%2 == 1 {
v.Schemes = append(v.Schemes, "http")
}
if c.RandUint64()%2 == 1 {
v.Schemes = append(v.Schemes, "https")
}
if c.RandUint64()%2 == 1 {
v.Schemes = append(v.Schemes, "ws")
}
if c.RandUint64()%2 == 1 {
v.Schemes = append(v.Schemes, "wss")
}
// Gnostic unconditionally makes security values non-null
// So do not fuzz null values into the array.
for i, val := range v.Security {
if val == nil {
v.Security[i] = make(map[string][]string)
}
for k, v := range val {
if v == nil {
val[k] = make([]string, 0)
}
}
}
},
func(v *SecurityScheme, c fuzz.Continue) {
v.Description = c.RandString() + "x"
c.Fuzz(&v.VendorExtensible)
switch c.Intn(3) {
case 0:
v.Type = "basic"
case 1:
v.Type = "apiKey"
switch c.Intn(2) {
case 0:
v.In = "header"
case 1:
v.In = "query"
default:
panic("unreachable")
}
v.Name = "x" + c.RandString()
case 2:
v.Type = "oauth2"
switch c.Intn(4) {
case 0:
v.Flow = "accessCode"
v.TokenURL = "https://" + c.RandString()
v.AuthorizationURL = "https://" + c.RandString()
case 1:
v.Flow = "application"
v.TokenURL = "https://" + c.RandString()
case 2:
v.Flow = "implicit"
v.AuthorizationURL = "https://" + c.RandString()
case 3:
v.Flow = "password"
v.TokenURL = "https://" + c.RandString()
default:
panic("unreachable")
}
c.Fuzz(&v.Scopes)
default:
panic("unreachable")
}
},
func(v *interface{}, c fuzz.Continue) {
*v = c.RandString() + "x"
},
func(v *string, c fuzz.Continue) {
*v = c.RandString() + "x"
},
func(v *ExternalDocumentation, c fuzz.Continue) {
v.Description = c.RandString() + "x"
v.URL = c.RandString() + "x"
},
func(v *SimpleSchema, c fuzz.Continue) {
c.FuzzNoCustom(v)
switch c.Intn(5) {
case 0:
v.Type = "string"
case 1:
v.Type = "number"
case 2:
v.Type = "boolean"
case 3:
v.Type = "integer"
case 4:
v.Type = "array"
default:
panic("unreachable")
}
switch c.Intn(5) {
case 0:
v.CollectionFormat = "csv"
case 1:
v.CollectionFormat = "ssv"
case 2:
v.CollectionFormat = "tsv"
case 3:
v.CollectionFormat = "pipes"
case 4:
v.CollectionFormat = ""
default:
panic("unreachable")
}
// None of the types which include SimpleSchema in our definitions
// actually support "example" in the official spec
v.Example = nil
// unsupported by openapi
v.Nullable = false
},
func(v *int64, c fuzz.Continue) {
c.Fuzz(v)
// Gnostic does not differentiate between 0 and non-specified
// so avoid using 0 for fuzzer
if *v == 0 {
*v = 1
}
},
func(v *float64, c fuzz.Continue) {
c.Fuzz(v)
// Gnostic does not differentiate between 0 and non-specified
// so avoid using 0 for fuzzer
if *v == 0.0 {
*v = 1.0
}
},
func(v *Parameter, c fuzz.Continue) {
if v == nil {
return
}
c.Fuzz(&v.VendorExtensible)
if c.RandBool() {
// body param
v.Description = c.RandString() + "x"
v.Name = c.RandString() + "x"
v.In = "body"
c.Fuzz(&v.Description)
c.Fuzz(&v.Required)
v.Schema = &Schema{}
c.Fuzz(&v.Schema)
} else {
c.Fuzz(&v.SimpleSchema)
c.Fuzz(&v.CommonValidations)
v.AllowEmptyValue = false
v.Description = c.RandString() + "x"
v.Name = c.RandString() + "x"
switch c.Intn(4) {
case 0:
// Header param
v.In = "header"
case 1:
// Form data param
v.In = "formData"
v.AllowEmptyValue = c.RandBool()
case 2:
// Query param
v.In = "query"
v.AllowEmptyValue = c.RandBool()
case 3:
// Path param
v.In = "path"
v.Required = true
default:
panic("unreachable")
}
// descendant Items of Parameter may not be refs
cur := v.Items
for cur != nil {
cur.Ref = Ref{}
cur = cur.Items
}
}
},
func(v *Schema, c fuzz.Continue) {
if c.RandBool() {
// file schema
c.Fuzz(&v.Default)
c.Fuzz(&v.Description)
c.Fuzz(&v.Example)
c.Fuzz(&v.ExternalDocs)
c.Fuzz(&v.Format)
c.Fuzz(&v.ReadOnly)
c.Fuzz(&v.Required)
c.Fuzz(&v.Title)
v.Type = StringOrArray{"file"}
} else {
// normal schema
c.Fuzz(&v.SchemaProps)
c.Fuzz(&v.SwaggerSchemaProps)
c.Fuzz(&v.VendorExtensible)
// c.Fuzz(&v.ExtraProps)
// ExtraProps will not roundtrip - gnostic throws out
// unrecognized keys
}
// Not supported by official openapi v2 spec
// and stripped by k8s apiserver
v.ID = ""
v.AnyOf = nil
v.OneOf = nil
v.Not = nil
v.Nullable = false
v.AdditionalItems = nil
v.Schema = ""
v.PatternProperties = nil
v.Definitions = nil
v.Dependencies = nil
},
}
var SwaggerDiffOptions = []cmp.Option{
// cmp.Diff panics on Ref since jsonreference.Ref uses unexported fields
cmp.Comparer(func(a Ref, b Ref) bool {
return a.String() == b.String()
}),
}

View File

@@ -21,7 +21,7 @@ import (
"strconv"
"github.com/go-openapi/jsonreference"
openapi_v2 "github.com/google/gnostic/openapiv2"
openapi_v2 "github.com/google/gnostic-models/openapiv2"
)
// Interfaces