1
16
17 package node
18
19 import (
20 "context"
21
22 v1 "k8s.io/api/core/v1"
23 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
24 "k8s.io/apimachinery/pkg/util/uuid"
25 "k8s.io/kubernetes/test/e2e/feature"
26 "k8s.io/kubernetes/test/e2e/framework"
27 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
28 e2epodoutput "k8s.io/kubernetes/test/e2e/framework/pod/output"
29 imageutils "k8s.io/kubernetes/test/utils/image"
30 admissionapi "k8s.io/pod-security-admission/api"
31
32 "github.com/onsi/ginkgo/v2"
33 "github.com/onsi/gomega"
34 )
35
36
37
38
39 var _ = SIGDescribe("Variable Expansion", func() {
40 f := framework.NewDefaultFramework("var-expansion")
41 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
42
43
48 framework.ConformanceIt("should allow composing env vars into new env vars", f.WithNodeConformance(), func(ctx context.Context) {
49 envVars := []v1.EnvVar{
50 {
51 Name: "FOO",
52 Value: "foo-value",
53 },
54 {
55 Name: "BAR",
56 Value: "bar-value",
57 },
58 {
59 Name: "FOOBAR",
60 Value: "$(FOO);;$(BAR)",
61 },
62 }
63 pod := newPod([]string{"sh", "-c", "env"}, envVars, nil, nil)
64
65 e2epodoutput.TestContainerOutput(ctx, f, "env composition", pod, 0, []string{
66 "FOO=foo-value",
67 "BAR=bar-value",
68 "FOOBAR=foo-value;;bar-value",
69 })
70 })
71
72
77 framework.ConformanceIt("should allow substituting values in a container's command", f.WithNodeConformance(), func(ctx context.Context) {
78 envVars := []v1.EnvVar{
79 {
80 Name: "TEST_VAR",
81 Value: "test-value",
82 },
83 }
84 pod := newPod([]string{"sh", "-c", "TEST_VAR=wrong echo \"$(TEST_VAR)\""}, envVars, nil, nil)
85
86 e2epodoutput.TestContainerOutput(ctx, f, "substitution in container's command", pod, 0, []string{
87 "test-value",
88 })
89 })
90
91
96 framework.ConformanceIt("should allow substituting values in a container's args", f.WithNodeConformance(), func(ctx context.Context) {
97 envVars := []v1.EnvVar{
98 {
99 Name: "TEST_VAR",
100 Value: "test-value",
101 },
102 }
103 pod := newPod([]string{"sh", "-c"}, envVars, nil, nil)
104 pod.Spec.Containers[0].Args = []string{"TEST_VAR=wrong echo \"$(TEST_VAR)\""}
105
106 e2epodoutput.TestContainerOutput(ctx, f, "substitution in container's args", pod, 0, []string{
107 "test-value",
108 })
109 })
110
111
116 framework.ConformanceIt("should allow substituting values in a volume subpath", func(ctx context.Context) {
117 envVars := []v1.EnvVar{
118 {
119 Name: "POD_NAME",
120 Value: "foo",
121 },
122 }
123 mounts := []v1.VolumeMount{
124 {
125 Name: "workdir1",
126 MountPath: "/logscontainer",
127 SubPathExpr: "$(POD_NAME)",
128 },
129 {
130 Name: "workdir1",
131 MountPath: "/testcontainer",
132 },
133 }
134 volumes := []v1.Volume{
135 {
136 Name: "workdir1",
137 VolumeSource: v1.VolumeSource{
138 EmptyDir: &v1.EmptyDirVolumeSource{},
139 },
140 },
141 }
142 pod := newPod([]string{}, envVars, mounts, volumes)
143 envVars[0].Value = pod.ObjectMeta.Name
144 pod.Spec.Containers[0].Command = []string{"sh", "-c", "test -d /testcontainer/" + pod.ObjectMeta.Name + ";echo $?"}
145
146 e2epodoutput.TestContainerOutput(ctx, f, "substitution in volume subpath", pod, 0, []string{
147 "0",
148 })
149 })
150
151
156 framework.ConformanceIt("should fail substituting values in a volume subpath with backticks", f.WithSlow(), func(ctx context.Context) {
157
158 envVars := []v1.EnvVar{
159 {
160 Name: "POD_NAME",
161 Value: "..",
162 },
163 }
164 mounts := []v1.VolumeMount{
165 {
166 Name: "workdir1",
167 MountPath: "/logscontainer",
168 SubPathExpr: "$(POD_NAME)",
169 },
170 }
171 volumes := []v1.Volume{
172 {
173 Name: "workdir1",
174 VolumeSource: v1.VolumeSource{
175 EmptyDir: &v1.EmptyDirVolumeSource{},
176 },
177 },
178 }
179 pod := newPod(nil, envVars, mounts, volumes)
180
181
182 testPodFailSubpath(ctx, f, pod)
183 })
184
185
190 framework.ConformanceIt("should fail substituting values in a volume subpath with absolute path", f.WithSlow(), func(ctx context.Context) {
191 absolutePath := "/tmp"
192 if framework.NodeOSDistroIs("windows") {
193
194 absolutePath = "C:\\Users"
195 }
196
197 envVars := []v1.EnvVar{
198 {
199 Name: "POD_NAME",
200 Value: absolutePath,
201 },
202 }
203 mounts := []v1.VolumeMount{
204 {
205 Name: "workdir1",
206 MountPath: "/logscontainer",
207 SubPathExpr: "$(POD_NAME)",
208 },
209 }
210 volumes := []v1.Volume{
211 {
212 Name: "workdir1",
213 VolumeSource: v1.VolumeSource{
214 EmptyDir: &v1.EmptyDirVolumeSource{},
215 },
216 },
217 }
218 pod := newPod(nil, envVars, mounts, volumes)
219
220
221 testPodFailSubpath(ctx, f, pod)
222 })
223
224
229 framework.ConformanceIt("should verify that a failing subpath expansion can be modified during the lifecycle of a container", f.WithSlow(), func(ctx context.Context) {
230
231 envVars := []v1.EnvVar{
232 {
233 Name: "POD_NAME",
234 Value: "foo",
235 },
236 {
237 Name: "ANNOTATION",
238 ValueFrom: &v1.EnvVarSource{
239 FieldRef: &v1.ObjectFieldSelector{
240 APIVersion: "v1",
241 FieldPath: "metadata.annotations['mysubpath']",
242 },
243 },
244 },
245 }
246 mounts := []v1.VolumeMount{
247 {
248 Name: "workdir1",
249 MountPath: "/subpath_mount",
250 SubPathExpr: "$(ANNOTATION)/$(POD_NAME)",
251 },
252 {
253 Name: "workdir1",
254 MountPath: "/volume_mount",
255 },
256 }
257 volumes := []v1.Volume{
258 {
259 Name: "workdir1",
260 VolumeSource: v1.VolumeSource{
261 EmptyDir: &v1.EmptyDirVolumeSource{},
262 },
263 },
264 }
265 pod := newPod([]string{"sh", "-c", "tail -f /dev/null"}, envVars, mounts, volumes)
266 pod.ObjectMeta.Annotations = map[string]string{"notmysubpath": "mypath"}
267
268 ginkgo.By("creating the pod with failed condition")
269 podClient := e2epod.NewPodClient(f)
270 pod = podClient.Create(ctx, pod)
271
272 getPod := e2epod.Get(f.ClientSet, pod)
273 gomega.Consistently(ctx, getPod).WithTimeout(framework.PodStartShortTimeout).Should(e2epod.BeInPhase(v1.PodPending))
274
275 ginkgo.By("updating the pod")
276 podClient.Update(ctx, pod.ObjectMeta.Name, func(pod *v1.Pod) {
277 if pod.ObjectMeta.Annotations == nil {
278 pod.ObjectMeta.Annotations = make(map[string]string)
279 }
280 pod.ObjectMeta.Annotations["mysubpath"] = "mypath"
281 })
282
283 ginkgo.By("waiting for pod running")
284 err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, framework.PodStartShortTimeout)
285 framework.ExpectNoError(err, "while waiting for pod to be running")
286
287 ginkgo.By("deleting the pod gracefully")
288 err = e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)
289 framework.ExpectNoError(err, "failed to delete pod")
290 })
291
292
301 framework.ConformanceIt("should succeed in writing subpaths in container", f.WithSlow(), func(ctx context.Context) {
302
303 envVars := []v1.EnvVar{
304 {
305 Name: "POD_NAME",
306 Value: "foo",
307 },
308 {
309 Name: "ANNOTATION",
310 ValueFrom: &v1.EnvVarSource{
311 FieldRef: &v1.ObjectFieldSelector{
312 APIVersion: "v1",
313 FieldPath: "metadata.annotations['mysubpath']",
314 },
315 },
316 },
317 }
318 mounts := []v1.VolumeMount{
319 {
320 Name: "workdir1",
321 MountPath: "/subpath_mount",
322 SubPathExpr: "$(ANNOTATION)/$(POD_NAME)",
323 },
324 {
325 Name: "workdir1",
326 MountPath: "/volume_mount",
327 },
328 }
329 volumes := []v1.Volume{
330 {
331 Name: "workdir1",
332 VolumeSource: v1.VolumeSource{
333 EmptyDir: &v1.EmptyDirVolumeSource{},
334 },
335 },
336 }
337 pod := newPod([]string{"sh", "-c", "tail -f /dev/null"}, envVars, mounts, volumes)
338 pod.ObjectMeta.Annotations = map[string]string{"mysubpath": "mypath"}
339
340 ginkgo.By("creating the pod")
341 podClient := e2epod.NewPodClient(f)
342 pod = podClient.Create(ctx, pod)
343
344 ginkgo.By("waiting for pod running")
345 err := e2epod.WaitTimeoutForPodRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, framework.PodStartShortTimeout)
346 framework.ExpectNoError(err, "while waiting for pod to be running")
347
348 ginkgo.By("creating a file in subpath")
349 cmd := "touch /volume_mount/mypath/foo/test.log"
350 _, _, err = e2epod.ExecShellInPodWithFullOutput(ctx, f, pod.Name, cmd)
351 if err != nil {
352 framework.Failf("expected to be able to write to subpath")
353 }
354
355 ginkgo.By("test for file in mounted path")
356 cmd = "test -f /subpath_mount/test.log"
357 _, _, err = e2epod.ExecShellInPodWithFullOutput(ctx, f, pod.Name, cmd)
358 if err != nil {
359 framework.Failf("expected to be able to verify file")
360 }
361
362 ginkgo.By("updating the annotation value")
363 podClient.Update(ctx, pod.ObjectMeta.Name, func(pod *v1.Pod) {
364 pod.ObjectMeta.Annotations["mysubpath"] = "mynewpath"
365 })
366
367 ginkgo.By("waiting for annotated pod running")
368 err = e2epod.WaitTimeoutForPodRunningInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace, framework.PodStartShortTimeout)
369 framework.ExpectNoError(err, "while waiting for annotated pod to be running")
370
371 ginkgo.By("deleting the pod gracefully")
372 err = e2epod.DeletePodWithWait(ctx, f.ClientSet, pod)
373 framework.ExpectNoError(err, "failed to delete pod")
374 })
375
376
382 framework.It("allow almost all printable ASCII characters as environment variable names", feature.RelaxedEnvironmentVariableValidation, func(ctx context.Context) {
383 envVars := []v1.EnvVar{
384 {
385 Name: "!\"#$%&'()",
386 Value: "value-1",
387 },
388 {
389 Name: "* +,-./0123456789",
390 Value: "value-2",
391 },
392 {
393 Name: ":;<>?@",
394 Value: "value-3",
395 },
396 {
397 Name: "[\\]^_`{}|~",
398 Value: "value-4",
399 },
400 }
401 pod := newPod([]string{"sh", "-c", "env"}, envVars, nil, nil)
402
403 e2epodoutput.TestContainerOutput(ctx, f, "env composition", pod, 0, []string{
404 "!\"#$%&'()=value-1",
405 "* +,-./0123456789=value-2",
406 ":;<>?@=value-3",
407 "[\\]^_`{}|~=value-4",
408 })
409 })
410
411 })
412
413 func testPodFailSubpath(ctx context.Context, f *framework.Framework, pod *v1.Pod) {
414 podClient := e2epod.NewPodClient(f)
415 pod = podClient.Create(ctx, pod)
416
417 ginkgo.DeferCleanup(e2epod.DeletePodWithWait, f.ClientSet, pod)
418
419 err := e2epod.WaitForPodContainerToFail(ctx, f.ClientSet, pod.Namespace, pod.Name, 0, "CreateContainerConfigError", framework.PodStartShortTimeout)
420 framework.ExpectNoError(err, "while waiting for the pod container to fail")
421 }
422
423 func newPod(command []string, envVars []v1.EnvVar, mounts []v1.VolumeMount, volumes []v1.Volume) *v1.Pod {
424 podName := "var-expansion-" + string(uuid.NewUUID())
425 return &v1.Pod{
426 ObjectMeta: metav1.ObjectMeta{
427 Name: podName,
428 Labels: map[string]string{"name": podName},
429 },
430 Spec: v1.PodSpec{
431 Containers: []v1.Container{newContainer("dapi-container", command, envVars, mounts)},
432 RestartPolicy: v1.RestartPolicyNever,
433 Volumes: volumes,
434 },
435 }
436 }
437
438 func newContainer(containerName string, command []string, envVars []v1.EnvVar, mounts []v1.VolumeMount) v1.Container {
439 return v1.Container{
440 Name: containerName,
441 Image: imageutils.GetE2EImage(imageutils.BusyBox),
442 Command: command,
443 Env: envVars,
444 VolumeMounts: mounts,
445 }
446 }
447
View as plain text