Files
odo/tests/integration/cmd_dev_test.go
Armel Soro b5ea6f144b Revisit CI to spin up clusters on-demand (#7159)
* Label ServiceBinding tests, so we can run them separately

They require installing additional components in the cluster (OLM, SBO, ...).

* Add GH Workflow for most of our tests (including cluster-related tests)

This allows to easily test even multiple versions of Kubernetes if needed.

For easier reporting and visualisation (and also avoid rebuilding odo many times),
Podman tests have also been relocated in this same Workflow.

Notes:
I tried to spin up lightweight OpenShift clusters but gave up because of several issues:
- MicroShift: I tried to use the aio container image, but this one is no longer maintained and is pretty old version of OCP.
Trying to follow the official guidelines did not work either because a base RHEL OS is mandatory
- CRC/OpenShiftLocal with Microshift preset: didnt pass the pre-checks because it detected an issue with nested virtualization on the GH Runner.

* Drop unused code in helper_oc and use namespace instead of project

When testing on Microshift, it seems that the Project API is purposely not implemented on MicroShift
2023-12-08 23:28:10 +00:00

5022 lines
187 KiB
Go

package integration
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
"strings"
"sync"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/pointer"
"github.com/redhat-developer/odo/pkg/labels"
"github.com/redhat-developer/odo/pkg/remotecmd"
segment "github.com/redhat-developer/odo/pkg/segment/context"
"github.com/redhat-developer/odo/pkg/state"
"github.com/redhat-developer/odo/pkg/storage"
"github.com/redhat-developer/odo/pkg/util"
"github.com/onsi/gomega/gexec"
"github.com/redhat-developer/odo/tests/helper"
)
var _ = Describe("odo dev command tests", func() {
var cmpName string
var commonVar helper.CommonVar
// This is run before every Spec (It)
var _ = BeforeEach(func() {
commonVar = helper.CommonBeforeEach()
cmpName = helper.RandString(6)
helper.Chdir(commonVar.Context)
Expect(helper.VerifyFileExists(".odo/env/env.yaml")).To(BeFalse())
})
// This is run after every Spec (It)
var _ = AfterEach(func() {
helper.CommonAfterEach(commonVar)
})
When("directory is empty", func() {
BeforeEach(func() {
Expect(helper.ListFilesInDir(commonVar.Context)).To(HaveLen(0))
})
It("should error", func() {
output := helper.Cmd("odo", "dev", "--random-ports").ShouldFail().Err()
Expect(output).To(ContainSubstring("The current directory does not represent an odo component"))
})
})
When("a component is bootstrapped", func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
devfile := "devfile.yaml"
if os.Getenv("KUBERNETES") == "true" {
devfile = "devfile-fsgroup.yaml"
}
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", devfile)).ShouldPass()
Expect(helper.VerifyFileExists(".odo/env/env.yaml")).To(BeFalse())
})
It("should fail to run odo dev when not connected to any cluster", Label(helper.LabelNoCluster), func() {
errOut := helper.Cmd("odo", "dev").ShouldFail().Err()
Expect(errOut).To(ContainSubstring("unable to access the cluster"))
})
It("should fail to run odo dev when podman is nil", Label(helper.LabelPodman), func() {
errOut := helper.Cmd("odo", "dev", "--platform", "podman").WithEnv("PODMAN_CMD=echo").ShouldFail().Err()
Expect(errOut).To(ContainSubstring("unable to access podman"))
})
It("should start on cluster even if Podman client takes long to initialize", func() {
if runtime.GOOS == "windows" {
Skip("skipped on Windows as it requires Unix permissions")
}
_, err := os.Stat("/bin/bash")
if errors.Is(err, fs.ErrNotExist) {
Skip("skipped because bash executable not found")
}
// odo dev on cluster should not wait for the Podman client to initialize properly, if this client takes very long.
// See https://github.com/redhat-developer/odo/issues/6575.
// StartDevMode will time out if Podman client takes too long to initialize.
delayer := helper.GenerateDelayedPodman(commonVar.Context, 10)
var devSession helper.DevSession
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: false,
CmdlineArgs: []string{"-v", "3"},
EnvVars: []string{
"PODMAN_CMD=" + delayer,
"PODMAN_CMD_INIT_TIMEOUT=1s",
},
})
Expect(err).ShouldNot(HaveOccurred())
defer func() {
devSession.Kill()
devSession.WaitEnd()
}()
Expect(devSession.ErrOut).Should(MatchRegexp("timeout \\([^()]+\\) while waiting for Podman version"))
})
When("using a default namespace", func() {
BeforeEach(func() {
commonVar.CliRunner.SetProject("default")
})
AfterEach(func() {
commonVar.CliRunner.SetProject(commonVar.Project)
})
It("should print warning about default namespace when running odo dev", func() {
namespace := "project"
if helper.IsKubernetesCluster() {
namespace = "namespace"
}
// Resources might not pass the security requirements on the default namespace on certain clusters (case of OpenShift 4.14),
// but this is not important here, as we just want to make sure that the warning message is displayed (even if the Dev Session does not start correctly).
devSession, err := helper.WaitForDevModeToContain(helper.DevSessionOpts{}, "Running on the cluster in Dev mode", false, false)
Expect(err).ShouldNot(HaveOccurred())
defer func() {
devSession.Stop()
devSession.WaitEnd()
}()
Expect(devSession.ErrOut).To(ContainSubstring(fmt.Sprintf("You are using \"default\" %[1]s, odo may not work as expected in the default %[1]s.", namespace)))
})
})
It("should add annotation to use ImageStreams", func() {
// #6376
err := helper.RunDevMode(helper.DevSessionOpts{}, func(session *gexec.Session, outContents, errContents string, ports map[string]string) {
annotations := commonVar.CliRunner.GetAnnotationsDeployment(cmpName, "app", commonVar.Project)
Expect(annotations["alpha.image.policy.openshift.io/resolve-names"]).To(Equal("*"))
})
Expect(err).ToNot(HaveOccurred())
})
It("should show validation errors if the devfile is incorrect", func() {
err := helper.RunDevMode(helper.DevSessionOpts{}, func(session *gexec.Session, outContents, errContents string, ports map[string]string) {
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "kind: run", "kind: build")
helper.WaitForOutputToContain("Error occurred on Push", 180, 10, session)
})
Expect(err).ToNot(HaveOccurred())
})
for _, podman := range []bool{true, false} {
podman := podman
It("should not sync .git directory", helper.LabelPodmanIf(podman, func() {
helper.MakeDir(".git")
err := helper.CreateFileWithContent(filepath.Join(commonVar.Context, ".git", "file.txt"), "aze")
Expect(err).ToNot(HaveOccurred())
helper.MakeDir("notgit")
err = helper.CreateFileWithContent(filepath.Join(commonVar.Context, "notgit", "otherfile.txt"), "aze")
Expect(err).ToNot(HaveOccurred())
err = helper.RunDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
}, func(session *gexec.Session, outContents, errContents string, ports map[string]string) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
component.Exec("runtime", []string{"ls", "/projects/.git/file.txt"}, pointer.Bool(false))
component.Exec("runtime", []string{"ls", "/projects/notgit/otherfile.txt"}, pointer.Bool(true))
})
Expect(err).ToNot(HaveOccurred())
}))
It("should sync .git directory and subfiles with --sync-git-dir", helper.LabelPodmanIf(podman, func() {
gitignorePath := filepath.Join(commonVar.Context, ".gitignore")
if err := helper.CreateFileWithContent(gitignorePath, `file.txt`); err != nil {
fmt.Printf("the .gitignore file was not created, reason %v", err.Error())
}
helper.MakeDir(".git")
err := helper.CreateFileWithContent(filepath.Join(commonVar.Context, ".git", "file.txt"), "aze")
Expect(err).ToNot(HaveOccurred())
err = helper.RunDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
SyncGitDir: true,
}, func(session *gexec.Session, outContents, errContents string, ports map[string]string) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
component.Exec("runtime", []string{"ls", "/projects/.git/file.txt"}, pointer.Bool(true))
})
Expect(err).ToNot(HaveOccurred())
}))
It("should use the index information from previous push operation", helper.LabelPodmanIf(podman, func() {
// Create a new file A
fileAPath, fileAText := helper.CreateSimpleFile(commonVar.Context, "my-file-", ".txt")
// watch that project
err := helper.RunDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
}, func(session *gexec.Session, outContents, errContents string, ports map[string]string) {
// Change some other file B
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "App started", "App is super started")
// File should exist, and its content should match what we initially set it to
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
execResult, _ := component.Exec("runtime", []string{"cat", "/projects/" + filepath.Base(fileAPath)}, pointer.Bool(true))
Expect(execResult).To(ContainSubstring(fileAText))
})
Expect(err).ToNot(HaveOccurred())
}))
It("should fail when using --random-ports and --port-forward together", helper.LabelPodmanIf(podman, func() {
args := []string{"dev", "--random-ports", "--port-forward=8000:3000"}
if podman {
args = append(args, "--platform", "podman")
}
errOut := helper.Cmd("odo", args...).ShouldFail().Err()
Expect(errOut).To(ContainSubstring("--random-ports and --port-forward cannot be used together"))
}))
for _, opts := range [][]string{
{"--build-command", "my-build-cmd"},
{"--run-command", "my-run-cmd"},
{"--build-command", "my-build-cmd", "--run-command", "my-run-cmd"},
} {
opts := opts
It("should fail when using --no-commands and --build-command and/or --run-command together", helper.LabelPodmanIf(podman, func() {
args := []string{"dev", "--no-commands"}
args = append(args, opts...)
if podman {
args = append(args, "--platform", "podman")
}
errOut := helper.Cmd("odo", args...).ShouldFail().Err()
Expect(errOut).To(ContainSubstring("--no-commands cannot be used with --build-command or --run-command"))
}))
}
for _, debug := range []bool{false, true} {
debug := debug
When(fmt.Sprintf("running with --no-commands (debug=%v)", debug), helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
BeforeEach(func() {
args := []string{"--no-commands"}
if debug {
args = append(args, "--debug")
}
opts := helper.DevSessionOpts{
RunOnPodman: podman,
CmdlineArgs: args,
}
var err error
devSession, err = helper.StartDevMode(opts)
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should start the dev session", func() {
By("not executing the build command", func() {
for _, out := range []string{devSession.StdOut, devSession.ErrOut} {
Expect(out).ShouldNot(ContainSubstring("Building your application in container on cluster"))
}
})
By("not executing the run command", func() {
for _, out := range []string{devSession.StdOut, devSession.ErrOut} {
Expect(out).ShouldNot(ContainSubstring("Executing the application"))
}
})
By("syncing the files", func() {
Expect(devSession.StdOut).Should(ContainSubstring("Syncing files into the container"))
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
execResult, _ := component.Exec("runtime", []string{"ls", "-lai", "/projects"}, pointer.Bool(true))
for _, fileName := range []string{"server.js", "package.json"} {
Expect(execResult).Should(ContainSubstring(fileName))
}
execResult, _ = component.Exec("runtime", []string{"cat", "/projects/server.js"}, pointer.Bool(true))
Expect(execResult).Should(ContainSubstring("App started"))
})
By("setting up port forwarding", func() {
Expect(devSession.Endpoints).ShouldNot(BeEmpty())
_, ok := devSession.Endpoints["3000"]
Expect(ok).To(BeTrue(), fmt.Sprintf("missing port forwarded for 3000: %v", devSession.Endpoints))
})
})
}))
}
}
It("ensure that index information is updated", func() {
err := helper.RunDevMode(helper.DevSessionOpts{}, func(session *gexec.Session, outContents, errContents string, ports map[string]string) {
indexAfterPush, err := util.ReadFileIndex(filepath.Join(commonVar.Context, ".odo", "odo-file-index.json"))
Expect(err).ToNot(HaveOccurred())
// Create a new file A
fileAPath, _ := helper.CreateSimpleFile(commonVar.Context, "my-file-", ".txt")
// Wait for the new file to exist in the index
Eventually(func() bool {
newIndexAfterPush, readErr := util.ReadFileIndex(filepath.Join(commonVar.Context, ".odo", "odo-file-index.json"))
if readErr != nil {
fmt.Fprintln(GinkgoWriter, "New index not found or could not be read", readErr)
return false
}
_, exists := newIndexAfterPush.Files[filepath.Base(fileAPath)]
if !exists {
fmt.Fprintln(GinkgoWriter, "path", fileAPath, "not found.", readErr)
}
return exists
}, 180, 10).Should(Equal(true))
// Delete file A and verify that it disappears from the index
err = os.Remove(fileAPath)
Expect(err).ToNot(HaveOccurred())
Eventually(func() bool {
newIndexAfterPush, err := util.ReadFileIndex(filepath.Join(commonVar.Context, ".odo", "odo-file-index.json"))
if err != nil {
fmt.Fprintln(GinkgoWriter, "New index not found or could not be read", err)
return false
}
// Sanity test: at least one file should be present
if len(newIndexAfterPush.Files) == 0 {
return false
}
// The fileA file should NOT be found
match := false
for relativeFilePath := range newIndexAfterPush.Files {
if strings.Contains(relativeFilePath, filepath.Base(fileAPath)) {
match = true
}
}
return !match
}, 180, 10).Should(Equal(true))
// Change server.js
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "App started", "App is super started")
helper.WaitForOutputToContain("server.js", 180, 10, session)
// Wait for the size values in the old and new index files to differ, indicating that watch has updated the index
Eventually(func() bool {
newIndexAfterPush, err := util.ReadFileIndex(filepath.Join(commonVar.Context, ".odo", "odo-file-index.json"))
if err != nil {
fmt.Fprintln(GinkgoWriter, "New index not found or could not be read", err)
return false
}
beforePushValue, exists := indexAfterPush.Files["server.js"]
if !exists {
fmt.Fprintln(GinkgoWriter, "server.js not found in old index file")
return false
}
afterPushValue, exists := newIndexAfterPush.Files["server.js"]
if !exists {
fmt.Fprintln(GinkgoWriter, "server.js not found in new index file")
return false
}
fmt.Fprintln(GinkgoWriter, "comparing old and new file sizes", beforePushValue.Size, afterPushValue.Size)
return beforePushValue.Size != afterPushValue.Size
}, 180, 10).Should(Equal(true))
})
Expect(err).ToNot(HaveOccurred())
})
It("should not set securitycontext for podsecurity admission", func() {
if os.Getenv("KUBERNETES") != "true" {
Skip("This is a Kubernetes specific scenario, skipping")
}
err := helper.RunDevMode(helper.DevSessionOpts{}, func(session *gexec.Session, outContents, errContents string, ports map[string]string) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
podDef := component.GetPodDef()
Expect(podDef.Spec.SecurityContext.RunAsNonRoot).To(BeNil())
Expect(podDef.Spec.SecurityContext.SeccompProfile).To(BeNil())
})
Expect(err).ToNot(HaveOccurred())
})
When("pod security is enforced as restricted", func() {
BeforeEach(func() {
commonVar.CliRunner.SetLabelsOnNamespace(
commonVar.Project,
"pod-security.kubernetes.io/enforce=restricted",
"pod-security.kubernetes.io/enforce-version=latest",
)
})
It("should set securitycontext for podsecurity admission", func() {
if os.Getenv("KUBERNETES") != "true" {
Skip("This is a Kubernetes specific scenario, skipping")
}
err := helper.RunDevMode(helper.DevSessionOpts{}, func(session *gexec.Session, outContents, errContents string, ports map[string]string) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
podDef := component.GetPodDef()
Expect(*podDef.Spec.SecurityContext.RunAsNonRoot).To(BeTrue())
Expect(string(podDef.Spec.SecurityContext.SeccompProfile.Type)).To(Equal("RuntimeDefault"))
})
Expect(err).ToNot(HaveOccurred())
})
})
When("a state file is not writable", func() {
BeforeEach(func() {
stateFile := filepath.Join(commonVar.Context, ".odo", "devstate.json")
helper.MakeDir(filepath.Dir(stateFile))
Expect(helper.CreateFileWithContent(stateFile, "")).ToNot(HaveOccurred())
Expect(os.Chmod(stateFile, 0400)).ToNot(HaveOccurred())
})
It("should fail running odo dev", func() {
res := helper.Cmd("odo", "dev", "--random-ports").ShouldFail()
stdout := res.Out()
stderr := res.Err()
Expect(stdout).To(ContainSubstring("Cleaning"))
Expect(stderr).To(ContainSubstring("unable to save state file"))
})
})
for _, podman := range []bool{true, false} {
podman := podman
When("recording telemetry data", helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.EnableTelemetryDebug()
devSession, _ := helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
devSession.Stop()
devSession.WaitEnd()
})
AfterEach(func() {
helper.ResetTelemetry()
})
It("should record the telemetry data correctly", func() {
td := helper.GetTelemetryDebugData()
Expect(td.Event).To(ContainSubstring("odo dev"))
Expect(td.Properties.Success).To(BeTrue())
Expect(td.Properties.Error).ToNot(ContainSubstring("user interrupted"))
Expect(td.Properties.CmdProperties[segment.ComponentType]).To(ContainSubstring("nodejs"))
Expect(td.Properties.CmdProperties[segment.Language]).To(ContainSubstring("nodejs"))
Expect(td.Properties.CmdProperties[segment.ProjectType]).To(ContainSubstring("nodejs"))
Expect(td.Properties.CmdProperties).Should(HaveKey(segment.Caller))
Expect(td.Properties.CmdProperties[segment.Caller]).To(BeEmpty())
experimentalValue := false
Expect(td.Properties.CmdProperties[segment.ExperimentalMode]).To(Equal(experimentalValue))
if podman {
Expect(td.Properties.CmdProperties[segment.Platform]).To(Equal("podman"))
Expect(td.Properties.CmdProperties[segment.PlatformVersion]).ToNot(BeEmpty())
} else if os.Getenv("KUBERNETES") == "true" {
Expect(td.Properties.CmdProperties[segment.Platform]).To(Equal("kubernetes"))
serverVersion := commonVar.CliRunner.GetVersion()
Expect(td.Properties.CmdProperties[segment.PlatformVersion]).To(ContainSubstring(serverVersion))
} else {
Expect(td.Properties.CmdProperties[segment.Platform]).To(Equal("openshift"))
serverVersion := commonVar.CliRunner.GetVersion()
if serverVersion == "" {
Expect(td.Properties.CmdProperties[segment.PlatformVersion]).To(BeNil())
} else {
Expect(td.Properties.CmdProperties[segment.PlatformVersion]).To(ContainSubstring(serverVersion))
}
}
})
}))
When("odo dev is executed", helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
When("odo dev is stopped", func() {
BeforeEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should delete the component from the platform", func() {
By("deleting the component", func() {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
component.ExpectIsNotDeployed()
})
By("exiting successfully", func() {
Expect(devSession.GetExitCode()).Should(Equal(0), "unexpected exit code for the dev session")
})
})
})
}))
}
When("an env.yaml file contains a non-current Project", func() {
BeforeEach(func() {
odoDir := filepath.Join(commonVar.Context, ".odo", "env")
helper.MakeDir(odoDir)
err := helper.CreateFileWithContent(filepath.Join(odoDir, "env.yaml"), `
ComponentSettings:
Project: another-project
`)
Expect(err).ShouldNot(HaveOccurred())
})
When("odo dev is executed", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Kill()
devSession.WaitEnd()
})
It("should not have modified env.yaml, and use current namespace", func() {
helper.FileShouldContainSubstring(".odo/env/env.yaml", "Project: another-project")
deploymentName := fmt.Sprintf("%s-%s", cmpName, "app")
out := commonVar.CliRunner.Run("get", "deployments", deploymentName, "-n", commonVar.Project).Out.Contents()
Expect(out).To(ContainSubstring(deploymentName))
})
})
})
When("odo dev is executed and Ephemeral is set to false", func() {
var devSession helper.DevSession
BeforeEach(func() {
if os.Getenv("KUBERNETES") == "true" {
Skip("This is a OpenShift specific scenario, skipping")
}
helper.Cmd("odo", "preference", "set", "-f", "Ephemeral", "false").ShouldPass()
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
When("killing odo dev and running odo delete component --wait", func() {
BeforeEach(func() {
devSession.Kill()
devSession.WaitEnd()
helper.Cmd("odo", "delete", "component", "--wait", "-f").ShouldPass()
})
It("should have deleted all resources before returning", func() {
By("deleting the service", func() {
services := commonVar.CliRunner.GetServices(commonVar.Project)
Expect(services).To(BeEmpty())
})
By("deleting the PVC", func() {
pvcs := commonVar.CliRunner.GetAllPVCNames(commonVar.Project)
Expect(pvcs).To(BeEmpty())
})
By("deleting the pod", func() {
pods := commonVar.CliRunner.GetAllPodNames(commonVar.Project)
Expect(pods).To(BeEmpty())
})
})
})
When("killing odo dev and another process replaces it", func() {
var newDevSession helper.DevSession
BeforeEach(func() {
pid := devSession.PID()
devSession.Kill()
devSession.WaitEnd()
devstate := fmt.Sprintf(".odo/devstate.%d.json", pid)
newdevstate := fmt.Sprintf(".odo/devstate.%d.json", helper.GetFirstProcess())
helper.ReplaceString(
devstate,
fmt.Sprintf("\"pid\": %d", pid),
fmt.Sprintf("\"pid\": %d", helper.GetFirstProcess()))
err := os.Rename(devstate, newdevstate)
Expect(err).ToNot(HaveOccurred())
})
It("should restart a new session successfully", func() {
var err error
newDevSession, err = helper.StartDevMode(helper.DevSessionOpts{
VerboseLevel: "4",
})
Expect(err).ToNot(HaveOccurred())
logMsg := fmt.Sprintf("process %d exists but is not odo, ignoring", helper.GetFirstProcess())
Expect(newDevSession.ErrOut).Should(ContainSubstring(logMsg))
newDevSession.Stop()
newDevSession.WaitEnd()
})
})
When("stopping odo dev normally", func() {
BeforeEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should have deleted all resources before returning", func() {
By("deleting the service", func() {
services := commonVar.CliRunner.GetServices(commonVar.Project)
Expect(services).To(BeEmpty())
})
By("deleting the PVC", func() {
pvcs := commonVar.CliRunner.GetAllPVCNames(commonVar.Project)
Expect(pvcs).To(BeEmpty())
})
By("deleting the pod", func() {
pods := commonVar.CliRunner.GetAllPodNames(commonVar.Project)
Expect(pods).To(BeEmpty())
})
})
})
})
When("odo dev is executed and Ephemeral is set to false", func() {
var devSession helper.DevSession
BeforeEach(func() {
if os.Getenv("KUBERNETES") == "true" {
Skip("This is a OpenShift specific scenario, skipping")
}
helper.Cmd("odo", "preference", "set", "-f", "Ephemeral", "false").ShouldPass()
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
if os.Getenv("KUBERNETES") == "true" {
return
}
// We stop the process so the process does not remain after the end of the tests
devSession.Kill()
devSession.WaitEnd()
})
It("should have created resources", func() {
By("creating a service", func() {
services := commonVar.CliRunner.GetServices(commonVar.Project)
Expect(services).To(SatisfyAll(
Not(BeEmpty()),
ContainSubstring(fmt.Sprintf("%s-app", cmpName)),
))
})
By("creating a PVC", func() {
pvcs := commonVar.CliRunner.GetAllPVCNames(commonVar.Project)
Expect(strings.Join(pvcs, "\n")).To(SatisfyAll(
Not(BeEmpty()),
ContainSubstring(fmt.Sprintf("%s-app", cmpName)),
))
})
By("creating a pod", func() {
pods := commonVar.CliRunner.GetAllPodNames(commonVar.Project)
Expect(strings.Join(pods, "\n")).To(SatisfyAll(
Not(BeEmpty()),
ContainSubstring(fmt.Sprintf("%s-app-", cmpName)),
))
})
// Returned pvc yaml contains ownerreference
By("creating a pvc with ownerreference", func() {
output := commonVar.CliRunner.Run("get", "pvc", "--namespace", commonVar.Project, "-o", `jsonpath='{range .items[*].metadata.ownerReferences[*]}{@..kind}{"/"}{@..name}{"\n"}{end}'`).Out.Contents()
Expect(string(output)).To(ContainSubstring(fmt.Sprintf("Deployment/%s-app", cmpName)))
})
})
})
for _, podman := range []bool{true, false} {
podman := podman
for _, noCommandsFlag := range []string{"", "--no-commands", "--no-commands=false"} {
noCommandsFlag := noCommandsFlag
suffix := "without --no-commands"
if noCommandsFlag != "" {
suffix = "with " + noCommandsFlag
}
When("odo is executed with --no-watch flag and "+suffix, helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
args := []string{"--no-watch"}
if noCommandsFlag != "" {
args = append(args, noCommandsFlag)
}
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: args,
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
When("a file in component directory is modified", func() {
BeforeEach(func() {
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "App started", "App is super started")
})
It("should not trigger a push", func() {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
execResult, _ := component.Exec("runtime", []string{"cat", "/projects/server.js"}, pointer.Bool(true))
Expect(execResult).To(ContainSubstring("App started"))
Expect(execResult).ToNot(ContainSubstring("App is super started"))
})
When("p is pressed", func() {
BeforeEach(func() {
if os.Getenv("SKIP_KEY_PRESS") == "true" {
Skip("This is a unix-terminal specific scenario, skipping")
}
devSession.PressKey('p')
})
It("should trigger a push", func() {
err := devSession.WaitSync()
Expect(err).ToNot(HaveOccurred())
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
execResult, _ := component.Exec("runtime", []string{"cat", "/projects/server.js"}, pointer.Bool(true))
Expect(execResult).To(ContainSubstring("App is super started"))
})
})
})
}))
}
}
When("a delay is necessary for the component to start and running odo dev", func() {
var devSession helper.DevSession
BeforeEach(func() {
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"),
"npm start",
// odo dev now waits some time until the app is ready or a timeout (current set to 1m) expires before starting port-forwarding.
// So we are sleeping more than the timeout.
// See https://github.com/redhat-developer/odo/issues/6667
"sleep 80 ; npm start")
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Kill()
devSession.WaitEnd()
})
It("should first fail then succeed querying endpoint", func() {
url := fmt.Sprintf("http://%s", devSession.Endpoints["3000"])
_, err := http.Get(url)
Expect(err).To(HaveOccurred())
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
Eventually(func() bool {
logs := helper.GetCliRunner().GetLogs(podName)
return strings.Contains(logs, "App started on PORT")
}, 180, 10).Should(Equal(true))
// Get new random port after restart
err = devSession.UpdateInfo()
Expect(err).ToNot(HaveOccurred())
url = fmt.Sprintf("http://%s", devSession.Endpoints["3000"])
resp, err := http.Get(url)
Expect(err).ToNot(HaveOccurred())
body, _ := io.ReadAll(resp.Body)
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
})
})
When("Automount volumes are present in the namespace", func() {
BeforeEach(func() {
commonVar.CliRunner.Run("apply", "-f", helper.GetExamplePath("manifests", "config-automount/"))
})
When("odo dev is executed", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should mount the volumes", func() {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
// Check volumes are mounted
for _, path := range []string{
"/tmp/automount-default-pvc",
"/etc/config/automount-default-configmap",
"/etc/secret/automount-default-secret",
"/tmp/automount-readonly-pvc",
"/mnt/mount-path/pvc",
"/mnt/mount-path/configmap",
"/mnt/mount-path/secret",
"/etc/config/automount-access-mode-configmap",
"/etc/config/automount-access-mode-configmap-decimal",
"/etc/secret/automount-access-mode-secret",
} {
var output string
Eventually(func() bool {
output, _ = component.Exec("runtime", []string{"df", path}, nil)
return len(output) > 0
}).WithPolling(1 * time.Second).WithTimeout(60 * time.Second).Should(BeTrue())
// This checks this is really a mount
Expect(output).ToNot(ContainSubstring("overlay"))
}
// Check files are present for configmap / secret
// and have expected access mode (by default 0644)
files := map[string]struct {
content string
accessMode string
}{
"/etc/config/automount-default-configmap/foo1": {
content: "bar1",
accessMode: "rw-r--r--",
},
"/etc/config/automount-default-configmap/ping1": {
content: "pong1",
accessMode: "rw-r--r--",
},
"/etc/secret/automount-default-secret/code1": {
content: "1234",
accessMode: "rw-r--r--",
},
"/etc/secret/automount-default-secret/secret1": {
content: "PassWd1",
accessMode: "rw-r--r--",
},
"/mnt/mount-path/configmap/foo2": {
content: "bar2",
accessMode: "rw-r--r--",
},
"/mnt/mount-path/configmap/ping2": {
content: "pong2",
accessMode: "rw-r--r--",
},
"/mnt/mount-path/secret/code2": {
content: "2345",
accessMode: "rw-r--r--",
},
"/mnt/mount-path/secret/secret2": {
content: "PassWd2",
accessMode: "rw-r--r--",
},
"/mnt/subpaths/foo5": {
content: "bar5",
accessMode: "rw-r--r--",
},
"/mnt/subpaths/ping5": {
content: "pong5",
accessMode: "rw-r--r--",
},
"/mnt/subpaths/code5": {
content: "5678",
accessMode: "rw-r--r--",
},
"/mnt/subpaths/secret5": {
content: "PassWd5",
accessMode: "rw-r--r--",
},
"/etc/config/automount-access-mode-configmap/config0444": {
content: "foo",
accessMode: "r--r--r--",
},
"/etc/config/automount-access-mode-configmap-decimal/config292": {
content: "foo-decimal",
accessMode: "r--r--r--",
},
"/etc/secret/automount-access-mode-secret/secret0444": {
content: "1234",
accessMode: "r--r--r--",
},
"/etc/config0444": {
content: "foo",
accessMode: "r--r--r--",
},
"/etc/secret0444": {
content: "5ecr3t",
accessMode: "r--r--r--",
},
}
for file, desc := range files {
output, _ := component.Exec("runtime", []string{"cat", file}, pointer.Bool(true))
Expect(output).To(Equal(desc.content))
// -L follows symlinks, to get the mode of the targeted file, as files reside on a ..data directory
output, _ = component.Exec("runtime", []string{"ls", "-lL", file}, pointer.Bool(true))
Expect(output).To(ContainSubstring(desc.accessMode))
}
envVars := map[string]string{
"foo4": "bar4",
"ping4": "pong4",
"code4": "4567",
"secret4": "PassWd4",
}
for name, value := range envVars {
output, _ := component.Exec("runtime", []string{"bash", "-c", "echo -n $" + name}, pointer.Bool(true))
Expect(output).To(Equal(value))
}
// Default PVC is not read-only
component.Exec("runtime", []string{"touch", "/tmp/automount-default-pvc/newfile"}, pointer.Bool(true))
// Read-only PVC is read-only
_, stderr := component.Exec("runtime", []string{"touch", "/tmp/automount-readonly-pvc/newfile"}, pointer.Bool(false))
Expect(stderr).To(ContainSubstring("Read-only file system"))
})
})
})
for _, podman := range []bool{true, false} {
podman := podman
When("build command takes really long to start", helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"),
"npm install",
"echo Will execute command after 20m ... && sleep 1200 && npm install")
})
It("should cancel build command and return if odo dev is stopped", func() {
opts := helper.DevSessionOpts{
RunOnPodman: podman,
}
devSession, err := helper.WaitForDevModeToContain(opts, "Building your application in container", false, false)
Expect(err).ShouldNot(HaveOccurred())
// Build is taking long => it should be cancellable
devSession.Stop()
// WaitEnd will timeout after some time, less than the execution of the build command above
devSession.WaitEnd()
})
}))
When("run command takes really long to start", helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"),
"npm start",
"echo Will execute command after 20m ... && sleep 1200 && npm start")
})
It("should cancel run command and return if odo dev is stopped", func() {
opts := helper.DevSessionOpts{
RunOnPodman: podman,
}
// Run command is launched in the background
devSession, err := helper.WaitForDevModeToContain(opts, "Waiting for the application to be ready", false, false)
Expect(err).ShouldNot(HaveOccurred())
// Build is taking long => it should be cancellable
devSession.Stop()
// WaitEnd will timeout after some time, less than the execution of the build command above
devSession.WaitEnd()
})
}))
}
})
Context("checking if odo dev matches local Devfile K8s resources and remote resources", func() {
for _, devfile := range []struct {
title string
devfileName string
envvars []string
deploymentName []string
newDeploymentName []string
}{
{
title: "without apply command",
devfileName: "devfile-with-k8s-resource.yaml",
envvars: nil,
deploymentName: []string{"my-component"},
newDeploymentName: []string{"my-new-component"},
},
{
title: "with apply command",
devfileName: "devfile-composite-apply-commands.yaml",
envvars: []string{"PODMAN_CMD=echo"},
deploymentName: []string{"my-k8s-component", "my-openshift-component"},
newDeploymentName: []string{"my-new-k8s-component", "my-new-openshift-component"},
},
} {
devfile := devfile
When(fmt.Sprintf("odo dev is executed to run a devfile containing a k8s resource %s", devfile.title), func() {
var (
devSession helper.DevSession
err error
getDeployArgs = []string{"get", "deployments", "-n", commonVar.Project}
)
BeforeEach(
func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", devfile.devfileName),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
EnvVars: devfile.envvars,
})
Expect(err).To(BeNil())
// ensure the deployment is created by `odo dev`
out := string(commonVar.CliRunner.Run(getDeployArgs...).Out.Contents())
helper.MatchAllInOutput(out, devfile.deploymentName)
// we fake the new deployment creation by changing the old deployment's name
helper.ReplaceStrings(filepath.Join(commonVar.Context, "devfile.yaml"), devfile.deploymentName, devfile.newDeploymentName)
err := devSession.WaitSync()
Expect(err).To(BeNil())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should have deleted the old resource and created the new resource", func() {
getDeployments := string(commonVar.CliRunner.Run(getDeployArgs...).Out.Contents())
for i := range devfile.deploymentName {
Expect(getDeployments).ToNot(ContainSubstring(devfile.deploymentName[i]))
Expect(getDeployments).To(ContainSubstring(devfile.newDeploymentName[i]))
}
})
})
}
})
for _, ctx := range []struct {
title string
devfile string
matchResources []string
}{
{
title: "odo dev is executed to run a devfile containing a k8s resource",
devfile: "devfile-with-k8s-resource.yaml",
matchResources: []string{"my-component"},
},
{
title: "odo dev is executed to run a devfile containing multiple k8s resource defined under a single Devfile component",
devfile: "devfile-with-multiple-k8s-resources-in-single-component.yaml",
matchResources: []string{"my-component", "my-component-2"},
},
} {
ctx := ctx
When(ctx.title, func() {
var (
devSession helper.DevSession
err error
getDeployArgs = []string{"get", "deployments", "-n", commonVar.Project}
)
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", ctx.devfile),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).To(BeNil())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should have created the necessary k8s resources", func() {
By("checking the output for the resources", func() {
helper.MatchAllInOutput(devSession.StdOut, ctx.matchResources)
})
By("fetching the resources from the cluster", func() {
helper.MatchAllInOutput(string(commonVar.CliRunner.Run(getDeployArgs...).Out.Contents()), ctx.matchResources)
})
})
})
}
for _, podman := range []bool{true, false} {
podman := podman
Context(fmt.Sprintf("multiple dev sessions with different project are running on same platform (podman=%v), same port", podman), helper.LabelPodmanIf(podman, func() {
const (
nodejsContainerPort = "3000"
goContainerPort = "8080"
nodejsCustomAddress = "127.0.10.3"
goCustomAddress = "127.0.10.1"
)
var (
nodejsProject, goProject string
nodejsDevSession, goDevSession helper.DevSession
nodejsLocalPort = helper.GetCustomStartPort()
goLocalPort = nodejsLocalPort + 1
nodejsURL = fmt.Sprintf("%s:%d", nodejsCustomAddress, nodejsLocalPort)
goURL = fmt.Sprintf("%s:%d", goCustomAddress, goLocalPort)
)
BeforeEach(func() {
if runtime.GOOS == "darwin" {
Skip("cannot run this test out of the box on macOS because the test uses a custom address in the range 127.0.0/8 and for macOS we need to ensure the addresses are open for request before using them; Ref: https://superuser.com/questions/458875/how-do-you-get-loopback-addresses-other-than-127-0-0-1-to-work-on-os-x#458877")
}
nodejsProject = helper.CreateNewContext()
goProject = helper.CreateNewContext()
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), nodejsProject)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
filepath.Join(nodejsProject, "devfile.yaml"),
cmpName+"-nodejs",
)
helper.CopyExample(filepath.Join("source", "go"), goProject)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "go-devfiles", "devfile.yaml"),
filepath.Join(goProject, "devfile.yaml"),
cmpName+"-go",
)
})
AfterEach(func() {
helper.DeleteDir(nodejsProject)
helper.DeleteDir(goProject)
})
When("odo dev session is run for nodejs component", func() {
BeforeEach(func() {
helper.Chdir(nodejsProject)
var err error
nodejsDevSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: []string{"--port-forward", fmt.Sprintf("%d:%s", nodejsLocalPort, nodejsContainerPort)},
RunOnPodman: podman,
TimeoutInSeconds: 0,
NoRandomPorts: true,
CustomAddress: nodejsCustomAddress,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
nodejsDevSession.Stop()
nodejsDevSession.WaitEnd()
helper.Chdir(commonVar.Context)
})
When("odo dev session is run for go project on the same port but different address", func() {
BeforeEach(func() {
helper.Chdir(goProject)
var err error
goDevSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: []string{"--port-forward", fmt.Sprintf("%d:%s", goLocalPort, goContainerPort)},
RunOnPodman: podman,
TimeoutInSeconds: 0,
NoRandomPorts: true,
CustomAddress: goCustomAddress,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
goDevSession.Stop()
goDevSession.WaitEnd()
helper.Chdir(commonVar.Context)
})
It("should be able to run both the sessions", func() {
Expect(nodejsDevSession.Endpoints[nodejsContainerPort]).To(BeEquivalentTo(nodejsURL))
Expect(goDevSession.Endpoints[goContainerPort]).To(BeEquivalentTo(goURL))
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", nodejsURL), "Hello from Node.js Starter Application!", 1, 0, 200)
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", goURL), "Hello, !", 1, 0, 200)
})
When("go and nodejs files are modified", func() {
BeforeEach(func() {
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
err := nodejsDevSession.WaitSync()
Expect(err).ToNot(HaveOccurred())
}()
go func() {
defer wg.Done()
err := goDevSession.WaitSync()
Expect(err).ToNot(HaveOccurred())
}()
helper.ReplaceString(filepath.Join(goProject, "main.go"), "Hello, %s!", "H3110, %s!")
helper.ReplaceString(filepath.Join(nodejsProject, "server.js"), "Hello from Node.js", "H3110 from Node.js")
wg.Wait()
})
It("should be possible to access both the projects on same address and port", func() {
Expect(nodejsDevSession.Endpoints[nodejsContainerPort]).To(BeEquivalentTo(nodejsURL))
Expect(goDevSession.Endpoints[goContainerPort]).To(BeEquivalentTo(goURL))
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", nodejsURL), "H3110 from Node.js Starter Application!", 1, 0, 200)
helper.HttpWaitForWithStatus(fmt.Sprintf("http://%s", goURL), "H3110, !", 1, 0, 200)
})
})
})
})
}))
}
for _, podman := range []bool{true, false} {
podman := podman
Context("port-forwarding for the component", helper.LabelPodmanIf(podman, func() {
for _, manual := range []bool{true, false} {
manual := manual
When("devfile has no endpoint", func() {
BeforeEach(func() {
if !podman {
helper.Cmd("odo", "set", "project", commonVar.Project).ShouldPass()
}
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-no-endpoint.yaml")).ShouldPass()
})
When("running odo dev", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
opts := []string{}
if manual {
opts = append(opts, "--no-watch")
}
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: opts,
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It(fmt.Sprintf("should have no endpoint forwarded (podman=%v, manual=%v)", podman, manual), func() {
Expect(devSession.Endpoints).To(BeEmpty())
})
})
})
for _, customAddress := range []bool{true, false} {
customAddress := customAddress
var localAddress string
if customAddress {
localAddress = "0.0.0.0"
}
for _, customPortForwarding := range []bool{true, false} {
customPortForwarding := customPortForwarding
var NoRandomPorts bool
if customPortForwarding {
NoRandomPorts = true
}
When("devfile has single endpoint", func() {
var (
localPort int
)
const (
containerPort = "3000"
)
BeforeEach(func() {
localPort = helper.GetCustomStartPort()
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml")).ShouldPass()
})
When("running odo dev", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
opts := []string{}
if customPortForwarding {
opts = []string{fmt.Sprintf("--port-forward=%d:%s", localPort, containerPort)}
}
if manual {
opts = append(opts, "--no-watch")
}
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: opts,
NoRandomPorts: NoRandomPorts,
RunOnPodman: podman,
CustomAddress: localAddress,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It(fmt.Sprintf("should expose the endpoint on localhost (podman=%v, manual=%v, customPortForwarding=%v, customAddress=%v)", podman, manual, customPortForwarding, customAddress), func() {
url := fmt.Sprintf("http://%s", devSession.Endpoints[containerPort])
if customPortForwarding {
Expect(url).To(ContainSubstring(strconv.Itoa(localPort)))
}
resp, err := http.Get(url)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
Expect(err).ToNot(HaveOccurred())
})
When("modifying name for container in Devfile", func() {
BeforeEach(func() {
if manual {
if os.Getenv("SKIP_KEY_PRESS") == "true" {
Skip("This is a unix-terminal specific scenario, skipping")
}
}
var (
wg sync.WaitGroup
err error
)
wg.Add(1)
go func() {
defer wg.Done()
err = devSession.WaitSync()
Expect(err).Should(Succeed())
}()
src := "runtime"
dst := "other"
helper.ReplaceString("devfile.yaml", src, dst)
if manual {
devSession.PressKey('p')
}
wg.Wait()
})
It(fmt.Sprintf("should react on the Devfile modification (podman=%v, manual=%v, customPortForwarding=%v, customAddress=%v)", podman, manual, customPortForwarding, customAddress), func() {
By("not warning users that odo dev needs to be restarted", func() {
warning := "Please restart 'odo dev'"
Expect(devSession.StdOut).ShouldNot(ContainSubstring(warning))
Expect(devSession.ErrOut).ShouldNot(ContainSubstring(warning))
})
By("updating the pod", func() {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
podDef := component.GetPodDef()
containerName := podDef.Spec.Containers[0].Name
Expect(containerName).To(ContainSubstring("other"))
})
By("exposing the endpoint", func() {
Eventually(func(g Gomega) {
url := fmt.Sprintf("http://%s", devSession.Endpoints[containerPort])
if customPortForwarding {
Expect(url).To(ContainSubstring(strconv.Itoa(localPort)))
}
if customAddress {
Expect(url).To(ContainSubstring(localAddress))
}
resp, err := http.Get(url)
g.Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
for _, i := range []string{"Hello from Node.js Starter Application!"} {
g.Expect(string(body)).To(ContainSubstring(i))
}
g.Expect(err).ToNot(HaveOccurred())
}).WithPolling(1 * time.Second).WithTimeout(20 * time.Second).Should(Succeed())
})
})
})
})
})
When("devfile has multiple endpoints", func() {
var (
localPort1, localPort2, localPort3 int
)
const (
// ContainerPort<N> are hard-coded from devfile-with-multiple-endpoints.yaml
// Note 1: Debug endpoints will not be exposed for this instance, so we do not add custom mapping for them.
// Note 2: We add custom mapping for all the endpoints so that none of them are assigned random ports from the 20001-30001 range;
// Note 2(contd.): this is to avoid a race condition where a test running in parallel is also assigned similar ranged port the one here, and we fail to access either of them.
containerPort1 = "3000"
containerPort2 = "4567"
containerPort3 = "7890"
)
BeforeEach(func() {
localPort1 = helper.GetCustomStartPort()
localPort2 = localPort1 + 1
localPort3 = localPort1 + 2
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-multiple-endpoints"), commonVar.Context)
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
})
When("running odo dev", func() {
var devSession helper.DevSession
BeforeEach(func() {
opts := []string{}
if customPortForwarding {
opts = []string{fmt.Sprintf("--port-forward=%d:%s", localPort1, containerPort1), fmt.Sprintf("--port-forward=%d:%s", localPort2, containerPort2), fmt.Sprintf("--port-forward=%d:%s", localPort3, containerPort3)}
}
if manual {
opts = append(opts, "--no-watch")
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: opts,
NoRandomPorts: NoRandomPorts,
RunOnPodman: podman,
CustomAddress: localAddress,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It(fmt.Sprintf("should expose all endpoints on localhost regardless of exposure(podman=%v, manual=%v, customPortForwarding=%v, customAddress=%v)", podman, manual, customPortForwarding, customAddress), func() {
By("not exposing debug endpoints", func() {
for _, p := range []int{5005, 5006} {
_, found := devSession.Endpoints[strconv.Itoa(p)]
Expect(found).To(BeFalse(), fmt.Sprintf("debug port %d should not be forwarded", p))
}
})
getServerResponse := func(containerPort, localPort string) (string, error) {
url := fmt.Sprintf("http://%s", devSession.Endpoints[containerPort])
if customPortForwarding {
Expect(url).To(ContainSubstring(localPort))
}
if customAddress {
Expect(url).To(ContainSubstring(localAddress))
}
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return string(body), nil
}
containerPorts := []string{containerPort1, containerPort2, containerPort3}
localPorts := []int{localPort1, localPort2, localPort3}
for i := range containerPorts {
containerPort := containerPorts[i]
localPort := localPorts[i]
By(fmt.Sprintf("exposing a port targeting container port %s", containerPort), func() {
r, err := getServerResponse(containerPort, strconv.Itoa(localPort))
Expect(err).ShouldNot(HaveOccurred())
helper.MatchAllInOutput(r, []string{"Hello from Node.js Starter Application!"})
})
}
helper.ReplaceString("server.js", "Hello from Node.js", "H3110 from Node.js")
if manual {
if os.Getenv("SKIP_KEY_PRESS") == "true" {
Skip("This is a unix-terminal specific scenario, skipping")
}
devSession.PressKey('p')
}
err := devSession.WaitSync()
Expect(err).Should(Succeed())
By("not warning users that odo dev needs to be restarted because the Devfile has not changed", func() {
warning := "Please restart 'odo dev'"
Expect(devSession.StdOut).ShouldNot(ContainSubstring(warning))
Expect(devSession.ErrOut).ShouldNot(ContainSubstring(warning))
})
for i := range containerPorts {
containerPort := containerPorts[i]
localPort := localPorts[i]
By(fmt.Sprintf("returning the right response when querying port forwarded for container port %s", containerPort),
func() {
Eventually(func(g Gomega) string {
r, err := getServerResponse(containerPort, strconv.Itoa(localPort))
g.Expect(err).ShouldNot(HaveOccurred())
return r
}, 180, 10).Should(Equal("H3110 from Node.js Starter Application!"))
})
}
})
})
})
}
}
}
}))
}
for _, devfileHandlerCtx := range []struct {
name string
sourceHandler func(path string, originalCmpName string)
}{
{
name: "with metadata.name",
},
{
name: "without metadata.name",
sourceHandler: func(path string, originalCmpName string) {
helper.UpdateDevfileContent(filepath.Join(path, "devfile.yaml"), []helper.DevfileUpdater{helper.DevfileMetadataNameRemover})
helper.ReplaceString(filepath.Join(path, "package.json"), "nodejs-starter", originalCmpName)
},
},
} {
devfileHandlerCtx := devfileHandlerCtx
for _, podman := range []bool{true, false} {
podman := podman
When("Devfile 2.1.0 is used - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-variables.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
})
When("doing odo dev", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("3. should check if the env variable has a correct value", func() {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
envVars := component.GetEnvVars("runtime")
// check if the env variable has a correct value. This value was substituted from in devfile from variable
Expect(envVars["FOO"]).To(Equal("bar"))
})
})
When("doing odo dev with --var flag", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: []string{"--var", "VALUE_TEST=baz"},
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should check if the env variable has a correct value", func() {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
envVars := component.GetEnvVars("runtime")
// check if the env variable has a correct value. This value was substituted from in devfile from variable
Expect(envVars["FOO"]).To(Equal("baz"))
})
})
When("doing odo dev with --var-file flag", func() {
var devSession helper.DevSession
varfilename := "vars.txt"
BeforeEach(func() {
var err error
err = helper.CreateFileWithContent(varfilename, "VALUE_TEST=baz")
Expect(err).ToNot(HaveOccurred())
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: []string{"--var-file", "vars.txt"},
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
helper.DeleteFile(varfilename)
})
It("should check if the env variable has a correct value", func() {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
envVars := component.GetEnvVars("runtime")
// check if the env variable has a correct value. This value was substituted from in devfile from variable
Expect(envVars["FOO"]).To(Equal("baz"))
})
})
When("doing odo dev with --var-file flag and setting value in env", func() {
var devSession helper.DevSession
varfilename := "vars.txt"
BeforeEach(func() {
var err error
_ = os.Setenv("VALUE_TEST", "baz")
err = helper.CreateFileWithContent(varfilename, "VALUE_TEST")
Expect(err).ToNot(HaveOccurred())
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: []string{"--var-file", "vars.txt"},
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
helper.DeleteFile(varfilename)
_ = os.Unsetenv("VALUE_TEST")
})
It("should check if the env variable has a correct value", func() {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
envVars := component.GetEnvVars("runtime")
// check if the env variable has a correct value. This value was substituted from in devfile from variable
Expect(envVars["FOO"]).To(Equal("baz"))
})
})
}))
When("running odo dev and single env var is set - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-single-env.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
})
It("should be able to exec command", func() {
err := helper.RunDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
}, func(session *gexec.Session, out, err string, ports map[string]string) {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
output, _ := component.Exec("runtime", []string{"ls", "-lai", "/projects"}, pointer.Bool(true))
helper.MatchAllInOutput(output, []string{"test_env_variable", "test_build_env_variable"})
})
Expect(err).ToNot(HaveOccurred())
})
}))
When("running odo dev and multiple env variables are set - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-multiple-envs.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
})
It("should be able to exec command", func() {
err := helper.RunDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
}, func(session *gexec.Session, out, err string, ports map[string]string) {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
output, _ := component.Exec("runtime", []string{"ls", "-lai", "/projects"}, pointer.Bool(true))
helper.MatchAllInOutput(output, []string{"test_build_env_variable1", "test_build_env_variable2", "test_env_variable1", "test_env_variable2"})
})
Expect(err).ToNot(HaveOccurred())
})
}))
When("doing odo dev and there is a env variable with spaces - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-command-env-with-space.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
})
It("should be able to exec command", func() {
err := helper.RunDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
}, func(session *gexec.Session, out, err string, ports map[string]string) {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
output, _ := component.Exec("runtime", []string{"ls", "-lai", "/projects"}, pointer.Bool(true))
helper.MatchAllInOutput(output, []string{"build env variable with space", "env with space"})
})
Expect(err).ToNot(HaveOccurred())
})
}))
}
When("creating local files and dir and running odo dev - "+devfileHandlerCtx.name, func() {
var newDirPath, newFilePath, stdOut, podName string
var devSession helper.DevSession
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
newFilePath = filepath.Join(commonVar.Context, "foobar.txt")
newDirPath = filepath.Join(commonVar.Context, "testdir")
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
// Create a new file that we plan on deleting later...
if err := helper.CreateFileWithContent(newFilePath, "hello world"); err != nil {
fmt.Printf("the foobar.txt file was not created, reason %v", err.Error())
}
// Create a new directory
helper.MakeDir(newDirPath)
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should correctly propagate changes to the container", func() {
// Check to see if it's been pushed (foobar.txt abd directory testdir)
podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects")
helper.MatchAllInOutput(stdOut, []string{"foobar.txt", "testdir"})
})
When("deleting local files and dir and waiting for sync", func() {
BeforeEach(func() {
// Now we delete the file and dir and push
helper.DeleteDir(newFilePath)
helper.DeleteDir(newDirPath)
err := devSession.WaitSync()
Expect(err).ToNot(HaveOccurred())
})
It("should not list deleted dir and file in container", func() {
podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
// Then check to see if it's truly been deleted
stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects")
helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt", "testdir"})
})
})
})
When("Starting a PostgreSQL service", Label(helper.LabelServiceBinding), func() {
BeforeEach(func() {
skipLogin := os.Getenv("SKIP_SERVICE_BINDING_TESTS")
if skipLogin == "true" {
Skip("Skipping service binding tests as SKIP_SERVICE_BINDING_TESTS is true")
}
// Ensure that the operators are installed
commonVar.CliRunner.EnsureOperatorIsInstalled("service-binding-operator")
commonVar.CliRunner.EnsureOperatorIsInstalled("cloud-native-postgresql")
Eventually(func() string {
out, _ := commonVar.CliRunner.GetBindableKinds()
return out
}, 120, 3).Should(ContainSubstring("Cluster"))
addBindableKind := commonVar.CliRunner.Run("apply", "-f", helper.GetExamplePath("manifests", "bindablekind-instance.yaml"))
Expect(addBindableKind.ExitCode()).To(BeEquivalentTo(0))
commonVar.CliRunner.EnsurePodIsUp(commonVar.Project, "cluster-sample-1")
})
When("creating local files and dir and running odo dev - "+devfileHandlerCtx.name, func() {
var newDirPath, newFilePath, stdOut, podName string
var devSession helper.DevSession
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
newFilePath = filepath.Join(commonVar.Context, "foobar.txt")
newDirPath = filepath.Join(commonVar.Context, "testdir")
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-service-binding-files.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
// Create a new file that we plan on deleting later...
if err := helper.CreateFileWithContent(newFilePath, "hello world"); err != nil {
fmt.Printf("the foobar.txt file was not created, reason %v", err.Error())
}
// Create a new directory
helper.MakeDir(newDirPath)
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should correctly propagate changes to the container", func() {
// Check to see if it's been pushed (foobar.txt abd directory testdir)
podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects")
helper.MatchAllInOutput(stdOut, []string{"foobar.txt", "testdir"})
})
When("deleting local files and dir and waiting for sync", func() {
BeforeEach(func() {
// Now we delete the file and dir and push
helper.DeleteDir(newFilePath)
helper.DeleteDir(newDirPath)
err := devSession.WaitSync()
Expect(err).ToNot(HaveOccurred())
})
It("should not list deleted dir and file in container", func() {
podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
// Then check to see if it's truly been deleted
stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects")
helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt", "testdir"})
})
})
})
})
When("adding local files to gitignore and running odo dev", func() {
var gitignorePath, newDirPath, newFilePath1, newFilePath2, newFilePath3, newFilePath4, newFilePath5, stdOut, podName string
var devSession helper.DevSession
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
gitignorePath = filepath.Join(commonVar.Context, ".gitignore")
newFilePath1 = filepath.Join(commonVar.Context, "foobar.txt")
newDirPath = filepath.Join(commonVar.Context, "testdir")
newFilePath2 = filepath.Join(newDirPath, "foobar.txt")
newFilePath3 = filepath.Join(newDirPath, "baz.txt")
newFilePath4 = filepath.Join(newDirPath, "ignore.css")
newFilePath5 = filepath.Join(newDirPath, "main.css")
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
if err := helper.CreateFileWithContent(newFilePath1, "hello world"); err != nil {
fmt.Printf("the foobar.txt file was not created, reason %v", err.Error())
}
// Create a new directory
helper.MakeDir(newDirPath)
if err := helper.CreateFileWithContent(newFilePath2, "hello world"); err != nil {
fmt.Printf("the foobar.txt file was not created, reason %v", err.Error())
}
if err := helper.CreateFileWithContent(newFilePath3, "hello world"); err != nil {
fmt.Printf("the foobar.txt file was not created, reason %v", err.Error())
}
if err := helper.CreateFileWithContent(newFilePath4, "div {}"); err != nil {
fmt.Printf("the %s file was not created, reason %v", newFilePath4, err.Error())
}
if err := helper.CreateFileWithContent(newFilePath5, "div {}"); err != nil {
fmt.Printf("the %s file was not created, reason %v", newFilePath5, err.Error())
}
if err := helper.CreateFileWithContent(gitignorePath, `foobar.txt
*.css
!main.css`); err != nil {
fmt.Printf("the .gitignore file was not created, reason %v", err.Error())
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
checkSyncedFiles := func(podName string) {
stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects")
helper.MatchAllInOutput(stdOut, []string{"testdir"})
helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt"})
stdOut = commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects/testdir")
helper.MatchAllInOutput(stdOut, []string{"baz.txt"})
helper.DontMatchAllInOutput(stdOut, []string{"foobar.txt"})
helper.MatchAllInOutput(stdOut, []string{"main.css"})
helper.DontMatchAllInOutput(stdOut, []string{"ignore.css"})
}
It("should not sync ignored files to the container", func() {
podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
checkSyncedFiles(podName)
})
When("modifying /testdir/baz.txt file", func() {
BeforeEach(func() {
helper.ReplaceString(newFilePath3, "hello world", "hello world!!!")
})
It("should synchronize it only", func() {
_ = devSession.WaitSync()
podName = commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
checkSyncedFiles(podName)
})
})
When("modifying /foobar.txt file", func() {
BeforeEach(func() {
helper.ReplaceString(newFilePath1, "hello world", "hello world!!!")
})
It("should not synchronize it", func() {
devSession.CheckNotSynced(10 * time.Second)
})
})
When("modifying /testdir/foobar.txt file", func() {
BeforeEach(func() {
helper.ReplaceString(newFilePath2, "hello world", "hello world!!!")
})
It("should not synchronize it", func() {
devSession.CheckNotSynced(10 * time.Second)
})
})
})
When("devfile has sourcemappings and running odo dev - "+devfileHandlerCtx.name, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfileSourceMapping.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should sync files to the correct location", func() {
// Verify source code was synced to /test instead of /projects
var statErr error
podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod(
podName,
"runtime",
commonVar.Project,
[]string{"stat", "/test/server.js"},
func(cmdOp string, err error) bool {
statErr = err
return err == nil
},
)
Expect(statErr).ToNot(HaveOccurred())
})
})
When("project and clonePath is present in devfile and running odo dev - "+devfileHandlerCtx.name, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
// devfile with clonePath set in project field
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-projects.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should sync to the correct dir in container", func() {
podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
// source code is synced to $PROJECTS_ROOT/clonePath
// $PROJECTS_ROOT is /projects by default, if sourceMapping is set it is same as sourceMapping
// for devfile-with-projects.yaml, sourceMapping is apps and clonePath is webapp
// so source code would be synced to /apps/webapp
output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/apps/webapp")
helper.MatchAllInOutput(output, []string{"package.json"})
// Verify the sync env variables are correct
helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/apps/webapp", "/apps", commonVar.CliRunner)
})
})
When("devfile project field is present and running odo dev - "+devfileHandlerCtx.name, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-projects.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
// reset clonePath and change the workdir accordingly, it should sync to project name
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "clonePath: webapp/", "# clonePath: webapp/")
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should sync to the correct dir in container", func() {
podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/apps/nodeshift")
helper.MatchAllInOutput(output, []string{"package.json"})
// Verify the sync env variables are correct
helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/apps/nodeshift", "/apps", commonVar.CliRunner)
})
})
When("multiple projects are present - "+devfileHandlerCtx.name, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-multiple-projects.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should sync to the correct dir in container", func() {
podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
// for devfile-with-multiple-projects.yaml source mapping is not set so $PROJECTS_ROOT is /projects
// multiple projects, so source code would sync to the first project /projects/webapp
output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects/webapp")
helper.MatchAllInOutput(output, []string{"package.json"})
// Verify the sync env variables are correct
helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/projects/webapp", "/projects", commonVar.CliRunner)
})
})
When("no project is present - "+devfileHandlerCtx.name, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should sync to the correct dir in container", func() {
podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
output := commonVar.CliRunner.ExecListDir(podName, commonVar.Project, "/projects")
helper.MatchAllInOutput(output, []string{"package.json"})
// Verify the sync env variables are correct
helper.VerifyContainerSyncEnv(podName, "runtime", commonVar.Project, "/projects", "/projects", commonVar.CliRunner)
})
})
When("running odo dev with devfile contain volume - "+devfileHandlerCtx.name, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-volumes.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should create pvc and reuse if it shares the same devfile volume name", func() {
var statErr error
var cmdOutput string
// Check to see if it's been pushed (foobar.txt abd directory testdir)
podName := commonVar.CliRunner.GetRunningPodNameByComponent(devfileCmpName, commonVar.Project)
commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod(
podName,
"runtime2",
commonVar.Project,
[]string{"cat", "/myvol/myfile.log"},
func(cmdOp string, err error) bool {
cmdOutput = cmdOp
statErr = err
return err == nil
},
)
Expect(statErr).ToNot(HaveOccurred())
Expect(cmdOutput).To(ContainSubstring("hello"))
commonVar.CliRunner.CheckCmdOpInRemoteDevfilePod(
podName,
"runtime2",
commonVar.Project,
[]string{"stat", "/data2"},
func(cmdOp string, err error) bool {
statErr = err
return err == nil
},
)
Expect(statErr).ToNot(HaveOccurred())
})
It("check the volume name and mount paths for the containers", func() {
deploymentName, err := util.NamespaceKubernetesObject(devfileCmpName, "app")
Expect(err).To(BeNil())
volumesMatched := false
// check the volume name and mount paths for the containers
volNamesAndPaths := commonVar.CliRunner.GetVolumeMountNamesandPathsFromContainer(deploymentName, "runtime", commonVar.Project)
volNamesAndPathsArr := strings.Fields(volNamesAndPaths)
for _, volNamesAndPath := range volNamesAndPathsArr {
volNamesAndPathArr := strings.Split(volNamesAndPath, ":")
if strings.Contains(volNamesAndPathArr[0], "myvol") && volNamesAndPathArr[1] == "/data" {
volumesMatched = true
}
}
Expect(volumesMatched).To(Equal(true))
})
})
When("running odo dev with devfile containing volume-component - "+devfileHandlerCtx.name, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
if os.Getenv("KUBERNETES") == "true" {
Skip("This is a OpenShift specific scenario, skipping")
}
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-volume-components.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
if os.Getenv("KUBERNETES") == "true" {
return
}
devSession.Stop()
devSession.WaitEnd()
})
It("should successfully use the volume components in container components", func() {
// Verify the pvc size for firstvol
storageSize := commonVar.CliRunner.GetPVCSize(devfileCmpName, "firstvol", commonVar.Project)
// should be the default size
Expect(storageSize).To(ContainSubstring("1Gi"))
// Verify the pvc size for secondvol
storageSize = commonVar.CliRunner.GetPVCSize(devfileCmpName, "secondvol", commonVar.Project)
// should be the specified size in the devfile volume component
Expect(storageSize).To(ContainSubstring("200Mi"))
})
})
}
Describe("1. devfile contains composite apply command", func() {
const (
k8sDeploymentName = "my-k8s-component"
openshiftDeploymentName = "my-openshift-component"
DEVFILEPORT = "8080"
)
var (
devSession helper.DevSession
err error
)
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-composite-apply-commands.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
for _, tt := range []struct {
name string
containerBackendGlobalExtraArgs []string
imageBuildExtraArgs []string
containerRunExtraArgs []string
}{
{
name: "odo dev is running",
},
{
name: "odo dev is running with image build extra args",
imageBuildExtraArgs: []string{
"--platform=linux/amd64",
"--build-arg=MY_ARG=my_value",
},
},
{
name: "odo dev is running with container backend global extra args",
containerBackendGlobalExtraArgs: []string{
"--log-level=error",
},
},
{
name: "odo dev is running with container run extra args",
containerRunExtraArgs: []string{
"--quiet",
"--tls-verify=false",
},
},
{
name: "odo dev is running with both image build and container run extra args",
imageBuildExtraArgs: []string{
"--platform=linux/amd64",
"--build-arg=MY_ARG=my_value",
},
containerBackendGlobalExtraArgs: []string{
"--log-level=panic",
},
containerRunExtraArgs: []string{
"--quiet",
"--tls-verify=false",
},
},
} {
tt := tt
for _, podman := range []bool{false, true} {
podman := podman
When(tt.name, helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
var env []string
if podman {
env = append(env, "ODO_PUSH_IMAGES=false")
} else {
env = append(env, "PODMAN_CMD=echo")
}
if len(tt.containerBackendGlobalExtraArgs) != 0 {
env = append(env, "ODO_CONTAINER_BACKEND_GLOBAL_ARGS="+strings.Join(tt.containerBackendGlobalExtraArgs, ";"))
}
if len(tt.imageBuildExtraArgs) != 0 {
env = append(env, "ODO_IMAGE_BUILD_ARGS="+strings.Join(tt.imageBuildExtraArgs, ";"))
}
var cmdLineArgs []string
if len(tt.containerRunExtraArgs) != 0 {
env = append(env, "ODO_CONTAINER_RUN_ARGS="+strings.Join(tt.containerRunExtraArgs, ";"))
}
if podman {
// Increasing verbosity to check that extra args are being passed to the "podman" commands
cmdLineArgs = append(cmdLineArgs, "-v=4")
}
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
EnvVars: env,
CmdlineArgs: cmdLineArgs,
})
Expect(err).ToNot(HaveOccurred())
})
It("should execute the composite apply commands successfully", func() {
checkDeploymentsExist := func() {
out := commonVar.CliRunner.Run("get", "deployments", k8sDeploymentName).Out.Contents()
Expect(string(out)).To(ContainSubstring(k8sDeploymentName))
out = commonVar.CliRunner.Run("get", "deployments", openshiftDeploymentName).Out.Contents()
Expect(string(out)).To(ContainSubstring(openshiftDeploymentName))
}
checkImageBuilt := func() {
var substring string
if len(tt.containerBackendGlobalExtraArgs) != 0 {
substring = strings.Join(tt.containerBackendGlobalExtraArgs, " ") + " "
}
substring += "build "
if len(tt.imageBuildExtraArgs) != 0 {
substring += strings.Join(tt.imageBuildExtraArgs, " ") + " "
}
substring += fmt.Sprintf("-t quay.io/unknown-account/myimage -f %s %s",
filepath.Join(commonVar.Context, "Dockerfile"), commonVar.Context)
if podman {
Expect(devSession.ErrOut).To(ContainSubstring(substring))
} else {
Expect(devSession.StdOut).To(ContainSubstring(substring))
Expect(devSession.StdOut).To(ContainSubstring("push quay.io/unknown-account/myimage"))
}
}
checkEndpointAccessible := func(message []string) {
url := fmt.Sprintf("http://%s", devSession.Endpoints[DEVFILEPORT])
resp, e := http.Get(url)
Expect(e).ToNot(HaveOccurred())
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
helper.MatchAllInOutput(string(body), message)
}
By("checking is the image was successfully built", func() {
checkImageBuilt()
})
if podman {
expected := "podman "
if len(tt.containerBackendGlobalExtraArgs) != 0 {
expected += fmt.Sprintf("%s ", strings.Join(tt.containerBackendGlobalExtraArgs, " "))
}
expected += "play kube "
if len(tt.containerRunExtraArgs) != 0 {
expected += fmt.Sprintf("%s ", strings.Join(tt.containerRunExtraArgs, " "))
}
expected += "-"
By("checking that extra args are passed to the podman play kube command", func() {
Expect(devSession.ErrOut).Should(ContainSubstring(expected))
})
}
By("checking the endpoint accessibility", func() {
checkEndpointAccessible([]string{"Hello world from node.js!"})
})
if !podman {
By("checking the deployment was created successfully", func() {
checkDeploymentsExist()
})
By("ensuring multiple deployments exist for selector error is not occurred", func() {
Expect(devSession.ErrOut).ToNot(ContainSubstring("multiple Deployments exist for the selector"))
})
}
By("checking odo dev watches correctly", func() {
// making changes to the project again
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "world from node.js", "from the new Node.js Starter Application")
err = devSession.WaitSync()
Expect(err).ToNot(HaveOccurred())
if !podman {
checkDeploymentsExist()
}
checkImageBuilt()
checkEndpointAccessible([]string{"Hello from the new Node.js Starter Application!"})
})
By("cleaning up the resources on ending the session", func() {
devSession.Stop()
devSession.WaitEnd()
if !podman {
out := commonVar.CliRunner.Run("get", "deployments").Out.Contents()
Expect(string(out)).ToNot(ContainSubstring(k8sDeploymentName))
Expect(string(out)).ToNot(ContainSubstring(openshiftDeploymentName))
}
})
})
}))
}
}
Context("the devfile contains an image component that uses a remote Dockerfile", func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
})
for _, env := range [][]string{
{"PODMAN_CMD=echo"},
{
"PODMAN_CMD=a-command-not-found-for-podman-should-make-odo-fallback-to-docker",
"DOCKER_CMD=echo",
},
} {
env := env
When(fmt.Sprintf("%v remote server returns a valid file when odo dev is run", env), func() {
var buildRegexp string
var server *httptest.Server
var url string
BeforeEach(func() {
buildRegexp = regexp.QuoteMeta("build -t quay.io/unknown-account/myimage -f ") +
".*\\.dockerfile " + regexp.QuoteMeta(commonVar.Context)
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, `# Dockerfile
FROM node:8.11.1-alpine
COPY . /app
WORKDIR /app
RUN npm install
CMD ["npm", "start"]
`)
}))
url = server.URL
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "./Dockerfile", url)
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
EnvVars: env,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
server.Close()
})
It("should build and push image when odo dev is run", func() {
lines, _ := helper.ExtractLines(devSession.StdOut)
_, ok := helper.FindFirstElementIndexMatchingRegExp(lines, buildRegexp)
Expect(ok).To(BeTrue(), "build regexp not found in output: "+buildRegexp)
Expect(devSession.StdOut).To(ContainSubstring("push quay.io/unknown-account/myimage"))
})
})
When(fmt.Sprintf("%v remote server returns an error when odo dev is run", env), func() {
var server *httptest.Server
var url string
BeforeEach(func() {
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
url = server.URL
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "./Dockerfile", url)
})
AfterEach(func() {
server.Close()
})
It("should not build images when odo dev is run", func() {
devSession, err := helper.WaitForDevModeToContain(
helper.DevSessionOpts{
EnvVars: env,
},
"failed to retrieve "+url,
true,
false)
Expect(err).To(BeNil())
Expect(devSession.StdOut).NotTo(ContainSubstring("build -t quay.io/unknown-account/myimage -f "))
Expect(devSession.StdOut).NotTo(ContainSubstring("push quay.io/unknown-account/myimage"))
})
})
}
})
})
for _, devfileHandlerCtx := range []struct {
name string
sourceHandler func(path string, originalCmpName string)
}{
{
name: "with metadata.name",
},
{
name: "without metadata.name",
sourceHandler: func(path string, originalCmpName string) {
helper.UpdateDevfileContent(filepath.Join(path, "devfile.yaml"), []helper.DevfileUpdater{helper.DevfileMetadataNameRemover})
helper.ReplaceString(filepath.Join(path, "package.json"), "nodejs-starter", originalCmpName)
},
},
} {
devfileHandlerCtx := devfileHandlerCtx
for _, podman := range []bool{true, false} {
podman := podman
When("running odo dev and devfile with composite command - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfileCompositeCommands.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should execute all commands in composite command", func() {
// Verify the command executed successfully
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
dir := "/projects/testfolder"
out, _ := component.Exec("runtime", []string{"stat", dir}, pointer.Bool(true))
Expect(out).To(ContainSubstring(dir))
})
}))
When("running odo dev and composite command is marked as parallel:true - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfileCompositeCommandsParallel.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should execute all commands in composite command", func() {
// Verify the command executed successfully
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
dir := "/projects/testfolder"
out, _ := component.Exec("runtime", []string{"stat", dir}, pointer.Bool(true))
Expect(out).To(ContainSubstring(dir))
})
}))
When("running odo dev and composite command are nested - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfileNestedCompCommands.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should execute all commands in composite commmand", func() {
// Verify the command executed successfully
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
dir := "/projects/testfolder"
out, _ := component.Exec("runtime", []string{"stat", dir}, pointer.Bool(true))
Expect(out).To(ContainSubstring(dir))
})
}))
When("running odo dev and composite command is used as a run command - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfileCompositeRunAndDebug.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should run successfully", func() {
By("telling the user that odo is synchronizing the files", func() {
Expect(string(devSession.StdOut)).Should(ContainSubstring("Syncing files into the container"))
})
By("verifying from the output that all commands have been executed", func() {
helper.MatchAllInOutput(devSession.StdOut, []string{
"Building your application in container",
"Executing the application (command: mkdir)",
"Executing the application (command: echo)",
"Executing the application (command: install)",
"Executing the application (command: start)",
})
})
By("verifying that any command that did not succeed in the middle has logged such information correctly", func() {
helper.MatchAllInOutput(devSession.ErrOut, []string{
"Devfile command \"echo\" exited with an error status",
"intentional-error-message",
})
})
By("building the application only once", func() {
// Because of the Spinner, the "Building your application in container" is printed twice in the captured devSession.StdOut.
// The bracket allows to match the last occurrence with the command execution timing information.
Expect(strings.Count(devSession.StdOut, "Building your application in container (command: install) [")).
To(BeNumerically("==", 1), "\nOUTPUT: "+devSession.StdOut+"\n")
})
By("verifying that the command did run successfully", func() {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
dir := "/projects/testfolder"
out, _ := component.Exec("runtime", []string{"stat", dir}, pointer.Bool(true))
Expect(out).To(ContainSubstring(dir))
})
})
}))
// This test does not pass on podman. There are flaky permissions issues on source volume mounted by both components sleeper-run and runtime
When("running build and run commands as composite in different containers and a shared volume - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
var devfileCmpName string
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfileCompositeBuildRunDebugInMultiContainersAndSharedVolume.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should run successfully", func() {
By("verifying from the output that all commands have been executed", func() {
helper.MatchAllInOutput(devSession.StdOut, []string{
"Building your application in container (command: mkdir)",
"Building your application in container (command: sleep-cmd-build)",
"Building your application in container (command: build-cmd)",
"Executing the application (command: sleep-cmd-run)",
"Executing the application (command: echo-with-error)",
"Executing the application (command: check-build-result)",
"Executing the application (command: start)",
})
})
By("verifying that any command that did not succeed in the middle has logged such information correctly", func() {
helper.MatchAllInOutput(devSession.ErrOut, []string{
"Devfile command \"echo-with-error\" exited with an error status",
"intentional-error-message",
})
})
By("building the application only once per exec command in the build command", func() {
// Because of the Spinner, the "Building your application in container" is printed twice in the captured devSession.StdOut.
// The bracket allows to match the last occurrence with the command execution timing information.
out := devSession.StdOut
for _, cmd := range []string{"mkdir", "sleep-cmd-build", "build-cmd"} {
Expect(strings.Count(out, fmt.Sprintf("Building your application in container (command: %s) [", cmd))).
To(BeNumerically("==", 1), "\nOUTPUT: "+devSession.StdOut+"\n")
}
})
By("verifying that the command did run successfully", func() {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
dir := "/projects/testfolder"
out, _ := component.Exec("runtime", []string{"stat", dir}, pointer.Bool(true))
Expect(out).To(ContainSubstring(dir))
})
})
}))
}
}
for _, podman := range []bool{false, true} {
podman := podman
When("running odo dev and prestart events are defined", helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-preStart.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
It("should not correctly execute PreStart commands", func() {
args := []string{"dev", "--random-ports"}
if podman {
args = append(args, "--platform", "podman")
}
cmd := helper.Cmd("odo", args...)
output := cmd.ShouldFail().Err()
// This is expected to fail for now.
// see https://github.com/redhat-developer/odo/issues/4187 for more info
helper.MatchAllInOutput(output, []string{"myprestart should either map to an apply command or a composite command with apply commands\n"})
})
}))
}
for _, podman := range []bool{false, true} {
podman := podman
When("running odo dev and run command throws an error", helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "npm start", "npm starts")
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should error out with some log", func() {
helper.MatchAllInOutput(devSession.ErrOut, []string{
"exited with an error status in",
"Did you mean one of these?",
})
})
}))
}
for _, podman := range []bool{false, true} {
for _, noWatch := range []bool{false, true} {
podman := podman
noWatch := noWatch
noWatchFlag := ""
if noWatch {
noWatchFlag = " --no-watch"
}
title := fmt.Sprintf("running odo dev%s and build command throws an error", noWatchFlag)
When(title, helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "npm install", "npm install-does-not-exist")
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
NoWatch: noWatch,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should error out with some log", func() {
helper.MatchAllInOutput(devSession.StdOut, []string{
"unable to exec command",
})
helper.MatchAllInOutput(devSession.ErrOut, []string{
"Usage: npm <command>",
"Did you mean one of these?",
})
})
}))
}
}
for _, podman := range []bool{false, true} {
podman := podman
When("Create and dev java-springboot component", helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
var devSession helper.DevSession
BeforeEach(func() {
devfileCmpName = "javaspringboot-" + helper.RandString(6)
helper.Cmd("odo", "init", "--name", devfileCmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "springboot", "devfile.yaml")).ShouldPass()
helper.CopyExample(filepath.Join("source", "devfiles", "springboot", "project"), commonVar.Context)
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should execute default build and run commands correctly", func() {
cmp := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
cmdOutput, _ := cmp.Exec("runtime",
[]string{
"bash", "-c",
// [s] to not match the current command: https://unix.stackexchange.com/questions/74185/how-can-i-prevent-grep-from-showing-up-in-ps-results
"grep [s]pring-boot:run /proc/*/cmdline",
},
pointer.Bool(true),
)
Expect(cmdOutput).To(MatchRegexp("Binary file .* matches"))
})
}))
}
for _, podman := range []bool{false, true} {
podman := podman
When("setting git config and running odo dev", func() {
remoteURL := "https://github.com/odo-devfiles/nodejs-ex"
devfileCmpName := "nodejs"
BeforeEach(func() {
if podman {
version := helper.GetPodmanVersion()
if strings.HasPrefix(version, "3.") {
Skip("Getting annotations is not available with Podman v3")
}
}
helper.Cmd("git", "init").ShouldPass()
remote := "origin"
helper.Cmd("git", "remote", "add", remote, remoteURL).ShouldPass()
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.Cmd("odo", "init", "--name", devfileCmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile.yaml")).ShouldPass()
})
It("should create vcs-uri annotation for the deployment when running odo dev",
helper.LabelPodmanIf(podman, func() {
err := helper.RunDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
}, func(session *gexec.Session, outContents string, errContents string, ports map[string]string) {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
annotations := component.GetAnnotations()
var valueFound bool
for key, value := range annotations {
// Pdoman adds a suffix to the annotation key with the name of the container
if strings.HasPrefix(key, "app.openshift.io/vcs-uri") && value == remoteURL {
valueFound = true
break
}
}
Expect(valueFound).To(BeTrue())
})
Expect(err).ToNot(HaveOccurred())
}))
})
}
for _, podman := range []bool{true, false} {
podman := podman
for _, devfileHandlerCtx := range []struct {
name string
sourceHandler func(path string, originalCmpName string)
}{
{
name: "with metadata.name",
},
{
name: "without metadata.name",
sourceHandler: func(path string, originalCmpName string) {
helper.UpdateDevfileContent(filepath.Join(path, "devfile.yaml"), []helper.DevfileUpdater{helper.DevfileMetadataNameRemover})
helper.ReplaceString(filepath.Join(path, "package.json"), "nodejs-starter", originalCmpName)
},
},
} {
devfileHandlerCtx := devfileHandlerCtx
When("running odo dev with alternative commands - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
type testCase struct {
buildCmd string
runCmd string
devAdditionalOpts []string
checkFunc func(stdout, stderr string)
}
testForCmd := func(tt testCase) {
err := helper.RunDevMode(helper.DevSessionOpts{
CmdlineArgs: tt.devAdditionalOpts,
RunOnPodman: podman,
}, func(session *gexec.Session, outContents string, errContents string, ports map[string]string) {
stdout := outContents
stderr := errContents
By("checking the output of the command", func() {
helper.MatchAllInOutput(stdout, []string{
"Syncing files into the container",
fmt.Sprintf("Building your application in container (command: %s)", tt.buildCmd),
fmt.Sprintf("Executing the application (command: %s)", tt.runCmd),
})
})
if tt.checkFunc != nil {
tt.checkFunc(stdout, stderr)
}
By("verifying the exposed application endpoint", func() {
url := fmt.Sprintf("http://%s", ports["3000"])
resp, err := http.Get(url)
Expect(err).ToNot(HaveOccurred())
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
helper.MatchAllInOutput(string(body), []string{"Hello from Node.js Starter Application!"})
})
})
Expect(err).ToNot(HaveOccurred())
}
remoteFileChecker := func(path string) {
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
out, _ := component.Exec("runtime", []string{"stat", path}, pointer.Bool(true))
Expect(out).To(ContainSubstring(path))
}
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-alternative-commands.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
})
When("running odo dev with a build command", func() {
buildCmdTestFunc := func(buildCmd string, checkFunc func(stdout, stderr string)) {
testForCmd(
testCase{
buildCmd: buildCmd,
runCmd: "devrun",
devAdditionalOpts: []string{"--build-command", buildCmd},
checkFunc: checkFunc,
},
)
}
It("should error out on an invalid command", func() {
By("calling with an invalid build command", func() {
devSession, err := helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
CmdlineArgs: []string{"--build-command", "build-command-does-not-exist"},
})
Expect(err).ToNot(HaveOccurred())
defer func() {
devSession.Stop()
devSession.WaitEnd()
}()
Expect(string(devSession.StdOut)).To(ContainSubstring("no build command with name \"build-command-does-not-exist\" found in Devfile"))
})
By("calling with a command of another kind (not build)", func() {
// devrun is a valid run command, not a build command
devSession, err := helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
CmdlineArgs: []string{"--build-command", "devrun"},
})
Expect(err).ToNot(HaveOccurred())
defer func() {
devSession.Stop()
devSession.WaitEnd()
}()
Expect(string(devSession.StdOut)).To(ContainSubstring("no build command with name \"devrun\" found in Devfile"))
})
})
It("should execute the custom non-default build command successfully", func() {
buildCmdTestFunc("my-custom-build", func(stdout, stderr string) {
By("checking that it did not execute the default build command", func() {
helper.DontMatchAllInOutput(stdout, []string{
"Building your application in container (command: devbuild)",
})
})
By("verifying that the custom command ran successfully", func() {
remoteFileChecker("/projects/file-from-my-custom-build")
})
})
})
It("should execute the default build command successfully if specified explicitly", func() {
// devbuild is the default build command
buildCmdTestFunc("devbuild", func(stdout, stderr string) {
By("checking that it did not execute the custom build command", func() {
helper.DontMatchAllInOutput(stdout, []string{
"Building your application in container (command: my-custom-build)",
})
})
})
})
})
When("running odo dev with a run command", func() {
runCmdTestFunc := func(runCmd string, checkFunc func(stdout, stderr string)) {
testForCmd(
testCase{
buildCmd: "devbuild",
runCmd: runCmd,
devAdditionalOpts: []string{"--run-command", runCmd},
checkFunc: checkFunc,
},
)
}
It("should error out on an invalid command", func() {
By("calling with an invalid run command", func() {
devSession, err := helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
CmdlineArgs: []string{"--run-command", "run-command-does-not-exist"},
})
Expect(err).ToNot(HaveOccurred())
defer func() {
devSession.Stop()
devSession.WaitEnd()
}()
Expect(string(devSession.StdOut)).To(ContainSubstring("no run command with name \"run-command-does-not-exist\" found in Devfile"))
})
By("calling with a command of another kind (not run)", func() {
// devbuild is a valid build command, not a run command
devSession, err := helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
CmdlineArgs: []string{"--run-command", "devbuild"},
})
Expect(err).ToNot(HaveOccurred())
defer func() {
devSession.Stop()
devSession.WaitEnd()
}()
Expect(string(devSession.StdOut)).To(ContainSubstring("no run command with name \"devbuild\" found in Devfile"))
})
})
It("should execute the custom non-default run command successfully", func() {
runCmdTestFunc("my-custom-run", func(stdout, stderr string) {
By("checking that it did not execute the default run command", func() {
helper.DontMatchAllInOutput(stdout, []string{
"Executing the application (command: devrun)",
})
})
By("verifying that the custom command ran successfully", func() {
remoteFileChecker("/projects/file-from-my-custom-run")
})
})
})
It("should execute the default run command successfully if specified explicitly", func() {
// devrun is the default run command
runCmdTestFunc("devrun", func(stdout, stderr string) {
By("checking that it did not execute the custom run command", func() {
helper.DontMatchAllInOutput(stdout, []string{
"Executing the application (command: my-custom-run)",
})
})
})
})
})
It("should execute the custom non-default build and run commands successfully", func() {
buildCmd := "my-custom-build"
runCmd := "my-custom-run"
testForCmd(
testCase{
buildCmd: buildCmd,
runCmd: runCmd,
devAdditionalOpts: []string{"--build-command", buildCmd, "--run-command", runCmd},
checkFunc: func(stdout, stderr string) {
By("checking that it did not execute the default build and run commands", func() {
helper.DontMatchAllInOutput(stdout, []string{
"Building your application in container (command: devbuild)",
"Executing the application (command: devrun)",
})
})
By("verifying that the custom build command ran successfully", func() {
remoteFileChecker("/projects/file-from-my-custom-build")
})
By("verifying that the custom run command ran successfully", func() {
remoteFileChecker("/projects/file-from-my-custom-run")
})
},
},
)
})
}))
}
}
// Tests https://github.com/redhat-developer/odo/issues/3838
for _, podman := range []bool{true, false} {
podman := podman
When("java-springboot application is created and running odo dev", helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
var component helper.Component
BeforeEach(func() {
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "springboot", "devfile-registry.yaml")).ShouldPass()
helper.CopyExample(filepath.Join("source", "devfiles", "springboot", "project"), commonVar.Context)
component = helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: []string{"-v", "4"},
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
When("Update the devfile.yaml", func() {
BeforeEach(func() {
helper.ReplaceString("devfile.yaml", "memoryLimit: 768Mi", "memoryLimit: 767Mi")
err := devSession.WaitSync()
Expect(err).ToNot(HaveOccurred())
})
It("Should build the application successfully", func() {
podLogs := component.GetPodLogs()
Expect(podLogs).To(ContainSubstring("BUILD SUCCESS"))
})
When("compare the local and remote files", func() {
remoteFiles := []string{}
localFiles := []string{}
BeforeEach(func() {
// commonVar.CliRunner.PodsShouldBeRunning(commonVar.Project, podName)
output, _ := component.Exec("tools", []string{"find", "/projects"}, pointer.Bool(true))
outputArr := []string{}
sc := bufio.NewScanner(strings.NewReader(output))
for sc.Scan() {
outputArr = append(outputArr, sc.Text())
}
for _, line := range outputArr {
if !strings.HasPrefix(line, "/projects"+"/") || strings.Contains(line, "lost+found") {
continue
}
newLine, err := filepath.Rel("/projects", line)
Expect(err).ToNot(HaveOccurred())
newLine = filepath.ToSlash(newLine)
if strings.HasPrefix(newLine, "target/") || newLine == "target" || strings.HasPrefix(newLine, ".") {
continue
}
remoteFiles = append(remoteFiles, newLine)
}
// 5) Acquire file from local context, filtering out .*
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
newPath := filepath.ToSlash(path)
if strings.HasPrefix(newPath, ".") {
return nil
}
localFiles = append(localFiles, newPath)
return nil
})
Expect(err).ToNot(HaveOccurred())
})
It("localFiles and remoteFiles should match", func() {
sort.Strings(localFiles)
sort.Strings(remoteFiles)
Expect(localFiles).To(Equal(remoteFiles))
})
})
})
}))
}
for _, podman := range []bool{false, true} {
podman := podman
When("node-js application is created and deployed with devfile schema 2.2.0", helper.LabelPodmanIf(podman, func() {
ensureResource := func(memorylimit, memoryrequest string) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
podDef := component.GetPodDef()
By("check for memoryLimit", func() {
memVal := podDef.Spec.Containers[0].Resources.Limits.Memory().String()
Expect(memVal).To(Equal(memorylimit))
})
if !podman {
// Resource Requests are not returned by podman generate kube (as of podman v4.3.1)
// TODO(feloy) are they taken into account?
By("check for memoryRequests", func() {
memVal := podDef.Spec.Containers[0].Resources.Requests.Memory().String()
Expect(memVal).To(Equal(memoryrequest))
})
}
}
var devSession helper.DevSession
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-MR-CL-CR.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should check memory Request and Limit", func() {
ensureResource("1Gi", "512Mi")
})
if !podman {
When("Update the devfile.yaml, and waiting synchronization", func() {
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-MR-CL-CR-modified.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
err := devSession.WaitSync()
Expect(err).ToNot(HaveOccurred())
})
It("should check cpuLimit, cpuRequests, memoryRequests after restart", func() {
ensureResource("1028Mi", "550Mi")
})
})
}
}))
}
for _, podman := range []bool{false, true} {
podman := podman
When("creating nodejs component, doing odo dev and run command has dev.odo.push.path attribute", helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
var devStarted bool
BeforeEach(func() {
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path",
helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-remote-attributes.yaml")).ShouldPass()
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
// create a folder and file which shouldn't be pushed
helper.MakeDir(filepath.Join(commonVar.Context, "views"))
_, _ = helper.CreateSimpleFile(filepath.Join(commonVar.Context, "views"), "view", ".html")
helper.ReplaceString("package.json", "node server.js", "node server/server.js")
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
devStarted = true
})
AfterEach(func() {
if devStarted {
devSession.Stop()
devSession.WaitEnd()
}
})
It("should sync only the mentioned files at the appropriate remote destination", func() {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
stdOut, _ := component.Exec("runtime", []string{"ls", "-lai", "/projects"}, pointer.Bool(true))
helper.MatchAllInOutput(stdOut, []string{"package.json", "server"})
helper.DontMatchAllInOutput(stdOut, []string{"test", "views", "devfile.yaml"})
stdOut, _ = component.Exec("runtime", []string{"ls", "-lai", "/projects/server"}, pointer.Bool(true))
helper.MatchAllInOutput(stdOut, []string{"server.js", "test"})
})
}))
}
// Test reused and adapted from the now-removed `cmd_devfile_delete_test.go`.
// cf. https://github.com/redhat-developer/odo/blob/24fd02673d25eb4c7bb166ec3369554a8e64b59c/tests/integration/devfile/cmd_devfile_delete_test.go#L172-L238
When("a component with endpoints is bootstrapped and pushed", func() {
var devSession helper.DevSession
BeforeEach(func() {
cmpName = "nodejs-with-endpoints"
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path",
helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Kill()
devSession.WaitEnd()
})
It("should not create Ingress or Route resources in the cluster", func() {
// Pod should exist
podName := commonVar.CliRunner.GetRunningPodNameByComponent(cmpName, commonVar.Project)
Expect(podName).NotTo(BeEmpty())
services := commonVar.CliRunner.GetServices(commonVar.Project)
Expect(services).To(SatisfyAll(
Not(BeEmpty()),
ContainSubstring(fmt.Sprintf("%s-app", cmpName)),
))
ingressesOut := commonVar.CliRunner.Run("get", "ingress",
"-n", commonVar.Project,
"-o", "custom-columns=NAME:.metadata.name",
"--no-headers").Out.Contents()
ingresses, err := helper.ExtractLines(string(ingressesOut))
Expect(err).To(BeNil())
Expect(ingresses).To(BeEmpty())
if !helper.IsKubernetesCluster() {
routesOut := commonVar.CliRunner.Run("get", "routes",
"-n", commonVar.Project,
"-o", "custom-columns=NAME:.metadata.name",
"--no-headers").Out.Contents()
routes, err := helper.ExtractLines(string(routesOut))
Expect(err).To(BeNil())
Expect(routes).To(BeEmpty())
}
})
})
for _, devfileHandlerCtx := range []struct {
name string
sourceHandler func(path string, originalCmpName string)
}{
{
name: "with metadata.name",
},
{
name: "without metadata.name",
sourceHandler: func(path string, originalCmpName string) {
helper.UpdateDevfileContent(filepath.Join(path, "devfile.yaml"), []helper.DevfileUpdater{helper.DevfileMetadataNameRemover})
helper.ReplaceString(filepath.Join(path, "package.json"), "nodejs-starter", originalCmpName)
},
},
} {
devfileHandlerCtx := devfileHandlerCtx
for _, podman := range []bool{true, false} {
podman := podman
When("a container component defines a Command or Args - "+devfileHandlerCtx.name, helper.LabelPodmanIf(podman, func() {
var devfileCmpName string
var devSession helper.DevSession
var err error
BeforeEach(func() {
devfileCmpName = helper.RandString(6)
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "issue-5620-devfile-with-container-command-args.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
devfileCmpName)
if devfileHandlerCtx.sourceHandler != nil {
devfileHandlerCtx.sourceHandler(commonVar.Context, devfileCmpName)
}
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should run odo dev successfully (#5620)", func() {
const errorMessage = "Failed to create the component:"
Expect(devSession.StdOut).ToNot(ContainSubstring(errorMessage))
Expect(devSession.ErrOut).ToNot(ContainSubstring(errorMessage))
component := helper.NewComponent(devfileCmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
component.Exec("runtime",
[]string{
remotecmd.ShellExecutable,
"-c",
fmt.Sprintf("kill -0 $(cat %s/.odo_cmd_run.pid) 2>/dev/null ; echo -n $?",
strings.TrimSuffix(storage.SharedDataMountPath, "/")),
},
pointer.Bool(true),
)
})
}))
}
}
for _, podman := range []bool{true, false} {
podman := podman
When("a component with multiple endpoints is run", helper.LabelPodmanIf(podman, func() {
stateFile := ".odo/devstate.json"
var devSession helper.DevSession
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-multiple-endpoints"), commonVar.Context)
if !podman {
helper.Cmd("odo", "set", "project", commonVar.Project).ShouldPass()
}
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
Expect(helper.VerifyFileExists(".odo/devstate.json")).To(BeFalse())
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
When("odo dev is stopped", func() {
BeforeEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should remove forwarded ports from state file", func() {
Expect(helper.VerifyFileExists(stateFile)).To(BeTrue())
contentJSON, err := os.ReadFile(stateFile)
Expect(err).ToNot(HaveOccurred())
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts", "")
})
})
}))
When("a component with multiple endpoints is run", helper.LabelPodmanIf(podman, func() {
stateFile := ".odo/devstate.json"
var devSession helper.DevSession
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-multiple-endpoints"), commonVar.Context)
if !podman {
helper.Cmd("odo", "set", "project", commonVar.Project).ShouldPass()
}
helper.Cmd("odo", "init", "--name", cmpName, "--devfile-path", helper.GetExamplePath("source", "devfiles", "nodejs", "devfile-with-multiple-endpoints.yaml")).ShouldPass()
Expect(helper.VerifyFileExists(".odo/devstate.json")).To(BeFalse())
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
// We stop the process so the process does not remain after the end of the tests
devSession.Stop()
devSession.WaitEnd()
})
It("should fail running a second session on the same platform", func() {
_, err := helper.WaitForDevModeToContain(helper.DevSessionOpts{
RunOnPodman: podman,
}, "unable to save state file", true, true)
Expect(err).To(HaveOccurred())
})
It("should create state files containing information, including forwarded ports", func() {
var pid int
var contentJSON []byte
By("creating a devsate.json file", func() {
Expect(helper.VerifyFileExists(stateFile)).To(BeTrue())
var err error
contentJSON, err = os.ReadFile(stateFile)
Expect(err).ToNot(HaveOccurred())
helper.JsonPathExist(string(contentJSON), "pid")
platform := "cluster"
if podman {
platform = "podman"
}
helper.JsonPathContentIs(string(contentJSON), "platform", platform)
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts.0.containerName", "runtime")
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts.1.containerName", "runtime")
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts.0.localAddress", "127.0.0.1")
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts.1.localAddress", "127.0.0.1")
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts.0.containerPort", "3000")
helper.JsonPathContentIs(string(contentJSON), "forwardedPorts.1.containerPort", "4567")
helper.JsonPathContentIsValidUserPort(string(contentJSON), "forwardedPorts.0.localPort")
helper.JsonPathContentIsValidUserPort(string(contentJSON), "forwardedPorts.1.localPort")
var content state.Content
err = json.Unmarshal(contentJSON, &content)
Expect(err).ShouldNot(HaveOccurred())
pid = content.PID
fmt.Printf("PID: %d\n", pid)
})
By("creating a devsate.$PID.json file with same content as devstate.json", func() {
pidStateFile := fmt.Sprintf(".odo/devstate.%d.json", pid)
pidContentJSON, err := os.ReadFile(pidStateFile)
Expect(err).ToNot(HaveOccurred())
Expect(pidContentJSON).To(Equal(contentJSON))
})
})
}))
// TODO: anandrkskd
// not test as expected,
// 1. git ignore should be modified before odo dev
// 2. should not pass on podman
When("a devfile with a local parent is used for odo dev and the parent is not synced", helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-child.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-parent.yaml"),
filepath.Join(commonVar.Context, "devfile-parent.yaml"),
cmpName+"-parent")
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
gitignorePath := filepath.Join(commonVar.Context, ".gitignore")
err = helper.AppendToFile(gitignorePath, "\n/devfile-parent.yaml\n")
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
// We stop the process so the process does not remain after the end of the tests
devSession.Stop()
devSession.WaitEnd()
})
When("updating the parent", func() {
BeforeEach(func() {
helper.ReplaceString("devfile-parent.yaml", "1024Mi", "1023Mi")
})
It("should update the component", func() {
Eventually(func() string {
err := devSession.UpdateInfo()
Expect(err).ToNot(HaveOccurred())
return devSession.StdOut
}, 180, 10).Should(ContainSubstring("Updating Component"))
})
})
}))
}
When("using devfile that contains K8s resource to run it on podman", Label(helper.LabelPodman), func() {
const (
imgName = "quay.io/unknown-account/myimage" // hard coded from the devfile-composite-apply-different-commandgk.yaml
)
var customImgName string
var devSession helper.DevSession
BeforeEach(func() {
customImgName = fmt.Sprintf("%s:%s", imgName, cmpName)
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-composite-apply-different-commandgk.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName,
)
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), imgName, customImgName)
var err error
devSession, err = helper.StartDevMode(
helper.DevSessionOpts{RunOnPodman: true, EnvVars: []string{"ODO_PUSH_IMAGES=false"}},
)
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should show warning about being unable to create the resource when running odo dev on podman", func() {
Expect(devSession.ErrOut).To(ContainSubstring("Kubernetes components are not supported on Podman. Skipping: "))
Expect(devSession.ErrOut).To(ContainSubstring("Apply Kubernetes/Openshift components are not supported on Podman. Skipping: "))
helper.MatchAllInOutput(devSession.ErrOut, []string{"deploy-k8s-resource", "deploy-a-third-k8s-resource"})
})
It("should build the images when running odo dev on podman", func() {
// we do not test push because then it becomes complex to setup image registry credentials to pull the image
// all pods created by odo have a `PullAlways` image policy.
Expect(devSession.StdOut).To(ContainSubstring("Building Image: %s", customImgName))
component := helper.NewPodmanComponent(cmpName, "app")
Expect(component.ListImages()).To(ContainSubstring(customImgName))
})
})
for _, podman := range []bool{true, false} {
podman := podman
When("a hotReload capable Run command is used with odo dev", helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
var executeRunCommand = "Executing the application (command: dev-run)"
var executeBuildCommand = "Building your application"
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "java-quarkus"), commonVar.Context)
helper.UpdateDevfileContent(filepath.Join(commonVar.Context, "devfile.yaml"), []helper.DevfileUpdater{helper.DevfileMetadataNameSetter(cmpName)})
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
// We stop the process so the process does not remain after the end of the tests
devSession.Stop()
devSession.WaitEnd()
})
It("should execute the build and run commands", func() {
Expect(string(devSession.StdOut)).To(ContainSubstring(executeBuildCommand))
Expect(string(devSession.StdOut)).To(ContainSubstring(executeRunCommand))
By("telling the user that odo is synchronizing the files", func() {
Expect(string(devSession.StdOut)).Should(ContainSubstring("Syncing files into the container"))
})
})
When("a source file is modified", func() {
BeforeEach(func() {
helper.ReplaceString(filepath.Join(commonVar.Context, "src", "main", "java", "org", "acme", "GreetingResource.java"), "Hello RESTEasy", "Hi RESTEasy")
err := devSession.WaitSync()
Expect(err).Should(Succeed(), devSession.StdOut)
})
It("should not re-execute the run command", func() {
Expect(string(devSession.StdOut)).To(ContainSubstring(executeBuildCommand))
Expect(string(devSession.StdOut)).ToNot(ContainSubstring(executeRunCommand))
By("telling the user that odo is synchronizing the files", func() {
Expect(string(devSession.StdOut)).Should(ContainSubstring("Syncing files into the container"))
})
})
})
}))
When("hotReload capable Build and Run commands are used with odo dev", helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
var executeRunCommand = "Executing the application (command: run)"
var executeBuildCommand = "Building your application"
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "angular"), commonVar.Context)
helper.UpdateDevfileContent(filepath.Join(commonVar.Context, "devfile.yaml"), []helper.DevfileUpdater{helper.DevfileMetadataNameSetter(cmpName)})
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
// We stop the process so the process does not remain after the end of the tests
devSession.Stop()
devSession.WaitEnd()
})
It("should execute the build and run commands", func() {
Expect(string(devSession.StdOut)).To(ContainSubstring(executeBuildCommand))
Expect(string(devSession.StdOut)).To(ContainSubstring(executeRunCommand))
By("telling the user that odo is synchronizing the files", func() {
Expect(string(devSession.StdOut)).Should(ContainSubstring("Syncing files into the container"))
})
})
When("a source file is modified", func() {
BeforeEach(func() {
helper.ReplaceString(filepath.Join(commonVar.Context, "src", "index.html"), "DevfileStackNodejsAngular", "Devfile Stack Nodejs Angular")
err := devSession.WaitSync()
Expect(err).Should(Succeed(), devSession.StdOut)
})
It("should not re-execute the run command", func() {
Expect(string(devSession.StdOut)).ToNot(ContainSubstring(executeBuildCommand))
Expect(string(devSession.StdOut)).ToNot(ContainSubstring(executeRunCommand))
By("telling the user that odo is synchronizing the files", func() {
Expect(string(devSession.StdOut)).Should(ContainSubstring("Syncing files into the container"))
})
})
})
}))
}
for _, podman := range []bool{true, false} {
podman := podman
Describe("Devfile with no metadata.name", helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-no-metadata-name.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName,
helper.DevfileMetadataNameRemover)
})
When("running odo dev against a component with no source code", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should use the directory as component name", func() {
// when no further source code is available, directory name is returned by alizer.DetectName as component name;
// and since it is all-numeric in our tests, an "x" prefix is added by util.GetDNS1123Name (called by alizer.DetectName)
componentName := "x" + filepath.Base(commonVar.Context)
component := helper.NewComponent(componentName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
component.Exec("runtime",
[]string{
remotecmd.ShellExecutable,
"-c",
fmt.Sprintf("cat %s/.odo_cmd_devrun.pid", strings.TrimSuffix(storage.SharedDataMountPath, "/")),
},
pointer.Bool(true),
)
})
})
}))
}
for _, t := range []struct {
whenTitle string
devfile string
checkDev func(cmpName string, podman bool)
checkDeploy func(cmpName string)
}{
{
whenTitle: "Devfile contains metadata.language",
devfile: "devfile-with-metadata-language.yaml",
checkDev: func(cmpName string, podman bool) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
componentLabels := component.GetLabels()
Expect(componentLabels["app.openshift.io/runtime"]).Should(Equal("javascript"))
if !podman {
commonVar.CliRunner.AssertContainsLabel(
"service",
commonVar.Project,
cmpName,
"app",
labels.ComponentDevMode,
"app.openshift.io/runtime",
"javascript",
)
}
},
checkDeploy: func(cmpName string) {
commonVar.CliRunner.AssertContainsLabel(
"deployment",
commonVar.Project,
cmpName,
"app",
labels.ComponentDeployMode,
"app.openshift.io/runtime",
"javascript",
)
},
},
{
whenTitle: "Devfile contains metadata.language invalid as a label value",
devfile: "devfile-with-metadata-language-as-invalid-label.yaml",
checkDev: func(cmpName string, podman bool) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
componentLabels := component.GetLabels()
Expect(componentLabels["app.openshift.io/runtime"]).Should(Equal("a-custom-language"))
if !podman {
commonVar.CliRunner.AssertContainsLabel(
"service",
commonVar.Project,
cmpName,
"app",
labels.ComponentDevMode,
"app.openshift.io/runtime",
"a-custom-language",
)
}
},
checkDeploy: func(cmpName string) {
commonVar.CliRunner.AssertContainsLabel(
"deployment",
commonVar.Project,
cmpName,
"app",
labels.ComponentDeployMode,
"app.openshift.io/runtime",
"a-custom-language",
)
},
},
{
whenTitle: "Devfile contains metadata.projectType",
devfile: "devfile-with-metadata-project-type.yaml",
checkDev: func(cmpName string, podman bool) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
componentLabels := component.GetLabels()
Expect(componentLabels["app.openshift.io/runtime"]).Should(Equal("nodejs"))
if !podman {
commonVar.CliRunner.AssertContainsLabel(
"service",
commonVar.Project,
cmpName,
"app",
labels.ComponentDevMode,
"app.openshift.io/runtime",
"nodejs",
)
}
},
checkDeploy: func(cmpName string) {
commonVar.CliRunner.AssertContainsLabel(
"deployment",
commonVar.Project,
cmpName,
"app",
labels.ComponentDeployMode,
"app.openshift.io/runtime",
"nodejs",
)
},
},
{
whenTitle: "Devfile contains metadata.projectType invalid as a label value",
devfile: "devfile-with-metadata-project-type-as-invalid-label.yaml",
checkDev: func(cmpName string, podman bool) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
componentLabels := component.GetLabels()
Expect(componentLabels["app.openshift.io/runtime"]).Should(Equal("dotnode"))
if !podman {
commonVar.CliRunner.AssertContainsLabel(
"service",
commonVar.Project,
cmpName,
"app",
labels.ComponentDevMode,
"app.openshift.io/runtime",
"dotnode",
)
}
},
checkDeploy: func(cmpName string) {
commonVar.CliRunner.AssertContainsLabel(
"deployment",
commonVar.Project,
cmpName,
"app",
labels.ComponentDeployMode,
"app.openshift.io/runtime",
"dotnode",
)
},
},
{
whenTitle: "Devfile contains neither metadata.language nor metadata.projectType",
devfile: "devfile-with-metadata-no-language-project-type.yaml",
checkDev: func(cmpName string, podman bool) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
componentLabels := component.GetLabels()
_, found := componentLabels["app.openshift.io/runtime"]
Expect(found).Should(BeFalse(), "app.openshift.io/runtime label exists")
if !podman {
commonVar.CliRunner.AssertNoContainsLabel(
"service",
commonVar.Project,
cmpName,
"app",
labels.ComponentDevMode,
"app.openshift.io/runtime",
)
}
},
checkDeploy: func(cmpName string) {
commonVar.CliRunner.AssertNoContainsLabel(
"deployment",
commonVar.Project,
cmpName,
"app",
labels.ComponentDeployMode,
"app.openshift.io/runtime",
)
},
},
} {
t := t
for _, podman := range []bool{true, false} {
podman := podman
When(t.whenTitle, helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", t.devfile),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
When("running odo dev", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should set the correct value in labels of resources", func() {
t.checkDev(cmpName, podman)
})
})
if !podman { // not implement for podman
When("odo deploy is executed", func() {
BeforeEach(func() {
helper.Cmd("odo", "deploy").ShouldPass()
})
AfterEach(func() {
helper.Cmd("odo", "delete", "component", "--force")
})
It("should set the correct value in labels of deployed resources", func() {
t.checkDeploy(cmpName)
})
})
}
}))
}
}
for _, ctx := range []struct {
devfile string
checkFunc func(podOut *corev1.Pod)
podman bool
}{
{
devfile: "devfile-pod-container-overrides.yaml",
checkFunc: func(podOut *corev1.Pod) {
Expect(podOut.Spec.Containers[0].Resources.Limits.Memory().String()).To(ContainSubstring("512Mi"))
Expect(podOut.Spec.Containers[0].Resources.Limits.Cpu().String()).To(ContainSubstring("250m"))
Expect(podOut.Spec.ServiceAccountName).To(ContainSubstring("new-service-account"))
},
podman: false,
},
{
devfile: "devfile-container-override-on-podman.yaml",
checkFunc: func(podOut *corev1.Pod) {
Expect(podOut.Spec.Containers[0].SecurityContext.RunAsUser).To(Equal(pointer.Int64(1001)))
Expect(podOut.Spec.Containers[0].SecurityContext.RunAsGroup).To(Equal(pointer.Int64(1001)))
},
podman: true,
},
} {
ctx := ctx
Context("Devfile contains pod-overrides and container-overrides attributes", helper.LabelPodmanIf(ctx.podman, func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", ctx.devfile),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
It("should override the content in the pod it creates for the component on the cluster", func() {
err := helper.RunDevMode(helper.DevSessionOpts{
RunOnPodman: ctx.podman,
}, func(session *gexec.Session, outContents, _ string, _ map[string]string) {
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
podOut := component.GetPodDef()
ctx.checkFunc(podOut)
})
Expect(err).To(BeNil())
})
}))
}
Context("odo dev on podman when podman in unavailable", Label(helper.LabelPodman), func() {
BeforeEach(func() {
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
It("should fail to run odo dev", func() {
errOut := helper.Cmd("odo", "dev", "--platform", "podman").WithEnv("PODMAN_CMD=echo").ShouldFail().Err()
Expect(errOut).To(ContainSubstring("unable to access podman. Do you have podman client installed and configured correctly? cause: exec: \"echo\": executable file not found in $PATH"))
})
})
Context("odo dev on podman with a devfile bound to fail", Label(helper.LabelPodman), func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
helper.ReplaceString(filepath.Join(commonVar.Context, "devfile.yaml"), "registry.access.redhat.com/ubi8/nodejs", "registry.access.redhat.com/ubi8/nose")
})
It("should fail with an error", func() {
devSession, err := helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: true,
})
Expect(err).ToNot(HaveOccurred())
defer func() {
devSession.Stop()
devSession.WaitEnd()
}()
helper.MatchAllInOutput(devSession.StdOut, []string{"Complete Podman output", "registry.access.redhat.com/ubi8/nose", "Repo not found"})
})
})
When("running applications listening on the container loopback interface", func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project-with-endpoint-on-loopback"), commonVar.Context)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-with-endpoint-on-loopback.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
haveHttpResponse := func(status int, body string) types.GomegaMatcher {
return WithTransform(func(urlWithoutProto string) (*http.Response, error) {
return http.Get("http://" + urlWithoutProto)
}, SatisfyAll(HaveHTTPStatus(status), HaveHTTPBody(body)))
}
for _, plt := range []string{"", "cluster"} {
plt := plt
It("should error out if using --ignore-localhost on any platform other than Podman", func() {
args := []string{"dev", "--ignore-localhost", "--random-ports"}
if plt != "" {
args = append(args, "--platform", plt)
}
stderr := helper.Cmd("odo", args...).ShouldFail().Err()
Expect(stderr).Should(ContainSubstring("--ignore-localhost cannot be used when running in cluster mode"))
})
It("should error out if using --forward-localhost on any platform other than Podman", func() {
args := []string{"dev", "--forward-localhost", "--random-ports"}
if plt != "" {
args = append(args, "--platform", plt)
}
stderr := helper.Cmd("odo", args...).ShouldFail().Err()
Expect(stderr).Should(ContainSubstring("--forward-localhost cannot be used when running in cluster mode"))
})
}
When("running on default cluster platform", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{})
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should port-forward successfully", func() {
By("not displaying warning message for loopback port", func() {
Expect(devSession.ErrOut).ShouldNot(ContainSubstring("Detected that the following port(s) can be reached only via the container loopback interface"))
})
By("forwarding both loopback and non-loopback ports", func() {
Expect(devSession.Endpoints).Should(HaveLen(2))
Expect(devSession.Endpoints).Should(SatisfyAll(HaveKey("3000"), HaveKey("3001")))
})
By("displaying both loopback and non-loopback ports as forwarded", func() {
Expect(devSession.StdOut).Should(SatisfyAll(
ContainSubstring("Forwarding from %s -> 3000", devSession.Endpoints["3000"]),
ContainSubstring("Forwarding from %s -> 3001", devSession.Endpoints["3001"])))
})
By("reaching both loopback and non-loopback ports via port-forwarding", func() {
for port, body := range map[int]string{
3000: "Hello from Node.js Application!",
3001: "Hello from Node.js Admin Application!",
} {
Eventually(func(g Gomega) {
g.Expect(devSession.Endpoints[strconv.Itoa(port)]).Should(haveHttpResponse(http.StatusOK, body))
}).WithTimeout(60 * time.Second).WithPolling(3 * time.Second).Should(Succeed())
}
})
})
})
Context("running on Podman", Label(helper.LabelPodman), func() {
It("should error out if using both --ignore-localhost and --forward-localhost", func() {
stderr := helper.Cmd("odo", "dev", "--random-ports", "--platform", "podman", "--ignore-localhost", "--forward-localhost").
ShouldFail().
Err()
Expect(stderr).Should(ContainSubstring("--ignore-localhost and --forward-localhost cannot be used together"))
})
It("should error out if not ignoring localhost", func() {
devSession, err := helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: true,
})
Expect(err).ToNot(HaveOccurred())
defer func() {
devSession.Stop()
devSession.WaitEnd()
}()
Expect(devSession.ErrOut).Should(ContainSubstring("Detected that the following port(s) can be reached only via the container loopback interface: admin (3001)"))
})
When("ignoring localhost", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: true,
CmdlineArgs: []string{"--ignore-localhost"},
})
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should port-forward successfully", func() {
By("displaying warning message for loopback port", func() {
Expect(devSession.ErrOut).Should(ContainSubstring("Detected that the following port(s) can be reached only via the container loopback interface: admin (3001)"))
})
By("creating a pod with a single container pod because --forward-localhost is false", func() {
podDef := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner).GetPodDef()
Expect(podDef.Spec.Containers).Should(HaveLen(1))
Expect(podDef.Spec.Containers[0].Name).Should(Equal(fmt.Sprintf("%s-app-runtime", cmpName)))
})
By("reaching the local port for the non-loopback interface", func() {
Eventually(func(g Gomega) {
g.Expect(devSession.Endpoints["3000"]).Should(haveHttpResponse(http.StatusOK, "Hello from Node.js Application!"))
}).WithTimeout(60 * time.Second).WithPolling(3 * time.Second).Should(Succeed())
})
By("not succeeding to reach the local port for the loopback interface", func() {
// By design, Podman will not forward to container apps listening on localhost.
// See https://github.com/redhat-developer/odo/issues/6510 and https://github.com/containers/podman/issues/17353
Consistently(func() error {
_, err := http.Get("http://" + devSession.Endpoints["3001"])
return err
}).Should(HaveOccurred())
})
})
When("making changes in the project source code during the dev session", func() {
BeforeEach(func() {
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "Hello from", "Hiya from the updated")
err := devSession.WaitSync()
Expect(err).ShouldNot(HaveOccurred())
})
It("should port-forward successfully", func() {
By("reaching the local port for the non-loopback interface", func() {
Eventually(func(g Gomega) {
g.Expect(devSession.Endpoints["3000"]).Should(haveHttpResponse(http.StatusOK, "Hiya from the updated Node.js Application!"))
}).WithTimeout(60 * time.Second).WithPolling(3 * time.Second).Should(Succeed())
})
By("not succeeding to reach the local port for the loopback interface", func() {
// By design, Podman will not forward to container apps listening on localhost.
// See https://github.com/redhat-developer/odo/issues/6510 and https://github.com/containers/podman/issues/17353
Consistently(func() error {
_, err := http.Get("http://" + devSession.Endpoints["3001"])
return err
}).Should(HaveOccurred())
})
})
})
})
When("forwarding localhost", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: true,
CmdlineArgs: []string{"--forward-localhost"},
})
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
devSession.WaitEnd()
})
It("should port-forward successfully", func() {
By("not displaying warning message for loopback port", func() {
for _, output := range []string{devSession.StdOut, devSession.ErrOut} {
Expect(output).ShouldNot(ContainSubstring("Detected that the following port(s) can be reached only via the container loopback interface"))
}
})
By("creating a pod with a two-containers pod because --forward-localhost is true", func() {
podDef := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner).GetPodDef()
Expect(podDef.Spec.Containers).Should(HaveLen(2))
var found bool
var pfHelperContainer corev1.Container
for _, container := range podDef.Spec.Containers {
if container.Name == fmt.Sprintf("%s-app-odo-helper-port-forwarding", cmpName) {
pfHelperContainer = container
found = true
break
}
}
Expect(found).Should(BeTrue(), fmt.Sprintf("Could not find container 'odo-helper-port-forwarding' in pod spec: %v", podDef))
Expect(pfHelperContainer.Image).Should(HavePrefix("quay.io/devfile/base-developer-image"))
})
By("reaching the local port for the non-loopback interface", func() {
Eventually(func(g Gomega) {
g.Expect(devSession.Endpoints["3000"]).Should(haveHttpResponse(http.StatusOK, "Hello from Node.js Application!"))
}).WithTimeout(60 * time.Second).WithPolling(3 * time.Second).Should(Succeed())
})
By("reaching the local port for the loopback interface", func() {
Eventually(func(g Gomega) {
g.Expect(devSession.Endpoints["3001"]).Should(haveHttpResponse(http.StatusOK, "Hello from Node.js Admin Application!"))
}).WithTimeout(60 * time.Second).WithPolling(3 * time.Second).Should(Succeed())
})
})
When("making changes in the project source code during the dev session", func() {
BeforeEach(func() {
helper.ReplaceString(filepath.Join(commonVar.Context, "server.js"), "Hello from", "Hiya from the updated")
err := devSession.WaitSync()
Expect(err).ShouldNot(HaveOccurred())
})
It("should port-forward successfully", func() {
By("reaching the local port for the non-loopback interface", func() {
Eventually(func(g Gomega) {
g.Expect(devSession.Endpoints["3000"]).Should(haveHttpResponse(http.StatusOK, "Hiya from the updated Node.js Application!"))
}).WithTimeout(60 * time.Second).WithPolling(3 * time.Second).Should(Succeed())
})
By("reaching the local port for the loopback interface", func() {
Eventually(func(g Gomega) {
g.Expect(devSession.Endpoints["3001"]).Should(haveHttpResponse(http.StatusOK, "Hiya from the updated Node.js Admin Application!"))
}).WithTimeout(60 * time.Second).WithPolling(3 * time.Second).Should(Succeed())
})
})
})
})
})
})
for _, podman := range []bool{false, true} {
podman := podman
// More details on https://github.com/devfile/api/issues/852#issuecomment-1211928487
When("starting with Devfile with autoBuild or deployByDefault components", helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-autobuild-deploybydefault.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
When("running odo dev with some components not referenced in the Devfile", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
var envvars []string
if podman {
envvars = append(envvars, "ODO_PUSH_IMAGES=false")
} else {
envvars = append(envvars, "PODMAN_CMD=echo")
}
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
EnvVars: envvars,
})
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
if podman {
devSession.WaitEnd()
}
})
It("should create the appropriate resources", func() {
if podman {
k8sOcComponents := helper.ExtractK8sAndOcComponentsFromOutputOnPodman(devSession.ErrOut)
By("handling Kubernetes/OpenShift components that would have been created automatically", func() {
Expect(k8sOcComponents).Should(ContainElements(
"k8s-deploybydefault-true-and-referenced",
"k8s-deploybydefault-true-and-not-referenced",
"k8s-deploybydefault-not-set-and-not-referenced",
"ocp-deploybydefault-true-and-referenced",
"ocp-deploybydefault-true-and-not-referenced",
"ocp-deploybydefault-not-set-and-not-referenced",
))
})
By("not handling Kubernetes/OpenShift components with deployByDefault=false", func() {
Expect(k8sOcComponents).ShouldNot(ContainElements(
"k8s-deploybydefault-false-and-referenced",
"k8s-deploybydefault-false-and-not-referenced",
"ocp-deploybydefault-false-and-referenced",
"ocp-deploybydefault-false-and-not-referenced",
))
})
By("not handling referenced Kubernetes/OpenShift components with deployByDefault unset", func() {
Expect(k8sOcComponents).ShouldNot(ContainElement("k8s-deploybydefault-not-set-and-referenced"))
})
} else {
By("automatically applying Kubernetes/OpenShift components with deployByDefault=true", func() {
for _, l := range []string{
"k8s-deploybydefault-true-and-referenced",
"k8s-deploybydefault-true-and-not-referenced",
"ocp-deploybydefault-true-and-referenced",
"ocp-deploybydefault-true-and-not-referenced",
} {
Expect(devSession.StdOut).Should(ContainSubstring("Creating resource Pod/%s", l))
}
})
By("automatically applying non-referenced Kubernetes/OpenShift components with deployByDefault not set", func() {
for _, l := range []string{
"k8s-deploybydefault-not-set-and-not-referenced",
"ocp-deploybydefault-not-set-and-not-referenced",
} {
Expect(devSession.StdOut).Should(ContainSubstring("Creating resource Pod/%s", l))
}
})
By("not applying Kubernetes/OpenShift components with deployByDefault=false", func() {
for _, l := range []string{
"k8s-deploybydefault-false-and-referenced",
"k8s-deploybydefault-false-and-not-referenced",
"ocp-deploybydefault-false-and-referenced",
"ocp-deploybydefault-false-and-not-referenced",
} {
Expect(devSession.StdOut).ShouldNot(ContainSubstring("Creating resource Pod/%s", l))
}
})
By("not applying referenced Kubernetes/OpenShift components with deployByDefault unset", func() {
Expect(devSession.StdOut).ShouldNot(ContainSubstring("Creating resource Pod/k8s-deploybydefault-not-set-and-referenced"))
})
}
imageMessagePrefix := "Building & Pushing Image"
if podman {
imageMessagePrefix = "Building Image"
}
By("automatically applying image components with autoBuild=true", func() {
for _, tag := range []string{
"autobuild-true-and-referenced",
"autobuild-true-and-not-referenced",
} {
Expect(devSession.StdOut).Should(ContainSubstring("%s: localhost:5000/odo-dev/node:%s", imageMessagePrefix, tag))
}
})
By("automatically applying non-referenced Image components with autoBuild not set", func() {
Expect(devSession.StdOut).Should(ContainSubstring("%s: localhost:5000/odo-dev/node:autobuild-not-set-and-not-referenced", imageMessagePrefix))
})
By("not applying image components with autoBuild=false", func() {
for _, tag := range []string{
"autobuild-false-and-referenced",
"autobuild-false-and-not-referenced",
} {
Expect(devSession.StdOut).ShouldNot(ContainSubstring("localhost:5000/odo-dev/node:%s", tag))
}
})
By("not applying referenced Image components with deployByDefault unset", func() {
Expect(devSession.StdOut).ShouldNot(ContainSubstring("localhost:5000/odo-dev/node:autobuild-not-set-and-referenced"))
})
})
})
When("running odo dev with some components referenced in the Devfile", func() {
var devSession helper.DevSession
BeforeEach(func() {
var err error
var envvars []string
if podman {
envvars = append(envvars, "ODO_PUSH_IMAGES=false")
} else {
envvars = append(envvars, "PODMAN_CMD=echo")
}
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
CmdlineArgs: []string{"--run-command", "run-with-referenced-components"},
EnvVars: envvars,
RunOnPodman: podman,
})
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
if podman {
devSession.WaitEnd()
}
})
It("should create the appropriate resources", func() {
if podman {
k8sOcComponents := helper.ExtractK8sAndOcComponentsFromOutputOnPodman(devSession.ErrOut)
By("handling Kubernetes/OpenShift components that would have been created automatically", func() {
Expect(k8sOcComponents).Should(ContainElements(
"k8s-deploybydefault-true-and-referenced",
"k8s-deploybydefault-true-and-not-referenced",
"k8s-deploybydefault-not-set-and-not-referenced",
"ocp-deploybydefault-true-and-referenced",
"ocp-deploybydefault-true-and-not-referenced",
"ocp-deploybydefault-not-set-and-not-referenced",
))
})
By("handling referenced Kubernetes/OpenShift components", func() {
Expect(k8sOcComponents).Should(ContainElements(
"k8s-deploybydefault-true-and-referenced",
"k8s-deploybydefault-false-and-referenced",
"k8s-deploybydefault-not-set-and-referenced",
"ocp-deploybydefault-true-and-referenced",
"ocp-deploybydefault-false-and-referenced",
"ocp-deploybydefault-not-set-and-referenced",
))
})
By("not handling non-referenced Kubernetes/OpenShift components with deployByDefault=false", func() {
Expect(k8sOcComponents).ShouldNot(ContainElements(
"k8s-deploybydefault-false-and-not-referenced",
"ocp-deploybydefault-false-and-not-referenced",
))
})
} else {
By("applying referenced Kubernetes/OpenShift components", func() {
for _, l := range []string{
"k8s-deploybydefault-true-and-referenced",
"k8s-deploybydefault-false-and-referenced",
"k8s-deploybydefault-not-set-and-referenced",
"ocp-deploybydefault-true-and-referenced",
"ocp-deploybydefault-false-and-referenced",
"ocp-deploybydefault-not-set-and-referenced",
} {
Expect(devSession.StdOut).Should(ContainSubstring("Creating resource Pod/%s", l))
}
})
By("automatically applying Kubernetes/OpenShift components with deployByDefault=true", func() {
for _, l := range []string{
"k8s-deploybydefault-true-and-referenced",
"k8s-deploybydefault-true-and-not-referenced",
"ocp-deploybydefault-true-and-referenced",
"ocp-deploybydefault-true-and-not-referenced",
} {
Expect(devSession.StdOut).Should(ContainSubstring("Creating resource Pod/%s", l))
}
})
By("automatically applying non-referenced Kubernetes/OpenShift components with deployByDefault not set", func() {
for _, l := range []string{
"k8s-deploybydefault-not-set-and-not-referenced",
"ocp-deploybydefault-not-set-and-not-referenced",
} {
Expect(devSession.StdOut).Should(ContainSubstring("Creating resource Pod/%s", l))
}
})
By("not applying non-referenced Kubernetes/OpenShift components with deployByDefault=false", func() {
for _, l := range []string{
"k8s-deploybydefault-false-and-not-referenced",
"ocp-deploybydefault-false-and-not-referenced",
} {
Expect(devSession.StdOut).ShouldNot(ContainSubstring("Creating resource Pod/%s", l))
}
})
}
imageMessagePrefix := "Building & Pushing Image"
if podman {
imageMessagePrefix = "Building Image"
}
By("applying referenced image components", func() {
for _, tag := range []string{
"autobuild-true-and-referenced",
"autobuild-false-and-referenced",
"autobuild-not-set-and-referenced",
} {
Expect(devSession.StdOut).Should(ContainSubstring("%s: localhost:5000/odo-dev/node:%s", imageMessagePrefix, tag))
}
})
By("automatically applying image components with autoBuild=true", func() {
for _, tag := range []string{
"autobuild-true-and-referenced",
"autobuild-true-and-not-referenced",
} {
Expect(devSession.StdOut).Should(ContainSubstring("%s: localhost:5000/odo-dev/node:%s", imageMessagePrefix, tag))
}
})
By("automatically applying non-referenced Image components with autoBuild not set", func() {
Expect(devSession.StdOut).Should(ContainSubstring("%s: localhost:5000/odo-dev/node:autobuild-not-set-and-not-referenced", imageMessagePrefix))
})
By("not applying non-referenced image components with autoBuild=false", func() {
Expect(devSession.StdOut).ShouldNot(ContainSubstring("localhost:5000/odo-dev/node:autobuild-false-and-not-referenced"))
})
})
})
}))
}
for _, podman := range []bool{false, true} {
podman := podman
Context("image names as selectors", helper.LabelPodmanIf(podman, func() {
When("starting with a Devfile with relative and absolute image names and Kubernetes resources", func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "nodejs"), commonVar.Context)
helper.CopyExample(
filepath.Join("source", "devfiles", "nodejs", "kubernetes", "devfile-image-names-as-selectors"),
filepath.Join(commonVar.Context, "kubernetes", "devfile-image-names-as-selectors"))
helper.CopyExampleDevFile(filepath.Join("source", "devfiles", "nodejs", "devfile-image-names-as-selectors.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
When("adding a local registry for images", func() {
const imageRegistry = "ttl.sh"
BeforeEach(func() {
helper.Cmd("odo", "preference", "set", "ImageRegistry", imageRegistry, "--force").ShouldPass()
})
AfterEach(func() {
helper.Cmd("odo", "preference", "unset", "ImageRegistry", "--force").ShouldPass()
})
extractContainerNameImageMapFn := func(resourceType, resourceName, jsonPath string) map[string]string {
result := make(map[string]string)
data := commonVar.CliRunner.Run("-n", commonVar.Project, "get", resourceType, resourceName,
"-o", fmt.Sprintf("jsonpath=%s", jsonPath)).Out.Contents()
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
l := scanner.Text()
name, image, found := strings.Cut(l, " ")
if !found {
continue
}
result[name] = image
}
return result
}
When("running odo dev", func() {
var devSession helper.DevSession
BeforeEach(func() {
var env []string
if podman {
env = append(env, "ODO_PUSH_IMAGES=false")
} else {
env = append(env, "PODMAN_CMD=echo")
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
EnvVars: env,
})
Expect(err).ShouldNot(HaveOccurred())
})
AfterEach(func() {
devSession.Stop()
if podman {
devSession.WaitEnd()
}
})
It("should treat relative image names as selectors", func() {
imageMessagePrefix := "Building & Pushing Image"
if podman {
imageMessagePrefix = "Building Image"
}
lines, err := helper.ExtractLines(devSession.StdOut)
Expect(err).ShouldNot(HaveOccurred())
var replacementImageName string
var imagesProcessed []string
re := regexp.MustCompile(fmt.Sprintf(`(?:%s):\s*([^\n]+)`, imageMessagePrefix))
replaceImageRe := regexp.MustCompile(fmt.Sprintf("%s/%s-nodejs-devtools:[^\n]+", imageRegistry, cmpName))
for _, l := range lines {
matches := re.FindStringSubmatch(l)
if len(matches) > 1 {
img := matches[1]
imagesProcessed = append(imagesProcessed, img)
if replaceImageRe.MatchString(img) {
replacementImageName = img
}
}
}
By("building and optionally pushing relative image components", func() {
Expect(replacementImageName).ShouldNot(BeEmpty(), "could not find image matching regexp %v", replaceImageRe)
Expect(imagesProcessed).Should(ContainElement(
MatchRegexp(fmt.Sprintf("%s/%s-nodejs-devtools:[^\n]+", imageRegistry, cmpName))))
})
By("building and optionally pushing absolute image components with no replacement", func() {
for _, img := range []string{"ttl.sh/odo-dev-node:1h", "ttl.sh/nodejs-devtools2:1h"} {
Expect(imagesProcessed).Should(ContainElement(img))
}
})
if !podman {
// On Podman, `odo dev` just warns if there are any Kubernetes/OpenShift components at the moment. But this is already tested elsewhere
// and not useful to test here.
// But we should definitely test it if we plan on supporting more K8s resources from those components.
type resourceData struct {
containers map[string]string
initContainers map[string]string
}
k8sResourcesDeployed := map[string]resourceData{
"CronJob": {
containers: extractContainerNameImageMapFn("CronJob", "my-ocp-cron-job",
"{range .spec.jobTemplate.spec.template.spec.containers[*]}{.name} {.image}{\"\\n\"}{end}"),
initContainers: extractContainerNameImageMapFn("CronJob", "my-ocp-cron-job",
"{range .spec.jobTemplate.spec.template.spec.initContainers[*]}{.name} {.image}{\"\\n\"}{end}"),
},
"DaemonSet": {
containers: extractContainerNameImageMapFn("DaemonSet", "my-k8s-daemonset",
"{range .spec.template.spec.containers[*]}{.name} {.image}{\"\\n\"}{end}"),
initContainers: extractContainerNameImageMapFn("DaemonSet", "my-k8s-daemonset",
"{range .spec.template.spec.initContainers[*]}{.name} {.image}{\"\\n\"}{end}"),
},
"Deployment": {
containers: extractContainerNameImageMapFn("Deployment", "my-k8s-deployment",
"{range .spec.template.spec.containers[*]}{.name} {.image}{\"\\n\"}{end}"),
initContainers: extractContainerNameImageMapFn("Deployment", "my-k8s-deployment",
"{range .spec.template.spec.initContainers[*]}{.name} {.image}{\"\\n\"}{end}"),
},
"Job": {
containers: extractContainerNameImageMapFn("Job", "my-ocp-job",
"{range .spec.template.spec.containers[*]}{.name} {.image}{\"\\n\"}{end}"),
initContainers: extractContainerNameImageMapFn("Job", "my-ocp-job",
"{range .spec.template.spec.initContainers[*]}{.name} {.image}{\"\\n\"}{end}"),
},
"Pod": {
containers: extractContainerNameImageMapFn("Pod", "my-k8s-pod",
"{range .spec.containers[*]}{.name} {.image}{\"\\n\"}{end}"),
initContainers: extractContainerNameImageMapFn("Pod", "my-k8s-pod",
"{range .spec.initContainers[*]}{.name} {.image}{\"\\n\"}{end}"),
},
"ReplicaSet": {
containers: extractContainerNameImageMapFn("ReplicaSet", "my-k8s-replicaset",
"{range .spec.template.spec.containers[*]}{.name} {.image}{\"\\n\"}{end}"),
initContainers: extractContainerNameImageMapFn("ReplicaSet", "my-k8s-replicaset",
"{range .spec.template.spec.initContainers[*]}{.name} {.image}{\"\\n\"}{end}"),
},
"ReplicationController": {
containers: extractContainerNameImageMapFn("ReplicationController", "my-k8s-replicationcontroller",
"{range .spec.template.spec.containers[*]}{.name} {.image}{\"\\n\"}{end}"),
initContainers: extractContainerNameImageMapFn("ReplicationController", "my-k8s-replicationcontroller",
"{range .spec.template.spec.initContainers[*]}{.name} {.image}{\"\\n\"}{end}"),
},
"StatefulSet": {
containers: extractContainerNameImageMapFn("StatefulSet", "my-k8s-statefulset",
"{range .spec.template.spec.containers[*]}{.name} {.image}{\"\\n\"}{end}"),
initContainers: extractContainerNameImageMapFn("StatefulSet", "my-k8s-statefulset",
"{range .spec.template.spec.initContainers[*]}{.name} {.image}{\"\\n\"}{end}"),
},
}
By("replacing matching image names in core Kubernetes components", func() {
const mainCont1 = "my-main-cont1"
for resType, data := range k8sResourcesDeployed {
Expect(data.containers[mainCont1]).Should(
Equal(replacementImageName),
func() string {
return fmt.Sprintf(
"unexpected image for container %q in %q deployed from K8s or OCP component. All resources: %v",
mainCont1, resType, k8sResourcesDeployed)
})
}
})
By("not replacing non-matching or absolute image names in core Kubernetes resources", func() {
const (
mainCont2 = "my-main-cont2"
initCont1 = "my-init-cont1"
initCont2 = "my-init-cont2"
)
for resType, data := range k8sResourcesDeployed {
Expect(data.containers[mainCont2]).Should(
Equal("ttl.sh/nodejs-devtools2:1h"),
func() string {
return fmt.Sprintf(
"unexpected image for container %q in %q deployed from K8s or OCP component. All resources: %v",
mainCont2, resType, k8sResourcesDeployed)
})
Expect(data.initContainers[initCont1]).Should(
Equal("ttl.sh/odo-dev-node:1h"),
func() string {
return fmt.Sprintf(
"unexpected image for init container %q in %q deployed from K8s or OCP component. All resources: %v",
initCont1, resType, k8sResourcesDeployed)
})
Expect(data.initContainers[initCont2]).Should(
Equal("nodejs-devtools007"),
func() string {
return fmt.Sprintf(
"unexpected image for init container %q in %q deployed from K8s or OCP component. All resources: %v",
initCont1, resType, k8sResourcesDeployed)
})
}
})
}
})
})
})
})
}))
}
for _, podman := range []bool{false, true} {
podman := podman
When("using a Devfile with no commands", helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-without-commands.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
for _, noCommands := range []bool{false, true} {
noCommands := noCommands
It(fmt.Sprintf("should start the Dev Session with --no-commands=%v", noCommands), func() {
devSession, err := helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
NoCommands: noCommands,
})
Expect(err).ShouldNot(HaveOccurred())
defer func() {
devSession.Stop()
devSession.WaitEnd()
}()
By("syncing the files", func() {
Expect(devSession.StdOut).Should(ContainSubstring("Syncing files into the container"))
component := helper.NewComponent(cmpName, "app", labels.ComponentDevMode, commonVar.Project, commonVar.CliRunner)
execResult, _ := component.Exec("runtime", []string{"ls", "-lai", "/projects"}, pointer.Bool(true))
for _, fileName := range []string{"server.js", "package.json"} {
Expect(execResult).Should(ContainSubstring(fileName))
}
execResult, _ = component.Exec("runtime", []string{"cat", "/projects/server.js"}, pointer.Bool(true))
Expect(execResult).Should(ContainSubstring("App started"))
})
By("not executing any build command", func() {
for _, out := range []string{devSession.StdOut, devSession.ErrOut} {
Expect(string(out)).ShouldNot(ContainSubstring("Building your application in container on cluster"))
}
})
By("not executing any run command", func() {
for _, out := range []string{devSession.StdOut, devSession.ErrOut} {
Expect(string(out)).ShouldNot(ContainSubstring("Executing the application"))
}
})
if !noCommands {
By("warning about missing default run command", func() {
Expect(devSession.ErrOut).Should(ContainSubstring("Missing default run command"))
})
}
By("setting up port forwarding", func() {
Expect(devSession.Endpoints).ShouldNot(BeEmpty())
_, ok := devSession.Endpoints["3000"]
Expect(ok).To(BeTrue(), fmt.Sprintf("missing port forwarded for 3000: %v", devSession.Endpoints))
})
})
}
}))
}
Context("cleanup of resources is not successful", func() {
for _, podman := range []bool{false, true} {
podman := podman
When("using a Devfile with a Pod failing to delete before the cleanup timeout", helper.LabelPodmanIf(podman, func() {
BeforeEach(func() {
helper.CopyExample(filepath.Join("source", "devfiles", "nodejs", "project"), commonVar.Context)
helper.CopyExampleDevFile(
filepath.Join("source", "devfiles", "nodejs", "devfile-with-container-failing-to-terminate-before-cleanup-timeout.yaml"),
filepath.Join(commonVar.Context, "devfile.yaml"),
cmpName)
})
When("odo dev is executed", helper.LabelPodmanIf(podman, func() {
var devSession helper.DevSession
BeforeEach(func() {
if podman {
Skip("podman does not support container lifecycle hooks and pod termination grace period")
}
var err error
devSession, err = helper.StartDevMode(helper.DevSessionOpts{
RunOnPodman: podman,
})
Expect(err).ToNot(HaveOccurred())
})
When("odo dev is stopped", func() {
BeforeEach(func() {
devSession.Stop()
devSession.WaitEnd()
Expect(devSession.UpdateInfo()).ShouldNot(HaveOccurred())
})
It("should report that the component could not be deleted", func() {
By("deleting the component", func() {
Expect(devSession.ErrOut).Should(ContainSubstring("could not delete the following resource(s)"))
Expect(devSession.ErrOut).Should(ContainSubstring("- Deployment/%s-app", cmpName))
})
By("not exiting successfully", func() {
Expect(devSession.GetExitCode()).ShouldNot(Equal(0), "unexpected exit code for the dev session")
})
})
})
}))
}))
}
})
})