mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-24 12:15:52 +00:00
Merge pull request #86377 from wojtek-t/immutable_secrets_api
API for immutable Secrets and ConfigMaps
This commit is contained in:
commit
37d9c22abe
8
api/openapi-spec/swagger.json
generated
8
api/openapi-spec/swagger.json
generated
@ -5354,6 +5354,10 @@
|
||||
"description": "Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'. Values with non-UTF-8 byte sequences must use the BinaryData field. The keys stored in Data must not overlap with the keys in the BinaryData field, this is enforced during validation process.",
|
||||
"type": "object"
|
||||
},
|
||||
"immutable": {
|
||||
"description": "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.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
"type": "string"
|
||||
@ -9455,6 +9459,10 @@
|
||||
"description": "Data contains the secret data. Each key must consist of alphanumeric characters, '-', '_' or '.'. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4",
|
||||
"type": "object"
|
||||
},
|
||||
"immutable": {
|
||||
"description": "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.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"kind": {
|
||||
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
"type": "string"
|
||||
|
@ -4735,6 +4735,12 @@ type Secret struct {
|
||||
// +optional
|
||||
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
|
||||
// characters, '-', '_' or '.'. The serialized form of the secret data is a
|
||||
// base64 encoded string, representing the arbitrary (possibly non-string)
|
||||
@ -4857,6 +4863,12 @@ type ConfigMap struct {
|
||||
// +optional
|
||||
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.
|
||||
// Each key must consist of alphanumeric characters, '-', '_' or '.'.
|
||||
// Values with non-UTF-8 byte sequences must use the BinaryData field.
|
||||
|
4
pkg/apis/core/v1/zz_generated.conversion.go
generated
4
pkg/apis/core/v1/zz_generated.conversion.go
generated
@ -2642,6 +2642,7 @@ func Convert_core_ComponentStatusList_To_v1_ComponentStatusList(in *core.Compone
|
||||
|
||||
func autoConvert_v1_ConfigMap_To_core_ConfigMap(in *v1.ConfigMap, out *core.ConfigMap, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
out.Immutable = (*bool)(unsafe.Pointer(in.Immutable))
|
||||
out.Data = *(*map[string]string)(unsafe.Pointer(&in.Data))
|
||||
out.BinaryData = *(*map[string][]byte)(unsafe.Pointer(&in.BinaryData))
|
||||
return nil
|
||||
@ -2654,6 +2655,7 @@ func Convert_v1_ConfigMap_To_core_ConfigMap(in *v1.ConfigMap, out *core.ConfigMa
|
||||
|
||||
func autoConvert_core_ConfigMap_To_v1_ConfigMap(in *core.ConfigMap, out *v1.ConfigMap, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
out.Immutable = (*bool)(unsafe.Pointer(in.Immutable))
|
||||
out.Data = *(*map[string]string)(unsafe.Pointer(&in.Data))
|
||||
out.BinaryData = *(*map[string][]byte)(unsafe.Pointer(&in.BinaryData))
|
||||
return nil
|
||||
@ -7006,6 +7008,7 @@ func Convert_core_ScopedResourceSelectorRequirement_To_v1_ScopedResourceSelector
|
||||
|
||||
func autoConvert_v1_Secret_To_core_Secret(in *v1.Secret, out *core.Secret, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
out.Immutable = (*bool)(unsafe.Pointer(in.Immutable))
|
||||
out.Data = *(*map[string][]byte)(unsafe.Pointer(&in.Data))
|
||||
// INFO: in.StringData opted out of conversion generation
|
||||
out.Type = core.SecretType(in.Type)
|
||||
@ -7014,6 +7017,7 @@ func autoConvert_v1_Secret_To_core_Secret(in *v1.Secret, out *core.Secret, s con
|
||||
|
||||
func autoConvert_core_Secret_To_v1_Secret(in *core.Secret, out *v1.Secret, s conversion.Scope) error {
|
||||
out.ObjectMeta = in.ObjectMeta
|
||||
out.Immutable = (*bool)(unsafe.Pointer(in.Immutable))
|
||||
out.Data = *(*map[string][]byte)(unsafe.Pointer(&in.Data))
|
||||
out.Type = v1.SecretType(in.Type)
|
||||
return nil
|
||||
|
@ -5005,6 +5005,16 @@ func ValidateSecretUpdate(newSecret, oldSecret *core.Secret) field.ErrorList {
|
||||
}
|
||||
|
||||
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)...)
|
||||
return allErrs
|
||||
@ -5051,8 +5061,20 @@ func ValidateConfigMap(cfg *core.ConfigMap) field.ErrorList {
|
||||
func ValidateConfigMapUpdate(newCfg, oldCfg *core.ConfigMap) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
validDockerSecret := func() core.Secret {
|
||||
return core.Secret{
|
||||
@ -13731,40 +13829,105 @@ func TestValidateConfigMapUpdate(t *testing.T) {
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
validConfigMap := func() core.ConfigMap {
|
||||
return newConfigMap("1", "validname", "validdns", map[string]string{"key": "value"})
|
||||
}
|
||||
|
||||
var (
|
||||
validConfigMap = newConfigMap("1", "validname", "validns", map[string]string{"key": "value"})
|
||||
noVersion = newConfigMap("", "validname", "validns", map[string]string{"key": "value"})
|
||||
)
|
||||
falseVal := false
|
||||
trueVal := true
|
||||
|
||||
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 {
|
||||
name string
|
||||
newCfg core.ConfigMap
|
||||
oldCfg core.ConfigMap
|
||||
isValid bool
|
||||
name string
|
||||
newCfg core.ConfigMap
|
||||
oldCfg core.ConfigMap
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
newCfg: validConfigMap,
|
||||
oldCfg: validConfigMap,
|
||||
isValid: true,
|
||||
name: "valid",
|
||||
newCfg: configMap,
|
||||
oldCfg: configMap,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
newCfg: noVersion,
|
||||
oldCfg: validConfigMap,
|
||||
isValid: false,
|
||||
name: "invalid",
|
||||
newCfg: noVersion,
|
||||
oldCfg: configMap,
|
||||
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 {
|
||||
errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
|
||||
if tc.isValid && len(errs) > 0 {
|
||||
t.Errorf("%v: unexpected error: %v", tc.name, errs)
|
||||
}
|
||||
if !tc.isValid && len(errs) == 0 {
|
||||
t.Errorf("%v: unexpected non-error", tc.name)
|
||||
}
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errs := ValidateConfigMapUpdate(&tc.newCfg, &tc.oldCfg)
|
||||
if tc.valid && len(errs) > 0 {
|
||||
t.Errorf("Unexpected error: %v", errs)
|
||||
}
|
||||
if !tc.valid && len(errs) == 0 {
|
||||
t.Errorf("Unexpected lack of error")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
10
pkg/apis/core/zz_generated.deepcopy.go
generated
10
pkg/apis/core/zz_generated.deepcopy.go
generated
@ -519,6 +519,11 @@ func (in *ConfigMap) DeepCopyInto(out *ConfigMap) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
if in.Immutable != nil {
|
||||
in, out := &in.Immutable, &out.Immutable
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Data != nil {
|
||||
in, out := &in.Data, &out.Data
|
||||
*out = make(map[string]string, len(*in))
|
||||
@ -4660,6 +4665,11 @@ func (in *Secret) DeepCopyInto(out *Secret) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
if in.Immutable != nil {
|
||||
in, out := &in.Immutable, &out.Immutable
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Data != nil {
|
||||
in, out := &in.Data, &out.Data
|
||||
*out = make(map[string][]byte, len(*in))
|
||||
|
@ -524,6 +524,12 @@ const (
|
||||
//
|
||||
// Enables topology aware service routing
|
||||
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() {
|
||||
@ -607,6 +613,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
AllowInsecureBackendProxy: {Default: true, PreRelease: featuregate.Beta},
|
||||
PodDisruptionBudget: {Default: true, PreRelease: featuregate.Beta},
|
||||
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
|
||||
// unintentionally on either side:
|
||||
|
@ -17,6 +17,7 @@ go_library(
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/validation:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
@ -25,6 +26,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -28,9 +28,11 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
pkgstorage "k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// strategy implements behavior for ConfigMap objects
|
||||
@ -54,7 +56,8 @@ func (strategy) NamespaceScoped() bool {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -72,12 +75,9 @@ func (strategy) AllowCreateOnUpdate() bool {
|
||||
}
|
||||
|
||||
func (strategy) PrepareForUpdate(ctx context.Context, newObj, oldObj runtime.Object) {
|
||||
_ = oldObj.(*api.ConfigMap)
|
||||
_ = newObj.(*api.ConfigMap)
|
||||
}
|
||||
|
||||
func (strategy) AllowUnconditionalUpdate() bool {
|
||||
return true
|
||||
oldConfigMap := oldObj.(*api.ConfigMap)
|
||||
newConfigMap := newObj.(*api.ConfigMap)
|
||||
dropDisabledFields(newConfigMap, oldConfigMap)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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.
|
||||
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
||||
configMap, ok := obj.(*api.ConfigMap)
|
||||
|
@ -17,6 +17,7 @@ go_library(
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/validation:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
@ -26,6 +27,7 @@ go_library(
|
||||
"//staging/src/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/storage/names:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -29,9 +29,11 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
pkgstorage "k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// strategy implements behavior for Secret objects
|
||||
@ -53,6 +55,8 @@ func (strategy) NamespaceScoped() bool {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -67,12 +71,25 @@ func (strategy) AllowCreateOnUpdate() bool {
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return true
|
||||
}
|
||||
|
1778
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
1778
staging/src/k8s.io/api/core/v1/generated.pb.go
generated
File diff suppressed because it is too large
Load Diff
@ -455,6 +455,14 @@ message ConfigMap {
|
||||
// +optional
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||
|
||||
// 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
|
||||
optional bool immutable = 4;
|
||||
|
||||
// Data contains the configuration data.
|
||||
// Each key must consist of alphanumeric characters, '-', '_' or '.'.
|
||||
// Values with non-UTF-8 byte sequences must use the BinaryData field.
|
||||
@ -4256,6 +4264,14 @@ message Secret {
|
||||
// +optional
|
||||
optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1;
|
||||
|
||||
// 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
|
||||
optional bool immutable = 5;
|
||||
|
||||
// Data contains the secret data. Each key must consist of alphanumeric
|
||||
// characters, '-', '_' or '.'. The serialized form of the secret data is a
|
||||
// base64 encoded string, representing the arbitrary (possibly non-string)
|
||||
|
@ -5424,6 +5424,14 @@ type Secret struct {
|
||||
// +optional
|
||||
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" protobuf:"varint,5,opt,name=immutable"`
|
||||
|
||||
// Data contains the secret data. Each key must consist of alphanumeric
|
||||
// characters, '-', '_' or '.'. The serialized form of the secret data is a
|
||||
// base64 encoded string, representing the arbitrary (possibly non-string)
|
||||
@ -5557,6 +5565,14 @@ type ConfigMap struct {
|
||||
// +optional
|
||||
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" protobuf:"varint,4,opt,name=immutable"`
|
||||
|
||||
// Data contains the configuration data.
|
||||
// Each key must consist of alphanumeric characters, '-', '_' or '.'.
|
||||
// Values with non-UTF-8 byte sequences must use the BinaryData field.
|
||||
|
@ -252,6 +252,7 @@ func (ComponentStatusList) SwaggerDoc() map[string]string {
|
||||
var map_ConfigMap = map[string]string{
|
||||
"": "ConfigMap holds configuration data for pods to consume.",
|
||||
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
|
||||
"immutable": "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.",
|
||||
"data": "Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'. Values with non-UTF-8 byte sequences must use the BinaryData field. The keys stored in Data must not overlap with the keys in the BinaryData field, this is enforced during validation process.",
|
||||
"binaryData": "BinaryData contains the binary data. Each key must consist of alphanumeric characters, '-', '_' or '.'. BinaryData can contain byte sequences that are not in the UTF-8 range. The keys stored in BinaryData must not overlap with the ones in the Data field, this is enforced during validation process. Using this field will require 1.10+ apiserver and kubelet.",
|
||||
}
|
||||
@ -2015,6 +2016,7 @@ func (ScopedResourceSelectorRequirement) SwaggerDoc() map[string]string {
|
||||
var map_Secret = map[string]string{
|
||||
"": "Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.",
|
||||
"metadata": "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata",
|
||||
"immutable": "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.",
|
||||
"data": "Data contains the secret data. Each key must consist of alphanumeric characters, '-', '_' or '.'. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4",
|
||||
"stringData": "stringData allows specifying non-binary secret data in string form. It is provided as a write-only convenience method. All keys and values are merged into the data field on write, overwriting any existing values. It is never output when reading from the API.",
|
||||
"type": "Used to facilitate programmatic handling of secret data.",
|
||||
|
@ -519,6 +519,11 @@ func (in *ConfigMap) DeepCopyInto(out *ConfigMap) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
if in.Immutable != nil {
|
||||
in, out := &in.Immutable, &out.Immutable
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Data != nil {
|
||||
in, out := &in.Data, &out.Data
|
||||
*out = make(map[string]string, len(*in))
|
||||
@ -4663,6 +4668,11 @@ func (in *Secret) DeepCopyInto(out *Secret) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
if in.Immutable != nil {
|
||||
in, out := &in.Immutable, &out.Immutable
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.Data != nil {
|
||||
in, out := &in.Data, &out.Data
|
||||
*out = make(map[string][]byte, len(*in))
|
||||
|
@ -40,10 +40,11 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"immutable": false,
|
||||
"data": {
|
||||
"19": "20"
|
||||
},
|
||||
"binaryData": {
|
||||
"21": "Dg=="
|
||||
"21": "Hg=="
|
||||
}
|
||||
}
|
Binary file not shown.
@ -1,8 +1,9 @@
|
||||
apiVersion: v1
|
||||
binaryData:
|
||||
"21": Dg==
|
||||
"21": Hg==
|
||||
data:
|
||||
"19": "20"
|
||||
immutable: false
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
annotations:
|
||||
|
@ -40,11 +40,12 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"immutable": false,
|
||||
"data": {
|
||||
"19": "Hg=="
|
||||
"19": "xw=="
|
||||
},
|
||||
"stringData": {
|
||||
"20": "21"
|
||||
},
|
||||
"type": "r鯹)晿\u003co,c鮽ort昍řČ扷5ƗǸ"
|
||||
"type": "鯹)晿\u003c"
|
||||
}
|
Binary file not shown.
@ -1,6 +1,7 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
"19": Hg==
|
||||
"19": xw==
|
||||
immutable: false
|
||||
kind: Secret
|
||||
metadata:
|
||||
annotations:
|
||||
@ -33,4 +34,4 @@ metadata:
|
||||
uid: "7"
|
||||
stringData:
|
||||
"20": "21"
|
||||
type: r鯹)晿<o,c鮽ort昍řČ扷5ƗǸ
|
||||
type: 鯹)晿<
|
||||
|
@ -90,7 +90,14 @@ func SecretHash(sec *v1.Secret) (string, error) {
|
||||
// Data, Kind, and Name are taken into account.
|
||||
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||
// 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 {
|
||||
m["binaryData"] = cm.BinaryData
|
||||
}
|
||||
@ -105,7 +112,16 @@ func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||
// Data, Kind, Name, and Type are taken into account.
|
||||
func encodeSecret(sec *v1.Secret) (string, error) {
|
||||
// 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 {
|
||||
return "", err
|
||||
}
|
||||
|
@ -178,8 +178,8 @@ not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
|
||||
obj interface{}
|
||||
expect int
|
||||
}{
|
||||
{"ConfigMap", v1.ConfigMap{}, 4},
|
||||
{"Secret", v1.Secret{}, 5},
|
||||
{"ConfigMap", v1.ConfigMap{}, 5},
|
||||
{"Secret", v1.Secret{}, 6},
|
||||
}
|
||||
for _, c := range cases {
|
||||
val := reflect.ValueOf(c.obj)
|
||||
|
@ -56,7 +56,14 @@ func SecretHash(sec *v1.Secret) (string, error) {
|
||||
// Data, Kind, and Name are taken into account.
|
||||
func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||
// 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 {
|
||||
m["binaryData"] = cm.BinaryData
|
||||
}
|
||||
@ -70,8 +77,17 @@ func encodeConfigMap(cm *v1.ConfigMap) (string, error) {
|
||||
// encodeSecret encodes a Secret.
|
||||
// Data, Kind, Name, and Type are taken into account.
|
||||
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
|
||||
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 {
|
||||
return "", err
|
||||
}
|
||||
|
@ -164,8 +164,8 @@ not their metadata (e.g. the Data of a ConfigMap, but nothing in ObjectMeta).
|
||||
obj interface{}
|
||||
expect int
|
||||
}{
|
||||
{"ConfigMap", v1.ConfigMap{}, 4},
|
||||
{"Secret", v1.Secret{}, 5},
|
||||
{"ConfigMap", v1.ConfigMap{}, 5},
|
||||
{"Secret", v1.Secret{}, 6},
|
||||
}
|
||||
for _, c := range cases {
|
||||
val := reflect.ValueOf(c.obj)
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"github.com/onsi/ginkgo"
|
||||
"github.com/onsi/gomega"
|
||||
"k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
@ -549,9 +550,55 @@ var _ = ginkgo.Describe("[sig-storage] ConfigMap", func() {
|
||||
|
||||
})
|
||||
|
||||
//The pod is in pending during volume creation until the configMap objects are available
|
||||
//or until mount the configMap volume times out. There is no configMap object defined for the pod, so it should return timout exception unless it is marked optional.
|
||||
//Slow (~5 mins)
|
||||
// It should be forbidden to change data for configmaps marked as immutable, but
|
||||
// allowed to modify its metadata independently of its state.
|
||||
ginkgo.It("should be immutable if `immutable` field is set [Feature:ImmutableEphemeralVolume]", func() {
|
||||
name := "immutable"
|
||||
configMap := newConfigMap(f, name)
|
||||
|
||||
currentConfigMap, err := f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(configMap)
|
||||
framework.ExpectNoError(err, "Failed to create config map %q in namespace %q", configMap.Name, configMap.Namespace)
|
||||
|
||||
currentConfigMap.Data["data-4"] = "value-4"
|
||||
currentConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(currentConfigMap)
|
||||
framework.ExpectNoError(err, "Failed to update config map %q in namespace %q", configMap.Name, configMap.Namespace)
|
||||
|
||||
// Mark config map as immutable.
|
||||
trueVal := true
|
||||
currentConfigMap.Immutable = &trueVal
|
||||
currentConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(currentConfigMap)
|
||||
framework.ExpectNoError(err, "Failed to mark config map %q in namespace %q as immutable", configMap.Name, configMap.Namespace)
|
||||
|
||||
// Ensure data can't be changed now.
|
||||
currentConfigMap.Data["data-5"] = "value-5"
|
||||
_, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(currentConfigMap)
|
||||
framework.ExpectEqual(apierrors.IsInvalid(err), true)
|
||||
|
||||
// Ensure config map can't be switched from immutable to mutable.
|
||||
currentConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Get(name, metav1.GetOptions{})
|
||||
framework.ExpectNoError(err, "Failed to get config map %q in namespace %q", configMap.Name, configMap.Namespace)
|
||||
framework.ExpectEqual(*currentConfigMap.Immutable, true)
|
||||
|
||||
falseVal := false
|
||||
currentConfigMap.Immutable = &falseVal
|
||||
_, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(currentConfigMap)
|
||||
framework.ExpectEqual(apierrors.IsInvalid(err), true)
|
||||
|
||||
// Ensure that metadata can be changed.
|
||||
currentConfigMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Get(name, metav1.GetOptions{})
|
||||
framework.ExpectNoError(err, "Failed to get config map %q in namespace %q", configMap.Name, configMap.Namespace)
|
||||
currentConfigMap.Labels = map[string]string{"label1": "value1"}
|
||||
_, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Update(currentConfigMap)
|
||||
framework.ExpectNoError(err, "Failed to update config map %q in namespace %q", configMap.Name, configMap.Namespace)
|
||||
|
||||
// Ensure that immutable config map can be deleted.
|
||||
err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Delete(name, &metav1.DeleteOptions{})
|
||||
framework.ExpectNoError(err, "Failed to delete config map %q in namespace %q", configMap.Name, configMap.Namespace)
|
||||
})
|
||||
|
||||
// The pod is in pending during volume creation until the configMap objects are available
|
||||
// or until mount the configMap volume times out. There is no configMap object defined for the pod, so it should return timout exception unless it is marked optional.
|
||||
// Slow (~5 mins)
|
||||
ginkgo.It("Should fail non-optional pod creation due to configMap object does not exist [Slow]", func() {
|
||||
volumeMountPath := "/etc/configmap-volumes"
|
||||
podName := "pod-configmaps-" + string(uuid.NewUUID())
|
||||
@ -559,9 +606,9 @@ var _ = ginkgo.Describe("[sig-storage] ConfigMap", func() {
|
||||
framework.ExpectError(err, "created pod %q with non-optional configMap in namespace %q", podName, f.Namespace.Name)
|
||||
})
|
||||
|
||||
//ConfigMap object defined for the pod, If a key is specified which is not present in the ConfigMap,
|
||||
// ConfigMap object defined for the pod, If a key is specified which is not present in the ConfigMap,
|
||||
// the volume setup will error unless it is marked optional, during the pod creation.
|
||||
//Slow (~5 mins)
|
||||
// Slow (~5 mins)
|
||||
ginkgo.It("Should fail non-optional pod creation due to the key in the configMap object does not exist [Slow]", func() {
|
||||
volumeMountPath := "/etc/configmap-volumes"
|
||||
podName := "pod-configmaps-" + string(uuid.NewUUID())
|
||||
@ -754,7 +801,7 @@ func createNonOptionalConfigMapPod(f *framework.Framework, volumeMountPath, podN
|
||||
createContainerName := "createcm-volume-test"
|
||||
createVolumeName := "createcm-volume"
|
||||
|
||||
//creating a pod without configMap object created, by mentioning the configMap volume source's local reference name
|
||||
// creating a pod without configMap object created, by mentioning the configMap volume source's local reference name
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
@ -810,7 +857,7 @@ func createNonOptionalConfigMapPodWithConfig(f *framework.Framework, volumeMount
|
||||
if configMap, err = f.ClientSet.CoreV1().ConfigMaps(f.Namespace.Name).Create(configMap); err != nil {
|
||||
framework.Failf("unable to create test configMap %s: %v", configMap.Name, err)
|
||||
}
|
||||
//creating a pod with configMap object, but with different key which is not present in configMap object.
|
||||
// creating a pod with configMap object, but with different key which is not present in configMap object.
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"path"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
@ -368,9 +369,55 @@ var _ = ginkgo.Describe("[sig-storage] Secrets", func() {
|
||||
gomega.Eventually(pollDeleteLogs, podLogTimeout, framework.Poll).Should(gomega.ContainSubstring("Error reading file /etc/secret-volumes/delete/data-1"))
|
||||
})
|
||||
|
||||
//The secret is in pending during volume creation until the secret objects are available
|
||||
//or until mount the secret volume times out. There is no secret object defined for the pod, so it should return timout exception unless it is marked optional.
|
||||
//Slow (~5 mins)
|
||||
// It should be forbidden to change data for secrets marked as immutable, but
|
||||
// allowed to modify its metadata independently of its state.
|
||||
ginkgo.It("should be immutable if `immutable` field is set [Feature:ImmutableEphemeralVolume]", func() {
|
||||
name := "immutable"
|
||||
secret := secretForTest(f.Namespace.Name, name)
|
||||
|
||||
currentSecret, err := f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(secret)
|
||||
framework.ExpectNoError(err, "Failed to create secret %q in namespace %q", secret.Name, secret.Namespace)
|
||||
|
||||
currentSecret.Data["data-4"] = []byte("value-4\n")
|
||||
currentSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(currentSecret)
|
||||
framework.ExpectNoError(err, "Failed to update secret %q in namespace %q", secret.Name, secret.Namespace)
|
||||
|
||||
// Mark secret as immutable.
|
||||
trueVal := true
|
||||
currentSecret.Immutable = &trueVal
|
||||
currentSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(currentSecret)
|
||||
framework.ExpectNoError(err, "Failed to mark secret %q in namespace %q as immutable", secret.Name, secret.Namespace)
|
||||
|
||||
// Ensure data can't be changed now.
|
||||
currentSecret.Data["data-5"] = []byte("value-5\n")
|
||||
_, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(currentSecret)
|
||||
framework.ExpectEqual(apierrors.IsInvalid(err), true)
|
||||
|
||||
// Ensure secret can't be switched from immutable to mutable.
|
||||
currentSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Get(name, metav1.GetOptions{})
|
||||
framework.ExpectNoError(err, "Failed to get secret %q in namespace %q", secret.Name, secret.Namespace)
|
||||
framework.ExpectEqual(*currentSecret.Immutable, true)
|
||||
|
||||
falseVal := false
|
||||
currentSecret.Immutable = &falseVal
|
||||
_, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(currentSecret)
|
||||
framework.ExpectEqual(apierrors.IsInvalid(err), true)
|
||||
|
||||
// Ensure that metadata can be changed.
|
||||
currentSecret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Get(name, metav1.GetOptions{})
|
||||
framework.ExpectNoError(err, "Failed to get secret %q in namespace %q", secret.Name, secret.Namespace)
|
||||
currentSecret.Labels = map[string]string{"label1": "value1"}
|
||||
_, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Update(currentSecret)
|
||||
framework.ExpectNoError(err, "Failed to update secret %q in namespace %q", secret.Name, secret.Namespace)
|
||||
|
||||
// Ensure that immutable secret can be deleted.
|
||||
err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(name, &metav1.DeleteOptions{})
|
||||
framework.ExpectNoError(err, "Failed to delete secret %q in namespace %q", secret.Name, secret.Namespace)
|
||||
})
|
||||
|
||||
// The secret is in pending during volume creation until the secret objects are available
|
||||
// or until mount the secret volume times out. There is no secret object defined for the pod, so it should return timout exception unless it is marked optional.
|
||||
// Slow (~5 mins)
|
||||
ginkgo.It("Should fail non-optional pod creation due to secret object does not exist [Slow]", func() {
|
||||
volumeMountPath := "/etc/secret-volumes"
|
||||
podName := "pod-secrets-" + string(uuid.NewUUID())
|
||||
@ -378,9 +425,9 @@ var _ = ginkgo.Describe("[sig-storage] Secrets", func() {
|
||||
framework.ExpectError(err, "created pod %q with non-optional secret in namespace %q", podName, f.Namespace.Name)
|
||||
})
|
||||
|
||||
//Secret object defined for the pod, If a key is specified which is not present in the secret,
|
||||
// Secret object defined for the pod, If a key is specified which is not present in the secret,
|
||||
// the volume setup will error unless it is marked optional, during the pod creation.
|
||||
//Slow (~5 mins)
|
||||
// Slow (~5 mins)
|
||||
ginkgo.It("Should fail non-optional pod creation due to the key in the secret object does not exist [Slow]", func() {
|
||||
volumeMountPath := "/etc/secret-volumes"
|
||||
podName := "pod-secrets-" + string(uuid.NewUUID())
|
||||
@ -548,7 +595,7 @@ func createNonOptionalSecretPod(f *framework.Framework, volumeMountPath, podName
|
||||
createContainerName := "creates-volume-test"
|
||||
createVolumeName := "creates-volume"
|
||||
|
||||
//creating a pod without secret object created, by mentioning the secret volume source reference name
|
||||
// creating a pod without secret object created, by mentioning the secret volume source reference name
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
@ -603,7 +650,7 @@ func createNonOptionalSecretPodWithSecret(f *framework.Framework, volumeMountPat
|
||||
if secret, err = f.ClientSet.CoreV1().Secrets(f.Namespace.Name).Create(secret); err != nil {
|
||||
framework.Failf("unable to create test secret %s: %v", secret.Name, err)
|
||||
}
|
||||
//creating a pod with secret object, with the key which is not present in secret object.
|
||||
// creating a pod with secret object, with the key which is not present in secret object.
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: podName,
|
||||
|
Loading…
Reference in New Issue
Block a user