1 package helper
2
3 import (
4 "encoding/json"
5 "fmt"
6 "regexp"
7 "strconv"
8 "strings"
9 "time"
10
11 . "github.com/onsi/ginkgo/v2"
12 . "github.com/onsi/gomega"
13 "github.com/onsi/gomega/gexec"
14
15 "github.com/redhat-developer/odo/pkg/labels"
16 )
17
18 const (
19 ResourceTypeDeployment = "deployment"
20 ResourceTypePod = "pod"
21 ResourceTypeJob = "job"
22 ResourceTypePVC = "pvc"
23 ResourceTypeService = "service"
24 )
25
26 type KubectlRunner struct {
27
28 path string
29 }
30
31
32 func NewKubectlRunner(kubectlPath string) KubectlRunner {
33 return KubectlRunner{
34 path: kubectlPath,
35 }
36 }
37
38
39 func (kubectl KubectlRunner) Run(args ...string) *gexec.Session {
40 session := CmdRunner(kubectl.path, args...)
41 Eventually(session).Should(gexec.Exit(0))
42 return session
43 }
44
45
46 func (kubectl KubectlRunner) Exec(podName string, projectName string, args []string, expectedSuccess *bool) (string, string) {
47
48 cmd := []string{"exec", podName, "--namespace", projectName}
49
50 cmd = append(cmd, args...)
51
52 cmdWrapper := Cmd(kubectl.path, cmd...)
53 if expectedSuccess == nil {
54 cmdWrapper = cmdWrapper.ShouldRun()
55 } else if *expectedSuccess {
56 cmdWrapper = cmdWrapper.ShouldPass()
57 } else {
58 cmdWrapper = cmdWrapper.ShouldFail()
59 }
60 return cmdWrapper.OutAndErr()
61 }
62
63
64 func (kubectl KubectlRunner) ExecListDir(podName string, projectName string, dir string) string {
65 stdOut := Cmd(kubectl.path, "exec", podName, "--namespace", projectName,
66 "--", "ls", "-lai", dir).ShouldPass().Out()
67 return stdOut
68 }
69
70
71 func (kubectl KubectlRunner) CheckCmdOpInRemoteDevfilePod(podName string, containerName string, prjName string, cmd []string, checkOp func(cmdOp string, err error) bool) bool {
72 var execOptions []string
73 execOptions = []string{"exec", podName, "--namespace", prjName, "--"}
74 if containerName != "" {
75 execOptions = []string{"exec", podName, "-c", containerName, "--namespace", prjName, "--"}
76 }
77 args := append(execOptions, cmd...)
78 session := CmdRunner(kubectl.path, args...)
79 stdOut := string(session.Wait().Out.Contents())
80 stdErr := string(session.Wait().Err.Contents())
81 if stdErr != "" && session.ExitCode() != 0 {
82 return checkOp(stdOut, fmt.Errorf("cmd %s failed with error %s on pod %s", cmd, stdErr, podName))
83 }
84 return checkOp(stdOut, nil)
85 }
86
87
88
89 func (kubectl KubectlRunner) GetRunningPodNameByComponent(compName string, namespace string) string {
90 selector := fmt.Sprintf("--selector=component=%s", compName)
91 stdOut := Cmd(kubectl.path, "get", ResourceTypePod, "--namespace", namespace, "--field-selector=status.phase=Running", selector, "-o", "jsonpath={.items[*].metadata.name}").ShouldPass().Out()
92 return strings.TrimSpace(stdOut)
93 }
94
95
96 func (kubectl KubectlRunner) GetJobNameByComponent(compName string, namespace string) string {
97 selector := fmt.Sprintf("--selector=app.kubernetes.io/instance=%s", compName)
98 stdOut := Cmd(kubectl.path, "get", ResourceTypeJob, "--namespace", namespace, selector, "-o", "jsonpath={.items[*].metadata.name}").ShouldPass().Out()
99 return strings.TrimSpace(stdOut)
100 }
101
102
103 func (kubectl KubectlRunner) GetPVCSize(compName, storageName, namespace string) string {
104 selector := fmt.Sprintf("--selector=app.kubernetes.io/storage-name=%s,app.kubernetes.io/instance=%s", storageName, compName)
105 stdOut := Cmd(kubectl.path, "get", ResourceTypePVC, "--namespace", namespace, selector, "-o", "jsonpath={.items[*].spec.resources.requests.storage}").ShouldPass().Out()
106 return strings.TrimSpace(stdOut)
107 }
108
109
110 func (kubectl KubectlRunner) GetPodInitContainers(compName string, namespace string) []string {
111 selector := fmt.Sprintf("--selector=component=%s", compName)
112 stdOut := Cmd(kubectl.path, "get", ResourceTypePod, "--namespace", namespace, "--field-selector=status.phase=Running", selector, "-o", "jsonpath={.items[*].spec.initContainers[*].name}").ShouldPass().Out()
113 return strings.Split(stdOut, " ")
114 }
115
116
117 func (kubectl KubectlRunner) GetVolumeMountNamesandPathsFromContainer(deployName string, containerName, namespace string) string {
118 volumeName := Cmd(kubectl.path, "get", "deploy", deployName, "--namespace", namespace,
119 "-o", "go-template="+
120 "{{range .spec.template.spec.containers}}{{if eq .name \""+containerName+
121 "\"}}{{range .volumeMounts}}{{.name}}{{\":\"}}{{.mountPath}}{{\"\\n\"}}{{end}}{{end}}{{end}}").ShouldPass().Out()
122
123 return strings.TrimSpace(volumeName)
124 }
125
126
127 func (kubectl KubectlRunner) GetContainerEnv(podName, containerName, namespace string) string {
128 containerEnv := Cmd(kubectl.path, "get", "po", podName, "--namespace", namespace,
129 "-o", "go-template="+
130 "{{range .spec.containers}}{{if eq .name \""+containerName+
131 "\"}}{{range .env}}{{.name}}{{\":\"}}{{.value}}{{\"\\n\"}}{{end}}{{end}}{{end}}").ShouldPass().Out()
132
133 return strings.TrimSpace(containerEnv)
134 }
135
136
137 func (kubectl KubectlRunner) WaitAndCheckForExistence(resourceType, namespace string, timeoutMinutes int) bool {
138 pingTimeout := time.After(time.Duration(timeoutMinutes) * time.Minute)
139
140
141 tick := time.Tick(time.Second)
142 for {
143 select {
144 case <-pingTimeout:
145 Fail(fmt.Sprintf("Timeout after %d minutes", timeoutMinutes))
146
147 case <-tick:
148 session := CmdRunner(kubectl.path, "get", resourceType, "--namespace", namespace)
149 Eventually(session).Should(gexec.Exit(0))
150
151 output := string(session.Wait().Err.Contents())
152
153 if strings.Contains(strings.ToLower(output), "no resources found") {
154 return true
155 }
156 }
157 }
158 }
159
160
161 func (kubectl KubectlRunner) GetServices(namespace string) string {
162 session := CmdRunner(kubectl.path, "get", ResourceTypeService, "--namespace", namespace)
163 Eventually(session).Should(gexec.Exit(0))
164 output := string(session.Wait().Out.Contents())
165 return output
166 }
167
168
169 func (kubectl KubectlRunner) CreateAndSetRandNamespaceProject() string {
170 projectName := GenerateProjectName()
171 kubectl.createAndSetRandNamespaceProject(projectName)
172 return projectName
173 }
174
175 func (kubectl KubectlRunner) createAndSetRandNamespaceProject(projectName string) string {
176 if kubectl.HasNamespaceProject(projectName) {
177 fmt.Fprintf(GinkgoWriter, "Namespace %q already exists\n", projectName)
178 } else {
179 fmt.Fprintf(GinkgoWriter, "Creating a new project: %s\n", projectName)
180 Cmd("kubectl", "create", "namespace", projectName).ShouldPass()
181 }
182 Cmd("kubectl", "config", "set-context", "--current", "--namespace", projectName).ShouldPass()
183
184 kubectl.ListNamespaceProject(projectName)
185 kubectl.addConfigMapForCleanup(projectName)
186 return projectName
187 }
188
189 func (kubectl KubectlRunner) SetProject(namespace string) string {
190 Cmd("kubectl", "config", "set-context", "--current", "--namespace", namespace).ShouldPass()
191 session := Cmd("kubectl", "get", "namespaces").ShouldPass().Out()
192 Expect(session).To(ContainSubstring(namespace))
193 return namespace
194 }
195
196
197 func (kubectl KubectlRunner) CreateAndSetRandNamespaceProjectOfLength(i int) string {
198 projectName := RandString(i)
199 kubectl.createAndSetRandNamespaceProject(projectName)
200 return projectName
201 }
202
203
204 func (kubectl KubectlRunner) DeleteNamespaceProject(projectName string, wait bool) {
205 fmt.Fprintf(GinkgoWriter, "Deleting project: %s\n", projectName)
206 Cmd("kubectl", "delete", "namespaces", projectName, "--wait="+strconv.FormatBool(wait)).ShouldPass()
207 }
208
209 func (kubectl KubectlRunner) GetEnvsDevFileDeployment(componentName, appName, projectName string) map[string]string {
210 var mapOutput = make(map[string]string)
211 selector := labels.Builder().WithComponentName(componentName).WithAppName(appName).SelectorFlag()
212 output := Cmd(kubectl.path, "get", ResourceTypeDeployment, selector, "--namespace", projectName,
213 "-o", "jsonpath='{range .items[0].spec.template.spec.containers[0].env[*]}{.name}:{.value}{\"\\n\"}{end}'").ShouldPass().Out()
214
215 for _, line := range strings.Split(output, "\n") {
216 line = strings.TrimPrefix(line, "'")
217 splits := strings.Split(line, ":")
218 name := splits[0]
219 value := strings.Join(splits[1:], ":")
220 mapOutput[name] = value
221 }
222 return mapOutput
223 }
224
225 func (kubectl KubectlRunner) GetAllPVCNames(namespace string) []string {
226 session := CmdRunner(kubectl.path, "get", ResourceTypePVC, "--namespace", namespace, "-o", "jsonpath={.items[*].metadata.name}")
227 Eventually(session).Should(gexec.Exit(0))
228 output := string(session.Wait().Out.Contents())
229 if output == "" {
230 return []string{}
231 }
232 return strings.Split(output, " ")
233 }
234
235
236 func (kubectl KubectlRunner) DeletePod(podName string, namespace string) {
237 Cmd(kubectl.path, "delete", ResourceTypePod, "--namespace", namespace, podName).ShouldPass()
238 }
239
240
241
242 func (kubectl KubectlRunner) WaitAndCheckForTerminatingState(resourceType, namespace string, timeoutMinutes int) bool {
243 return WaitAndCheckForTerminatingState(kubectl.path, resourceType, namespace, timeoutMinutes)
244 }
245
246
247 func (kubectl KubectlRunner) VerifyResourceDeleted(ri ResourceInfo) {
248 session := CmdRunner(kubectl.path, "get", ri.ResourceType, "--namespace", ri.Namespace)
249 Eventually(session).Should(gexec.Exit(0))
250 output := string(session.Wait().Out.Contents())
251 Expect(output).NotTo(ContainSubstring(ri.ResourceName))
252 }
253
254
255 func (kubectl KubectlRunner) VerifyResourceToBeDeleted(ri ResourceInfo) {
256 deletedOrMarkedToDelete := func() bool {
257 session := CmdRunner(kubectl.path, "get", ri.ResourceType, ri.ResourceName, "--namespace", ri.Namespace, "-o", "jsonpath='{.metadata.deletionTimestamp}'")
258 exit := session.Wait().ExitCode()
259 if exit == 1 {
260
261 return true
262 }
263 content := session.Wait().Out.Contents()
264
265 return len(content) > 0
266 }
267 Expect(deletedOrMarkedToDelete()).To(BeTrue())
268 }
269
270
271
272 func (kubectl KubectlRunner) GetAnnotationsDeployment(componentName, appName, projectName string) map[string]string {
273 return GetAnnotationsDeployment(kubectl.path, componentName, appName, projectName)
274 }
275
276
277 func (kubectl KubectlRunner) GetAllPodsInNs(namespace string) string {
278 args := []string{"get", ResourceTypePod, "-n", namespace}
279 noResourcesMsg := fmt.Sprintf("No resources found in %s namespace", namespace)
280 kubectl.WaitForRunnerCmdOut(args, 1, true, func(output string) bool {
281 return !strings.Contains(output, noResourcesMsg)
282 }, true)
283 return Cmd(kubectl.path, args...).ShouldPass().Out()
284 }
285
286
287 func (kubectl KubectlRunner) GetAllPodNames(namespace string) []string {
288 session := CmdRunner(kubectl.path, "get", "pods", "--namespace", namespace, "-o", "jsonpath={.items[*].metadata.name}")
289 Eventually(session).Should(gexec.Exit(0))
290 output := string(session.Wait().Out.Contents())
291 if output == "" {
292 return []string{}
293 }
294 return strings.Split(output, " ")
295 }
296
297 func (kubectl KubectlRunner) PodsShouldBeRunning(project string, regex string) {
298
299 pods := kubectl.GetAllPodsInNs(project)
300
301 pod := regexp.MustCompile(regex).FindString(pods)
302
303 args := []string{"get", ResourceTypePod, pod, "-o", "template=\"{{.status.phase}}\"", "-n", project}
304 kubectl.WaitForRunnerCmdOut(args, 1, true, func(output string) bool {
305 return strings.Contains(output, "Running")
306 })
307 }
308
309
310
311
312
313
314
315
316
317
318 func (kubectl KubectlRunner) WaitForRunnerCmdOut(args []string, timeout int, errOnFail bool, check func(output string) bool, includeStdErr ...bool) bool {
319 pingTimeout := time.After(time.Duration(timeout) * time.Minute)
320
321
322 tick := time.Tick(time.Second)
323 for {
324 select {
325 case <-pingTimeout:
326 Fail(fmt.Sprintf("Timeout after %v minutes", timeout))
327
328 case <-tick:
329 session := CmdRunner(kubectl.path, args...)
330 if errOnFail {
331 Eventually(session).Should(gexec.Exit(0), runningCmd(session.Command))
332 } else {
333 Eventually(session).Should(gexec.Exit(), runningCmd(session.Command))
334 }
335 session.Wait()
336 output := string(session.Out.Contents())
337
338 if len(includeStdErr) > 0 && includeStdErr[0] {
339 output += "\n"
340 output += string(session.Err.Contents())
341 }
342 if check(strings.TrimSpace(output)) {
343 return true
344 }
345 }
346 }
347 }
348
349
350 func (kubectl KubectlRunner) CreateSecret(secretName, secretPass, project string) {
351 Cmd(kubectl.path, "create", "secret", "generic", secretName, "--from-literal=password="+secretPass, "-n", project).ShouldPass()
352 }
353
354
355 func (kubectl KubectlRunner) GetSecrets(project string) string {
356 return GetSecrets(kubectl.path, project)
357 }
358
359
360 func (kubectl KubectlRunner) GetEnvRefNames(componentName, appName, projectName string) []string {
361 return GetEnvRefNames(kubectl.path, componentName, appName, projectName)
362 }
363
364
365 func (kubectl KubectlRunner) GetEnvFromEntry(componentName string, appName string, projectName string) string {
366 return GetEnvFromEntry(kubectl.path, componentName, appName, projectName)
367 }
368
369
370 func (kubectl KubectlRunner) GetVolumeNamesFromDeployment(componentName, appName, projectName string) map[string]string {
371 return GetVolumeNamesFromDeployment(kubectl.path, componentName, appName, projectName)
372 }
373
374
375 func (kubectl KubectlRunner) addConfigMapForCleanup(projectName string) {
376 Cmd(kubectl.path, "create", "configmap", "config-map-for-cleanup", "--from-literal", "type=testing", "--from-literal", "team=odo", "-n", projectName).ShouldPass()
377 }
378
379
380
381 func (kubectl KubectlRunner) ScalePodToZero(componentName, appName, projectName string) {
382 podName := kubectl.GetRunningPodNameByComponent(componentName, projectName)
383 Cmd(kubectl.path, "scale", "deploy", strings.Join([]string{componentName, appName}, "-"), "--replicas=0").ShouldPass()
384 kubectl.WaitForRunnerCmdOut([]string{"get", "-n", projectName, ResourceTypePod, podName}, 1, false, func(output string) bool {
385 return !strings.Contains(output, podName)
386 })
387 }
388
389 func (kubectl KubectlRunner) GetBindableKinds() (string, string) {
390 return Cmd(kubectl.path, "get", "bindablekinds", "bindable-kinds", "-ojsonpath='{.status[*].kind}'").ShouldRun().OutAndErr()
391 }
392
393 func (kubectl KubectlRunner) GetServiceBinding(name, projectName string) (string, string) {
394 return Cmd(kubectl.path, "get", "servicebinding", name, "-n", projectName).ShouldRun().OutAndErr()
395 }
396
397 func (kubectl KubectlRunner) EnsureOperatorIsInstalled(partialOperatorName string) {
398 WaitForCmdOut(kubectl.path, []string{"get", "csv", "-o", "jsonpath={.items[?(@.status.phase==\"Succeeded\")].metadata.name}"}, 4, true, func(output string) bool {
399 return strings.Contains(output, partialOperatorName)
400 })
401 }
402
403 func (kubectl KubectlRunner) EnsurePodIsUp(namespace, podName string) {
404 WaitForCmdOut(kubectl.path, []string{"get", "pods", "-n", namespace, "-o", "jsonpath='{range .items[*]}{.metadata.name}'"}, 4, true, func(output string) bool {
405 return strings.Contains(output, podName)
406 })
407 }
408
409 func (kubectl KubectlRunner) GetNamespaceProject() string {
410 return Cmd(kubectl.path, "get", "namespace").ShouldPass().Out()
411 }
412
413 func (kubectl KubectlRunner) HasNamespaceProject(name string) bool {
414 out := Cmd(kubectl.path, "get", "namespace", name, "-o", "jsonpath={.metadata.name}").
415 ShouldRun().Out()
416 return strings.Contains(out, name)
417 }
418
419 func (kubectl KubectlRunner) ListNamespaceProject(name string) {
420 Eventually(func() string {
421 return Cmd(kubectl.path, "get", "ns").ShouldRun().Out()
422 }, 30, 1).Should(ContainSubstring(name))
423 }
424
425 func (kubectl KubectlRunner) GetActiveNamespace() string {
426 return Cmd(kubectl.path, "config", "view", "--minify", "-ojsonpath={..namespace}").ShouldPass().Out()
427 }
428
429 func (kubectl KubectlRunner) GetAllNamespaceProjects() []string {
430 output := Cmd(kubectl.path, "get", "namespaces",
431 "-o", "custom-columns=NAME:.metadata.name",
432 "--no-headers").ShouldPass().Out()
433 result, err := ExtractLines(output)
434 Expect(err).ShouldNot(HaveOccurred())
435 return result
436 }
437
438 func (kubectl KubectlRunner) GetLogs(podName string) string {
439 output := Cmd(kubectl.path, "logs", podName).ShouldPass().Out()
440 return output
441 }
442
443 func (kubectl KubectlRunner) AssertContainsLabel(kind, namespace, componentName, appName, mode, key, value string) {
444 selector := labels.Builder().WithComponentName(componentName).WithAppName(appName).WithMode(mode).SelectorFlag()
445 all := Cmd(kubectl.path, "get", kind, selector, "-n", namespace, "-o", "jsonpath={.items[0].metadata.labels}").ShouldPass().Out()
446 Expect(all).To(ContainSubstring(fmt.Sprintf(`"%s":"%s"`, key, value)))
447 }
448
449 func (kubectl KubectlRunner) AssertNoContainsLabel(kind, namespace, componentName, appName, mode, key string) {
450 selector := labels.Builder().WithComponentName(componentName).WithAppName(appName).WithMode(mode).SelectorFlag()
451 all := Cmd(kubectl.path, "get", kind, selector, "-n", namespace, "-o", "jsonpath={.items[0].metadata.labels}").ShouldPass().Out()
452 Expect(all).ToNot(ContainSubstring(fmt.Sprintf(`"%s"`, key)))
453 }
454
455 func (kubectl KubectlRunner) AssertNonAuthenticated() {
456
457 }
458
459 func (kubectl KubectlRunner) GetVersion() string {
460 res := Cmd(kubectl.path, "version", "--output=json").ShouldPass().Out()
461 var js map[string]interface{}
462 err := json.Unmarshal([]byte(res), &js)
463 Expect(err).ShouldNot(HaveOccurred())
464 sv := js["serverVersion"].(map[string]interface{})
465 minor := sv["minor"].(string)
466 major := sv["major"].(string)
467 return major + "." + minor
468 }
469
470 func (kubectl KubectlRunner) SetLabelsOnNamespace(ns string, labelValues ...string) {
471 args := []string{"label", "namespaces", ns}
472 args = append(args, labelValues...)
473 Cmd(kubectl.path, args...).ShouldPass()
474 }
475
View as plain text