Files
virtual-kubelet/internal/podutils/env_internal_test.go
Sargun Dhillon c437e05ad0 Move env var code into its own package
This creates a new package -- podutils. The env var related code
doesn't really have any business being part of the node package,
and to create a separation of concerns, faster tests, and just
general code isolation and cleanliness, we can move the env
var related code into this package. This change is purely hygiene,
and not logic related.

For node, the package is under internal, because the constructor
references manager, which is an internal package.
2020-11-06 14:49:53 -08:00

1093 lines
34 KiB
Go

// Copyright © 2017 The virtual-kubelet 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 podutils
import (
"context"
"sort"
"testing"
gocmp "github.com/google/go-cmp/cmp"
"gotest.tools/assert"
is "gotest.tools/assert/cmp"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
testutil "github.com/virtual-kubelet/virtual-kubelet/internal/test/util"
)
const (
// defaultEventRecorderBufferSize is the default buffer size to use when creating fake event recorders.
defaultEventRecorderBufferSize = 5
// envVarName1 is a string that can be used as the name of an environment value.
envVarName1 = "FOO"
// envVarValue1 is a string meant to be used as the value of the "envVarName1" environment value.
envVarValue1 = "foo_value"
// envVarName2 is a string that can be used as the name of an environment value.
envVarName2 = "BAR"
// envVarValue2 is a string meant to be used as the value of the "envVarName2" environment value.
envVarValue2 = "bar_value"
// envVarName12 is a key that can be used as the name of an environment variable.
envVarName12 = "FOOBAR"
// envVarName3 is a string that can be used as the name of an environment value.
envVarName3 = "CHO"
// envVarName4 is a string that can be used as the name of an environment value.
envVarName4 = "CAR"
// invalidKey1 is a key that cannot be used as the name of an environment variable (since it starts with a digit).
invalidKey1 = "1INVALID"
// invalidKey2 is a key that cannot be used as the name of an environment variable (since it starts with a digit).
invalidKey2 = "2INVALID"
// invalidKey3 is a key that cannot be used as the name of an environment variable (since it starts with a digit).
invalidKey3 = "3INVALID"
// keyFoo is a key that can be used as the name of an environment variable.
keyFoo = "FOO"
// keyBar is a key that can be used as the name of an environment variable.
keyBar = "BAR"
// keyBaz is a key that can be used as the name of an environment variable.
keyBaz = "BAZ"
// namespace is the namespace to which mock resources used in the tests belong.
namespace = "foo"
// prefixConfigMap1 is the prefix used in ".envFrom" fields that reference "config-map-1".
prefixConfigMap1 = "FROM_CONFIGMAP_1_"
// prefixConfigMap2 is the prefix used in ".envFrom" fields that reference "config-map-2".
prefixConfigMap2 = "FROM_CONFIGMAP_2_"
// prefixSecret1 is the prefix used in ".envFrom" fields that reference "secret-1".
prefixSecret1 = "FROM_SECRET_1_"
// prefixSecret1 is the prefix used in ".envFrom" fields that reference "secret-1".
prefixSecret2 = "FROM_SECRET_2_"
)
var (
// bFalse represents the "false" value.
// Used so we can take its address when a pointer to a bool is required.
bFalse = false
// bFalse represents the "true" value.
// Used so we can take its address when a pointer to a bool is required.
bTrue = true
// configMap1 is a configmap containing a single key, valid as the name of an environment variable.
configMap1 = testutil.FakeConfigMap(namespace, "configmap-1", map[string]string{
keyFoo: "__foo__",
})
// configMap2 is a configmap containing a single key, valid as the name of an environment variable.
configMap2 = testutil.FakeConfigMap(namespace, "configmap-2", map[string]string{
keyBar: "__bar__",
})
// configMap3 is a configmap containing a single key, valid as the name of an environment variable.
configMap3 = testutil.FakeConfigMap(namespace, "configmap-2", map[string]string{
keyFoo: "__foo__",
keyBar: "__bar__",
})
// invalidConfigMap1 is a configmap containing two keys, one of which is invalid as the name of an environment variable.
invalidConfigMap1 = testutil.FakeConfigMap(namespace, "invalid-configmap-1", map[string]string{
keyFoo: "__foo__",
invalidKey1: "will-be-skipped",
invalidKey2: "will-be-skipped",
})
// secret1 is a secret containing a single key, valid as the name of an environment variable.
secret1 = testutil.FakeSecret(namespace, "secret-1", map[string]string{
keyBaz: "__baz__",
})
// secret2 is a secret containing a single key, valid as the name of an environment variable.
secret2 = testutil.FakeSecret(namespace, "secret-2", map[string]string{
keyFoo: "__foo__",
})
// invalidSecret1 is a secret containing two keys, one of which is invalid as the name for an environment variable.
invalidSecret1 = testutil.FakeSecret(namespace, "invalid-secret-1", map[string]string{
invalidKey3: "will-be-skipped",
keyBaz: "__baz__",
})
)
// TestPopulatePodWithInitContainersUsingEnv populates the environment of a pod with four containers (two init containers, two containers) using ".env".
// Then, it checks that the resulting environment for each container contains the expected environment variables.
func TestPopulatePodWithInitContainersUsingEnv(t *testing.T) {
rm := testutil.FakeResourceManager(configMap1, configMap2, secret1, secret2)
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
// Create a pod object having two init containers and two containers.
// The containers' environment is to be populated both from directly-provided values and from keys in two configmaps and two secrets.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: configMap1.Name,
},
Key: keyFoo,
// This scenario has been observed before https://github.com/virtual-kubelet/virtual-kubelet/issues/444#issuecomment-449611851.
Optional: nil,
},
},
},
},
},
{
Env: []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secret1.Name,
},
Key: keyBaz,
Optional: &bFalse,
},
},
},
},
},
},
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: configMap1.Name,
},
Key: keyFoo,
// This scenario has been observed before https://github.com/virtual-kubelet/virtual-kubelet/issues/444#issuecomment-449611851.
Optional: nil,
},
},
},
},
},
{
Env: []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: secret1.Name,
},
Key: keyBaz,
Optional: &bFalse,
},
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pod's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, err)
// Make sure that all the containers' environments contain all the expected keys and values.
assert.Check(t, is.DeepEqual(pod.Spec.InitContainers[0].Env, []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
Value: configMap1.Data[keyFoo],
},
}, sortOpt))
assert.Check(t, is.DeepEqual(pod.Spec.InitContainers[1].Env, []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
Value: string(secret1.Data[keyBaz]),
},
}, sortOpt))
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
Value: configMap1.Data[keyFoo],
},
}, sortOpt))
assert.Check(t, is.DeepEqual(pod.Spec.Containers[1].Env, []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
Value: string(secret1.Data[keyBaz]),
},
}, sortOpt))
}
// TestPopulatePodWithInitContainersUsingEnv populates the environment of a pod with four containers (two init containers, two containers) using ".env".
// Then, it checks that the resulting environment for each container contains the expected environment variables.
func TestPopulatePodWithInitContainersUsingEnvWithFieldRef(t *testing.T) {
rm := testutil.FakeResourceManager(configMap1, configMap2, secret1, secret2)
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
// Create a pod object having two init containers and two containers.
// The containers' environment is to be populated both from directly-provided values and from keys in two configmaps and two secrets.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
Labels: map[string]string{
"zone": "us-est-coast",
"cluster": "test-cluster1",
"rack": "rack-22",
},
Annotations: map[string]string{
"build": "two",
"builder": "john-doe",
},
},
Spec: corev1.PodSpec{
NodeName: "namenode",
ServiceAccountName: "serviceaccount",
InitContainers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: envVarName1,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "spec.nodeName",
},
},
},
{
Name: envVarName2,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.labels",
},
},
},
{
Name: envVarName3,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.annotations",
},
},
},
{
Name: envVarName4,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "spec.serviceAccountName",
},
},
},
},
},
},
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: envVarName1,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "spec.nodeName",
},
},
},
{
Name: envVarName2,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.labels",
},
},
},
{
Name: envVarName3,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.annotations",
},
},
},
{
Name: envVarName4,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "spec.serviceAccountName",
},
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pod's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.NilError(t, err)
// Make sure that all the containers' environments contain all the expected keys and values.
assert.Check(t, is.DeepEqual(pod.Spec.InitContainers[0].Env, []corev1.EnvVar{
{
Name: envVarName1,
Value: "namenode",
},
{
Name: envVarName2,
Value: "cluster=\"test-cluster1\"\nrack=\"rack-22\"\nzone=\"us-est-coast\"",
},
{
Name: envVarName3,
Value: "build=\"two\"\nbuilder=\"john-doe\"",
},
{
Name: envVarName4,
Value: "serviceaccount",
},
}, sortOpt))
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: envVarName1,
Value: "namenode",
},
{
Name: envVarName2,
Value: "cluster=\"test-cluster1\"\nrack=\"rack-22\"\nzone=\"us-est-coast\"",
},
{
Name: envVarName3,
Value: "build=\"two\"\nbuilder=\"john-doe\"",
},
{
Name: envVarName4,
Value: "serviceaccount",
},
}, sortOpt))
}
// TestPopulatePodWithInitContainersUsingEnvFrom populates the environment of a pod with four containers (two init containers, two containers) using ".envFrom".
// Then, it checks that the resulting environment for each container contains the expected environment variables.
func TestPopulatePodWithInitContainersUsingEnvFrom(t *testing.T) {
rm := testutil.FakeResourceManager(configMap1, configMap2, secret1, secret2)
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
// Create a pod object having two init containers and two containers.
// The containers' environment is to be populated from two configmaps and two secrets.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: prefixConfigMap1,
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: configMap1.Name,
},
},
},
},
},
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: prefixConfigMap2,
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: configMap2.Name,
},
},
},
},
},
},
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: prefixSecret1,
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: secret1.Name,
},
},
},
},
},
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: prefixSecret2,
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: secret2.Name,
},
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pod's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, err)
// Make sure that all the containers' environments contain all the expected keys and values.
assert.Check(t, is.DeepEqual(pod.Spec.InitContainers[0].Env, []corev1.EnvVar{
{
Name: prefixConfigMap1 + keyFoo,
Value: configMap1.Data[keyFoo],
},
}, sortOpt))
assert.Check(t, is.DeepEqual(pod.Spec.InitContainers[1].Env, []corev1.EnvVar{
{
Name: prefixConfigMap2 + keyBar,
Value: configMap2.Data[keyBar],
},
}, sortOpt))
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: prefixSecret1 + keyBaz,
Value: string(secret1.Data[keyBaz]),
},
}, sortOpt))
assert.Check(t, is.DeepEqual(pod.Spec.Containers[1].Env, []corev1.EnvVar{
{
Name: prefixSecret2 + keyFoo,
Value: string(secret2.Data[keyFoo]),
},
}, sortOpt))
}
// TestEnvFromTwoConfigMapsAndOneSecret populates the environment of a container from two configmaps and one secret.
// Then, it checks that the resulting environment contains all the expected environment variables and values.
func TestEnvFromTwoConfigMapsAndOneSecret(t *testing.T) {
rm := testutil.FakeResourceManager(configMap1, configMap2, secret1)
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
// Create a pod object having a single container.
// The container's environment is to be populated from two configmaps and one secret.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
Prefix: prefixConfigMap1,
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: configMap1.Name,
},
},
},
{
Prefix: prefixConfigMap2,
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: configMap2.Name,
},
},
},
{
Prefix: prefixSecret1,
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: secret1.Name,
},
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the container's environment.
err := populateContainerEnvironment(context.Background(), pod, &pod.Spec.Containers[0], rm, er)
assert.Check(t, err)
// Make sure that the container's environment contains all the expected keys and values.
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: prefixConfigMap1 + keyFoo,
Value: configMap1.Data[keyFoo],
},
{
Name: prefixConfigMap2 + keyBar,
Value: configMap2.Data[keyBar],
},
{
Name: prefixSecret1 + keyBaz,
Value: string(secret1.Data[keyBaz]),
},
}, sortOpt))
// Make sure that no events have been recorded, as the configmaps and secrets are valid.
assert.Check(t, is.Len(er.Events, 0))
}
// TestEnvFromConfigMapAndSecretWithInvalidKeys populates the environment of a container from a configmap and a secret containing invalid keys.
// Then, it checks that the resulting environment contains all the expected environment variables and values, and that the invalid keys have been skipped.
func TestEnvFromConfigMapAndSecretWithInvalidKeys(t *testing.T) {
rm := testutil.FakeResourceManager(invalidConfigMap1, invalidSecret1)
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
// Create a pod object having a single container.
// The container's environment is to be populated from a configmap and a secret, both of which have some invalid keys.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: invalidConfigMap1.Name,
},
},
},
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: invalidSecret1.Name,
},
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pods's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, err)
// Make sure that the container's environment has two variables (corresponding to the single valid key in both the configmap and the secret).
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: keyFoo,
Value: invalidConfigMap1.Data[keyFoo],
},
{
Name: keyBaz,
Value: string(invalidSecret1.Data[keyBaz]),
},
}, sortOpt))
// Make sure that two events have been received (one for the configmap and one for the secret).
assert.Check(t, is.Len(er.Events, 2))
// Grab the first event (which should correspond to the configmap) and make sure it has the correct reason and message.
event1 := <-er.Events
assert.Check(t, is.Contains(event1, ReasonInvalidEnvironmentVariableNames))
assert.Check(t, is.Contains(event1, invalidKey1))
assert.Check(t, is.Contains(event1, invalidKey2))
// Grab the second event (which should correspond to the secret) and make sure it has the correct reason and message.
event2 := <-er.Events
assert.Check(t, is.Contains(event2, ReasonInvalidEnvironmentVariableNames))
assert.Check(t, is.Contains(event2, invalidKey3))
}
// TestEnvOverridesEnvFrom populates the environment of a container from a configmap, and from another configmap's key with a "conflicting" key.
// Then, it checks that the value of the "conflicting" key has been correctly overridden.
func TestEnvOverridesEnvFrom(t *testing.T) {
rm := testutil.FakeResourceManager(configMap3)
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
// override will override the value of "keyFoo" from "configMap3".
override := "__override__"
// Create a pod object having a single container.
// The container's environment is to be populated from a configmap, and later overridden with a value provided directly.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: configMap3.Name,
},
},
},
},
Env: []corev1.EnvVar{
{
Name: keyFoo, // One of the keys in configMap3.
Value: override,
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pods's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, err)
// Make sure that the container's environment contains all the expected keys and values.
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: keyFoo,
Value: override,
},
{
Name: keyBar,
Value: configMap3.Data[keyBar],
},
},
sortOpt,
))
// Make sure that no events have been recorded, as the configmaps and secrets are valid.
assert.Check(t, is.Len(er.Events, 0))
}
var sortOpt gocmp.Option = gocmp.Transformer("Sort", sortEnv)
func sortEnv(in []corev1.EnvVar) []corev1.EnvVar {
out := append([]corev1.EnvVar(nil), in...)
sort.Slice(out, func(i, j int) bool {
return out[i].Name < out[j].Name
})
return out
}
// TestEnvFromInexistentConfigMaps populates the environment of a container from two configmaps (one of them optional) that do not exist.
// Then, it checks that the expected events have been recorded.
func TestEnvFromInexistentConfigMaps(t *testing.T) {
rm := testutil.FakeResourceManager()
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
missingConfigMap1Name := "missing-config-map-1"
missingConfigMap2Name := "missing-config-map-2"
// Create a pod object having a single container.
// The container's environment is to be populated from two configmaps that do not exist.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: missingConfigMap1Name,
},
// The configmap reference is optional.
Optional: &bTrue,
},
},
{
ConfigMapRef: &corev1.ConfigMapEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: missingConfigMap2Name,
},
// The configmap reference is mandatory.
Optional: &bFalse,
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pods's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, is.ErrorContains(err, ""))
// Make sure that two events have been recorded with the correct reason and message.
assert.Check(t, is.Len(er.Events, 2))
event1 := <-er.Events
assert.Check(t, is.Contains(event1, ReasonOptionalConfigMapNotFound))
assert.Check(t, is.Contains(event1, missingConfigMap1Name))
event2 := <-er.Events
assert.Check(t, is.Contains(event2, ReasonMandatoryConfigMapNotFound))
assert.Check(t, is.Contains(event2, missingConfigMap2Name))
}
func TestEnvFromInexistentSecrets(t *testing.T) {
rm := testutil.FakeResourceManager()
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
missingSecret1Name := "missing-secret-1"
missingSecret2Name := "missing-secret-2"
// Create a pod object having a single container.
// The container's environment is to be populated from two secrets that do not exist.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
EnvFrom: []corev1.EnvFromSource{
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: missingSecret1Name,
},
// The secret reference is optional.
Optional: &bTrue,
},
},
{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: missingSecret2Name,
},
// The secret reference is mandatory.
Optional: &bFalse,
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pods's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, is.ErrorContains(err, ""))
// Make sure that two events have been recorded with the correct reason and message.
assert.Check(t, is.Len(er.Events, 2))
event1 := <-er.Events
assert.Check(t, is.Contains(event1, ReasonOptionalSecretNotFound))
assert.Check(t, is.Contains(event1, missingSecret1Name))
event2 := <-er.Events
assert.Check(t, is.Contains(event2, ReasonMandatorySecretNotFound))
assert.Check(t, is.Contains(event2, missingSecret2Name))
}
// TestEnvReferencingInexistentConfigMapKey tries populates the environment of a container using a keys from a configmaps that does not exist.
// Then, it checks that the expected event has been recorded.
func TestEnvReferencingInexistentConfigMapKey(t *testing.T) {
rm := testutil.FakeResourceManager()
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
missingConfigMapName := "missing-config-map"
// Create a pod object having a single container and referencing a key from a configmap that does not exist.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: "envvar",
ValueFrom: &corev1.EnvVarSource{
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: missingConfigMapName,
},
Key: "key",
// This scenario has been observed before https://github.com/virtual-kubelet/virtual-kubelet/issues/444#issuecomment-449611851.
// A nil value of optional means "mandatory", hence we should expect "PopulateEnvironmentVariables" to return an error.
Optional: nil,
},
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pods's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, is.ErrorContains(err, ""))
// Make sure that two events have been recorded with the correct reason and message.
assert.Check(t, is.Len(er.Events, 1))
event1 := <-er.Events
assert.Check(t, is.Contains(event1, ReasonMandatoryConfigMapNotFound))
assert.Check(t, is.Contains(event1, missingConfigMapName))
}
// TestEnvReferencingInexistentSecretKey tries populates the environment of a container using a keys from a secret that does not exist.
// Then, it checks that the expected event has been recorded.
func TestEnvReferencingInexistentSecretKey(t *testing.T) {
rm := testutil.FakeResourceManager()
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
missingSecretName := "missing-secret"
// Create a pod object having a single container and referencing a key from a secret that does not exist.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: "envvar",
ValueFrom: &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: missingSecretName,
},
Key: "key",
// This scenario has been observed before https://github.com/virtual-kubelet/virtual-kubelet/issues/444#issuecomment-449611851.
// A nil value of optional means "mandatory", hence we should expect "PopulateEnvironmentVariables" to return an error.
Optional: nil,
},
},
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pods's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, is.ErrorContains(err, ""))
// Make sure that two events have been recorded with the correct reason and message.
assert.Check(t, is.Len(er.Events, 1))
event1 := <-er.Events
assert.Check(t, is.Contains(event1, ReasonMandatorySecretNotFound))
assert.Check(t, is.Contains(event1, missingSecretName))
}
// TestServiceEnvVar tries to populate the environment of a container using services with ServiceLinks enabled and disabled.
func TestServiceEnvVar(t *testing.T) {
namespace2 := "namespace-02"
service1 := testutil.FakeService(metav1.NamespaceDefault, "kubernetes", "1.2.3.1", "TCP", 8081)
service2 := testutil.FakeService(namespace, "test", "1.2.3.3", "TCP", 8083)
// unused svc to show it isn't populated within a different namespace.
service3 := testutil.FakeService(namespace2, "unused", "1.2.3.4", "TCP", 8084)
rm := testutil.FakeResourceManager(service1, service2, service3)
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "test-pod-name",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{Name: envVarName1, Value: envVarValue1},
},
},
},
},
}
testCases := []struct {
name string // the name of the test case
enableServiceLinks *bool // enabling service links
expectedEnvs []corev1.EnvVar // a set of expected environment vars
}{
{
name: "ServiceLinks disabled",
enableServiceLinks: &bFalse,
expectedEnvs: []corev1.EnvVar{
{Name: envVarName1, Value: envVarValue1},
{Name: "KUBERNETES_SERVICE_PORT", Value: "8081"},
{Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.1"},
{Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.1:8081"},
{Name: "KUBERNETES_PORT_8081_TCP", Value: "tcp://1.2.3.1:8081"},
{Name: "KUBERNETES_PORT_8081_TCP_PROTO", Value: "tcp"},
{Name: "KUBERNETES_PORT_8081_TCP_PORT", Value: "8081"},
{Name: "KUBERNETES_PORT_8081_TCP_ADDR", Value: "1.2.3.1"},
},
},
{
name: "ServiceLinks enabled",
enableServiceLinks: &bTrue,
expectedEnvs: []corev1.EnvVar{
{Name: envVarName1, Value: envVarValue1},
{Name: "TEST_SERVICE_HOST", Value: "1.2.3.3"},
{Name: "TEST_SERVICE_PORT", Value: "8083"},
{Name: "TEST_PORT", Value: "tcp://1.2.3.3:8083"},
{Name: "TEST_PORT_8083_TCP", Value: "tcp://1.2.3.3:8083"},
{Name: "TEST_PORT_8083_TCP_PROTO", Value: "tcp"},
{Name: "TEST_PORT_8083_TCP_PORT", Value: "8083"},
{Name: "TEST_PORT_8083_TCP_ADDR", Value: "1.2.3.3"},
{Name: "KUBERNETES_SERVICE_PORT", Value: "8081"},
{Name: "KUBERNETES_SERVICE_HOST", Value: "1.2.3.1"},
{Name: "KUBERNETES_PORT", Value: "tcp://1.2.3.1:8081"},
{Name: "KUBERNETES_PORT_8081_TCP", Value: "tcp://1.2.3.1:8081"},
{Name: "KUBERNETES_PORT_8081_TCP_PROTO", Value: "tcp"},
{Name: "KUBERNETES_PORT_8081_TCP_PORT", Value: "8081"},
{Name: "KUBERNETES_PORT_8081_TCP_ADDR", Value: "1.2.3.1"},
},
},
}
for _, tc := range testCases {
pod.Spec.EnableServiceLinks = tc.enableServiceLinks
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.NilError(t, err, "[%s]", tc.name)
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, tc.expectedEnvs, sortOpt))
}
}
// TestComposingEnv tests that env var can be composed from the existing env vars.
func TestComposingEnv(t *testing.T) {
rm := testutil.FakeResourceManager()
er := testutil.FakeEventRecorder(defaultEventRecorderBufferSize)
// Create a pod object having a single container.
// The container's third environment variable is composed of the previous two.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: "pod-0",
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Env: []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
Value: envVarValue2,
},
{
Name: envVarName12,
Value: "$(" + envVarName1 + ")$(" + envVarName2 + ")", // "$(envVarName1)$(envVarName2)"
},
},
},
},
EnableServiceLinks: &bFalse,
},
}
// Populate the pods's environment.
err := PopulateEnvironmentVariables(context.Background(), pod, rm, er)
assert.Check(t, err)
// Make sure that the container's environment contains all the expected keys and values.
assert.Check(t, is.DeepEqual(pod.Spec.Containers[0].Env, []corev1.EnvVar{
{
Name: envVarName1,
Value: envVarValue1,
},
{
Name: envVarName2,
Value: envVarValue2,
},
{
Name: envVarName12,
Value: envVarValue1 + envVarValue2,
},
},
sortOpt,
))
// Make sure that no events have been recorded.
assert.Check(t, is.Len(er.Events, 0))
}