Merge pull request #77333 from sttts/sttts-structural-crd-pruning
apiextensions: implement structural schema CRD pruning
This commit is contained in:
4
api/openapi-spec/swagger.json
generated
4
api/openapi-spec/swagger.json
generated
@@ -16500,6 +16500,10 @@
|
|||||||
"$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceDefinitionNames",
|
"$ref": "#/definitions/io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1beta1.CustomResourceDefinitionNames",
|
||||||
"description": "Names are the names used to describe this custom resource"
|
"description": "Names are the names used to describe this custom resource"
|
||||||
},
|
},
|
||||||
|
"preserveUnknownFields": {
|
||||||
|
"description": "preserveUnknownFields disables pruning of object fields which are not specified in the OpenAPI schema. apiVersion, kind, metadata and known fields inside metadata are always preserved. Defaults to true in v1beta and will default to false in v1.",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"scope": {
|
"scope": {
|
||||||
"description": "Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced",
|
"description": "Scope indicates whether this resource is cluster or namespace scoped. Default is namespaced",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
@@ -15,6 +15,7 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||||
"//vendor/github.com/google/gofuzz:go_default_library",
|
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -25,6 +25,7 @@ import (
|
|||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
||||||
@@ -69,6 +70,9 @@ func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
|
|||||||
if obj.Conversion.Strategy == apiextensions.WebhookConverter && len(obj.Conversion.ConversionReviewVersions) == 0 {
|
if obj.Conversion.Strategy == apiextensions.WebhookConverter && len(obj.Conversion.ConversionReviewVersions) == 0 {
|
||||||
obj.Conversion.ConversionReviewVersions = []string{"v1beta1"}
|
obj.Conversion.ConversionReviewVersions = []string{"v1beta1"}
|
||||||
}
|
}
|
||||||
|
if obj.PreserveUnknownFields == nil {
|
||||||
|
obj.PreserveUnknownFields = pointer.BoolPtr(true)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
func(obj *apiextensions.CustomResourceDefinition, c fuzz.Continue) {
|
func(obj *apiextensions.CustomResourceDefinition, c fuzz.Continue) {
|
||||||
c.FuzzNoCustom(obj)
|
c.FuzzNoCustom(obj)
|
||||||
|
@@ -73,6 +73,12 @@ type CustomResourceDefinitionSpec struct {
|
|||||||
|
|
||||||
// `conversion` defines conversion settings for the CRD.
|
// `conversion` defines conversion settings for the CRD.
|
||||||
Conversion *CustomResourceConversion
|
Conversion *CustomResourceConversion
|
||||||
|
|
||||||
|
// preserveUnknownFields disables pruning of object fields which are not
|
||||||
|
// specified in the OpenAPI schema. apiVersion, kind, metadata and known
|
||||||
|
// fields inside metadata are always preserved.
|
||||||
|
// Defaults to true in v1beta and will default to false in v1.
|
||||||
|
PreserveUnknownFields *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomResourceConversion describes how to convert different versions of a CR.
|
// CustomResourceConversion describes how to convert different versions of a CR.
|
||||||
|
@@ -72,6 +72,9 @@ func SetDefaults_CustomResourceDefinitionSpec(obj *CustomResourceDefinitionSpec)
|
|||||||
if obj.Conversion.Strategy == WebhookConverter && len(obj.Conversion.ConversionReviewVersions) == 0 {
|
if obj.Conversion.Strategy == WebhookConverter && len(obj.Conversion.ConversionReviewVersions) == 0 {
|
||||||
obj.Conversion.ConversionReviewVersions = []string{SchemeGroupVersion.Version}
|
obj.Conversion.ConversionReviewVersions = []string{SchemeGroupVersion.Version}
|
||||||
}
|
}
|
||||||
|
if obj.PreserveUnknownFields == nil {
|
||||||
|
obj.PreserveUnknownFields = utilpointer.BoolPtr(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults_ServiceReference sets defaults for Webhook's ServiceReference
|
// SetDefaults_ServiceReference sets defaults for Webhook's ServiceReference
|
||||||
|
@@ -709,6 +709,16 @@ func (m *CustomResourceDefinitionSpec) MarshalTo(dAtA []byte) (int, error) {
|
|||||||
}
|
}
|
||||||
i += n13
|
i += n13
|
||||||
}
|
}
|
||||||
|
if m.PreserveUnknownFields != nil {
|
||||||
|
dAtA[i] = 0x50
|
||||||
|
i++
|
||||||
|
if *m.PreserveUnknownFields {
|
||||||
|
dAtA[i] = 1
|
||||||
|
} else {
|
||||||
|
dAtA[i] = 0
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
return i, nil
|
return i, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1855,6 +1865,9 @@ func (m *CustomResourceDefinitionSpec) Size() (n int) {
|
|||||||
l = m.Conversion.Size()
|
l = m.Conversion.Size()
|
||||||
n += 1 + l + sovGenerated(uint64(l))
|
n += 1 + l + sovGenerated(uint64(l))
|
||||||
}
|
}
|
||||||
|
if m.PreserveUnknownFields != nil {
|
||||||
|
n += 2
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2339,6 +2352,7 @@ func (this *CustomResourceDefinitionSpec) String() string {
|
|||||||
`Versions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Versions), "CustomResourceDefinitionVersion", "CustomResourceDefinitionVersion", 1), `&`, ``, 1) + `,`,
|
`Versions:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Versions), "CustomResourceDefinitionVersion", "CustomResourceDefinitionVersion", 1), `&`, ``, 1) + `,`,
|
||||||
`AdditionalPrinterColumns:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.AdditionalPrinterColumns), "CustomResourceColumnDefinition", "CustomResourceColumnDefinition", 1), `&`, ``, 1) + `,`,
|
`AdditionalPrinterColumns:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.AdditionalPrinterColumns), "CustomResourceColumnDefinition", "CustomResourceColumnDefinition", 1), `&`, ``, 1) + `,`,
|
||||||
`Conversion:` + strings.Replace(fmt.Sprintf("%v", this.Conversion), "CustomResourceConversion", "CustomResourceConversion", 1) + `,`,
|
`Conversion:` + strings.Replace(fmt.Sprintf("%v", this.Conversion), "CustomResourceConversion", "CustomResourceConversion", 1) + `,`,
|
||||||
|
`PreserveUnknownFields:` + valueToStringGenerated(this.PreserveUnknownFields) + `,`,
|
||||||
`}`,
|
`}`,
|
||||||
}, "")
|
}, "")
|
||||||
return s
|
return s
|
||||||
@@ -4316,6 +4330,27 @@ func (m *CustomResourceDefinitionSpec) Unmarshal(dAtA []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
iNdEx = postIndex
|
iNdEx = postIndex
|
||||||
|
case 10:
|
||||||
|
if wireType != 0 {
|
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field PreserveUnknownFields", wireType)
|
||||||
|
}
|
||||||
|
var v int
|
||||||
|
for shift := uint(0); ; shift += 7 {
|
||||||
|
if shift >= 64 {
|
||||||
|
return ErrIntOverflowGenerated
|
||||||
|
}
|
||||||
|
if iNdEx >= l {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
b := dAtA[iNdEx]
|
||||||
|
iNdEx++
|
||||||
|
v |= (int(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b := bool(v != 0)
|
||||||
|
m.PreserveUnknownFields = &b
|
||||||
default:
|
default:
|
||||||
iNdEx = preIndex
|
iNdEx = preIndex
|
||||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||||
@@ -7466,186 +7501,187 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var fileDescriptorGenerated = []byte{
|
var fileDescriptorGenerated = []byte{
|
||||||
// 2884 bytes of a gzipped FileDescriptorProto
|
// 2908 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcd, 0x73, 0x1c, 0x47,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0xcd, 0x73, 0x23, 0x47,
|
||||||
0x15, 0xf7, 0xec, 0x6a, 0xa5, 0x55, 0x4b, 0xb2, 0xa4, 0xb6, 0xad, 0x8c, 0x15, 0x67, 0x57, 0xde,
|
0x15, 0xdf, 0x91, 0x2c, 0x5b, 0x6e, 0xdb, 0x6b, 0xbb, 0x77, 0xed, 0xcc, 0x3a, 0x1b, 0xc9, 0xab,
|
||||||
0x90, 0x20, 0x82, 0xbd, 0x4a, 0x4c, 0x42, 0x42, 0xaa, 0x38, 0x68, 0x25, 0x25, 0xa5, 0xc4, 0xfa,
|
0x90, 0x60, 0xc2, 0xae, 0x9c, 0x2c, 0x09, 0x09, 0xa9, 0xe2, 0x60, 0xd9, 0x4e, 0xca, 0xc9, 0xda,
|
||||||
0xa0, 0xd7, 0x4e, 0x0c, 0xf9, 0x6c, 0xcd, 0xf4, 0xae, 0xc6, 0x9a, 0x2f, 0x4f, 0xcf, 0xac, 0xa4,
|
0x32, 0xad, 0xdd, 0x64, 0x21, 0x9f, 0x6d, 0x4d, 0x4b, 0x9e, 0xf5, 0x7c, 0xed, 0xf4, 0x8c, 0x6c,
|
||||||
0x0a, 0x50, 0x7c, 0x54, 0x0a, 0x8a, 0x02, 0x42, 0x91, 0x5c, 0x28, 0xe0, 0x10, 0x28, 0x2e, 0x1c,
|
0x57, 0x80, 0xe2, 0xa3, 0x52, 0x50, 0x14, 0x10, 0x8a, 0xe4, 0x42, 0x15, 0x1c, 0x02, 0xc5, 0x85,
|
||||||
0xe0, 0x00, 0x37, 0xf8, 0x03, 0x72, 0x4c, 0x71, 0xca, 0x81, 0xda, 0xc2, 0x9b, 0x33, 0x37, 0xaa,
|
0x03, 0x1c, 0xe0, 0x06, 0x7f, 0x40, 0x8e, 0x29, 0x4e, 0x39, 0x50, 0x2a, 0x56, 0xb9, 0xc2, 0x8d,
|
||||||
0xa8, 0xd2, 0x89, 0xea, 0x8f, 0xe9, 0x99, 0x9d, 0xdd, 0xb5, 0x5d, 0xf1, 0x6e, 0xcc, 0x4d, 0xf3,
|
0x2a, 0xaa, 0x7c, 0xa2, 0xfa, 0x63, 0x7a, 0x46, 0x23, 0x69, 0xd7, 0x95, 0x95, 0xb2, 0xdc, 0xac,
|
||||||
0xbe, 0x7e, 0xaf, 0x5f, 0xbf, 0x7e, 0xfd, 0xfa, 0xad, 0x40, 0xe3, 0xe0, 0x39, 0x5a, 0xb5, 0xbc,
|
0xf7, 0xf5, 0x7b, 0xfd, 0xfa, 0xf5, 0xeb, 0xd7, 0x6f, 0x0c, 0x1a, 0x07, 0xcf, 0xd1, 0xb2, 0xe9,
|
||||||
0x95, 0x83, 0x68, 0x8f, 0x04, 0x2e, 0x09, 0x09, 0x5d, 0x69, 0x11, 0xd7, 0xf4, 0x82, 0x15, 0xc9,
|
0xae, 0x1e, 0x84, 0x7b, 0xc4, 0x77, 0x48, 0x40, 0xe8, 0x6a, 0x8b, 0x38, 0x86, 0xeb, 0xaf, 0x4a,
|
||||||
0xc0, 0xbe, 0x45, 0x8e, 0x42, 0xe2, 0x52, 0xcb, 0x73, 0xe9, 0x65, 0xec, 0x5b, 0x94, 0x04, 0x2d,
|
0x06, 0xf6, 0x4c, 0x72, 0x14, 0x10, 0x87, 0x9a, 0xae, 0x43, 0xaf, 0x60, 0xcf, 0xa4, 0xc4, 0x6f,
|
||||||
0x12, 0xac, 0xf8, 0x07, 0x4d, 0xc6, 0xa3, 0xdd, 0x02, 0x2b, 0xad, 0xa7, 0xf6, 0x48, 0x88, 0x9f,
|
0x11, 0x7f, 0xd5, 0x3b, 0x68, 0x32, 0x1e, 0xed, 0x16, 0x58, 0x6d, 0x3d, 0xb5, 0x47, 0x02, 0xfc,
|
||||||
0x5a, 0x69, 0x12, 0x97, 0x04, 0x38, 0x24, 0x66, 0xd5, 0x0f, 0xbc, 0xd0, 0x83, 0x5f, 0x17, 0xe6,
|
0xd4, 0x6a, 0x93, 0x38, 0xc4, 0xc7, 0x01, 0x31, 0xca, 0x9e, 0xef, 0x06, 0x2e, 0xfc, 0xba, 0x30,
|
||||||
0xaa, 0x5d, 0xd2, 0x6f, 0x29, 0x73, 0x55, 0xff, 0xa0, 0xc9, 0x78, 0xb4, 0x5b, 0xa0, 0x2a, 0xcd,
|
0x57, 0xee, 0x92, 0x7e, 0x4b, 0x99, 0x2b, 0x7b, 0x07, 0x4d, 0xc6, 0xa3, 0xdd, 0x02, 0x65, 0x69,
|
||||||
0x2d, 0x5e, 0x6e, 0x5a, 0xe1, 0x7e, 0xb4, 0x57, 0x35, 0x3c, 0x67, 0xa5, 0xe9, 0x35, 0xbd, 0x15,
|
0x6e, 0xe9, 0x4a, 0xd3, 0x0c, 0xf6, 0xc3, 0xbd, 0x72, 0xdd, 0xb5, 0x57, 0x9b, 0x6e, 0xd3, 0x5d,
|
||||||
0x6e, 0x75, 0x2f, 0x6a, 0xf0, 0x2f, 0xfe, 0xc1, 0xff, 0x12, 0x68, 0x8b, 0x4f, 0x27, 0xce, 0x3b,
|
0xe5, 0x56, 0xf7, 0xc2, 0x06, 0xff, 0xc5, 0x7f, 0xf0, 0xbf, 0x04, 0xda, 0xd2, 0xd3, 0xb1, 0xf3,
|
||||||
0xd8, 0xd8, 0xb7, 0x5c, 0x12, 0x1c, 0x27, 0x1e, 0x3b, 0x24, 0xc4, 0x2b, 0xad, 0x1e, 0x1f, 0x17,
|
0x36, 0xae, 0xef, 0x9b, 0x0e, 0xf1, 0x8f, 0x63, 0x8f, 0x6d, 0x12, 0xe0, 0xd5, 0x56, 0x8f, 0x8f,
|
||||||
0x57, 0x06, 0x69, 0x05, 0x91, 0x1b, 0x5a, 0x0e, 0xe9, 0x51, 0xf8, 0xea, 0xdd, 0x14, 0xa8, 0xb1,
|
0x4b, 0xab, 0x83, 0xb4, 0xfc, 0xd0, 0x09, 0x4c, 0x9b, 0xf4, 0x28, 0x7c, 0xf5, 0x5e, 0x0a, 0xb4,
|
||||||
0x4f, 0x1c, 0x9c, 0xd5, 0xab, 0x9c, 0x68, 0x60, 0x7e, 0xcd, 0x73, 0x5b, 0x24, 0x60, 0xab, 0x44,
|
0xbe, 0x4f, 0x6c, 0x9c, 0xd6, 0x2b, 0x9d, 0x68, 0x60, 0x7e, 0xdd, 0x75, 0x5a, 0xc4, 0x67, 0xab,
|
||||||
0xe4, 0x56, 0x44, 0x68, 0x08, 0x6b, 0x20, 0x1f, 0x59, 0xa6, 0xae, 0x2d, 0x69, 0xcb, 0x93, 0xb5,
|
0x44, 0xe4, 0x76, 0x48, 0x68, 0x00, 0x2b, 0x20, 0x1b, 0x9a, 0x86, 0xae, 0x2d, 0x6b, 0x2b, 0x93,
|
||||||
0x27, 0x3f, 0x6a, 0x97, 0x4f, 0x75, 0xda, 0xe5, 0xfc, 0xf5, 0xcd, 0xf5, 0x93, 0x76, 0xf9, 0xe2,
|
0x95, 0x27, 0x3f, 0x6a, 0x17, 0xcf, 0x74, 0xda, 0xc5, 0xec, 0x8d, 0xad, 0x8d, 0x93, 0x76, 0xf1,
|
||||||
0x20, 0xa4, 0xf0, 0xd8, 0x27, 0xb4, 0x7a, 0x7d, 0x73, 0x1d, 0x31, 0x65, 0xf8, 0x22, 0x98, 0x37,
|
0xd2, 0x20, 0xa4, 0xe0, 0xd8, 0x23, 0xb4, 0x7c, 0x63, 0x6b, 0x03, 0x31, 0x65, 0xf8, 0x22, 0x98,
|
||||||
0x09, 0xb5, 0x02, 0x62, 0xae, 0xee, 0x6e, 0xbe, 0x22, 0xec, 0xeb, 0x39, 0x6e, 0xf1, 0xbc, 0xb4,
|
0x37, 0x08, 0x35, 0x7d, 0x62, 0xac, 0xed, 0x6e, 0xbd, 0x22, 0xec, 0xeb, 0x19, 0x6e, 0xf1, 0x82,
|
||||||
0x38, 0xbf, 0x9e, 0x15, 0x40, 0xbd, 0x3a, 0xf0, 0x06, 0x98, 0xf0, 0xf6, 0x6e, 0x12, 0x23, 0xa4,
|
0xb4, 0x38, 0xbf, 0x91, 0x16, 0x40, 0xbd, 0x3a, 0xf0, 0x26, 0x98, 0x70, 0xf7, 0x6e, 0x91, 0x7a,
|
||||||
0x7a, 0x7e, 0x29, 0xbf, 0x3c, 0x75, 0xe5, 0x72, 0x35, 0xd9, 0x41, 0xe5, 0x02, 0xdf, 0x36, 0xb9,
|
0x40, 0xf5, 0xec, 0x72, 0x76, 0x65, 0xea, 0xea, 0x95, 0x72, 0xbc, 0x83, 0xca, 0x05, 0xbe, 0x6d,
|
||||||
0xd8, 0x2a, 0xc2, 0x87, 0x1b, 0xf1, 0xce, 0xd5, 0x66, 0x25, 0xda, 0xc4, 0x8e, 0xb0, 0x82, 0x62,
|
0x72, 0xb1, 0x65, 0x84, 0x0f, 0x37, 0xa3, 0x9d, 0xab, 0xcc, 0x4a, 0xb4, 0x89, 0xaa, 0xb0, 0x82,
|
||||||
0x73, 0x95, 0xdf, 0xe7, 0x00, 0x4c, 0x2f, 0x9e, 0xfa, 0x9e, 0x4b, 0xc9, 0x50, 0x56, 0x4f, 0xc1,
|
0x22, 0x73, 0xa5, 0xdf, 0x65, 0x00, 0x4c, 0x2e, 0x9e, 0x7a, 0xae, 0x43, 0xc9, 0x50, 0x56, 0x4f,
|
||||||
0x9c, 0xc1, 0x2d, 0x87, 0xc4, 0x94, 0xb8, 0x7a, 0xee, 0xb3, 0x78, 0xaf, 0x4b, 0xfc, 0xb9, 0xb5,
|
0xc1, 0x5c, 0x9d, 0x5b, 0x0e, 0x88, 0x21, 0x71, 0xf5, 0xcc, 0x67, 0xf1, 0x5e, 0x97, 0xf8, 0x73,
|
||||||
0x8c, 0x39, 0xd4, 0x03, 0x00, 0xaf, 0x81, 0xf1, 0x80, 0xd0, 0xc8, 0x0e, 0xf5, 0xfc, 0x92, 0xb6,
|
0xeb, 0x29, 0x73, 0xa8, 0x07, 0x00, 0x5e, 0x07, 0xe3, 0x3e, 0xa1, 0xa1, 0x15, 0xe8, 0xd9, 0x65,
|
||||||
0x3c, 0x75, 0xe5, 0xd2, 0x40, 0x28, 0x9e, 0xdf, 0x2c, 0xf9, 0xaa, 0xad, 0xa7, 0xaa, 0xf5, 0x10,
|
0x6d, 0x65, 0xea, 0xea, 0xe5, 0x81, 0x50, 0x3c, 0xbf, 0x59, 0xf2, 0x95, 0x5b, 0x4f, 0x95, 0x6b,
|
||||||
0x87, 0x11, 0xad, 0x9d, 0x96, 0x48, 0xe3, 0x88, 0xdb, 0x40, 0xd2, 0x56, 0xe5, 0xc7, 0x39, 0x30,
|
0x01, 0x0e, 0x42, 0x5a, 0x39, 0x2b, 0x91, 0xc6, 0x11, 0xb7, 0x81, 0xa4, 0xad, 0xd2, 0x8f, 0x33,
|
||||||
0x97, 0x8e, 0x52, 0xcb, 0x22, 0x87, 0xf0, 0x10, 0x4c, 0x04, 0x22, 0x59, 0x78, 0x9c, 0xa6, 0xae,
|
0x60, 0x2e, 0x19, 0xa5, 0x96, 0x49, 0x0e, 0xe1, 0x21, 0x98, 0xf0, 0x45, 0xb2, 0xf0, 0x38, 0x4d,
|
||||||
0xec, 0x56, 0xef, 0xeb, 0x58, 0x55, 0x7b, 0x92, 0xb0, 0x36, 0xc5, 0xf6, 0x4c, 0x7e, 0xa0, 0x18,
|
0x5d, 0xdd, 0x2d, 0xdf, 0xd7, 0xb1, 0x2a, 0xf7, 0x24, 0x61, 0x65, 0x8a, 0xed, 0x99, 0xfc, 0x81,
|
||||||
0x0d, 0xbe, 0x03, 0x8a, 0x81, 0xdc, 0x28, 0x9e, 0x4d, 0x53, 0x57, 0xbe, 0x31, 0x44, 0x64, 0x61,
|
0x22, 0x34, 0xf8, 0x0e, 0xc8, 0xfb, 0x72, 0xa3, 0x78, 0x36, 0x4d, 0x5d, 0xfd, 0xc6, 0x10, 0x91,
|
||||||
0xb8, 0x36, 0xdd, 0x69, 0x97, 0x8b, 0xf1, 0x17, 0x52, 0x80, 0x95, 0xf7, 0x73, 0xa0, 0xb4, 0x16,
|
0x85, 0xe1, 0xca, 0x74, 0xa7, 0x5d, 0xcc, 0x47, 0xbf, 0x90, 0x02, 0x2c, 0xbd, 0x9f, 0x01, 0x85,
|
||||||
0xd1, 0xd0, 0x73, 0x10, 0xa1, 0x5e, 0x14, 0x18, 0x64, 0xcd, 0xb3, 0x23, 0xc7, 0x5d, 0x27, 0x0d,
|
0xf5, 0x90, 0x06, 0xae, 0x8d, 0x08, 0x75, 0x43, 0xbf, 0x4e, 0xd6, 0x5d, 0x2b, 0xb4, 0x9d, 0x0d,
|
||||||
0xcb, 0xb5, 0x42, 0x96, 0xad, 0x4b, 0x60, 0xcc, 0xc5, 0x0e, 0x91, 0xd9, 0x33, 0x2d, 0x63, 0x3a,
|
0xd2, 0x30, 0x1d, 0x33, 0x60, 0xd9, 0xba, 0x0c, 0xc6, 0x1c, 0x6c, 0x13, 0x99, 0x3d, 0xd3, 0x32,
|
||||||
0xb6, 0x8d, 0x1d, 0x82, 0x38, 0x87, 0x49, 0xb0, 0x64, 0x91, 0x67, 0x41, 0x49, 0x5c, 0x3b, 0xf6,
|
0xa6, 0x63, 0x3b, 0xd8, 0x26, 0x88, 0x73, 0x98, 0x04, 0x4b, 0x16, 0x79, 0x16, 0x94, 0xc4, 0xf5,
|
||||||
0x09, 0xe2, 0x1c, 0xf8, 0x38, 0x18, 0x6f, 0x78, 0x81, 0x83, 0xc5, 0x3e, 0x4e, 0x26, 0x3b, 0xf3,
|
0x63, 0x8f, 0x20, 0xce, 0x81, 0x8f, 0x83, 0xf1, 0x86, 0xeb, 0xdb, 0x58, 0xec, 0xe3, 0x64, 0xbc,
|
||||||
0x02, 0xa7, 0x22, 0xc9, 0x85, 0xcf, 0x80, 0x29, 0x93, 0x50, 0x23, 0xb0, 0x7c, 0x06, 0xad, 0x8f,
|
0x33, 0x2f, 0x70, 0x2a, 0x92, 0x5c, 0xf8, 0x0c, 0x98, 0x32, 0x08, 0xad, 0xfb, 0xa6, 0xc7, 0xa0,
|
||||||
0x71, 0xe1, 0x33, 0x52, 0x78, 0x6a, 0x3d, 0x61, 0xa1, 0xb4, 0x1c, 0xbc, 0x04, 0x8a, 0x7e, 0x60,
|
0xf5, 0x31, 0x2e, 0x7c, 0x4e, 0x0a, 0x4f, 0x6d, 0xc4, 0x2c, 0x94, 0x94, 0x83, 0x97, 0x41, 0xde,
|
||||||
0x79, 0x81, 0x15, 0x1e, 0xeb, 0x85, 0x25, 0x6d, 0xb9, 0x50, 0x9b, 0x93, 0x3a, 0xc5, 0x5d, 0x49,
|
0xf3, 0x4d, 0xd7, 0x37, 0x83, 0x63, 0x3d, 0xb7, 0xac, 0xad, 0xe4, 0x2a, 0x73, 0x52, 0x27, 0xbf,
|
||||||
0x47, 0x4a, 0x02, 0x2e, 0x81, 0xe2, 0x4b, 0xf5, 0x9d, 0xed, 0x5d, 0x1c, 0xee, 0xeb, 0xe3, 0x1c,
|
0x2b, 0xe9, 0x48, 0x49, 0xc0, 0x65, 0x90, 0x7f, 0xa9, 0x56, 0xdd, 0xd9, 0xc5, 0xc1, 0xbe, 0x3e,
|
||||||
0x61, 0x8c, 0x49, 0xa3, 0xe2, 0x4d, 0x49, 0xad, 0xfc, 0x33, 0x07, 0xf4, 0x6c, 0x54, 0xe2, 0x90,
|
0xce, 0x11, 0xc6, 0x98, 0x34, 0xca, 0xdf, 0x92, 0xd4, 0xd2, 0x3f, 0x32, 0x40, 0x4f, 0x47, 0x25,
|
||||||
0xc2, 0x17, 0x40, 0x91, 0x86, 0xac, 0xe2, 0x34, 0x8f, 0x65, 0x4c, 0x9e, 0x88, 0xc1, 0xea, 0x92,
|
0x0a, 0x29, 0x7c, 0x01, 0xe4, 0x69, 0xc0, 0x2a, 0x4e, 0xf3, 0x58, 0xc6, 0xe4, 0x89, 0x08, 0xac,
|
||||||
0x7e, 0xd2, 0x2e, 0x2f, 0x24, 0x1a, 0x31, 0x95, 0xc7, 0x43, 0xe9, 0xc2, 0xdf, 0x6a, 0xe0, 0xcc,
|
0x26, 0xe9, 0x27, 0xed, 0xe2, 0x62, 0xac, 0x11, 0x51, 0x79, 0x3c, 0x94, 0x2e, 0xfc, 0x8d, 0x06,
|
||||||
0x21, 0xd9, 0xdb, 0xf7, 0xbc, 0x83, 0x35, 0xdb, 0x22, 0x6e, 0xb8, 0xe6, 0xb9, 0x0d, 0xab, 0x29,
|
0xce, 0x1d, 0x92, 0xbd, 0x7d, 0xd7, 0x3d, 0x58, 0xb7, 0x4c, 0xe2, 0x04, 0xeb, 0xae, 0xd3, 0x30,
|
||||||
0x73, 0x00, 0xdd, 0x67, 0x0e, 0xbc, 0xda, 0x6b, 0xb9, 0xf6, 0x50, 0xa7, 0x5d, 0x3e, 0xd3, 0x87,
|
0x9b, 0x32, 0x07, 0xd0, 0x7d, 0xe6, 0xc0, 0xab, 0xbd, 0x96, 0x2b, 0x0f, 0x75, 0xda, 0xc5, 0x73,
|
||||||
0x81, 0xfa, 0xf9, 0x01, 0x6f, 0x00, 0xdd, 0xc8, 0x1c, 0x12, 0x59, 0xc0, 0x44, 0xd9, 0x9a, 0xac,
|
0x7d, 0x18, 0xa8, 0x9f, 0x1f, 0xf0, 0x26, 0xd0, 0xeb, 0xa9, 0x43, 0x22, 0x0b, 0x98, 0x28, 0x5b,
|
||||||
0x5d, 0xe8, 0xb4, 0xcb, 0xfa, 0xda, 0x00, 0x19, 0x34, 0x50, 0xbb, 0xf2, 0xc3, 0x7c, 0x36, 0xbc,
|
0x93, 0x95, 0x8b, 0x9d, 0x76, 0x51, 0x5f, 0x1f, 0x20, 0x83, 0x06, 0x6a, 0x97, 0x7e, 0x98, 0x4d,
|
||||||
0xa9, 0x74, 0x7b, 0x1b, 0x14, 0xd9, 0x31, 0x36, 0x71, 0x88, 0xe5, 0x41, 0x7c, 0xf2, 0xde, 0x0e,
|
0x87, 0x37, 0x91, 0x6e, 0x6f, 0x83, 0x3c, 0x3b, 0xc6, 0x06, 0x0e, 0xb0, 0x3c, 0x88, 0x4f, 0x9e,
|
||||||
0xbd, 0xa8, 0x19, 0x5b, 0x24, 0xc4, 0x35, 0x28, 0x37, 0x04, 0x24, 0x34, 0xa4, 0xac, 0xc2, 0xef,
|
0xee, 0xd0, 0x8b, 0x9a, 0xb1, 0x4d, 0x02, 0x5c, 0x81, 0x72, 0x43, 0x40, 0x4c, 0x43, 0xca, 0x2a,
|
||||||
0x80, 0x31, 0xea, 0x13, 0x43, 0x06, 0xfa, 0xb5, 0xfb, 0x3d, 0x6c, 0x03, 0x16, 0x52, 0xf7, 0x89,
|
0xfc, 0x0e, 0x18, 0xa3, 0x1e, 0xa9, 0xcb, 0x40, 0xbf, 0x76, 0xbf, 0x87, 0x6d, 0xc0, 0x42, 0x6a,
|
||||||
0x91, 0x9c, 0x05, 0xf6, 0x85, 0x38, 0x2c, 0x7c, 0x57, 0x03, 0xe3, 0x94, 0x17, 0x28, 0x59, 0xd4,
|
0x1e, 0xa9, 0xc7, 0x67, 0x81, 0xfd, 0x42, 0x1c, 0x16, 0xbe, 0xab, 0x81, 0x71, 0xca, 0x0b, 0x94,
|
||||||
0xde, 0x18, 0x95, 0x07, 0x99, 0x2a, 0x28, 0xbe, 0x91, 0x04, 0xaf, 0xfc, 0x27, 0x07, 0x2e, 0x0e,
|
0x2c, 0x6a, 0x6f, 0x8c, 0xca, 0x83, 0x54, 0x15, 0x14, 0xbf, 0x91, 0x04, 0x2f, 0xfd, 0x27, 0x03,
|
||||||
0x52, 0x5d, 0xf3, 0x5c, 0x53, 0x6c, 0xc7, 0xa6, 0x3c, 0xdb, 0x22, 0xd3, 0x9f, 0x49, 0x9f, 0xed,
|
0x2e, 0x0d, 0x52, 0x5d, 0x77, 0x1d, 0x43, 0x6c, 0xc7, 0x96, 0x3c, 0xdb, 0x22, 0xd3, 0x9f, 0x49,
|
||||||
0x93, 0x76, 0xf9, 0xb1, 0xbb, 0x1a, 0x48, 0x15, 0x81, 0xaf, 0xa9, 0x75, 0x8b, 0x42, 0x71, 0xb1,
|
0x9e, 0xed, 0x93, 0x76, 0xf1, 0xb1, 0x7b, 0x1a, 0x48, 0x14, 0x81, 0xaf, 0xa9, 0x75, 0x8b, 0x42,
|
||||||
0xdb, 0xb1, 0x93, 0x76, 0x79, 0x56, 0xa9, 0x75, 0xfb, 0x0a, 0x5b, 0x00, 0xda, 0x98, 0x86, 0xd7,
|
0x71, 0xa9, 0xdb, 0xb1, 0x93, 0x76, 0x71, 0x56, 0xa9, 0x75, 0xfb, 0x0a, 0x5b, 0x00, 0x5a, 0x98,
|
||||||
0x02, 0xec, 0x52, 0x61, 0xd6, 0x72, 0x88, 0x0c, 0xdf, 0x13, 0xf7, 0x96, 0x1e, 0x4c, 0xa3, 0xb6,
|
0x06, 0xd7, 0x7d, 0xec, 0x50, 0x61, 0xd6, 0xb4, 0x89, 0x0c, 0xdf, 0x13, 0xa7, 0x4b, 0x0f, 0xa6,
|
||||||
0x28, 0x21, 0xe1, 0xd5, 0x1e, 0x6b, 0xa8, 0x0f, 0x02, 0xab, 0x5b, 0x01, 0xc1, 0x54, 0x95, 0xa2,
|
0x51, 0x59, 0x92, 0x90, 0xf0, 0x5a, 0x8f, 0x35, 0xd4, 0x07, 0x81, 0xd5, 0x2d, 0x9f, 0x60, 0xaa,
|
||||||
0xd4, 0x8d, 0xc2, 0xa8, 0x48, 0x72, 0xe1, 0x97, 0xc0, 0x84, 0x43, 0x28, 0xc5, 0x4d, 0xc2, 0xeb,
|
0x4a, 0x51, 0xe2, 0x46, 0x61, 0x54, 0x24, 0xb9, 0xf0, 0x4b, 0x60, 0xc2, 0x26, 0x94, 0xe2, 0x26,
|
||||||
0xcf, 0x64, 0x72, 0x45, 0x6f, 0x09, 0x32, 0x8a, 0xf9, 0xac, 0x3f, 0xb9, 0x30, 0x28, 0x6a, 0x57,
|
0xe1, 0xf5, 0x67, 0x32, 0xbe, 0xa2, 0xb7, 0x05, 0x19, 0x45, 0x7c, 0xd6, 0x9f, 0x5c, 0x1c, 0x14,
|
||||||
0x2d, 0x1a, 0xc2, 0xd7, 0x7b, 0x0e, 0x40, 0xf5, 0xde, 0x56, 0xc8, 0xb4, 0x79, 0xfa, 0xab, 0xe2,
|
0xb5, 0x6b, 0x26, 0x0d, 0xe0, 0xeb, 0x3d, 0x07, 0xa0, 0x7c, 0xba, 0x15, 0x32, 0x6d, 0x9e, 0xfe,
|
||||||
0x17, 0x53, 0x52, 0xc9, 0xff, 0x6d, 0x50, 0xb0, 0x42, 0xe2, 0xc4, 0x77, 0xf7, 0xab, 0x23, 0xca,
|
0xaa, 0xf8, 0x45, 0x94, 0x44, 0xf2, 0x7f, 0x1b, 0xe4, 0xcc, 0x80, 0xd8, 0xd1, 0xdd, 0xfd, 0xea,
|
||||||
0xbd, 0xda, 0x8c, 0xf4, 0xa1, 0xb0, 0xc9, 0xd0, 0x90, 0x00, 0xad, 0xfc, 0x21, 0x07, 0x1e, 0x19,
|
0x88, 0x72, 0xaf, 0x32, 0x23, 0x7d, 0xc8, 0x6d, 0x31, 0x34, 0x24, 0x40, 0x4b, 0xbf, 0xcf, 0x80,
|
||||||
0xa4, 0xc2, 0x2e, 0x14, 0xca, 0x22, 0xee, 0xdb, 0x51, 0x80, 0x6d, 0x99, 0x71, 0x2a, 0xe2, 0xbb,
|
0x47, 0x06, 0xa9, 0xb0, 0x0b, 0x85, 0xb2, 0x88, 0x7b, 0x56, 0xe8, 0x63, 0x4b, 0x66, 0x9c, 0x8a,
|
||||||
0x9c, 0x8a, 0x24, 0x97, 0x95, 0x7c, 0x6a, 0xb9, 0xcd, 0xc8, 0xc6, 0x81, 0x4c, 0x27, 0xb5, 0xea,
|
0xf8, 0x2e, 0xa7, 0x22, 0xc9, 0x65, 0x25, 0x9f, 0x9a, 0x4e, 0x33, 0xb4, 0xb0, 0x2f, 0xd3, 0x49,
|
||||||
0xba, 0xa4, 0x23, 0x25, 0x01, 0xab, 0x00, 0xd0, 0x7d, 0x2f, 0x08, 0x39, 0x86, 0xac, 0x5e, 0xa7,
|
0xad, 0xba, 0x26, 0xe9, 0x48, 0x49, 0xc0, 0x32, 0x00, 0x74, 0xdf, 0xf5, 0x03, 0x8e, 0x21, 0xab,
|
||||||
0x59, 0x81, 0xa8, 0x2b, 0x2a, 0x4a, 0x49, 0xb0, 0x1b, 0xed, 0xc0, 0x72, 0x4d, 0xb9, 0xeb, 0xea,
|
0xd7, 0x59, 0x56, 0x20, 0x6a, 0x8a, 0x8a, 0x12, 0x12, 0xec, 0x46, 0x3b, 0x30, 0x1d, 0x43, 0xee,
|
||||||
0x14, 0xbf, 0x6c, 0xb9, 0x26, 0xe2, 0x1c, 0x86, 0x6f, 0x5b, 0x34, 0x64, 0x14, 0xb9, 0xe5, 0x5d,
|
0xba, 0x3a, 0xc5, 0x2f, 0x9b, 0x8e, 0x81, 0x38, 0x87, 0xe1, 0x5b, 0x26, 0x0d, 0x18, 0x45, 0x6e,
|
||||||
0x51, 0xe7, 0x92, 0x4a, 0x82, 0xe1, 0x1b, 0xac, 0xea, 0x7b, 0x81, 0x45, 0xa8, 0x3e, 0x9e, 0xe0,
|
0x79, 0x57, 0xd4, 0xb9, 0xa4, 0x92, 0x60, 0xf8, 0x75, 0x56, 0xf5, 0x5d, 0xdf, 0x24, 0x54, 0x1f,
|
||||||
0xaf, 0x29, 0x2a, 0x4a, 0x49, 0x54, 0x7e, 0x5d, 0x1c, 0x9c, 0x24, 0xac, 0x94, 0xc0, 0x47, 0x41,
|
0x8f, 0xf1, 0xd7, 0x15, 0x15, 0x25, 0x24, 0x4a, 0xff, 0xca, 0x0f, 0x4e, 0x12, 0x56, 0x4a, 0xe0,
|
||||||
0xa1, 0x19, 0x78, 0x91, 0x2f, 0xa3, 0xa4, 0xa2, 0xfd, 0x22, 0x23, 0x22, 0xc1, 0x63, 0x59, 0xd9,
|
0xa3, 0x20, 0xd7, 0xf4, 0xdd, 0xd0, 0x93, 0x51, 0x52, 0xd1, 0x7e, 0x91, 0x11, 0x91, 0xe0, 0xb1,
|
||||||
0xea, 0x6a, 0x53, 0x55, 0x56, 0xc6, 0xcd, 0x69, 0xcc, 0x87, 0xdf, 0xd7, 0x40, 0xc1, 0x95, 0xc1,
|
0xac, 0x6c, 0x75, 0xb5, 0xa9, 0x2a, 0x2b, 0xa3, 0xe6, 0x34, 0xe2, 0xc3, 0xef, 0x6b, 0x20, 0xe7,
|
||||||
0x61, 0x29, 0xf7, 0xfa, 0x88, 0xf2, 0x82, 0x87, 0x37, 0x71, 0x57, 0x44, 0x5e, 0x20, 0xc3, 0xa7,
|
0xc8, 0xe0, 0xb0, 0x94, 0x7b, 0x7d, 0x44, 0x79, 0xc1, 0xc3, 0x1b, 0xbb, 0x2b, 0x22, 0x2f, 0x90,
|
||||||
0x41, 0x81, 0x1a, 0x9e, 0x4f, 0x64, 0xd4, 0x4b, 0xb1, 0x50, 0x9d, 0x11, 0x4f, 0xda, 0xe5, 0x99,
|
0xe1, 0xd3, 0x20, 0x47, 0xeb, 0xae, 0x47, 0x64, 0xd4, 0x0b, 0x91, 0x50, 0x8d, 0x11, 0x4f, 0xda,
|
||||||
0xd8, 0x1c, 0x27, 0x20, 0x21, 0x0c, 0x7f, 0xa4, 0x01, 0xd0, 0xc2, 0xb6, 0x65, 0x62, 0xde, 0x32,
|
0xc5, 0x99, 0xc8, 0x1c, 0x27, 0x20, 0x21, 0x0c, 0x7f, 0xa4, 0x01, 0xd0, 0xc2, 0x96, 0x69, 0x60,
|
||||||
0x14, 0xb8, 0xfb, 0xc3, 0x4d, 0xeb, 0x57, 0x94, 0x79, 0xb1, 0x69, 0xc9, 0x37, 0x4a, 0x41, 0xc3,
|
0xde, 0x32, 0xe4, 0xb8, 0xfb, 0xc3, 0x4d, 0xeb, 0x57, 0x94, 0x79, 0xb1, 0x69, 0xf1, 0x6f, 0x94,
|
||||||
0xf7, 0x34, 0x30, 0x4d, 0xa3, 0xbd, 0x40, 0x6a, 0x51, 0xde, 0x5c, 0x4c, 0x5d, 0xf9, 0xe6, 0x50,
|
0x80, 0x86, 0xef, 0x69, 0x60, 0x9a, 0x86, 0x7b, 0xbe, 0xd4, 0xa2, 0xbc, 0xb9, 0x98, 0xba, 0xfa,
|
||||||
0x7d, 0xa9, 0xa7, 0x00, 0x6a, 0x73, 0x9d, 0x76, 0x79, 0x3a, 0x4d, 0x41, 0x5d, 0x0e, 0xc0, 0x9f,
|
0xcd, 0xa1, 0xfa, 0x52, 0x4b, 0x00, 0x54, 0xe6, 0x3a, 0xed, 0xe2, 0x74, 0x92, 0x82, 0xba, 0x1c,
|
||||||
0x6a, 0xa0, 0xd8, 0x8a, 0xef, 0xec, 0x09, 0x7e, 0xe0, 0xdf, 0x1c, 0xd1, 0xc6, 0xca, 0x8c, 0x4a,
|
0x80, 0x3f, 0xd5, 0x40, 0xbe, 0x15, 0xdd, 0xd9, 0x13, 0xfc, 0xc0, 0xbf, 0x39, 0xa2, 0x8d, 0x95,
|
||||||
0x4e, 0x81, 0xea, 0x03, 0x94, 0x07, 0xf0, 0x6f, 0x1a, 0xd0, 0xb1, 0x29, 0x0a, 0x3c, 0xb6, 0x77,
|
0x19, 0x15, 0x9f, 0x02, 0xd5, 0x07, 0x28, 0x0f, 0xe0, 0x5f, 0x35, 0xa0, 0x63, 0x43, 0x14, 0x78,
|
||||||
0x03, 0xcb, 0x0d, 0x49, 0x20, 0xfa, 0x4d, 0xaa, 0x17, 0xb9, 0x7b, 0xc3, 0xbd, 0x0b, 0xb3, 0xbd,
|
0x6c, 0xed, 0xfa, 0xa6, 0x13, 0x10, 0x5f, 0xf4, 0x9b, 0x54, 0xcf, 0x73, 0xf7, 0x86, 0x7b, 0x17,
|
||||||
0x6c, 0x6d, 0x49, 0x7a, 0xa7, 0xaf, 0x0e, 0x70, 0x03, 0x0d, 0x74, 0x90, 0x27, 0x5a, 0xd2, 0xd2,
|
0xa6, 0x7b, 0xd9, 0xca, 0xb2, 0xf4, 0x4e, 0x5f, 0x1b, 0xe0, 0x06, 0x1a, 0xe8, 0x20, 0x4f, 0xb4,
|
||||||
0xe8, 0x93, 0x23, 0x48, 0xb4, 0xa4, 0x97, 0x92, 0xd5, 0x21, 0xe9, 0xa0, 0x52, 0xd0, 0x95, 0xf7,
|
0xb8, 0xa5, 0xd1, 0x27, 0x47, 0x90, 0x68, 0x71, 0x2f, 0x25, 0xab, 0x43, 0xdc, 0x41, 0x25, 0xa0,
|
||||||
0xf2, 0xd9, 0xa6, 0x3d, 0x7b, 0xe9, 0xc3, 0x0f, 0x84, 0xb3, 0x62, 0x29, 0x54, 0xd7, 0x78, 0x70,
|
0x61, 0x15, 0x2c, 0x78, 0x3e, 0xe1, 0x00, 0x37, 0x9c, 0x03, 0xc7, 0x3d, 0x74, 0x5e, 0x30, 0x89,
|
||||||
0xdf, 0x1e, 0xd1, 0xde, 0xab, 0x5b, 0x3b, 0x69, 0xbc, 0x14, 0x89, 0xa2, 0x94, 0x1f, 0xf0, 0x57,
|
0x65, 0x50, 0x1d, 0x2c, 0x6b, 0x2b, 0xf9, 0xca, 0x85, 0x4e, 0xbb, 0xb8, 0xb0, 0xdb, 0x4f, 0x00,
|
||||||
0x1a, 0x98, 0xc1, 0x86, 0x41, 0xfc, 0x90, 0x98, 0xa2, 0x16, 0xe7, 0x3e, 0x87, 0x72, 0x73, 0x4e,
|
0xf5, 0xd7, 0x2b, 0xbd, 0x97, 0x4d, 0xbf, 0x02, 0xd2, 0x5d, 0x04, 0xfc, 0x40, 0xac, 0x5e, 0xc4,
|
||||||
0x7a, 0x35, 0xb3, 0x9a, 0x86, 0x46, 0xdd, 0x9e, 0xc0, 0xe7, 0xc1, 0x69, 0x1a, 0x7a, 0x01, 0x31,
|
0x86, 0xea, 0x1a, 0xdf, 0xad, 0xb7, 0x47, 0x94, 0x4c, 0xaa, 0x0d, 0x88, 0x3b, 0x39, 0x45, 0xa2,
|
||||||
0x33, 0x5d, 0x2e, 0xec, 0xb4, 0xcb, 0xa7, 0xeb, 0x5d, 0x1c, 0x94, 0x91, 0xac, 0x7c, 0x3a, 0x06,
|
0x28, 0xe1, 0x07, 0xfc, 0x95, 0x06, 0x66, 0x70, 0xbd, 0x4e, 0xbc, 0x80, 0x18, 0xa2, 0xb8, 0x67,
|
||||||
0xca, 0x77, 0x39, 0x19, 0xf7, 0xf0, 0x8e, 0x7a, 0x1c, 0x8c, 0xf3, 0xe5, 0x9a, 0x3c, 0x2a, 0xc5,
|
0x3e, 0x87, 0xfa, 0xb5, 0x20, 0xbd, 0x9a, 0x59, 0x4b, 0x42, 0xa3, 0x6e, 0x4f, 0xe0, 0xf3, 0xe0,
|
||||||
0x54, 0xe7, 0xc6, 0xa9, 0x48, 0x72, 0x59, 0x5d, 0x67, 0xf8, 0xac, 0xdb, 0xc8, 0x73, 0x41, 0x55,
|
0x2c, 0x0d, 0x5c, 0x9f, 0x18, 0xa9, 0xb6, 0x19, 0x76, 0xda, 0xc5, 0xb3, 0xb5, 0x2e, 0x0e, 0x4a,
|
||||||
0xd7, 0xeb, 0x82, 0x8c, 0x62, 0x3e, 0x7c, 0x07, 0x8c, 0x8b, 0x39, 0x09, 0x2f, 0xaa, 0x23, 0x2c,
|
0x49, 0x96, 0x3e, 0x1d, 0x03, 0xc5, 0x7b, 0x1c, 0xb5, 0x53, 0x3c, 0xcc, 0x1e, 0x07, 0xe3, 0x7c,
|
||||||
0x8c, 0x80, 0xfb, 0xc9, 0xa1, 0x90, 0x84, 0xec, 0x2d, 0x88, 0x85, 0x07, 0x5d, 0x10, 0xef, 0x58,
|
0xb9, 0x06, 0x8f, 0x4a, 0x3e, 0xd1, 0x0a, 0x72, 0x2a, 0x92, 0x5c, 0x76, 0x51, 0x30, 0x7c, 0xd6,
|
||||||
0x81, 0xc6, 0xff, 0xcf, 0x2b, 0x50, 0xe5, 0xbf, 0x5a, 0xf6, 0xdc, 0xa7, 0x96, 0x5a, 0x37, 0xb0,
|
0xbe, 0x64, 0xb9, 0xa0, 0xba, 0x28, 0x6a, 0x82, 0x8c, 0x22, 0x3e, 0x7c, 0x07, 0x8c, 0x8b, 0xc1,
|
||||||
0x4d, 0xe0, 0x3a, 0x98, 0x63, 0x8f, 0x0c, 0x44, 0x7c, 0xdb, 0x32, 0x30, 0xe5, 0x6f, 0x5c, 0x91,
|
0x0b, 0xaf, 0xd2, 0x23, 0xac, 0xb4, 0x80, 0xfb, 0xc9, 0xa1, 0x90, 0x84, 0xec, 0xad, 0xb0, 0xb9,
|
||||||
0x70, 0x6a, 0xec, 0x52, 0xcf, 0xf0, 0x51, 0x8f, 0x06, 0x7c, 0x09, 0x40, 0xd1, 0x78, 0x77, 0xd9,
|
0x07, 0x5d, 0x61, 0xef, 0x5a, 0xd2, 0xc6, 0xff, 0xcf, 0x4b, 0x5a, 0xe9, 0xbf, 0x5a, 0xfa, 0xdc,
|
||||||
0x11, 0x3d, 0x84, 0x6a, 0xa1, 0xeb, 0x3d, 0x12, 0xa8, 0x8f, 0x16, 0x5c, 0x03, 0xf3, 0x36, 0xde,
|
0x27, 0x96, 0x5a, 0xab, 0x63, 0x8b, 0xc0, 0x0d, 0x30, 0xc7, 0x5e, 0x2d, 0x88, 0x78, 0x96, 0x59,
|
||||||
0x23, 0x76, 0x9d, 0xd8, 0xc4, 0x08, 0xbd, 0x80, 0x9b, 0x12, 0x53, 0x80, 0x73, 0x9d, 0x76, 0x79,
|
0xc7, 0x94, 0x3f, 0x9a, 0x45, 0xc2, 0xa9, 0x39, 0x4e, 0x2d, 0xc5, 0x47, 0x3d, 0x1a, 0xf0, 0x25,
|
||||||
0xfe, 0x6a, 0x96, 0x89, 0x7a, 0xe5, 0x2b, 0x17, 0xb3, 0xc7, 0x2b, 0xbd, 0x70, 0xf1, 0x9c, 0xf9,
|
0x00, 0x45, 0x27, 0xdf, 0x65, 0x47, 0x34, 0x25, 0xaa, 0x27, 0xaf, 0xf5, 0x48, 0xa0, 0x3e, 0x5a,
|
||||||
0x30, 0x07, 0x16, 0x07, 0x67, 0x06, 0xfc, 0x41, 0xf2, 0xea, 0x12, 0x4d, 0xf5, 0x9b, 0xa3, 0xca,
|
0x70, 0x1d, 0xcc, 0x5b, 0x78, 0x8f, 0x58, 0x35, 0x62, 0x91, 0x7a, 0xe0, 0xfa, 0xdc, 0x94, 0x18,
|
||||||
0x42, 0xf9, 0xec, 0x02, 0xbd, 0x4f, 0x2e, 0xf8, 0x5d, 0xd6, 0xe1, 0x60, 0x3b, 0x9e, 0xf3, 0xbc,
|
0x2b, 0x2c, 0x74, 0xda, 0xc5, 0xf9, 0x6b, 0x69, 0x26, 0xea, 0x95, 0x2f, 0x5d, 0x4a, 0x1f, 0xaf,
|
||||||
0x31, 0x32, 0x17, 0x18, 0x48, 0x6d, 0x52, 0x34, 0x4f, 0xd8, 0xe6, 0xbd, 0x12, 0xb6, 0x49, 0xe5,
|
0xe4, 0xc2, 0xc5, 0xfb, 0xe8, 0xc3, 0x0c, 0x58, 0x1a, 0x9c, 0x19, 0xf0, 0x07, 0xf1, 0x33, 0x4e,
|
||||||
0x8f, 0x5a, 0xf6, 0xe1, 0x9d, 0x9c, 0x60, 0xf8, 0x33, 0x0d, 0xcc, 0x7a, 0x3e, 0x71, 0x57, 0x77,
|
0x74, 0xe9, 0x6f, 0x8e, 0x2a, 0x0b, 0xe5, 0x3b, 0x0e, 0xf4, 0xbe, 0xe1, 0xe0, 0x77, 0x59, 0xcb,
|
||||||
0x37, 0x5f, 0xf9, 0x8a, 0x38, 0xc9, 0x32, 0x54, 0xdb, 0xf7, 0xe9, 0xe7, 0x4b, 0xf5, 0x9d, 0x6d,
|
0x84, 0xad, 0x68, 0x70, 0xf4, 0xc6, 0xc8, 0x5c, 0x60, 0x20, 0x95, 0x49, 0xd1, 0x8d, 0x61, 0x8b,
|
||||||
0x61, 0x70, 0x37, 0xf0, 0x7c, 0x5a, 0x3b, 0xd3, 0x69, 0x97, 0x67, 0x77, 0xba, 0xa1, 0x50, 0x16,
|
0x37, 0x5f, 0xd8, 0x22, 0xa5, 0x3f, 0x68, 0xe9, 0x97, 0x7c, 0x7c, 0x82, 0xe1, 0xcf, 0x34, 0x30,
|
||||||
0xbb, 0xe2, 0x80, 0x73, 0x1b, 0x47, 0x21, 0x09, 0x5c, 0x6c, 0xaf, 0x7b, 0x46, 0xe4, 0x10, 0x37,
|
0xeb, 0x7a, 0xc4, 0x59, 0xdb, 0xdd, 0x7a, 0xe5, 0x2b, 0xe2, 0x24, 0xcb, 0x50, 0xed, 0xdc, 0xa7,
|
||||||
0x14, 0x8e, 0x66, 0x86, 0x44, 0xda, 0x3d, 0x0e, 0x89, 0x1e, 0x01, 0xf9, 0x28, 0xb0, 0x65, 0x16,
|
0x9f, 0x2f, 0xd5, 0xaa, 0x3b, 0xc2, 0xe0, 0xae, 0xef, 0x7a, 0xb4, 0x72, 0xae, 0xd3, 0x2e, 0xce,
|
||||||
0x4f, 0xa9, 0x21, 0x28, 0xba, 0x8a, 0x18, 0xbd, 0x72, 0x11, 0x8c, 0x31, 0x3f, 0xe1, 0x79, 0x90,
|
0x56, 0xbb, 0xa1, 0x50, 0x1a, 0xbb, 0x64, 0x83, 0x85, 0xcd, 0xa3, 0x80, 0xf8, 0x0e, 0xb6, 0x36,
|
||||||
0x0f, 0xf0, 0x21, 0xb7, 0x3a, 0x5d, 0x9b, 0x60, 0x22, 0x08, 0x1f, 0x22, 0x46, 0xab, 0xfc, 0xbb,
|
0xdc, 0x7a, 0x68, 0x13, 0x27, 0x10, 0x8e, 0xa6, 0xa6, 0x4e, 0xda, 0x29, 0xa7, 0x4e, 0x8f, 0x80,
|
||||||
0x04, 0x66, 0x33, 0x6b, 0x81, 0x8b, 0x20, 0xa7, 0x26, 0xab, 0x40, 0x1a, 0xcd, 0x6d, 0xae, 0xa3,
|
0x6c, 0xe8, 0x5b, 0x32, 0x8b, 0xa7, 0xd4, 0x54, 0x15, 0x5d, 0x43, 0x8c, 0x5e, 0xba, 0x04, 0xc6,
|
||||||
0x9c, 0x65, 0xc2, 0x67, 0x55, 0xf1, 0x15, 0xa0, 0x65, 0x55, 0xcf, 0x39, 0x95, 0xb5, 0xb4, 0x89,
|
0x98, 0x9f, 0xf0, 0x02, 0xc8, 0xfa, 0xf8, 0x90, 0x5b, 0x9d, 0xae, 0x4c, 0x30, 0x11, 0x84, 0x0f,
|
||||||
0x39, 0xe6, 0x48, 0x5c, 0x38, 0x99, 0x0f, 0xa4, 0x21, 0x4f, 0x89, 0xf0, 0x81, 0x34, 0x10, 0xa3,
|
0x11, 0xa3, 0x95, 0xfe, 0x5d, 0x00, 0xb3, 0xa9, 0xb5, 0xc0, 0x25, 0x90, 0x51, 0xa3, 0x5a, 0x20,
|
||||||
0x7d, 0xd6, 0x09, 0x59, 0x3c, 0xa2, 0x2b, 0xdc, 0xc3, 0x88, 0x6e, 0xfc, 0x8e, 0x23, 0xba, 0x47,
|
0x8d, 0x66, 0xb6, 0x36, 0x50, 0xc6, 0x34, 0xe0, 0xb3, 0xaa, 0xf8, 0x0a, 0xd0, 0xa2, 0xaa, 0xe7,
|
||||||
0x41, 0x21, 0xb4, 0x42, 0x9b, 0xe8, 0x13, 0xdd, 0x2f, 0x8f, 0x6b, 0x8c, 0x88, 0x04, 0x0f, 0xde,
|
0x9c, 0xca, 0x7a, 0xe4, 0xd8, 0x1c, 0x73, 0x24, 0x2a, 0x9c, 0xcc, 0x07, 0xd2, 0x90, 0xa7, 0x44,
|
||||||
0x04, 0x13, 0x26, 0x69, 0xe0, 0xc8, 0x0e, 0xf5, 0x22, 0x4f, 0xa1, 0xb5, 0x21, 0xa4, 0x90, 0x98,
|
0xf8, 0x40, 0x1a, 0x88, 0xd1, 0x3e, 0xeb, 0xc8, 0x2d, 0x9a, 0xf9, 0xe5, 0x4e, 0x31, 0xf3, 0x1b,
|
||||||
0x9f, 0xae, 0x0b, 0xbb, 0x28, 0x06, 0x80, 0x8f, 0x81, 0x09, 0x07, 0x1f, 0x59, 0x4e, 0xe4, 0xf0,
|
0xbf, 0xeb, 0xcc, 0xef, 0x51, 0x90, 0x0b, 0xcc, 0xc0, 0x22, 0xfa, 0x44, 0xf7, 0x53, 0xe6, 0x3a,
|
||||||
0x9e, 0x4c, 0x13, 0x62, 0x5b, 0x82, 0x84, 0x62, 0x1e, 0xab, 0x8c, 0xe4, 0xc8, 0xb0, 0x23, 0x6a,
|
0x23, 0x22, 0xc1, 0x83, 0xb7, 0xc0, 0x84, 0x41, 0x1a, 0x38, 0xb4, 0x02, 0x3d, 0xcf, 0x53, 0x68,
|
||||||
0xb5, 0x88, 0x64, 0xea, 0x80, 0xdf, 0x9e, 0xaa, 0x32, 0x6e, 0x64, 0xf8, 0xa8, 0x47, 0x83, 0x83,
|
0x7d, 0x08, 0x29, 0x24, 0x06, 0xb2, 0x1b, 0xc2, 0x2e, 0x8a, 0x00, 0xe0, 0x63, 0x60, 0xc2, 0xc6,
|
||||||
0x59, 0x2e, 0x57, 0x9e, 0x4a, 0x81, 0x09, 0x12, 0x8a, 0x79, 0xdd, 0x60, 0x52, 0x7e, 0x7a, 0x10,
|
0x47, 0xa6, 0x1d, 0xda, 0xbc, 0xc9, 0xd3, 0x84, 0xd8, 0xb6, 0x20, 0xa1, 0x88, 0xc7, 0x2a, 0x23,
|
||||||
0x98, 0x54, 0xee, 0xd1, 0x80, 0x5f, 0x06, 0x93, 0x0e, 0x3e, 0xba, 0x4a, 0xdc, 0x66, 0xb8, 0xaf,
|
0x39, 0xaa, 0x5b, 0x21, 0x35, 0x5b, 0x44, 0x32, 0x65, 0x03, 0xa6, 0x2a, 0xe3, 0x66, 0x8a, 0x8f,
|
||||||
0xcf, 0x2c, 0x69, 0xcb, 0xf9, 0xda, 0x4c, 0xa7, 0x5d, 0x9e, 0xdc, 0x8a, 0x89, 0x28, 0xe1, 0x73,
|
0x7a, 0x34, 0x38, 0x98, 0xe9, 0x70, 0xe5, 0xa9, 0x04, 0x98, 0x20, 0xa1, 0x88, 0xd7, 0x0d, 0x26,
|
||||||
0x61, 0xcb, 0x95, 0xc2, 0xa7, 0x53, 0xc2, 0x31, 0x11, 0x25, 0x7c, 0xd6, 0x41, 0xf8, 0x38, 0x64,
|
0xe5, 0xa7, 0x07, 0x81, 0x49, 0xe5, 0x1e, 0x0d, 0xf8, 0x65, 0x30, 0x69, 0xe3, 0xa3, 0x6b, 0xc4,
|
||||||
0x87, 0x4b, 0x9f, 0xed, 0x7e, 0x19, 0xee, 0x0a, 0x32, 0x8a, 0xf9, 0x70, 0x19, 0x14, 0x1d, 0x7c,
|
0x69, 0x06, 0xfb, 0xfa, 0xcc, 0xb2, 0xb6, 0x92, 0xad, 0xcc, 0x74, 0xda, 0xc5, 0xc9, 0xed, 0x88,
|
||||||
0xc4, 0x5f, 0xf1, 0xfa, 0x1c, 0x37, 0xcb, 0x67, 0xc9, 0x5b, 0x92, 0x86, 0x14, 0x97, 0x4b, 0x5a,
|
0x88, 0x62, 0x3e, 0x17, 0x36, 0x1d, 0x29, 0x7c, 0x36, 0x21, 0x1c, 0x11, 0x51, 0xcc, 0x67, 0x1d,
|
||||||
0xae, 0x90, 0x9c, 0x4f, 0x49, 0x4a, 0x1a, 0x52, 0x5c, 0x96, 0xc4, 0x91, 0x6b, 0xdd, 0x8a, 0x88,
|
0x84, 0x87, 0x03, 0x76, 0xb8, 0xf4, 0xd9, 0xee, 0xa7, 0xe6, 0xae, 0x20, 0xa3, 0x88, 0x0f, 0x57,
|
||||||
0x10, 0x86, 0x3c, 0x32, 0x2a, 0x89, 0xaf, 0x27, 0x2c, 0x94, 0x96, 0x63, 0xaf, 0x68, 0x27, 0xb2,
|
0x40, 0xde, 0xc6, 0x47, 0x7c, 0x2c, 0xa0, 0xcf, 0x71, 0xb3, 0x7c, 0x38, 0xbd, 0x2d, 0x69, 0x48,
|
||||||
0x43, 0xcb, 0xb7, 0xc9, 0x4e, 0x43, 0x3f, 0xc3, 0xe3, 0xcf, 0xfb, 0xe4, 0x2d, 0x45, 0x45, 0x29,
|
0x71, 0xb9, 0xa4, 0xe9, 0x08, 0xc9, 0xf9, 0x84, 0xa4, 0xa4, 0x21, 0xc5, 0x65, 0x49, 0x1c, 0x3a,
|
||||||
0x09, 0x48, 0xc0, 0x18, 0x71, 0x23, 0x47, 0x3f, 0xcb, 0x2f, 0xf6, 0xa1, 0xa4, 0xa0, 0x3a, 0x39,
|
0xe6, 0xed, 0x90, 0x08, 0x61, 0xc8, 0x23, 0xa3, 0x92, 0xf8, 0x46, 0xcc, 0x42, 0x49, 0x39, 0xf6,
|
||||||
0x1b, 0x6e, 0xe4, 0x20, 0x6e, 0x1e, 0x3e, 0x0b, 0x66, 0x1c, 0x7c, 0xc4, 0xca, 0x01, 0x09, 0x42,
|
0x2c, 0xb7, 0x43, 0x2b, 0x30, 0x3d, 0x8b, 0x54, 0x1b, 0xfa, 0x39, 0x1e, 0x7f, 0xde, 0x78, 0x6f,
|
||||||
0xf6, 0xbe, 0x3f, 0xc7, 0x17, 0x3f, 0xcf, 0x3a, 0xce, 0xad, 0x34, 0x03, 0x75, 0xcb, 0x71, 0x45,
|
0x2b, 0x2a, 0x4a, 0x48, 0x40, 0x02, 0xc6, 0x88, 0x13, 0xda, 0xfa, 0x79, 0x7e, 0xb1, 0x0f, 0x25,
|
||||||
0xcb, 0x4d, 0x29, 0x2e, 0xa4, 0x14, 0xd3, 0x0c, 0xd4, 0x2d, 0xc7, 0x22, 0x1d, 0x90, 0x5b, 0x91,
|
0x05, 0xd5, 0xc9, 0xd9, 0x74, 0x42, 0x1b, 0x71, 0xf3, 0xf0, 0x59, 0x30, 0x63, 0xe3, 0x23, 0x56,
|
||||||
0x15, 0x10, 0x53, 0x7f, 0x88, 0x37, 0xa9, 0x72, 0xbe, 0x2f, 0x68, 0x48, 0x71, 0x61, 0x2b, 0x1e,
|
0x0e, 0x88, 0x1f, 0x98, 0x84, 0xea, 0x0b, 0x7c, 0xf1, 0xf3, 0xac, 0xe3, 0xdc, 0x4e, 0x32, 0x50,
|
||||||
0xf7, 0xe8, 0xfc, 0x18, 0x5e, 0x1f, 0x6e, 0x25, 0xdf, 0x09, 0x56, 0x83, 0x00, 0x1f, 0x8b, 0x9b,
|
0xb7, 0x1c, 0x57, 0x34, 0x9d, 0x84, 0xe2, 0x62, 0x42, 0x31, 0xc9, 0x40, 0xdd, 0x72, 0x2c, 0xd2,
|
||||||
0x26, 0x3d, 0xe8, 0x81, 0x14, 0x14, 0xb0, 0x6d, 0xef, 0x34, 0xf4, 0xf3, 0x3c, 0xf6, 0xc3, 0xbe,
|
0x3e, 0xb9, 0x1d, 0x9a, 0x3e, 0x31, 0xf4, 0x87, 0x78, 0x93, 0x2a, 0x3f, 0x18, 0x08, 0x1a, 0x52,
|
||||||
0x41, 0x54, 0xd5, 0x59, 0x65, 0x20, 0x48, 0x60, 0x31, 0x50, 0xcf, 0x65, 0xa9, 0xb1, 0x38, 0x5a,
|
0x5c, 0xd8, 0x8a, 0xe6, 0x47, 0x3a, 0x3f, 0x86, 0x37, 0x86, 0x5b, 0xc9, 0xab, 0xfe, 0x9a, 0xef,
|
||||||
0xd0, 0x1d, 0x06, 0x82, 0x04, 0x16, 0x5f, 0xa9, 0x7b, 0xbc, 0xd3, 0xd0, 0x1f, 0x1e, 0xf1, 0x4a,
|
0xe3, 0x63, 0x71, 0xd3, 0x24, 0x27, 0x47, 0x90, 0x82, 0x1c, 0xb6, 0xac, 0x6a, 0x43, 0xbf, 0xc0,
|
||||||
0x19, 0x08, 0x12, 0x58, 0xd0, 0x02, 0x79, 0xd7, 0x0b, 0xf5, 0x0b, 0x23, 0xb9, 0x9e, 0xf9, 0x85,
|
0x63, 0x3f, 0xec, 0x1b, 0x44, 0x55, 0x9d, 0x35, 0x06, 0x82, 0x04, 0x16, 0x03, 0x75, 0x1d, 0x96,
|
||||||
0xb3, 0xed, 0x85, 0x88, 0x61, 0xc0, 0x5f, 0x6a, 0x00, 0xf8, 0x49, 0x8a, 0x3e, 0x32, 0x94, 0x29,
|
0x1a, 0x4b, 0xa3, 0x05, 0xad, 0x32, 0x10, 0x24, 0xb0, 0xf8, 0x4a, 0x9d, 0xe3, 0x6a, 0x43, 0x7f,
|
||||||
0x42, 0x06, 0xb2, 0x9a, 0xe4, 0xf6, 0x86, 0x1b, 0x06, 0xc7, 0xc9, 0x3b, 0x32, 0x75, 0x06, 0x52,
|
0x78, 0xc4, 0x2b, 0x65, 0x20, 0x48, 0x60, 0x41, 0x13, 0x64, 0x1d, 0x37, 0xd0, 0x2f, 0x8e, 0xe4,
|
||||||
0x5e, 0xc0, 0xdf, 0x69, 0xe0, 0x6c, 0xba, 0x4d, 0x56, 0xee, 0x95, 0x78, 0x44, 0xae, 0x0d, 0x3b,
|
0x7a, 0xe6, 0x17, 0xce, 0x8e, 0x1b, 0x20, 0x86, 0x01, 0x7f, 0xa9, 0x01, 0xe0, 0xc5, 0x29, 0xfa,
|
||||||
0xcd, 0x6b, 0x9e, 0x67, 0xd7, 0xf4, 0x4e, 0xbb, 0x7c, 0x76, 0xb5, 0x0f, 0x2a, 0xea, 0xeb, 0x0b,
|
0xc8, 0x50, 0xc6, 0x12, 0x29, 0xc8, 0x72, 0x9c, 0xdb, 0x9b, 0x4e, 0xe0, 0x1f, 0xc7, 0xef, 0xc8,
|
||||||
0xfc, 0x93, 0x06, 0xe6, 0x65, 0x15, 0x4d, 0x79, 0x58, 0xe6, 0x01, 0x24, 0xc3, 0x0e, 0x60, 0x16,
|
0xc4, 0x19, 0x48, 0x78, 0x01, 0x7f, 0xab, 0x81, 0xf3, 0xc9, 0x36, 0x59, 0xb9, 0x57, 0xe0, 0x11,
|
||||||
0x47, 0xc4, 0x51, 0xfd, 0x2e, 0xdd, 0xc3, 0x47, 0xbd, 0xae, 0xc1, 0xbf, 0x6a, 0x60, 0xda, 0x24,
|
0xb9, 0x3e, 0xec, 0x34, 0xaf, 0xb8, 0xae, 0x55, 0xd1, 0x3b, 0xed, 0xe2, 0xf9, 0xb5, 0x3e, 0xa8,
|
||||||
0x3e, 0x71, 0x4d, 0xe2, 0x1a, 0xcc, 0xd7, 0xa5, 0xa1, 0x8c, 0x0d, 0xb2, 0xbe, 0xae, 0xa7, 0x20,
|
0xa8, 0xaf, 0x2f, 0xf0, 0x8f, 0x1a, 0x98, 0x97, 0x55, 0x34, 0xe1, 0x61, 0x91, 0x07, 0x90, 0x0c,
|
||||||
0x84, 0x9b, 0x55, 0xe9, 0xe6, 0x74, 0x9a, 0x75, 0xd2, 0x2e, 0x2f, 0x24, 0xaa, 0x69, 0x0e, 0xea,
|
0x3b, 0x80, 0x69, 0x1c, 0x11, 0x47, 0xf5, 0xa1, 0xbb, 0x87, 0x8f, 0x7a, 0x5d, 0x83, 0x7f, 0xd1,
|
||||||
0xf2, 0x12, 0xbe, 0xaf, 0x81, 0xd9, 0x64, 0x03, 0xc4, 0x95, 0x72, 0x71, 0x84, 0x79, 0xc0, 0xdb,
|
0xc0, 0xb4, 0x41, 0x3c, 0xe2, 0x18, 0xc4, 0xa9, 0x33, 0x5f, 0x97, 0x87, 0x32, 0x36, 0x48, 0xfb,
|
||||||
0xd7, 0xd5, 0x6e, 0x40, 0x94, 0xf5, 0x00, 0xfe, 0x59, 0x63, 0x9d, 0x5a, 0xfc, 0xee, 0xa3, 0x7a,
|
0xba, 0x91, 0x80, 0x10, 0x6e, 0x96, 0xa5, 0x9b, 0xd3, 0x49, 0xd6, 0x49, 0xbb, 0xb8, 0x18, 0xab,
|
||||||
0x85, 0xc7, 0xf2, 0xad, 0xa1, 0xc7, 0x52, 0x21, 0x88, 0x50, 0x5e, 0x4a, 0x5a, 0x41, 0xc5, 0x39,
|
0x26, 0x39, 0xa8, 0xcb, 0x4b, 0xf8, 0xbe, 0x06, 0x66, 0xe3, 0x0d, 0x10, 0x57, 0xca, 0xa5, 0x11,
|
||||||
0x69, 0x97, 0xcf, 0xa5, 0x23, 0xa9, 0x18, 0x28, 0xed, 0x21, 0xfc, 0x89, 0x06, 0xa6, 0x49, 0xd2,
|
0xe6, 0x01, 0x6f, 0x5f, 0xd7, 0xba, 0x01, 0x51, 0xda, 0x03, 0xf8, 0x27, 0x8d, 0x75, 0x6a, 0xd1,
|
||||||
0x71, 0x53, 0xfd, 0xd1, 0xa1, 0x04, 0xb1, 0x6f, 0x13, 0x2f, 0x5e, 0xea, 0x29, 0x16, 0x45, 0x5d,
|
0xbb, 0x8f, 0xea, 0x25, 0x1e, 0xcb, 0xb7, 0x86, 0x1e, 0x4b, 0x85, 0x20, 0x42, 0x79, 0x39, 0x6e,
|
||||||
0xd8, 0xac, 0x83, 0x24, 0x47, 0xd8, 0xf1, 0x6d, 0xa2, 0x7f, 0x61, 0xc8, 0x1d, 0xe4, 0x86, 0xb0,
|
0x05, 0x15, 0xe7, 0xa4, 0x5d, 0x5c, 0x48, 0x46, 0x52, 0x31, 0x50, 0xd2, 0x43, 0xf8, 0x13, 0x0d,
|
||||||
0x8b, 0x62, 0x00, 0x78, 0x09, 0x14, 0xdd, 0xc8, 0xb6, 0xf1, 0x9e, 0x4d, 0xf4, 0xc7, 0x78, 0x2f,
|
0x4c, 0x93, 0xb8, 0xe3, 0xa6, 0xfa, 0xa3, 0x43, 0x09, 0x62, 0xdf, 0x26, 0x5e, 0xbc, 0xd4, 0x13,
|
||||||
0xa2, 0xa6, 0x98, 0xdb, 0x92, 0x8e, 0x94, 0x04, 0x6c, 0x80, 0xa5, 0xa3, 0x97, 0xd5, 0x7f, 0xf4,
|
0x2c, 0x8a, 0xba, 0xb0, 0x59, 0x07, 0x49, 0x8e, 0xb0, 0xed, 0x59, 0x44, 0xff, 0xc2, 0x90, 0x3b,
|
||||||
0xec, 0x06, 0x84, 0xe3, 0x5f, 0x77, 0x0f, 0x5c, 0xef, 0xd0, 0x7d, 0xc1, 0x22, 0xb6, 0x49, 0xf5,
|
0xc8, 0x4d, 0x61, 0x17, 0x45, 0x00, 0xf0, 0x32, 0xc8, 0x3b, 0xa1, 0x65, 0xe1, 0x3d, 0x8b, 0xe8,
|
||||||
0xc7, 0xb9, 0x95, 0xc5, 0x4e, 0xbb, 0xbc, 0x70, 0xa3, 0xaf, 0x04, 0xba, 0xab, 0x0d, 0xf8, 0x1a,
|
0x8f, 0xf1, 0x5e, 0x44, 0x8d, 0x45, 0x77, 0x24, 0x1d, 0x29, 0x09, 0xd8, 0x00, 0xcb, 0x47, 0x2f,
|
||||||
0x78, 0x38, 0x25, 0xb3, 0xe1, 0xec, 0x11, 0xd3, 0x24, 0x66, 0xfc, 0x70, 0xd3, 0xbf, 0xc8, 0x21,
|
0xab, 0x7f, 0x11, 0xea, 0x3b, 0xb8, 0xd3, 0x1f, 0xe7, 0x56, 0x96, 0x3a, 0xed, 0xe2, 0xe2, 0xcd,
|
||||||
0xd4, 0x01, 0xbf, 0x91, 0x15, 0x40, 0x77, 0xd2, 0x86, 0x57, 0xc1, 0x42, 0x8a, 0xbd, 0xe9, 0x86,
|
0xfe, 0xa3, 0xbd, 0x7b, 0xda, 0x80, 0xaf, 0x81, 0x87, 0x13, 0x32, 0x9b, 0xf6, 0x1e, 0x31, 0x0c,
|
||||||
0x3b, 0x41, 0x3d, 0x0c, 0x2c, 0xb7, 0xa9, 0x2f, 0x73, 0xbb, 0x67, 0xe3, 0x13, 0x79, 0x23, 0xc5,
|
0x62, 0x44, 0x0f, 0x37, 0xfd, 0x8b, 0x62, 0x78, 0x18, 0x1d, 0xf0, 0x9b, 0x69, 0x01, 0x74, 0x37,
|
||||||
0x43, 0x03, 0x74, 0x16, 0xd9, 0xd3, 0x31, 0x53, 0x7a, 0xe0, 0x1c, 0xc8, 0x1f, 0x10, 0xf9, 0x0b,
|
0x6d, 0x78, 0x0d, 0x2c, 0x26, 0xd8, 0x5b, 0x4e, 0x50, 0xf5, 0x6b, 0x81, 0x6f, 0x3a, 0x4d, 0x7d,
|
||||||
0x39, 0x62, 0x7f, 0x42, 0x13, 0x14, 0x5a, 0xd8, 0x8e, 0xe2, 0xd7, 0xef, 0x90, 0xaf, 0x2d, 0x24,
|
0x85, 0xdb, 0x3d, 0x1f, 0x9d, 0xc8, 0x9b, 0x09, 0x1e, 0x1a, 0xa0, 0xb3, 0xc4, 0x9e, 0x8e, 0xa9,
|
||||||
0x8c, 0x3f, 0x9f, 0x7b, 0x4e, 0x5b, 0xfc, 0x40, 0x03, 0x0b, 0xfd, 0x2b, 0xe2, 0x03, 0x75, 0xeb,
|
0xd2, 0x03, 0xe7, 0x40, 0xf6, 0x80, 0xc8, 0x4f, 0xee, 0x88, 0xfd, 0x09, 0x0d, 0x90, 0x6b, 0x61,
|
||||||
0x37, 0x1a, 0x98, 0xef, 0x29, 0x7e, 0x7d, 0x3c, 0xba, 0xd5, 0xed, 0xd1, 0x6b, 0xc3, 0xae, 0x62,
|
0x2b, 0x8c, 0x5e, 0xbf, 0x43, 0xbe, 0xb6, 0x90, 0x30, 0xfe, 0x7c, 0xe6, 0x39, 0x6d, 0xe9, 0x03,
|
||||||
0x62, 0xd7, 0x78, 0xeb, 0x96, 0x76, 0xef, 0xe7, 0x1a, 0x98, 0xcb, 0xd6, 0x93, 0x07, 0x19, 0xaf,
|
0x0d, 0x2c, 0xf6, 0xaf, 0x88, 0x0f, 0xd4, 0xad, 0x5f, 0x6b, 0x60, 0xbe, 0xa7, 0xf8, 0xf5, 0xf1,
|
||||||
0xca, 0x07, 0x39, 0xb0, 0xd0, 0xbf, 0xe3, 0x84, 0x81, 0x7a, 0x5a, 0x8f, 0x66, 0x44, 0xd1, 0x6f,
|
0xe8, 0x76, 0xb7, 0x47, 0xaf, 0x0d, 0xbb, 0x8a, 0x89, 0x5d, 0xe3, 0xad, 0x5b, 0xd2, 0xbd, 0x9f,
|
||||||
0x9c, 0xf9, 0xae, 0x06, 0xa6, 0x6e, 0x2a, 0xb9, 0xf8, 0x17, 0xd4, 0xa1, 0x0f, 0x47, 0xe2, 0x02,
|
0x6b, 0x60, 0x2e, 0x5d, 0x4f, 0x1e, 0x64, 0xbc, 0x4a, 0x1f, 0x64, 0xc0, 0x62, 0xff, 0x8e, 0x13,
|
||||||
0x9e, 0x30, 0x28, 0x4a, 0xe3, 0x56, 0xfe, 0xa2, 0x81, 0x73, 0x7d, 0x6f, 0x26, 0xf6, 0x86, 0xc7,
|
0xfa, 0xea, 0x69, 0x3d, 0x9a, 0x11, 0x45, 0xbf, 0x71, 0xe6, 0xbb, 0x1a, 0x98, 0xba, 0xa5, 0xe4,
|
||||||
0xb6, 0xed, 0x1d, 0x8a, 0x19, 0x57, 0x6a, 0x80, 0xbc, 0xca, 0xa9, 0x48, 0x72, 0x53, 0xd1, 0xcb,
|
0xa2, 0x4f, 0xb2, 0x43, 0x1f, 0x8e, 0x44, 0x05, 0x3c, 0x66, 0x50, 0x94, 0xc4, 0x2d, 0xfd, 0x59,
|
||||||
0x7d, 0x5e, 0xd1, 0xab, 0xfc, 0x5d, 0x03, 0x17, 0xee, 0x94, 0x89, 0x0f, 0x64, 0x4b, 0x97, 0x41,
|
0x03, 0x0b, 0x7d, 0x6f, 0x26, 0xf6, 0x86, 0xc7, 0x96, 0xe5, 0x1e, 0x8a, 0x19, 0x57, 0x62, 0x80,
|
||||||
0x51, 0x76, 0x95, 0xc7, 0x7c, 0x3b, 0xe5, 0x43, 0x4a, 0x16, 0x0d, 0xfe, 0x4f, 0x43, 0xe2, 0xaf,
|
0xbc, 0xc6, 0xa9, 0x48, 0x72, 0x13, 0xd1, 0xcb, 0x7c, 0x5e, 0xd1, 0x2b, 0xfd, 0x4d, 0x03, 0x17,
|
||||||
0xca, 0x87, 0x1a, 0x98, 0xab, 0x93, 0xa0, 0x65, 0x19, 0x04, 0x91, 0x06, 0x09, 0x88, 0x6b, 0x10,
|
0xef, 0x96, 0x89, 0x0f, 0x64, 0x4b, 0x57, 0x40, 0x5e, 0x76, 0x95, 0xc7, 0x7c, 0x3b, 0xe5, 0x43,
|
||||||
0xb8, 0x02, 0x26, 0xf9, 0x4f, 0x97, 0x3e, 0x36, 0xe2, 0xb9, 0xfe, 0xbc, 0x0c, 0xf9, 0xe4, 0x76,
|
0x4a, 0x16, 0x0d, 0xfe, 0x5f, 0x48, 0xe2, 0xaf, 0xd2, 0x87, 0x1a, 0x98, 0xab, 0x11, 0xbf, 0x65,
|
||||||
0xcc, 0x40, 0x89, 0x8c, 0xfa, 0x0d, 0x20, 0x37, 0xf0, 0x37, 0x80, 0x0b, 0x60, 0xcc, 0x4f, 0x26,
|
0xd6, 0x09, 0x22, 0x0d, 0xe2, 0x13, 0xa7, 0x4e, 0xe0, 0x2a, 0x98, 0xe4, 0xdf, 0x42, 0x3d, 0x5c,
|
||||||
0xa4, 0x45, 0xc6, 0xe5, 0x43, 0x51, 0x4e, 0xe5, 0x5c, 0x2f, 0x08, 0xf9, 0xd8, 0xa7, 0x20, 0xb9,
|
0x8f, 0xe6, 0xfa, 0xf3, 0x32, 0xe4, 0x93, 0x3b, 0x11, 0x03, 0xc5, 0x32, 0xea, 0x1b, 0x40, 0x66,
|
||||||
0x5e, 0x10, 0x22, 0x4e, 0xad, 0xfc, 0x43, 0x03, 0xfd, 0xfe, 0xbd, 0x07, 0xb6, 0xc0, 0x04, 0x15,
|
0xe0, 0x37, 0x80, 0x8b, 0x60, 0xcc, 0x8b, 0x27, 0xa4, 0x79, 0xc6, 0xe5, 0x43, 0x51, 0x4e, 0xe5,
|
||||||
0xae, 0xcb, 0xd0, 0xee, 0xdc, 0x67, 0x68, 0xb3, 0x81, 0x10, 0xf7, 0x6a, 0x4c, 0x8d, 0xc1, 0x58,
|
0x5c, 0xd7, 0x0f, 0xf8, 0xd8, 0x27, 0x27, 0xb9, 0xae, 0x1f, 0x20, 0x4e, 0x2d, 0xfd, 0x5d, 0x03,
|
||||||
0x74, 0x0d, 0x5c, 0x8b, 0x5c, 0x53, 0x4e, 0x3c, 0xa7, 0x45, 0x74, 0xd7, 0x56, 0x05, 0x0d, 0x29,
|
0xfd, 0xfe, 0x5f, 0x08, 0xb6, 0xc0, 0x04, 0x15, 0xae, 0xcb, 0xd0, 0x56, 0xef, 0x33, 0xb4, 0xe9,
|
||||||
0x2e, 0x3c, 0x2f, 0x66, 0x73, 0xa9, 0x81, 0x57, 0x3c, 0x97, 0xab, 0x5d, 0xfe, 0xe8, 0x76, 0xe9,
|
0x40, 0x88, 0x7b, 0x35, 0xa2, 0x46, 0x60, 0x2c, 0xba, 0x75, 0x5c, 0x09, 0x1d, 0x43, 0x4e, 0x3c,
|
||||||
0xd4, 0xc7, 0xb7, 0x4b, 0xa7, 0x3e, 0xb9, 0x5d, 0x3a, 0xf5, 0xbd, 0x4e, 0x49, 0xfb, 0xa8, 0x53,
|
0xa7, 0x45, 0x74, 0xd7, 0xd7, 0x04, 0x0d, 0x29, 0x2e, 0xbc, 0x20, 0x66, 0x73, 0x89, 0x81, 0x57,
|
||||||
0xd2, 0x3e, 0xee, 0x94, 0xb4, 0x4f, 0x3a, 0x25, 0xed, 0x5f, 0x9d, 0x92, 0xf6, 0x8b, 0x4f, 0x4b,
|
0x34, 0x97, 0xab, 0x5c, 0xf9, 0xe8, 0x4e, 0xe1, 0xcc, 0xc7, 0x77, 0x0a, 0x67, 0x3e, 0xb9, 0x53,
|
||||||
0xa7, 0xbe, 0x35, 0x21, 0x5d, 0xfb, 0x5f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x05, 0x6b, 0x7f,
|
0x38, 0xf3, 0xbd, 0x4e, 0x41, 0xfb, 0xa8, 0x53, 0xd0, 0x3e, 0xee, 0x14, 0xb4, 0x4f, 0x3a, 0x05,
|
||||||
0x74, 0x2b, 0x00, 0x00,
|
0xed, 0x9f, 0x9d, 0x82, 0xf6, 0x8b, 0x4f, 0x0b, 0x67, 0xbe, 0x35, 0x21, 0x5d, 0xfb, 0x5f, 0x00,
|
||||||
|
0x00, 0x00, 0xff, 0xff, 0x10, 0x75, 0x48, 0x06, 0xc5, 0x2b, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
@@ -247,6 +247,12 @@ message CustomResourceDefinitionSpec {
|
|||||||
// `conversion` defines conversion settings for the CRD.
|
// `conversion` defines conversion settings for the CRD.
|
||||||
// +optional
|
// +optional
|
||||||
optional CustomResourceConversion conversion = 9;
|
optional CustomResourceConversion conversion = 9;
|
||||||
|
|
||||||
|
// preserveUnknownFields disables pruning of object fields which are not
|
||||||
|
// specified in the OpenAPI schema. apiVersion, kind, metadata and known
|
||||||
|
// fields inside metadata are always preserved.
|
||||||
|
// Defaults to true in v1beta and will default to false in v1.
|
||||||
|
optional bool preserveUnknownFields = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
|
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
|
||||||
|
@@ -78,6 +78,12 @@ type CustomResourceDefinitionSpec struct {
|
|||||||
// `conversion` defines conversion settings for the CRD.
|
// `conversion` defines conversion settings for the CRD.
|
||||||
// +optional
|
// +optional
|
||||||
Conversion *CustomResourceConversion `json:"conversion,omitempty" protobuf:"bytes,9,opt,name=conversion"`
|
Conversion *CustomResourceConversion `json:"conversion,omitempty" protobuf:"bytes,9,opt,name=conversion"`
|
||||||
|
|
||||||
|
// preserveUnknownFields disables pruning of object fields which are not
|
||||||
|
// specified in the OpenAPI schema. apiVersion, kind, metadata and known
|
||||||
|
// fields inside metadata are always preserved.
|
||||||
|
// Defaults to true in v1beta and will default to false in v1.
|
||||||
|
PreserveUnknownFields *bool `json:"preserveUnknownFields,omitempty" protobuf:"varint,10,opt,name=preserveUnknownFields"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CustomResourceConversion describes how to convert different versions of a CR.
|
// CustomResourceConversion describes how to convert different versions of a CR.
|
||||||
|
@@ -504,6 +504,7 @@ func autoConvert_v1beta1_CustomResourceDefinitionSpec_To_apiextensions_CustomRes
|
|||||||
} else {
|
} else {
|
||||||
out.Conversion = nil
|
out.Conversion = nil
|
||||||
}
|
}
|
||||||
|
out.PreserveUnknownFields = (*bool)(unsafe.Pointer(in.PreserveUnknownFields))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -550,6 +551,7 @@ func autoConvert_apiextensions_CustomResourceDefinitionSpec_To_v1beta1_CustomRes
|
|||||||
} else {
|
} else {
|
||||||
out.Conversion = nil
|
out.Conversion = nil
|
||||||
}
|
}
|
||||||
|
out.PreserveUnknownFields = (*bool)(unsafe.Pointer(in.PreserveUnknownFields))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -283,6 +283,11 @@ func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefiniti
|
|||||||
*out = new(CustomResourceConversion)
|
*out = new(CustomResourceConversion)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.PreserveUnknownFields != nil {
|
||||||
|
in, out := &in.PreserveUnknownFields, &out.PreserveUnknownFields
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@ go_library(
|
|||||||
deps = [
|
deps = [
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/features:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||||
|
@@ -21,6 +21,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
genericvalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
@@ -102,9 +103,9 @@ func ValidateUpdateCustomResourceDefinitionStatus(obj, oldObj *apiextensions.Cus
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateCustomResourceDefinitionVersion statically validates.
|
// ValidateCustomResourceDefinitionVersion statically validates.
|
||||||
func ValidateCustomResourceDefinitionVersion(version *apiextensions.CustomResourceDefinitionVersion, fldPath *field.Path, statusEnabled bool) field.ErrorList {
|
func ValidateCustomResourceDefinitionVersion(version *apiextensions.CustomResourceDefinitionVersion, fldPath *field.Path, mustBeStructural, statusEnabled bool) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionValidation(version.Schema, statusEnabled, fldPath.Child("schema"))...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionValidation(version.Schema, mustBeStructural, statusEnabled, fldPath.Child("schema"))...)
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionSubresources(version.Subresources, fldPath.Child("subresources"))...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionSubresources(version.Subresources, fldPath.Child("subresources"))...)
|
||||||
for i := range version.AdditionalPrinterColumns {
|
for i := range version.AdditionalPrinterColumns {
|
||||||
allErrs = append(allErrs, ValidateCustomResourceColumnDefinition(&version.AdditionalPrinterColumns[i], fldPath.Child("additionalPrinterColumns").Index(i))...)
|
allErrs = append(allErrs, ValidateCustomResourceColumnDefinition(&version.AdditionalPrinterColumns[i], fldPath.Child("additionalPrinterColumns").Index(i))...)
|
||||||
@@ -130,6 +131,20 @@ func validateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
|
|||||||
|
|
||||||
allErrs = append(allErrs, validateEnumStrings(fldPath.Child("scope"), string(spec.Scope), []string{string(apiextensions.ClusterScoped), string(apiextensions.NamespaceScoped)}, true)...)
|
allErrs = append(allErrs, validateEnumStrings(fldPath.Child("scope"), string(spec.Scope), []string{string(apiextensions.ClusterScoped), string(apiextensions.NamespaceScoped)}, true)...)
|
||||||
|
|
||||||
|
mustBeStructural := false
|
||||||
|
if spec.PreserveUnknownFields == nil || *spec.PreserveUnknownFields == false {
|
||||||
|
mustBeStructural = true
|
||||||
|
// check that either a global schema or versioned schemas are set
|
||||||
|
if spec.Validation == nil || spec.Validation.OpenAPIV3Schema == nil {
|
||||||
|
for i, v := range spec.Versions {
|
||||||
|
schemaPath := fldPath.Child("versions").Index(i).Child("schema", "openAPIV3Schema")
|
||||||
|
if v.Served && (v.Schema == nil || v.Schema.OpenAPIV3Schema == nil) {
|
||||||
|
allErrs = append(allErrs, field.Required(schemaPath, "because otherwise all fields are pruned"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
storageFlagCount := 0
|
storageFlagCount := 0
|
||||||
versionsMap := map[string]bool{}
|
versionsMap := map[string]bool{}
|
||||||
uniqueNames := true
|
uniqueNames := true
|
||||||
@@ -146,7 +161,7 @@ func validateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
|
|||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("versions").Index(i).Child("name"), spec.Versions[i].Name, strings.Join(errs, ",")))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("versions").Index(i).Child("name"), spec.Versions[i].Name, strings.Join(errs, ",")))
|
||||||
}
|
}
|
||||||
subresources := getSubresourcesForVersion(spec, version.Name)
|
subresources := getSubresourcesForVersion(spec, version.Name)
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionVersion(&version, fldPath.Child("versions").Index(i), hasStatusEnabled(subresources))...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionVersion(&version, fldPath.Child("versions").Index(i), mustBeStructural, hasStatusEnabled(subresources))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The top-level and per-version fields are mutual exclusive
|
// The top-level and per-version fields are mutual exclusive
|
||||||
@@ -201,7 +216,7 @@ func validateCustomResourceDefinitionSpec(spec *apiextensions.CustomResourceDefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionNames(&spec.Names, fldPath.Child("names"))...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionNames(&spec.Names, fldPath.Child("names"))...)
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionValidation(spec.Validation, hasAnyStatusEnabled(spec), fldPath.Child("validation"))...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionValidation(spec.Validation, mustBeStructural, hasAnyStatusEnabled(spec), fldPath.Child("validation"))...)
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionSubresources(spec.Subresources, fldPath.Child("subresources"))...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionSubresources(spec.Subresources, fldPath.Child("subresources"))...)
|
||||||
|
|
||||||
for i := range spec.AdditionalPrinterColumns {
|
for i := range spec.AdditionalPrinterColumns {
|
||||||
@@ -531,7 +546,7 @@ type specStandardValidator interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateCustomResourceDefinitionValidation statically validates
|
// ValidateCustomResourceDefinitionValidation statically validates
|
||||||
func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiextensions.CustomResourceValidation, statusSubresourceEnabled bool, fldPath *field.Path) field.ErrorList {
|
func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiextensions.CustomResourceValidation, mustBeStructural, statusSubresourceEnabled bool, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if customResourceValidation == nil {
|
if customResourceValidation == nil {
|
||||||
@@ -573,6 +588,17 @@ func ValidateCustomResourceDefinitionValidation(customResourceValidation *apiext
|
|||||||
|
|
||||||
openAPIV3Schema := &specStandardValidatorV3{}
|
openAPIV3Schema := &specStandardValidatorV3{}
|
||||||
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...)
|
allErrs = append(allErrs, ValidateCustomResourceDefinitionOpenAPISchema(schema, fldPath.Child("openAPIV3Schema"), openAPIV3Schema)...)
|
||||||
|
|
||||||
|
if mustBeStructural {
|
||||||
|
if ss, err := structuralschema.NewStructural(schema); err != nil {
|
||||||
|
// if the generic schema validation did its job, we should never get an error here. Hence, we hide it if there are validation errors already.
|
||||||
|
if len(allErrs) == 0 {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("openAPIV3Schema"), "", err.Error()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
allErrs = append(allErrs, structuralschema.ValidateStructural(ss, fldPath.Child("openAPIV3Schema"))...)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if validation passed otherwise, make sure we can actually construct a schema validator from this custom resource validation.
|
// if validation passed otherwise, make sure we can actually construct a schema validator from this custom resource validation.
|
||||||
|
@@ -103,6 +103,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -147,6 +148,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -191,6 +193,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -231,6 +234,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
URL: strPtr(""),
|
URL: strPtr(""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -272,6 +276,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
URL: strPtr("https://example.com/webhook"),
|
URL: strPtr("https://example.com/webhook"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -310,6 +315,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Strategy: apiextensions.ConversionStrategyType("None"),
|
Strategy: apiextensions.ConversionStrategyType("None"),
|
||||||
ConversionReviewVersions: []string{"v1beta1"},
|
ConversionReviewVersions: []string{"v1beta1"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -351,6 +357,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"invalid-version"},
|
ConversionReviewVersions: []string{"invalid-version"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -392,6 +399,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"0v"},
|
ConversionReviewVersions: []string{"0v"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -434,6 +442,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"invalid-version", "v1beta1"},
|
ConversionReviewVersions: []string{"invalid-version", "v1beta1"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -473,6 +482,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"v1beta1", "v1beta1"},
|
ConversionReviewVersions: []string{"v1beta1", "v1beta1"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -510,6 +520,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Conversion: &apiextensions.CustomResourceConversion{
|
Conversion: &apiextensions.CustomResourceConversion{
|
||||||
Strategy: apiextensions.ConversionStrategyType("Webhook"),
|
Strategy: apiextensions.ConversionStrategyType("Webhook"),
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -547,6 +558,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Conversion: &apiextensions.CustomResourceConversion{
|
Conversion: &apiextensions.CustomResourceConversion{
|
||||||
Strategy: apiextensions.ConversionStrategyType("non_existing_conversion"),
|
Strategy: apiextensions.ConversionStrategyType("non_existing_conversion"),
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -584,6 +596,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Conversion: &apiextensions.CustomResourceConversion{
|
Conversion: &apiextensions.CustomResourceConversion{
|
||||||
Strategy: apiextensions.ConversionStrategyType("None"),
|
Strategy: apiextensions.ConversionStrategyType("None"),
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -621,6 +634,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Conversion: &apiextensions.CustomResourceConversion{
|
Conversion: &apiextensions.CustomResourceConversion{
|
||||||
Strategy: apiextensions.ConversionStrategyType("None"),
|
Strategy: apiextensions.ConversionStrategyType("None"),
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -659,6 +673,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Conversion: &apiextensions.CustomResourceConversion{
|
Conversion: &apiextensions.CustomResourceConversion{
|
||||||
Strategy: apiextensions.ConversionStrategyType("None"),
|
Strategy: apiextensions.ConversionStrategyType("None"),
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -691,6 +706,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Conversion: &apiextensions.CustomResourceConversion{
|
Conversion: &apiextensions.CustomResourceConversion{
|
||||||
Strategy: apiextensions.ConversionStrategyType("None"),
|
Strategy: apiextensions.ConversionStrategyType("None"),
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{},
|
StoredVersions: []string{},
|
||||||
@@ -709,6 +725,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
Plural: "plural",
|
Plural: "plural",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
errors: []validationMatch{
|
errors: []validationMatch{
|
||||||
@@ -752,6 +769,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Kind: "value()*a",
|
Kind: "value()*a",
|
||||||
ListKind: "value()*a",
|
ListKind: "value()*a",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -798,6 +816,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Kind: "matching",
|
Kind: "matching",
|
||||||
ListKind: "matching",
|
ListKind: "matching",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -843,6 +862,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{Allows: false},
|
AdditionalProperties: &apiextensions.JSONSchemaPropsOrBool{Allows: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -880,6 +900,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -923,6 +944,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Kind: "Plural",
|
Kind: "Plural",
|
||||||
ListKind: "PluralList",
|
ListKind: "PluralList",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -962,6 +984,7 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
Kind: "Plural",
|
Kind: "Plural",
|
||||||
ListKind: "PluralList",
|
ListKind: "PluralList",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -971,36 +994,206 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-preserve-unknown-fields"),
|
invalid("spec", "validation", "openAPIV3Schema", "x-kubernetes-preserve-unknown-fields"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "preserveUnknownFields with unstructural global schema",
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Validation: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validUnstructuralValidationSchema,
|
||||||
|
},
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "version2",
|
||||||
|
Served: true,
|
||||||
|
Storage: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
required("spec", "validation", "openAPIV3Schema", "properties[spec]", "type"),
|
||||||
|
required("spec", "validation", "openAPIV3Schema", "properties[status]", "type"),
|
||||||
|
required("spec", "validation", "openAPIV3Schema", "items", "type"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "preserveUnknownFields with unstructural schema in one version",
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
Schema: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validValidationSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "version2",
|
||||||
|
Served: true,
|
||||||
|
Storage: false,
|
||||||
|
Schema: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validUnstructuralValidationSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
required("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[spec]", "type"),
|
||||||
|
required("spec", "versions[1]", "schema", "openAPIV3Schema", "properties[status]", "type"),
|
||||||
|
required("spec", "versions[1]", "schema", "openAPIV3Schema", "items", "type"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "preserveUnknownFields with no schema in one version",
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
Schema: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validValidationSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "version2",
|
||||||
|
Served: true,
|
||||||
|
Storage: false,
|
||||||
|
Schema: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
required("spec", "versions[1]", "schema", "openAPIV3Schema"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "preserveUnknownFields with no schema at all",
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
Schema: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "version2",
|
||||||
|
Served: true,
|
||||||
|
Storage: false,
|
||||||
|
Schema: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
required("spec", "versions[0]", "schema", "openAPIV3Schema"),
|
||||||
|
required("spec", "versions[1]", "schema", "openAPIV3Schema"),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
// duplicate defaulting behaviour
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
|
// duplicate defaulting behaviour
|
||||||
tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
|
if tc.resource.Spec.Conversion != nil && tc.resource.Spec.Conversion.Strategy == apiextensions.WebhookConverter && len(tc.resource.Spec.Conversion.ConversionReviewVersions) == 0 {
|
||||||
}
|
tc.resource.Spec.Conversion.ConversionReviewVersions = []string{"v1beta1"}
|
||||||
errs := ValidateCustomResourceDefinition(tc.resource)
|
}
|
||||||
seenErrs := make([]bool, len(errs))
|
errs := ValidateCustomResourceDefinition(tc.resource)
|
||||||
|
seenErrs := make([]bool, len(errs))
|
||||||
|
|
||||||
for _, expectedError := range tc.errors {
|
for _, expectedError := range tc.errors {
|
||||||
found := false
|
found := false
|
||||||
for i, err := range errs {
|
for i, err := range errs {
|
||||||
if expectedError.matches(err) && !seenErrs[i] {
|
if expectedError.matches(err) && !seenErrs[i] {
|
||||||
found = true
|
found = true
|
||||||
seenErrs[i] = true
|
seenErrs[i] = true
|
||||||
break
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
t.Errorf("expected %v at %v, got %v", expectedError.errorType, expectedError.path.String(), errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
for i, seen := range seenErrs {
|
||||||
t.Errorf("%s: expected %v at %v, got %v", tc.name, expectedError.errorType, expectedError.path.String(), errs)
|
if !seen {
|
||||||
|
t.Errorf("unexpected error: %v", errs[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
for i, seen := range seenErrs {
|
|
||||||
if !seen {
|
|
||||||
t.Errorf("%s: unexpected error: %v", tc.name, errs[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1043,6 +1236,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"invalid-version"},
|
ConversionReviewVersions: []string{"invalid-version"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -1078,6 +1272,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"invalid-version_0, invalid-version"},
|
ConversionReviewVersions: []string{"invalid-version_0, invalid-version"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -1117,6 +1312,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"invalid-version"},
|
ConversionReviewVersions: []string{"invalid-version"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -1152,6 +1348,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"v1beta1", "invalid-version"},
|
ConversionReviewVersions: []string{"v1beta1", "invalid-version"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -1193,6 +1390,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
ConversionReviewVersions: []string{"invalid-version"},
|
ConversionReviewVersions: []string{"invalid-version"},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -1227,6 +1425,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
URL: strPtr("https://example.com/webhook"),
|
URL: strPtr("https://example.com/webhook"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -1260,6 +1459,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind",
|
Kind: "kind",
|
||||||
ListKind: "listkind",
|
ListKind: "listkind",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1292,6 +1492,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind",
|
Kind: "kind",
|
||||||
ListKind: "listkind",
|
ListKind: "listkind",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1329,6 +1530,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind",
|
Kind: "kind",
|
||||||
ListKind: "listkind",
|
ListKind: "listkind",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1364,6 +1566,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind",
|
Kind: "kind",
|
||||||
ListKind: "listkind",
|
ListKind: "listkind",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1406,6 +1609,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind",
|
Kind: "kind",
|
||||||
ListKind: "listkind",
|
ListKind: "listkind",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1442,6 +1646,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind",
|
Kind: "kind",
|
||||||
ListKind: "listkind",
|
ListKind: "listkind",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1481,6 +1686,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind",
|
Kind: "kind",
|
||||||
ListKind: "listkind",
|
ListKind: "listkind",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1516,6 +1722,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind2",
|
Kind: "kind2",
|
||||||
ListKind: "listkind2",
|
ListKind: "listkind2",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1556,6 +1763,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind",
|
Kind: "kind",
|
||||||
ListKind: "listkind",
|
ListKind: "listkind",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1591,6 +1799,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "kind2",
|
Kind: "kind2",
|
||||||
ListKind: "listkind2",
|
ListKind: "listkind2",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
AcceptedNames: apiextensions.CustomResourceDefinitionNames{
|
||||||
@@ -1639,6 +1848,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "Plural",
|
Kind: "Plural",
|
||||||
ListKind: "PluralList",
|
ListKind: "PluralList",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -1680,6 +1890,7 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
Kind: "Plural",
|
Kind: "Plural",
|
||||||
ListKind: "PluralList",
|
ListKind: "PluralList",
|
||||||
},
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
},
|
},
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
StoredVersions: []string{"version"},
|
StoredVersions: []string{"version"},
|
||||||
@@ -1690,6 +1901,208 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
forbidden("spec", "subresources"),
|
forbidden("spec", "subresources"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "switch off preserveUnknownFields with structural schema before and after",
|
||||||
|
old: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Validation: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validValidationSchema,
|
||||||
|
},
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Validation: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validUnstructuralValidationSchema,
|
||||||
|
},
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{
|
||||||
|
required("spec", "validation", "openAPIV3Schema", "properties[spec]", "type"),
|
||||||
|
required("spec", "validation", "openAPIV3Schema", "properties[status]", "type"),
|
||||||
|
required("spec", "validation", "openAPIV3Schema", "items", "type"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "switch off preserveUnknownFields without structural schema before, but with after",
|
||||||
|
old: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Validation: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validUnstructuralValidationSchema,
|
||||||
|
},
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Validation: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validValidationSchema,
|
||||||
|
},
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "switch on preserveUnknownFields without structural schema",
|
||||||
|
old: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Validation: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validValidationSchema,
|
||||||
|
},
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "plural.group.com",
|
||||||
|
ResourceVersion: "42",
|
||||||
|
},
|
||||||
|
Spec: apiextensions.CustomResourceDefinitionSpec{
|
||||||
|
Group: "group.com",
|
||||||
|
Version: "version",
|
||||||
|
Validation: &apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: validUnstructuralValidationSchema,
|
||||||
|
},
|
||||||
|
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
||||||
|
{
|
||||||
|
Name: "version",
|
||||||
|
Served: true,
|
||||||
|
Storage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Scope: apiextensions.NamespaceScoped,
|
||||||
|
Names: apiextensions.CustomResourceDefinitionNames{
|
||||||
|
Plural: "plural",
|
||||||
|
Singular: "singular",
|
||||||
|
Kind: "Plural",
|
||||||
|
ListKind: "PluralList",
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(true),
|
||||||
|
},
|
||||||
|
Status: apiextensions.CustomResourceDefinitionStatus{
|
||||||
|
StoredVersions: []string{"version"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errors: []validationMatch{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
@@ -1721,10 +2134,11 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
|
|
||||||
func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
input apiextensions.CustomResourceValidation
|
input apiextensions.CustomResourceValidation
|
||||||
statusEnabled bool
|
mustBeStructural bool
|
||||||
wantError bool
|
statusEnabled bool
|
||||||
|
wantError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty",
|
name: "empty",
|
||||||
@@ -1844,10 +2258,28 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
},
|
},
|
||||||
wantError: false,
|
wantError: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "must be structural, but isn't",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{},
|
||||||
|
},
|
||||||
|
mustBeStructural: true,
|
||||||
|
wantError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "must be structural",
|
||||||
|
input: apiextensions.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mustBeStructural: true,
|
||||||
|
wantError: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got := ValidateCustomResourceDefinitionValidation(&tt.input, tt.statusEnabled, field.NewPath("spec", "validation"))
|
got := ValidateCustomResourceDefinitionValidation(&tt.input, tt.mustBeStructural, tt.statusEnabled, field.NewPath("spec", "validation"))
|
||||||
if !tt.wantError && len(got) > 0 {
|
if !tt.wantError && len(got) > 0 {
|
||||||
t.Errorf("Expected no error, but got: %v", got)
|
t.Errorf("Expected no error, but got: %v", got)
|
||||||
} else if tt.wantError && len(got) == 0 {
|
} else if tt.wantError && len(got) == 0 {
|
||||||
@@ -1860,6 +2292,42 @@ func TestValidateCustomResourceDefinitionValidation(t *testing.T) {
|
|||||||
var example = apiextensions.JSON(`"This is an example"`)
|
var example = apiextensions.JSON(`"This is an example"`)
|
||||||
|
|
||||||
var validValidationSchema = &apiextensions.JSONSchemaProps{
|
var validValidationSchema = &apiextensions.JSONSchemaProps{
|
||||||
|
Description: "This is a description",
|
||||||
|
Type: "object",
|
||||||
|
Format: "date-time",
|
||||||
|
Title: "This is a title",
|
||||||
|
Maximum: float64Ptr(10),
|
||||||
|
ExclusiveMaximum: true,
|
||||||
|
Minimum: float64Ptr(5),
|
||||||
|
ExclusiveMinimum: true,
|
||||||
|
MaxLength: int64Ptr(10),
|
||||||
|
MinLength: int64Ptr(5),
|
||||||
|
Pattern: "^[a-z]$",
|
||||||
|
MaxItems: int64Ptr(10),
|
||||||
|
MinItems: int64Ptr(5),
|
||||||
|
MultipleOf: float64Ptr(3),
|
||||||
|
Required: []string{"spec", "status"},
|
||||||
|
Properties: map[string]apiextensions.JSONSchemaProps{
|
||||||
|
"spec": {
|
||||||
|
Type: "object",
|
||||||
|
Items: &apiextensions.JSONSchemaPropsOrArray{
|
||||||
|
Schema: &apiextensions.JSONSchemaProps{
|
||||||
|
Description: "This is a schema nested under Items",
|
||||||
|
Type: "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
Type: "object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ExternalDocs: &apiextensions.ExternalDocumentation{
|
||||||
|
Description: "This is an external documentation description",
|
||||||
|
},
|
||||||
|
Example: &example,
|
||||||
|
}
|
||||||
|
|
||||||
|
var validUnstructuralValidationSchema = &apiextensions.JSONSchemaProps{
|
||||||
Description: "This is a description",
|
Description: "This is a description",
|
||||||
Type: "object",
|
Type: "object",
|
||||||
Format: "date-time",
|
Format: "date-time",
|
||||||
|
@@ -201,6 +201,11 @@ func (in *CustomResourceDefinitionSpec) DeepCopyInto(out *CustomResourceDefiniti
|
|||||||
*out = new(CustomResourceConversion)
|
*out = new(CustomResourceConversion)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.PreserveUnknownFields != nil {
|
||||||
|
in, out := &in.PreserveUnknownFields, &out.PreserveUnknownFields
|
||||||
|
*out = new(bool)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,6 +23,8 @@ go_library(
|
|||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/conversion:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/validation:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
|
||||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset:go_default_library",
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset:go_default_library",
|
||||||
|
@@ -30,6 +30,8 @@ import (
|
|||||||
"github.com/go-openapi/validate"
|
"github.com/go-openapi/validate"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
|
"k8s.io/apiextensions-apiserver/pkg/apiserver/conversion"
|
||||||
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
structuralpruning "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning"
|
||||||
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
apiservervalidation "k8s.io/apiextensions-apiserver/pkg/apiserver/validation"
|
||||||
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
|
informers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion/apiextensions/internalversion"
|
||||||
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
|
listers "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/internalversion"
|
||||||
@@ -39,6 +41,7 @@ import (
|
|||||||
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/registry/customresource"
|
"k8s.io/apiextensions-apiserver/pkg/registry/customresource"
|
||||||
"k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor"
|
"k8s.io/apiextensions-apiserver/pkg/registry/customresource/tableconvertor"
|
||||||
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
"k8s.io/apimachinery/pkg/api/meta"
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
@@ -520,6 +523,20 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for nil because we dereference this throughout the handler code.
|
||||||
|
// Note: we always default this to non-nil. But we should guard these dereferences any way.
|
||||||
|
if crd.Spec.PreserveUnknownFields == nil {
|
||||||
|
return nil, fmt.Errorf("unexpected nil spec.preserveUnknownFields in the CustomResourceDefinition")
|
||||||
|
}
|
||||||
|
|
||||||
|
var structuralSchema *structuralschema.Structural
|
||||||
|
if validationSchema != nil {
|
||||||
|
structuralSchema, err = structuralschema.NewStructural(validationSchema.OpenAPIV3Schema)
|
||||||
|
if *crd.Spec.PreserveUnknownFields == false && err != nil {
|
||||||
|
return nil, err // validation should avoid this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var statusSpec *apiextensions.CustomResourceSubresourceStatus
|
var statusSpec *apiextensions.CustomResourceSubresourceStatus
|
||||||
var statusValidator *validate.SchemaValidator
|
var statusValidator *validate.SchemaValidator
|
||||||
subresources, err := apiextensions.GetSubresourcesForVersion(crd, v.Name)
|
subresources, err := apiextensions.GetSubresourcesForVersion(crd, v.Name)
|
||||||
@@ -570,10 +587,13 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
|||||||
scaleSpec,
|
scaleSpec,
|
||||||
),
|
),
|
||||||
crdConversionRESTOptionsGetter{
|
crdConversionRESTOptionsGetter{
|
||||||
RESTOptionsGetter: r.restOptionsGetter,
|
RESTOptionsGetter: r.restOptionsGetter,
|
||||||
converter: safeConverter,
|
converter: safeConverter,
|
||||||
decoderVersion: schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name},
|
decoderVersion: schema.GroupVersion{Group: crd.Spec.Group, Version: v.Name},
|
||||||
encoderVersion: schema.GroupVersion{Group: crd.Spec.Group, Version: storageVersion},
|
encoderVersion: schema.GroupVersion{Group: crd.Spec.Group, Version: storageVersion},
|
||||||
|
structuralSchema: structuralSchema,
|
||||||
|
structuralSchemaGK: kind.GroupKind(),
|
||||||
|
preserveUnknownFields: *crd.Spec.PreserveUnknownFields,
|
||||||
},
|
},
|
||||||
crd.Status.AcceptedNames.Categories,
|
crd.Status.AcceptedNames.Categories,
|
||||||
table,
|
table,
|
||||||
@@ -595,7 +615,14 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
|||||||
ClusterScoped: clusterScoped,
|
ClusterScoped: clusterScoped,
|
||||||
SelfLinkPathPrefix: selfLinkPrefix,
|
SelfLinkPathPrefix: selfLinkPrefix,
|
||||||
},
|
},
|
||||||
Serializer: unstructuredNegotiatedSerializer{typer: typer, creator: creator, converter: safeConverter},
|
Serializer: unstructuredNegotiatedSerializer{
|
||||||
|
typer: typer,
|
||||||
|
creator: creator,
|
||||||
|
converter: safeConverter,
|
||||||
|
structuralSchema: structuralSchema,
|
||||||
|
structuralSchemaGK: kind.GroupKind(),
|
||||||
|
preserveUnknownFields: *crd.Spec.PreserveUnknownFields,
|
||||||
|
},
|
||||||
ParameterCodec: parameterCodec,
|
ParameterCodec: parameterCodec,
|
||||||
|
|
||||||
Creater: creator,
|
Creater: creator,
|
||||||
@@ -646,6 +673,13 @@ func (r *crdHandler) getOrCreateServingInfoFor(crd *apiextensions.CustomResource
|
|||||||
// shallow copy
|
// shallow copy
|
||||||
statusScope := *requestScopes[v.Name]
|
statusScope := *requestScopes[v.Name]
|
||||||
statusScope.Subresource = "status"
|
statusScope.Subresource = "status"
|
||||||
|
statusScope.Serializer = unstructuredNegotiatedSerializer{
|
||||||
|
typer: typer, creator: creator,
|
||||||
|
converter: safeConverter,
|
||||||
|
structuralSchema: structuralSchema,
|
||||||
|
structuralSchemaGK: kind.GroupKind(),
|
||||||
|
preserveUnknownFields: *crd.Spec.PreserveUnknownFields,
|
||||||
|
}
|
||||||
statusScope.Namer = handlers.ContextBasedNaming{
|
statusScope.Namer = handlers.ContextBasedNaming{
|
||||||
SelfLinker: meta.NewAccessor(),
|
SelfLinker: meta.NewAccessor(),
|
||||||
ClusterScoped: clusterScoped,
|
ClusterScoped: clusterScoped,
|
||||||
@@ -680,6 +714,10 @@ type unstructuredNegotiatedSerializer struct {
|
|||||||
typer runtime.ObjectTyper
|
typer runtime.ObjectTyper
|
||||||
creator runtime.ObjectCreater
|
creator runtime.ObjectCreater
|
||||||
converter runtime.ObjectConvertor
|
converter runtime.ObjectConvertor
|
||||||
|
|
||||||
|
structuralSchema *structuralschema.Structural
|
||||||
|
structuralSchemaGK schema.GroupKind
|
||||||
|
preserveUnknownFields bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
|
func (s unstructuredNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo {
|
||||||
@@ -712,7 +750,7 @@ func (s unstructuredNegotiatedSerializer) EncoderForVersion(encoder runtime.Enco
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s unstructuredNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
|
func (s unstructuredNegotiatedSerializer) DecoderToVersion(decoder runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder {
|
||||||
d := schemaCoercingDecoder{delegate: decoder, validator: unstructuredSchemaCoercer{}}
|
d := schemaCoercingDecoder{delegate: decoder, validator: unstructuredSchemaCoercer{structuralSchema: s.structuralSchema, structuralSchemaGK: s.structuralSchemaGK, preserveUnknownFields: s.preserveUnknownFields}}
|
||||||
return versioning.NewDefaultingCodecForScheme(Scheme, nil, d, nil, gv)
|
return versioning.NewDefaultingCodecForScheme(Scheme, nil, d, nil, gv)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -801,19 +839,29 @@ func (in crdStorageMap) clone() crdStorageMap {
|
|||||||
// provided custom converter and custom encoder and decoder version.
|
// provided custom converter and custom encoder and decoder version.
|
||||||
type crdConversionRESTOptionsGetter struct {
|
type crdConversionRESTOptionsGetter struct {
|
||||||
generic.RESTOptionsGetter
|
generic.RESTOptionsGetter
|
||||||
converter runtime.ObjectConvertor
|
converter runtime.ObjectConvertor
|
||||||
encoderVersion schema.GroupVersion
|
encoderVersion schema.GroupVersion
|
||||||
decoderVersion schema.GroupVersion
|
decoderVersion schema.GroupVersion
|
||||||
|
structuralSchema *structuralschema.Structural
|
||||||
|
structuralSchemaGK schema.GroupKind
|
||||||
|
preserveUnknownFields bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t crdConversionRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
|
func (t crdConversionRESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) {
|
||||||
ret, err := t.RESTOptionsGetter.GetRESTOptions(resource)
|
ret, err := t.RESTOptionsGetter.GetRESTOptions(resource)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
d := schemaCoercingDecoder{delegate: ret.StorageConfig.Codec, validator: unstructuredSchemaCoercer{
|
d := schemaCoercingDecoder{delegate: ret.StorageConfig.Codec, validator: unstructuredSchemaCoercer{
|
||||||
// drop invalid fields while decoding old CRs (before we had any ObjectMeta validation)
|
// drop invalid fields while decoding old CRs (before we haven't had any ObjectMeta validation)
|
||||||
dropInvalidMetadata: true,
|
dropInvalidMetadata: true,
|
||||||
|
structuralSchema: t.structuralSchema,
|
||||||
|
structuralSchemaGK: t.structuralSchemaGK,
|
||||||
|
preserveUnknownFields: t.preserveUnknownFields,
|
||||||
|
}}
|
||||||
|
c := schemaCoercingConverter{delegate: t.converter, validator: unstructuredSchemaCoercer{
|
||||||
|
structuralSchema: t.structuralSchema,
|
||||||
|
structuralSchemaGK: t.structuralSchemaGK,
|
||||||
|
preserveUnknownFields: t.preserveUnknownFields,
|
||||||
}}
|
}}
|
||||||
c := schemaCoercingConverter{delegate: t.converter, validator: unstructuredSchemaCoercer{}}
|
|
||||||
ret.StorageConfig.Codec = versioning.NewCodec(
|
ret.StorageConfig.Codec = versioning.NewCodec(
|
||||||
ret.StorageConfig.Codec,
|
ret.StorageConfig.Codec,
|
||||||
d,
|
d,
|
||||||
@@ -894,13 +942,17 @@ func (v schemaCoercingConverter) ConvertFieldLabel(gvk schema.GroupVersionKind,
|
|||||||
return v.delegate.ConvertFieldLabel(gvk, label, value)
|
return v.delegate.ConvertFieldLabel(gvk, label, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// unstructuredSchemaCoercer does the validation for Unstructured that json.Unmarshal
|
// unstructuredSchemaCoercer adds to unstructured unmarshalling what json.Unmarshal does
|
||||||
// does for native types. This includes:
|
// in addition for native types when decoding into Golang structs:
|
||||||
// - validating and pruning ObjectMeta (here with optional error instead of pruning)
|
//
|
||||||
// - TODO: application of an OpenAPI validator (against the whole object or a top-level field of it).
|
// - validating and pruning ObjectMeta
|
||||||
// - TODO: optionally application of post-validation algorithms like defaulting and/or OpenAPI based pruning.
|
// - generic pruning of unknown fields following a structural schema.
|
||||||
type unstructuredSchemaCoercer struct {
|
type unstructuredSchemaCoercer struct {
|
||||||
dropInvalidMetadata bool
|
dropInvalidMetadata bool
|
||||||
|
|
||||||
|
structuralSchema *structuralschema.Structural
|
||||||
|
structuralSchemaGK schema.GroupKind
|
||||||
|
preserveUnknownFields bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *unstructuredSchemaCoercer) apply(u *unstructured.Unstructured) error {
|
func (v *unstructuredSchemaCoercer) apply(u *unstructured.Unstructured) error {
|
||||||
@@ -918,6 +970,15 @@ func (v *unstructuredSchemaCoercer) apply(u *unstructured.Unstructured) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compare group and kind because also other object like DeleteCollection options pass through here
|
||||||
|
gv, err := schema.ParseGroupVersion(apiVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !v.preserveUnknownFields && gv.Group == v.structuralSchemaGK.Group && kind == v.structuralSchemaGK.Kind {
|
||||||
|
structuralpruning.Prune(u.Object, v.structuralSchema)
|
||||||
|
}
|
||||||
|
|
||||||
// restore meta fields, starting clean
|
// restore meta fields, starting clean
|
||||||
if foundKind {
|
if foundKind {
|
||||||
u.SetKind(kind)
|
u.SetKind(kind)
|
||||||
|
@@ -31,7 +31,10 @@ filegroup(
|
|||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
name = "all-srcs",
|
name = "all-srcs",
|
||||||
srcs = [":package-srcs"],
|
srcs = [
|
||||||
|
":package-srcs",
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning:all-srcs",
|
||||||
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
)
|
)
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||||
|
|
||||||
|
go_library(
|
||||||
|
name = "go_default_library",
|
||||||
|
srcs = ["algorithm.go"],
|
||||||
|
importmap = "k8s.io/kubernetes/vendor/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning",
|
||||||
|
importpath = "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = ["//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library"],
|
||||||
|
)
|
||||||
|
|
||||||
|
go_test(
|
||||||
|
name = "go_default_test",
|
||||||
|
srcs = ["algorithm_test.go"],
|
||||||
|
embed = [":go_default_library"],
|
||||||
|
deps = [
|
||||||
|
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "package-srcs",
|
||||||
|
srcs = glob(["**"]),
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:private"],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "all-srcs",
|
||||||
|
srcs = [":package-srcs"],
|
||||||
|
tags = ["automanaged"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 pruning
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prune removes object fields in obj which are not specified in s.
|
||||||
|
func Prune(obj map[string]interface{}, s *structuralschema.Structural) {
|
||||||
|
prune(obj, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func prune(x interface{}, s *structuralschema.Structural) {
|
||||||
|
if s != nil && s.XPreserveUnknownFields {
|
||||||
|
skipPrune(x, s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x := x.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
if s == nil {
|
||||||
|
for k := range x {
|
||||||
|
delete(x, k)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range x {
|
||||||
|
prop, ok := s.Properties[k]
|
||||||
|
if ok {
|
||||||
|
prune(v, &prop)
|
||||||
|
} else if s.AdditionalProperties != nil {
|
||||||
|
prune(v, s.AdditionalProperties.Structural)
|
||||||
|
} else {
|
||||||
|
delete(x, k)
|
||||||
|
}
|
||||||
|
fmt.Printf("deleting %q => %#v\n", k, x)
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
if s == nil {
|
||||||
|
for _, v := range x {
|
||||||
|
prune(v, nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, v := range x {
|
||||||
|
prune(v, s.Items)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// scalars, do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipPrune(x interface{}, s *structuralschema.Structural) {
|
||||||
|
if s == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch x := x.(type) {
|
||||||
|
case map[string]interface{}:
|
||||||
|
for k, v := range x {
|
||||||
|
if prop, ok := s.Properties[k]; ok {
|
||||||
|
prune(v, &prop)
|
||||||
|
} else {
|
||||||
|
skipPrune(v, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case []interface{}:
|
||||||
|
for _, v := range x {
|
||||||
|
skipPrune(v, s.Items)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// scalars, do nothing
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,298 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 pruning
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
structuralschema "k8s.io/apiextensions-apiserver/pkg/apiserver/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPrune(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
json string
|
||||||
|
schema *structuralschema.Structural
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"empty", "null", nil, "null"},
|
||||||
|
{"scalar", "4", &structuralschema.Structural{}, "4"},
|
||||||
|
{"scalar array", "[1,2]", &structuralschema.Structural{
|
||||||
|
Items: &structuralschema.Structural{},
|
||||||
|
}, "[1,2]"},
|
||||||
|
{"object array", `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, &structuralschema.Structural{
|
||||||
|
Items: &structuralschema.Structural{
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"a": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, `[{"a":1},{},{"a":1,"c":3}]`},
|
||||||
|
{"object array with nil schema", `[{"a":1},{"b":1},{"a":1,"b":2,"c":3}]`, nil, `[{},{},{}]`},
|
||||||
|
{"object array object", `{"array":[{"a":1},{"b":1},{"a":1,"b":2,"c":3}],"unspecified":{"a":1},"specified":{"a":1,"b":2,"c":3}}`, &structuralschema.Structural{
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"array": {
|
||||||
|
Items: &structuralschema.Structural{
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"a": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"specified": {
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"a": {},
|
||||||
|
"c": {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, `{"array":[{"a":1},{},{"a":1,"c":3}],"specified":{"a":1,"c":3}}`},
|
||||||
|
{"nested x-kubernetes-preserve-unknown-fields", `
|
||||||
|
{
|
||||||
|
"unspecified":"bar",
|
||||||
|
"alpha": "abc",
|
||||||
|
"beta": 42.0,
|
||||||
|
"unspecifiedObject": {"unspecified": "bar"},
|
||||||
|
"pruning": {
|
||||||
|
"unspecified": "bar",
|
||||||
|
"unspecifiedObject": {"unspecified": "bar"},
|
||||||
|
"pruning": {"unspecified": "bar"},
|
||||||
|
"preserving": {"unspecified": "bar"}
|
||||||
|
},
|
||||||
|
"preserving": {
|
||||||
|
"unspecified": "bar",
|
||||||
|
"unspecifiedObject": {"unspecified": "bar"},
|
||||||
|
"pruning": {"unspecified": "bar"},
|
||||||
|
"preserving": {"unspecified": "bar"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, &structuralschema.Structural{
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"alpha": {Generic: structuralschema.Generic{Type: "string"}},
|
||||||
|
"beta": {Generic: structuralschema.Generic{Type: "number"}},
|
||||||
|
"pruning": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"preserving": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||||
|
},
|
||||||
|
"pruning": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"preserving": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"preserving": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||||
|
},
|
||||||
|
"pruning": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, `
|
||||||
|
{
|
||||||
|
"unspecified":"bar",
|
||||||
|
"alpha": "abc",
|
||||||
|
"beta": 42.0,
|
||||||
|
"unspecifiedObject": {"unspecified": "bar"},
|
||||||
|
"pruning": {
|
||||||
|
"pruning": {},
|
||||||
|
"preserving": {"unspecified": "bar"}
|
||||||
|
},
|
||||||
|
"preserving": {
|
||||||
|
"unspecified": "bar",
|
||||||
|
"unspecifiedObject": {"unspecified": "bar"},
|
||||||
|
"pruning": {},
|
||||||
|
"preserving": {"unspecified": "bar"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`},
|
||||||
|
{"additionalProperties with schema", `{"a":1,"b":1,"c":{"a":1,"b":2,"c":{"a":1}}}`, &structuralschema.Structural{
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"a": {},
|
||||||
|
"c": {
|
||||||
|
Generic: structuralschema.Generic{
|
||||||
|
AdditionalProperties: &structuralschema.StructuralOrBool{
|
||||||
|
Structural: &structuralschema.Structural{
|
||||||
|
Generic: structuralschema.Generic{
|
||||||
|
Type: "integer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, `{"a":1,"c":{"a":1,"b":2,"c":{}}}`},
|
||||||
|
{"additionalProperties with bool", `{"a":1,"b":1,"c":{"a":1,"b":2,"c":{"a":1}}}`, &structuralschema.Structural{
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"a": {},
|
||||||
|
"c": {
|
||||||
|
Generic: structuralschema.Generic{
|
||||||
|
AdditionalProperties: &structuralschema.StructuralOrBool{
|
||||||
|
Bool: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, `{"a":1,"c":{"a":1,"b":2,"c":{}}}`},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
var in interface{}
|
||||||
|
if err := json.Unmarshal([]byte(tt.json), &in); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var expected interface{}
|
||||||
|
if err := json.Unmarshal([]byte(tt.expected), &expected); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prune(in, tt.schema)
|
||||||
|
if !reflect.DeepEqual(in, expected) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
enc := json.NewEncoder(&buf)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
err := enc.Encode(in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected result mashalling error: %v", err)
|
||||||
|
}
|
||||||
|
t.Errorf("expected: %s\ngot: %s", tt.expected, buf.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const smallInstance = `
|
||||||
|
{
|
||||||
|
"unspecified":"bar",
|
||||||
|
"alpha": "abc",
|
||||||
|
"beta": 42.0,
|
||||||
|
"unspecifiedObject": {"unspecified": "bar"},
|
||||||
|
"pruning": {
|
||||||
|
"pruning": {},
|
||||||
|
"preserving": {"unspecified": "bar"}
|
||||||
|
},
|
||||||
|
"preserving": {
|
||||||
|
"unspecified": "bar",
|
||||||
|
"unspecifiedObject": {"unspecified": "bar"},
|
||||||
|
"pruning": {},
|
||||||
|
"preserving": {"unspecified": "bar"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func BenchmarkPrune(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
schema := &structuralschema.Structural{
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"alpha": {Generic: structuralschema.Generic{Type: "string"}},
|
||||||
|
"beta": {Generic: structuralschema.Generic{Type: "number"}},
|
||||||
|
"pruning": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"preserving": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||||
|
},
|
||||||
|
"pruning": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"preserving": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||||
|
Properties: map[string]structuralschema.Structural{
|
||||||
|
"preserving": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
Extensions: structuralschema.Extensions{XPreserveUnknownFields: true},
|
||||||
|
},
|
||||||
|
"pruning": {
|
||||||
|
Generic: structuralschema.Generic{Type: "object"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var obj map[string]interface{}
|
||||||
|
err := json.Unmarshal([]byte(smallInstance), &obj)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instances := make([]map[string]interface{}, 0, b.N)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
instances = append(instances, runtime.DeepCopyJSON(obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
Prune(instances[i], schema)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDeepCopy(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
var obj map[string]interface{}
|
||||||
|
err := json.Unmarshal([]byte(smallInstance), &obj)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
instances := make([]map[string]interface{}, 0, b.N)
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
instances = append(instances, runtime.DeepCopyJSON(obj))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkUnmarshal(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
|
||||||
|
instances := make([]map[string]interface{}, b.N)
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
err := json.Unmarshal([]byte(smallInstance), &instances[i])
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -14,6 +14,7 @@ go_test(
|
|||||||
"change_test.go",
|
"change_test.go",
|
||||||
"finalization_test.go",
|
"finalization_test.go",
|
||||||
"objectmeta_test.go",
|
"objectmeta_test.go",
|
||||||
|
"pruning_test.go",
|
||||||
"registration_test.go",
|
"registration_test.go",
|
||||||
"subresources_test.go",
|
"subresources_test.go",
|
||||||
"table_test.go",
|
"table_test.go",
|
||||||
@@ -54,6 +55,7 @@ go_test(
|
|||||||
"//vendor/github.com/coreos/etcd/pkg/transport:go_default_library",
|
"//vendor/github.com/coreos/etcd/pkg/transport:go_default_library",
|
||||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
"//vendor/sigs.k8s.io/yaml:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@@ -0,0 +1,459 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 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 integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3"
|
||||||
|
"github.com/coreos/etcd/pkg/transport"
|
||||||
|
"sigs.k8s.io/yaml"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/json"
|
||||||
|
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/client-go/dynamic"
|
||||||
|
"k8s.io/utils/pointer"
|
||||||
|
|
||||||
|
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||||
|
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||||
|
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pruningFixture = &apiextensionsv1beta1.CustomResourceDefinition{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foos.tests.apiextensions.k8s.io"},
|
||||||
|
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
|
||||||
|
Group: "tests.apiextensions.k8s.io",
|
||||||
|
Version: "v1beta1",
|
||||||
|
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
|
||||||
|
Plural: "foos",
|
||||||
|
Singular: "foo",
|
||||||
|
Kind: "Foo",
|
||||||
|
ListKind: "FooList",
|
||||||
|
},
|
||||||
|
Scope: apiextensionsv1beta1.ClusterScoped,
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
Subresources: &apiextensionsv1beta1.CustomResourceSubresources{
|
||||||
|
Status: &apiextensionsv1beta1.CustomResourceSubresourceStatus{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
fooSchema = `
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
alpha:
|
||||||
|
type: string
|
||||||
|
beta:
|
||||||
|
type: number
|
||||||
|
`
|
||||||
|
|
||||||
|
fooStatusSchema = `
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
alpha:
|
||||||
|
type: string
|
||||||
|
beta:
|
||||||
|
type: number
|
||||||
|
`
|
||||||
|
|
||||||
|
fooSchemaPreservingUnknownFields = `
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
alpha:
|
||||||
|
type: string
|
||||||
|
beta:
|
||||||
|
type: number
|
||||||
|
preserving:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
properties:
|
||||||
|
preserving:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
pruning:
|
||||||
|
type: object
|
||||||
|
pruning:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
preserving:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
pruning:
|
||||||
|
type: object
|
||||||
|
x-kubernetes-preserve-unknown-fields: true
|
||||||
|
`
|
||||||
|
|
||||||
|
fooInstance = `
|
||||||
|
kind: Foo
|
||||||
|
apiVersion: tests.apiextensions.k8s.io/v1beta1
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
`
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPruningCreate(t *testing.T) {
|
||||||
|
tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tearDownFn()
|
||||||
|
|
||||||
|
crd := pruningFixture.DeepCopy()
|
||||||
|
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooSchema), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Creating CR and expect 'unspecified' fields to be pruned")
|
||||||
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
|
foo := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
unstructured.SetNestedField(foo.Object, "bar", "unspecified")
|
||||||
|
unstructured.SetNestedField(foo.Object, "abc", "alpha")
|
||||||
|
unstructured.SetNestedField(foo.Object, float64(42.0), "beta")
|
||||||
|
unstructured.SetNestedField(foo.Object, "bar", "metadata", "unspecified")
|
||||||
|
unstructured.SetNestedField(foo.Object, "bar", "metadata", "labels", "foo")
|
||||||
|
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create CR: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("CR created: %#v", foo.UnstructuredContent())
|
||||||
|
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "unspecified"); found {
|
||||||
|
t.Errorf("Expected 'unspecified' field to be pruned, but it was not")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "alpha"); !found {
|
||||||
|
t.Errorf("Expected specified 'alpha' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "beta"); !found {
|
||||||
|
t.Errorf("Expected specified 'beta' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "metadata", "unspecified"); found {
|
||||||
|
t.Errorf("Expected 'metadata.unspecified' field to be pruned, but it was not")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "metadata", "labels", "foo"); !found {
|
||||||
|
t.Errorf("Expected specified 'metadata.labels[foo]' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPruningStatus(t *testing.T) {
|
||||||
|
tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tearDownFn()
|
||||||
|
|
||||||
|
crd := pruningFixture.DeepCopy()
|
||||||
|
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooStatusSchema), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Creating CR and expect 'unspecified' fields to be pruned")
|
||||||
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
|
foo := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create CR: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("CR created: %#v", foo.UnstructuredContent())
|
||||||
|
|
||||||
|
unstructured.SetNestedField(foo.Object, "bar", "status", "unspecified")
|
||||||
|
unstructured.SetNestedField(foo.Object, "abc", "status", "alpha")
|
||||||
|
unstructured.SetNestedField(foo.Object, float64(42.0), "status", "beta")
|
||||||
|
unstructured.SetNestedField(foo.Object, "bar", "metadata", "unspecified")
|
||||||
|
|
||||||
|
foo, err = fooClient.UpdateStatus(foo, metav1.UpdateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to update status: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "unspecified"); found {
|
||||||
|
t.Errorf("Expected 'status.unspecified' field to be pruned, but it was not")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "status", "alpha"); !found {
|
||||||
|
t.Errorf("Expected specified 'status.alpha' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "status", "beta"); !found {
|
||||||
|
t.Errorf("Expected specified 'status.beta' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "metadata", "unspecified"); found {
|
||||||
|
t.Errorf("Expected 'metadata.unspecified' field to be pruned, but it was not")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPruningFromStorage(t *testing.T) {
|
||||||
|
tearDown, config, options, err := fixtures.StartDefaultServer(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
apiExtensionClient, err := clientset.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamicClient, err := dynamic.NewForConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConfig, err := options.Config()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crd := pruningFixture.DeepCopy()
|
||||||
|
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooSchema), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
restOptions, err := serverConfig.GenericConfig.RESTOptionsGetter.GetRESTOptions(schema.GroupResource{Group: crd.Spec.Group, Resource: crd.Spec.Names.Plural})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
tlsInfo := transport.TLSInfo{
|
||||||
|
CertFile: restOptions.StorageConfig.Transport.CertFile,
|
||||||
|
KeyFile: restOptions.StorageConfig.Transport.KeyFile,
|
||||||
|
CAFile: restOptions.StorageConfig.Transport.CAFile,
|
||||||
|
}
|
||||||
|
tlsConfig, err := tlsInfo.ClientConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
etcdConfig := clientv3.Config{
|
||||||
|
Endpoints: restOptions.StorageConfig.Transport.ServerList,
|
||||||
|
TLS: tlsConfig,
|
||||||
|
}
|
||||||
|
etcdclient, err := clientv3.New(etcdConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Creating object with unknown field manually in etcd")
|
||||||
|
|
||||||
|
original := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooInstance), &original.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
unstructured.SetNestedField(original.Object, "bar", "unspecified")
|
||||||
|
unstructured.SetNestedField(original.Object, "abc", "alpha")
|
||||||
|
unstructured.SetNestedField(original.Object, float64(42), "beta")
|
||||||
|
unstructured.SetNestedField(original.Object, "bar", "metadata", "labels", "foo")
|
||||||
|
|
||||||
|
// Note: we don't add metadata.unspecified as in the other tests. ObjectMeta pruning is independent of the generic pruning
|
||||||
|
// and we do not guarantee that we prune ObjectMeta on read from etcd.
|
||||||
|
|
||||||
|
ctx := genericapirequest.WithNamespace(genericapirequest.NewContext(), metav1.NamespaceDefault)
|
||||||
|
key := path.Join("/", restOptions.StorageConfig.Prefix, crd.Spec.Group, "foos/foo")
|
||||||
|
val, _ := json.Marshal(original.UnstructuredContent())
|
||||||
|
if _, err := etcdclient.Put(ctx, key, string(val)); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Checking that CustomResource is pruned from unknown fields")
|
||||||
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
|
foo, err := fooClient.Get("foo", metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "unspecified"); found {
|
||||||
|
t.Errorf("Expected 'unspecified' field to be pruned, but it was not")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "alpha"); !found {
|
||||||
|
t.Errorf("Expected specified 'alpha' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "beta"); !found {
|
||||||
|
t.Errorf("Expected specified 'beta' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we don't check metadata.foo as in the other tests. ObjectMeta pruning is independent of the generic pruning
|
||||||
|
// and we do not guarantee that we prune ObjectMeta on read from etcd.
|
||||||
|
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "metadata", "labels", "foo"); !found {
|
||||||
|
t.Errorf("Expected specified 'metadata.labels[foo]' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPruningPatch(t *testing.T) {
|
||||||
|
tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tearDownFn()
|
||||||
|
|
||||||
|
crd := pruningFixture.DeepCopy()
|
||||||
|
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooSchema), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
|
foo := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create CR: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("CR created: %#v", foo.UnstructuredContent())
|
||||||
|
|
||||||
|
// a patch with a change
|
||||||
|
patch := []byte(`{"alpha": "abc", "beta": 42.0, "unspecified": "bar", "metadata": {"unspecified": "bar", "labels":{"foo":"bar"}}}`)
|
||||||
|
if foo, err = fooClient.Patch("foo", types.MergePatchType, patch, metav1.PatchOptions{}); err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "unspecified"); found {
|
||||||
|
t.Errorf("Expected 'unspecified' field to be pruned, but it was not")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "alpha"); !found {
|
||||||
|
t.Errorf("Expected specified 'alpha' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "beta"); !found {
|
||||||
|
t.Errorf("Expected specified 'beta' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "metadata", "unspecified"); found {
|
||||||
|
t.Errorf("Expected 'metadata.unspecified' field to be pruned, but it was not")
|
||||||
|
}
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, "metadata", "labels", "foo"); !found {
|
||||||
|
t.Errorf("Expected specified 'metadata.labels[foo]' field to stay, but it was pruned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPruningCreatePreservingUnknownFields(t *testing.T) {
|
||||||
|
tearDownFn, apiExtensionClient, dynamicClient, err := fixtures.StartDefaultServerWithClients(t)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer tearDownFn()
|
||||||
|
|
||||||
|
crd := pruningFixture.DeepCopy()
|
||||||
|
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooSchemaPreservingUnknownFields), &crd.Spec.Validation.OpenAPIV3Schema); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, dynamicClient)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("Creating CR and expect 'unspecified' field to be pruned")
|
||||||
|
fooClient := dynamicClient.Resource(schema.GroupVersionResource{crd.Spec.Group, crd.Spec.Version, crd.Spec.Names.Plural})
|
||||||
|
foo := &unstructured.Unstructured{}
|
||||||
|
if err := yaml.Unmarshal([]byte(fooInstance), &foo.Object); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
unstructured.SetNestedField(foo.Object, "bar", "unspecified")
|
||||||
|
unstructured.SetNestedField(foo.Object, "abc", "alpha")
|
||||||
|
unstructured.SetNestedField(foo.Object, float64(42.0), "beta")
|
||||||
|
unstructured.SetNestedField(foo.Object, "bar", "metadata", "unspecified")
|
||||||
|
unstructured.SetNestedField(foo.Object, "bar", "metadata", "labels", "foo")
|
||||||
|
unstructured.SetNestedField(foo.Object, map[string]interface{}{
|
||||||
|
"unspecified": "bar",
|
||||||
|
"unspecifiedObject": map[string]interface{}{"unspecified": "bar"},
|
||||||
|
"pruning": map[string]interface{}{"unspecified": "bar"},
|
||||||
|
"preserving": map[string]interface{}{"unspecified": "bar"},
|
||||||
|
}, "pruning")
|
||||||
|
unstructured.SetNestedField(foo.Object, map[string]interface{}{
|
||||||
|
"unspecified": "bar",
|
||||||
|
"unspecifiedObject": map[string]interface{}{"unspecified": "bar"},
|
||||||
|
"pruning": map[string]interface{}{"unspecified": "bar"},
|
||||||
|
"preserving": map[string]interface{}{"unspecified": "bar"},
|
||||||
|
}, "preserving")
|
||||||
|
|
||||||
|
foo, err = fooClient.Create(foo, metav1.CreateOptions{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to create CR: %v", err)
|
||||||
|
}
|
||||||
|
t.Logf("CR created: %#v", foo.UnstructuredContent())
|
||||||
|
|
||||||
|
for _, pth := range [][]string{
|
||||||
|
{"unspecified"},
|
||||||
|
{"alpha"},
|
||||||
|
{"beta"},
|
||||||
|
{"metadata", "labels", "foo"},
|
||||||
|
|
||||||
|
{"pruning", "pruning"},
|
||||||
|
{"pruning", "preserving"},
|
||||||
|
{"pruning", "preserving", "unspecified"},
|
||||||
|
|
||||||
|
{"preserving", "unspecified"},
|
||||||
|
{"preserving", "unspecifiedObject"},
|
||||||
|
{"preserving", "unspecifiedObject", "unspecified"},
|
||||||
|
{"preserving", "pruning"},
|
||||||
|
{"preserving", "preserving"},
|
||||||
|
{"preserving", "preserving", "unspecified"},
|
||||||
|
} {
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, pth...); !found {
|
||||||
|
t.Errorf("Expected '%s' field to stay, but it was pruned", strings.Join(pth, "."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, pth := range [][]string{
|
||||||
|
{"metadata", "unspecified"},
|
||||||
|
|
||||||
|
{"pruning", "unspecified"},
|
||||||
|
{"pruning", "unspecifiedObject"},
|
||||||
|
{"pruning", "unspecifiedObject", "unspecified"},
|
||||||
|
{"pruning", "pruning", "unspecified"},
|
||||||
|
|
||||||
|
{"preserving", "pruning", "unspecified"},
|
||||||
|
} {
|
||||||
|
if _, found, _ := unstructured.NestedFieldNoCopy(foo.Object, pth...); found {
|
||||||
|
t.Errorf("Expected '%s' field to be pruned, but it was not", strings.Join(pth, "."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -181,7 +181,7 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
|
|||||||
defer testcrd.CleanUp()
|
defer testcrd.CleanUp()
|
||||||
webhookCleanup := registerMutatingWebhookForCustomResource(f, context, testcrd)
|
webhookCleanup := registerMutatingWebhookForCustomResource(f, context, testcrd)
|
||||||
defer webhookCleanup()
|
defer webhookCleanup()
|
||||||
testMutatingCustomResourceWebhook(f, testcrd.Crd, testcrd.DynamicClients["v1"])
|
testMutatingCustomResourceWebhook(f, testcrd.Crd, testcrd.DynamicClients["v1"], false)
|
||||||
})
|
})
|
||||||
|
|
||||||
ginkgo.It("Should deny crd creation", func() {
|
ginkgo.It("Should deny crd creation", func() {
|
||||||
@@ -202,6 +202,35 @@ var _ = SIGDescribe("AdmissionWebhook", func() {
|
|||||||
testMultiVersionCustomResourceWebhook(f, testcrd)
|
testMultiVersionCustomResourceWebhook(f, testcrd)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
ginkgo.It("Should mutate custom resource with pruning", func() {
|
||||||
|
const prune = true
|
||||||
|
testcrd, err := createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f, func(crd *apiextensionsv1beta1.CustomResourceDefinition) {
|
||||||
|
crd.Spec.PreserveUnknownFields = pointer.BoolPtr(false)
|
||||||
|
crd.Spec.Validation = &apiextensionsv1beta1.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
|
||||||
|
"data": {
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
|
||||||
|
"mutation-start": {Type: "string"},
|
||||||
|
"mutation-stage-1": {Type: "string"},
|
||||||
|
// mutation-stage-2 is intentionally missing such that it is pruned
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer testcrd.CleanUp()
|
||||||
|
webhookCleanup := registerMutatingWebhookForCustomResource(f, context, testcrd)
|
||||||
|
defer webhookCleanup()
|
||||||
|
testMutatingCustomResourceWebhook(f, testcrd.Crd, testcrd.DynamicClients["v1"], prune)
|
||||||
|
})
|
||||||
|
|
||||||
ginkgo.It("Should deny crd creation", func() {
|
ginkgo.It("Should deny crd creation", func() {
|
||||||
crdWebhookCleanup := registerValidatingWebhookForCRD(f, context)
|
crdWebhookCleanup := registerValidatingWebhookForCRD(f, context)
|
||||||
defer crdWebhookCleanup()
|
defer crdWebhookCleanup()
|
||||||
@@ -1329,7 +1358,7 @@ func testCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1beta1
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testMutatingCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
|
func testMutatingCustomResourceWebhook(f *framework.Framework, crd *apiextensionsv1beta1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface, prune bool) {
|
||||||
ginkgo.By("Creating a custom resource that should be mutated by the webhook")
|
ginkgo.By("Creating a custom resource that should be mutated by the webhook")
|
||||||
crName := "cr-instance-1"
|
crName := "cr-instance-1"
|
||||||
cr := &unstructured.Unstructured{
|
cr := &unstructured.Unstructured{
|
||||||
@@ -1350,7 +1379,9 @@ func testMutatingCustomResourceWebhook(f *framework.Framework, crd *apiextension
|
|||||||
expectedCRData := map[string]interface{}{
|
expectedCRData := map[string]interface{}{
|
||||||
"mutation-start": "yes",
|
"mutation-start": "yes",
|
||||||
"mutation-stage-1": "yes",
|
"mutation-stage-1": "yes",
|
||||||
"mutation-stage-2": "yes",
|
}
|
||||||
|
if !prune {
|
||||||
|
expectedCRData["mutation-stage-2"] = "yes"
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(expectedCRData, mutatedCR.Object["data"]) {
|
if !reflect.DeepEqual(expectedCRData, mutatedCR.Object["data"]) {
|
||||||
framework.Failf("\nexpected %#v\n, got %#v\n", expectedCRData, mutatedCR.Object["data"])
|
framework.Failf("\nexpected %#v\n, got %#v\n", expectedCRData, mutatedCR.Object["data"])
|
||||||
@@ -1571,9 +1602,9 @@ func testSlowWebhookTimeoutNoError(f *framework.Framework) {
|
|||||||
|
|
||||||
// createAdmissionWebhookMultiVersionTestCRDWithV1Storage creates a new CRD specifically
|
// createAdmissionWebhookMultiVersionTestCRDWithV1Storage creates a new CRD specifically
|
||||||
// for the admissin webhook calling test.
|
// for the admissin webhook calling test.
|
||||||
func createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f *framework.Framework) (*crd.TestCrd, error) {
|
func createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f *framework.Framework, opts ...crd.Option) (*crd.TestCrd, error) {
|
||||||
group := fmt.Sprintf("%s-multiversion-crd-test.k8s.io", f.BaseName)
|
group := fmt.Sprintf("%s-multiversion-crd-test.k8s.io", f.BaseName)
|
||||||
return crd.CreateMultiVersionTestCRD(f, group, func(crd *apiextensionsv1beta1.CustomResourceDefinition) {
|
return crd.CreateMultiVersionTestCRD(f, group, append([]crd.Option{func(crd *apiextensionsv1beta1.CustomResourceDefinition) {
|
||||||
crd.Spec.Versions = []apiextensionsv1beta1.CustomResourceDefinitionVersion{
|
crd.Spec.Versions = []apiextensionsv1beta1.CustomResourceDefinitionVersion{
|
||||||
{
|
{
|
||||||
Name: "v1",
|
Name: "v1",
|
||||||
@@ -1586,7 +1617,7 @@ func createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f *framework.Framewo
|
|||||||
Storage: false,
|
Storage: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
}}, opts...)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// servedAPIVersions returns the API versions served by the CRD.
|
// servedAPIVersions returns the API versions served by the CRD.
|
||||||
|
@@ -113,6 +113,9 @@ var (
|
|||||||
gvr("", "v1", "nodes/proxy"): {"*": testSubresourceProxy},
|
gvr("", "v1", "nodes/proxy"): {"*": testSubresourceProxy},
|
||||||
gvr("", "v1", "pods/proxy"): {"*": testSubresourceProxy},
|
gvr("", "v1", "pods/proxy"): {"*": testSubresourceProxy},
|
||||||
gvr("", "v1", "services/proxy"): {"*": testSubresourceProxy},
|
gvr("", "v1", "services/proxy"): {"*": testSubresourceProxy},
|
||||||
|
|
||||||
|
gvr("random.numbers.com", "v1", "integers"): {"create": testPruningRandomNumbers},
|
||||||
|
gvr("custom.fancy.com", "v2", "pants"): {"create": testNoPruningCustomFancy},
|
||||||
}
|
}
|
||||||
|
|
||||||
// admissionExemptResources lists objects which are exempt from admission validation/mutation,
|
// admissionExemptResources lists objects which are exempt from admission validation/mutation,
|
||||||
@@ -921,6 +924,46 @@ func testSubresourceProxy(c *testContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testPruningRandomNumbers(c *testContext) {
|
||||||
|
testResourceCreate(c)
|
||||||
|
|
||||||
|
cr2pant, err := c.client.Resource(c.gvr).Get("fortytwo", metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foo, found, err := unstructured.NestedString(cr2pant.Object, "foo")
|
||||||
|
if err != nil {
|
||||||
|
c.t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if found {
|
||||||
|
c.t.Errorf("expected .foo to be pruned, but got: %s", foo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNoPruningCustomFancy(c *testContext) {
|
||||||
|
testResourceCreate(c)
|
||||||
|
|
||||||
|
cr2pant, err := c.client.Resource(c.gvr).Get("cr2pant", metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
c.t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
foo, _, err := unstructured.NestedString(cr2pant.Object, "foo")
|
||||||
|
if err != nil {
|
||||||
|
c.t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that no pruning took place
|
||||||
|
if expected, got := "test", foo; expected != got {
|
||||||
|
c.t.Errorf("expected /foo to be %q, got: %q", expected, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// utility methods
|
// utility methods
|
||||||
//
|
//
|
||||||
|
@@ -76,5 +76,6 @@ go_library(
|
|||||||
"//test/integration/framework:go_default_library",
|
"//test/integration/framework:go_default_library",
|
||||||
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
|
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
|
||||||
"//vendor/github.com/coreos/etcd/clientv3/concurrency:go_default_library",
|
"//vendor/github.com/coreos/etcd/clientv3/concurrency:go_default_library",
|
||||||
|
"//vendor/k8s.io/utils/pointer:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@@ -22,6 +22,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/features"
|
"k8s.io/kubernetes/pkg/features"
|
||||||
|
"k8s.io/utils/pointer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetEtcdStorageData returns etcd data for all persisted objects.
|
// GetEtcdStorageData returns etcd data for all persisted objects.
|
||||||
@@ -485,6 +486,10 @@ func GetEtcdStorageDataForNamespace(namespace string) map[schema.GroupVersionRes
|
|||||||
ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr4panda",
|
ExpectedEtcdPath: "/registry/awesome.bears.com/pandas/cr4panda",
|
||||||
ExpectedGVK: gvkP("awesome.bears.com", "v1", "Panda"),
|
ExpectedGVK: gvkP("awesome.bears.com", "v1", "Panda"),
|
||||||
},
|
},
|
||||||
|
gvr("random.numbers.com", "v1", "integers"): {
|
||||||
|
Stub: `{"kind": "Integer", "apiVersion": "random.numbers.com/v1", "metadata": {"name": "fortytwo"}, "value": 42, "garbage": "oiujnasdf"}`, // requires TypeMeta due to CRD scheme's UnstructuredObjectTyper
|
||||||
|
ExpectedEtcdPath: "/registry/random.numbers.com/integers/fortytwo",
|
||||||
|
},
|
||||||
// --
|
// --
|
||||||
|
|
||||||
// k8s.io/kubernetes/pkg/apis/auditregistration/v1alpha1
|
// k8s.io/kubernetes/pkg/apis/auditregistration/v1alpha1
|
||||||
@@ -580,6 +585,32 @@ func GetCustomResourceDefinitionData() []*apiextensionsv1beta1.CustomResourceDef
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// cluster scoped with legacy version field and pruning.
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "integers.random.numbers.com",
|
||||||
|
},
|
||||||
|
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
|
||||||
|
Group: "random.numbers.com",
|
||||||
|
Version: "v1",
|
||||||
|
Scope: apiextensionsv1beta1.ClusterScoped,
|
||||||
|
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
|
||||||
|
Plural: "integers",
|
||||||
|
Kind: "Integer",
|
||||||
|
},
|
||||||
|
Validation: &apiextensionsv1beta1.CustomResourceValidation{
|
||||||
|
OpenAPIV3Schema: &apiextensionsv1beta1.JSONSchemaProps{
|
||||||
|
Type: "object",
|
||||||
|
Properties: map[string]apiextensionsv1beta1.JSONSchemaProps{
|
||||||
|
"value": {
|
||||||
|
Type: "number",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PreserveUnknownFields: pointer.BoolPtr(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
// cluster scoped with versions field
|
// cluster scoped with versions field
|
||||||
{
|
{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@@ -1073,6 +1073,7 @@ k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/validation
|
|||||||
k8s.io/apiextensions-apiserver/pkg/apiserver
|
k8s.io/apiextensions-apiserver/pkg/apiserver
|
||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/conversion
|
k8s.io/apiextensions-apiserver/pkg/apiserver/conversion
|
||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/schema
|
k8s.io/apiextensions-apiserver/pkg/apiserver/schema
|
||||||
|
k8s.io/apiextensions-apiserver/pkg/apiserver/schema/pruning
|
||||||
k8s.io/apiextensions-apiserver/pkg/apiserver/validation
|
k8s.io/apiextensions-apiserver/pkg/apiserver/validation
|
||||||
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset
|
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset
|
||||||
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme
|
k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/scheme
|
||||||
|
Reference in New Issue
Block a user