mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-09-09 05:01:46 +00:00
remove pod presets
This commit is contained in:
@@ -1,65 +0,0 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["admission_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/controller:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/settings/v1alpha1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/admission/testing:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/listers/settings/v1alpha1:go_default_library",
|
||||
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["admission.go"],
|
||||
importpath = "k8s.io/kubernetes/plugin/pkg/admission/podpreset",
|
||||
deps = [
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/pods:go_default_library",
|
||||
"//pkg/apis/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/settings/v1alpha1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//staging/src/k8s.io/apimachinery/pkg/util/validation/field:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",
|
||||
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/informers:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//staging/src/k8s.io/client-go/listers/settings/v1alpha1:go_default_library",
|
||||
"//vendor/k8s.io/klog/v2:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
@@ -1,455 +0,0 @@
|
||||
/*
|
||||
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 podpreset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
settingsv1alpha1 "k8s.io/api/settings/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
genericadmissioninitializer "k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
settingsv1alpha1listers "k8s.io/client-go/listers/settings/v1alpha1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/pods"
|
||||
apiscorev1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
annotationPrefix = "podpreset.admission.kubernetes.io"
|
||||
// PluginName is a string with the name of the plugin
|
||||
PluginName = "PodPreset"
|
||||
)
|
||||
|
||||
// Register registers a plugin
|
||||
func Register(plugins *admission.Plugins) {
|
||||
plugins.Register(PluginName, func(config io.Reader) (admission.Interface, error) {
|
||||
return NewPlugin(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// Plugin is an implementation of admission.Interface.
|
||||
type Plugin struct {
|
||||
*admission.Handler
|
||||
client kubernetes.Interface
|
||||
|
||||
lister settingsv1alpha1listers.PodPresetLister
|
||||
}
|
||||
|
||||
var _ admission.MutationInterface = &Plugin{}
|
||||
var _ = genericadmissioninitializer.WantsExternalKubeInformerFactory(&Plugin{})
|
||||
var _ = genericadmissioninitializer.WantsExternalKubeClientSet(&Plugin{})
|
||||
|
||||
// NewPlugin creates a new pod preset admission plugin.
|
||||
func NewPlugin() *Plugin {
|
||||
return &Plugin{
|
||||
Handler: admission.NewHandler(admission.Create, admission.Update),
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateInitialization validates the Plugin was initialized properly
|
||||
func (p *Plugin) ValidateInitialization() error {
|
||||
if p.client == nil {
|
||||
return fmt.Errorf("%s requires a client", PluginName)
|
||||
}
|
||||
if p.lister == nil {
|
||||
return fmt.Errorf("%s requires a lister", PluginName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetExternalKubeClientSet registers the client into Plugin
|
||||
func (p *Plugin) SetExternalKubeClientSet(client kubernetes.Interface) {
|
||||
p.client = client
|
||||
}
|
||||
|
||||
// SetExternalKubeInformerFactory registers an informer factory into Plugin
|
||||
func (p *Plugin) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
|
||||
podPresetInformer := f.Settings().V1alpha1().PodPresets()
|
||||
p.lister = podPresetInformer.Lister()
|
||||
p.SetReadyFunc(podPresetInformer.Informer().HasSynced)
|
||||
}
|
||||
|
||||
// Admit injects a pod with the specific fields for each pod preset it matches.
|
||||
func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) 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")
|
||||
}
|
||||
|
||||
if _, isMirrorPod := pod.Annotations[api.MirrorPodAnnotationKey]; isMirrorPod {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ignore if exclusion annotation is present
|
||||
if podAnnotations := pod.GetAnnotations(); podAnnotations != nil {
|
||||
klog.V(5).Infof("Looking at pod annotations, found: %v", podAnnotations)
|
||||
if podAnnotations[api.PodPresetOptOutAnnotationKey] == "true" {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
list, err := p.lister.PodPresets(a.GetNamespace()).List(labels.Everything())
|
||||
if err != nil {
|
||||
return fmt.Errorf("listing pod presets failed: %v", err)
|
||||
}
|
||||
|
||||
matchingPPs, err := filterPodPresets(list, pod)
|
||||
if err != nil {
|
||||
return fmt.Errorf("filtering pod presets failed: %v", err)
|
||||
}
|
||||
|
||||
if len(matchingPPs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
presetNames := make([]string, len(matchingPPs))
|
||||
for i, pp := range matchingPPs {
|
||||
presetNames[i] = pp.GetName()
|
||||
}
|
||||
|
||||
// detect merge conflict
|
||||
err = safeToApplyPodPresetsOnPod(pod, matchingPPs)
|
||||
if err != nil {
|
||||
// conflict, ignore the error, but raise an event
|
||||
klog.Warningf("conflict occurred while applying podpresets: %s on pod: %v err: %v",
|
||||
strings.Join(presetNames, ","), pod.GetGenerateName(), err)
|
||||
return nil
|
||||
}
|
||||
|
||||
applyPodPresetsOnPod(pod, matchingPPs)
|
||||
|
||||
klog.Infof("applied podpresets: %s successfully on Pod: %+v ", strings.Join(presetNames, ","), pod.GetGenerateName())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// filterPodPresets returns list of PodPresets which match given Pod.
|
||||
func filterPodPresets(list []*settingsv1alpha1.PodPreset, pod *api.Pod) ([]*settingsv1alpha1.PodPreset, error) {
|
||||
var matchingPPs []*settingsv1alpha1.PodPreset
|
||||
|
||||
for _, pp := range list {
|
||||
selector, err := metav1.LabelSelectorAsSelector(&pp.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("label selector conversion failed: %v for selector: %v", pp.Spec.Selector, err)
|
||||
}
|
||||
|
||||
// check if the pod labels match the selector
|
||||
if !selector.Matches(labels.Set(pod.Labels)) {
|
||||
continue
|
||||
}
|
||||
klog.V(4).Infof("PodPreset %s matches pod %s labels", pp.GetName(), pod.GetName())
|
||||
matchingPPs = append(matchingPPs, pp)
|
||||
}
|
||||
return matchingPPs, nil
|
||||
}
|
||||
|
||||
// safeToApplyPodPresetsOnPod determines if there is any conflict in information
|
||||
// injected by given PodPresets in the Pod.
|
||||
func safeToApplyPodPresetsOnPod(pod *api.Pod, podPresets []*settingsv1alpha1.PodPreset) error {
|
||||
var errs []error
|
||||
|
||||
// volumes attribute is defined at the Pod level, so determine if volumes
|
||||
// injection is causing any conflict.
|
||||
if _, err := mergeVolumes(pod.Spec.Volumes, podPresets); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
pods.VisitContainersWithPath(&pod.Spec, field.NewPath("spec"), func(c *api.Container, _ *field.Path) bool {
|
||||
if err := safeToApplyPodPresetsOnContainer(c, podPresets); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// safeToApplyPodPresetsOnContainer determines if there is any conflict in
|
||||
// information injected by given PodPresets in the given container.
|
||||
func safeToApplyPodPresetsOnContainer(ctr *api.Container, podPresets []*settingsv1alpha1.PodPreset) error {
|
||||
var errs []error
|
||||
// check if it is safe to merge env vars and volume mounts from given podpresets and
|
||||
// container's existing env vars.
|
||||
if _, err := mergeEnv(ctr.Env, podPresets); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
if _, err := mergeVolumeMounts(ctr.VolumeMounts, podPresets); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// mergeEnv merges a list of env vars with the env vars injected by given list podPresets.
|
||||
// It returns an error if it detects any conflict during the merge.
|
||||
func mergeEnv(envVars []api.EnvVar, podPresets []*settingsv1alpha1.PodPreset) ([]api.EnvVar, error) {
|
||||
origEnv := map[string]api.EnvVar{}
|
||||
for _, v := range envVars {
|
||||
origEnv[v.Name] = v
|
||||
}
|
||||
|
||||
mergedEnv := make([]api.EnvVar, len(envVars))
|
||||
copy(mergedEnv, envVars)
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, pp := range podPresets {
|
||||
for _, v := range pp.Spec.Env {
|
||||
internalEnv := api.EnvVar{}
|
||||
if err := apiscorev1.Convert_v1_EnvVar_To_core_EnvVar(&v, &internalEnv, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
found, ok := origEnv[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
origEnv[v.Name] = internalEnv
|
||||
mergedEnv = append(mergedEnv, internalEnv)
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, internalEnv) {
|
||||
errs = append(errs, fmt.Errorf("merging env for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := utilerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mergedEnv, err
|
||||
}
|
||||
|
||||
type envFromMergeKey struct {
|
||||
prefix string
|
||||
configMapRefName string
|
||||
secretRefName string
|
||||
}
|
||||
|
||||
func newEnvFromMergeKey(e api.EnvFromSource) envFromMergeKey {
|
||||
k := envFromMergeKey{prefix: e.Prefix}
|
||||
if e.ConfigMapRef != nil {
|
||||
k.configMapRefName = e.ConfigMapRef.Name
|
||||
}
|
||||
if e.SecretRef != nil {
|
||||
k.secretRefName = e.SecretRef.Name
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func mergeEnvFrom(envSources []api.EnvFromSource, podPresets []*settingsv1alpha1.PodPreset) ([]api.EnvFromSource, error) {
|
||||
var mergedEnvFrom []api.EnvFromSource
|
||||
|
||||
// merge envFrom using a identify key to ensure Admit reinvocations are idempotent
|
||||
origEnvSources := map[envFromMergeKey]api.EnvFromSource{}
|
||||
for _, envSource := range envSources {
|
||||
origEnvSources[newEnvFromMergeKey(envSource)] = envSource
|
||||
}
|
||||
mergedEnvFrom = append(mergedEnvFrom, envSources...)
|
||||
var errs []error
|
||||
for _, pp := range podPresets {
|
||||
for _, envFromSource := range pp.Spec.EnvFrom {
|
||||
internalEnvFrom := api.EnvFromSource{}
|
||||
if err := apiscorev1.Convert_v1_EnvFromSource_To_core_EnvFromSource(&envFromSource, &internalEnvFrom, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
found, ok := origEnvSources[newEnvFromMergeKey(internalEnvFrom)]
|
||||
if !ok {
|
||||
mergedEnvFrom = append(mergedEnvFrom, internalEnvFrom)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(found, internalEnvFrom) {
|
||||
errs = append(errs, fmt.Errorf("merging envFrom for %s has a conflict: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), internalEnvFrom, found))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err := utilerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mergedEnvFrom, nil
|
||||
}
|
||||
|
||||
// mergeVolumeMounts merges given list of VolumeMounts with the volumeMounts
|
||||
// injected by given podPresets. It returns an error if it detects any conflict during the merge.
|
||||
func mergeVolumeMounts(volumeMounts []api.VolumeMount, podPresets []*settingsv1alpha1.PodPreset) ([]api.VolumeMount, error) {
|
||||
|
||||
origVolumeMounts := map[string]api.VolumeMount{}
|
||||
volumeMountsByPath := map[string]api.VolumeMount{}
|
||||
for _, v := range volumeMounts {
|
||||
origVolumeMounts[v.Name] = v
|
||||
volumeMountsByPath[v.MountPath] = v
|
||||
}
|
||||
|
||||
mergedVolumeMounts := make([]api.VolumeMount, len(volumeMounts))
|
||||
copy(mergedVolumeMounts, volumeMounts)
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, pp := range podPresets {
|
||||
for _, v := range pp.Spec.VolumeMounts {
|
||||
internalVolumeMount := api.VolumeMount{}
|
||||
if err := apiscorev1.Convert_v1_VolumeMount_To_core_VolumeMount(&v, &internalVolumeMount, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
found, ok := origVolumeMounts[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
origVolumeMounts[v.Name] = internalVolumeMount
|
||||
mergedVolumeMounts = append(mergedVolumeMounts, internalVolumeMount)
|
||||
} else {
|
||||
// make sure they are identical or throw an error
|
||||
// shall we throw an error for identical volumeMounts ?
|
||||
if !reflect.DeepEqual(found, internalVolumeMount) {
|
||||
errs = append(errs, fmt.Errorf("merging volume mounts for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
|
||||
}
|
||||
}
|
||||
|
||||
found, ok = volumeMountsByPath[v.MountPath]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
volumeMountsByPath[v.MountPath] = internalVolumeMount
|
||||
} else {
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, internalVolumeMount) {
|
||||
errs = append(errs, fmt.Errorf("merging volume mounts for %s has a conflict on mount path %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.MountPath, v, found))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := utilerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mergedVolumeMounts, err
|
||||
}
|
||||
|
||||
// mergeVolumes merges given list of Volumes with the volumes injected by given
|
||||
// podPresets. It returns an error if it detects any conflict during the merge.
|
||||
func mergeVolumes(volumes []api.Volume, podPresets []*settingsv1alpha1.PodPreset) ([]api.Volume, error) {
|
||||
origVolumes := map[string]api.Volume{}
|
||||
for _, v := range volumes {
|
||||
origVolumes[v.Name] = v
|
||||
}
|
||||
|
||||
mergedVolumes := make([]api.Volume, len(volumes))
|
||||
copy(mergedVolumes, volumes)
|
||||
|
||||
var errs []error
|
||||
|
||||
for _, pp := range podPresets {
|
||||
for _, v := range pp.Spec.Volumes {
|
||||
internalVolume := api.Volume{}
|
||||
if err := apiscorev1.Convert_v1_Volume_To_core_Volume(&v, &internalVolume, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
found, ok := origVolumes[v.Name]
|
||||
if !ok {
|
||||
// if we don't already have it append it and continue
|
||||
origVolumes[v.Name] = internalVolume
|
||||
mergedVolumes = append(mergedVolumes, internalVolume)
|
||||
continue
|
||||
}
|
||||
|
||||
// make sure they are identical or throw an error
|
||||
if !reflect.DeepEqual(found, internalVolume) {
|
||||
errs = append(errs, fmt.Errorf("merging volumes for %s has a conflict on %s: \n%#v\ndoes not match\n%#v\n in container", pp.GetName(), v.Name, v, found))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := utilerrors.NewAggregate(errs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(mergedVolumes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return mergedVolumes, err
|
||||
}
|
||||
|
||||
// applyPodPresetsOnPod updates the PodSpec with merged information from all the
|
||||
// applicable PodPresets. It ignores the errors of merge functions because merge
|
||||
// errors have already been checked in safeToApplyPodPresetsOnPod function.
|
||||
func applyPodPresetsOnPod(pod *api.Pod, podPresets []*settingsv1alpha1.PodPreset) {
|
||||
if len(podPresets) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
volumes, _ := mergeVolumes(pod.Spec.Volumes, podPresets)
|
||||
pod.Spec.Volumes = volumes
|
||||
|
||||
for i, ctr := range pod.Spec.Containers {
|
||||
applyPodPresetsOnContainer(&ctr, podPresets)
|
||||
pod.Spec.Containers[i] = ctr
|
||||
}
|
||||
for i, iCtr := range pod.Spec.InitContainers {
|
||||
applyPodPresetsOnContainer(&iCtr, podPresets)
|
||||
pod.Spec.InitContainers[i] = iCtr
|
||||
}
|
||||
|
||||
// add annotation
|
||||
if pod.ObjectMeta.Annotations == nil {
|
||||
pod.ObjectMeta.Annotations = map[string]string{}
|
||||
}
|
||||
|
||||
for _, pp := range podPresets {
|
||||
pod.ObjectMeta.Annotations[fmt.Sprintf("%s/podpreset-%s", annotationPrefix, pp.GetName())] = pp.GetResourceVersion()
|
||||
}
|
||||
}
|
||||
|
||||
// applyPodPresetsOnContainer injects envVars, VolumeMounts and envFrom from
|
||||
// given podPresets in to the given container. It ignores conflict errors
|
||||
// because it assumes those have been checked already by the caller.
|
||||
func applyPodPresetsOnContainer(ctr *api.Container, podPresets []*settingsv1alpha1.PodPreset) {
|
||||
envVars, _ := mergeEnv(ctr.Env, podPresets)
|
||||
ctr.Env = envVars
|
||||
|
||||
volumeMounts, _ := mergeVolumeMounts(ctr.VolumeMounts, podPresets)
|
||||
ctr.VolumeMounts = volumeMounts
|
||||
|
||||
envFrom, _ := mergeEnvFrom(ctr.EnvFrom, podPresets)
|
||||
ctr.EnvFrom = envFrom
|
||||
}
|
@@ -1,890 +0,0 @@
|
||||
/*
|
||||
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
fuzz "github.com/google/gofuzz"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
settingsv1alpha1 "k8s.io/api/settings/v1alpha1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
kadmission "k8s.io/apiserver/pkg/admission"
|
||||
admissiontesting "k8s.io/apiserver/pkg/admission/testing"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
settingsv1alpha1listers "k8s.io/client-go/listers/settings/v1alpha1"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/controller"
|
||||
)
|
||||
|
||||
func TestMergeEnv(t *testing.T) {
|
||||
tests := map[string]struct {
|
||||
orig []api.EnvVar
|
||||
mod []corev1.EnvVar
|
||||
result []api.EnvVar
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []corev1.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: []corev1.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: []corev1.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: []corev1.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(
|
||||
test.orig,
|
||||
[]*settingsv1alpha1.PodPreset{{Spec: settingsv1alpha1.PodPresetSpec{Env: test.mod}}},
|
||||
)
|
||||
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 []corev1.EnvFromSource
|
||||
result []api.EnvFromSource
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.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: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.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(
|
||||
test.orig,
|
||||
[]*settingsv1alpha1.PodPreset{{Spec: settingsv1alpha1.PodPresetSpec{EnvFrom: test.mod}}},
|
||||
)
|
||||
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 []corev1.VolumeMount
|
||||
result []api.VolumeMount
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []corev1.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
result: []api.VolumeMount{
|
||||
{
|
||||
Name: "simply-mounted-volume",
|
||||
MountPath: "/opt/",
|
||||
},
|
||||
},
|
||||
shouldFail: false,
|
||||
},
|
||||
"good merge": {
|
||||
mod: []corev1.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: []corev1.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: []corev1.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: []corev1.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(
|
||||
test.orig,
|
||||
[]*settingsv1alpha1.PodPreset{{Spec: settingsv1alpha1.PodPresetSpec{VolumeMounts: test.mod}}},
|
||||
)
|
||||
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 []corev1.Volume
|
||||
result []api.Volume
|
||||
shouldFail bool
|
||||
}{
|
||||
"empty original": {
|
||||
mod: []corev1.Volume{
|
||||
{Name: "vol", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.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: []corev1.Volume{
|
||||
{Name: "vol", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.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: []corev1.Volume{
|
||||
{Name: "vol3", VolumeSource: corev1.VolumeSource{HostPath: &corev1.HostPathVolumeSource{Path: "/etc/apparmor.d"}}},
|
||||
{Name: "vol2", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.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: []corev1.Volume{
|
||||
{Name: "vol3", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}},
|
||||
{Name: "vol2", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.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(
|
||||
test.orig,
|
||||
[]*settingsv1alpha1.PodPreset{{Spec: settingsv1alpha1.PodPresetSpec{Volumes: test.mod}}},
|
||||
)
|
||||
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 settingsv1alpha1listers.PodPresetLister, objects ...runtime.Object) kadmission.MutationInterface {
|
||||
// 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 &Plugin{
|
||||
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 := &settingsv1alpha1.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "othernamespace",
|
||||
},
|
||||
Spec: settingsv1alpha1.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(t, 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 := &settingsv1alpha1.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settingsv1alpha1.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(t, pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitConflictShouldNotModifyPod(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"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
origPod := *pod
|
||||
|
||||
pip := &settingsv1alpha1.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settingsv1alpha1.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Env: []corev1.EnvVar{{Name: "abc", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(t, pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(&origPod, pod) {
|
||||
t.Fatalf("pod should not get modified in case of conflict origPod: %+v got: %+v", &origPod, pod)
|
||||
}
|
||||
}
|
||||
|
||||
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 := &settingsv1alpha1.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settingsv1alpha1.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{{Name: "vol", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}},
|
||||
Env: []corev1.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := admitPod(t, pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitMirrorPod(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
mirrorPod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
Annotations: map[string]string{api.MirrorPodAnnotationKey: "mirror"},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settingsv1alpha1.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settingsv1alpha1.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{{Name: "vol", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}},
|
||||
Env: []corev1.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := admitPod(t, mirrorPod, pip); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
container := mirrorPod.Spec.Containers[0]
|
||||
if len(mirrorPod.Spec.Volumes) != 0 ||
|
||||
len(container.VolumeMounts) != 0 ||
|
||||
len(container.Env) != 0 ||
|
||||
len(container.EnvFrom) != 0 {
|
||||
t.Fatalf("mirror pod is updated by PodPreset admission:\n\tVolumes got %d, expected 0\n\tVolumeMounts go %d, expected 0\n\tEnv got, %d expected 0\n\tEnvFrom got %d, expected 0", len(mirrorPod.Spec.Volumes), len(container.VolumeMounts), len(container.Env), len(container.EnvFrom))
|
||||
}
|
||||
}
|
||||
|
||||
func TestExclusionNoAdmit(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
Namespace: "namespace",
|
||||
Labels: map[string]string{
|
||||
"security": "S2",
|
||||
},
|
||||
Annotations: map[string]string{
|
||||
api.PodPresetOptOutAnnotationKey: "true",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{
|
||||
{
|
||||
Name: containerName,
|
||||
Env: []api.EnvVar{{Name: "abc", Value: "value2"}, {Name: "ABCD", Value: "value3"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
pip := &settingsv1alpha1.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "namespace",
|
||||
},
|
||||
Spec: settingsv1alpha1.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{{Name: "vol", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}},
|
||||
Env: []corev1.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
originalPod := pod.DeepCopy()
|
||||
err := admitPod(t, pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify PodSpec has not been mutated
|
||||
if !reflect.DeepEqual(pod, originalPod) {
|
||||
t.Fatalf("Expected pod spec of '%v' to be unchanged", pod.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdmitEmptyPodNamespace(t *testing.T) {
|
||||
containerName := "container"
|
||||
|
||||
pod := &api.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mypod",
|
||||
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 := &settingsv1alpha1.PodPreset{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "hello",
|
||||
Namespace: "different", // (pod will be submitted to namespace 'namespace')
|
||||
},
|
||||
Spec: settingsv1alpha1.PodPresetSpec{
|
||||
Selector: metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: "security",
|
||||
Operator: metav1.LabelSelectorOpIn,
|
||||
Values: []string{"S2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Volumes: []corev1.Volume{{Name: "vol", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}},
|
||||
Env: []corev1.EnvVar{{Name: "abcd", Value: "value"}, {Name: "ABC", Value: "value"}},
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
{
|
||||
Prefix: "pre_",
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "abc"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
originalPod := pod.DeepCopy()
|
||||
err := admitPod(t, pod, pip)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// verify PodSpec has not been mutated
|
||||
if !reflect.DeepEqual(pod, originalPod) {
|
||||
t.Fatalf("pod should not get modified in case of emptyNamespace origPod: %+v got: %+v", originalPod, pod)
|
||||
}
|
||||
}
|
||||
|
||||
func admitPod(t *testing.T, pod *api.Pod, pip *settingsv1alpha1.PodPreset) error {
|
||||
informerFactory := informers.NewSharedInformerFactory(nil, controller.NoResyncPeriodFunc())
|
||||
store := informerFactory.Settings().V1alpha1().PodPresets().Informer().GetStore()
|
||||
store.Add(pip)
|
||||
plugin := admissiontesting.WithReinvocationTesting(t, NewTestAdmission(informerFactory.Settings().V1alpha1().PodPresets().Lister()))
|
||||
attrs := kadmission.NewAttributesRecord(
|
||||
pod,
|
||||
nil,
|
||||
api.Kind("Pod").WithVersion("version"),
|
||||
"namespace",
|
||||
"",
|
||||
api.Resource("pods").WithVersion("version"),
|
||||
"",
|
||||
kadmission.Create,
|
||||
&metav1.CreateOptions{},
|
||||
false,
|
||||
&user.DefaultInfo{},
|
||||
)
|
||||
|
||||
err := plugin.Admit(context.TODO(), attrs, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestEnvFromMergeKey(t *testing.T) {
|
||||
f := fuzz.New()
|
||||
for i := 0; i < 100; i++ {
|
||||
t.Run(fmt.Sprintf("Run %d/100", i), func(t *testing.T) {
|
||||
orig := api.EnvFromSource{}
|
||||
f.Fuzz(&orig)
|
||||
clone := api.EnvFromSource{}
|
||||
f.Fuzz(&clone)
|
||||
|
||||
key := newEnvFromMergeKey(orig)
|
||||
|
||||
// copy all key fields into the clone so it only differs by fields not from the key
|
||||
clone.Prefix = key.prefix
|
||||
if orig.ConfigMapRef == nil {
|
||||
clone.ConfigMapRef = nil
|
||||
} else {
|
||||
if clone.ConfigMapRef == nil {
|
||||
clone.ConfigMapRef = &api.ConfigMapEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{},
|
||||
}
|
||||
}
|
||||
clone.ConfigMapRef.Name = key.configMapRefName
|
||||
}
|
||||
if orig.SecretRef == nil {
|
||||
clone.SecretRef = nil
|
||||
} else {
|
||||
if clone.SecretRef == nil {
|
||||
clone.SecretRef = &api.SecretEnvSource{
|
||||
LocalObjectReference: api.LocalObjectReference{},
|
||||
}
|
||||
}
|
||||
clone.SecretRef.Name = key.secretRefName
|
||||
}
|
||||
|
||||
// zero out known non-identifying fields
|
||||
for _, e := range []api.EnvFromSource{orig, clone} {
|
||||
if e.ConfigMapRef != nil {
|
||||
e.ConfigMapRef.Optional = nil
|
||||
}
|
||||
if e.SecretRef != nil {
|
||||
e.SecretRef.Optional = nil
|
||||
}
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(orig, clone) {
|
||||
t.Errorf("expected all but known non-identifying fields for envFrom to be in envFromMergeKey but found unaccounted for differences, diff:\n%s", diff.ObjectReflectDiff(orig, clone))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user