mirror of https://github.com/k3s-io/k3s
583 lines
15 KiB
Go
583 lines
15 KiB
Go
/*
|
|
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
|
|
}
|