mirror of https://github.com/k3s-io/k3s
Add settings API and admission controller
export functions from pkg/api/validation add settings API add settings to pkg/registry add settings api to pkg/master/master.go add admission control plugin for pod preset add new admission control plugin to kube-apiserver add settings to import_known_versions.go add settings to codegen add validation tests add settings to client generation add protobufs generation for settings api update linted packages add settings to testapi add settings install to clientset add start of e2e add pod preset plugin to config-test.sh Signed-off-by: Jess Frazelle <acidburn@google.com>pull/6/head
parent
2869ff6a0f
commit
fe84363ce2
|
@ -191,7 +191,7 @@ fi
|
|||
ENABLE_RESCHEDULER="${KUBE_ENABLE_RESCHEDULER:-true}"
|
||||
|
||||
# If we included ResourceQuota, we should keep it at the end of the list to prevent incrementing quota usage prematurely.
|
||||
ADMISSION_CONTROL="${KUBE_ADMISSION_CONTROL:-NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds}"
|
||||
ADMISSION_CONTROL="${KUBE_ADMISSION_CONTROL:-NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds,PodPreset}"
|
||||
|
||||
# Optional: if set to true kube-up will automatically check for existing resources and clean them up.
|
||||
KUBE_UP_AUTOMATIC_CLEANUP=${KUBE_UP_AUTOMATIC_CLEANUP:-false}
|
||||
|
|
|
@ -39,6 +39,7 @@ import (
|
|||
_ "k8s.io/kubernetes/plugin/pkg/admission/namespace/lifecycle"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/persistentvolume/label"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/podnodeselector"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/podpreset"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/resourcequota"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/security/podsecuritypolicy"
|
||||
_ "k8s.io/kubernetes/plugin/pkg/admission/securitycontext/scdeny"
|
||||
|
|
|
@ -46,6 +46,7 @@ var (
|
|||
"storage/",
|
||||
"apps/",
|
||||
"policy/",
|
||||
"settings/",
|
||||
}, "group/versions that client-gen will generate clients for. At most one version per group is allowed. Specified in the format \"group1/version1,group2/version2...\". Default to \"api/,extensions/,autoscaling/,batch/,rbac/\"")
|
||||
includedTypesOverrides = flag.StringSlice("included-types-overrides", []string{}, "list of group/version/type for which client should be generated. By default, client is generated for all types which have genclient=true in types.go. This overrides that. For each groupVersion in this list, only the types mentioned here will be included. The default check of genclient=true will be used for other group versions.")
|
||||
basePath = flag.String("input-base", "k8s.io/kubernetes/pkg/apis", "base path to look for the api group. Default to \"k8s.io/kubernetes/pkg/apis\"")
|
||||
|
|
|
@ -85,6 +85,7 @@ func New() *Generator {
|
|||
`k8s.io/kubernetes/federation/apis/federation/v1beta1`,
|
||||
`k8s.io/kubernetes/pkg/apis/certificates/v1beta1`,
|
||||
`k8s.io/kubernetes/pkg/apis/imagepolicy/v1alpha1`,
|
||||
`k8s.io/kubernetes/pkg/apis/settings/v1alpha1`,
|
||||
`k8s.io/kubernetes/pkg/apis/storage/v1beta1`,
|
||||
}, ","),
|
||||
DropEmbeddedFields: "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta",
|
||||
|
|
|
@ -79,6 +79,8 @@ pkg/apis/imagepolicy/install
|
|||
pkg/apis/policy/install
|
||||
pkg/apis/rbac/install
|
||||
pkg/apis/rbac/v1alpha1
|
||||
pkg/apis/settings/install
|
||||
pkg/apis/settings/validation
|
||||
pkg/apis/storage/install
|
||||
pkg/apis/storage/validation
|
||||
pkg/bootstrap/api
|
||||
|
@ -103,6 +105,8 @@ pkg/client/informers/informers_generated/externalversions/policy/v1beta1
|
|||
pkg/client/informers/informers_generated/externalversions/rbac
|
||||
pkg/client/informers/informers_generated/externalversions/rbac/v1alpha1
|
||||
pkg/client/informers/informers_generated/externalversions/rbac/v1beta1
|
||||
pkg/client/informers/informers_generated/externalversions/settings
|
||||
pkg/client/informers/informers_generated/externalversions/settings/v1alpha1
|
||||
pkg/client/informers/informers_generated/externalversions/storage
|
||||
pkg/client/informers/informers_generated/externalversions/storage/v1beta1
|
||||
pkg/client/informers/informers_generated/internalversion
|
||||
|
@ -122,6 +126,8 @@ pkg/client/informers/informers_generated/internalversion/policy
|
|||
pkg/client/informers/informers_generated/internalversion/policy/internalversion
|
||||
pkg/client/informers/informers_generated/internalversion/rbac
|
||||
pkg/client/informers/informers_generated/internalversion/rbac/internalversion
|
||||
pkg/client/informers/informers_generated/internalversion/settings
|
||||
pkg/client/informers/informers_generated/internalversion/settings/internalversion
|
||||
pkg/client/informers/informers_generated/internalversion/storage
|
||||
pkg/client/informers/informers_generated/internalversion/storage/internalversion
|
||||
pkg/client/listers/apps/internalversion
|
||||
|
@ -152,6 +158,8 @@ pkg/client/listers/policy/v1beta1
|
|||
pkg/client/listers/rbac/internalversion
|
||||
pkg/client/listers/rbac/v1alpha1
|
||||
pkg/client/listers/rbac/v1beta1
|
||||
pkg/client/listers/settings/internalversion
|
||||
pkg/client/listers/settings/v1alpha1
|
||||
pkg/client/listers/storage/internalversion
|
||||
pkg/client/listers/storage/v1beta1
|
||||
pkg/client/metrics
|
||||
|
|
|
@ -29,7 +29,7 @@ KUBE_OUTPUT_BINPATH="${KUBE_OUTPUT}/bin"
|
|||
# compression for build container
|
||||
KUBE_RSYNC_COMPRESS="${KUBE_RSYNC_COMPRESS:-0}"
|
||||
|
||||
# Set no_proxy for localhost if behind a proxy, otherwise,
|
||||
# Set no_proxy for localhost if behind a proxy, otherwise,
|
||||
# the connections to localhost in scripts will time out
|
||||
export no_proxy=127.0.0.1,localhost
|
||||
|
||||
|
@ -68,7 +68,8 @@ imagepolicy.k8s.io/v1alpha1 \
|
|||
policy/v1beta1 \
|
||||
rbac.authorization.k8s.io/v1beta1 \
|
||||
rbac.authorization.k8s.io/v1alpha1 \
|
||||
storage.k8s.io/v1beta1\
|
||||
settings.k8s.io/v1alpha1 \
|
||||
storage.k8s.io/v1beta1 \
|
||||
}"
|
||||
|
||||
# not all group versions are exposed by the server. This list contains those
|
||||
|
|
|
@ -43,7 +43,7 @@ GV_DIRS=()
|
|||
for gv in "${GROUP_VERSIONS[@]}"; do
|
||||
# add items, but strip off any leading apis/ you find to match command expectations
|
||||
api_dir=$(kube::util::group-version-to-pkg-path "${gv}")
|
||||
nopkg_dir=${api_dir#pkg/}
|
||||
nopkg_dir=${api_dir#pkg/}
|
||||
pkg_dir=${nopkg_dir#apis/}
|
||||
|
||||
# skip groups that aren't being served, clients for these don't matter
|
||||
|
|
|
@ -45,6 +45,7 @@ import (
|
|||
"k8s.io/kubernetes/pkg/apis/imagepolicy"
|
||||
"k8s.io/kubernetes/pkg/apis/policy"
|
||||
"k8s.io/kubernetes/pkg/apis/rbac"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
"k8s.io/kubernetes/pkg/apis/storage"
|
||||
|
||||
_ "k8s.io/kubernetes/federation/apis/federation/install"
|
||||
|
@ -60,6 +61,7 @@ import (
|
|||
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/policy/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/settings/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/storage/install"
|
||||
)
|
||||
|
||||
|
@ -75,6 +77,7 @@ var (
|
|||
Federation TestGroup
|
||||
Rbac TestGroup
|
||||
Certificates TestGroup
|
||||
Settings TestGroup
|
||||
Storage TestGroup
|
||||
ImagePolicy TestGroup
|
||||
|
||||
|
@ -230,6 +233,15 @@ func init() {
|
|||
externalTypes: api.Scheme.KnownTypes(externalGroupVersion),
|
||||
}
|
||||
}
|
||||
if _, ok := Groups[settings.GroupName]; !ok {
|
||||
externalGroupVersion := schema.GroupVersion{Group: settings.GroupName, Version: api.Registry.GroupOrDie(settings.GroupName).GroupVersion.Version}
|
||||
Groups[settings.GroupName] = TestGroup{
|
||||
externalGroupVersion: externalGroupVersion,
|
||||
internalGroupVersion: settings.SchemeGroupVersion,
|
||||
internalTypes: api.Scheme.KnownTypes(settings.SchemeGroupVersion),
|
||||
externalTypes: api.Scheme.KnownTypes(externalGroupVersion),
|
||||
}
|
||||
}
|
||||
if _, ok := Groups[storage.GroupName]; !ok {
|
||||
externalGroupVersion := schema.GroupVersion{Group: storage.GroupName, Version: api.Registry.GroupOrDie(storage.GroupName).GroupVersion.Version}
|
||||
Groups[storage.GroupName] = TestGroup{
|
||||
|
@ -276,6 +288,7 @@ func init() {
|
|||
Extensions = Groups[extensions.GroupName]
|
||||
Federation = Groups[federation.GroupName]
|
||||
Rbac = Groups[rbac.GroupName]
|
||||
Settings = Groups[settings.GroupName]
|
||||
Storage = Groups[storage.GroupName]
|
||||
ImagePolicy = Groups[imagepolicy.GroupName]
|
||||
Authorization = Groups[authorization.GroupName]
|
||||
|
|
|
@ -319,7 +319,7 @@ func ValidateNoNewFinalizers(newFinalizers []string, oldFinalizers []string, fld
|
|||
return genericvalidation.ValidateNoNewFinalizers(newFinalizers, oldFinalizers, fldPath)
|
||||
}
|
||||
|
||||
func validateVolumes(volumes []api.Volume, fldPath *field.Path) (sets.String, field.ErrorList) {
|
||||
func ValidateVolumes(volumes []api.Volume, fldPath *field.Path) (sets.String, field.ErrorList) {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allNames := sets.String{}
|
||||
|
@ -1330,7 +1330,7 @@ func validateContainerPorts(ports []api.ContainerPort, fldPath *field.Path) fiel
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateEnv(vars []api.EnvVar, fldPath *field.Path) field.ErrorList {
|
||||
func ValidateEnv(vars []api.EnvVar, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
for i, ev := range vars {
|
||||
|
@ -1422,7 +1422,7 @@ func validateContainerResourceFieldSelector(fs *api.ResourceFieldSelector, expre
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateEnvFrom(vars []api.EnvFromSource, fldPath *field.Path) field.ErrorList {
|
||||
func ValidateEnvFrom(vars []api.EnvFromSource, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
for i, ev := range vars {
|
||||
idxPath := fldPath.Index(i)
|
||||
|
@ -1523,7 +1523,7 @@ func validateSecretKeySelector(s *api.SecretKeySelector, fldPath *field.Path) fi
|
|||
return allErrs
|
||||
}
|
||||
|
||||
func validateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath *field.Path) field.ErrorList {
|
||||
func ValidateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
mountpoints := sets.NewString()
|
||||
|
||||
|
@ -1778,8 +1778,8 @@ func validateContainers(containers []api.Container, volumes sets.String, fldPath
|
|||
|
||||
allErrs = append(allErrs, validateProbe(ctr.ReadinessProbe, idxPath.Child("readinessProbe"))...)
|
||||
allErrs = append(allErrs, validateContainerPorts(ctr.Ports, idxPath.Child("ports"))...)
|
||||
allErrs = append(allErrs, validateEnv(ctr.Env, idxPath.Child("env"))...)
|
||||
allErrs = append(allErrs, validateVolumeMounts(ctr.VolumeMounts, volumes, idxPath.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, ValidateEnv(ctr.Env, idxPath.Child("env"))...)
|
||||
allErrs = append(allErrs, ValidateVolumeMounts(ctr.VolumeMounts, volumes, idxPath.Child("volumeMounts"))...)
|
||||
allErrs = append(allErrs, validatePullPolicy(ctr.ImagePullPolicy, idxPath.Child("imagePullPolicy"))...)
|
||||
allErrs = append(allErrs, ValidateResourceRequirements(&ctr.Resources, idxPath.Child("resources"))...)
|
||||
allErrs = append(allErrs, ValidateSecurityContext(ctr.SecurityContext, idxPath.Child("securityContext"))...)
|
||||
|
@ -1988,7 +1988,7 @@ func ValidatePod(pod *api.Pod) field.ErrorList {
|
|||
func ValidatePodSpec(spec *api.PodSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allVolumes, vErrs := validateVolumes(spec.Volumes, fldPath.Child("volumes"))
|
||||
allVolumes, vErrs := ValidateVolumes(spec.Volumes, fldPath.Child("volumes"))
|
||||
allErrs = append(allErrs, vErrs...)
|
||||
allErrs = append(allErrs, validateContainers(spec.Containers, allVolumes, fldPath.Child("containers"))...)
|
||||
allErrs = append(allErrs, validateInitContainers(spec.InitContainers, spec.Containers, allVolumes, fldPath.Child("initContainers"))...)
|
||||
|
|
|
@ -1931,7 +1931,7 @@ func TestValidateVolumes(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
names, errs := validateVolumes([]api.Volume{tc.vol}, field.NewPath("field"))
|
||||
names, errs := ValidateVolumes([]api.Volume{tc.vol}, field.NewPath("field"))
|
||||
if len(errs) > 0 && tc.errtype == "" {
|
||||
t.Errorf("[%d: %q] unexpected error(s): %v", i, tc.name, errs)
|
||||
} else if len(errs) > 1 {
|
||||
|
@ -1957,7 +1957,7 @@ func TestValidateVolumes(t *testing.T) {
|
|||
{Name: "abc", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "abc", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
}
|
||||
_, errs := validateVolumes(dupsCase, field.NewPath("field"))
|
||||
_, errs := ValidateVolumes(dupsCase, field.NewPath("field"))
|
||||
if len(errs) == 0 {
|
||||
t.Errorf("expected error")
|
||||
} else if len(errs) != 1 {
|
||||
|
@ -2121,7 +2121,7 @@ func TestValidateEnv(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
if errs := validateEnv(successCase, field.NewPath("field")); len(errs) != 0 {
|
||||
if errs := ValidateEnv(successCase, field.NewPath("field")); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
|
||||
|
@ -2303,7 +2303,7 @@ func TestValidateEnv(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range errorCases {
|
||||
if errs := validateEnv(tc.envs, field.NewPath("field")); len(errs) == 0 {
|
||||
if errs := ValidateEnv(tc.envs, field.NewPath("field")); len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", tc.name)
|
||||
} else {
|
||||
for i := range errs {
|
||||
|
@ -2341,7 +2341,7 @@ func TestValidateEnvFrom(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}
|
||||
if errs := validateEnvFrom(successCase, field.NewPath("field")); len(errs) != 0 {
|
||||
if errs := ValidateEnvFrom(successCase, field.NewPath("field")); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
|
||||
|
@ -2413,7 +2413,7 @@ func TestValidateEnvFrom(t *testing.T) {
|
|||
},
|
||||
}
|
||||
for _, tc := range errorCases {
|
||||
if errs := validateEnvFrom(tc.envs, field.NewPath("field")); len(errs) == 0 {
|
||||
if errs := ValidateEnvFrom(tc.envs, field.NewPath("field")); len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", tc.name)
|
||||
} else {
|
||||
for i := range errs {
|
||||
|
@ -2439,7 +2439,7 @@ func TestValidateVolumeMounts(t *testing.T) {
|
|||
{Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
|
||||
{Name: "abc", MountPath: "c:/foo/bar"},
|
||||
}
|
||||
if errs := validateVolumeMounts(successCase, volumes, field.NewPath("field")); len(errs) != 0 {
|
||||
if errs := ValidateVolumeMounts(successCase, volumes, field.NewPath("field")); len(errs) != 0 {
|
||||
t.Errorf("expected success: %v", errs)
|
||||
}
|
||||
|
||||
|
@ -2454,7 +2454,7 @@ func TestValidateVolumeMounts(t *testing.T) {
|
|||
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
|
||||
}
|
||||
for k, v := range errorCases {
|
||||
if errs := validateVolumeMounts(v, volumes, field.NewPath("field")); len(errs) == 0 {
|
||||
if errs := ValidateVolumeMounts(v, volumes, field.NewPath("field")); len(errs) == 0 {
|
||||
t.Errorf("expected failure for %s", k)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// +groupName=settings.k8s.io
|
||||
package settings // import "k8s.io/kubernetes/pkg/apis/settings"
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package install installs the settings API group, making it available as
|
||||
// an option to all of the API encoding/decoding machinery.
|
||||
package install
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apimachinery/announced"
|
||||
"k8s.io/apimachinery/pkg/apimachinery/registered"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
"k8s.io/kubernetes/pkg/apis/settings/v1alpha1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Install(api.GroupFactoryRegistry, api.Registry, api.Scheme)
|
||||
}
|
||||
|
||||
// Install registers the API group and adds types to a scheme
|
||||
func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *registered.APIRegistrationManager, scheme *runtime.Scheme) {
|
||||
if err := announced.NewGroupMetaFactory(
|
||||
&announced.GroupMetaFactoryArgs{
|
||||
GroupName: settings.GroupName,
|
||||
VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version},
|
||||
ImportPrefix: "k8s.io/kubernetes/pkg/apis/settings",
|
||||
AddInternalObjectsToScheme: settings.AddToScheme,
|
||||
},
|
||||
announced.VersionToSchemeFunc{
|
||||
v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme,
|
||||
},
|
||||
).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package settings
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "settings.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
// Adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&PodPreset{},
|
||||
&PodPresetList{},
|
||||
)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package settings
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// +genclient=true
|
||||
|
||||
// PodPreset is a policy resource that defines additional runtime
|
||||
// requirements for a Pod.
|
||||
type PodPreset struct {
|
||||
metav1.TypeMeta
|
||||
// +optional
|
||||
metav1.ObjectMeta
|
||||
|
||||
// +optional
|
||||
Spec PodPresetSpec
|
||||
}
|
||||
|
||||
// PodPresetSpec is a description of a pod injection policy.
|
||||
type PodPresetSpec struct {
|
||||
// Selector is a label query over a set of resources, in this case pods.
|
||||
// Required.
|
||||
Selector metav1.LabelSelector
|
||||
// Env defines the collection of EnvVar to inject into containers.
|
||||
// +optional
|
||||
Env []api.EnvVar
|
||||
// EnvFrom defines the collection of EnvFromSource to inject into containers.
|
||||
// +optional
|
||||
EnvFrom []api.EnvFromSource
|
||||
// Volumes defines the collection of Volume to inject into the pod.
|
||||
// +optional
|
||||
Volumes []api.Volume
|
||||
// VolumeMounts defines the collection of VolumeMount to inject into containers.
|
||||
// +optional
|
||||
VolumeMounts []api.VolumeMount
|
||||
}
|
||||
|
||||
// PodPresetList is a list of PodPreset objects.
|
||||
type PodPresetList struct {
|
||||
metav1.TypeMeta
|
||||
// +optional
|
||||
metav1.ListMeta
|
||||
|
||||
Items []PodPreset
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/settings
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
// +groupName=settings.k8s.io
|
||||
package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/settings/v1alpha1"
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name use in this package
|
||||
const GroupName = "settings.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&PodPreset{},
|
||||
&PodPresetList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
)
|
||||
|
||||
// +genclient=true
|
||||
|
||||
// PodPreset is a policy resource that defines additional runtime
|
||||
// requirements for a Pod.
|
||||
type PodPreset struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// +optional
|
||||
Spec PodPresetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
|
||||
}
|
||||
|
||||
// PodPresetSpec is a description of a pod injection policy.
|
||||
type PodPresetSpec struct {
|
||||
// Selector is a label query over a set of resources, in this case pods.
|
||||
// Required.
|
||||
Selector metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,1,opt,name=selector"`
|
||||
|
||||
// Env defines the collection of EnvVar to inject into containers.
|
||||
// +optional
|
||||
Env []v1.EnvVar `json:"env,omitempty" protobuf:"bytes,2,rep,name=env"`
|
||||
// EnvFrom defines the collection of EnvFromSource to inject into containers.
|
||||
// +optional
|
||||
EnvFrom []v1.EnvFromSource `json:"envFrom,omitempty" protobuf:"bytes,3,rep,name=envFrom"`
|
||||
// Volumes defines the collection of Volume to inject into the pod.
|
||||
// +optional
|
||||
Volumes []v1.Volume `json:"volumes,omitempty" protobuf:"bytes,4,rep,name=volumes"`
|
||||
// VolumeMounts defines the collection of VolumeMount to inject into containers.
|
||||
// +optional
|
||||
VolumeMounts []v1.VolumeMount `json:"volumeMounts,omitempty" protobuf:"bytes,5,rep,name=volumeMounts"`
|
||||
}
|
||||
|
||||
// PodPresetList is a list of PodPreset objects.
|
||||
type PodPresetList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard list metadata.
|
||||
// More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Items is a list of schema objects.
|
||||
Items []PodPreset `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
unversionedvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
apivalidation "k8s.io/kubernetes/pkg/api/validation"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
)
|
||||
|
||||
// ValidatePodPresetName can be used to check whether the given PodPreset name is valid.
|
||||
// Prefix indicates this name will be used as part of generation, in which case
|
||||
// trailing dashes are allowed.
|
||||
func ValidatePodPresetName(name string, prefix bool) []string {
|
||||
// TODO: Validate that there's name for the suffix inserted by the pods.
|
||||
// Currently this is just "-index". In the future we may allow a user
|
||||
// specified list of suffixes and we need to validate the longest one.
|
||||
return apivalidation.NameIsDNSSubdomain(name, prefix)
|
||||
}
|
||||
|
||||
// ValidatePodPresetSpec tests if required fields in the PodPreset spec are set.
|
||||
func ValidatePodPresetSpec(spec *settings.PodPresetSpec, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&spec.Selector, fldPath.Child("selector"))...)
|
||||
|
||||
if spec.Env == nil && spec.EnvFrom == nil && spec.VolumeMounts == nil && spec.Volumes == nil {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("volumes", "env", "envFrom", "volumeMounts"), "must specify at least one"))
|
||||
}
|
||||
|
||||
volumes, vErrs := apivalidation.ValidateVolumes(spec.Volumes, fldPath.Child("volumes"))
|
||||
allErrs = append(allErrs, vErrs...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateEnv(spec.Env, fldPath.Child("env"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateEnvFrom(spec.EnvFrom, fldPath.Child("envFrom"))...)
|
||||
allErrs = append(allErrs, apivalidation.ValidateVolumeMounts(spec.VolumeMounts, volumes, fldPath.Child("volumeMounts"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidatePodPreset validates a PodPreset.
|
||||
func ValidatePodPreset(pip *settings.PodPreset) field.ErrorList {
|
||||
allErrs := apivalidation.ValidateObjectMeta(&pip.ObjectMeta, true, ValidatePodPresetName, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, ValidatePodPresetSpec(&pip.Spec, field.NewPath("spec"))...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// ValidatePodPresetUpdate tests if required fields in the PodPreset are set.
|
||||
func ValidatePodPresetUpdate(pip, oldPip *settings.PodPreset) field.ErrorList {
|
||||
allErrs := apivalidation.ValidateObjectMetaUpdate(&pip.ObjectMeta, &oldPip.ObjectMeta, field.NewPath("metadata"))
|
||||
allErrs = append(allErrs, ValidatePodPresetSpec(&pip.Spec, field.NewPath("spec"))...)
|
||||
|
||||
return allErrs
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package validation
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
)
|
||||
|
||||
func TestValidateEmptyPodPreset(t *testing.T) {
|
||||
emptyPodPreset := &settings.PodPreset{
|
||||
Spec: settings.PodPresetSpec{},
|
||||
}
|
||||
|
||||
errList := ValidatePodPreset(emptyPodPreset)
|
||||
if errList == nil {
|
||||
t.Fatal("empty pod preset should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateEmptyPodPresetItems(t *testing.T) {
|
||||
emptyPodPreset := &settings.PodPreset{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "sample",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: v1.LabelSelector{
|
||||
MatchExpressions: []v1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: v1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errList := ValidatePodPreset(emptyPodPreset)
|
||||
if !strings.Contains(errList.ToAggregate().Error(), "must specify at least one") {
|
||||
t.Fatal("empty pod preset with label selector should return an error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePodPresets(t *testing.T) {
|
||||
p := &settings.PodPreset{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "sample",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: v1.LabelSelector{
|
||||
MatchExpressions: []v1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: v1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errList := ValidatePodPreset(p)
|
||||
if errList != nil {
|
||||
if errList.ToAggregate() != nil {
|
||||
t.Fatalf("errors: %#v", errList.ToAggregate().Error())
|
||||
}
|
||||
}
|
||||
|
||||
p = &settings.PodPreset{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "sample",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: v1.LabelSelector{
|
||||
MatchExpressions: []v1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: v1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
VolumeMounts: []api.VolumeMount{
|
||||
{Name: "vol", MountPath: "/foo"},
|
||||
},
|
||||
EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errList = ValidatePodPreset(p)
|
||||
if errList != nil {
|
||||
if errList.ToAggregate() != nil {
|
||||
t.Fatalf("errors: %#v", errList.ToAggregate().Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePodPresetsiVolumeMountError(t *testing.T) {
|
||||
p := &settings.PodPreset{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "sample",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: v1.LabelSelector{
|
||||
MatchExpressions: []v1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: v1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
|
||||
VolumeMounts: []api.VolumeMount{
|
||||
{Name: "dne", MountPath: "/foo"},
|
||||
},
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
errList := ValidatePodPreset(p)
|
||||
if !strings.Contains(errList.ToAggregate().Error(), "spec.volumeMounts[0].name: Not found") {
|
||||
t.Fatal("should have returned error for volume that does not exist")
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ import (
|
|||
_ "k8s.io/kubernetes/pkg/apis/extensions/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/policy/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/settings/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/storage/install"
|
||||
)
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
_ "k8s.io/kubernetes/pkg/apis/imagepolicy/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/policy/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/rbac/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/settings/install"
|
||||
_ "k8s.io/kubernetes/pkg/apis/storage/install"
|
||||
)
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@ import (
|
|||
policyapiv1beta1 "k8s.io/kubernetes/pkg/apis/policy/v1beta1"
|
||||
rbacapi "k8s.io/kubernetes/pkg/apis/rbac/v1alpha1"
|
||||
rbacv1beta1 "k8s.io/kubernetes/pkg/apis/rbac/v1beta1"
|
||||
settingsapi "k8s.io/kubernetes/pkg/apis/settings/v1alpha1"
|
||||
storageapiv1beta1 "k8s.io/kubernetes/pkg/apis/storage/v1beta1"
|
||||
corev1client "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
|
||||
coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion"
|
||||
|
@ -68,6 +69,7 @@ import (
|
|||
extensionsrest "k8s.io/kubernetes/pkg/registry/extensions/rest"
|
||||
policyrest "k8s.io/kubernetes/pkg/registry/policy/rest"
|
||||
rbacrest "k8s.io/kubernetes/pkg/registry/rbac/rest"
|
||||
settingsrest "k8s.io/kubernetes/pkg/registry/settings/rest"
|
||||
storagerest "k8s.io/kubernetes/pkg/registry/storage/rest"
|
||||
)
|
||||
|
||||
|
@ -248,6 +250,7 @@ func (c completedConfig) New() (*Master, error) {
|
|||
policyrest.RESTStorageProvider{},
|
||||
rbacrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorizer},
|
||||
storagerest.RESTStorageProvider{},
|
||||
settingsrest.RESTStorageProvider{},
|
||||
}
|
||||
m.InstallAPIs(c.Config.APIResourceConfigSource, c.Config.GenericConfig.RESTOptionsGetter, restStorageProviders...)
|
||||
|
||||
|
@ -371,6 +374,7 @@ func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig {
|
|||
policyapiv1beta1.SchemeGroupVersion,
|
||||
rbacv1beta1.SchemeGroupVersion,
|
||||
rbacapi.SchemeGroupVersion,
|
||||
settingsapi.SchemeGroupVersion,
|
||||
storageapiv1beta1.SchemeGroupVersion,
|
||||
certificatesapiv1beta1.SchemeGroupVersion,
|
||||
authorizationapiv1.SchemeGroupVersion,
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package podpreset // import "k8s.io/kubernetes/pkg/registry/settings/podpreset"
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package podpreset
|
||||
|
||||
import (
|
||||
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
)
|
||||
|
||||
// Registry is an interface for things that know how to store PodPresets.
|
||||
type Registry interface {
|
||||
ListPodPresets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*settings.PodPresetList, error)
|
||||
CreatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error
|
||||
UpdatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error
|
||||
GetPodPreset(ctx genericapirequest.Context, ppID string, options *metav1.GetOptions) (*settings.PodPreset, error)
|
||||
DeletePodPreset(ctx genericapirequest.Context, ppID string) error
|
||||
WatchPodPresets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error)
|
||||
}
|
||||
|
||||
// storage puts strong typing around storage calls
|
||||
type storage struct {
|
||||
rest.StandardStorage
|
||||
}
|
||||
|
||||
// NewRegistry returns a new Registry interface for the given Storage. Any mismatched
|
||||
// types will panic.
|
||||
func NewRegistry(s rest.StandardStorage) Registry {
|
||||
return &storage{s}
|
||||
}
|
||||
|
||||
func (s *storage) ListPodPresets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (*settings.PodPresetList, error) {
|
||||
obj, err := s.List(ctx, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return obj.(*settings.PodPresetList), nil
|
||||
}
|
||||
|
||||
func (s *storage) CreatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error {
|
||||
_, err := s.Create(ctx, pp)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *storage) UpdatePodPreset(ctx genericapirequest.Context, pp *settings.PodPreset) error {
|
||||
_, _, err := s.Update(ctx, pp.Name, rest.DefaultUpdatedObjectInfo(pp, api.Scheme))
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *storage) WatchPodPresets(ctx genericapirequest.Context, options *metainternalversion.ListOptions) (watch.Interface, error) {
|
||||
return s.Watch(ctx, options)
|
||||
}
|
||||
|
||||
func (s *storage) GetPodPreset(ctx genericapirequest.Context, name string, options *metav1.GetOptions) (*settings.PodPreset, error) {
|
||||
obj, err := s.Get(ctx, name, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*settings.PodPreset), nil
|
||||
}
|
||||
|
||||
func (s *storage) DeletePodPreset(ctx genericapirequest.Context, name string) error {
|
||||
_, _, err := s.Delete(ctx, name, nil)
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
settingsapi "k8s.io/kubernetes/pkg/apis/settings"
|
||||
"k8s.io/kubernetes/pkg/registry/cachesize"
|
||||
"k8s.io/kubernetes/pkg/registry/settings/podpreset"
|
||||
)
|
||||
|
||||
// rest implements a RESTStorage for replication controllers against etcd
|
||||
type REST struct {
|
||||
*genericregistry.Store
|
||||
}
|
||||
|
||||
// NewREST returns a RESTStorage object that will work against replication controllers.
|
||||
func NewREST(optsGetter generic.RESTOptionsGetter) *REST {
|
||||
store := &genericregistry.Store{
|
||||
Copier: api.Scheme,
|
||||
NewFunc: func() runtime.Object { return &settingsapi.PodPreset{} },
|
||||
NewListFunc: func() runtime.Object { return &settingsapi.PodPresetList{} },
|
||||
ObjectNameFunc: func(obj runtime.Object) (string, error) {
|
||||
return obj.(*settingsapi.PodPreset).GetName(), nil
|
||||
},
|
||||
PredicateFunc: podpreset.Matcher,
|
||||
QualifiedResource: settingsapi.Resource("podpresets"),
|
||||
WatchCacheSize: cachesize.GetWatchCacheSizeByResource("podpresets"),
|
||||
|
||||
CreateStrategy: podpreset.Strategy,
|
||||
UpdateStrategy: podpreset.Strategy,
|
||||
DeleteStrategy: podpreset.Strategy,
|
||||
}
|
||||
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: podpreset.GetAttrs}
|
||||
if err := store.CompleteWithOptions(options); err != nil {
|
||||
panic(err) // TODO: Propagate error up
|
||||
}
|
||||
|
||||
return &REST{store}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package podpreset
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
apistorage "k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
"k8s.io/kubernetes/pkg/apis/settings/validation"
|
||||
)
|
||||
|
||||
// podPresetStrategy implements verification logic for Pod Injection Policies.
|
||||
type podPresetStrategy struct {
|
||||
runtime.ObjectTyper
|
||||
names.NameGenerator
|
||||
}
|
||||
|
||||
// Strategy is the default logic that applies when creating and updating Pod Injection Policy objects.
|
||||
var Strategy = podPresetStrategy{api.Scheme, names.SimpleNameGenerator}
|
||||
|
||||
// NamespaceScoped returns true because all Pod Injection Policies need to be within a namespace.
|
||||
func (podPresetStrategy) NamespaceScoped() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PrepareForCreate clears the status of a Pod Injection Policy before creation.
|
||||
func (podPresetStrategy) PrepareForCreate(ctx genericapirequest.Context, obj runtime.Object) {
|
||||
pip := obj.(*settings.PodPreset)
|
||||
pip.Generation = 1
|
||||
}
|
||||
|
||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||
func (podPresetStrategy) PrepareForUpdate(ctx genericapirequest.Context, obj, old runtime.Object) {
|
||||
newPodPreset := obj.(*settings.PodPreset)
|
||||
oldPodPreset := old.(*settings.PodPreset)
|
||||
|
||||
// Update is not allowed
|
||||
newPodPreset.Spec = oldPodPreset.Spec
|
||||
}
|
||||
|
||||
// Validate validates a new PodPreset.
|
||||
func (podPresetStrategy) Validate(ctx genericapirequest.Context, obj runtime.Object) field.ErrorList {
|
||||
pip := obj.(*settings.PodPreset)
|
||||
return validation.ValidatePodPreset(pip)
|
||||
}
|
||||
|
||||
// Canonicalize normalizes the object after validation.
|
||||
func (podPresetStrategy) Canonicalize(obj runtime.Object) {}
|
||||
|
||||
// AllowCreateOnUpdate is false for PodPreset; this means POST is needed to create one.
|
||||
func (podPresetStrategy) AllowCreateOnUpdate() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// ValidateUpdate is the default update validation for an end user.
|
||||
func (podPresetStrategy) ValidateUpdate(ctx genericapirequest.Context, obj, old runtime.Object) field.ErrorList {
|
||||
validationErrorList := validation.ValidatePodPreset(obj.(*settings.PodPreset))
|
||||
updateErrorList := validation.ValidatePodPresetUpdate(obj.(*settings.PodPreset), old.(*settings.PodPreset))
|
||||
return append(validationErrorList, updateErrorList...)
|
||||
}
|
||||
|
||||
// AllowUnconditionalUpdate is the default update policy for Pod Injection Policy objects.
|
||||
func (podPresetStrategy) AllowUnconditionalUpdate() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SelectableFields returns a field set that represents the object.
|
||||
func SelectableFields(pip *settings.PodPreset) fields.Set {
|
||||
return generic.ObjectMetaFieldsSet(&pip.ObjectMeta, true)
|
||||
}
|
||||
|
||||
// GetAttrs returns labels and fields of a given object for filtering purposes.
|
||||
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
||||
pip, ok := obj.(*settings.PodPreset)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("given object is not a PodPreset.")
|
||||
}
|
||||
return labels.Set(pip.ObjectMeta.Labels), SelectableFields(pip), nil
|
||||
}
|
||||
|
||||
// Matcher is the filter used by the generic etcd backend to watch events
|
||||
// from etcd to clients of the apiserver only interested in specific labels/fields.
|
||||
func Matcher(label labels.Selector, field fields.Selector) apistorage.SelectionPredicate {
|
||||
return apistorage.SelectionPredicate{
|
||||
Label: label,
|
||||
Field: field,
|
||||
GetAttrs: GetAttrs,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package rest
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
settingsapiv1alpha1 "k8s.io/kubernetes/pkg/apis/settings/v1alpha1"
|
||||
podpresetstore "k8s.io/kubernetes/pkg/registry/settings/podpreset/storage"
|
||||
)
|
||||
|
||||
type RESTStorageProvider struct{}
|
||||
|
||||
func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) {
|
||||
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(settings.GroupName, api.Registry, api.Scheme, api.ParameterCodec, api.Codecs)
|
||||
|
||||
if apiResourceConfigSource.AnyResourcesForVersionEnabled(settingsapiv1alpha1.SchemeGroupVersion) {
|
||||
apiGroupInfo.VersionedResourcesStorageMap[settingsapiv1alpha1.SchemeGroupVersion.Version] = p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter)
|
||||
apiGroupInfo.GroupMeta.GroupVersion = settingsapiv1alpha1.SchemeGroupVersion
|
||||
}
|
||||
|
||||
return apiGroupInfo, true
|
||||
}
|
||||
|
||||
func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage {
|
||||
version := settingsapiv1alpha1.SchemeGroupVersion
|
||||
|
||||
storage := map[string]rest.Storage{}
|
||||
if apiResourceConfigSource.ResourceEnabled(version.WithResource("podpresets")) {
|
||||
podPresetStorage := podpresetstore.NewREST(restOptionsGetter)
|
||||
storage["podpresets"] = podPresetStorage
|
||||
}
|
||||
return storage
|
||||
}
|
||||
|
||||
func (p RESTStorageProvider) GroupName() string {
|
||||
return settings.GroupName
|
||||
}
|
|
@ -0,0 +1,320 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admission
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
settingslisters "k8s.io/kubernetes/pkg/client/listers/settings/internalversion"
|
||||
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
|
||||
)
|
||||
|
||||
const (
|
||||
annotationPrefix = "podpreset.admission.kubernetes.io"
|
||||
pluginName = "PodPreset"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin(pluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// podPresetPlugin is an implementation of admission.Interface.
|
||||
type podPresetPlugin struct {
|
||||
*admission.Handler
|
||||
client internalclientset.Interface
|
||||
|
||||
lister settingslisters.PodPresetLister
|
||||
}
|
||||
|
||||
var _ = kubeapiserveradmission.WantsInformerFactory(&podPresetPlugin{})
|
||||
var _ = kubeapiserveradmission.WantsInternalClientSet(&podPresetPlugin{})
|
||||
|
||||
// NewPlugin creates a new pod preset admission plugin.
|
||||
func NewPlugin() *podPresetPlugin {
|
||||
return &podPresetPlugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin *podPresetPlugin) Validate() error {
|
||||
if plugin.client == nil {
|
||||
return fmt.Errorf("%s requires a client", pluginName)
|
||||
}
|
||||
if plugin.lister == nil {
|
||||
return fmt.Errorf("%s requires a lister", pluginName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *podPresetPlugin) SetInternalClientSet(client internalclientset.Interface) {
|
||||
a.client = client
|
||||
}
|
||||
|
||||
func (a *podPresetPlugin) SetInformerFactory(f informers.SharedInformerFactory) {
|
||||
podPresetInformer := f.Settings().InternalVersion().PodPresets()
|
||||
a.lister = podPresetInformer.Lister()
|
||||
a.SetReadyFunc(podPresetInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// Admit injects a pod with the specific fields for each pod preset it matches.
|
||||
func (c *podPresetPlugin) Admit(a admission.Attributes) error {
|
||||
// Ignore all calls to subresources or resources other than pods.
|
||||
// Ignore all operations other than CREATE.
|
||||
if len(a.GetSubresource()) != 0 || a.GetResource().GroupResource() != api.Resource("pods") || a.GetOperation() != admission.Create {
|
||||
return nil
|
||||
}
|
||||
|
||||
pod, ok := a.GetObject().(*api.Pod)
|
||||
if !ok {
|
||||
return errors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted")
|
||||
}
|
||||
list, err := c.lister.PodPresets(pod.GetNamespace()).List(labels.Everything())
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing pod presets failed: %v", err)
|
||||
}
|
||||
|
||||
// get the pod presets and iterate over them
|
||||
for _, pip := range list {
|
||||
selector, err := metav1.LabelSelectorAsSelector(&pip.Spec.Selector)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing pod presets for namespace:%s failed: %v", pod.GetNamespace(), err)
|
||||
}
|
||||
|
||||
// check if the pod labels match the selector
|
||||
if !selector.Matches(labels.Set(pod.Labels)) {
|
||||
continue
|
||||
}
|
||||
|
||||
glog.V(4).Infof("PodPreset %s matches pod %s labels", pip.GetName(), pod.GetName())
|
||||
|
||||
// merge in policy for Env
|
||||
if pip.Spec.Env != nil {
|
||||
for i, ctr := range pod.Spec.Containers {
|
||||
r, err := mergeEnv(pip, ctr.Env)
|
||||
if err != nil {
|
||||
// add event to pod
|
||||
c.addEvent(pod, pip, err.Error())
|
||||
|
||||
return nil
|
||||
}
|
||||
pod.Spec.Containers[i].Env = r
|
||||
}
|
||||
}
|
||||
|
||||
// merge in policy for EnvFrom
|
||||
if pip.Spec.EnvFrom != nil {
|
||||
for i, ctr := range pod.Spec.Containers {
|
||||
r, err := mergeEnvFrom(pip, ctr.EnvFrom)
|
||||
if err != nil {
|
||||
// add event to pod
|
||||
c.addEvent(pod, pip, err.Error())
|
||||
|
||||
return nil
|
||||
}
|
||||
pod.Spec.Containers[i].EnvFrom = r
|
||||
}
|
||||
}
|
||||
|
||||
// merge in policy for VolumeMounts
|
||||
if pip.Spec.VolumeMounts != nil {
|
||||
for i, ctr := range pod.Spec.Containers {
|
||||
r, err := mergeVolumeMounts(pip, ctr.VolumeMounts)
|
||||
if err != nil {
|
||||
// add event to pod
|
||||
c.addEvent(pod, pip, err.Error())
|
||||
|
||||
return nil
|
||||
}
|
||||
pod.Spec.Containers[i].VolumeMounts = r
|
||||
}
|
||||
}
|
||||
|
||||
// merge in policy for Volumes
|
||||
if pip.Spec.Volumes != nil {
|
||||
r, err := mergeVolumes(pip, pod.Spec.Volumes)
|
||||
if err != nil {
|
||||
// add event to pod
|
||||
c.addEvent(pod, pip, err.Error())
|
||||
|
||||
return nil
|
||||
}
|
||||
pod.Spec.Volumes = r
|
||||
}
|
||||
|
||||
glog.V(4).Infof("PodPreset %s merged with pod %s successfully", pip.GetName(), pod.GetName())
|
||||
|
||||
// add annotation
|
||||
if pod.ObjectMeta.Annotations == nil {
|
||||
pod.ObjectMeta.Annotations = map[string]string{}
|
||||
}
|
||||
pod.ObjectMeta.Annotations[fmt.Sprintf("%s/%s", annotationPrefix, pip.GetName())] = pip.GetResourceVersion()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func mergeEnv(pip *settings.PodPreset, original []api.EnvVar) ([]api.EnvVar, error) {
|
||||
// if there were no original envvar just return the pip envvar
|
||||
if original == nil {
|
||||
return pip.Spec.Env, nil
|
||||
}
|
||||
|
||||
orig := map[string]interface{}{}
|
||||
for _, v := range original {
|
||||
orig[v.Name] = v
|
||||
}
|
||||
|
||||
// check for conflicts.
|
||||
for _, v := range pip.Spec.Env {
|
||||
found, ok := orig[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
original = append(original, v)
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, v) {
|
||||
return nil, fmt.Errorf("merging env for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pip.GetName(), v.Name, v, found)
|
||||
}
|
||||
}
|
||||
|
||||
return original, nil
|
||||
}
|
||||
|
||||
func mergeEnvFrom(pip *settings.PodPreset, original []api.EnvFromSource) ([]api.EnvFromSource, error) {
|
||||
// if there were no original envfrom just return the pip envfrom
|
||||
if original == nil {
|
||||
return pip.Spec.EnvFrom, nil
|
||||
}
|
||||
|
||||
return append(original, pip.Spec.EnvFrom...), nil
|
||||
}
|
||||
|
||||
func mergeVolumeMounts(pip *settings.PodPreset, original []api.VolumeMount) ([]api.VolumeMount, error) {
|
||||
// if there were no original volume mount just return the pip volume mount
|
||||
if original == nil {
|
||||
return pip.Spec.VolumeMounts, nil
|
||||
}
|
||||
|
||||
// first key by name
|
||||
orig := map[string]interface{}{}
|
||||
for _, v := range original {
|
||||
orig[v.Name] = v
|
||||
}
|
||||
|
||||
// check for conflicts.
|
||||
for _, v := range pip.Spec.VolumeMounts {
|
||||
found, ok := orig[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it continue
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, v) {
|
||||
return nil, fmt.Errorf("merging volume mounts for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pip.GetName(), v.Name, v, found)
|
||||
}
|
||||
}
|
||||
|
||||
// key by mount path
|
||||
orig = map[string]interface{}{}
|
||||
for _, v := range original {
|
||||
orig[v.MountPath] = v
|
||||
}
|
||||
|
||||
// check for conflicts.
|
||||
for _, v := range pip.Spec.VolumeMounts {
|
||||
found, ok := orig[v.MountPath]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
original = append(original, v)
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, v) {
|
||||
return nil, fmt.Errorf("merging volume mounts for %s has a conflict on mount path %s: \n%#v\ndoes not match\n%#v\n in container", pip.GetName(), v.MountPath, v, found)
|
||||
}
|
||||
}
|
||||
|
||||
return original, nil
|
||||
}
|
||||
|
||||
func mergeVolumes(pip *settings.PodPreset, original []api.Volume) ([]api.Volume, error) {
|
||||
// if there were no original volumes just return the pip volumes
|
||||
if original == nil {
|
||||
return pip.Spec.Volumes, nil
|
||||
}
|
||||
|
||||
orig := map[string]api.Volume{}
|
||||
for _, v := range original {
|
||||
orig[v.Name] = v
|
||||
}
|
||||
|
||||
// check for conflicts.
|
||||
for _, v := range pip.Spec.Volumes {
|
||||
found, ok := orig[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
original = append(original, v)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(found, v) {
|
||||
return nil, fmt.Errorf("merging volumes for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\nin pod spec", pip.GetName(), v.Name, v, found)
|
||||
}
|
||||
}
|
||||
|
||||
return original, nil
|
||||
}
|
||||
|
||||
func (c *podPresetPlugin) addEvent(pod *api.Pod, pip *settings.PodPreset, message string) {
|
||||
ref, err := api.GetReference(api.Scheme, pod)
|
||||
if err != nil {
|
||||
glog.Errorf("pip %s: get reference for pod %s failed: %v", pip.GetName(), pod.GetName(), err)
|
||||
return
|
||||
}
|
||||
|
||||
e := &api.Event{
|
||||
InvolvedObject: *ref,
|
||||
Message: message,
|
||||
Source: api.EventSource{
|
||||
Component: fmt.Sprintf("pip %s", pip.GetName()),
|
||||
},
|
||||
Type: "Warning",
|
||||
}
|
||||
|
||||
if _, err := c.client.Core().Events(pod.GetNamespace()).Create(e); err != nil {
|
||||
glog.Errorf("pip %s: creating pod event failed: %v", pip.GetName(), err)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -0,0 +1,582 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admission
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
kadmission "k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/settings"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
||||
informers "k8s.io/kubernetes/pkg/client/informers/informers_generated/internalversion"
|
||||
settingslisters "k8s.io/kubernetes/pkg/client/listers/settings/internalversion"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
)
|
||||
|
||||
func TestMergeEnv(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.EnvVar
|
||||
mod []api.EnvVar
|
||||
result []api.EnvVar
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
result: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
orig: []api.EnvVar{{Name: "abcd", Value: "value2"}, {Name: "hello", Value: "value3"}},
|
||||
mod: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
result: []api.EnvVar{{Name: "abcd", Value: "value2"}, {Name: "hello", Value: "value3"}, {Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
shouldFail: false,
|
||||
},
|
||||
"conflict": {
|
||||
orig: []api.EnvVar{{Name: "abc", Value: "value3"}},
|
||||
mod: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
shouldFail: true,
|
||||
},
|
||||
"one is exact same": {
|
||||
orig: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "hello", Value: "value3"}},
|
||||
mod: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
result: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "hello", Value: "value3"}, {Name: "ABC", Value: "value3"}},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
result, err := mergeEnv(
|
||||
&settings.PodPreset{Spec: settings.PodPresetSpec{Env: test.mod}},
|
||||
test.orig,
|
||||
)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Fatalf("expected test %q to fail but got nil", name)
|
||||
}
|
||||
if !test.shouldFail && err != nil {
|
||||
t.Fatalf("test %q failed: %v", name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.result, result) {
|
||||
t.Fatalf("results were not equal for test %q: got %#v; expected: %#v", name, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeEnvFrom(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.EnvFromSource
|
||||
mod []api.EnvFromSource
|
||||
result []api.EnvFromSource
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
orig: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "thing"},
|
||||
},
|
||||
},
|
||||
},
|
||||
mod: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "thing"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
result, err := mergeEnvFrom(
|
||||
&settings.PodPreset{Spec: settings.PodPresetSpec{EnvFrom: test.mod}},
|
||||
test.orig,
|
||||
)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Fatalf("expected test %q to fail but got nil", name)
|
||||
}
|
||||
if !test.shouldFail && err != nil {
|
||||
t.Fatalf("test %q failed: %v", name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.result, result) {
|
||||
t.Fatalf("results were not equal for test %q: got %#v; expected: %#v", name, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeVolumeMounts(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.VolumeMount
|
||||
mod []api.VolumeMount
|
||||
result []api.VolumeMount
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
result: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
orig: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
result: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"conflict": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/things/",
|
||||
},
|
||||
},
|
||||
orig: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
"conflict on mount path": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
{
|
||||
Name: "things-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
orig: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
"one is exact same": {
|
||||
mod: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
orig: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
},
|
||||
result: []api.VolumeMount{
|
||||
{
|
||||
Name: "etc-volume",
|
||||
MountPath: "/etc/",
|
||||
},
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
result, err := mergeVolumeMounts(
|
||||
&settings.PodPreset{Spec: settings.PodPresetSpec{VolumeMounts: test.mod}},
|
||||
test.orig,
|
||||
)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Fatalf("expected test %q to fail but got nil", name)
|
||||
}
|
||||
if !test.shouldFail && err != nil {
|
||||
t.Fatalf("test %q failed: %v", name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.result, result) {
|
||||
t.Fatalf("results were not equal for test %q: got %#v; expected: %#v", name, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeVolumes(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.Volume
|
||||
mod []api.Volume
|
||||
result []api.Volume
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []api.Volume{
|
||||
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
result: []api.Volume{
|
||||
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
orig: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
mod: []api.Volume{
|
||||
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
result: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"conflict": {
|
||||
orig: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
mod: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: "/etc/apparmor.d"}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
shouldFail: true,
|
||||
},
|
||||
"one is exact same": {
|
||||
orig: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
mod: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
result: []api.Volume{
|
||||
{Name: "vol3", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol4", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
result, err := mergeVolumes(
|
||||
&settings.PodPreset{Spec: settings.PodPresetSpec{Volumes: test.mod}},
|
||||
test.orig,
|
||||
)
|
||||
if test.shouldFail && err == nil {
|
||||
t.Fatalf("expected test %q to fail but got nil", name)
|
||||
}
|
||||
if !test.shouldFail && err != nil {
|
||||
t.Fatalf("test %q failed: %v", name, err)
|
||||
}
|
||||
if !reflect.DeepEqual(test.result, result) {
|
||||
t.Fatalf("results were not equal for test %q: got %#v; expected: %#v", name, result, test.result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewTestAdmission provides an admission plugin with test implementations of internal structs. It uses
|
||||
// an authorizer that always returns true.
|
||||
func NewTestAdmission(lister settingslisters.PodPresetLister, objects ...runtime.Object) kadmission.Interface {
|
||||
// Build a test client that the admission plugin can use to look up the service account missing from its cache
|
||||
client := fake.NewSimpleClientset(objects...)
|
||||
|
||||
return &podPresetPlugin{
|
||||
client: client,
|
||||
Handler: kadmission.NewHandler(kadmission.Create),
|
||||
lister: lister,
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitConflictWithDifferentNamespaceShouldDoNothing(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "othernamespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: v1.LabelSelector{
|
||||
MatchExpressions: []v1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: v1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitConflictWithNonMatchingLabelsShouldNotError(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: v1.LabelSelector{
|
||||
MatchExpressions: []v1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: v1.LabelSelectorOpIn,
|
||||
Values: []string{"S3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmit(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABCD", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: v1.LabelSelector{
|
||||
MatchExpressions: []v1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: v1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []api.Volume{{Name: "vol", VolumeSource: api.VolumeSource{EmptyDir: &api.EmptyDirVolumeSource{}}}},
|
||||
Env: []api.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []api.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func admitPod(pod *api.Pod, pip *settings.PodPreset) error {
|
||||
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
|
||||
store := informerFactory.Settings().InternalVersion().PodPresets().Informer().GetStore()
|
||||
store.Add(pip)
|
||||
plugin := NewTestAdmission(informerFactory.Settings().InternalVersion().PodPresets().Lister())
|
||||
attrs := kadmission.NewAttributesRecord(
|
||||
pod,
|
||||
nil,
|
||||
api.Kind("Pod").WithVersion("version"),
|
||||
"namespace",
|
||||
"",
|
||||
api.Resource("pods").WithVersion("version"),
|
||||
"",
|
||||
kadmission.Create,
|
||||
&user.DefaultInfo{},
|
||||
)
|
||||
|
||||
err := plugin.Admit(attrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -73,6 +73,7 @@ go_library(
|
|||
"persistent_volumes-disruptive.go",
|
||||
"persistent_volumes-vsphere.go",
|
||||
"pod_gc.go",
|
||||
"podpreset.go",
|
||||
"pods.go",
|
||||
"portforward.go",
|
||||
"pre_stop.go",
|
||||
|
@ -118,6 +119,7 @@ go_library(
|
|||
"//pkg/apis/extensions:go_default_library",
|
||||
"//pkg/apis/extensions/v1beta1:go_default_library",
|
||||
"//pkg/apis/rbac/v1beta1:go_default_library",
|
||||
"//pkg/apis/settings/v1alpha1:go_default_library",
|
||||
"//pkg/apis/storage/util:go_default_library",
|
||||
"//pkg/apis/storage/v1beta1:go_default_library",
|
||||
"//pkg/apis/storage/v1beta1/util:go_default_library",
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
settings "k8s.io/kubernetes/pkg/apis/settings/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
"k8s.io/kubernetes/test/e2e/framework"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = framework.KubeDescribe("PodPreset", func() {
|
||||
f := framework.NewDefaultFramework("podpreset")
|
||||
|
||||
var podClient *framework.PodClient
|
||||
BeforeEach(func() {
|
||||
// only run on gce for the time being til we find an easier way to update
|
||||
// the admission controllers used on the others
|
||||
framework.SkipUnlessProviderIs("gce")
|
||||
podClient = f.PodClient()
|
||||
})
|
||||
|
||||
// Simplest case: all pods succeed promptly
|
||||
It("should create a pod preset", func() {
|
||||
By("Creating a pod preset")
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{{Name: "vol", VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}},
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{Name: "vol", MountPath: "/foo"},
|
||||
},
|
||||
Env: []v1.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := createPodPreset(f.ClientSet, f.Namespace.Name, pip)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("creating the pod")
|
||||
name := "pod-preset-pod"
|
||||
value := strconv.Itoa(time.Now().Nanosecond())
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: f.Namespace.Name,
|
||||
Labels: map[string]string{
|
||||
"name": "foo",
|
||||
"time": value,
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
Image: "gcr.io/google_containers/nginx-slim:0.7",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("setting up watch")
|
||||
selector := labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))
|
||||
options := metav1.ListOptions{LabelSelector: selector.String()}
|
||||
pods, err := podClient.List(options)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to query for pod")
|
||||
Expect(len(pods.Items)).To(Equal(0))
|
||||
options = metav1.ListOptions{
|
||||
LabelSelector: selector.String(),
|
||||
ResourceVersion: pods.ListMeta.ResourceVersion,
|
||||
}
|
||||
w, err := podClient.Watch(options)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to set up watch")
|
||||
|
||||
By("submitting the pod to kubernetes")
|
||||
podClient.Create(pod)
|
||||
|
||||
By("verifying the pod is in kubernetes")
|
||||
selector = labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))
|
||||
options = metav1.ListOptions{LabelSelector: selector.String()}
|
||||
pods, err = podClient.List(options)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to query for pod")
|
||||
Expect(len(pods.Items)).To(Equal(1))
|
||||
|
||||
By("verifying pod creation was observed")
|
||||
select {
|
||||
case event, _ := <-w.ResultChan():
|
||||
if event.Type != watch.Added {
|
||||
framework.Failf("Failed to observe pod creation: %v", event)
|
||||
}
|
||||
case <-time.After(framework.PodStartTimeout):
|
||||
Fail("Timeout while waiting for pod creation")
|
||||
}
|
||||
|
||||
// We need to wait for the pod to be running, otherwise the deletion
|
||||
// may be carried out immediately rather than gracefully.
|
||||
framework.ExpectNoError(f.WaitForPodRunning(pod.Name))
|
||||
|
||||
By("ensuring pod is modified")
|
||||
// save the running pod
|
||||
pod, err = podClient.Get(pod.Name, metav1.GetOptions{})
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to GET scheduled pod")
|
||||
|
||||
// check the annotation is there
|
||||
if _, ok := pod.Annotations["podpreset.admission.kubernetes.io/hello"]; !ok {
|
||||
framework.Failf("Annotation not found in pod annotations: \n%v\n", pod.Annotations)
|
||||
}
|
||||
|
||||
// verify the env is the same
|
||||
if !reflect.DeepEqual(pip.Spec.Env, pod.Spec.Containers[0].Env) {
|
||||
framework.Failf("env of pod container does not match the env of the pip: expected %#v, got: %#v", pip.Spec.Env, pod.Spec.Containers[0].Env)
|
||||
}
|
||||
})
|
||||
|
||||
It("should not modify the pod on conflict", func() {
|
||||
By("Creating a pod preset")
|
||||
|
||||
pip := &settings.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: f.Namespace.Name,
|
||||
},
|
||||
Spec: settings.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []v1.Volume{{Name: "vol", VolumeSource: v1.VolumeSource{EmptyDir: &v1.EmptyDirVolumeSource{}}}},
|
||||
VolumeMounts: []v1.VolumeMount{
|
||||
{Name: "vol", MountPath: "/foo"},
|
||||
},
|
||||
Env: []v1.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := createPodPreset(f.ClientSet, f.Namespace.Name, pip)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("creating the pod")
|
||||
name := "pod-preset-pod"
|
||||
value := strconv.Itoa(time.Now().Nanosecond())
|
||||
originalPod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: f.Namespace.Name,
|
||||
Labels: map[string]string{
|
||||
"name": "foo",
|
||||
"time": value,
|
||||
"security": "S2",
|
||||
},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "nginx",
|
||||
Image: "gcr.io/google_containers/nginx-slim:0.7",
|
||||
Env: []v1.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
By("setting up watch")
|
||||
selector := labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))
|
||||
options := metav1.ListOptions{LabelSelector: selector.String()}
|
||||
pods, err := podClient.List(options)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to query for pod")
|
||||
Expect(len(pods.Items)).To(Equal(0))
|
||||
options = metav1.ListOptions{
|
||||
LabelSelector: selector.String(),
|
||||
ResourceVersion: pods.ListMeta.ResourceVersion,
|
||||
}
|
||||
w, err := podClient.Watch(options)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to set up watch")
|
||||
|
||||
By("submitting the pod to kubernetes")
|
||||
podClient.Create(originalPod)
|
||||
|
||||
By("verifying the pod is in kubernetes")
|
||||
selector = labels.SelectorFromSet(labels.Set(map[string]string{"time": value}))
|
||||
options = metav1.ListOptions{LabelSelector: selector.String()}
|
||||
pods, err = podClient.List(options)
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to query for pod")
|
||||
Expect(len(pods.Items)).To(Equal(1))
|
||||
|
||||
By("verifying pod creation was observed")
|
||||
select {
|
||||
case event, _ := <-w.ResultChan():
|
||||
if event.Type != watch.Added {
|
||||
framework.Failf("Failed to observe pod creation: %v", event)
|
||||
}
|
||||
case <-time.After(framework.PodStartTimeout):
|
||||
Fail("Timeout while waiting for pod creation")
|
||||
}
|
||||
|
||||
// We need to wait for the pod to be running, otherwise the deletion
|
||||
// may be carried out immediately rather than gracefully.
|
||||
framework.ExpectNoError(f.WaitForPodRunning(originalPod.Name))
|
||||
|
||||
By("ensuring pod is modified")
|
||||
// save the running pod
|
||||
pod, err := podClient.Get(originalPod.Name, metav1.GetOptions{})
|
||||
Expect(err).NotTo(HaveOccurred(), "failed to GET scheduled pod")
|
||||
|
||||
// check the annotation is not there
|
||||
if _, ok := pod.Annotations["podpreset.admission.kubernetes.io/hello"]; ok {
|
||||
framework.Failf("Annotation found in pod annotations and should not be: \n%v\n", pod.Annotations)
|
||||
}
|
||||
|
||||
// verify the env is the same
|
||||
if !reflect.DeepEqual(originalPod.Spec.Containers[0].Env, pod.Spec.Containers[0].Env) {
|
||||
framework.Failf("env of pod container does not match the env of the original pod: expected %#v, got: %#v", originalPod.Spec.Containers[0].Env, pod.Spec.Containers[0].Env)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
func getPodPreset(c clientset.Interface, ns, name string) (*settings.PodPreset, error) {
|
||||
return c.Settings().PodPresets(ns).Get(name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func createPodPreset(c clientset.Interface, ns string, job *settings.PodPreset) (*settings.PodPreset, error) {
|
||||
return c.Settings().PodPresets(ns).Create(job)
|
||||
}
|
||||
|
||||
func updatePodPreset(c clientset.Interface, ns string, job *settings.PodPreset) (*settings.PodPreset, error) {
|
||||
return c.Settings().PodPresets(ns).Update(job)
|
||||
}
|
||||
|
||||
func deletePodPreset(c clientset.Interface, ns, name string) error {
|
||||
return c.Settings().PodPresets(ns).Delete(name, nil)
|
||||
}
|
Loading…
Reference in New Issue