mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-04 09:49:50 +00:00
Merge pull request #84713 from sttts/sttts-crd-defaulting-ga
Promote CRD defaulting to GA
This commit is contained in:
commit
64be5ae5f1
@ -593,7 +593,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
apiextensionsfeatures.CustomResourceSubresources: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
apiextensionsfeatures.CustomResourceSubresources: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
apiextensionsfeatures.CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
apiextensionsfeatures.CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
apiextensionsfeatures.CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
apiextensionsfeatures.CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
apiextensionsfeatures.CustomResourceDefaulting: {Default: true, PreRelease: featuregate.Beta},
|
apiextensionsfeatures.CustomResourceDefaulting: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // TODO: remove in 1.18
|
||||||
|
|
||||||
// features that enable backwards compatibility but are scheduled to be removed
|
// features that enable backwards compatibility but are scheduled to be removed
|
||||||
// ...
|
// ...
|
||||||
|
@ -1048,9 +1048,6 @@ func allowDefaults(requestGV schema.GroupVersion, oldCRDSpec *apiextensions.Cust
|
|||||||
// don't tighten validation on existing persisted data
|
// don't tighten validation on existing persisted data
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if !utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourceDefaulting) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if requestGV == apiextensionsv1beta1.SchemeGroupVersion {
|
if requestGV == apiextensionsv1beta1.SchemeGroupVersion {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1613,44 +1613,6 @@ func TestValidateCustomResourceDefinition(t *testing.T) {
|
|||||||
invalid("spec", "versions[3]", "subresources", "scale", "labelSelectorPath"),
|
invalid("spec", "versions[3]", "subresources", "scale", "labelSelectorPath"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "defaults with disabled feature gate",
|
|
||||||
resource: &apiextensions.CustomResourceDefinition{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{Name: "plural.group.com"},
|
|
||||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
|
||||||
Group: "group.com",
|
|
||||||
Version: "version",
|
|
||||||
Versions: singleVersionList,
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "number",
|
|
||||||
Default: jsonPtr(42.0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PreserveUnknownFields: pointer.BoolPtr(true),
|
|
||||||
},
|
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
|
||||||
StoredVersions: []string{"version"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestGV: apiextensionsv1beta1.SchemeGroupVersion,
|
|
||||||
disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting},
|
|
||||||
errors: []validationMatch{
|
|
||||||
forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "default"), // disabled feature-gate
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "defaults with enabled feature gate via v1beta1",
|
name: "defaults with enabled feature gate via v1beta1",
|
||||||
resource: &apiextensions.CustomResourceDefinition{
|
resource: &apiextensions.CustomResourceDefinition{
|
||||||
@ -5871,473 +5833,6 @@ func TestValidateCustomResourceDefinitionUpdate(t *testing.T) {
|
|||||||
errors: []validationMatch{},
|
errors: []validationMatch{},
|
||||||
enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting},
|
enabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "ratcheting validation of defaults with disabled feature gate via v1beta1",
|
|
||||||
old: &apiextensions.CustomResourceDefinition{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "plural.group.com",
|
|
||||||
ResourceVersion: "42",
|
|
||||||
},
|
|
||||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
|
||||||
Group: "group.com",
|
|
||||||
Version: "version",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "number",
|
|
||||||
Default: jsonPtr(42.0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "number",
|
|
||||||
Default: jsonPtr(42.0),
|
|
||||||
},
|
|
||||||
"b": {
|
|
||||||
Type: "number",
|
|
||||||
Default: jsonPtr(43.0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PreserveUnknownFields: pointer.BoolPtr(false),
|
|
||||||
},
|
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
|
||||||
StoredVersions: []string{"version"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestGV: apiextensionsv1beta1.SchemeGroupVersion,
|
|
||||||
errors: []validationMatch{},
|
|
||||||
disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ratcheting validation of defaults with disabled feature gate via v1",
|
|
||||||
old: &apiextensions.CustomResourceDefinition{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "plural.group.com",
|
|
||||||
ResourceVersion: "42",
|
|
||||||
},
|
|
||||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
|
||||||
Group: "group.com",
|
|
||||||
Version: "version",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "number",
|
|
||||||
Default: jsonPtr(42.0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "number",
|
|
||||||
Default: jsonPtr(42.0),
|
|
||||||
},
|
|
||||||
"b": {
|
|
||||||
Type: "number",
|
|
||||||
Default: jsonPtr(43.0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PreserveUnknownFields: pointer.BoolPtr(false),
|
|
||||||
},
|
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
|
||||||
StoredVersions: []string{"version"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestGV: apiextensionsv1.SchemeGroupVersion,
|
|
||||||
errors: []validationMatch{},
|
|
||||||
disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ratcheting validation of defaults with disabled feature gate via v1, non-structural, no defaults before",
|
|
||||||
old: &apiextensions.CustomResourceDefinition{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "plural.group.com",
|
|
||||||
ResourceVersion: "42",
|
|
||||||
},
|
|
||||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
|
||||||
Group: "group.com",
|
|
||||||
Version: "version",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "number",
|
|
||||||
Default: jsonPtr(42.0),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PreserveUnknownFields: pointer.BoolPtr(false),
|
|
||||||
},
|
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
|
||||||
StoredVersions: []string{"version"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestGV: apiextensionsv1.SchemeGroupVersion,
|
|
||||||
errors: []validationMatch{
|
|
||||||
forbidden("spec", "validation", "openAPIV3Schema", "properties[a]", "default"),
|
|
||||||
},
|
|
||||||
disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ratcheting validation of defaults with disabled feature gate via v1, unpruned => unpruned",
|
|
||||||
old: &apiextensions.CustomResourceDefinition{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "plural.group.com",
|
|
||||||
ResourceVersion: "42",
|
|
||||||
},
|
|
||||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
|
||||||
Group: "group.com",
|
|
||||||
Version: "version",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"foo": {Type: "string"},
|
|
||||||
},
|
|
||||||
Default: jsonPtr(map[string]interface{}{
|
|
||||||
"unknown": "unknown",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"foo": {Type: "string"},
|
|
||||||
},
|
|
||||||
Default: jsonPtr(map[string]interface{}{
|
|
||||||
"unknown": "unknown",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
"b": {
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"foo": {Type: "string"},
|
|
||||||
},
|
|
||||||
Default: jsonPtr(map[string]interface{}{
|
|
||||||
"unknown": "unknown",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PreserveUnknownFields: pointer.BoolPtr(false),
|
|
||||||
},
|
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
|
||||||
StoredVersions: []string{"version"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestGV: apiextensionsv1.SchemeGroupVersion,
|
|
||||||
errors: []validationMatch{},
|
|
||||||
disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "ratcheting validation of defaults with disabled feature gate via v1, pruned => unpruned",
|
|
||||||
old: &apiextensions.CustomResourceDefinition{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: "plural.group.com",
|
|
||||||
ResourceVersion: "42",
|
|
||||||
},
|
|
||||||
Spec: apiextensions.CustomResourceDefinitionSpec{
|
|
||||||
Group: "group.com",
|
|
||||||
Version: "version",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"foo": {Type: "string"},
|
|
||||||
},
|
|
||||||
Default: jsonPtr(map[string]interface{}{
|
|
||||||
"foo": "foo",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
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",
|
|
||||||
Versions: []apiextensions.CustomResourceDefinitionVersion{
|
|
||||||
{
|
|
||||||
Name: "version",
|
|
||||||
Served: true,
|
|
||||||
Storage: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Scope: apiextensions.NamespaceScoped,
|
|
||||||
Names: apiextensions.CustomResourceDefinitionNames{
|
|
||||||
Plural: "plural",
|
|
||||||
Singular: "singular",
|
|
||||||
Kind: "Plural",
|
|
||||||
ListKind: "PluralList",
|
|
||||||
},
|
|
||||||
Validation: &apiextensions.CustomResourceValidation{
|
|
||||||
OpenAPIV3Schema: &apiextensions.JSONSchemaProps{
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"a": {
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"foo": {Type: "string"},
|
|
||||||
},
|
|
||||||
Default: jsonPtr(map[string]interface{}{
|
|
||||||
"foo": "foo",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
"b": {
|
|
||||||
Type: "object",
|
|
||||||
Properties: map[string]apiextensions.JSONSchemaProps{
|
|
||||||
"foo": {Type: "string"},
|
|
||||||
},
|
|
||||||
Default: jsonPtr(map[string]interface{}{
|
|
||||||
"unknown": "unknown",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
PreserveUnknownFields: pointer.BoolPtr(false),
|
|
||||||
},
|
|
||||||
Status: apiextensions.CustomResourceDefinitionStatus{
|
|
||||||
StoredVersions: []string{"version"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
requestGV: apiextensionsv1.SchemeGroupVersion,
|
|
||||||
errors: []validationMatch{
|
|
||||||
invalid("spec", "validation", "openAPIV3Schema", "properties[b]", "default"),
|
|
||||||
},
|
|
||||||
disabledFeatures: []featuregate.Feature{features.CustomResourceDefaulting},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "add default with enabled feature gate, structural schema, without pruning",
|
name: "add default with enabled feature gate, structural schema, without pruning",
|
||||||
old: &apiextensions.CustomResourceDefinition{
|
old: &apiextensions.CustomResourceDefinition{
|
||||||
|
@ -62,8 +62,12 @@ const (
|
|||||||
|
|
||||||
// owner: @sttts
|
// owner: @sttts
|
||||||
// alpha: v1.15
|
// alpha: v1.15
|
||||||
|
// beta: v1.16
|
||||||
|
// GA: v1.17
|
||||||
//
|
//
|
||||||
// CustomResourceDefaulting enables OpenAPI defaulting in CustomResources.
|
// CustomResourceDefaulting enables OpenAPI defaulting in CustomResources.
|
||||||
|
//
|
||||||
|
// TODO: remove in 1.18
|
||||||
CustomResourceDefaulting featuregate.Feature = "CustomResourceDefaulting"
|
CustomResourceDefaulting featuregate.Feature = "CustomResourceDefaulting"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -79,5 +83,5 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
CustomResourceSubresources: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
CustomResourceSubresources: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
CustomResourceWebhookConversion: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
CustomResourcePublishOpenAPI: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
CustomResourceDefaulting: {Default: true, PreRelease: featuregate.Beta},
|
CustomResourceDefaulting: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,9 @@ limitations under the License.
|
|||||||
package apimachinery
|
package apimachinery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/onsi/ginkgo"
|
"github.com/onsi/ginkgo"
|
||||||
|
|
||||||
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
@ -26,9 +29,12 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
"k8s.io/apimachinery/pkg/util/diff"
|
"k8s.io/apimachinery/pkg/util/diff"
|
||||||
"k8s.io/apimachinery/pkg/util/uuid"
|
"k8s.io/apimachinery/pkg/util/uuid"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/util/retry"
|
"k8s.io/client-go/util/retry"
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
@ -251,6 +257,128 @@ var _ = SIGDescribe("CustomResourceDefinition resources [Privileged:ClusterAdmin
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*
|
||||||
|
Release : v1.17
|
||||||
|
Testname: Custom Resource Definition, defaulting
|
||||||
|
Description: Create a custom resource definition without default. Create CR. Add default and read CR until
|
||||||
|
the default is applied. Create another CR. Remove default, add default for another field and read CR until
|
||||||
|
new field is defaulted, but old default stays.
|
||||||
|
*/
|
||||||
|
ginkgo.It("custom resource defaulting for requests and from storage works ", func() {
|
||||||
|
config, err := framework.LoadConfig()
|
||||||
|
framework.ExpectNoError(err, "loading config")
|
||||||
|
apiExtensionClient, err := clientset.NewForConfig(config)
|
||||||
|
framework.ExpectNoError(err, "initializing apiExtensionClient")
|
||||||
|
dynamicClient, err := dynamic.NewForConfig(config)
|
||||||
|
framework.ExpectNoError(err, "initializing dynamic client")
|
||||||
|
|
||||||
|
// Create CRD without default and waits for the resource to be recognized and available.
|
||||||
|
crd := fixtures.NewRandomNameV1CustomResourceDefinition(v1.ClusterScoped)
|
||||||
|
if crd.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties == nil {
|
||||||
|
crd.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties = map[string]v1.JSONSchemaProps{}
|
||||||
|
}
|
||||||
|
crd.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties["a"] = v1.JSONSchemaProps{Type: "string"}
|
||||||
|
crd.Spec.Versions[0].Schema.OpenAPIV3Schema.Properties["b"] = v1.JSONSchemaProps{Type: "string"}
|
||||||
|
crd, err = fixtures.CreateNewV1CustomResourceDefinitionWatchUnsafe(crd, apiExtensionClient)
|
||||||
|
framework.ExpectNoError(err, "creating CustomResourceDefinition")
|
||||||
|
defer func() {
|
||||||
|
err = fixtures.DeleteV1CustomResourceDefinition(crd, apiExtensionClient)
|
||||||
|
framework.ExpectNoError(err, "deleting CustomResourceDefinition")
|
||||||
|
}()
|
||||||
|
|
||||||
|
// create CR without default in storage
|
||||||
|
name1 := names.SimpleNameGenerator.GenerateName("cr-1")
|
||||||
|
gvr := schema.GroupVersionResource{
|
||||||
|
Group: crd.Spec.Group,
|
||||||
|
Version: crd.Spec.Versions[0].Name,
|
||||||
|
Resource: crd.Spec.Names.Plural,
|
||||||
|
}
|
||||||
|
crClient := dynamicClient.Resource(gvr)
|
||||||
|
_, err = crClient.Create(&unstructured.Unstructured{Object: map[string]interface{}{
|
||||||
|
"apiVersion": gvr.Group + "/" + gvr.Version,
|
||||||
|
"kind": crd.Spec.Names.Kind,
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": name1,
|
||||||
|
},
|
||||||
|
}}, metav1.CreateOptions{})
|
||||||
|
framework.ExpectNoError(err, "creating CR")
|
||||||
|
|
||||||
|
// Setting default for a to "A" and waiting for the CR to get defaulted on read
|
||||||
|
crd, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Patch(crd.Name, types.JSONPatchType, []byte(`[
|
||||||
|
{"op":"add","path":"/spec/versions/0/schema/openAPIV3Schema/properties/a/default", "value": "A"}
|
||||||
|
]`))
|
||||||
|
framework.ExpectNoError(err, "setting default for a to \"A\" in schema")
|
||||||
|
|
||||||
|
err = wait.PollImmediate(time.Millisecond*100, wait.ForeverTestTimeout, func() (bool, error) {
|
||||||
|
u1, err := crClient.Get(name1, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
a, found, err := unstructured.NestedFieldNoCopy(u1.Object, "a")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if a != "A" {
|
||||||
|
return false, fmt.Errorf("expected a:\"A\", but got a:%q", a)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
framework.ExpectNoError(err, "waiting for CR to be defaulted on read")
|
||||||
|
|
||||||
|
// create CR with default in storage
|
||||||
|
name2 := names.SimpleNameGenerator.GenerateName("cr-2")
|
||||||
|
u2, err := crClient.Create(&unstructured.Unstructured{Object: map[string]interface{}{
|
||||||
|
"apiVersion": gvr.Group + "/" + gvr.Version,
|
||||||
|
"kind": crd.Spec.Names.Kind,
|
||||||
|
"metadata": map[string]interface{}{
|
||||||
|
"name": name2,
|
||||||
|
},
|
||||||
|
}}, metav1.CreateOptions{})
|
||||||
|
framework.ExpectNoError(err, "creating CR")
|
||||||
|
v, found, err := unstructured.NestedFieldNoCopy(u2.Object, "a")
|
||||||
|
framework.ExpectEqual(found, true, "\"a\" is defaulted")
|
||||||
|
framework.ExpectEqual(v, "A", "\"a\" is defaulted to \"A\"")
|
||||||
|
|
||||||
|
// Deleting default for a, adding default "B" for b and waiting for the CR to get defaulted on read for b
|
||||||
|
crd, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Patch(crd.Name, types.JSONPatchType, []byte(`[
|
||||||
|
{"op":"remove","path":"/spec/versions/0/schema/openAPIV3Schema/properties/a/default"},
|
||||||
|
{"op":"add","path":"/spec/versions/0/schema/openAPIV3Schema/properties/b/default", "value": "B"}
|
||||||
|
]`))
|
||||||
|
framework.ExpectNoError(err, "setting default for b to \"B\" and remove default for a")
|
||||||
|
|
||||||
|
err = wait.PollImmediate(time.Millisecond*100, wait.ForeverTestTimeout, func() (bool, error) {
|
||||||
|
u2, err := crClient.Get(name2, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
b, found, err := unstructured.NestedFieldNoCopy(u2.Object, "b")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if b != "B" {
|
||||||
|
return false, fmt.Errorf("expected b:\"B\", but got b:%q", b)
|
||||||
|
}
|
||||||
|
a, found, err := unstructured.NestedFieldNoCopy(u2.Object, "a")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return false, fmt.Errorf("expected a:\"A\" to be unchanged, but it was removed")
|
||||||
|
}
|
||||||
|
if a != "A" {
|
||||||
|
return false, fmt.Errorf("expected a:\"A\" to be unchanged, but it changed to %q", a)
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
})
|
||||||
|
framework.ExpectNoError(err, "waiting for CR to be defaulted on read for b and a staying the same")
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
func unstructuredToCRD(obj *unstructured.Unstructured) *v1.CustomResourceDefinition {
|
func unstructuredToCRD(obj *unstructured.Unstructured) *v1.CustomResourceDefinition {
|
||||||
|
Loading…
Reference in New Issue
Block a user