1 package utils
2
3 import (
4 "testing"
5
6 "github.com/devfile/library/v2/pkg/devfile/parser/data"
7 "github.com/google/go-cmp/cmp"
8
9 "github.com/redhat-developer/odo/pkg/storage"
10 "github.com/redhat-developer/odo/pkg/util"
11
12 devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
13 devfileParser "github.com/devfile/library/v2/pkg/devfile/parser"
14 corev1 "k8s.io/api/core/v1"
15 )
16
17 func TestAddOdoProjectVolume(t *testing.T) {
18
19 tests := []struct {
20 name string
21 containers []corev1.Container
22 containerWithProjectVolMount []string
23 volMount map[string]string
24 }{
25 {
26 name: "Case: nil passed as containers slice",
27 containers: nil,
28 },
29 {
30 name: "Case: Various containers with and without PROJECTS_ROOT",
31 containers: []corev1.Container{
32 {
33 Name: "container1",
34 Env: []corev1.EnvVar{
35 {
36 Name: _envProjectsRoot,
37 Value: "/path1",
38 },
39 },
40 },
41 {
42 Name: "container2",
43 Env: []corev1.EnvVar{
44 {
45 Name: _envProjectsRoot,
46 Value: "/path2",
47 },
48 },
49 },
50 {
51 Name: "container3",
52 Env: []corev1.EnvVar{
53 {
54 Name: "RANDOM",
55 Value: "/path3",
56 },
57 },
58 },
59 },
60 containerWithProjectVolMount: []string{"container1", "container2"},
61 volMount: map[string]string{
62 "container1": "/path1",
63 "container2": "/path2",
64 },
65 },
66 }
67 for _, tt := range tests {
68 t.Run(tt.name, func(t *testing.T) {
69
70 AddOdoProjectVolume(tt.containers)
71
72 for wantContainerName, wantMountPath := range tt.volMount {
73 matched := false
74 for _, container := range tt.containers {
75 if container.Name == wantContainerName {
76 for _, volMount := range container.VolumeMounts {
77 if volMount.Name == storage.OdoSourceVolume && volMount.MountPath == wantMountPath {
78 matched = true
79 }
80 }
81 }
82 }
83
84 if !matched {
85 t.Error("TestAddOdoProjectVolume error: did not match the volume mount for odo-projects")
86 }
87 }
88 })
89 }
90 }
91
92 func TestAddOdoMandatoryVolume(t *testing.T) {
93 findContainerByName := func(containers []corev1.Container, name string) (corev1.Container, bool) {
94 for _, c := range containers {
95 if c.Name == name {
96 return c, true
97 }
98 }
99 return corev1.Container{}, false
100 }
101
102 hasVolumeMount := func(volumeMounts []corev1.VolumeMount, mountPath string, volName string) bool {
103 for _, v := range volumeMounts {
104 if v.Name == volName && v.MountPath == mountPath {
105 return true
106 }
107 }
108 return false
109 }
110
111 for _, tt := range []struct {
112 name string
113 containers []corev1.Container
114 wantVolumeMounts map[string]map[string]string
115 }{
116 {
117 name: "nil as containers slice",
118 containers: nil,
119 },
120 {
121 name: "containers with no existing volume mounts",
122 containers: []corev1.Container{
123 {
124 Name: "container1",
125 },
126 {
127 Name: "container2",
128 },
129 },
130 wantVolumeMounts: map[string]map[string]string{
131 "container1": {storage.SharedDataMountPath: storage.SharedDataVolumeName},
132 "container2": {storage.SharedDataMountPath: storage.SharedDataVolumeName},
133 },
134 },
135 {
136 name: "containers with existing volume mounts",
137 containers: []corev1.Container{
138 {
139 Name: "container1",
140 VolumeMounts: []corev1.VolumeMount{
141 {
142 Name: "vol1",
143 MountPath: "/container1/vol1",
144 },
145 {
146 Name: "vol2",
147 MountPath: "/container1/vol2",
148 },
149 },
150 },
151 {
152 Name: "container2",
153 VolumeMounts: []corev1.VolumeMount{
154 {
155 Name: "vol2",
156 MountPath: "/container2/vol2",
157 },
158 },
159 },
160 {
161 Name: "container3",
162 },
163 },
164 wantVolumeMounts: map[string]map[string]string{
165 "container1": {
166 "/container1/vol1": "vol1",
167 "/container1/vol2": "vol2",
168 storage.SharedDataMountPath: storage.SharedDataVolumeName,
169 },
170 "container2": {
171 "/container2/vol2": "vol2",
172 storage.SharedDataMountPath: storage.SharedDataVolumeName,
173 },
174 "container3": {storage.SharedDataMountPath: storage.SharedDataVolumeName},
175 },
176 },
177 } {
178 t.Run(tt.name, func(t *testing.T) {
179 AddOdoMandatoryVolume(tt.containers)
180
181 for containerName, volMounts := range tt.wantVolumeMounts {
182 c, ok := findContainerByName(tt.containers, containerName)
183 if !ok {
184 t.Errorf("container %s defined in expected volume mounts, but not in container list for test",
185 containerName)
186 }
187 for mountPath, vol := range volMounts {
188 if !hasVolumeMount(c.VolumeMounts, mountPath, vol) {
189 t.Errorf("expected %s to be mounted under %s in container %s", vol, mountPath, c.Name)
190 }
191 }
192 }
193 })
194 }
195 }
196
197 func TestUpdateContainersEntrypointsIfNeeded(t *testing.T) {
198 const (
199 buildCommand = "my-build"
200 buildCmdLine = "echo my-build-command-line"
201 buildContainerComponent = "build-container-component"
202 )
203 const (
204 runCommand = "my-run"
205 runCmdLine = "echo my-run-command-line"
206 runContainerComponent = "run-container-component"
207 )
208 const (
209 debugCommand = "my-debug"
210 debugCmdLine = "echo my-run-command-line"
211 debugContainerComponent = "debug-container-component"
212 )
213
214 execBuildGroup := devfilev1.CommandGroup{
215 IsDefault: util.GetBool(true),
216 Kind: devfilev1.BuildCommandGroupKind,
217 }
218 execRunGroup := devfilev1.CommandGroup{
219 IsDefault: util.GetBool(true),
220 Kind: devfilev1.RunCommandGroupKind,
221 }
222 execDebugGroup := devfilev1.CommandGroup{
223 IsDefault: util.GetBool(true),
224 Kind: devfilev1.DebugCommandGroupKind,
225 }
226
227 for _, tt := range []struct {
228 name string
229 commands []devfilev1.Command
230 components []devfilev1.Component
231 buildCommand string
232 runCommand string
233 debugCommand string
234 buildContainerCommand []string
235 runContainerCommand []string
236 debugContainerCommand []string
237 buildContainerArgs []string
238 runContainerArgs []string
239 debugContainerArgs []string
240 wantErr bool
241
242 expectedContainerCommand map[string][]string
243
244 expectedContainerArgs map[string][]string
245 }{
246 {
247 name: "invalid run command",
248 runCommand: "a-non-existing-run-name",
249 wantErr: true,
250 },
251 {
252 name: "invalid debug command",
253 runCommand: runCommand,
254 debugCommand: "a-non-existing-debug-name",
255 wantErr: true,
256 },
257 {
258 name: "missing build command specified by name",
259 buildCommand: buildCommand + "-not-found",
260 runCommand: runCommand,
261 debugCommand: debugCommand,
262 wantErr: true,
263 },
264 {
265 name: "containers without any command or args => must be overridden with 'tail -f /dev/null'",
266 buildCommand: buildCommand,
267 runCommand: runCommand,
268 debugCommand: debugCommand,
269 wantErr: false,
270 expectedContainerCommand: map[string][]string{
271 buildContainerComponent: {"tail"},
272 runContainerComponent: {"tail"},
273 debugContainerComponent: {"tail"},
274 },
275 expectedContainerArgs: map[string][]string{
276 buildContainerComponent: {"-f", "/dev/null"},
277 runContainerComponent: {"-f", "/dev/null"},
278 debugContainerComponent: {"-f", "/dev/null"},
279 },
280 },
281 {
282 name: "containers with one without any command or args => must be overridden with 'tail -f /dev/null'",
283 buildCommand: buildCommand,
284 runCommand: runCommand,
285 debugCommand: debugCommand,
286 wantErr: false,
287 buildContainerCommand: []string{"npm"},
288 buildContainerArgs: []string{"install"},
289 runContainerCommand: []string{"printenv"},
290 runContainerArgs: []string{"HOSTNAME"},
291 expectedContainerCommand: map[string][]string{
292 buildContainerComponent: {"npm"},
293 runContainerComponent: {"printenv"},
294 debugContainerComponent: {"tail"},
295 },
296 expectedContainerArgs: map[string][]string{
297 buildContainerComponent: {"install"},
298 runContainerComponent: {"HOSTNAME"},
299 debugContainerComponent: {"-f", "/dev/null"},
300 },
301 },
302 {
303 name: "default build command, containers with one without any command or args => must be overridden with 'tail -f /dev/null'",
304 runCommand: runCommand,
305 debugCommand: debugCommand,
306 wantErr: false,
307 runContainerCommand: []string{"printenv"},
308 runContainerArgs: []string{"HOSTNAME"},
309 expectedContainerCommand: map[string][]string{
310 buildContainerComponent: {"tail"},
311 runContainerComponent: {"printenv"},
312 debugContainerComponent: {"tail"},
313 },
314 expectedContainerArgs: map[string][]string{
315 buildContainerComponent: {"-f", "/dev/null"},
316 runContainerComponent: {"HOSTNAME"},
317 debugContainerComponent: {"-f", "/dev/null"},
318 },
319 },
320 {
321 name: "containers with explicit command or args",
322 buildCommand: buildCommand,
323 runCommand: runCommand,
324 debugCommand: debugCommand,
325 wantErr: false,
326 buildContainerCommand: []string{"npm"},
327 buildContainerArgs: []string{"install"},
328 runContainerCommand: []string{"printenv"},
329 runContainerArgs: []string{"HOSTNAME"},
330 debugContainerCommand: []string{"tail"},
331 debugContainerArgs: []string{"-f", "/path/to/my/custom/log/file"},
332 expectedContainerCommand: map[string][]string{
333 buildContainerComponent: {"npm"},
334 runContainerComponent: {"printenv"},
335 debugContainerComponent: {"tail"},
336 },
337 expectedContainerArgs: map[string][]string{
338 buildContainerComponent: {"install"},
339 runContainerComponent: {"HOSTNAME"},
340 debugContainerComponent: {"-f", "/path/to/my/custom/log/file"},
341 },
342 },
343 } {
344 t.Run(tt.name, func(t *testing.T) {
345 devfileData, err := data.NewDevfileData(string(data.APISchemaVersion220))
346 if err != nil {
347 t.Error(err)
348 }
349 err = devfileData.AddComponents([]devfilev1.Component{
350 {
351 Name: buildContainerComponent,
352 ComponentUnion: devfilev1.ComponentUnion{
353 Container: &devfilev1.ContainerComponent{
354 Container: devfilev1.Container{},
355 },
356 },
357 },
358 {
359 Name: runContainerComponent,
360 ComponentUnion: devfilev1.ComponentUnion{
361 Container: &devfilev1.ContainerComponent{
362 Container: devfilev1.Container{},
363 },
364 },
365 },
366 {
367 Name: debugContainerComponent,
368 ComponentUnion: devfilev1.ComponentUnion{
369 Container: &devfilev1.ContainerComponent{
370 Container: devfilev1.Container{},
371 },
372 },
373 },
374 })
375 if err != nil {
376 t.Error(err)
377 }
378 err = devfileData.AddCommands([]devfilev1.Command{
379 {
380 Id: buildCommand,
381 CommandUnion: devfilev1.CommandUnion{
382 Exec: &devfilev1.ExecCommand{
383 CommandLine: buildCmdLine,
384 Component: buildContainerComponent,
385 LabeledCommand: devfilev1.LabeledCommand{
386 BaseCommand: devfilev1.BaseCommand{
387 Group: &execBuildGroup,
388 },
389 },
390 },
391 },
392 },
393 {
394 Id: runCommand,
395 CommandUnion: devfilev1.CommandUnion{
396 Exec: &devfilev1.ExecCommand{
397 CommandLine: runCmdLine,
398 Component: runContainerComponent,
399 LabeledCommand: devfilev1.LabeledCommand{
400 BaseCommand: devfilev1.BaseCommand{
401 Group: &execRunGroup,
402 },
403 },
404 },
405 },
406 },
407 {
408 Id: debugCommand,
409 CommandUnion: devfilev1.CommandUnion{
410 Exec: &devfilev1.ExecCommand{
411 CommandLine: debugCmdLine,
412 Component: debugContainerComponent,
413 LabeledCommand: devfilev1.LabeledCommand{
414 BaseCommand: devfilev1.BaseCommand{
415 Group: &execDebugGroup,
416 },
417 },
418 },
419 },
420 },
421 })
422 if err != nil {
423 t.Error(err)
424 }
425 devfileObj := devfileParser.DevfileObj{
426 Data: devfileData,
427 }
428
429 containerForComponents := []corev1.Container{
430 {
431 Name: buildContainerComponent,
432 Command: tt.buildContainerCommand,
433 Args: tt.buildContainerArgs,
434 },
435 {
436 Name: runContainerComponent,
437 Command: tt.runContainerCommand,
438 Args: tt.runContainerArgs,
439 },
440 {
441 Name: debugContainerComponent,
442 Command: tt.debugContainerCommand,
443 Args: tt.debugContainerArgs,
444 },
445 }
446
447 containers, err := UpdateContainersEntrypointsIfNeeded(devfileObj, containerForComponents, tt.buildCommand, tt.runCommand, tt.debugCommand)
448 if tt.wantErr != (err != nil) {
449 t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
450 return
451 }
452
453 if len(containers) != len(tt.expectedContainerCommand) {
454 t.Errorf("length of expectedContainerCommand must match the one of containers, please fix test %q", tt.name)
455 }
456 if len(containers) != len(tt.expectedContainerArgs) {
457 t.Errorf("length of expectedContainerArgs must match the one of containers, please fix test %q", tt.name)
458 }
459 for _, c := range containers {
460 if len(c.Command) == 0 {
461 t.Errorf("empty command for container %q", c.Name)
462 }
463 if len(c.Args) == 0 {
464 t.Errorf("empty command for container %q", c.Args)
465 }
466
467 if diff := cmp.Diff(tt.expectedContainerCommand[c.Name], c.Command); diff != "" {
468 t.Errorf("UpdateContainersEntrypointsIfNeeded() expectedContainerCommand[%s] mismatch (-want +got):\n%s", c.Name, diff)
469 }
470 if diff := cmp.Diff(tt.expectedContainerArgs[c.Name], c.Args); diff != "" {
471 t.Errorf("UpdateContainersEntrypointsIfNeeded() expectedContainerArgs[%s] mismatch (-want +got):\n%s", c.Name, diff)
472 }
473 }
474
475 })
476 }
477 }
478
View as plain text