mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-16 23:29:21 +00:00
Use OpenAPI V3 for client side SMP
This commit is contained in:
parent
74fefd877f
commit
4f3b0b1518
@ -20,12 +20,17 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||||
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
|
forkedjson "k8s.io/apimachinery/third_party/forked/golang/json"
|
||||||
openapi "k8s.io/kube-openapi/pkg/util/proto"
|
openapi "k8s.io/kube-openapi/pkg/util/proto"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const patchMergeKey = "x-kubernetes-patch-merge-key"
|
||||||
|
const patchStrategy = "x-kubernetes-patch-strategy"
|
||||||
|
|
||||||
type PatchMeta struct {
|
type PatchMeta struct {
|
||||||
patchStrategies []string
|
patchStrategies []string
|
||||||
patchMergeKey string
|
patchMergeKey string
|
||||||
@ -148,6 +153,90 @@ func GetTagStructTypeOrDie(dataStruct interface{}) reflect.Type {
|
|||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PatchMetaFromOpenAPIV3 struct {
|
||||||
|
// SchemaList is required to resolve OpenAPI V3 references
|
||||||
|
SchemaList map[string]*spec.Schema
|
||||||
|
Schema *spec.Schema
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PatchMetaFromOpenAPIV3) traverse(key string) (PatchMetaFromOpenAPIV3, error) {
|
||||||
|
if s.Schema == nil {
|
||||||
|
return PatchMetaFromOpenAPIV3{}, nil
|
||||||
|
}
|
||||||
|
if len(s.Schema.Properties) == 0 {
|
||||||
|
return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key)
|
||||||
|
}
|
||||||
|
subschema, ok := s.Schema.Properties[key]
|
||||||
|
if !ok {
|
||||||
|
return PatchMetaFromOpenAPIV3{}, fmt.Errorf("unable to find api field \"%s\"", key)
|
||||||
|
}
|
||||||
|
return PatchMetaFromOpenAPIV3{SchemaList: s.SchemaList, Schema: &subschema}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolve(l *PatchMetaFromOpenAPIV3) error {
|
||||||
|
if len(l.Schema.AllOf) > 0 {
|
||||||
|
l.Schema = &l.Schema.AllOf[0]
|
||||||
|
}
|
||||||
|
if refString := l.Schema.Ref.String(); refString != "" {
|
||||||
|
str := strings.TrimPrefix(refString, "#/components/schemas/")
|
||||||
|
sch, ok := l.SchemaList[str]
|
||||||
|
if ok {
|
||||||
|
l.Schema = sch
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Unable to resolve %s in OpenAPI V3", refString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForStruct(key string) (LookupPatchMeta, PatchMeta, error) {
|
||||||
|
l, err := s.traverse(key)
|
||||||
|
if err != nil {
|
||||||
|
return l, PatchMeta{}, err
|
||||||
|
}
|
||||||
|
p := PatchMeta{}
|
||||||
|
f, ok := l.Schema.Extensions[patchMergeKey]
|
||||||
|
if ok {
|
||||||
|
p.SetPatchMergeKey(f.(string))
|
||||||
|
}
|
||||||
|
g, ok := l.Schema.Extensions[patchStrategy]
|
||||||
|
if ok {
|
||||||
|
p.SetPatchStrategies(strings.Split(g.(string), ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = resolve(&l)
|
||||||
|
return l, p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PatchMetaFromOpenAPIV3) LookupPatchMetadataForSlice(key string) (LookupPatchMeta, PatchMeta, error) {
|
||||||
|
l, err := s.traverse(key)
|
||||||
|
if err != nil {
|
||||||
|
return l, PatchMeta{}, err
|
||||||
|
}
|
||||||
|
p := PatchMeta{}
|
||||||
|
f, ok := l.Schema.Extensions[patchMergeKey]
|
||||||
|
if ok {
|
||||||
|
p.SetPatchMergeKey(f.(string))
|
||||||
|
}
|
||||||
|
g, ok := l.Schema.Extensions[patchStrategy]
|
||||||
|
if ok {
|
||||||
|
p.SetPatchStrategies(strings.Split(g.(string), ","))
|
||||||
|
}
|
||||||
|
if l.Schema.Items != nil {
|
||||||
|
l.Schema = l.Schema.Items.Schema
|
||||||
|
}
|
||||||
|
resolve(&l)
|
||||||
|
return l, p, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s PatchMetaFromOpenAPIV3) Name() string {
|
||||||
|
schema := s.Schema
|
||||||
|
if len(schema.Type) > 0 {
|
||||||
|
return strings.Join(schema.Type, "")
|
||||||
|
}
|
||||||
|
return "Struct"
|
||||||
|
}
|
||||||
|
|
||||||
type PatchMetaFromOpenAPI struct {
|
type PatchMetaFromOpenAPI struct {
|
||||||
Schema openapi.Schema
|
Schema openapi.Schema
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,9 @@ import (
|
|||||||
var (
|
var (
|
||||||
fakeMergeItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-merge-item.json")}
|
fakeMergeItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-merge-item.json")}
|
||||||
fakePrecisionItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-precision-item.json")}
|
fakePrecisionItemSchema = sptest.Fake{Path: filepath.Join("testdata", "swagger-precision-item.json")}
|
||||||
|
|
||||||
|
fakeMergeItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-merge-item-v3.json")}
|
||||||
|
fakePrecisionItemV3Schema = sptest.OpenAPIV3Getter{Path: filepath.Join("testdata", "swagger-precision-item-v3.json")}
|
||||||
)
|
)
|
||||||
|
|
||||||
type SortMergeListTestCases struct {
|
type SortMergeListTestCases struct {
|
||||||
@ -284,9 +287,14 @@ func TestSortMergeLists(t *testing.T) {
|
|||||||
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
||||||
}
|
}
|
||||||
|
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
|
||||||
|
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
|
||||||
|
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
|
||||||
|
}
|
||||||
schemas := []LookupPatchMeta{
|
schemas := []LookupPatchMeta{
|
||||||
mergeItemStructSchema,
|
mergeItemStructSchema,
|
||||||
mergeItemOpenapiSchema,
|
mergeItemOpenapiSchema,
|
||||||
|
mergeItemOpenapiV3Schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
tc := SortMergeListTestCases{}
|
tc := SortMergeListTestCases{}
|
||||||
@ -766,9 +774,14 @@ func TestCustomStrategicMergePatch(t *testing.T) {
|
|||||||
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
||||||
}
|
}
|
||||||
|
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
|
||||||
|
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
|
||||||
|
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
|
||||||
|
}
|
||||||
schemas := []LookupPatchMeta{
|
schemas := []LookupPatchMeta{
|
||||||
mergeItemStructSchema,
|
mergeItemStructSchema,
|
||||||
mergeItemOpenapiSchema,
|
mergeItemOpenapiSchema,
|
||||||
|
mergeItemOpenapiV3Schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
tc := StrategicMergePatchTestCases{}
|
tc := StrategicMergePatchTestCases{}
|
||||||
@ -6169,9 +6182,14 @@ func TestStrategicMergePatch(t *testing.T) {
|
|||||||
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
||||||
}
|
}
|
||||||
|
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
|
||||||
|
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
|
||||||
|
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
|
||||||
|
}
|
||||||
schemas := []LookupPatchMeta{
|
schemas := []LookupPatchMeta{
|
||||||
mergeItemStructSchema,
|
mergeItemStructSchema,
|
||||||
mergeItemOpenapiSchema,
|
mergeItemOpenapiSchema,
|
||||||
|
mergeItemOpenapiV3Schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
tc := StrategicMergePatchTestCases{}
|
tc := StrategicMergePatchTestCases{}
|
||||||
@ -6564,9 +6582,14 @@ func TestNumberConversion(t *testing.T) {
|
|||||||
precisionItemOpenapiSchema := PatchMetaFromOpenAPI{
|
precisionItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
Schema: sptest.GetSchemaOrDie(&fakePrecisionItemSchema, "precisionItem"),
|
Schema: sptest.GetSchemaOrDie(&fakePrecisionItemSchema, "precisionItem"),
|
||||||
}
|
}
|
||||||
|
precisionItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
|
||||||
|
SchemaList: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas,
|
||||||
|
Schema: fakePrecisionItemV3Schema.SchemaOrDie().Components.Schemas["precisionItem"],
|
||||||
|
}
|
||||||
precisionItemSchemas := []LookupPatchMeta{
|
precisionItemSchemas := []LookupPatchMeta{
|
||||||
precisionItemStructSchema,
|
precisionItemStructSchema,
|
||||||
precisionItemOpenapiSchema,
|
precisionItemOpenapiSchema,
|
||||||
|
precisionItemOpenapiV3Schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, schema := range precisionItemSchemas {
|
for _, schema := range precisionItemSchemas {
|
||||||
@ -6774,9 +6797,14 @@ func TestReplaceWithRawExtension(t *testing.T) {
|
|||||||
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
||||||
}
|
}
|
||||||
|
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
|
||||||
|
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
|
||||||
|
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
|
||||||
|
}
|
||||||
schemas := []LookupPatchMeta{
|
schemas := []LookupPatchMeta{
|
||||||
mergeItemStructSchema,
|
mergeItemStructSchema,
|
||||||
mergeItemOpenapiSchema,
|
mergeItemOpenapiSchema,
|
||||||
|
mergeItemOpenapiV3Schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, schema := range schemas {
|
for _, schema := range schemas {
|
||||||
@ -6946,9 +6974,14 @@ func TestUnknownField(t *testing.T) {
|
|||||||
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
mergeItemOpenapiSchema := PatchMetaFromOpenAPI{
|
||||||
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
Schema: sptest.GetSchemaOrDie(&fakeMergeItemSchema, "mergeItem"),
|
||||||
}
|
}
|
||||||
|
mergeItemOpenapiV3Schema := PatchMetaFromOpenAPIV3{
|
||||||
|
SchemaList: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas,
|
||||||
|
Schema: fakeMergeItemV3Schema.SchemaOrDie().Components.Schemas["mergeItem"],
|
||||||
|
}
|
||||||
schemas := []LookupPatchMeta{
|
schemas := []LookupPatchMeta{
|
||||||
mergeItemStructSchema,
|
mergeItemStructSchema,
|
||||||
mergeItemOpenapiSchema,
|
mergeItemOpenapiSchema,
|
||||||
|
mergeItemOpenapiV3Schema,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, k := range sets.StringKeySet(testcases).List() {
|
for _, k := range sets.StringKeySet(testcases).List() {
|
||||||
|
180
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item-v3.json
vendored
Normal file
180
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-merge-item-v3.json
vendored
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0",
|
||||||
|
"info": {
|
||||||
|
"title": "StrategicMergePatchTestingMergeItem",
|
||||||
|
"version": "v3.0"
|
||||||
|
},
|
||||||
|
"paths": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"mergeItem": {
|
||||||
|
"description": "MergeItem is type definition for testing strategic merge.",
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Name field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "Value field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"description": "Other field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mergingList": {
|
||||||
|
"description": "MergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"default": {},
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/mergeItem"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-merge-key": "name",
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
},
|
||||||
|
"nonMergingList": {
|
||||||
|
"description": "NonMergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/mergeItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mergingIntList": {
|
||||||
|
"description": "MergingIntList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
},
|
||||||
|
"nonMergingIntList": {
|
||||||
|
"description": "NonMergingIntList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mergeItemPtr": {
|
||||||
|
"description": "MergeItemPtr field.",
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/mergeItem"}
|
||||||
|
],
|
||||||
|
"x-kubernetes-patch-merge-key": "name",
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
},
|
||||||
|
"simpleMap": {
|
||||||
|
"description": "SimpleMap field.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"replacingItem": {
|
||||||
|
"description": "ReplacingItem field.",
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/io.k8s.apimachinery.pkg.runtime.RawExtension"}
|
||||||
|
],
|
||||||
|
"x-kubernetes-patch-strategy": "replace"
|
||||||
|
},
|
||||||
|
"retainKeysMap": {
|
||||||
|
"description": "RetainKeysMap field.",
|
||||||
|
"allOf": [
|
||||||
|
{"$ref": "#/components/schemas/retainKeysMergeItem"}
|
||||||
|
],
|
||||||
|
"x-kubernetes-patch-strategy": "retainKeys"
|
||||||
|
},
|
||||||
|
"retainKeysMergingList": {
|
||||||
|
"description": "RetainKeysMergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/mergeItem"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-merge-key": "name",
|
||||||
|
"x-kubernetes-patch-strategy": "merge,retainKeys"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-kubernetes-group-version-kind": [
|
||||||
|
{
|
||||||
|
"group": "fake-group",
|
||||||
|
"kind": "mergeItem",
|
||||||
|
"version": "some-version"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"retainKeysMergeItem": {
|
||||||
|
"description": "RetainKeysMergeItem is type definition for testing strategic merge.",
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Name field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "Value field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"other": {
|
||||||
|
"description": "Other field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"simpleMap": {
|
||||||
|
"description": "SimpleMap field.",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mergingList": {
|
||||||
|
"description": "MergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/mergeItem"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-merge-key": "name",
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
},
|
||||||
|
"nonMergingList": {
|
||||||
|
"description": "NonMergingList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/mergeItem"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mergingIntList": {
|
||||||
|
"description": "MergingIntList field.",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"x-kubernetes-patch-strategy": "merge"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-kubernetes-group-version-kind": [
|
||||||
|
{
|
||||||
|
"group": "fake-group",
|
||||||
|
"kind": "retainKeysMergeItem",
|
||||||
|
"version": "some-version"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"io.k8s.apimachinery.pkg.runtime.RawExtension": {
|
||||||
|
"description": "RawExtension is used to hold extensions in external versions.",
|
||||||
|
"required": [
|
||||||
|
"Raw"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"Raw": {
|
||||||
|
"description": "Raw is the underlying serialization of this object.",
|
||||||
|
"type": "string",
|
||||||
|
"format": "byte"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item-v3.json
vendored
Normal file
49
staging/src/k8s.io/apimachinery/pkg/util/strategicpatch/testdata/swagger-precision-item-v3.json
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0",
|
||||||
|
"info": {
|
||||||
|
"title": "StrategicMergePatchTestingPrecisionItem",
|
||||||
|
"version": "v1.9.0"
|
||||||
|
},
|
||||||
|
"paths": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"precisionItem": {
|
||||||
|
"description": "PrecisionItem is type definition for testing strategic merge.",
|
||||||
|
"required": [],
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"description": "Name field.",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"int32": {
|
||||||
|
"description": "Int32 field.",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int32"
|
||||||
|
},
|
||||||
|
"int64": {
|
||||||
|
"description": "Int64 field.",
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"float32": {
|
||||||
|
"description": "Float32 field.",
|
||||||
|
"type": "number",
|
||||||
|
"format": "float32"
|
||||||
|
},
|
||||||
|
"float64": {
|
||||||
|
"description": "Float64 field.",
|
||||||
|
"type": "number",
|
||||||
|
"format": "float64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-kubernetes-group-version-kind": [
|
||||||
|
{
|
||||||
|
"group": "fake-group",
|
||||||
|
"kind": "precisionItem",
|
||||||
|
"version": "some-version"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type OpenAPIV3Getter struct {
|
||||||
|
Path string
|
||||||
|
once sync.Once
|
||||||
|
bytes []byte
|
||||||
|
openapiv3 spec3.OpenAPI
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OpenAPIV3Getter) SchemaBytesOrDie() []byte {
|
||||||
|
f.once.Do(func() {
|
||||||
|
_, err := os.Stat(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
spec, err := os.ReadFile(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
f.bytes = spec
|
||||||
|
})
|
||||||
|
return f.bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *OpenAPIV3Getter) SchemaOrDie() *spec3.OpenAPI {
|
||||||
|
f.once.Do(func() {
|
||||||
|
_, err := os.Stat(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
spec, err := os.ReadFile(f.Path)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.openapiv3.UnmarshalJSON(spec)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return &f.openapiv3
|
||||||
|
}
|
@ -39,6 +39,8 @@ import (
|
|||||||
"k8s.io/cli-runtime/pkg/printers"
|
"k8s.io/cli-runtime/pkg/printers"
|
||||||
"k8s.io/cli-runtime/pkg/resource"
|
"k8s.io/cli-runtime/pkg/resource"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
|
cachedopenapi "k8s.io/client-go/openapi/cached"
|
||||||
|
"k8s.io/client-go/openapi3"
|
||||||
"k8s.io/client-go/util/csaupgrade"
|
"k8s.io/client-go/util/csaupgrade"
|
||||||
"k8s.io/component-base/version"
|
"k8s.io/component-base/version"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
@ -106,6 +108,7 @@ type ApplyOptions struct {
|
|||||||
Mapper meta.RESTMapper
|
Mapper meta.RESTMapper
|
||||||
DynamicClient dynamic.Interface
|
DynamicClient dynamic.Interface
|
||||||
OpenAPISchema openapi.Resources
|
OpenAPISchema openapi.Resources
|
||||||
|
OpenAPIV3Root openapi3.Root
|
||||||
|
|
||||||
Namespace string
|
Namespace string
|
||||||
EnforceNamespace bool
|
EnforceNamespace bool
|
||||||
@ -283,6 +286,12 @@ func (flags *ApplyFlags) ToOptions(f cmdutil.Factory, cmd *cobra.Command, baseNa
|
|||||||
}
|
}
|
||||||
|
|
||||||
openAPISchema, _ := f.OpenAPISchema()
|
openAPISchema, _ := f.OpenAPISchema()
|
||||||
|
var openAPIV3Root openapi3.Root
|
||||||
|
openAPIV3Client, err := f.OpenAPIV3Client()
|
||||||
|
if err == nil {
|
||||||
|
cachedOpenAPIV3Client := cachedopenapi.NewClient(openAPIV3Client)
|
||||||
|
openAPIV3Root = openapi3.NewRoot(cachedOpenAPIV3Client)
|
||||||
|
}
|
||||||
|
|
||||||
validationDirective, err := cmdutil.GetValidationDirective(cmd)
|
validationDirective, err := cmdutil.GetValidationDirective(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -361,6 +370,7 @@ func (flags *ApplyFlags) ToOptions(f cmdutil.Factory, cmd *cobra.Command, baseNa
|
|||||||
Mapper: mapper,
|
Mapper: mapper,
|
||||||
DynamicClient: dynamicClient,
|
DynamicClient: dynamicClient,
|
||||||
OpenAPISchema: openAPISchema,
|
OpenAPISchema: openAPISchema,
|
||||||
|
OpenAPIV3Root: openAPIV3Root,
|
||||||
|
|
||||||
IOStreams: flags.IOStreams,
|
IOStreams: flags.IOStreams,
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ import (
|
|||||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||||
"k8s.io/cli-runtime/pkg/resource"
|
"k8s.io/cli-runtime/pkg/resource"
|
||||||
dynamicfakeclient "k8s.io/client-go/dynamic/fake"
|
dynamicfakeclient "k8s.io/client-go/dynamic/fake"
|
||||||
|
openapiclient "k8s.io/client-go/openapi"
|
||||||
|
"k8s.io/client-go/openapi/openapitest"
|
||||||
restclient "k8s.io/client-go/rest"
|
restclient "k8s.io/client-go/rest"
|
||||||
"k8s.io/client-go/rest/fake"
|
"k8s.io/client-go/rest/fake"
|
||||||
testing2 "k8s.io/client-go/testing"
|
testing2 "k8s.io/client-go/testing"
|
||||||
@ -64,11 +66,16 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "testdata", "openapi", "swagger.json")}
|
fakeSchema = sptest.Fake{Path: filepath.Join("..", "..", "..", "testdata", "openapi", "swagger.json")}
|
||||||
|
fakeOpenAPIV3Legacy = sptest.OpenAPIV3Getter{Path: filepath.Join("..", "..", "..", "testdata", "openapi", "v3", "api", "v1.json")}
|
||||||
|
fakeOpenAPIV3AppsV1 = sptest.OpenAPIV3Getter{Path: filepath.Join("..", "..", "..", "testdata", "openapi", "v3", "apis", "apps", "v1.json")}
|
||||||
testingOpenAPISchemas = []testOpenAPISchema{AlwaysErrorsOpenAPISchema, FakeOpenAPISchema}
|
testingOpenAPISchemas = []testOpenAPISchema{AlwaysErrorsOpenAPISchema, FakeOpenAPISchema}
|
||||||
AlwaysErrorsOpenAPISchema = testOpenAPISchema{
|
AlwaysErrorsOpenAPISchema = testOpenAPISchema{
|
||||||
OpenAPISchemaFn: func() (openapi.Resources, error) {
|
OpenAPISchemaFn: func() (openapi.Resources, error) {
|
||||||
return nil, errors.New("cannot get openapi spec")
|
return nil, errors.New("cannot get openapi spec")
|
||||||
},
|
},
|
||||||
|
OpenAPIV3ClientFunc: func() (openapiclient.Client, error) {
|
||||||
|
return nil, errors.New("cannot get openapiv3 client")
|
||||||
|
},
|
||||||
}
|
}
|
||||||
FakeOpenAPISchema = testOpenAPISchema{
|
FakeOpenAPISchema = testOpenAPISchema{
|
||||||
OpenAPISchemaFn: func() (openapi.Resources, error) {
|
OpenAPISchemaFn: func() (openapi.Resources, error) {
|
||||||
@ -78,12 +85,19 @@ var (
|
|||||||
}
|
}
|
||||||
return openapi.NewOpenAPIData(s)
|
return openapi.NewOpenAPIData(s)
|
||||||
},
|
},
|
||||||
|
OpenAPIV3ClientFunc: func() (openapiclient.Client, error) {
|
||||||
|
c := openapitest.NewFakeClient()
|
||||||
|
c.PathsMap["api/v1"] = openapitest.FakeGroupVersion{GVSpec: fakeOpenAPIV3Legacy.SchemaBytesOrDie()}
|
||||||
|
c.PathsMap["apis/apps/v1"] = openapitest.FakeGroupVersion{GVSpec: fakeOpenAPIV3AppsV1.SchemaBytesOrDie()}
|
||||||
|
return c, nil
|
||||||
|
},
|
||||||
}
|
}
|
||||||
codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
codec = scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
|
||||||
)
|
)
|
||||||
|
|
||||||
type testOpenAPISchema struct {
|
type testOpenAPISchema struct {
|
||||||
OpenAPISchemaFn func() (openapi.Resources, error)
|
OpenAPISchemaFn func() (openapi.Resources, error)
|
||||||
|
OpenAPIV3ClientFunc func() (openapiclient.Client, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyExtraArgsFail(t *testing.T) {
|
func TestApplyExtraArgsFail(t *testing.T) {
|
||||||
@ -684,6 +698,7 @@ func TestApplyObjectWithoutAnnotation(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
tf.OpenAPIV3ClientFunc = FakeOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
cmd := NewCmdApply("kubectl", tf, ioStreams)
|
cmd := NewCmdApply("kubectl", tf, ioStreams)
|
||||||
@ -730,6 +745,7 @@ func TestApplyObject(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -778,6 +794,7 @@ func TestApplyPruneObjects(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -1014,6 +1031,7 @@ func TestApplyPruneObjectsWithAllowlist(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
for _, resource := range tc.currentResources {
|
for _, resource := range tc.currentResources {
|
||||||
@ -1192,6 +1210,7 @@ func TestApplyCSAMigration(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = FakeOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = FakeOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = FakeOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -1280,6 +1299,7 @@ func TestApplyObjectOutput(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -1341,6 +1361,7 @@ func TestApplyRetry(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -1516,6 +1537,7 @@ func testApplyMultipleObjects(t *testing.T, asList bool) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -1611,6 +1633,7 @@ func TestApplyNULLPreservation(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -1673,6 +1696,7 @@ func TestUnstructuredApply(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -1737,6 +1761,7 @@ func TestUnstructuredIdempotentApply(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
|
||||||
|
|
||||||
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
ioStreams, _, buf, errBuf := genericiooptions.NewTestIOStreams()
|
||||||
@ -1979,6 +2004,7 @@ func TestForceApply(t *testing.T) {
|
|||||||
fakeDynamicClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
fakeDynamicClient := dynamicfakeclient.NewSimpleDynamicClient(scheme)
|
||||||
tf.FakeDynamicClient = fakeDynamicClient
|
tf.FakeDynamicClient = fakeDynamicClient
|
||||||
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
tf.OpenAPISchemaFunc = testingOpenAPISchema.OpenAPISchemaFn
|
||||||
|
tf.OpenAPIV3ClientFunc = testingOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
tf.Client = tf.UnstructuredClient
|
tf.Client = tf.UnstructuredClient
|
||||||
tf.ClientConfigVal = &restclient.Config{}
|
tf.ClientConfigVal = &restclient.Config{}
|
||||||
|
|
||||||
@ -2830,6 +2856,7 @@ func TestApplyWithPruneV2(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tf.Client = tf.UnstructuredClient
|
tf.Client = tf.UnstructuredClient
|
||||||
|
tf.OpenAPIV3ClientFunc = FakeOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
|
|
||||||
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.ApplySet}, t, func(t *testing.T) {
|
cmdtesting.WithAlphaEnvs([]cmdutil.FeatureGate{cmdutil.ApplySet}, t, func(t *testing.T) {
|
||||||
manifests := []string{"manifest1", "manifest2"}
|
manifests := []string{"manifest1", "manifest2"}
|
||||||
@ -3104,6 +3131,7 @@ func TestApplyWithPruneV2Fail(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tf.Client = tf.UnstructuredClient
|
tf.Client = tf.UnstructuredClient
|
||||||
|
tf.OpenAPIV3ClientFunc = FakeOpenAPISchema.OpenAPIV3ClientFunc
|
||||||
|
|
||||||
testdirs := []string{"testdata/prune/simple"}
|
testdirs := []string{"testdata/prune/simple"}
|
||||||
for _, testdir := range testdirs {
|
for _, testdir := range testdirs {
|
||||||
|
@ -37,6 +37,9 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
"k8s.io/cli-runtime/pkg/resource"
|
"k8s.io/cli-runtime/pkg/resource"
|
||||||
|
"k8s.io/client-go/openapi3"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||||
"k8s.io/kubectl/pkg/scheme"
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
"k8s.io/kubectl/pkg/util"
|
"k8s.io/kubectl/pkg/util"
|
||||||
@ -50,6 +53,10 @@ const (
|
|||||||
backOffPeriod = 1 * time.Second
|
backOffPeriod = 1 * time.Second
|
||||||
// how many times we can retry before back off
|
// how many times we can retry before back off
|
||||||
triesBeforeBackOff = 1
|
triesBeforeBackOff = 1
|
||||||
|
// groupVersionKindExtensionKey is the key used to lookup the
|
||||||
|
// GroupVersionKind value for an object definition from the
|
||||||
|
// definition's "extensions" map.
|
||||||
|
groupVersionKindExtensionKey = "x-kubernetes-group-version-kind"
|
||||||
)
|
)
|
||||||
|
|
||||||
var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
|
var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
|
||||||
@ -74,6 +81,7 @@ type Patcher struct {
|
|||||||
Retries int
|
Retries int
|
||||||
|
|
||||||
OpenapiSchema openapi.Resources
|
OpenapiSchema openapi.Resources
|
||||||
|
OpenAPIV3Root openapi3.Root
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPatcher(o *ApplyOptions, info *resource.Info, helper *resource.Helper) (*Patcher, error) {
|
func newPatcher(o *ApplyOptions, info *resource.Info, helper *resource.Helper) (*Patcher, error) {
|
||||||
@ -92,6 +100,7 @@ func newPatcher(o *ApplyOptions, info *resource.Info, helper *resource.Helper) (
|
|||||||
Timeout: o.DeleteOptions.Timeout,
|
Timeout: o.DeleteOptions.Timeout,
|
||||||
GracePeriod: o.DeleteOptions.GracePeriod,
|
GracePeriod: o.DeleteOptions.GracePeriod,
|
||||||
OpenapiSchema: openapiSchema,
|
OpenapiSchema: openapiSchema,
|
||||||
|
OpenAPIV3Root: o.OpenAPIV3Root,
|
||||||
Retries: maxPatchRetry,
|
Retries: maxPatchRetry,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -118,7 +127,35 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
|
|||||||
var patchType types.PatchType
|
var patchType types.PatchType
|
||||||
var patch []byte
|
var patch []byte
|
||||||
|
|
||||||
if p.OpenapiSchema != nil {
|
if p.OpenAPIV3Root != nil {
|
||||||
|
gvkSupported, err := p.gvkSupportsPatchOpenAPIV3(p.Mapping.GroupVersionKind)
|
||||||
|
if err != nil {
|
||||||
|
// Realistically this error logging is not needed (not present in V2),
|
||||||
|
// but would help us in debugging if users encounter a problem
|
||||||
|
// with OpenAPI V3 not present in V2.
|
||||||
|
klog.V(5).Infof("warning: OpenAPI V3 path does not exist - group: %s, version %s, kind %s\n",
|
||||||
|
p.Mapping.GroupVersionKind.Group, p.Mapping.GroupVersionKind.Version, p.Mapping.GroupVersionKind.Kind)
|
||||||
|
} else {
|
||||||
|
if gvkSupported {
|
||||||
|
patch, err = p.buildStrategicMergePatchFromOpenAPIV3(original, modified, current)
|
||||||
|
if err != nil {
|
||||||
|
// Fall back to OpenAPI V2 if there is a problem
|
||||||
|
// We should remove the fallback in the future,
|
||||||
|
// but for the first release it might be beneficial
|
||||||
|
// to fall back to OpenAPI V2 while logging the error
|
||||||
|
// and seeing if we get any bug reports.
|
||||||
|
fmt.Fprintf(errOut, "warning: error calculating patch from openapi v3 spec: %v\n", err)
|
||||||
|
} else {
|
||||||
|
patchType = types.StrategicMergePatchType
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
klog.V(5).Infof("warning: OpenAPI V3 path does not support strategic merge patch - group: %s, version %s, kind %s\n",
|
||||||
|
p.Mapping.GroupVersionKind.Group, p.Mapping.GroupVersionKind.Version, p.Mapping.GroupVersionKind.Kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if patch == nil && p.OpenapiSchema != nil {
|
||||||
// if openapischema is used, we'll try to get required patch type for this GVK from Open API.
|
// if openapischema is used, we'll try to get required patch type for this GVK from Open API.
|
||||||
// if it fails or could not find any patch type, fall back to baked-in patch type determination.
|
// if it fails or could not find any patch type, fall back to baked-in patch type determination.
|
||||||
if patchType, err = p.getPatchTypeFromOpenAPI(p.Mapping.GroupVersionKind); err == nil && patchType == types.StrategicMergePatchType {
|
if patchType, err = p.getPatchTypeFromOpenAPI(p.Mapping.GroupVersionKind); err == nil && patchType == types.StrategicMergePatchType {
|
||||||
@ -182,6 +219,90 @@ func (p *Patcher) buildMergePatch(original, modified, current []byte) ([]byte, e
|
|||||||
return patch, nil
|
return patch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// gvkSupportsPatchOpenAPIV3 checks if a particular GVK supports the patch operation.
|
||||||
|
// It returns an error if the OpenAPI V3 could not be downloaded.
|
||||||
|
func (p *Patcher) gvkSupportsPatchOpenAPIV3(gvk schema.GroupVersionKind) (bool, error) {
|
||||||
|
gvSpec, err := p.OpenAPIV3Root.GVSpec(schema.GroupVersion{
|
||||||
|
Group: p.Mapping.GroupVersionKind.Group,
|
||||||
|
Version: p.Mapping.GroupVersionKind.Version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if gvSpec == nil || gvSpec.Paths == nil || gvSpec.Paths.Paths == nil {
|
||||||
|
return false, fmt.Errorf("gvk group: %s, version: %s, kind: %s does not exist for OpenAPI V3", gvk.Group, gvk.Version, gvk.Kind)
|
||||||
|
}
|
||||||
|
for _, path := range gvSpec.Paths.Paths {
|
||||||
|
if path.Patch != nil {
|
||||||
|
if gvkMatchesSingle(p.Mapping.GroupVersionKind, path.Patch.Extensions) {
|
||||||
|
if path.Patch.RequestBody == nil || path.Patch.RequestBody.Content == nil {
|
||||||
|
// GVK exists but does not support requestBody. Indication of malformed OpenAPI.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if _, ok := path.Patch.RequestBody.Content["application/strategic-merge-patch+json"]; ok {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
// GVK exists but strategic-merge-patch is not supported. Likely to be a CRD or aggregated resource.
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func gvkMatchesArray(targetGVK schema.GroupVersionKind, ext spec.Extensions) bool {
|
||||||
|
var gvkList []map[string]string
|
||||||
|
err := ext.GetObject(groupVersionKindExtensionKey, &gvkList)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, gvkMap := range gvkList {
|
||||||
|
if gvkMap["group"] == targetGVK.Group &&
|
||||||
|
gvkMap["version"] == targetGVK.Version &&
|
||||||
|
gvkMap["kind"] == targetGVK.Kind {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func gvkMatchesSingle(targetGVK schema.GroupVersionKind, ext spec.Extensions) bool {
|
||||||
|
var gvkMap map[string]string
|
||||||
|
err := ext.GetObject(groupVersionKindExtensionKey, &gvkMap)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return gvkMap["group"] == targetGVK.Group &&
|
||||||
|
gvkMap["version"] == targetGVK.Version &&
|
||||||
|
gvkMap["kind"] == targetGVK.Kind
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Patcher) buildStrategicMergePatchFromOpenAPIV3(original, modified, current []byte) ([]byte, error) {
|
||||||
|
gvSpec, err := p.OpenAPIV3Root.GVSpec(schema.GroupVersion{
|
||||||
|
Group: p.Mapping.GroupVersionKind.Group,
|
||||||
|
Version: p.Mapping.GroupVersionKind.Version,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if gvSpec == nil || gvSpec.Components == nil {
|
||||||
|
return nil, fmt.Errorf("OpenAPI V3 Components is nil")
|
||||||
|
}
|
||||||
|
for _, c := range gvSpec.Components.Schemas {
|
||||||
|
if !gvkMatchesArray(p.Mapping.GroupVersionKind, c.Extensions) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
lookupPatchMeta := strategicpatch.PatchMetaFromOpenAPIV3{Schema: c, SchemaList: gvSpec.Components.Schemas}
|
||||||
|
if openapiv3Patch, err := strategicpatch.CreateThreeWayMergePatch(original, modified, current, lookupPatchMeta, p.Overwrite); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return openapiv3Patch, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// buildStrategicMergeFromOpenAPI builds patch from OpenAPI if it is enabled.
|
// buildStrategicMergeFromOpenAPI builds patch from OpenAPI if it is enabled.
|
||||||
// This is used for core types which is published in openapi.
|
// This is used for core types which is published in openapi.
|
||||||
func (p *Patcher) buildStrategicMergeFromOpenAPI(original, modified, current []byte) ([]byte, error) {
|
func (p *Patcher) buildStrategicMergeFromOpenAPI(original, modified, current []byte) ([]byte, error) {
|
||||||
|
15675
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/apps/v1.json
vendored
Normal file
15675
staging/src/k8s.io/kubectl/testdata/openapi/v3/apis/apps/v1.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user