Merge pull request #41931 from jessfraz/pip

Automatic merge from submit-queue (batch tested with PRs 41931, 39821, 41841, 42197, 42195)

Admission Controller: Add Pod Preset

Based off the proposal in https://github.com/kubernetes/community/pull/254

cc @pmorie @pwittrock 

TODO:
- [ ] tests



**What this PR does / why we need it**: Implements the Pod Injection Policy admission controller

**Which issue this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close that issue when PR gets merged)*: fixes #

**Special notes for your reviewer**:

**Release note**:

```release-note
Added new Api `PodPreset` to enable defining cross-cutting injection of Volumes and Environment into Pods.
```
This commit is contained in:
Kubernetes Submit Queue
2017-03-01 20:07:54 -08:00
committed by GitHub
147 changed files with 18508 additions and 363 deletions

View File

@@ -29,6 +29,7 @@ filegroup(
"//plugin/pkg/admission/namespace/lifecycle:all-srcs",
"//plugin/pkg/admission/persistentvolume/label:all-srcs",
"//plugin/pkg/admission/podnodeselector:all-srcs",
"//plugin/pkg/admission/podpreset:all-srcs",
"//plugin/pkg/admission/resourcequota:all-srcs",
"//plugin/pkg/admission/security:all-srcs",
"//plugin/pkg/admission/securitycontext/scdeny:all-srcs",

View File

@@ -0,0 +1,60 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["admission_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/settings:go_default_library",
"//pkg/client/clientset_generated/internalclientset/fake:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/settings/internalversion:go_default_library",
"//pkg/controller:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apiserver/pkg/admission",
"//vendor:k8s.io/apiserver/pkg/authentication/user",
],
)
go_library(
name = "go_default_library",
srcs = ["admission.go"],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/apis/settings:go_default_library",
"//pkg/client/clientset_generated/internalclientset:go_default_library",
"//pkg/client/informers/informers_generated/internalversion:go_default_library",
"//pkg/client/listers/settings/internalversion:go_default_library",
"//pkg/kubeapiserver/admission:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apiserver/pkg/admission",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@@ -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
}
}

View File

@@ -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
}