1 package testutil
2
3 import (
4 "fmt"
5 "strconv"
6 "strings"
7
8 v1 "k8s.io/api/core/v1"
9 "k8s.io/apimachinery/pkg/api/resource"
10
11 "github.com/linkerd/linkerd2/pkg/k8s"
12 )
13
14 const enabled = "true"
15
16
17
18
19 type InjectValidator struct {
20 NoInitContainer bool
21 AutoInject bool
22 AdminPort int
23 ControlPort int
24 EnableDebug bool
25 EnableExternalProfiles bool
26 ImagePullPolicy string
27 InboundPort int
28 InitImage string
29 InitImageVersion string
30 OutboundPort int
31 CPULimit string
32 EphemeralStorageLimit string
33 CPURequest string
34 MemoryLimit string
35 MemoryRequest string
36 EphemeralStorageRequest string
37 Image string
38 LogLevel string
39 LogFormat string
40 UID int
41 GID int
42 Version string
43 RequireIdentityOnPorts string
44 SkipOutboundPorts string
45 OpaquePorts string
46 SkipInboundPorts string
47 OutboundConnectTimeout string
48 InboundConnectTimeout string
49 WaitBeforeExitSeconds int
50 SkipSubnets string
51 ShutdownGracePeriod string
52 }
53
54 func (iv *InjectValidator) getContainer(pod *v1.PodSpec, name string, isInit bool) *v1.Container {
55 containers := pod.Containers
56 if isInit {
57 containers = pod.InitContainers
58 }
59 for _, container := range containers {
60 if container.Name == name {
61 return &container
62 }
63 }
64 return nil
65 }
66
67 func (iv *InjectValidator) validateEnvVar(container *v1.Container, envName, expectedValue string) error {
68 for _, env := range container.Env {
69 if env.Name == envName {
70 if env.Value == expectedValue {
71 return nil
72 }
73 return fmt.Errorf("env: %s, expected: %s, actual %s", envName, expectedValue, env.Value)
74 }
75
76 }
77 return fmt.Errorf("cannot find env: %s", envName)
78 }
79
80 func (iv *InjectValidator) validatePort(container *v1.Container, portName string, expectedValue int) error {
81 for _, port := range container.Ports {
82 if port.Name == portName {
83 if port.ContainerPort == int32(expectedValue) {
84 return nil
85 }
86 return fmt.Errorf("port: %s, expected: %d, actual %d", portName, expectedValue, port.ContainerPort)
87 }
88
89 }
90 return fmt.Errorf("cannot find port: %s", portName)
91 }
92
93 func (iv *InjectValidator) validateDebugContainer(pod *v1.PodSpec) error {
94 if iv.EnableDebug {
95 proxyContainer := iv.getContainer(pod, k8s.DebugContainerName, false)
96 if proxyContainer == nil {
97 return fmt.Errorf("container %s missing", k8s.DebugContainerName)
98 }
99 }
100 return nil
101 }
102
103 func (iv *InjectValidator) validateProxyContainer(pod *v1.PodSpec) error {
104 proxyContainer := iv.getContainer(pod, k8s.ProxyContainerName, false)
105 if proxyContainer == nil {
106 return fmt.Errorf("container %s missing", k8s.ProxyContainerName)
107 }
108
109 if iv.AdminPort != 0 {
110 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_ADMIN_LISTEN_ADDR", fmt.Sprintf("[::]:%d", iv.AdminPort)); err != nil {
111 return err
112 }
113 if proxyContainer.LivenessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {
114 return fmt.Errorf("livenessProbe: expected: %d, actual %d", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)
115 }
116 if proxyContainer.ReadinessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {
117 return fmt.Errorf("readinessProbe: expected: %d, actual %d", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)
118 }
119
120 if err := iv.validatePort(proxyContainer, k8s.ProxyAdminPortName, iv.AdminPort); err != nil {
121 return err
122 }
123 }
124
125 if iv.ControlPort != 0 {
126 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_CONTROL_LISTEN_ADDR", fmt.Sprintf("[::]:%d", iv.ControlPort)); err != nil {
127 return err
128 }
129 }
130
131 if iv.EnableExternalProfiles {
132 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_DESTINATION_PROFILE_SUFFIXES", "."); err != nil {
133 return err
134 }
135 }
136
137 if iv.ImagePullPolicy != "" {
138 if string(proxyContainer.ImagePullPolicy) != iv.ImagePullPolicy {
139 return fmt.Errorf("pullPolicy: expected: %s, actual %s", iv.ImagePullPolicy, string(proxyContainer.ImagePullPolicy))
140 }
141 }
142
143 if iv.InboundPort != 0 {
144 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_INBOUND_LISTEN_ADDR", fmt.Sprintf("[::]:%d", iv.InboundPort)); err != nil {
145 return err
146 }
147 if proxyContainer.LivenessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {
148 return fmt.Errorf("livenessProbe: expected: %d, actual %d", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)
149 }
150 if proxyContainer.ReadinessProbe.HTTPGet.Port.IntVal != int32(iv.AdminPort) {
151 return fmt.Errorf("readinessProbe: expected: %d, actual %d", iv.AdminPort, proxyContainer.LivenessProbe.HTTPGet.Port.IntVal)
152 }
153 if err := iv.validatePort(proxyContainer, k8s.ProxyPortName, iv.InboundPort); err != nil {
154 return err
155 }
156 }
157
158 if iv.OutboundPort != 0 {
159 if err := iv.validateEnvVar(
160 proxyContainer,
161 "LINKERD2_PROXY_OUTBOUND_LISTEN_ADDRS",
162 fmt.Sprintf("127.0.0.1:%d", iv.OutboundPort),
163 ); err != nil {
164 return err
165 }
166 }
167
168 if iv.CPULimit != "" {
169 limit := resource.MustParse(iv.CPULimit)
170 if proxyContainer.Resources.Limits.Cpu() != nil {
171 if !proxyContainer.Resources.Limits.Cpu().Equal(limit) {
172 return fmt.Errorf("CpuLimit: expected %v, actual %v", &limit, proxyContainer.Resources.Limits.Cpu())
173 }
174 } else {
175 return fmt.Errorf("CpuLimit: expected %v, but none", &limit)
176 }
177
178 }
179
180 if iv.CPURequest != "" {
181 request := resource.MustParse(iv.CPURequest)
182 if proxyContainer.Resources.Requests.Cpu() != nil {
183 if !proxyContainer.Resources.Requests.Cpu().Equal(request) {
184 return fmt.Errorf("CpuRequest: expected %v, actual %v", &request, proxyContainer.Resources.Requests.Cpu())
185 }
186 } else {
187 return fmt.Errorf("CpuRequest: expected %v, but none", &request)
188 }
189 }
190
191 if iv.MemoryLimit != "" {
192 limit := resource.MustParse(iv.MemoryLimit)
193 if proxyContainer.Resources.Limits.Memory() != nil {
194 if !proxyContainer.Resources.Limits.Memory().Equal(limit) {
195 return fmt.Errorf("MemLimit: expected %v, actual %v", &limit, proxyContainer.Resources.Limits.Memory())
196 }
197 } else {
198 return fmt.Errorf("MemLimit: expected %v, but none", &limit)
199 }
200 }
201
202 if iv.MemoryRequest != "" {
203 request := resource.MustParse(iv.MemoryRequest)
204 if proxyContainer.Resources.Requests.Memory() != nil {
205 if !proxyContainer.Resources.Requests.Memory().Equal(request) {
206 return fmt.Errorf("MemRequest: expected %v, actual %v", &request, proxyContainer.Resources.Requests.Memory())
207 }
208 } else {
209 return fmt.Errorf("MemRequest: expected %v, but none", &request)
210 }
211 }
212
213 if iv.EphemeralStorageLimit != "" {
214 limit := resource.MustParse(iv.EphemeralStorageLimit)
215 if proxyContainer.Resources.Limits.StorageEphemeral() != nil {
216 if !proxyContainer.Resources.Limits.StorageEphemeral().Equal(limit) {
217 return fmt.Errorf("EphemeralStorageLimit: expected %v, actual %v", &limit, proxyContainer.Resources.Limits.StorageEphemeral())
218 }
219 } else {
220 return fmt.Errorf("EphemeralStorageLimit: expected %v, but none", &limit)
221 }
222 }
223
224 if iv.EphemeralStorageRequest != "" {
225 request := resource.MustParse(iv.EphemeralStorageRequest)
226 if proxyContainer.Resources.Requests.StorageEphemeral() != nil {
227 if !proxyContainer.Resources.Requests.StorageEphemeral().Equal(request) {
228 return fmt.Errorf("EphemeralStorageRequest: expected %v, actual %v", &request, proxyContainer.Resources.Requests.StorageEphemeral())
229 }
230 } else {
231 return fmt.Errorf("EphemeralStorageRequest: expected %v, but none", &request)
232 }
233 }
234
235 if iv.Image != "" || iv.Version != "" {
236 image := strings.Split(proxyContainer.Image, ":")
237
238 if len(image) != 2 {
239 return fmt.Errorf("invalid proxy container image string: %s", proxyContainer.Image)
240 }
241
242 if iv.Image != "" {
243 if image[0] != iv.Image {
244 return fmt.Errorf("proxyImage: expected %s, actual %s", iv.Image, image[0])
245 }
246 }
247
248 if iv.Version != "" {
249 if image[1] != iv.Version {
250 return fmt.Errorf("proxyImageVersion: expected %s, actual %s", iv.Version, image[1])
251 }
252 }
253 }
254
255 if iv.LogLevel != "" {
256 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_LOG", iv.LogLevel); err != nil {
257 return err
258 }
259 }
260
261 if iv.LogFormat != "" {
262 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_LOG_FORMAT", iv.LogFormat); err != nil {
263 return err
264 }
265 }
266
267 if iv.UID != 0 {
268 if proxyContainer.SecurityContext.RunAsUser == nil {
269 return fmt.Errorf("no RunAsUser specified")
270 }
271 if *proxyContainer.SecurityContext.RunAsUser != int64(iv.UID) {
272 return fmt.Errorf("runAsUser: expected %d, actual %d", iv.UID, *proxyContainer.SecurityContext.RunAsUser)
273 }
274 }
275
276 if iv.GID != 0 {
277 if proxyContainer.SecurityContext.RunAsGroup == nil {
278 return fmt.Errorf("no RunAsGroup specified")
279 }
280 if *proxyContainer.SecurityContext.RunAsGroup != int64(iv.GID) {
281 return fmt.Errorf("runAsGroup: expected %d, actual %d", iv.GID, *proxyContainer.SecurityContext.RunAsGroup)
282 }
283 }
284
285 if iv.RequireIdentityOnPorts != "" {
286 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_INBOUND_PORTS_REQUIRE_IDENTITY", iv.RequireIdentityOnPorts); err != nil {
287 return err
288 }
289 }
290
291 if iv.OpaquePorts != "" {
292 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_INBOUND_PORTS_DISABLE_PROTOCOL_DETECTION", iv.OpaquePorts); err != nil {
293 return err
294 }
295 }
296
297 if iv.OutboundConnectTimeout != "" {
298 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_OUTBOUND_CONNECT_TIMEOUT", iv.OutboundConnectTimeout); err != nil {
299 return err
300 }
301 }
302
303 if iv.OutboundConnectTimeout != "" {
304 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_INBOUND_CONNECT_TIMEOUT", iv.InboundConnectTimeout); err != nil {
305 return err
306 }
307 }
308
309 if iv.WaitBeforeExitSeconds != 0 {
310 expectedCmd := fmt.Sprintf("/bin/sleep,%d", iv.WaitBeforeExitSeconds)
311 actual := strings.Join(proxyContainer.Lifecycle.PreStop.Exec.Command, ",")
312 if expectedCmd != strings.Join(proxyContainer.Lifecycle.PreStop.Exec.Command, ",") {
313 return fmt.Errorf("preStopHook: expected %s, actual %s", expectedCmd, actual)
314 }
315 }
316
317 if iv.ShutdownGracePeriod != "" {
318 if err := iv.validateEnvVar(proxyContainer, "LINKERD2_PROXY_SHUTDOWN_GRACE_PERIOD", iv.ShutdownGracePeriod); err != nil {
319 return err
320 }
321 }
322
323 return nil
324 }
325
326 func (iv *InjectValidator) validateInitContainer(pod *v1.PodSpec) error {
327 if iv.NoInitContainer {
328 return nil
329 }
330 initContainer := iv.getContainer(pod, k8s.InitContainerName, true)
331 if initContainer == nil {
332 return fmt.Errorf("container %s missing", k8s.InitContainerName)
333 }
334
335 if iv.InitImage != "" || iv.InitImageVersion != "" {
336
337 image := strings.Split(initContainer.Image, ":")
338
339 if len(image) != 2 {
340 return fmt.Errorf("invalid proxy init image string: %s", initContainer.Image)
341 }
342
343 if iv.InitImage != "" {
344 if image[0] != iv.InitImage {
345 return fmt.Errorf("proxyInitImage: expected %s, actual %s", iv.InitImage, image[0])
346 }
347 }
348
349 if iv.InitImageVersion != "" {
350 if image[1] != iv.InitImageVersion {
351 return fmt.Errorf("proxyInitImageVersion: expected %s, actual %s", iv.InitImageVersion, image[1])
352 }
353 }
354 }
355
356 if iv.InboundPort != 0 {
357 if err := iv.validateArg(initContainer, "--incoming-proxy-port", strconv.Itoa(iv.InboundPort)); err != nil {
358 return err
359 }
360 }
361
362 if iv.OutboundPort != 0 {
363 if err := iv.validateArg(initContainer, "--proxy-uid", strconv.Itoa(iv.UID)); err != nil {
364 return err
365 }
366 }
367
368 if iv.UID != 0 {
369 if err := iv.validateArg(initContainer, "--outgoing-proxy-port", strconv.Itoa(iv.OutboundPort)); err != nil {
370 return err
371 }
372 }
373
374 if iv.SkipInboundPorts != "" {
375 expectedPorts := fmt.Sprintf("%d,%d,%s", iv.ControlPort, iv.AdminPort, iv.SkipInboundPorts)
376 if err := iv.validateArg(initContainer, "--inbound-ports-to-ignore", expectedPorts); err != nil {
377 return err
378 }
379 }
380
381 if iv.SkipOutboundPorts != "" {
382 if err := iv.validateArg(initContainer, "--outbound-ports-to-ignore", iv.SkipOutboundPorts); err != nil {
383 return err
384 }
385 }
386
387 if iv.SkipSubnets != "" {
388 if err := iv.validateArg(initContainer, "--skip-subnets", iv.SkipSubnets); err != nil {
389 return err
390 }
391 }
392
393 return nil
394 }
395
396 func (iv *InjectValidator) validateArg(container *v1.Container, argName, expectedValue string) error {
397 for i, arg := range container.Args {
398 if arg == argName {
399 if len(container.Args) < i+2 {
400 return fmt.Errorf("No value for arg %s", argName)
401 }
402 if container.Args[i+1] != expectedValue {
403 return fmt.Errorf("container arg %s expected: %s, actual %s", argName, expectedValue, container.Args[i+1])
404 }
405 return nil
406 }
407 }
408
409 return fmt.Errorf("Could not find arg: %s", argName)
410
411 }
412
413
414
415 func (iv *InjectValidator) ValidatePod(pod *v1.PodSpec) error {
416
417 if err := iv.validateProxyContainer(pod); err != nil {
418 return err
419 }
420
421 if err := iv.validateInitContainer(pod); err != nil {
422 return err
423 }
424
425 if err := iv.validateDebugContainer(pod); err != nil {
426 return err
427 }
428
429 return nil
430 }
431
432
433
434 func (iv *InjectValidator) GetFlagsAndAnnotations() ([]string, map[string]string) {
435 annotations := make(map[string]string)
436 var flags []string
437
438 if iv.AutoInject {
439 annotations[k8s.ProxyInjectAnnotation] = k8s.ProxyInjectEnabled
440 }
441
442 if iv.AdminPort != 0 {
443 annotations[k8s.ProxyAdminPortAnnotation] = strconv.Itoa(iv.AdminPort)
444 flags = append(flags, fmt.Sprintf("--admin-port=%s", strconv.Itoa(iv.AdminPort)))
445 }
446
447 if iv.ControlPort != 0 {
448 annotations[k8s.ProxyControlPortAnnotation] = strconv.Itoa(iv.ControlPort)
449 flags = append(flags, fmt.Sprintf("--control-port=%s", strconv.Itoa(iv.ControlPort)))
450 }
451
452 if iv.EnableDebug {
453 annotations[k8s.ProxyEnableDebugAnnotation] = enabled
454 flags = append(flags, "--enable-debug-sidecar")
455 }
456
457 if iv.EnableExternalProfiles {
458 annotations[k8s.ProxyEnableExternalProfilesAnnotation] = enabled
459 flags = append(flags, "--enable-external-profiles")
460 }
461
462 if iv.ImagePullPolicy != "" {
463 annotations[k8s.ProxyImagePullPolicyAnnotation] = iv.ImagePullPolicy
464 flags = append(flags, fmt.Sprintf("--image-pull-policy=%s", iv.ImagePullPolicy))
465 }
466
467 if iv.InboundPort != 0 {
468 annotations[k8s.ProxyInboundPortAnnotation] = strconv.Itoa(iv.InboundPort)
469 flags = append(flags, fmt.Sprintf("--inbound-port=%s", strconv.Itoa(iv.InboundPort)))
470 }
471
472 if iv.InitImage != "" {
473 annotations[k8s.ProxyInitImageAnnotation] = iv.InitImage
474 flags = append(flags, fmt.Sprintf("--init-image=%s", iv.InitImage))
475 }
476
477 if iv.InitImageVersion != "" {
478 annotations[k8s.ProxyInitImageVersionAnnotation] = iv.InitImageVersion
479 flags = append(flags, fmt.Sprintf("--init-image-version=%s", iv.InitImageVersion))
480 }
481
482 if iv.OutboundPort != 0 {
483 annotations[k8s.ProxyOutboundPortAnnotation] = strconv.Itoa(iv.OutboundPort)
484 flags = append(flags, fmt.Sprintf("--outbound-port=%s", strconv.Itoa(iv.OutboundPort)))
485 }
486
487 if iv.CPULimit != "" {
488 annotations[k8s.ProxyCPULimitAnnotation] = iv.CPULimit
489 flags = append(flags, fmt.Sprintf("--proxy-cpu-limit=%s", iv.CPULimit))
490 }
491
492 if iv.CPURequest != "" {
493 annotations[k8s.ProxyCPURequestAnnotation] = iv.CPURequest
494 flags = append(flags, fmt.Sprintf("--proxy-cpu-request=%s", iv.CPURequest))
495 }
496
497 if iv.MemoryLimit != "" {
498 annotations[k8s.ProxyMemoryLimitAnnotation] = iv.MemoryLimit
499 flags = append(flags, fmt.Sprintf("--proxy-memory-limit=%s", iv.MemoryLimit))
500 }
501
502 if iv.EphemeralStorageLimit != "" {
503 annotations[k8s.ProxyEphemeralStorageLimitAnnotation] = iv.EphemeralStorageLimit
504
505
506 }
507
508 if iv.MemoryRequest != "" {
509 annotations[k8s.ProxyMemoryRequestAnnotation] = iv.MemoryRequest
510 flags = append(flags, fmt.Sprintf("--proxy-memory-request=%s", iv.MemoryRequest))
511 }
512
513 if iv.EphemeralStorageRequest != "" {
514 annotations[k8s.ProxyEphemeralStorageRequestAnnotation] = iv.EphemeralStorageRequest
515
516
517 }
518
519 if iv.Image != "" {
520 annotations[k8s.ProxyImageAnnotation] = iv.Image
521 flags = append(flags, fmt.Sprintf("--proxy-image=%s", iv.Image))
522 }
523
524 if iv.LogLevel != "" {
525 annotations[k8s.ProxyLogLevelAnnotation] = iv.LogLevel
526 flags = append(flags, fmt.Sprintf("--proxy-log-level=%s", iv.LogLevel))
527 }
528
529 if iv.LogFormat != "" {
530 annotations[k8s.ProxyLogFormatAnnotation] = iv.LogFormat
531 }
532
533 if iv.UID != 0 {
534 annotations[k8s.ProxyUIDAnnotation] = strconv.Itoa(iv.UID)
535 flags = append(flags, fmt.Sprintf("--proxy-uid=%s", strconv.Itoa(iv.UID)))
536 }
537
538 if iv.GID != 0 {
539 annotations[k8s.ProxyGIDAnnotation] = strconv.Itoa(iv.GID)
540 flags = append(flags, fmt.Sprintf("--proxy-gid=%s", strconv.Itoa(iv.GID)))
541 }
542
543 if iv.Version != "" {
544 annotations[k8s.ProxyVersionOverrideAnnotation] = iv.Version
545 flags = append(flags, fmt.Sprintf("--proxy-version=%s", iv.Version))
546 }
547
548 if iv.RequireIdentityOnPorts != "" {
549 annotations[k8s.ProxyRequireIdentityOnInboundPortsAnnotation] = iv.RequireIdentityOnPorts
550 flags = append(flags, fmt.Sprintf("--require-identity-on-inbound-ports =%s", iv.RequireIdentityOnPorts))
551 }
552
553 if iv.SkipInboundPorts != "" {
554 annotations[k8s.ProxyIgnoreInboundPortsAnnotation] = iv.SkipInboundPorts
555 flags = append(flags, fmt.Sprintf("--skip-inbound-ports=%s", iv.SkipInboundPorts))
556 }
557
558 if iv.OpaquePorts != "" {
559 annotations[k8s.ProxyOpaquePortsAnnotation] = iv.OpaquePorts
560 }
561
562 if iv.SkipOutboundPorts != "" {
563 annotations[k8s.ProxyIgnoreOutboundPortsAnnotation] = iv.SkipOutboundPorts
564 flags = append(flags, fmt.Sprintf("--skip-outbound-ports=%s", iv.SkipOutboundPorts))
565 }
566
567 if iv.OutboundConnectTimeout != "" {
568 annotations[k8s.ProxyOutboundConnectTimeout] = iv.OutboundConnectTimeout
569 }
570
571 if iv.InboundConnectTimeout != "" {
572 annotations[k8s.ProxyInboundConnectTimeout] = iv.InboundConnectTimeout
573 }
574
575 if iv.WaitBeforeExitSeconds != 0 {
576 annotations[k8s.ProxyWaitBeforeExitSecondsAnnotation] = strconv.Itoa(iv.WaitBeforeExitSeconds)
577 flags = append(flags, fmt.Sprintf("--wait-before-exit-secondst=%s", strconv.Itoa(iv.WaitBeforeExitSeconds)))
578
579 }
580
581 if iv.SkipSubnets != "" {
582 annotations[k8s.ProxySkipSubnetsAnnotation] = iv.SkipSubnets
583 flags = append(flags, fmt.Sprintf("--skip-subnets=%s", iv.SkipSubnets))
584 }
585
586 if iv.ShutdownGracePeriod != "" {
587 annotations[k8s.ProxyShutdownGracePeriodAnnotation] = iv.ShutdownGracePeriod
588 flags = append(flags, fmt.Sprintf("--shutdown-grace-period=%s", iv.ShutdownGracePeriod))
589 }
590
591 return flags, annotations
592 }
593
View as plain text