mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
Immutable field and validation
This commit is contained in:
parent
ef69bc910f
commit
e612ebfdff
@ -4735,6 +4735,12 @@ type Secret struct {
|
|||||||
// +optional
|
// +optional
|
||||||
metav1.ObjectMeta
|
metav1.ObjectMeta
|
||||||
|
|
||||||
|
// Immutable field, if set, ensures that data stored in the Secret cannot
|
||||||
|
// be updated (only object metadata can be modified).
|
||||||
|
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
|
||||||
|
// +optional
|
||||||
|
Immutable *bool
|
||||||
|
|
||||||
// Data contains the secret data. Each key must consist of alphanumeric
|
// Data contains the secret data. Each key must consist of alphanumeric
|
||||||
// characters, '-', '_' or '.'. The serialized form of the secret data is a
|
// characters, '-', '_' or '.'. The serialized form of the secret data is a
|
||||||
// base64 encoded string, representing the arbitrary (possibly non-string)
|
// base64 encoded string, representing the arbitrary (possibly non-string)
|
||||||
@ -4857,6 +4863,12 @@ type ConfigMap struct {
|
|||||||
// +optional
|
// +optional
|
||||||
metav1.ObjectMeta
|
metav1.ObjectMeta
|
||||||
|
|
||||||
|
// Immutable field, if set, ensures that data stored in the ConfigMap cannot
|
||||||
|
// be updated (only object metadata can be modified).
|
||||||
|
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
|
||||||
|
// +optional
|
||||||
|
Immutable *bool
|
||||||
|
|
||||||
// Data contains the configuration data.
|
// Data contains the configuration data.
|
||||||
// Each key must consist of alphanumeric characters, '-', '_' or '.'.
|
// Each key must consist of alphanumeric characters, '-', '_' or '.'.
|
||||||
// Values with non-UTF-8 byte sequences must use the BinaryData field.
|
// Values with non-UTF-8 byte sequences must use the BinaryData field.
|
||||||
|
@ -5005,6 +5005,16 @@ func ValidateSecretUpdate(newSecret, oldSecret *core.Secret) field.ErrorList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...)
|
allErrs = append(allErrs, ValidateImmutableField(newSecret.Type, oldSecret.Type, field.NewPath("type"))...)
|
||||||
|
if oldSecret.Immutable != nil && *oldSecret.Immutable {
|
||||||
|
if !reflect.DeepEqual(newSecret.Immutable, oldSecret.Immutable) {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set"))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(newSecret.Data, oldSecret.Data) {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set"))
|
||||||
|
}
|
||||||
|
// We don't validate StringData, as it was already converted back to Data
|
||||||
|
// before validation is happening.
|
||||||
|
}
|
||||||
|
|
||||||
allErrs = append(allErrs, ValidateSecret(newSecret)...)
|
allErrs = append(allErrs, ValidateSecret(newSecret)...)
|
||||||
return allErrs
|
return allErrs
|
||||||
@ -5051,8 +5061,20 @@ func ValidateConfigMap(cfg *core.ConfigMap) field.ErrorList {
|
|||||||
func ValidateConfigMapUpdate(newCfg, oldCfg *core.ConfigMap) field.ErrorList {
|
func ValidateConfigMapUpdate(newCfg, oldCfg *core.ConfigMap) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, ValidateObjectMetaUpdate(&newCfg.ObjectMeta, &oldCfg.ObjectMeta, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, ValidateObjectMetaUpdate(&newCfg.ObjectMeta, &oldCfg.ObjectMeta, field.NewPath("metadata"))...)
|
||||||
allErrs = append(allErrs, ValidateConfigMap(newCfg)...)
|
|
||||||
|
|
||||||
|
if oldCfg.Immutable != nil && *oldCfg.Immutable {
|
||||||
|
if !reflect.DeepEqual(newCfg.Immutable, oldCfg.Immutable) {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("immutable"), "field is immutable when `immutable` is set"))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(newCfg.Data, oldCfg.Data) {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("data"), "field is immutable when `immutable` is set"))
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(newCfg.BinaryData, oldCfg.BinaryData) {
|
||||||
|
allErrs = append(allErrs, field.Forbidden(field.NewPath("binaryData"), "field is immutable when `immutable` is set"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allErrs = append(allErrs, ValidateConfigMap(newCfg)...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13117,6 +13117,104 @@ func TestValidateSecret(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateSecretUpdate(t *testing.T) {
|
||||||
|
validSecret := func() core.Secret {
|
||||||
|
return core.Secret{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
ResourceVersion: "20",
|
||||||
|
},
|
||||||
|
Data: map[string][]byte{
|
||||||
|
"data-1": []byte("bar"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
falseVal := false
|
||||||
|
trueVal := true
|
||||||
|
|
||||||
|
secret := validSecret()
|
||||||
|
immutableSecret := validSecret()
|
||||||
|
immutableSecret.Immutable = &trueVal
|
||||||
|
mutableSecret := validSecret()
|
||||||
|
mutableSecret.Immutable = &falseVal
|
||||||
|
|
||||||
|
secretWithData := validSecret()
|
||||||
|
secretWithData.Data["data-2"] = []byte("baz")
|
||||||
|
immutableSecretWithData := validSecret()
|
||||||
|
immutableSecretWithData.Immutable = &trueVal
|
||||||
|
immutableSecretWithData.Data["data-2"] = []byte("baz")
|
||||||
|
|
||||||
|
secretWithChangedData := validSecret()
|
||||||
|
secretWithChangedData.Data["data-1"] = []byte("foo")
|
||||||
|
immutableSecretWithChangedData := validSecret()
|
||||||
|
immutableSecretWithChangedData.Immutable = &trueVal
|
||||||
|
immutableSecretWithChangedData.Data["data-1"] = []byte("foo")
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
oldSecret core.Secret
|
||||||
|
newSecret core.Secret
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "mark secret immutable",
|
||||||
|
oldSecret: secret,
|
||||||
|
newSecret: immutableSecret,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "revert immutable secret",
|
||||||
|
oldSecret: immutableSecret,
|
||||||
|
newSecret: secret,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "makr immutable secret mutable",
|
||||||
|
oldSecret: immutableSecret,
|
||||||
|
newSecret: mutableSecret,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add data in secret",
|
||||||
|
oldSecret: secret,
|
||||||
|
newSecret: secretWithData,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add data in immutable secret",
|
||||||
|
oldSecret: immutableSecret,
|
||||||
|
newSecret: immutableSecretWithData,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change data in secret",
|
||||||
|
oldSecret: secret,
|
||||||
|
newSecret: secretWithChangedData,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change data in immutable secret",
|
||||||
|
oldSecret: immutableSecret,
|
||||||
|
newSecret: immutableSecretWithChangedData,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
errs := ValidateSecretUpdate(&tc.newSecret, &tc.oldSecret)
|
||||||
|
if tc.valid && len(errs) > 0 {
|
||||||
|
t.Errorf("Unexpected error: %v", errs)
|
||||||
|
}
|
||||||
|
if !tc.valid && len(errs) == 0 {
|
||||||
|
t.Errorf("Unexpected lack of error")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestValidateDockerConfigSecret(t *testing.T) {
|
func TestValidateDockerConfigSecret(t *testing.T) {
|
||||||
validDockerSecret := func() core.Secret {
|
validDockerSecret := func() core.Secret {
|
||||||
return core.Secret{
|
return core.Secret{
|
||||||
@ -13731,40 +13829,105 @@ func TestValidateConfigMapUpdate(t *testing.T) {
|
|||||||
Data: data,
|
Data: data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
validConfigMap := func() core.ConfigMap {
|
||||||
|
return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"})
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
falseVal := false
|
||||||
validConfigMap = newConfigMap("1", "validname", "validns", map[string]string{"key": "value"})
|
trueVal := true
|
||||||
noVersion = newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
|
|
||||||
)
|
configMap := validConfigMap()
|
||||||
|
immutableConfigMap := validConfigMap()
|
||||||
|
immutableConfigMap.Immutable = &trueVal
|
||||||
|
mutableConfigMap := validConfigMap()
|
||||||
|
mutableConfigMap.Immutable = &falseVal
|
||||||
|
|
||||||
|
configMapWithData := validConfigMap()
|
||||||
|
configMapWithData.Data["key-2"] = "value-2"
|
||||||
|
immutableConfigMapWithData := validConfigMap()
|
||||||
|
immutableConfigMapWithData.Immutable = &trueVal
|
||||||
|
immutableConfigMapWithData.Data["key-2"] = "value-2"
|
||||||
|
|
||||||
|
configMapWithChangedData := validConfigMap()
|
||||||
|
configMapWithChangedData.Data["key"] = "foo"
|
||||||
|
immutableConfigMapWithChangedData := validConfigMap()
|
||||||
|
immutableConfigMapWithChangedData.Immutable = &trueVal
|
||||||
|
immutableConfigMapWithChangedData.Data["key"] = "foo"
|
||||||
|
|
||||||
|
noVersion := newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
newCfg core.ConfigMap
|
newCfg core.ConfigMap
|
||||||
oldCfg core.ConfigMap
|
oldCfg core.ConfigMap
|
||||||
isValid bool
|
valid bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid",
|
name: "valid",
|
||||||
newCfg: validConfigMap,
|
newCfg: configMap,
|
||||||
oldCfg: validConfigMap,
|
oldCfg: configMap,
|
||||||
isValid: true,
|
valid: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid",
|
name: "invalid",
|
||||||
newCfg: noVersion,
|
newCfg: noVersion,
|
||||||
oldCfg: validConfigMap,
|
oldCfg: configMap,
|
||||||
isValid: false,
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mark configmap immutable",
|
||||||
|
oldCfg: configMap,
|
||||||
|
newCfg: immutableConfigMap,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "revert immutable configmap",
|
||||||
|
oldCfg: immutableConfigMap,
|
||||||
|
newCfg: configMap,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mark immutable configmap mutable",
|
||||||
|
oldCfg: immutableConfigMap,
|
||||||
|
newCfg: mutableConfigMap,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add data in configmap",
|
||||||
|
oldCfg: configMap,
|
||||||
|
newCfg: configMapWithData,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add data in immutable configmap",
|
||||||
|
oldCfg: immutableConfigMap,
|
||||||
|
newCfg: immutableConfigMapWithData,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change data in configmap",
|
||||||
|
oldCfg: configMap,
|
||||||
|
newCfg: configMapWithChangedData,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "change data in immutable configmap",
|
||||||
|
oldCfg: immutableConfigMap,
|
||||||
|
newCfg: immutableConfigMapWithChangedData,
|
||||||
|
valid: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
if tc.isValid && len(errs) > 0 {
|
errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
|
||||||
t.Errorf("%v: unexpected error: %v", tc.name, errs)
|
if tc.valid && len(errs) > 0 {
|
||||||
}
|
t.Errorf("Unexpected error: %v", errs)
|
||||||
if !tc.isValid && len(errs) == 0 {
|
}
|
||||||
t.Errorf("%v: unexpected non-error", tc.name)
|
if !tc.valid && len(errs) == 0 {
|
||||||
}
|
t.Errorf("Unexpected lack of error")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -548,6 +548,12 @@ const (
|
|||||||
//
|
//
|
||||||
// Enables topology aware service routing
|
// Enables topology aware service routing
|
||||||
ServiceTopology featuregate.Feature = "ServiceTopology"
|
ServiceTopology featuregate.Feature = "ServiceTopology"
|
||||||
|
|
||||||
|
// owner: @wojtek-t
|
||||||
|
// alpha: v1.18
|
||||||
|
//
|
||||||
|
// Enables a feature to make secrets and configmaps data immutable.
|
||||||
|
ImmutableEphemeralVolumes featuregate.Feature = "ImmutableEphemeralVolumes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -634,6 +640,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
|||||||
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.Beta},
|
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.Beta},
|
||||||
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},
|
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},
|
||||||
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
|
ServiceTopology: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
ImmutableEphemeralVolumes: {Default: false, PreRelease: featuregate.Alpha},
|
||||||
|
|
||||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||||
// unintentionally on either side:
|
// unintentionally on either side:
|
||||||
|
@ -28,9 +28,11 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
pkgstorage "k8s.io/apiserver/pkg/storage"
|
pkgstorage "k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
// strategy implements behavior for ConfigMap objects
|
// strategy implements behavior for ConfigMap objects
|
||||||
@ -54,7 +56,8 @@ func (strategy) NamespaceScoped() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||||
_ = obj.(*api.ConfigMap)
|
configMap := obj.(*api.ConfigMap)
|
||||||
|
dropDisabledFields(configMap, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
@ -72,12 +75,9 @@ func (strategy) AllowCreateOnUpdate() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) PrepareForUpdate(ctx context.Context, newObj, oldObj runtime.Object) {
|
func (strategy) PrepareForUpdate(ctx context.Context, newObj, oldObj runtime.Object) {
|
||||||
_ = oldObj.(*api.ConfigMap)
|
oldConfigMap := oldObj.(*api.ConfigMap)
|
||||||
_ = newObj.(*api.ConfigMap)
|
newConfigMap := newObj.(*api.ConfigMap)
|
||||||
}
|
dropDisabledFields(newConfigMap, oldConfigMap)
|
||||||
|
|
||||||
func (strategy) AllowUnconditionalUpdate() bool {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) ValidateUpdate(ctx context.Context, newObj, oldObj runtime.Object) field.ErrorList {
|
func (strategy) ValidateUpdate(ctx context.Context, newObj, oldObj runtime.Object) field.ErrorList {
|
||||||
@ -86,6 +86,20 @@ func (strategy) ValidateUpdate(ctx context.Context, newObj, oldObj runtime.Objec
|
|||||||
return validation.ValidateConfigMapUpdate(newCfg, oldCfg)
|
return validation.ValidateConfigMapUpdate(newCfg, oldCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isImmutableInUse(configMap *api.ConfigMap) bool {
|
||||||
|
return configMap != nil && configMap.Immutable != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropDisabledFields(configMap *api.ConfigMap, oldConfigMap *api.ConfigMap) {
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.ImmutableEphemeralVolumes) && !isImmutableInUse(oldConfigMap) {
|
||||||
|
configMap.Immutable = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (strategy) AllowUnconditionalUpdate() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
||||||
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
||||||
configMap, ok := obj.(*api.ConfigMap)
|
configMap, ok := obj.(*api.ConfigMap)
|
||||||
|
@ -29,9 +29,11 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
pkgstorage "k8s.io/apiserver/pkg/storage"
|
pkgstorage "k8s.io/apiserver/pkg/storage"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||||
api "k8s.io/kubernetes/pkg/apis/core"
|
api "k8s.io/kubernetes/pkg/apis/core"
|
||||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
|
"k8s.io/kubernetes/pkg/features"
|
||||||
)
|
)
|
||||||
|
|
||||||
// strategy implements behavior for Secret objects
|
// strategy implements behavior for Secret objects
|
||||||
@ -53,6 +55,8 @@ func (strategy) NamespaceScoped() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
func (strategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||||
|
secret := obj.(*api.Secret)
|
||||||
|
dropDisabledFields(secret, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
@ -67,12 +71,25 @@ func (strategy) AllowCreateOnUpdate() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
||||||
|
newSecret := obj.(*api.Secret)
|
||||||
|
oldSecret := old.(*api.Secret)
|
||||||
|
dropDisabledFields(newSecret, oldSecret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
return validation.ValidateSecretUpdate(obj.(*api.Secret), old.(*api.Secret))
|
return validation.ValidateSecretUpdate(obj.(*api.Secret), old.(*api.Secret))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isImmutableInUse(secret *api.Secret) bool {
|
||||||
|
return secret != nil && secret.Immutable != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dropDisabledFields(secret *api.Secret, oldSecret *api.Secret) {
|
||||||
|
if !utilfeature.DefaultFeatureGate.Enabled(features.ImmutableEphemeralVolumes) && !isImmutableInUse(oldSecret) {
|
||||||
|
secret.Immutable = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (strategy) AllowUnconditionalUpdate() bool {
|
func (strategy) AllowUnconditionalUpdate() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -5424,6 +5424,14 @@ type Secret struct {
|
|||||||
// +optional
|
// +optional
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
// Immutable, if set to true, ensures that data stored in the Secret cannot
|
||||||
|
// be updated (only object metadata can be modified).
|
||||||
|
// If not set to true, the field can be modified at any time.
|
||||||
|
// Defaulted to nil.
|
||||||
|
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
|
||||||
|
// +optional
|
||||||
|
Immutable *bool `json:"immutable,omitempty"`
|
||||||
|
|
||||||
// Data contains the secret data. Each key must consist of alphanumeric
|
// Data contains the secret data. Each key must consist of alphanumeric
|
||||||
// characters, '-', '_' or '.'. The serialized form of the secret data is a
|
// characters, '-', '_' or '.'. The serialized form of the secret data is a
|
||||||
// base64 encoded string, representing the arbitrary (possibly non-string)
|
// base64 encoded string, representing the arbitrary (possibly non-string)
|
||||||
@ -5557,6 +5565,14 @@ type ConfigMap struct {
|
|||||||
// +optional
|
// +optional
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||||
|
|
||||||
|
// Immutable, if set to true, ensures that data stored in the ConfigMap cannot
|
||||||
|
// be updated (only object metadata can be modified).
|
||||||
|
// If not set to true, the field can be modified at any time.
|
||||||
|
// Defaulted to nil.
|
||||||
|
// This is an alpha field enabled by ImmutableEphemeralVolumes feature gate.
|
||||||
|
// +optional
|
||||||
|
Immutable *bool `json:"immutable,omitempty"`
|
||||||
|
|
||||||
// Data contains the configuration data.
|
// Data contains the configuration data.
|
||||||
// Each key must consist of alphanumeric characters, '-', '_' or '.'.
|
// Each key must consist of alphanumeric characters, '-', '_' or '.'.
|
||||||
// Values with non-UTF-8 byte sequences must use the BinaryData field.
|
// Values with non-UTF-8 byte sequences must use the BinaryData field.
|
||||||
|
@ -90,7 +90,14 @@ func SecretHash(sec *v1.Secret) (string, error) {
|
|||||||
// Data, Kind, and Name are taken into account.
|
// Data, Kind, and Name are taken into account.
|
||||||
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||||
// json.Marshal sorts the keys in a stable order in the encoding
|
// json.Marshal sorts the keys in a stable order in the encoding
|
||||||
m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data}
|
m := map[string]interface{}{
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"name": cm.Name,
|
||||||
|
"data": cm.Data,
|
||||||
|
}
|
||||||
|
if cm.Immutable != nil {
|
||||||
|
m["immutable"] = *cm.Immutable
|
||||||
|
}
|
||||||
if len(cm.BinaryData) > 0 {
|
if len(cm.BinaryData) > 0 {
|
||||||
m["binaryData"] = cm.BinaryData
|
m["binaryData"] = cm.BinaryData
|
||||||
}
|
}
|
||||||
@ -105,7 +112,16 @@ func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
|||||||
// Data, Kind, Name, and Type are taken into account.
|
// Data, Kind, Name, and Type are taken into account.
|
||||||
func encodeSecret(sec *v1.Secret) (string, error) {
|
func encodeSecret(sec *v1.Secret) (string, error) {
|
||||||
// json.Marshal sorts the keys in a stable order in the encoding
|
// json.Marshal sorts the keys in a stable order in the encoding
|
||||||
data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data})
|
m := map[string]interface{}{
|
||||||
|
"kind": "Secret",
|
||||||
|
"type": sec.Type,
|
||||||
|
"name": sec.Name,
|
||||||
|
"data": sec.Data,
|
||||||
|
}
|
||||||
|
if sec.Immutable != nil {
|
||||||
|
m["immutable"] = *sec.Immutable
|
||||||
|
}
|
||||||
|
data, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -178,8 +178,8 @@ not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
|
|||||||
obj interface{}
|
obj interface{}
|
||||||
expect int
|
expect int
|
||||||
}{
|
}{
|
||||||
{"ConfigMap", v1.ConfigMap{}, 4},
|
{"ConfigMap", v1.ConfigMap{}, 5},
|
||||||
{"Secret", v1.Secret{}, 5},
|
{"Secret", v1.Secret{}, 6},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
val := reflect.ValueOf(c.obj)
|
val := reflect.ValueOf(c.obj)
|
||||||
|
@ -56,7 +56,14 @@ func SecretHash(sec *v1.Secret) (string, error) {
|
|||||||
// Data, Kind, and Name are taken into account.
|
// Data, Kind, and Name are taken into account.
|
||||||
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||||
// json.Marshal sorts the keys in a stable order in the encoding
|
// json.Marshal sorts the keys in a stable order in the encoding
|
||||||
m := map[string]interface{}{"kind": "ConfigMap", "name": cm.Name, "data": cm.Data}
|
m := map[string]interface{}{
|
||||||
|
"kind": "ConfigMap",
|
||||||
|
"name": cm.Name,
|
||||||
|
"data": cm.Data,
|
||||||
|
}
|
||||||
|
if cm.Immutable != nil {
|
||||||
|
m["immutable"] = *cm.Immutable
|
||||||
|
}
|
||||||
if len(cm.BinaryData) > 0 {
|
if len(cm.BinaryData) > 0 {
|
||||||
m["binaryData"] = cm.BinaryData
|
m["binaryData"] = cm.BinaryData
|
||||||
}
|
}
|
||||||
@ -70,8 +77,17 @@ func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
|||||||
// encodeSecret encodes a Secret.
|
// encodeSecret encodes a Secret.
|
||||||
// Data, Kind, Name, and Type are taken into account.
|
// Data, Kind, Name, and Type are taken into account.
|
||||||
func encodeSecret(sec *v1.Secret) (string, error) {
|
func encodeSecret(sec *v1.Secret) (string, error) {
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"kind": "Secret",
|
||||||
|
"type": sec.Type,
|
||||||
|
"name": sec.Name,
|
||||||
|
"data": sec.Data,
|
||||||
|
}
|
||||||
|
if sec.Immutable != nil {
|
||||||
|
m["immutable"] = *sec.Immutable
|
||||||
|
}
|
||||||
// json.Marshal sorts the keys in a stable order in the encoding
|
// json.Marshal sorts the keys in a stable order in the encoding
|
||||||
data, err := json.Marshal(map[string]interface{}{"kind": "Secret", "type": sec.Type, "name": sec.Name, "data": sec.Data})
|
data, err := json.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -164,8 +164,8 @@ not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
|
|||||||
obj interface{}
|
obj interface{}
|
||||||
expect int
|
expect int
|
||||||
}{
|
}{
|
||||||
{"ConfigMap", v1.ConfigMap{}, 4},
|
{"ConfigMap", v1.ConfigMap{}, 5},
|
||||||
{"Secret", v1.Secret{}, 5},
|
{"Secret", v1.Secret{}, 6},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
val := reflect.ValueOf(c.obj)
|
val := reflect.ValueOf(c.obj)
|
||||||
|
Loading…
Reference in New Issue
Block a user