mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-10-30 17:58:14 +00:00
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>
This commit is contained in:
320
plugin/pkg/admission/podpreset/admission.go
Normal file
320
plugin/pkg/admission/podpreset/admission.go
Normal 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
|
||||
}
|
||||
}
|
||||
582
plugin/pkg/admission/podpreset/admission_test.go
Normal file
582
plugin/pkg/admission/podpreset/admission_test.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user