From 9cf6278c99a5a014b5b6b5da458935ccac0a0f51 Mon Sep 17 00:00:00 2001 From: Luca Di Maio Date: Tue, 7 Nov 2023 13:10:12 +0100 Subject: [PATCH 1/3] tests: parallelize tests using labels and matrix Signed-off-by: Luca Di Maio --- .github/workflows/e2e-tests.yaml | 7 +- e2e/tests/build/build.go | 330 +-- e2e/tests/ide/ide.go | 62 +- e2e/tests/integration/integration.go | 128 +- e2e/tests/machine/create.go | 70 +- e2e/tests/machine/delete.go | 64 +- e2e/tests/machineprovider/machineprovider.go | 232 +- e2e/tests/provider/provider.go | 154 +- e2e/tests/proxyprovider/proxyprovider.go | 380 +-- e2e/tests/ssh/ssh.go | 106 +- e2e/tests/up/up.go | 2418 +++++++++--------- 11 files changed, 1988 insertions(+), 1963 deletions(-) diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index 0f5851b3b..e27e76f31 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -23,6 +23,11 @@ env: jobs: test-e2e: runs-on: ubuntu-latest + strategy: + fail-fast: true + max-parallel: 8 + matrix: + label: [ "build", "ide", "integration", "machine", "machineprovider", "provider", "proxyprovider", "ssh", "up" ] steps: - name: Checkout repo @@ -56,4 +61,4 @@ jobs: - name: E2E test working-directory: ./e2e run: | - sudo KUBECONFIG=/home/runner/.kube/config go test -v -ginkgo.v -timeout 3600s + sudo KUBECONFIG=/home/runner/.kube/config go test -v -ginkgo.v -timeout 3600s --ginkgo.label-filter=${{ matrix.label }} diff --git a/e2e/tests/build/build.go b/e2e/tests/build/build.go index 80822a653..80c881d18 100644 --- a/e2e/tests/build/build.go +++ b/e2e/tests/build/build.go @@ -15,170 +15,172 @@ import ( ) var _ = DevPodDescribe("devpod build test suite", func() { - var initialDir string - var dockerHelper *docker.DockerHelper - - ginkgo.BeforeEach(func() { - var err error - initialDir, err = os.Getwd() - framework.ExpectNoError(err) - dockerHelper = &docker.DockerHelper{DockerCommand: "docker"} - }) - - ginkgo.It("build docker buildx", func() { - ctx := context.Background() - - f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/build/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) - - cfg := getDevcontainerConfig(tempDir) - - dockerfilePath := tempDir + "/.devcontainer/Dockerfile" - dockerfileContent, err := os.ReadFile(dockerfilePath) - framework.ExpectNoError(err) - _, modifiedDockerfileContents, err := dockerfile.EnsureDockerfileHasFinalStageName(string(dockerfileContent), config.DockerfileDefaultTarget) - framework.ExpectNoError(err) - - prebuildRepo := "test-repo" - - // do the build - err = f.DevPodBuild(ctx, tempDir, "--force-build", "--platform", "linux/amd64,linux/arm64", "--repository", prebuildRepo, "--skip-push") - framework.ExpectNoError(err) - - // make sure images are there - prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default) - framework.ExpectNoError(err) - _, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false) - framework.ExpectNoError(err) - - prebuildHash, err = config.CalculatePrebuildHash(cfg, "linux/arm64", "arm64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default) - framework.ExpectNoError(err) - _, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false) - framework.ExpectNoError(err) - }) - - ginkgo.It("should build image without repository specified if skip-push flag is set", func() { - ctx := context.Background() - - f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/build/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) - - cfg := getDevcontainerConfig(tempDir) - - dockerfilePath := tempDir + "/.devcontainer/Dockerfile" - dockerfileContent, err := os.ReadFile(dockerfilePath) - framework.ExpectNoError(err) - _, modifiedDockerfileContents, err := dockerfile.EnsureDockerfileHasFinalStageName(string(dockerfileContent), config.DockerfileDefaultTarget) - framework.ExpectNoError(err) - - // do the build - err = f.DevPodBuild(ctx, tempDir, "--skip-push") - framework.ExpectNoError(err) - - // make sure images are there - prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default) - framework.ExpectNoError(err) - _, err = dockerHelper.InspectImage(ctx, dockerdriver.GetImageName(tempDir, prebuildHash), false) - framework.ExpectNoError(err) - }) - - ginkgo.It("should build the image of the referenced service from the docker compose file", func() { - ctx := context.Background() - - f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/build/testdata/docker-compose") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) - - prebuildRepo := "test-repo" - - // do the build - err = f.DevPodBuild(ctx, tempDir, "--repository", prebuildRepo, "--skip-push") - framework.ExpectNoError(err) - }) - - ginkgo.It("build docker internal buildkit", func() { - ctx := context.Background() - - f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/build/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) - - cfg := getDevcontainerConfig(tempDir) - - dockerfilePath := tempDir + "/.devcontainer/Dockerfile" - dockerfileContent, err := os.ReadFile(dockerfilePath) - framework.ExpectNoError(err) - _, modifiedDockerfileContents, err := dockerfile.EnsureDockerfileHasFinalStageName(string(dockerfileContent), config.DockerfileDefaultTarget) - framework.ExpectNoError(err) - - prebuildRepo := "test-repo" - - // do the build - err = f.DevPodBuild(ctx, tempDir, "--force-build", "--force-internal-buildkit", "--repository", prebuildRepo, "--skip-push") - framework.ExpectNoError(err) - - // make sure images are there - prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default) - framework.ExpectNoError(err) - - _, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false) - framework.ExpectNoError(err) - }) - - ginkgo.It("build kubernetes dockerless", func() { - ctx := context.Background() - - f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/build/testdata/kubernetes") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - _ = f.DevPodProviderDelete(ctx, "kubernetes") - err = f.DevPodProviderAdd(ctx, "kubernetes") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "kubernetes", "-o", "KUBERNETES_NAMESPACE=devpod") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // do the up - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - // check if ssh works - out, err := f.DevPodSSH(ctx, tempDir, "echo -n $MY_TEST") - framework.ExpectNoError(err) - framework.ExpectEqual(out, "test456", "should contain my-test") + ginkgo.Context("testing build", ginkgo.Label("build"), ginkgo.Ordered, func() { + var initialDir string + var dockerHelper *docker.DockerHelper + + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) + dockerHelper = &docker.DockerHelper{DockerCommand: "docker"} + }) + + ginkgo.It("build docker buildx", func() { + ctx := context.Background() + + f := framework.NewDefaultFramework(initialDir + "/bin") + tempDir, err := framework.CopyToTempDir("tests/build/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) + + cfg := getDevcontainerConfig(tempDir) + + dockerfilePath := tempDir + "/.devcontainer/Dockerfile" + dockerfileContent, err := os.ReadFile(dockerfilePath) + framework.ExpectNoError(err) + _, modifiedDockerfileContents, err := dockerfile.EnsureDockerfileHasFinalStageName(string(dockerfileContent), config.DockerfileDefaultTarget) + framework.ExpectNoError(err) + + prebuildRepo := "test-repo" + + // do the build + err = f.DevPodBuild(ctx, tempDir, "--force-build", "--platform", "linux/amd64,linux/arm64", "--repository", prebuildRepo, "--skip-push") + framework.ExpectNoError(err) + + // make sure images are there + prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default) + framework.ExpectNoError(err) + _, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false) + framework.ExpectNoError(err) + + prebuildHash, err = config.CalculatePrebuildHash(cfg, "linux/arm64", "arm64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default) + framework.ExpectNoError(err) + _, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false) + framework.ExpectNoError(err) + }) + + ginkgo.It("should build image without repository specified if skip-push flag is set", func() { + ctx := context.Background() + + f := framework.NewDefaultFramework(initialDir + "/bin") + tempDir, err := framework.CopyToTempDir("tests/build/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) + + cfg := getDevcontainerConfig(tempDir) + + dockerfilePath := tempDir + "/.devcontainer/Dockerfile" + dockerfileContent, err := os.ReadFile(dockerfilePath) + framework.ExpectNoError(err) + _, modifiedDockerfileContents, err := dockerfile.EnsureDockerfileHasFinalStageName(string(dockerfileContent), config.DockerfileDefaultTarget) + framework.ExpectNoError(err) + + // do the build + err = f.DevPodBuild(ctx, tempDir, "--skip-push") + framework.ExpectNoError(err) + + // make sure images are there + prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default) + framework.ExpectNoError(err) + _, err = dockerHelper.InspectImage(ctx, dockerdriver.GetImageName(tempDir, prebuildHash), false) + framework.ExpectNoError(err) + }) + + ginkgo.It("should build the image of the referenced service from the docker compose file", func() { + ctx := context.Background() + + f := framework.NewDefaultFramework(initialDir + "/bin") + tempDir, err := framework.CopyToTempDir("tests/build/testdata/docker-compose") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) + + prebuildRepo := "test-repo" + + // do the build + err = f.DevPodBuild(ctx, tempDir, "--repository", prebuildRepo, "--skip-push") + framework.ExpectNoError(err) + }) + + ginkgo.It("build docker internal buildkit", func() { + ctx := context.Background() + + f := framework.NewDefaultFramework(initialDir + "/bin") + tempDir, err := framework.CopyToTempDir("tests/build/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) + + cfg := getDevcontainerConfig(tempDir) + + dockerfilePath := tempDir + "/.devcontainer/Dockerfile" + dockerfileContent, err := os.ReadFile(dockerfilePath) + framework.ExpectNoError(err) + _, modifiedDockerfileContents, err := dockerfile.EnsureDockerfileHasFinalStageName(string(dockerfileContent), config.DockerfileDefaultTarget) + framework.ExpectNoError(err) + + prebuildRepo := "test-repo" + + // do the build + err = f.DevPodBuild(ctx, tempDir, "--force-build", "--force-internal-buildkit", "--repository", prebuildRepo, "--skip-push") + framework.ExpectNoError(err) + + // make sure images are there + prebuildHash, err := config.CalculatePrebuildHash(cfg, "linux/amd64", "amd64", filepath.Dir(cfg.Origin), dockerfilePath, modifiedDockerfileContents, log.Default) + framework.ExpectNoError(err) + + _, err = dockerHelper.InspectImage(ctx, prebuildRepo+":"+prebuildHash, false) + framework.ExpectNoError(err) + }) + + ginkgo.It("build kubernetes dockerless", func() { + ctx := context.Background() + + f := framework.NewDefaultFramework(initialDir + "/bin") + tempDir, err := framework.CopyToTempDir("tests/build/testdata/kubernetes") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + _ = f.DevPodProviderDelete(ctx, "kubernetes") + err = f.DevPodProviderAdd(ctx, "kubernetes") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "kubernetes", "-o", "KUBERNETES_NAMESPACE=devpod") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // do the up + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + // check if ssh works + out, err := f.DevPodSSH(ctx, tempDir, "echo -n $MY_TEST") + framework.ExpectNoError(err) + framework.ExpectEqual(out, "test456", "should contain my-test") + }) }) }) diff --git a/e2e/tests/ide/ide.go b/e2e/tests/ide/ide.go index 9ff2e1d67..8e69d0312 100644 --- a/e2e/tests/ide/ide.go +++ b/e2e/tests/ide/ide.go @@ -9,46 +9,48 @@ import ( ) var _ = DevPodDescribe("devpod ide test suite", func() { - var initialDir string + ginkgo.Context("testing ides", ginkgo.Label("ide"), ginkgo.Ordered, func() { + var initialDir string - ginkgo.BeforeEach(func() { - var err error - initialDir, err = os.Getwd() - framework.ExpectNoError(err) - }) + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) + }) - ginkgo.It("start ides", func() { - ctx := context.Background() + ginkgo.It("start ides", func() { + ctx := context.Background() - f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/ide/testdata") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + f := framework.NewDefaultFramework(initialDir + "/bin") + tempDir, err := framework.CopyToTempDir("tests/ide/testdata") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - err = f.DevPodUpWithIDE(ctx, tempDir, "--open-ide=false", "--ide=vscode") - framework.ExpectNoError(err) + err = f.DevPodUpWithIDE(ctx, tempDir, "--open-ide=false", "--ide=vscode") + framework.ExpectNoError(err) - err = f.DevPodUpWithIDE(ctx, tempDir, "--open-ide=false", "--ide=openvscode") - framework.ExpectNoError(err) + err = f.DevPodUpWithIDE(ctx, tempDir, "--open-ide=false", "--ide=openvscode") + framework.ExpectNoError(err) - err = f.DevPodUpWithIDE(ctx, tempDir, "--open-ide=false", "--ide=jupyternotebook") - framework.ExpectNoError(err) + err = f.DevPodUpWithIDE(ctx, tempDir, "--open-ide=false", "--ide=jupyternotebook") + framework.ExpectNoError(err) - err = f.DevPodUpWithIDE(ctx, tempDir, "--open-ide=false", "--ide=fleet") - framework.ExpectNoError(err) + err = f.DevPodUpWithIDE(ctx, tempDir, "--open-ide=false", "--ide=fleet") + framework.ExpectNoError(err) - // check if ssh works - err = f.DevPodSSHEchoTestString(ctx, tempDir) - framework.ExpectNoError(err) + // check if ssh works + err = f.DevPodSSHEchoTestString(ctx, tempDir) + framework.ExpectNoError(err) - // TODO: test jetbrains ides + // TODO: test jetbrains ides + }) }) }) diff --git a/e2e/tests/integration/integration.go b/e2e/tests/integration/integration.go index 46ff531d6..3f07697d0 100644 --- a/e2e/tests/integration/integration.go +++ b/e2e/tests/integration/integration.go @@ -12,79 +12,81 @@ import ( ) var _ = ginkgo.Describe("[integration]: devpod provider ssh test suite", ginkgo.Ordered, func() { - var initialDir string - ctx := context.Background() + ginkgo.Context("testing provider integration", ginkgo.Label("integration"), ginkgo.Ordered, func() { + var initialDir string + ctx := context.Background() - ginkgo.BeforeEach(func() { - var err error - initialDir, err = os.Getwd() - framework.ExpectNoError(err) - }) - - ginkgo.It("should generate ssh keypairs", func() { - _, err := os.Stat(os.Getenv("HOME") + "/.ssh/id_rsa") - if err != nil { - fmt.Println("generating ssh keys") - cmd := exec.Command("ssh-keygen", "-q", "-t", "rsa", "-N", "", "-f", os.Getenv("HOME")+"/.ssh/id_rsa") - err = cmd.Run() + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() framework.ExpectNoError(err) - - cmd = exec.Command("ssh-keygen", "-y", "-f", os.Getenv("HOME")+"/.ssh/id_rsa") - output, err := cmd.Output() + }) + + ginkgo.It("should generate ssh keypairs", func() { + _, err := os.Stat(os.Getenv("HOME") + "/.ssh/id_rsa") + if err != nil { + fmt.Println("generating ssh keys") + cmd := exec.Command("ssh-keygen", "-q", "-t", "rsa", "-N", "", "-f", os.Getenv("HOME")+"/.ssh/id_rsa") + err = cmd.Run() + framework.ExpectNoError(err) + + cmd = exec.Command("ssh-keygen", "-y", "-f", os.Getenv("HOME")+"/.ssh/id_rsa") + output, err := cmd.Output() + framework.ExpectNoError(err) + + err = os.WriteFile(os.Getenv("HOME")+"/.ssh/id_rsa.pub", output, 0600) + framework.ExpectNoError(err) + } + + cmd := exec.Command("ssh-keygen", "-y", "-f", os.Getenv("HOME")+"/.ssh/id_rsa") + publicKey, err := cmd.Output() framework.ExpectNoError(err) - err = os.WriteFile(os.Getenv("HOME")+"/.ssh/id_rsa.pub", output, 0600) + _, err = os.Stat(os.Getenv("HOME") + "/.ssh/authorized_keys") + if err != nil { + err = os.WriteFile(os.Getenv("HOME")+"/.ssh/authorized_keys", publicKey, 0600) + framework.ExpectNoError(err) + } else { + f, err := os.OpenFile(os.Getenv("HOME")+"/.ssh/authorized_keys", + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + framework.ExpectNoError(err) + + defer f.Close() + _, err = f.Write(publicKey) + framework.ExpectNoError(err) + } + }) + + ginkgo.It("should add provider to devpod", func() { + f := framework.NewDefaultFramework(initialDir + "/bin") + // ensure we don't have the ssh provider present + err := f.DevPodProviderDelete(ctx, "ssh") + if err != nil { + fmt.Println("warning: " + err.Error()) + } + + err = f.DevPodProviderAdd(ctx, "ssh", "-o", "HOST=localhost") framework.ExpectNoError(err) - } + }) - cmd := exec.Command("ssh-keygen", "-y", "-f", os.Getenv("HOME")+"/.ssh/id_rsa") - publicKey, err := cmd.Output() - framework.ExpectNoError(err) - - _, err = os.Stat(os.Getenv("HOME") + "/.ssh/authorized_keys") - if err != nil { - err = os.WriteFile(os.Getenv("HOME")+"/.ssh/authorized_keys", publicKey, 0600) - framework.ExpectNoError(err) - } else { - f, err := os.OpenFile(os.Getenv("HOME")+"/.ssh/authorized_keys", - os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + ginkgo.It("should run devpod up", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + err := f.DevPodUp(ctx, "tests/integration/testdata/") framework.ExpectNoError(err) + }) - defer f.Close() - _, err = f.Write(publicKey) + ginkgo.It("should run commands to workspace via ssh", func() { + cmd := exec.Command("ssh", "testdata.devpod", "echo", "test") + output, err := cmd.Output() framework.ExpectNoError(err) - } - }) - ginkgo.It("should add provider to devpod", func() { - f := framework.NewDefaultFramework(initialDir + "/bin") - // ensure we don't have the ssh provider present - err := f.DevPodProviderDelete(ctx, "ssh") - if err != nil { - fmt.Println("warning: " + err.Error()) - } + gomega.Expect(output).To(gomega.Equal([]byte("test\n"))) + }) - err = f.DevPodProviderAdd(ctx, "ssh", "-o", "HOST=localhost") - framework.ExpectNoError(err) - }) - - ginkgo.It("should run devpod up", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") - err := f.DevPodUp(ctx, "tests/integration/testdata/") - framework.ExpectNoError(err) - }) - - ginkgo.It("should run commands to workspace via ssh", func() { - cmd := exec.Command("ssh", "testdata.devpod", "echo", "test") - output, err := cmd.Output() - framework.ExpectNoError(err) - - gomega.Expect(output).To(gomega.Equal([]byte("test\n"))) - }) - - ginkgo.It("should cleanup devpod workspace", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") - err := f.DevPodWorkspaceDelete(ctx, "testdata") - framework.ExpectNoError(err) + ginkgo.It("should cleanup devpod workspace", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + err := f.DevPodWorkspaceDelete(ctx, "testdata") + framework.ExpectNoError(err) + }) }) }) diff --git a/e2e/tests/machine/create.go b/e2e/tests/machine/create.go index cd11a217b..0d6f5d475 100644 --- a/e2e/tests/machine/create.go +++ b/e2e/tests/machine/create.go @@ -10,39 +10,41 @@ import ( ) var _ = DevPodDescribe("devpod machine create", func() { - ctx := context.Background() - initialDir, err := os.Getwd() - if err != nil { - panic(err) - } - - ginkgo.It("should add simple machine and then delete it", func() { - tempDir, err := framework.CopyToTempDir("tests/machine/testdata") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - // Ensure that mock-provider is deleted - _ = f.DevPodProviderDelete(ctx, "mock-provider") - - ginkgo.By("Add mock provider") - err = f.DevPodProviderAdd(ctx, tempDir+"/mock-provider.yaml") - framework.ExpectNoError(err) - - ginkgo.By("Use mock provier") - err = f.DevPodProviderUse(context.Background(), "mock-provider") - framework.ExpectNoError(err) - - machineUUID, _ := uuid.NewRandom() - machineName := machineUUID.String() - - ginkgo.By("Create test machine with mock provider") - err = f.DevPodMachineCreate([]string{machineName}) - framework.ExpectNoError(err) - - ginkgo.By("Remove test machine") - err = f.DevPodMachineDelete([]string{machineName}) - framework.ExpectNoError(err) + ginkgo.Context("testing machine", ginkgo.Label("machine"), ginkgo.Ordered, func() { + ctx := context.Background() + initialDir, err := os.Getwd() + if err != nil { + panic(err) + } + + ginkgo.It("should add simple machine and then delete it", func() { + tempDir, err := framework.CopyToTempDir("tests/machine/testdata") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + // Ensure that mock-provider is deleted + _ = f.DevPodProviderDelete(ctx, "mock-provider") + + ginkgo.By("Add mock provider") + err = f.DevPodProviderAdd(ctx, tempDir+"/mock-provider.yaml") + framework.ExpectNoError(err) + + ginkgo.By("Use mock provier") + err = f.DevPodProviderUse(context.Background(), "mock-provider") + framework.ExpectNoError(err) + + machineUUID, _ := uuid.NewRandom() + machineName := machineUUID.String() + + ginkgo.By("Create test machine with mock provider") + err = f.DevPodMachineCreate([]string{machineName}) + framework.ExpectNoError(err) + + ginkgo.By("Remove test machine") + err = f.DevPodMachineDelete([]string{machineName}) + framework.ExpectNoError(err) + }) }) }) diff --git a/e2e/tests/machine/delete.go b/e2e/tests/machine/delete.go index 280d097b7..3d0abe6b6 100644 --- a/e2e/tests/machine/delete.go +++ b/e2e/tests/machine/delete.go @@ -10,46 +10,48 @@ import ( ) var _ = DevPodDescribe("devpod machine delete", func() { - ctx := context.Background() - initialDir, err := os.Getwd() - if err != nil { - panic(err) - } + ginkgo.Context("testing machine", ginkgo.Label("machine"), ginkgo.Ordered, func() { + ctx := context.Background() + initialDir, err := os.Getwd() + if err != nil { + panic(err) + } - ginkgo.It("should delete a non-existing machine and get an error", func() { - tempDir, err := framework.CopyToTempDir("tests/machine/testdata") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ginkgo.It("should delete a non-existing machine and get an error", func() { + tempDir, err := framework.CopyToTempDir("tests/machine/testdata") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - f := framework.NewDefaultFramework(initialDir + "/bin") + f := framework.NewDefaultFramework(initialDir + "/bin") - // Ensure that mock-provider is deleted - _ = f.DevPodProviderDelete(ctx, "mock-provider") + // Ensure that mock-provider is deleted + _ = f.DevPodProviderDelete(ctx, "mock-provider") - ginkgo.By("Add mock provider") - err = f.DevPodProviderAdd(ctx, tempDir+"/mock-provider.yaml") - framework.ExpectNoError(err) + ginkgo.By("Add mock provider") + err = f.DevPodProviderAdd(ctx, tempDir+"/mock-provider.yaml") + framework.ExpectNoError(err) - ginkgo.By("Use mock provier") - err = f.DevPodProviderUse(context.Background(), "mock-provider") - framework.ExpectNoError(err) + ginkgo.By("Use mock provier") + err = f.DevPodProviderUse(context.Background(), "mock-provider") + framework.ExpectNoError(err) - machineUUID1, _ := uuid.NewRandom() - machineName1 := machineUUID1.String() + machineUUID1, _ := uuid.NewRandom() + machineName1 := machineUUID1.String() - machineUUID2, _ := uuid.NewRandom() - machineName2 := machineUUID2.String() + machineUUID2, _ := uuid.NewRandom() + machineName2 := machineUUID2.String() - ginkgo.By("Create test machine with mock provider") - err = f.DevPodMachineCreate([]string{machineName1}) - framework.ExpectNoError(err) + ginkgo.By("Create test machine with mock provider") + err = f.DevPodMachineCreate([]string{machineName1}) + framework.ExpectNoError(err) - ginkgo.By("Remove existing test machine") - err = f.DevPodMachineDelete([]string{machineName1}) - framework.ExpectNoError(err) + ginkgo.By("Remove existing test machine") + err = f.DevPodMachineDelete([]string{machineName1}) + framework.ExpectNoError(err) - ginkgo.By("Remove not existing test machine (should get an error)") - err = f.DevPodMachineDelete([]string{machineName2}) - framework.ExpectError(err) + ginkgo.By("Remove not existing test machine (should get an error)") + err = f.DevPodMachineDelete([]string{machineName2}) + framework.ExpectError(err) + }) }) }) diff --git a/e2e/tests/machineprovider/machineprovider.go b/e2e/tests/machineprovider/machineprovider.go index ab70084ec..ea128ad6d 100644 --- a/e2e/tests/machineprovider/machineprovider.go +++ b/e2e/tests/machineprovider/machineprovider.go @@ -13,146 +13,148 @@ import ( ) var _ = DevPodDescribe("devpod machine provider test suite", func() { - var initialDir string + ginkgo.Context("testing machine providers", ginkgo.Label("machineprovider"), ginkgo.Ordered, func() { + var initialDir string - ginkgo.BeforeEach(func() { - var err error - initialDir, err = os.Getwd() - framework.ExpectNoError(err) - }) - - ginkgo.It("test start / stop / status", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") - - // copy test dir - tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/machineprovider/testdata/machineprovider") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - _ = os.RemoveAll(tempDir) - }) - - tempDirLocation, err := os.MkdirTemp("", "") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - _ = os.RemoveAll(tempDirLocation) - }) - - // create docker provider - err = f.DevPodProviderAdd(ctx, filepath.Join(tempDir, "provider.yaml"), "-o", "LOCATION="+tempDirLocation) - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - err = f.DevPodProviderDelete(context.Background(), "docker123") + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() framework.ExpectNoError(err) }) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - - // expect workspace - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - // check status - status, err := f.DevPodStatus(ctx, tempDir) - framework.ExpectNoError(err) - framework.ExpectEqual(strings.ToUpper(status.State), "RUNNING", "workspace status did not match") + ginkgo.It("test start / stop / status", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") - // stop container - err = f.DevPodStop(ctx, tempDir) - framework.ExpectNoError(err) + // copy test dir + tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/machineprovider/testdata/machineprovider") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + _ = os.RemoveAll(tempDir) + }) - // check status - status, err = f.DevPodStatus(ctx, tempDir) - framework.ExpectNoError(err) - framework.ExpectEqual(strings.ToUpper(status.State), "STOPPED", "workspace status did not match") + tempDirLocation, err := os.MkdirTemp("", "") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + _ = os.RemoveAll(tempDirLocation) + }) - // wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + // create docker provider + err = f.DevPodProviderAdd(ctx, filepath.Join(tempDir, "provider.yaml"), "-o", "LOCATION="+tempDirLocation) + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + err = f.DevPodProviderDelete(context.Background(), "docker123") + framework.ExpectNoError(err) + }) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) - // check if ssh works as it should start the container - out, err := f.DevPodSSH(ctx, tempDir, fmt.Sprintf("cat /workspaces/%s/test.txt", workspace.ID)) - framework.ExpectNoError(err) - framework.ExpectEqual(out, "Test123", "workspace content does not match") + // expect workspace + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - // delete workspace - err = f.DevPodWorkspaceDelete(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) + // check status + status, err := f.DevPodStatus(ctx, tempDir) + framework.ExpectNoError(err) + framework.ExpectEqual(strings.ToUpper(status.State), "RUNNING", "workspace status did not match") - ginkgo.It("test devpod inactivity timeout", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") + // stop container + err = f.DevPodStop(ctx, tempDir) + framework.ExpectNoError(err) - // copy test dir - tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/machineprovider/testdata/machineprovider2") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - err = os.RemoveAll(tempDir) + // check status + status, err = f.DevPodStatus(ctx, tempDir) framework.ExpectNoError(err) - }) + framework.ExpectEqual(strings.ToUpper(status.State), "STOPPED", "workspace status did not match") - tempDirLocation, err := os.MkdirTemp("", "") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - err = os.RemoveAll(tempDirLocation) + // wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) framework.ExpectNoError(err) - }) - // create provider - _ = f.DevPodProviderDelete(ctx, "docker123") - err = f.DevPodProviderAdd(ctx, filepath.Join(tempDir, "provider.yaml")) - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - err = f.DevPodProviderDelete(context.Background(), "docker123") + // check if ssh works as it should start the container + out, err := f.DevPodSSH(ctx, tempDir, fmt.Sprintf("cat /workspaces/%s/test.txt", workspace.ID)) framework.ExpectNoError(err) - }) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + framework.ExpectEqual(out, "Test123", "workspace content does not match") - // wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug", "--daemon-interval=3s") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { // delete workspace - err = f.DevPodWorkspaceDelete(context.Background(), tempDir) + err = f.DevPodWorkspaceDelete(ctx, tempDir) framework.ExpectNoError(err) - }) + }, ginkgo.SpecTimeout(60*time.Second)) - // check status - status, err := f.DevPodStatus(ctx, tempDir, "--container-status=false") - framework.ExpectNoError(err) - framework.ExpectEqual(strings.ToUpper(status.State), "RUNNING", "workspace status did not match") + ginkgo.It("test devpod inactivity timeout", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") - // stop container - err = f.DevPodStop(ctx, tempDir) - framework.ExpectNoError(err) + // copy test dir + tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/machineprovider/testdata/machineprovider2") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + err = os.RemoveAll(tempDir) + framework.ExpectNoError(err) + }) - // check status - status, err = f.DevPodStatus(ctx, tempDir, "--container-status=false") - framework.ExpectNoError(err) - framework.ExpectEqual(strings.ToUpper(status.State), "STOPPED", "workspace status did not match") + tempDirLocation, err := os.MkdirTemp("", "") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + err = os.RemoveAll(tempDirLocation) + framework.ExpectNoError(err) + }) + + // create provider + _ = f.DevPodProviderDelete(ctx, "docker123") + err = f.DevPodProviderAdd(ctx, filepath.Join(tempDir, "provider.yaml")) + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + err = f.DevPodProviderDelete(context.Background(), "docker123") + framework.ExpectNoError(err) + }) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug", "--daemon-interval=3s") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + // delete workspace + err = f.DevPodWorkspaceDelete(context.Background(), tempDir) + framework.ExpectNoError(err) + }) - // wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--daemon-interval=3s") - framework.ExpectNoError(err) + // check status + status, err := f.DevPodStatus(ctx, tempDir, "--container-status=false") + framework.ExpectNoError(err) + framework.ExpectEqual(strings.ToUpper(status.State), "RUNNING", "workspace status did not match") - // check status - status, err = f.DevPodStatus(ctx, tempDir, "--container-status=false") - framework.ExpectNoError(err) - framework.ExpectEqual(strings.ToUpper(status.State), "RUNNING", "workspace status did not match") + // stop container + err = f.DevPodStop(ctx, tempDir) + framework.ExpectNoError(err) - // wait until workspace is stopped again - now := time.Now() - for { - status, err := f.DevPodStatus(ctx, tempDir, "--container-status=false") + // check status + status, err = f.DevPodStatus(ctx, tempDir, "--container-status=false") + framework.ExpectNoError(err) + framework.ExpectEqual(strings.ToUpper(status.State), "STOPPED", "workspace status did not match") + + // wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--daemon-interval=3s") framework.ExpectNoError(err) - framework.ExpectEqual(time.Since(now) < time.Minute*2, true, "machine did not shutdown in time") - if status.State == "Stopped" { - break - } - time.Sleep(time.Second * 2) - } - }, ginkgo.SpecTimeout(300*time.Second)) + // check status + status, err = f.DevPodStatus(ctx, tempDir, "--container-status=false") + framework.ExpectNoError(err) + framework.ExpectEqual(strings.ToUpper(status.State), "RUNNING", "workspace status did not match") + + // wait until workspace is stopped again + now := time.Now() + for { + status, err := f.DevPodStatus(ctx, tempDir, "--container-status=false") + framework.ExpectNoError(err) + framework.ExpectEqual(time.Since(now) < time.Minute*2, true, "machine did not shutdown in time") + if status.State == "Stopped" { + break + } + + time.Sleep(time.Second * 2) + } + }, ginkgo.SpecTimeout(300*time.Second)) + }) }) diff --git a/e2e/tests/provider/provider.go b/e2e/tests/provider/provider.go index b58dd6e0d..9f05e72d9 100644 --- a/e2e/tests/provider/provider.go +++ b/e2e/tests/provider/provider.go @@ -10,81 +10,83 @@ import ( ) var _ = DevPodDescribe("devpod provider test suite", func() { - ctx := context.Background() - initialDir, err := os.Getwd() - if err != nil { - panic(err) - } - - ginkgo.It("should add simple provider and delete it", func() { - tempDir, err := framework.CopyToTempDir("tests/provider/testdata/simple-k8s-provider") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - // Ensure that provider 1 is deleted - err = f.DevPodProviderDelete(ctx, "provider1", "--ignore-not-found") - framework.ExpectNoError(err) - - // Add provider 1 - err = f.DevPodProviderAdd(ctx, tempDir+"/provider1.yaml") - framework.ExpectNoError(err) - - // Ensure provider 1 exists but not provider X - err = f.DevPodProviderUse(context.Background(), "provider1") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "providerX") - framework.ExpectError(err) - - // Cleanup: delete provider 1 - err = f.DevPodProviderDelete(ctx, "provider1") - framework.ExpectNoError(err) - - // Cleanup: ensure provider 1 is deleted - err = f.DevPodProviderUse(context.Background(), "provider1") - framework.ExpectError(err) - }) - - ginkgo.It("should add simple provider and update it", func() { - tempDir, err := framework.CopyToTempDir("tests/provider/testdata/simple-k8s-provider") - framework.ExpectNoError(err) - defer framework.CleanupTempDir(initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - // Ensure that provider 2 is deleted - err = f.DevPodProviderDelete(ctx, "provider2", "--ignore-not-found") - framework.ExpectNoError(err) - - // Add provider 2 and use it - err = f.DevPodProviderAdd(ctx, tempDir+"/provider2.yaml") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "provider2") - framework.ExpectNoError(err) - - // Ensure provider 2 namespace parameter has the default value - ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second)) - err = f.DevPodProviderOptionsCheckNamespaceDescription(ctx, "provider2", "The namespace to use") - framework.ExpectNoError(err) - cancel() - - // Update provider 2 (change the namespace description value) - err = f.DevPodProviderUpdate(context.Background(), "provider2", tempDir+"/provider2-update.yaml") - framework.ExpectNoError(err) - - // Ensure that provider 2 was updated - ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(30*time.Second)) - err = f.DevPodProviderOptionsCheckNamespaceDescription(ctx, "provider2", "Updated namespace parameter") - framework.ExpectNoError(err) - cancel() - - // Cleanup: delete provider 2 - err = f.DevPodProviderDelete(context.Background(), "provider2") - framework.ExpectNoError(err) - - // Cleanup: ensure provider 2 is deleted - err = f.DevPodProviderUse(context.Background(), "provider2") - framework.ExpectError(err) + ginkgo.Context("testing non-machine providers", ginkgo.Label("provider"), ginkgo.Ordered, func() { + ctx := context.Background() + initialDir, err := os.Getwd() + if err != nil { + panic(err) + } + + ginkgo.It("should add simple provider and delete it", func() { + tempDir, err := framework.CopyToTempDir("tests/provider/testdata/simple-k8s-provider") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + // Ensure that provider 1 is deleted + err = f.DevPodProviderDelete(ctx, "provider1", "--ignore-not-found") + framework.ExpectNoError(err) + + // Add provider 1 + err = f.DevPodProviderAdd(ctx, tempDir+"/provider1.yaml") + framework.ExpectNoError(err) + + // Ensure provider 1 exists but not provider X + err = f.DevPodProviderUse(context.Background(), "provider1") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "providerX") + framework.ExpectError(err) + + // Cleanup: delete provider 1 + err = f.DevPodProviderDelete(ctx, "provider1") + framework.ExpectNoError(err) + + // Cleanup: ensure provider 1 is deleted + err = f.DevPodProviderUse(context.Background(), "provider1") + framework.ExpectError(err) + }) + + ginkgo.It("should add simple provider and update it", func() { + tempDir, err := framework.CopyToTempDir("tests/provider/testdata/simple-k8s-provider") + framework.ExpectNoError(err) + defer framework.CleanupTempDir(initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + // Ensure that provider 2 is deleted + err = f.DevPodProviderDelete(ctx, "provider2", "--ignore-not-found") + framework.ExpectNoError(err) + + // Add provider 2 and use it + err = f.DevPodProviderAdd(ctx, tempDir+"/provider2.yaml") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "provider2") + framework.ExpectNoError(err) + + // Ensure provider 2 namespace parameter has the default value + ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second)) + err = f.DevPodProviderOptionsCheckNamespaceDescription(ctx, "provider2", "The namespace to use") + framework.ExpectNoError(err) + cancel() + + // Update provider 2 (change the namespace description value) + err = f.DevPodProviderUpdate(context.Background(), "provider2", tempDir+"/provider2-update.yaml") + framework.ExpectNoError(err) + + // Ensure that provider 2 was updated + ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(30*time.Second)) + err = f.DevPodProviderOptionsCheckNamespaceDescription(ctx, "provider2", "Updated namespace parameter") + framework.ExpectNoError(err) + cancel() + + // Cleanup: delete provider 2 + err = f.DevPodProviderDelete(context.Background(), "provider2") + framework.ExpectNoError(err) + + // Cleanup: ensure provider 2 is deleted + err = f.DevPodProviderUse(context.Background(), "provider2") + framework.ExpectError(err) + }) }) }) diff --git a/e2e/tests/proxyprovider/proxyprovider.go b/e2e/tests/proxyprovider/proxyprovider.go index c469fa0ec..25db3ec33 100644 --- a/e2e/tests/proxyprovider/proxyprovider.go +++ b/e2e/tests/proxyprovider/proxyprovider.go @@ -14,200 +14,202 @@ import ( ) var _ = DevPodDescribe("devpod proxy provider test suite", func() { - ctx := context.Background() - var initialDir string - var devPodDir string - - ginkgo.BeforeEach(func() { - var err error - initialDir, err = os.Getwd() - framework.ExpectNoError(err) - - devPodDir, err = framework.CopyToTempDir("tests/proxyprovider/testdata/proxyprovider") - framework.ExpectNoError(err) - - // add & remove provider - f := framework.NewDefaultFramework(initialDir + "/bin") - err = f.DevPodProviderAdd(ctx, "./proxy-provider.yaml", "-o", "LOCATION="+devPodDir) - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "proxy-provider") - framework.ExpectNoError(err) - }) - - ginkgo.AfterEach(func() { - // run after each - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderDelete(ctx, "proxy-provider") - - // remove temp dir - framework.CleanupTempDir(initialDir, devPodDir) - }) - - ginkgo.It("create workspace via proxy provider", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") - - // copy test dir - tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/proxyprovider/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - _ = os.RemoveAll(tempDir) + ginkgo.Context("testing proxy providers", ginkgo.Label("proxyprovider"), ginkgo.Ordered, func() { + ctx := context.Background() + var initialDir string + var devPodDir string + + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) + + devPodDir, err = framework.CopyToTempDir("tests/proxyprovider/testdata/proxyprovider") + framework.ExpectNoError(err) + + // add & remove provider + f := framework.NewDefaultFramework(initialDir + "/bin") + err = f.DevPodProviderAdd(ctx, "./proxy-provider.yaml", "-o", "LOCATION="+devPodDir) + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "proxy-provider") + framework.ExpectNoError(err) }) - // create docker provider - err = f.DevPodProviderAdd(ctx, filepath.Join(tempDir, "custom-docker-provider.yaml"), "--devpod-home", devPodDir) - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - - // expect secret to not be there - fileBytes, err := os.ReadFile(filepath.Join(devPodDir, "agent", "contexts", "default", "workspaces", filepath.Base(tempDir), "workspace.json")) - framework.ExpectNoError(err) - framework.ExpectEqual(strings.Contains(string(fileBytes), "my-secret-value"), false, "workspace.json shouldn't contain provider secret") - - // expect workspace - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - // check if ssh works as it should start the container - out, err := f.DevPodSSH(ctx, tempDir, fmt.Sprintf("cat /workspaces/%s/test.txt", workspace.ID)) - framework.ExpectNoError(err) - framework.ExpectEqual(out, "Test123", "workspace content does not match") + ginkgo.AfterEach(func() { + // run after each + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderDelete(ctx, "proxy-provider") - // delete workspace - err = f.DevPodWorkspaceDelete(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(120*time.Second)) - - ginkgo.It("create & stop workspace via proxy provider", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") - - // copy test dir - tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/proxyprovider/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - _ = os.RemoveAll(tempDir) + // remove temp dir + framework.CleanupTempDir(initialDir, devPodDir) }) - // create docker provider - err = f.DevPodProviderAdd(ctx, "docker", "--devpod-home", devPodDir) - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - - // expect workspace - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - // check if ssh works - err = f.DevPodSSHEchoTestString(ctx, tempDir) - framework.ExpectNoError(err) - - // check if stop works - err = f.DevPodStop(ctx, tempDir) - framework.ExpectNoError(err) - - // check if status is stopped - status, err := f.DevPodStatus(ctx, tempDir) - framework.ExpectNoError(err) - framework.ExpectEqual(status.State, client.StatusStopped, "state does not match") - - // check if ssh works as it should start the container - out, err := f.DevPodSSH(ctx, tempDir, fmt.Sprintf("cat /workspaces/%s/test.txt", workspace.ID)) - framework.ExpectNoError(err) - framework.ExpectEqual(out, "Test123", "workspace content does not match") - - // delete workspace - err = f.DevPodWorkspaceDelete(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(120*time.Second)) + ginkgo.It("create workspace via proxy provider", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + + // copy test dir + tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/proxyprovider/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + _ = os.RemoveAll(tempDir) + }) - ginkgo.It("recreate workspace", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") + // create docker provider + err = f.DevPodProviderAdd(ctx, filepath.Join(tempDir, "custom-docker-provider.yaml"), "--devpod-home", devPodDir) + framework.ExpectNoError(err) - // copy test dir - tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/proxyprovider/testdata/docker-recreate") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - _ = os.RemoveAll(tempDir) - }) - - // create docker provider - err = f.DevPodProviderAdd(ctx, "docker", "--devpod-home", devPodDir) - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - - // expect workspace - _, err = f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - // check if ssh works - err = f.DevPodSSHEchoTestString(ctx, tempDir) - framework.ExpectNoError(err) - - // delete & move .devcontainer.json - err = os.Remove(filepath.Join(tempDir, ".devcontainer.json")) - framework.ExpectNoError(err) - err = os.Rename(filepath.Join(tempDir, ".devcontainer.json2"), filepath.Join(tempDir, ".devcontainer.json")) - framework.ExpectNoError(err) - - // check if recreate works - err = f.DevPodUp(ctx, tempDir, "--recreate") - framework.ExpectNoError(err) - - // check if ssh works as it should start the container - out, err := f.DevPodSSH(ctx, tempDir, "echo -n $DEVPOD_WORKSPACE_CHANGED") - framework.ExpectNoError(err) - framework.ExpectEqual(out, "TRUE", "workspace has not changed") - - // delete workspace - err = f.DevPodWorkspaceDelete(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(120*time.Second)) - - ginkgo.It("devcontainer path workspace", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") - - // copy test dir - tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/proxyprovider/testdata/docker-recreate") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - _ = os.RemoveAll(tempDir) - }) - - // create docker provider - err = f.DevPodProviderAdd(ctx, "docker", "--devpod-home", devPodDir) - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug", "--devcontainer-path", ".devcontainer.json2") - framework.ExpectNoError(err) - - // expect workspace - _, err = f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - // check if ssh works as it should start the container - out, err := f.DevPodSSH(ctx, tempDir, "echo -n $DEVPOD_WORKSPACE_CHANGED") - framework.ExpectNoError(err) - framework.ExpectEqual(out, "TRUE", "devcontainer path wasn't applied") - - // delete workspace - err = f.DevPodWorkspaceDelete(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(120*time.Second)) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + + // expect secret to not be there + fileBytes, err := os.ReadFile(filepath.Join(devPodDir, "agent", "contexts", "default", "workspaces", filepath.Base(tempDir), "workspace.json")) + framework.ExpectNoError(err) + framework.ExpectEqual(strings.Contains(string(fileBytes), "my-secret-value"), false, "workspace.json shouldn't contain provider secret") + + // expect workspace + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + // check if ssh works as it should start the container + out, err := f.DevPodSSH(ctx, tempDir, fmt.Sprintf("cat /workspaces/%s/test.txt", workspace.ID)) + framework.ExpectNoError(err) + framework.ExpectEqual(out, "Test123", "workspace content does not match") + + // delete workspace + err = f.DevPodWorkspaceDelete(ctx, tempDir) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(120*time.Second)) + + ginkgo.It("create & stop workspace via proxy provider", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + + // copy test dir + tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/proxyprovider/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + _ = os.RemoveAll(tempDir) + }) + + // create docker provider + err = f.DevPodProviderAdd(ctx, "docker", "--devpod-home", devPodDir) + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + + // expect workspace + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + // check if ssh works + err = f.DevPodSSHEchoTestString(ctx, tempDir) + framework.ExpectNoError(err) + + // check if stop works + err = f.DevPodStop(ctx, tempDir) + framework.ExpectNoError(err) + + // check if status is stopped + status, err := f.DevPodStatus(ctx, tempDir) + framework.ExpectNoError(err) + framework.ExpectEqual(status.State, client.StatusStopped, "state does not match") + + // check if ssh works as it should start the container + out, err := f.DevPodSSH(ctx, tempDir, fmt.Sprintf("cat /workspaces/%s/test.txt", workspace.ID)) + framework.ExpectNoError(err) + framework.ExpectEqual(out, "Test123", "workspace content does not match") + + // delete workspace + err = f.DevPodWorkspaceDelete(ctx, tempDir) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(120*time.Second)) + + ginkgo.It("recreate workspace", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + + // copy test dir + tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/proxyprovider/testdata/docker-recreate") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + _ = os.RemoveAll(tempDir) + }) + + // create docker provider + err = f.DevPodProviderAdd(ctx, "docker", "--devpod-home", devPodDir) + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + + // expect workspace + _, err = f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + // check if ssh works + err = f.DevPodSSHEchoTestString(ctx, tempDir) + framework.ExpectNoError(err) + + // delete & move .devcontainer.json + err = os.Remove(filepath.Join(tempDir, ".devcontainer.json")) + framework.ExpectNoError(err) + err = os.Rename(filepath.Join(tempDir, ".devcontainer.json2"), filepath.Join(tempDir, ".devcontainer.json")) + framework.ExpectNoError(err) + + // check if recreate works + err = f.DevPodUp(ctx, tempDir, "--recreate") + framework.ExpectNoError(err) + + // check if ssh works as it should start the container + out, err := f.DevPodSSH(ctx, tempDir, "echo -n $DEVPOD_WORKSPACE_CHANGED") + framework.ExpectNoError(err) + framework.ExpectEqual(out, "TRUE", "workspace has not changed") + + // delete workspace + err = f.DevPodWorkspaceDelete(ctx, tempDir) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(120*time.Second)) + + ginkgo.It("devcontainer path workspace", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") + + // copy test dir + tempDir, err := framework.CopyToTempDirWithoutChdir(initialDir + "/tests/proxyprovider/testdata/docker-recreate") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + _ = os.RemoveAll(tempDir) + }) + + // create docker provider + err = f.DevPodProviderAdd(ctx, "docker", "--devpod-home", devPodDir) + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug", "--devcontainer-path", ".devcontainer.json2") + framework.ExpectNoError(err) + + // expect workspace + _, err = f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + // check if ssh works as it should start the container + out, err := f.DevPodSSH(ctx, tempDir, "echo -n $DEVPOD_WORKSPACE_CHANGED") + framework.ExpectNoError(err) + framework.ExpectEqual(out, "TRUE", "devcontainer path wasn't applied") + + // delete workspace + err = f.DevPodWorkspaceDelete(ctx, tempDir) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(120*time.Second)) + }) }) diff --git a/e2e/tests/ssh/ssh.go b/e2e/tests/ssh/ssh.go index 89c892187..10b1b71fc 100644 --- a/e2e/tests/ssh/ssh.go +++ b/e2e/tests/ssh/ssh.go @@ -16,68 +16,70 @@ import ( ) var _ = DevPodDescribe("devpod ssh test suite", func() { - ctx := context.Background() - initialDir, err := os.Getwd() - if err != nil { - panic(err) - } - - ginkgo.It("should start a new workspace with a docker provider (default) and ssh into it", func() { - tempDir, err := framework.CopyToTempDir("tests/ssh/testdata/local-test") - framework.ExpectNoError(err) - defer framework.CleanupTempDir(initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) + ginkgo.Context("testing ssh command", ginkgo.Label("ssh"), ginkgo.Ordered, func() { + ctx := context.Background() + initialDir, err := os.Getwd() + if err != nil { + panic(err) + } - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ginkgo.It("should start a new workspace with a docker provider (default) and ssh into it", func() { + tempDir, err := framework.CopyToTempDir("tests/ssh/testdata/local-test") + framework.ExpectNoError(err) + defer framework.CleanupTempDir(initialDir, tempDir) - // Start up devpod workspace - devpodUpDeadline := time.Now().Add(5 * time.Minute) - devpodUpCtx, cancel := context.WithDeadline(context.Background(), devpodUpDeadline) - defer cancel() - err = f.DevPodUp(devpodUpCtx, tempDir) - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) - devpodSSHDeadline := time.Now().Add(20 * time.Second) - devpodSSHCtx, cancelSSH := context.WithDeadline(context.Background(), devpodSSHDeadline) - defer cancelSSH() - err = f.DevPodSSHEchoTestString(devpodSSHCtx, tempDir) - framework.ExpectNoError(err) - }) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.It("should start a new workspace with a docker provider (default) and forward gpg agent into it", func() { - tempDir, err := framework.CopyToTempDir("tests/ssh/testdata/gpg-forwarding") - framework.ExpectNoError(err) - defer framework.CleanupTempDir(initialDir, tempDir) + // Start up devpod workspace + devpodUpDeadline := time.Now().Add(5 * time.Minute) + devpodUpCtx, cancel := context.WithDeadline(context.Background(), devpodUpDeadline) + defer cancel() + err = f.DevPodUp(devpodUpCtx, tempDir) + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) + devpodSSHDeadline := time.Now().Add(20 * time.Second) + devpodSSHCtx, cancelSSH := context.WithDeadline(context.Background(), devpodSSHDeadline) + defer cancelSSH() + err = f.DevPodSSHEchoTestString(devpodSSHCtx, tempDir) + framework.ExpectNoError(err) + }) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ginkgo.It("should start a new workspace with a docker provider (default) and forward gpg agent into it", func() { + tempDir, err := framework.CopyToTempDir("tests/ssh/testdata/gpg-forwarding") + framework.ExpectNoError(err) + defer framework.CleanupTempDir(initialDir, tempDir) - out, err := exec.Command("gpg", "-k").Output() - if err != nil || len(out) == 0 { - err = f.SetupGPG(tempDir) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(context.Background(), "docker") framework.ExpectNoError(err) - } - // Start up devpod workspace - devpodUpDeadline := time.Now().Add(5 * time.Minute) - devpodUpCtx, cancel := context.WithDeadline(context.Background(), devpodUpDeadline) - defer cancel() - err = f.DevPodUp(devpodUpCtx, tempDir, "--gpg-agent-forwarding") - framework.ExpectNoError(err) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - devpodSSHDeadline := time.Now().Add(20 * time.Second) - devpodSSHCtx, cancelSSH := context.WithDeadline(context.Background(), devpodSSHDeadline) - defer cancelSSH() - err = f.DevPodSSHGpgTestKey(devpodSSHCtx, tempDir) - framework.ExpectNoError(err) + out, err := exec.Command("gpg", "-k").Output() + if err != nil || len(out) == 0 { + err = f.SetupGPG(tempDir) + framework.ExpectNoError(err) + } + + // Start up devpod workspace + devpodUpDeadline := time.Now().Add(5 * time.Minute) + devpodUpCtx, cancel := context.WithDeadline(context.Background(), devpodUpDeadline) + defer cancel() + err = f.DevPodUp(devpodUpCtx, tempDir, "--gpg-agent-forwarding") + framework.ExpectNoError(err) + + devpodSSHDeadline := time.Now().Add(20 * time.Second) + devpodSSHCtx, cancelSSH := context.WithDeadline(context.Background(), devpodSSHDeadline) + defer cancelSSH() + err = f.DevPodSSHGpgTestKey(devpodSSHCtx, tempDir) + framework.ExpectNoError(err) + }) }) ginkgo.It("should start a new workspace with a docker provider (default) and forward a port into it", func() { diff --git a/e2e/tests/up/up.go b/e2e/tests/up/up.go index ce7d0207f..b38019a5e 100644 --- a/e2e/tests/up/up.go +++ b/e2e/tests/up/up.go @@ -25,614 +25,360 @@ import ( ) var _ = DevPodDescribe("devpod up test suite", func() { - var dockerHelper *docker.DockerHelper - var composeHelper *compose.ComposeHelper - var initialDir string - - ginkgo.BeforeEach(func() { - var err error - initialDir, err = os.Getwd() - framework.ExpectNoError(err) - - dockerHelper = &docker.DockerHelper{DockerCommand: "docker"} - composeHelper, err = compose.NewComposeHelper("", dockerHelper) - framework.ExpectNoError(err) - }) + ginkgo.Context("testing up command", ginkgo.Label("up"), ginkgo.Ordered, func() { + var dockerHelper *docker.DockerHelper + var composeHelper *compose.ComposeHelper + var initialDir string + + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) - ginkgo.It("with env vars", func() { - ctx := context.Background() - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err := f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - name := "vscode-remote-try-python" - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), name) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, "github.com/microsoft/vscode-remote-try-python") - framework.ExpectNoError(err) - - // check env var - out, err := f.DevPodSSH(ctx, name, "echo -n $TEST_VAR") - framework.ExpectNoError(err) - framework.ExpectEqual(out, "", "should be empty") - - // set env var - value := "test-variable" - err = f.DevPodUp(ctx, name, "--workspace-env", "TEST_VAR="+value) - framework.ExpectNoError(err) - - // check env var - out, err = f.DevPodSSH(ctx, name, "echo -n $TEST_VAR") - framework.ExpectNoError(err) - framework.ExpectEqual(out, value, "should be set now") - - // check env var again - err = f.DevPodUp(ctx, name) - framework.ExpectNoError(err) - - // check env var - out, err = f.DevPodSSH(ctx, name, "echo -n $TEST_VAR") - framework.ExpectNoError(err) - framework.ExpectEqual(out, value, "should still be set") - - // delete env var - err = f.DevPodUp(ctx, name, "--workspace-env", "TEST_VAR=") - framework.ExpectNoError(err) - - // check env var - out, err = f.DevPodSSH(ctx, name, "echo -n $TEST_VAR") - framework.ExpectNoError(err) - framework.ExpectEqual(out, "", "should be empty") - }) + dockerHelper = &docker.DockerHelper{DockerCommand: "docker"} + composeHelper, err = compose.NewComposeHelper("", dockerHelper) + framework.ExpectNoError(err) + }) - ginkgo.It("should allow checkout of a GitRepo from a commit hash", func() { - ctx := context.Background() - f := framework.NewDefaultFramework(initialDir + "/bin") + ginkgo.It("with env vars", func() { + ctx := context.Background() + f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderDelete(ctx, "docker") - err := f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + _ = f.DevPodProviderDelete(ctx, "docker") + err := f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - name := "vscode-remote-try-python-sha256-0c1547c" - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), name) + name := "vscode-remote-try-python" + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), name) - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, "github.com/microsoft/vscode-remote-try-python@sha256:0c1547c") - framework.ExpectNoError(err) - }) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, "github.com/microsoft/vscode-remote-try-python") + framework.ExpectNoError(err) - ginkgo.It("should allow checkout of a GitRepo from a pull request reference", func() { - ctx := context.Background() - f := framework.NewDefaultFramework(initialDir + "/bin") + // check env var + out, err := f.DevPodSSH(ctx, name, "echo -n $TEST_VAR") + framework.ExpectNoError(err) + framework.ExpectEqual(out, "", "should be empty") - _ = f.DevPodProviderDelete(ctx, "docker") - err := f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + // set env var + value := "test-variable" + err = f.DevPodUp(ctx, name, "--workspace-env", "TEST_VAR="+value) + framework.ExpectNoError(err) - name := "devpod" - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), name) + // check env var + out, err = f.DevPodSSH(ctx, name, "echo -n $TEST_VAR") + framework.ExpectNoError(err) + framework.ExpectEqual(out, value, "should be set now") - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, "github.com/loft-sh/devpod@pull/3/head") - framework.ExpectNoError(err) - }) + // check env var again + err = f.DevPodUp(ctx, name) + framework.ExpectNoError(err) - ginkgo.It("run devpod in Kubernetes", func() { - ctx := context.Background() - f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/up/testdata/kubernetes") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - _ = f.DevPodProviderDelete(ctx, "kubernetes") - err = f.DevPodProviderAdd(ctx, "kubernetes", "-o", "KUBERNETES_NAMESPACE=devpod") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - err = f.DevPodProviderDelete(ctx, "kubernetes") + // check env var + out, err = f.DevPodSSH(ctx, name, "echo -n $TEST_VAR") framework.ExpectNoError(err) - }) + framework.ExpectEqual(out, value, "should still be set") - // run up - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - // check pod is there - cmd := exec.Command("kubectl", "get", "pods", "-l", "devpod.sh/created=true", "-o", "json", "-n", "devpod") - stdout, err := cmd.Output() - framework.ExpectNoError(err) - - // check if pod is there - list := &framework.PodList{} - err = json.Unmarshal(stdout, list) - framework.ExpectNoError(err) - framework.ExpectEqual(len(list.Items), 1, "Expect 1 pod") - framework.ExpectEqual(len(list.Items[0].Spec.Containers), 1, "Expect 1 container") - framework.ExpectEqual(list.Items[0].Spec.Containers[0].Image, "mcr.microsoft.com/devcontainers/go:0-1.19-bullseye", "Expect container image") - - // check if ssh works - err = f.DevPodSSHEchoTestString(ctx, tempDir) - framework.ExpectNoError(err) - - // stop workspace - err = f.DevPodWorkspaceStop(ctx, tempDir) - framework.ExpectNoError(err) - - // check pod is there - cmd = exec.Command("kubectl", "get", "pods", "-l", "devpod.sh/created=true", "-o", "json", "-n", "devpod") - stdout, err = cmd.Output() - framework.ExpectNoError(err) - - // check if pod is there - list = &framework.PodList{} - err = json.Unmarshal(stdout, list) - framework.ExpectNoError(err) - framework.ExpectEqual(len(list.Items), 0, "Expect no pods") - - // run up - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - // check pod is there - cmd = exec.Command("kubectl", "get", "pods", "-l", "devpod.sh/created=true", "-o", "json", "-n", "devpod") - stdout, err = cmd.Output() - framework.ExpectNoError(err) - - // check if pod is there - list = &framework.PodList{} - err = json.Unmarshal(stdout, list) - framework.ExpectNoError(err) - framework.ExpectEqual(len(list.Items), 1, "Expect 1 pod") - - // check if ssh works - err = f.DevPodSSHEchoTestString(ctx, tempDir) - framework.ExpectNoError(err) - - // delete workspace - err = f.DevPodWorkspaceDelete(ctx, tempDir) - framework.ExpectNoError(err) - }) + // delete env var + err = f.DevPodUp(ctx, name, "--workspace-env", "TEST_VAR=") + framework.ExpectNoError(err) - ginkgo.It("create workspace without devcontainer.json", func() { - const providerName = "test-docker" - ctx := context.Background() - - f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/up/testdata/no-devcontainer") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - // provider add, use and delete afterwards - err = f.DevPodProviderAdd(ctx, "docker", "--name", providerName) - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, providerName) - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { - err = f.DevPodProviderDelete(ctx, providerName) + // check env var + out, err = f.DevPodSSH(ctx, name, "echo -n $TEST_VAR") framework.ExpectNoError(err) + framework.ExpectEqual(out, "", "should be empty") }) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + ginkgo.It("should allow checkout of a GitRepo from a commit hash", func() { + ctx := context.Background() + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err := f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) + name := "vscode-remote-try-python-sha256-0c1547c" + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), name) - projectName := workspace.ID - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, "github.com/microsoft/vscode-remote-try-python@sha256:0c1547c") + framework.ExpectNoError(err) }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - devcontainerPath := filepath.Join("/workspaces", projectName, ".devcontainer.json") + ginkgo.It("should allow checkout of a GitRepo from a pull request reference", func() { + ctx := context.Background() + f := framework.NewDefaultFramework(initialDir + "/bin") - containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat " + devcontainerPath, projectName}) - framework.ExpectNoError(err) - expectedImageName := language.MapConfig[language.Go].ImageContainer.Image + _ = f.DevPodProviderDelete(ctx, "docker") + err := f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - gomega.Expect(containerEnvPath).To(gomega.Equal(fmt.Sprintf("{\"image\":\"%s\"}", expectedImageName))) + name := "devpod" + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), name) - err = f.DevPodWorkspaceDelete(ctx, tempDir) - framework.ExpectNoError(err) - }) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, "github.com/loft-sh/devpod@pull/3/head") + framework.ExpectNoError(err) + }) - ginkgo.Context("print error message correctly", func() { - ginkgo.It("make sure devpod output is correct and log-output works correctly", func(ctx context.Context) { + ginkgo.It("run devpod in Kubernetes", func() { + ctx := context.Background() f := framework.NewDefaultFramework(initialDir + "/bin") - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + tempDir, err := framework.CopyToTempDir("tests/up/testdata/kubernetes") framework.ExpectNoError(err) ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - err = f.DevPodProviderAdd(ctx, "docker", "--name", "test-docker") + _ = f.DevPodProviderDelete(ctx, "kubernetes") + err = f.DevPodProviderAdd(ctx, "kubernetes", "-o", "KUBERNETES_NAMESPACE=devpod") framework.ExpectNoError(err) ginkgo.DeferCleanup(func() { - err = f.DevPodProviderDelete(context.Background(), "test-docker") + err = f.DevPodProviderDelete(ctx, "kubernetes") framework.ExpectNoError(err) }) - err = f.DevPodProviderUse(ctx, "test-docker", "-o", "DOCKER_PATH=abc", "--skip-init") + // run up + err = f.DevPodUp(ctx, tempDir) framework.ExpectNoError(err) - // Wait for devpod workspace to come online - stdout, stderr, err := f.DevPodUpStreams(ctx, tempDir, "--log-output=json") - deleteErr := f.DevPodWorkspaceDelete(ctx, tempDir, "--force") - framework.ExpectNoError(deleteErr) - framework.ExpectError(err, "expected error") - framework.ExpectNoError(verifyLogStream(strings.NewReader(stdout))) - framework.ExpectNoError(verifyLogStream(strings.NewReader(stderr))) - framework.ExpectNoError(findMessage(strings.NewReader(stdout), "exec: \"abc\": executable file not found in $PATH")) - }, ginkgo.SpecTimeout(60*time.Second)) - }) - - ginkgo.Context("cleanup up on failure", func() { - ginkgo.It("ensure workspace cleanup when failing to create a workspace", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err := f.DevPodProviderUse(ctx, "docker") + // check pod is there + cmd := exec.Command("kubectl", "get", "pods", "-l", "devpod.sh/created=true", "-o", "json", "-n", "devpod") + stdout, err := cmd.Output() framework.ExpectNoError(err) - initialList, err := f.DevPodList(ctx) + // check if pod is there + list := &framework.PodList{} + err = json.Unmarshal(stdout, list) framework.ExpectNoError(err) - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, "github.com/i/do-not-exist.git") - framework.ExpectError(err) + framework.ExpectEqual(len(list.Items), 1, "Expect 1 pod") + framework.ExpectEqual(len(list.Items[0].Spec.Containers), 1, "Expect 1 container") + framework.ExpectEqual(list.Items[0].Spec.Containers[0].Image, "mcr.microsoft.com/devcontainers/go:0-1.19-bullseye", "Expect container image") - out, err := f.DevPodList(ctx) - framework.ExpectNoError(err) - framework.ExpectEqual(out, initialList) - }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("ensure workspace cleanup when not a git or folder", func(ctx context.Context) { - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err := f.DevPodProviderUse(ctx, "docker") + // check if ssh works + err = f.DevPodSSHEchoTestString(ctx, tempDir) framework.ExpectNoError(err) - initialList, err := f.DevPodList(ctx) + // stop workspace + err = f.DevPodWorkspaceStop(ctx, tempDir) framework.ExpectNoError(err) - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, "test1234.com") - framework.ExpectError(err) - out, err := f.DevPodList(ctx) + // check pod is there + cmd = exec.Command("kubectl", "get", "pods", "-l", "devpod.sh/created=true", "-o", "json", "-n", "devpod") + stdout, err = cmd.Output() framework.ExpectNoError(err) - framework.ExpectEqual(out, initialList) - }, ginkgo.SpecTimeout(60*time.Second)) - }) - ginkgo.Context("using docker provider", func() { - ginkgo.Context("with rootfull podman", ginkgo.Ordered, func() { - ginkgo.It("should setup rootful podman", func(ctx context.Context) { - wrapper, err := os.Create(initialDir + "/bin/podman-rootful") - framework.ExpectNoError(err) + // check if pod is there + list = &framework.PodList{} + err = json.Unmarshal(stdout, list) + framework.ExpectNoError(err) + framework.ExpectEqual(len(list.Items), 0, "Expect no pods") - defer wrapper.Close() + // run up + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - _, err = wrapper.WriteString(`#!/bin/sh - sudo podman "$@" - `) - framework.ExpectNoError(err) + // check pod is there + cmd = exec.Command("kubectl", "get", "pods", "-l", "devpod.sh/created=true", "-o", "json", "-n", "devpod") + stdout, err = cmd.Output() + framework.ExpectNoError(err) - err = wrapper.Close() - framework.ExpectNoError(err) + // check if pod is there + list = &framework.PodList{} + err = json.Unmarshal(stdout, list) + framework.ExpectNoError(err) + framework.ExpectEqual(len(list.Items), 1, "Expect 1 pod") - cmd := exec.Command("sudo", "chmod", "+x", initialDir+"/bin/podman-rootful") - err = cmd.Run() - framework.ExpectNoError(err) + // check if ssh works + err = f.DevPodSSHEchoTestString(ctx, tempDir) + framework.ExpectNoError(err) - err = exec.Command(initialDir+"/bin/podman-rootful", "ps").Run() - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) + // delete workspace + err = f.DevPodWorkspaceDelete(ctx, tempDir) + framework.ExpectNoError(err) + }) - ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ginkgo.It("create workspace without devcontainer.json", func() { + const providerName = "test-docker" + ctx := context.Background() - f := framework.NewDefaultFramework(initialDir + "/bin") + f := framework.NewDefaultFramework(initialDir + "/bin") + tempDir, err := framework.CopyToTempDir("tests/up/testdata/no-devcontainer") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker", "-o", "DOCKER_PATH="+initialDir+"/bin/podman-rootful") + // provider add, use and delete afterwards + err = f.DevPodProviderAdd(ctx, "docker", "--name", providerName) + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, providerName) + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + err = f.DevPodProviderDelete(ctx, providerName) framework.ExpectNoError(err) + }) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - }) - ginkgo.Context("with rootless podman", ginkgo.Ordered, func() { - ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + projectName := workspace.ID + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - f := framework.NewDefaultFramework(initialDir + "/bin") + devcontainerPath := filepath.Join("/workspaces", projectName, ".devcontainer.json") - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker", "-o", "DOCKER_PATH=podman") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat " + devcontainerPath, projectName}) + framework.ExpectNoError(err) + expectedImageName := language.MapConfig[language.Go].ImageContainer.Image - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + gomega.Expect(containerEnvPath).To(gomega.Equal(fmt.Sprintf("{\"image\":\"%s\"}", expectedImageName))) - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) + err = f.DevPodWorkspaceDelete(ctx, tempDir) + framework.ExpectNoError(err) }) - ginkgo.Context("with docker", ginkgo.Ordered, func() { - ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { + ginkgo.Context("print error message correctly", func() { + ginkgo.It("make sure devpod output is correct and log-output works correctly", func(ctx context.Context) { + f := framework.NewDefaultFramework(initialDir + "/bin") tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") framework.ExpectNoError(err) ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker", "--name", "test-docker") framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { + err = f.DevPodProviderDelete(context.Background(), "test-docker") + framework.ExpectNoError(err) + }) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) + err = f.DevPodProviderUse(ctx, "test-docker", "-o", "DOCKER_PATH=abc", "--skip-init") framework.ExpectNoError(err) + + // Wait for devpod workspace to come online + stdout, stderr, err := f.DevPodUpStreams(ctx, tempDir, "--log-output=json") + deleteErr := f.DevPodWorkspaceDelete(ctx, tempDir, "--force") + framework.ExpectNoError(deleteErr) + framework.ExpectError(err, "expected error") + framework.ExpectNoError(verifyLogStream(strings.NewReader(stdout))) + framework.ExpectNoError(verifyLogStream(strings.NewReader(stderr))) + framework.ExpectNoError(findMessage(strings.NewReader(stdout), "exec: \"abc\": executable file not found in $PATH")) }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should start a new workspace and substitute devcontainer.json variables", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-variables") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + }) + ginkgo.Context("cleanup up on failure", func() { + ginkgo.It("ensure workspace cleanup when failing to create a workspace", func(ctx context.Context) { f := framework.NewDefaultFramework(initialDir + "/bin") _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - projectName := workspace.ID - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - devContainerID, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/dev-container-id.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(devContainerID).NotTo(gomega.BeEmpty()) - - containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-env-path.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerEnvPath).To(gomega.ContainSubstring("/usr/local/bin")) - - localEnvHome, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-env-home.out", projectName}) + err := f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) - gomega.Expect(localEnvHome).To(gomega.Equal(os.Getenv("HOME"))) - localWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder.out", projectName}) + initialList, err := f.DevPodList(ctx) framework.ExpectNoError(err) - gomega.Expect(localWorkspaceFolder).To(gomega.Equal(tempDir)) - - localWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder-basename.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) - - containerWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerWorkspaceFolder).To(gomega.Equal(filepath.Join("/workspaces", filepath.Base(tempDir)))) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, "github.com/i/do-not-exist.git") + framework.ExpectError(err) - containerWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder-basename.out", projectName}) + out, err := f.DevPodList(ctx) framework.ExpectNoError(err) - gomega.Expect(containerWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) + framework.ExpectEqual(out, initialList) }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with mounts", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-mounts") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - + ginkgo.It("ensure workspace cleanup when not a git or folder", func(ctx context.Context) { f := framework.NewDefaultFramework(initialDir + "/bin") _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir, "--debug") + err := f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) + initialList, err := f.DevPodList(ctx) framework.ExpectNoError(err) - projectName := workspace.ID + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, "test1234.com") + framework.ExpectError(err) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) + out, err := f.DevPodList(ctx) framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + framework.ExpectEqual(out, initialList) + }, ginkgo.SpecTimeout(60*time.Second)) + }) - foo, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt1/foo.txt", projectName}) - framework.ExpectNoError(err) - gomega.Expect(foo).To(gomega.Equal("BAR")) + ginkgo.Context("using docker provider", func() { + ginkgo.Context("with rootfull podman", ginkgo.Ordered, func() { + ginkgo.It("should setup rootful podman", func(ctx context.Context) { + wrapper, err := os.Create(initialDir + "/bin/podman-rootful") + framework.ExpectNoError(err) - bar, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt2/bar.txt", projectName}) - framework.ExpectNoError(err) - gomega.Expect(bar).To(gomega.Equal("FOO")) - }, ginkgo.SpecTimeout(60*time.Second)) + defer wrapper.Close() - ginkgo.It("should start a new workspace with multistage build", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-with-multi-stage-build") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + _, err = wrapper.WriteString(`#!/bin/sh + sudo podman "$@" + `) + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + err = wrapper.Close() + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + cmd := exec.Command("sudo", "chmod", "+x", initialDir+"/bin/podman-rootful") + err = cmd.Run() + framework.ExpectNoError(err) - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(180*time.Second)) + err = exec.Command(initialDir+"/bin/podman-rootful", "ps").Run() + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.Context("should start a new workspace with features", func() { - ginkgo.It("ensure dependencies installed via features are accessible in lifecycle hooks", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-features-lifecycle-hooks") + ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") framework.ExpectNoError(err) ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker", "-o", "DOCKER_PATH="+initialDir+"/bin/podman-rootful") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") + err = f.DevPodUp(ctx, tempDir) framework.ExpectNoError(err) }, ginkgo.SpecTimeout(60*time.Second)) }) - ginkgo.It("should start a new workspace with dotfiles - no install script", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--dotfiles", "https://github.com/loft-sh/example-dotfiles") - framework.ExpectNoError(err) - - out, err := f.DevPodSSH(ctx, tempDir, "ls ~/.file*") - framework.ExpectNoError(err) - - expectedOutput := `/home/vscode/.file1 -/home/vscode/.file2 -/home/vscode/.file3 -` - framework.ExpectEqual(out, expectedOutput, "should match") - }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should start a new workspace with dotfiles - install script", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--dotfiles", "https://github.com/loft-sh/example-dotfiles", "--dotfiles-script", "install-example") - framework.ExpectNoError(err) - - out, err := f.DevPodSSH(ctx, tempDir, "ls /tmp/worked") - framework.ExpectNoError(err) - - expectedOutput := "/tmp/worked\n" - - framework.ExpectEqual(out, expectedOutput, "should match") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with custom image", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--devcontainer-image", "mcr.microsoft.com/vscode/devcontainers/base:alpine") - framework.ExpectNoError(err) - - out, err := f.DevPodSSH(ctx, tempDir, "grep ^ID= /etc/os-release") - framework.ExpectNoError(err) - - expectedOutput := "ID=alpine\n" - unexpectedOutput := "ID=debian\n" - - framework.ExpectEqual(out, expectedOutput, "should match") - framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") - }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should start a new workspace with custom image and skip building", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-with-multi-stage-build") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ginkgo.Context("with rootless podman", ginkgo.Ordered, func() { + ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--devcontainer-image", "mcr.microsoft.com/vscode/devcontainers/base:alpine") - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") - out, err := f.DevPodSSH(ctx, tempDir, "grep ^ID= /etc/os-release") - framework.ExpectNoError(err) + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker", "-o", "DOCKER_PATH=podman") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - expectedOutput := "ID=alpine\n" - unexpectedOutput := "ID=debian\n" + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - framework.ExpectEqual(out, expectedOutput, "should match") - framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") - }, ginkgo.SpecTimeout(60*time.Second)) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + }) - ginkgo.Context("should start a workspace from a Dockerfile build", func() { - ginkgo.It("should rebuild image in case of changes in files in build context", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-dockerfile-buildcontext") + ginkgo.Context("with docker", ginkgo.Ordered, func() { + ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") framework.ExpectNoError(err) ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) @@ -641,7 +387,7 @@ var _ = DevPodDescribe("devpod up test suite", func() { _ = f.DevPodProviderDelete(ctx, "docker") err = f.DevPodProviderAdd(ctx, "docker") framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") + err = f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) @@ -649,42 +395,132 @@ var _ = DevPodDescribe("devpod up test suite", func() { // Wait for devpod workspace to come online (deadline: 30s) err = f.DevPodUp(ctx, tempDir) framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should start a new workspace and substitute devcontainer.json variables", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-variables") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) workspace, err := f.FindWorkspace(ctx, tempDir) framework.ExpectNoError(err) - container, err := dockerHelper.FindDevContainer(ctx, []string{ + projectName := workspace.ID + ids, err := dockerHelper.FindContainer(ctx, []string{ fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), }) framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - image1 := container.Config.LegacyImage - - scriptFile, err := os.OpenFile(tempDir+"/scripts/alias.sh", - os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + devContainerID, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/dev-container-id.out", projectName}) framework.ExpectNoError(err) + gomega.Expect(devContainerID).NotTo(gomega.BeEmpty()) - defer scriptFile.Close() + containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-env-path.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerEnvPath).To(gomega.ContainSubstring("/usr/local/bin")) - ginkgo.By("Changing a file within the context") - _, err = scriptFile.Write([]byte("alias yr='date +%Y'")) + localEnvHome, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-env-home.out", projectName}) framework.ExpectNoError(err) + gomega.Expect(localEnvHome).To(gomega.Equal(os.Getenv("HOME"))) - ginkgo.By("Starting DevPod again with --recreate") - err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + localWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder.out", projectName}) framework.ExpectNoError(err) + gomega.Expect(localWorkspaceFolder).To(gomega.Equal(tempDir)) - container, err = dockerHelper.FindDevContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) + localWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder-basename.out", projectName}) framework.ExpectNoError(err) + gomega.Expect(localWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) - image2 := container.Config.LegacyImage + containerWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerWorkspaceFolder).To(gomega.Equal(filepath.Join("/workspaces", filepath.Base(tempDir)))) - gomega.Expect(image2).ShouldNot(gomega.Equal(image1), "images should be different") + containerWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder-basename.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should not rebuild image for changes in files mentioned in .dockerignore", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-dockerfile-buildcontext") + + ginkgo.It("should start a new workspace with mounts", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-mounts") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + foo, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt1/foo.txt", projectName}) + framework.ExpectNoError(err) + gomega.Expect(foo).To(gomega.Equal("BAR")) + + bar, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt2/bar.txt", projectName}) + framework.ExpectNoError(err) + gomega.Expect(bar).To(gomega.Equal("FOO")) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with multistage build", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-with-multi-stage-build") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(180*time.Second)) + + ginkgo.Context("should start a new workspace with features", func() { + ginkgo.It("ensure dependencies installed via features are accessible in lifecycle hooks", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-features-lifecycle-hooks") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + }) + ginkgo.It("should start a new workspace with dotfiles - no install script", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") framework.ExpectNoError(err) ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) @@ -693,786 +529,764 @@ var _ = DevPodDescribe("devpod up test suite", func() { _ = f.DevPodProviderDelete(ctx, "docker") err = f.DevPodProviderAdd(ctx, "docker") framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") + err = f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) + err = f.DevPodUp(ctx, tempDir, "--dotfiles", "https://github.com/loft-sh/example-dotfiles") framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) + out, err := f.DevPodSSH(ctx, tempDir, "ls ~/.file*") framework.ExpectNoError(err) - container, err := dockerHelper.FindDevContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) + expectedOutput := `/home/vscode/.file1 +/home/vscode/.file2 +/home/vscode/.file3 +` + framework.ExpectEqual(out, expectedOutput, "should match") + }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should start a new workspace with dotfiles - install script", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - image1 := container.Config.LegacyImage + f := framework.NewDefaultFramework(initialDir + "/bin") - scriptFile, err := os.OpenFile(tempDir+"/scripts/install.sh", - os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) - defer scriptFile.Close() + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.By("Changing a file within context") - _, err = scriptFile.Write([]byte("apt install python")) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--dotfiles", "https://github.com/loft-sh/example-dotfiles", "--dotfiles-script", "install-example") framework.ExpectNoError(err) - ginkgo.By("Starting DevPod again with --recreate") - err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + out, err := f.DevPodSSH(ctx, tempDir, "ls /tmp/worked") framework.ExpectNoError(err) - container, err = dockerHelper.FindDevContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) + expectedOutput := "/tmp/worked\n" + + framework.ExpectEqual(out, expectedOutput, "should match") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with custom image", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - image2 := container.Config.LegacyImage + f := framework.NewDefaultFramework(initialDir + "/bin") - gomega.Expect(image2).Should(gomega.Equal(image1), "image should be same") + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--devcontainer-image", "mcr.microsoft.com/vscode/devcontainers/base:alpine") + framework.ExpectNoError(err) + + out, err := f.DevPodSSH(ctx, tempDir, "grep ^ID= /etc/os-release") + framework.ExpectNoError(err) + + expectedOutput := "ID=alpine\n" + unexpectedOutput := "ID=debian\n" + + framework.ExpectEqual(out, expectedOutput, "should match") + framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") }, ginkgo.SpecTimeout(60*time.Second)) - }) - ginkgo.It("should use http headers to download feature", func(ctx context.Context) { - server := ghttp.NewServer() + ginkgo.It("should start a new workspace with custom image and skip building", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-with-multi-stage-build") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-features-http-headers") - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") - featureArchiveFilePath := path.Join(tempDir, "devcontainer-feature-hello.tgz") - featureFiles := []string{path.Join(tempDir, "devcontainer-feature.json"), path.Join(tempDir, "install.sh")} - err = createTarGzArchive(featureArchiveFilePath, featureFiles) - framework.ExpectNoError(err) + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - devContainerFileBuf, err := os.ReadFile(path.Join(tempDir, ".devcontainer.json")) - framework.ExpectNoError(err) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - output := strings.Replace(string(devContainerFileBuf), "#{server_url}", server.URL(), -1) - err = os.WriteFile(path.Join(tempDir, ".devcontainer.json"), []byte(output), 0644) - framework.ExpectNoError(err) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--devcontainer-image", "mcr.microsoft.com/vscode/devcontainers/base:alpine") + framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ginkgo.DeferCleanup(server.Close) + out, err := f.DevPodSSH(ctx, tempDir, "grep ^ID= /etc/os-release") + framework.ExpectNoError(err) - respHeader := http.Header{} - respHeader.Set("Content-Disposition", "attachment; filename=devcontainer-feature-hello.tgz") + expectedOutput := "ID=alpine\n" + unexpectedOutput := "ID=debian\n" - featureArchiveFileBuf, err := os.ReadFile(featureArchiveFilePath) - framework.ExpectNoError(err) + framework.ExpectEqual(out, expectedOutput, "should match") + framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") + }, ginkgo.SpecTimeout(60*time.Second)) - f := framework.NewDefaultFramework(initialDir + "/bin") - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", "/devcontainer-feature-hello.tgz"), - ghttp.VerifyHeaderKV("Foo-Header", "Foo"), - ghttp.RespondWith(http.StatusOK, featureArchiveFileBuf, respHeader), - ), - ) - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + ginkgo.Context("should start a workspace from a Dockerfile build", func() { + ginkgo.It("should rebuild image in case of changes in files in build context", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-dockerfile-buildcontext") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + f := framework.NewDefaultFramework(initialDir + "/bin") - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - server.Close() - }, ginkgo.SpecTimeout(60*time.Second)) - }) + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) - ginkgo.Context("with docker-compose", func() { - ginkgo.It("should start a new workspace with root folder configuration", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + container, err := dockerHelper.FindDevContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) + image1 := container.Config.LegacyImage - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + scriptFile, err := os.OpenFile(tempDir+"/scripts/alias.sh", + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + framework.ExpectNoError(err) - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) + defer scriptFile.Close() - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") + ginkgo.By("Changing a file within the context") + _, err = scriptFile.Write([]byte("alias yr='date +%Y'")) + framework.ExpectNoError(err) - mount := containerDetail.Mounts[0] - gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) - gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) - gomega.Expect(mount.RW).To(gomega.BeTrue()) - }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.By("Starting DevPod again with --recreate") + err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + framework.ExpectNoError(err) - ginkgo.It("should start a new workspace with sub-folder configuration", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-subfolder") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + container, err = dockerHelper.FindDevContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + image2 := container.Config.LegacyImage - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + gomega.Expect(image2).ShouldNot(gomega.Equal(image1), "images should be different") + }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should not rebuild image for changes in files mentioned in .dockerignore", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-dockerfile-buildcontext") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - mount := containerDetail.Mounts[0] - gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) - gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) - gomega.Expect(mount.RW).To(gomega.BeTrue()) - }, ginkgo.SpecTimeout(60*time.Second)) + container, err := dockerHelper.FindDevContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) - ginkgo.It("should start a new workspace with multiple services", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-multiple-services") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + image1 := container.Config.LegacyImage - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + scriptFile, err := os.OpenFile(tempDir+"/scripts/install.sh", + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + defer scriptFile.Close() - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + ginkgo.By("Changing a file within context") + _, err = scriptFile.Write([]byte("apt install python")) + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := composeHelper.GetProjectName(workspace.UID) + ginkgo.By("Starting DevPod again with --recreate") + err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + framework.ExpectNoError(err) - appIDs, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(appIDs).To(gomega.HaveLen(1), "app container to be created") + container, err = dockerHelper.FindDevContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + + image2 := container.Config.LegacyImage - dbIDs, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "db"), + gomega.Expect(image2).Should(gomega.Equal(image1), "image should be same") + }, ginkgo.SpecTimeout(60*time.Second)) }) - framework.ExpectNoError(err) - gomega.Expect(dbIDs).To(gomega.HaveLen(1), "db container to be created") - }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should use http headers to download feature", func(ctx context.Context) { + server := ghttp.NewServer() - ginkgo.It("should start a new workspace with specific services", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-run-services") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-features-http-headers") + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + featureArchiveFilePath := path.Join(tempDir, "devcontainer-feature-hello.tgz") + featureFiles := []string{path.Join(tempDir, "devcontainer-feature.json"), path.Join(tempDir, "install.sh")} + err = createTarGzArchive(featureArchiveFilePath, featureFiles) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + devContainerFileBuf, err := os.ReadFile(path.Join(tempDir, ".devcontainer.json")) + framework.ExpectNoError(err) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + output := strings.Replace(string(devContainerFileBuf), "#{server_url}", server.URL(), -1) + err = os.WriteFile(path.Join(tempDir, ".devcontainer.json"), []byte(output), 0644) + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := composeHelper.GetProjectName(workspace.UID) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ginkgo.DeferCleanup(server.Close) - appIDs, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(appIDs).To(gomega.HaveLen(1), "app container to be created") + respHeader := http.Header{} + respHeader.Set("Content-Disposition", "attachment; filename=devcontainer-feature-hello.tgz") - dbIDs, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "db"), - }) - framework.ExpectNoError(err) - gomega.Expect(dbIDs).To(gomega.BeEmpty(), "db container not to be created") - }, ginkgo.SpecTimeout(60*time.Second)) + featureArchiveFileBuf, err := os.ReadFile(featureArchiveFilePath) + framework.ExpectNoError(err) - ginkgo.It("should start a new workspace with .devcontainer docker-compose overrides", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-overrides") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + f := framework.NewDefaultFramework(initialDir + "/bin") + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/devcontainer-feature-hello.tgz"), + ghttp.VerifyHeaderKV("Foo-Header", "Foo"), + ghttp.RespondWith(http.StatusOK, featureArchiveFileBuf, respHeader), + ), + ) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + server.Close() + }, ginkgo.SpecTimeout(60*time.Second)) + }) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := composeHelper.GetProjectName(workspace.UID) + ginkgo.Context("with docker-compose", func() { + ginkgo.It("should start a new workspace with root folder configuration", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - mount := containerDetail.Mounts[0] - gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) - gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) - gomega.Expect(mount.RW).To(gomega.BeTrue()) - }, ginkgo.SpecTimeout(60*time.Second)) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - ginkgo.It("should start a new workspace with container environment variables set", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-container-env") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + mount := containerDetail.Mounts[0] + gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) + gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) + gomega.Expect(mount.RW).To(gomega.BeTrue()) + }, ginkgo.SpecTimeout(60*time.Second)) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID + ginkgo.It("should start a new workspace with sub-folder configuration", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-subfolder") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - err = f.ExecCommand(ctx, true, true, "BAR", []string{"ssh", "--command", "echo $FOO", projectName}) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.It("should start a new workspace with container user", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-container-user") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + mount := containerDetail.Mounts[0] + gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) + gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) + gomega.Expect(mount.RW).To(gomega.BeTrue()) + }, ginkgo.SpecTimeout(60*time.Second)) - err = f.ExecCommand(ctx, true, true, "root", []string{"ssh", "--command", "ps u -p 1", projectName}) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should start a new workspace with multiple services", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-multiple-services") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ginkgo.It("should start a new workspace with privileged", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-privileged") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := composeHelper.GetProjectName(workspace.UID) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) + appIDs, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(appIDs).To(gomega.HaveLen(1), "app container to be created") - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + dbIDs, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "db"), + }) + framework.ExpectNoError(err) + gomega.Expect(dbIDs).To(gomega.HaveLen(1), "db container to be created") + }, ginkgo.SpecTimeout(60*time.Second)) - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) + ginkgo.It("should start a new workspace with specific services", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-run-services") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.HostConfig.Privileged).To(gomega.BeTrue(), "container run with privileged true") - }, ginkgo.SpecTimeout(60*time.Second)) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - ginkgo.It("should start a new workspace with capAdd", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-capadd") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := composeHelper.GetProjectName(workspace.UID) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + appIDs, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(appIDs).To(gomega.HaveLen(1), "app container to be created") - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) + dbIDs, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "db"), + }) + framework.ExpectNoError(err) + gomega.Expect(dbIDs).To(gomega.BeEmpty(), "db container not to be created") + }, ginkgo.SpecTimeout(60*time.Second)) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + ginkgo.It("should start a new workspace with .devcontainer docker-compose overrides", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-overrides") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.HostConfig.CapAdd).To(gomega.ContainElement("SYS_PTRACE"), "image capabilities are not duplicated") - gomega.Expect(containerDetail.HostConfig.CapAdd).To(gomega.ContainElement("NET_ADMIN"), "devcontainer configuration can add capabilities") - }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.It("should start a new workspace with securityOpt", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-securityOpt") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := composeHelper.GetProjectName(workspace.UID) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + mount := containerDetail.Mounts[0] + gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) + gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) + gomega.Expect(mount.RW).To(gomega.BeTrue()) + }, ginkgo.SpecTimeout(60*time.Second)) - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) + ginkgo.It("should start a new workspace with container environment variables set", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-container-env") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.HostConfig.SecurityOpt).To(gomega.ContainElement("seccomp=unconfined"), "securityOpts contain seccomp=unconfined") - gomega.Expect(containerDetail.HostConfig.SecurityOpt).To(gomega.ContainElement("apparmor=unconfined"), "securityOpts contain apparmor=unconfined") - }, ginkgo.SpecTimeout(60*time.Second)) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - ginkgo.It("should start a new workspace with override command", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-override-command") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) + err = f.ExecCommand(ctx, true, true, "BAR", []string{"ssh", "--command", "echo $FOO", projectName}) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + ginkgo.It("should start a new workspace with container user", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-container-user") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.Config.Entrypoint).NotTo(gomega.ContainElement("bash"), "overrides container entry point") - gomega.Expect(containerDetail.Config.Cmd).To(gomega.BeEmpty(), "overrides container command") - }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.It("should start a new workspace with remote env", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-remote-env") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + err = f.ExecCommand(ctx, true, true, "root", []string{"ssh", "--command", "ps u -p 1", projectName}) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID + ginkgo.It("should start a new workspace with privileged", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-privileged") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - err = f.ExecCommand(ctx, true, true, "/home/vscode/remote-env.out", []string{"ssh", "--command", "ls $HOME/remote-env.out", projectName}) - framework.ExpectNoError(err) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - err = f.ExecCommand(ctx, true, true, "BAR", []string{"ssh", "--command", "cat $HOME/remote-env.out", projectName}) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) - ginkgo.It("should start a new workspace with remote user", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-remote-user") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.HostConfig.Privileged).To(gomega.BeTrue(), "container run with privileged true") + }, ginkgo.SpecTimeout(60*time.Second)) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + ginkgo.It("should start a new workspace with capAdd", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-capadd") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - err = f.ExecCommand(ctx, true, true, "root", []string{"ssh", "--command", "cat $HOME/remote-user.out", projectName}) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - ginkgo.It("should start a new workspace and substitute devcontainer.json variables", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-variables") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.HostConfig.CapAdd).To(gomega.ContainElement("SYS_PTRACE"), "image capabilities are not duplicated") + gomega.Expect(containerDetail.HostConfig.CapAdd).To(gomega.ContainElement("NET_ADMIN"), "devcontainer configuration can add capabilities") + }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ginkgo.It("should start a new workspace with securityOpt", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-securityOpt") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - devContainerID, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/dev-container-id.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(devContainerID).NotTo(gomega.BeEmpty()) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-env-path.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerEnvPath).To(gomega.ContainSubstring("/usr/local/bin")) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - localEnvHome, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-env-home.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localEnvHome).To(gomega.Equal(os.Getenv("HOME"))) + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) - localWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localWorkspaceFolder).To(gomega.Equal(tempDir)) + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.HostConfig.SecurityOpt).To(gomega.ContainElement("seccomp=unconfined"), "securityOpts contain seccomp=unconfined") + gomega.Expect(containerDetail.HostConfig.SecurityOpt).To(gomega.ContainElement("apparmor=unconfined"), "securityOpts contain apparmor=unconfined") + }, ginkgo.SpecTimeout(60*time.Second)) - localWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder-basename.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) + ginkgo.It("should start a new workspace with override command", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-override-command") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - containerWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerWorkspaceFolder).To(gomega.Equal("/workspaces")) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - containerWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder-basename.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerWorkspaceFolderBasename).To(gomega.Equal("workspaces")) - }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.It("should start a new workspace with mounts", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-mounts") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) - // Check for docker-compose container running - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.Config.Entrypoint).NotTo(gomega.ContainElement("bash"), "overrides container entry point") + gomega.Expect(containerDetail.Config.Cmd).To(gomega.BeEmpty(), "overrides container command") + }, ginkgo.SpecTimeout(60*time.Second)) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + ginkgo.It("should start a new workspace with remote env", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-remote-env") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - _, _, err = f.ExecCommandCapture(ctx, []string{"ssh", "--command", "touch /home/vscode/mnt1/foo.txt", projectName, "--user", "root"}) - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - _, _, err = f.ExecCommandCapture(ctx, []string{"ssh", "--command", "echo -n BAR > /home/vscode/mnt1/foo.txt", projectName, "--user", "root"}) - framework.ExpectNoError(err) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - foo, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt1/foo.txt", projectName}) - framework.ExpectNoError(err) - gomega.Expect(foo).To(gomega.Equal("BAR")) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - bar, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt2/bar.txt", projectName}) - framework.ExpectNoError(err) - gomega.Expect(bar).To(gomega.Equal("FOO")) - }, ginkgo.SpecTimeout(60*time.Second)) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID - ginkgo.It("should start a new workspace with multistage build", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-with-multi-stage-build") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + err = f.ExecCommand(ctx, true, true, "/home/vscode/remote-env.out", []string{"ssh", "--command", "ls $HOME/remote-env.out", projectName}) + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + err = f.ExecCommand(ctx, true, true, "BAR", []string{"ssh", "--command", "cat $HOME/remote-env.out", projectName}) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(180*time.Second)) + ginkgo.It("should start a new workspace with remote user", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-remote-user") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ginkgo.It("should start a new workspace with host:port forwardPorts", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-forward-ports") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - // Check for docker-compose container running - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - done := make(chan error) - - sshContext, sshCancel := context.WithCancel(context.Background()) - go func() { - cmd := exec.CommandContext(sshContext, "ssh", projectName+".devpod", "sleep", "10") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Start(); err != nil { - done <- err - return - } - - if err := cmd.Wait(); err != nil { - done <- err - return - } - - done <- nil - }() - - gomega.Eventually(func(g gomega.Gomega) { - response, err := http.Get("http://localhost:8080") - g.Expect(err).NotTo(gomega.HaveOccurred()) - - body, err := io.ReadAll(response.Body) - g.Expect(err).NotTo(gomega.HaveOccurred()) - g.Expect(body).To(gomega.ContainSubstring("Thank you for using nginx.")) - }). - WithPolling(1 * time.Second). - WithTimeout(20 * time.Second). - Should(gomega.Succeed()) - - sshCancel() - err = <-done - - gomega.Expect(err).To(gomega.Or( - gomega.MatchError("signal: killed"), - gomega.MatchError(context.Canceled), - )) - }, ginkgo.SpecTimeout(60*time.Second)) + err = f.ExecCommand(ctx, true, true, "root", []string{"ssh", "--command", "cat $HOME/remote-user.out", projectName}) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should start a new workspace with features", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-features") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ginkgo.It("should start a new workspace and substitute devcontainer.json variables", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-variables") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - vclusterVersionOutput, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "vcluster --version", projectName}) - framework.ExpectNoError(err) - gomega.Expect(vclusterVersionOutput).To(gomega.ContainSubstring("vcluster version 0.15.2")) - }, ginkgo.SpecTimeout(60*time.Second)) + devContainerID, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/dev-container-id.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(devContainerID).NotTo(gomega.BeEmpty()) - ginkgo.It("should start a new workspace with env-file", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-env-file") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-env-path.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerEnvPath).To(gomega.ContainSubstring("/usr/local/bin")) - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) + localEnvHome, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-env-home.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localEnvHome).To(gomega.Equal(os.Getenv("HOME"))) - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + localWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localWorkspaceFolder).To(gomega.Equal(tempDir)) - devPodUpOutput, _, err := f.ExecCommandCapture(ctx, []string{"up", "--debug", "--ide", "none", tempDir}) - framework.ExpectNoError(err) + localWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder-basename.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) + containerWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerWorkspaceFolder).To(gomega.Equal("/workspaces")) - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - gomega.Expect(devPodUpOutput).NotTo(gomega.ContainSubstring("Defaulting to a blank string.")) - }, ginkgo.SpecTimeout(60*time.Second)) + containerWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder-basename.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerWorkspaceFolderBasename).To(gomega.Equal("workspaces")) + }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.Context("with lifecycle commands", func() { - ginkgo.It("should start a new workspace and execute array based lifecycle commands", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-lifecycle-array") + ginkgo.It("should start a new workspace with mounts", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-mounts") framework.ExpectNoError(err) ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) @@ -1481,9 +1295,10 @@ var _ = DevPodDescribe("devpod up test suite", func() { err = f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) + // Check for docker-compose container running ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - err = f.DevPodUp(ctx, tempDir) + err = f.DevPodUp(ctx, tempDir, "--debug") framework.ExpectNoError(err) workspace, err := f.FindWorkspace(ctx, tempDir) @@ -1497,84 +1312,40 @@ var _ = DevPodDescribe("devpod up test suite", func() { framework.ExpectNoError(err) gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - initializeCommand, err := os.ReadFile(filepath.Join(tempDir, "initialize-command.out")) + _, _, err = f.ExecCommandCapture(ctx, []string{"ssh", "--command", "touch /home/vscode/mnt1/foo.txt", projectName, "--user", "root"}) framework.ExpectNoError(err) - gomega.Expect(initializeCommand).To(gomega.ContainSubstring("initializeCommand")) - onCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/on-create-command.out", projectName}) + _, _, err = f.ExecCommandCapture(ctx, []string{"ssh", "--command", "echo -n BAR > /home/vscode/mnt1/foo.txt", projectName, "--user", "root"}) framework.ExpectNoError(err) - gomega.Expect(onCreateCommand).To(gomega.ContainSubstring("onCreateCommand")) - updateContentCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/update-content-command.out", projectName}) + foo, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt1/foo.txt", projectName}) framework.ExpectNoError(err) - gomega.Expect(updateContentCommand).To(gomega.Equal("updateContentCommand")) + gomega.Expect(foo).To(gomega.Equal("BAR")) - postCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-create-command.out", projectName}) + bar, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt2/bar.txt", projectName}) framework.ExpectNoError(err) - gomega.Expect(postCreateCommand).To(gomega.Equal("postCreateCommand")) + gomega.Expect(bar).To(gomega.Equal("FOO")) + }, ginkgo.SpecTimeout(60*time.Second)) - postStartCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-start-command.out", projectName}) + ginkgo.It("should start a new workspace with multistage build", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-with-multi-stage-build") framework.ExpectNoError(err) - gomega.Expect(postStartCommand).To(gomega.Equal("postStartCommand")) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - postAttachCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-attach-command.out", projectName}) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) - gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) - }, ginkgo.SpecTimeout(60*time.Second)) - //ginkgo.FIt("should start a new workspace and execute object based lifecycle commands", func(ctx context.Context) { - // tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-lifecycle-object") - // framework.ExpectNoError(err) - // ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - // - // f := framework.NewDefaultFramework(initialDir + "/bin") - // _ = f.DevPodProviderAdd(ctx, "docker"}) - // err = f.DevPodProviderUse(context.Background(), "docker") - // framework.ExpectNoError(err) - // - // err = f.DevPodUp(ctx, tempDir) - // framework.ExpectNoError(err) - // - // // Check for docker-compose container running - // projectName := composeHelper.ToProjectName(filepath.Base(tempDir)) - // ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), projectName) - // - // ids, err := dockerHelper.FindContainer(ctx, []string{ - // fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - // fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - // }) - // framework.ExpectNoError(err) - // gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - // - // initializeCommand, err := os.ReadFile(filepath.Join(tempDir, "initialize-command.out")) - // framework.ExpectNoError(err) - // gomega.Expect(initializeCommand).To(gomega.ContainSubstring("initializeCommand")) - // - // onCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/on-create-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(onCreateCommand).To(gomega.ContainSubstring("onCreateCommand")) - // - // updateContentCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/update-content-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(updateContentCommand).To(gomega.Equal("updateContentCommand")) - // - // postCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-create-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(postCreateCommand).To(gomega.Equal("postCreateCommand")) - // - // postStartCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-start-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(postStartCommand).To(gomega.Equal("postStartCommand")) - // - // postAttachCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-attach-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) - //}, ginkgo.SpecTimeout(60*time.Second)) - }) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(180*time.Second)) - ginkgo.Context("with --recreate", func() { - ginkgo.It("should NOT delete container when rebuild fails", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-rebuild-fail") + ginkgo.It("should start a new workspace with host:port forwardPorts", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-forward-ports") framework.ExpectNoError(err) ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) @@ -1583,16 +1354,16 @@ var _ = DevPodDescribe("devpod up test suite", func() { err = f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) + // Check for docker-compose container running ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.By("Starting DevPod") - err = f.DevPodUp(ctx, tempDir) + err = f.DevPodUp(ctx, tempDir, "--debug") framework.ExpectNoError(err) workspace, err := f.FindWorkspace(ctx, tempDir) framework.ExpectNoError(err) + projectName := workspace.ID - ginkgo.By("Should start a docker-compose container") ids, err := dockerHelper.FindContainer(ctx, []string{ fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), @@ -1600,35 +1371,81 @@ var _ = DevPodDescribe("devpod up test suite", func() { framework.ExpectNoError(err) gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - ginkgo.By("Modifying .devcontainer.json with failing changes") - origPath := filepath.Join(tempDir, ".devcontainer.json") - err = os.Remove(origPath) - framework.ExpectNoError(err) + done := make(chan error) + + sshContext, sshCancel := context.WithCancel(context.Background()) + go func() { + cmd := exec.CommandContext(sshContext, "ssh", projectName+".devpod", "sleep", "10") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Start(); err != nil { + done <- err + return + } + + if err := cmd.Wait(); err != nil { + done <- err + return + } + + done <- nil + }() + + gomega.Eventually(func(g gomega.Gomega) { + response, err := http.Get("http://localhost:8080") + g.Expect(err).NotTo(gomega.HaveOccurred()) + + body, err := io.ReadAll(response.Body) + g.Expect(err).NotTo(gomega.HaveOccurred()) + g.Expect(body).To(gomega.ContainSubstring("Thank you for using nginx.")) + }). + WithPolling(1 * time.Second). + WithTimeout(20 * time.Second). + Should(gomega.Succeed()) + + sshCancel() + err = <-done + + gomega.Expect(err).To(gomega.Or( + gomega.MatchError("signal: killed"), + gomega.MatchError(context.Canceled), + )) + }, ginkgo.SpecTimeout(60*time.Second)) - failingConfig, err := os.Open(filepath.Join(tempDir, "fail.devcontainer.json")) + ginkgo.It("should start a new workspace with features", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-features") framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - newConfig, err := os.Create(origPath) + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") framework.ExpectNoError(err) - _, err = io.Copy(newConfig, failingConfig) + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir, "--debug") framework.ExpectNoError(err) - ginkgo.By("Starting DevPod again with --recreate") - err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") - framework.ExpectError(err) + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID - ginkgo.By("Should leave original container running") - ids2, err := dockerHelper.FindContainer(ctx, []string{ + ids, err := dockerHelper.FindContainer(ctx, []string{ fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), }) framework.ExpectNoError(err) - gomega.Expect(ids2[0]).To(gomega.Equal(ids[0]), "Should use original container") - }) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - ginkgo.It("should delete container upon successful rebuild", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-rebuild-success") + vclusterVersionOutput, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "vcluster --version", projectName}) + framework.ExpectNoError(err) + gomega.Expect(vclusterVersionOutput).To(gomega.ContainSubstring("vcluster version 0.15.2")) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with env-file", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-env-file") framework.ExpectNoError(err) ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) @@ -1639,32 +1456,217 @@ var _ = DevPodDescribe("devpod up test suite", func() { ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - ginkgo.By("Starting DevPod") - err = f.DevPodUp(ctx, tempDir) + devPodUpOutput, _, err := f.ExecCommandCapture(ctx, []string{"up", "--debug", "--ide", "none", tempDir}) framework.ExpectNoError(err) workspace, err := f.FindWorkspace(ctx, tempDir) framework.ExpectNoError(err) - ginkgo.By("Should start a docker-compose container") ids, err := dockerHelper.FindContainer(ctx, []string{ fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), }) framework.ExpectNoError(err) gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + gomega.Expect(devPodUpOutput).NotTo(gomega.ContainSubstring("Defaulting to a blank string.")) + }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.By("Starting DevPod again with --recreate") - err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") - framework.ExpectNoError(err) + ginkgo.Context("with lifecycle commands", func() { + ginkgo.It("should start a new workspace and execute array based lifecycle commands", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-lifecycle-array") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + initializeCommand, err := os.ReadFile(filepath.Join(tempDir, "initialize-command.out")) + framework.ExpectNoError(err) + gomega.Expect(initializeCommand).To(gomega.ContainSubstring("initializeCommand")) + + onCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/on-create-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(onCreateCommand).To(gomega.ContainSubstring("onCreateCommand")) + + updateContentCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/update-content-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(updateContentCommand).To(gomega.Equal("updateContentCommand")) + + postCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-create-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(postCreateCommand).To(gomega.Equal("postCreateCommand")) + + postStartCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-start-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(postStartCommand).To(gomega.Equal("postStartCommand")) + + postAttachCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-attach-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) + }, ginkgo.SpecTimeout(60*time.Second)) + + //ginkgo.FIt("should start a new workspace and execute object based lifecycle commands", func(ctx context.Context) { + // tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-lifecycle-object") + // framework.ExpectNoError(err) + // ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + // + // f := framework.NewDefaultFramework(initialDir + "/bin") + // _ = f.DevPodProviderAdd(ctx, "docker"}) + // err = f.DevPodProviderUse(context.Background(), "docker") + // framework.ExpectNoError(err) + // + // err = f.DevPodUp(ctx, tempDir) + // framework.ExpectNoError(err) + // + // // Check for docker-compose container running + // projectName := composeHelper.ToProjectName(filepath.Base(tempDir)) + // ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), projectName) + // + // ids, err := dockerHelper.FindContainer(ctx, []string{ + // fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + // fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + // }) + // framework.ExpectNoError(err) + // gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + // + // initializeCommand, err := os.ReadFile(filepath.Join(tempDir, "initialize-command.out")) + // framework.ExpectNoError(err) + // gomega.Expect(initializeCommand).To(gomega.ContainSubstring("initializeCommand")) + // + // onCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/on-create-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(onCreateCommand).To(gomega.ContainSubstring("onCreateCommand")) + // + // updateContentCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/update-content-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(updateContentCommand).To(gomega.Equal("updateContentCommand")) + // + // postCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-create-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(postCreateCommand).To(gomega.Equal("postCreateCommand")) + // + // postStartCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-start-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(postStartCommand).To(gomega.Equal("postStartCommand")) + // + // postAttachCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-attach-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) + //}, ginkgo.SpecTimeout(60*time.Second)) + }) - ginkgo.By("Should start a new docker-compose container on rebuild") - ids2, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + ginkgo.Context("with --recreate", func() { + ginkgo.It("should NOT delete container when rebuild fails", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-rebuild-fail") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + ginkgo.By("Starting DevPod") + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ginkgo.By("Should start a docker-compose container") + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + ginkgo.By("Modifying .devcontainer.json with failing changes") + origPath := filepath.Join(tempDir, ".devcontainer.json") + err = os.Remove(origPath) + framework.ExpectNoError(err) + + failingConfig, err := os.Open(filepath.Join(tempDir, "fail.devcontainer.json")) + framework.ExpectNoError(err) + + newConfig, err := os.Create(origPath) + framework.ExpectNoError(err) + + _, err = io.Copy(newConfig, failingConfig) + framework.ExpectNoError(err) + + ginkgo.By("Starting DevPod again with --recreate") + err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + framework.ExpectError(err) + + ginkgo.By("Should leave original container running") + ids2, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids2[0]).To(gomega.Equal(ids[0]), "Should use original container") + }) + + ginkgo.It("should delete container upon successful rebuild", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-rebuild-success") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + ginkgo.By("Starting DevPod") + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ginkgo.By("Should start a docker-compose container") + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + ginkgo.By("Starting DevPod again with --recreate") + err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + framework.ExpectNoError(err) + + ginkgo.By("Should start a new docker-compose container on rebuild") + ids2, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids2[0]).NotTo(gomega.Equal(ids[0]), "Should restart container") }) - framework.ExpectNoError(err) - gomega.Expect(ids2[0]).NotTo(gomega.Equal(ids[0]), "Should restart container") }) }) }) From ec8c26c1555b7bda9d3d7240780dc0c5802a44b2 Mon Sep 17 00:00:00 2001 From: Luca Di Maio Date: Fri, 17 Nov 2023 16:29:30 +0100 Subject: [PATCH 2/3] tests: increase parallelization Signed-off-by: Luca Di Maio --- .github/workflows/e2e-tests.yaml | 2 +- e2e/tests/up/docker.go | 447 ++++++++++ e2e/tests/up/docker_compose.go | 919 ++++++++++++++++++++ e2e/tests/up/podman.go | 91 ++ e2e/tests/up/up.go | 1370 ------------------------------ 5 files changed, 1458 insertions(+), 1371 deletions(-) create mode 100644 e2e/tests/up/docker.go create mode 100644 e2e/tests/up/docker_compose.go create mode 100644 e2e/tests/up/podman.go diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index e27e76f31..3e1f112a3 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -27,7 +27,7 @@ jobs: fail-fast: true max-parallel: 8 matrix: - label: [ "build", "ide", "integration", "machine", "machineprovider", "provider", "proxyprovider", "ssh", "up" ] + label: ["build", "ide", "integration", "machine", "machineprovider", "provider", "proxyprovider", "ssh", "up", "up-docker", "up-podman", "up-docker-compose"] steps: - name: Checkout repo diff --git a/e2e/tests/up/docker.go b/e2e/tests/up/docker.go new file mode 100644 index 000000000..ca614e828 --- /dev/null +++ b/e2e/tests/up/docker.go @@ -0,0 +1,447 @@ +package up + +import ( + "context" + "fmt" + "net/http" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/loft-sh/devpod/e2e/framework" + "github.com/loft-sh/devpod/pkg/devcontainer/config" + docker "github.com/loft-sh/devpod/pkg/docker" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "github.com/onsi/gomega/ghttp" +) + +var _ = DevPodDescribe("devpod up test suite", func() { + ginkgo.Context("testing up command", ginkgo.Label("up-docker"), ginkgo.Ordered, func() { + var dockerHelper *docker.DockerHelper + var initialDir string + + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) + + dockerHelper = &docker.DockerHelper{DockerCommand: "docker"} + framework.ExpectNoError(err) + }) + + ginkgo.Context("with docker", ginkgo.Ordered, func() { + ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should start a new workspace and substitute devcontainer.json variables", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-variables") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + projectName := workspace.ID + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + devContainerID, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/dev-container-id.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(devContainerID).NotTo(gomega.BeEmpty()) + + containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-env-path.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerEnvPath).To(gomega.ContainSubstring("/usr/local/bin")) + + localEnvHome, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-env-home.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localEnvHome).To(gomega.Equal(os.Getenv("HOME"))) + + localWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localWorkspaceFolder).To(gomega.Equal(tempDir)) + + localWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder-basename.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) + + containerWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerWorkspaceFolder).To(gomega.Equal(filepath.Join("/workspaces", filepath.Base(tempDir)))) + + containerWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder-basename.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with mounts", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-mounts") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + foo, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt1/foo.txt", projectName}) + framework.ExpectNoError(err) + gomega.Expect(foo).To(gomega.Equal("BAR")) + + bar, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt2/bar.txt", projectName}) + framework.ExpectNoError(err) + gomega.Expect(bar).To(gomega.Equal("FOO")) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with multistage build", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-with-multi-stage-build") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(180*time.Second)) + + ginkgo.Context("should start a new workspace with features", func() { + ginkgo.It("ensure dependencies installed via features are accessible in lifecycle hooks", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-features-lifecycle-hooks") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + }) + ginkgo.It("should start a new workspace with dotfiles - no install script", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--dotfiles", "https://github.com/loft-sh/example-dotfiles") + framework.ExpectNoError(err) + + out, err := f.DevPodSSH(ctx, tempDir, "ls ~/.file*") + framework.ExpectNoError(err) + + expectedOutput := `/home/vscode/.file1 +/home/vscode/.file2 +/home/vscode/.file3 +` + framework.ExpectEqual(out, expectedOutput, "should match") + }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should start a new workspace with dotfiles - install script", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--dotfiles", "https://github.com/loft-sh/example-dotfiles", "--dotfiles-script", "install-example") + framework.ExpectNoError(err) + + out, err := f.DevPodSSH(ctx, tempDir, "ls /tmp/worked") + framework.ExpectNoError(err) + + expectedOutput := "/tmp/worked\n" + + framework.ExpectEqual(out, expectedOutput, "should match") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with custom image", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--devcontainer-image", "mcr.microsoft.com/vscode/devcontainers/base:alpine") + framework.ExpectNoError(err) + + out, err := f.DevPodSSH(ctx, tempDir, "grep ^ID= /etc/os-release") + framework.ExpectNoError(err) + + expectedOutput := "ID=alpine\n" + unexpectedOutput := "ID=debian\n" + + framework.ExpectEqual(out, expectedOutput, "should match") + framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") + }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should start a new workspace with custom image and skip building", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-with-multi-stage-build") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--devcontainer-image", "mcr.microsoft.com/vscode/devcontainers/base:alpine") + framework.ExpectNoError(err) + + out, err := f.DevPodSSH(ctx, tempDir, "grep ^ID= /etc/os-release") + framework.ExpectNoError(err) + + expectedOutput := "ID=alpine\n" + unexpectedOutput := "ID=debian\n" + + framework.ExpectEqual(out, expectedOutput, "should match") + framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.Context("should start a workspace from a Dockerfile build", func() { + ginkgo.It("should rebuild image in case of changes in files in build context", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-dockerfile-buildcontext") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + container, err := dockerHelper.FindDevContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + + image1 := container.Config.LegacyImage + + scriptFile, err := os.OpenFile(tempDir+"/scripts/alias.sh", + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + framework.ExpectNoError(err) + + defer scriptFile.Close() + + ginkgo.By("Changing a file within the context") + _, err = scriptFile.Write([]byte("alias yr='date +%Y'")) + framework.ExpectNoError(err) + + ginkgo.By("Starting DevPod again with --recreate") + err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + framework.ExpectNoError(err) + + container, err = dockerHelper.FindDevContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + + image2 := container.Config.LegacyImage + + gomega.Expect(image2).ShouldNot(gomega.Equal(image1), "images should be different") + }, ginkgo.SpecTimeout(60*time.Second)) + ginkgo.It("should not rebuild image for changes in files mentioned in .dockerignore", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-dockerfile-buildcontext") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + container, err := dockerHelper.FindDevContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + + image1 := container.Config.LegacyImage + + scriptFile, err := os.OpenFile(tempDir+"/scripts/install.sh", + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) + framework.ExpectNoError(err) + + defer scriptFile.Close() + + ginkgo.By("Changing a file within context") + _, err = scriptFile.Write([]byte("apt install python")) + framework.ExpectNoError(err) + + ginkgo.By("Starting DevPod again with --recreate") + err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + framework.ExpectNoError(err) + + container, err = dockerHelper.FindDevContainer(ctx, []string{ + fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), + }) + framework.ExpectNoError(err) + + image2 := container.Config.LegacyImage + + gomega.Expect(image2).Should(gomega.Equal(image1), "image should be same") + }, ginkgo.SpecTimeout(60*time.Second)) + }) + ginkgo.It("should use http headers to download feature", func(ctx context.Context) { + server := ghttp.NewServer() + + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-features-http-headers") + framework.ExpectNoError(err) + + featureArchiveFilePath := path.Join(tempDir, "devcontainer-feature-hello.tgz") + featureFiles := []string{path.Join(tempDir, "devcontainer-feature.json"), path.Join(tempDir, "install.sh")} + err = createTarGzArchive(featureArchiveFilePath, featureFiles) + framework.ExpectNoError(err) + + devContainerFileBuf, err := os.ReadFile(path.Join(tempDir, ".devcontainer.json")) + framework.ExpectNoError(err) + + output := strings.Replace(string(devContainerFileBuf), "#{server_url}", server.URL(), -1) + err = os.WriteFile(path.Join(tempDir, ".devcontainer.json"), []byte(output), 0644) + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + ginkgo.DeferCleanup(server.Close) + + respHeader := http.Header{} + respHeader.Set("Content-Disposition", "attachment; filename=devcontainer-feature-hello.tgz") + + featureArchiveFileBuf, err := os.ReadFile(featureArchiveFilePath) + framework.ExpectNoError(err) + + f := framework.NewDefaultFramework(initialDir + "/bin") + server.AppendHandlers( + ghttp.CombineHandlers( + ghttp.VerifyRequest("GET", "/devcontainer-feature-hello.tgz"), + ghttp.VerifyHeaderKV("Foo-Header", "Foo"), + ghttp.RespondWith(http.StatusOK, featureArchiveFileBuf, respHeader), + ), + ) + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + server.Close() + }, ginkgo.SpecTimeout(60*time.Second)) + }) + }) +}) diff --git a/e2e/tests/up/docker_compose.go b/e2e/tests/up/docker_compose.go new file mode 100644 index 000000000..bb66ec95e --- /dev/null +++ b/e2e/tests/up/docker_compose.go @@ -0,0 +1,919 @@ +package up + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/docker/docker/api/types" + "github.com/loft-sh/devpod/e2e/framework" + "github.com/loft-sh/devpod/pkg/compose" + docker "github.com/loft-sh/devpod/pkg/docker" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +var _ = DevPodDescribe("devpod up test suite", func() { + ginkgo.Context("testing up command", ginkgo.Label("up-docker-compose"), ginkgo.Ordered, func() { + var dockerHelper *docker.DockerHelper + var composeHelper *compose.ComposeHelper + var initialDir string + + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) + + dockerHelper = &docker.DockerHelper{DockerCommand: "docker"} + composeHelper, err = compose.NewComposeHelper("", dockerHelper) + framework.ExpectNoError(err) + }) + + ginkgo.Context("with docker-compose", func() { + ginkgo.It("should start a new workspace with root folder configuration", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) + + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") + + mount := containerDetail.Mounts[0] + gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) + gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) + gomega.Expect(mount.RW).To(gomega.BeTrue()) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with sub-folder configuration", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-subfolder") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) + + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") + + mount := containerDetail.Mounts[0] + gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) + gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) + gomega.Expect(mount.RW).To(gomega.BeTrue()) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with multiple services", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-multiple-services") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := composeHelper.GetProjectName(workspace.UID) + + appIDs, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(appIDs).To(gomega.HaveLen(1), "app container to be created") + + dbIDs, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "db"), + }) + framework.ExpectNoError(err) + gomega.Expect(dbIDs).To(gomega.HaveLen(1), "db container to be created") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with specific services", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-run-services") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := composeHelper.GetProjectName(workspace.UID) + + appIDs, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(appIDs).To(gomega.HaveLen(1), "app container to be created") + + dbIDs, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "db"), + }) + framework.ExpectNoError(err) + gomega.Expect(dbIDs).To(gomega.BeEmpty(), "db container not to be created") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with .devcontainer docker-compose overrides", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-overrides") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := composeHelper.GetProjectName(workspace.UID) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) + + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") + + mount := containerDetail.Mounts[0] + gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) + gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) + gomega.Expect(mount.RW).To(gomega.BeTrue()) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with container environment variables set", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-container-env") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + err = f.ExecCommand(ctx, true, true, "BAR", []string{"ssh", "--command", "echo $FOO", projectName}) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with container user", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-container-user") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + err = f.ExecCommand(ctx, true, true, "root", []string{"ssh", "--command", "ps u -p 1", projectName}) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with privileged", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-privileged") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) + + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.HostConfig.Privileged).To(gomega.BeTrue(), "container run with privileged true") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with capAdd", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-capadd") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) + + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.HostConfig.CapAdd).To(gomega.ContainElement("SYS_PTRACE"), "image capabilities are not duplicated") + gomega.Expect(containerDetail.HostConfig.CapAdd).To(gomega.ContainElement("NET_ADMIN"), "devcontainer configuration can add capabilities") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with securityOpt", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-securityOpt") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) + + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.HostConfig.SecurityOpt).To(gomega.ContainElement("seccomp=unconfined"), "securityOpts contain seccomp=unconfined") + gomega.Expect(containerDetail.HostConfig.SecurityOpt).To(gomega.ContainElement("apparmor=unconfined"), "securityOpts contain apparmor=unconfined") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with override command", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-override-command") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + var containerDetails []types.ContainerJSON + err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) + framework.ExpectNoError(err) + + containerDetail := containerDetails[0] + gomega.Expect(containerDetail.Config.Entrypoint).NotTo(gomega.ContainElement("bash"), "overrides container entry point") + gomega.Expect(containerDetail.Config.Cmd).To(gomega.BeEmpty(), "overrides container command") + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with remote env", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-remote-env") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + err = f.ExecCommand(ctx, true, true, "/home/vscode/remote-env.out", []string{"ssh", "--command", "ls $HOME/remote-env.out", projectName}) + framework.ExpectNoError(err) + + err = f.ExecCommand(ctx, true, true, "BAR", []string{"ssh", "--command", "cat $HOME/remote-env.out", projectName}) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with remote user", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-remote-user") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + err = f.ExecCommand(ctx, true, true, "root", []string{"ssh", "--command", "cat $HOME/remote-user.out", projectName}) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace and substitute devcontainer.json variables", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-variables") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + devContainerID, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/dev-container-id.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(devContainerID).NotTo(gomega.BeEmpty()) + + containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-env-path.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerEnvPath).To(gomega.ContainSubstring("/usr/local/bin")) + + localEnvHome, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-env-home.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localEnvHome).To(gomega.Equal(os.Getenv("HOME"))) + + localWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localWorkspaceFolder).To(gomega.Equal(tempDir)) + + localWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder-basename.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(localWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) + + containerWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerWorkspaceFolder).To(gomega.Equal("/workspaces")) + + containerWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder-basename.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(containerWorkspaceFolderBasename).To(gomega.Equal("workspaces")) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with mounts", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-mounts") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + // Check for docker-compose container running + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + _, _, err = f.ExecCommandCapture(ctx, []string{"ssh", "--command", "touch /home/vscode/mnt1/foo.txt", projectName, "--user", "root"}) + framework.ExpectNoError(err) + + _, _, err = f.ExecCommandCapture(ctx, []string{"ssh", "--command", "echo -n BAR > /home/vscode/mnt1/foo.txt", projectName, "--user", "root"}) + framework.ExpectNoError(err) + + foo, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt1/foo.txt", projectName}) + framework.ExpectNoError(err) + gomega.Expect(foo).To(gomega.Equal("BAR")) + + bar, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt2/bar.txt", projectName}) + framework.ExpectNoError(err) + gomega.Expect(bar).To(gomega.Equal("FOO")) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with multistage build", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-with-multi-stage-build") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(180*time.Second)) + + ginkgo.It("should start a new workspace with host:port forwardPorts", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-forward-ports") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + // Check for docker-compose container running + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + done := make(chan error) + + sshContext, sshCancel := context.WithCancel(context.Background()) + go func() { + cmd := exec.CommandContext(sshContext, "ssh", projectName+".devpod", "sleep", "10") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Start(); err != nil { + done <- err + return + } + + if err := cmd.Wait(); err != nil { + done <- err + return + } + + done <- nil + }() + + gomega.Eventually(func(g gomega.Gomega) { + response, err := http.Get("http://localhost:8080") + g.Expect(err).NotTo(gomega.HaveOccurred()) + + body, err := io.ReadAll(response.Body) + g.Expect(err).NotTo(gomega.HaveOccurred()) + g.Expect(body).To(gomega.ContainSubstring("Thank you for using nginx.")) + }). + WithPolling(1 * time.Second). + WithTimeout(20 * time.Second). + Should(gomega.Succeed()) + + sshCancel() + err = <-done + + gomega.Expect(err).To(gomega.Or( + gomega.MatchError("signal: killed"), + gomega.MatchError(context.Canceled), + )) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with features", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-features") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir, "--debug") + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + vclusterVersionOutput, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "vcluster --version", projectName}) + framework.ExpectNoError(err) + gomega.Expect(vclusterVersionOutput).To(gomega.ContainSubstring("vcluster version 0.15.2")) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with env-file", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-env-file") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + devPodUpOutput, _, err := f.ExecCommandCapture(ctx, []string{"up", "--debug", "--ide", "none", tempDir}) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + gomega.Expect(devPodUpOutput).NotTo(gomega.ContainSubstring("Defaulting to a blank string.")) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.Context("with lifecycle commands", func() { + ginkgo.It("should start a new workspace and execute array based lifecycle commands", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-lifecycle-array") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + projectName := workspace.ID + + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + initializeCommand, err := os.ReadFile(filepath.Join(tempDir, "initialize-command.out")) + framework.ExpectNoError(err) + gomega.Expect(initializeCommand).To(gomega.ContainSubstring("initializeCommand")) + + onCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/on-create-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(onCreateCommand).To(gomega.ContainSubstring("onCreateCommand")) + + updateContentCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/update-content-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(updateContentCommand).To(gomega.Equal("updateContentCommand")) + + postCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-create-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(postCreateCommand).To(gomega.Equal("postCreateCommand")) + + postStartCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-start-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(postStartCommand).To(gomega.Equal("postStartCommand")) + + postAttachCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-attach-command.out", projectName}) + framework.ExpectNoError(err) + gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) + }, ginkgo.SpecTimeout(60*time.Second)) + + //ginkgo.FIt("should start a new workspace and execute object based lifecycle commands", func(ctx context.Context) { + // tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-lifecycle-object") + // framework.ExpectNoError(err) + // ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + // + // f := framework.NewDefaultFramework(initialDir + "/bin") + // _ = f.DevPodProviderAdd(ctx, "docker"}) + // err = f.DevPodProviderUse(context.Background(), "docker") + // framework.ExpectNoError(err) + // + // err = f.DevPodUp(ctx, tempDir) + // framework.ExpectNoError(err) + // + // // Check for docker-compose container running + // projectName := composeHelper.ToProjectName(filepath.Base(tempDir)) + // ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), projectName) + // + // ids, err := dockerHelper.FindContainer(ctx, []string{ + // fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + // fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + // }) + // framework.ExpectNoError(err) + // gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + // + // initializeCommand, err := os.ReadFile(filepath.Join(tempDir, "initialize-command.out")) + // framework.ExpectNoError(err) + // gomega.Expect(initializeCommand).To(gomega.ContainSubstring("initializeCommand")) + // + // onCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/on-create-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(onCreateCommand).To(gomega.ContainSubstring("onCreateCommand")) + // + // updateContentCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/update-content-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(updateContentCommand).To(gomega.Equal("updateContentCommand")) + // + // postCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-create-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(postCreateCommand).To(gomega.Equal("postCreateCommand")) + // + // postStartCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-start-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(postStartCommand).To(gomega.Equal("postStartCommand")) + // + // postAttachCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-attach-command.out", projectName}) + // framework.ExpectNoError(err) + // gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) + //}, ginkgo.SpecTimeout(60*time.Second)) + }) + + ginkgo.Context("with --recreate", func() { + ginkgo.It("should NOT delete container when rebuild fails", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-rebuild-fail") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + ginkgo.By("Starting DevPod") + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ginkgo.By("Should start a docker-compose container") + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + ginkgo.By("Modifying .devcontainer.json with failing changes") + origPath := filepath.Join(tempDir, ".devcontainer.json") + err = os.Remove(origPath) + framework.ExpectNoError(err) + + failingConfig, err := os.Open(filepath.Join(tempDir, "fail.devcontainer.json")) + framework.ExpectNoError(err) + + newConfig, err := os.Create(origPath) + framework.ExpectNoError(err) + + _, err = io.Copy(newConfig, failingConfig) + framework.ExpectNoError(err) + + ginkgo.By("Starting DevPod again with --recreate") + err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + framework.ExpectError(err) + + ginkgo.By("Should leave original container running") + ids2, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids2[0]).To(gomega.Equal(ids[0]), "Should use original container") + }) + + ginkgo.It("should delete container upon successful rebuild", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-rebuild-success") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + ginkgo.By("Starting DevPod") + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + + workspace, err := f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + ginkgo.By("Should start a docker-compose container") + ids, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") + + ginkgo.By("Starting DevPod again with --recreate") + err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") + framework.ExpectNoError(err) + + ginkgo.By("Should start a new docker-compose container on rebuild") + ids2, err := dockerHelper.FindContainer(ctx, []string{ + fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), + fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), + }) + framework.ExpectNoError(err) + gomega.Expect(ids2[0]).NotTo(gomega.Equal(ids[0]), "Should restart container") + }) + }) + }) + }) +}) diff --git a/e2e/tests/up/podman.go b/e2e/tests/up/podman.go new file mode 100644 index 000000000..b97fccbea --- /dev/null +++ b/e2e/tests/up/podman.go @@ -0,0 +1,91 @@ +package up + +import ( + "context" + "os" + "os/exec" + "time" + + "github.com/loft-sh/devpod/e2e/framework" + "github.com/onsi/ginkgo/v2" +) + +var _ = DevPodDescribe("devpod up test suite", func() { + ginkgo.Context("testing up command", ginkgo.Label("up-podman"), ginkgo.Ordered, func() { + var initialDir string + + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) + }) + + ginkgo.Context("using docker provider", func() { + ginkgo.Context("with rootfull podman", ginkgo.Ordered, func() { + ginkgo.It("should setup rootful podman", func(ctx context.Context) { + wrapper, err := os.Create(initialDir + "/bin/podman-rootful") + framework.ExpectNoError(err) + + defer wrapper.Close() + + _, err = wrapper.WriteString(`#!/bin/sh + sudo podman "$@" + `) + framework.ExpectNoError(err) + + err = wrapper.Close() + framework.ExpectNoError(err) + + cmd := exec.Command("sudo", "chmod", "+x", initialDir+"/bin/podman-rootful") + err = cmd.Run() + framework.ExpectNoError(err) + + err = exec.Command(initialDir+"/bin/podman-rootful", "ps").Run() + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + + ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker", "-o", "DOCKER_PATH="+initialDir+"/bin/podman-rootful") + framework.ExpectNoError(err) + + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + }) + ginkgo.Context("with rootless podman", ginkgo.Ordered, func() { + ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + f := framework.NewDefaultFramework(initialDir + "/bin") + + _ = f.DevPodProviderDelete(ctx, "docker") + err = f.DevPodProviderAdd(ctx, "docker", "-o", "DOCKER_PATH=podman") + framework.ExpectNoError(err) + err = f.DevPodProviderUse(ctx, "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Wait for devpod workspace to come online (deadline: 30s) + err = f.DevPodUp(ctx, tempDir) + framework.ExpectNoError(err) + }, ginkgo.SpecTimeout(60*time.Second)) + }) + }) + }) +}) diff --git a/e2e/tests/up/up.go b/e2e/tests/up/up.go index b38019a5e..522e3840b 100644 --- a/e2e/tests/up/up.go +++ b/e2e/tests/up/up.go @@ -4,30 +4,23 @@ import ( "context" "encoding/json" "fmt" - "io" - "net/http" "os" "os/exec" - "path" "path/filepath" "strings" "time" - "github.com/docker/docker/api/types" "github.com/loft-sh/devpod/e2e/framework" - "github.com/loft-sh/devpod/pkg/compose" "github.com/loft-sh/devpod/pkg/devcontainer/config" docker "github.com/loft-sh/devpod/pkg/docker" "github.com/loft-sh/devpod/pkg/language" "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" - "github.com/onsi/gomega/ghttp" ) var _ = DevPodDescribe("devpod up test suite", func() { ginkgo.Context("testing up command", ginkgo.Label("up"), ginkgo.Ordered, func() { var dockerHelper *docker.DockerHelper - var composeHelper *compose.ComposeHelper var initialDir string ginkgo.BeforeEach(func() { @@ -36,7 +29,6 @@ var _ = DevPodDescribe("devpod up test suite", func() { framework.ExpectNoError(err) dockerHelper = &docker.DockerHelper{DockerCommand: "docker"} - composeHelper, err = compose.NewComposeHelper("", dockerHelper) framework.ExpectNoError(err) }) @@ -308,1367 +300,5 @@ var _ = DevPodDescribe("devpod up test suite", func() { framework.ExpectEqual(out, initialList) }, ginkgo.SpecTimeout(60*time.Second)) }) - - ginkgo.Context("using docker provider", func() { - ginkgo.Context("with rootfull podman", ginkgo.Ordered, func() { - ginkgo.It("should setup rootful podman", func(ctx context.Context) { - wrapper, err := os.Create(initialDir + "/bin/podman-rootful") - framework.ExpectNoError(err) - - defer wrapper.Close() - - _, err = wrapper.WriteString(`#!/bin/sh - sudo podman "$@" - `) - framework.ExpectNoError(err) - - err = wrapper.Close() - framework.ExpectNoError(err) - - cmd := exec.Command("sudo", "chmod", "+x", initialDir+"/bin/podman-rootful") - err = cmd.Run() - framework.ExpectNoError(err) - - err = exec.Command(initialDir+"/bin/podman-rootful", "ps").Run() - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker", "-o", "DOCKER_PATH="+initialDir+"/bin/podman-rootful") - framework.ExpectNoError(err) - - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - }) - ginkgo.Context("with rootless podman", ginkgo.Ordered, func() { - ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker", "-o", "DOCKER_PATH=podman") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - }) - - ginkgo.Context("with docker", ginkgo.Ordered, func() { - ginkgo.It("should start a new workspace with existing image", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should start a new workspace and substitute devcontainer.json variables", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-variables") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - projectName := workspace.ID - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - devContainerID, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/dev-container-id.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(devContainerID).NotTo(gomega.BeEmpty()) - - containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-env-path.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerEnvPath).To(gomega.ContainSubstring("/usr/local/bin")) - - localEnvHome, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-env-home.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localEnvHome).To(gomega.Equal(os.Getenv("HOME"))) - - localWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localWorkspaceFolder).To(gomega.Equal(tempDir)) - - localWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder-basename.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) - - containerWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerWorkspaceFolder).To(gomega.Equal(filepath.Join("/workspaces", filepath.Base(tempDir)))) - - containerWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder-basename.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with mounts", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-mounts") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - foo, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt1/foo.txt", projectName}) - framework.ExpectNoError(err) - gomega.Expect(foo).To(gomega.Equal("BAR")) - - bar, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt2/bar.txt", projectName}) - framework.ExpectNoError(err) - gomega.Expect(bar).To(gomega.Equal("FOO")) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with multistage build", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-with-multi-stage-build") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(180*time.Second)) - - ginkgo.Context("should start a new workspace with features", func() { - ginkgo.It("ensure dependencies installed via features are accessible in lifecycle hooks", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-features-lifecycle-hooks") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - }) - ginkgo.It("should start a new workspace with dotfiles - no install script", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--dotfiles", "https://github.com/loft-sh/example-dotfiles") - framework.ExpectNoError(err) - - out, err := f.DevPodSSH(ctx, tempDir, "ls ~/.file*") - framework.ExpectNoError(err) - - expectedOutput := `/home/vscode/.file1 -/home/vscode/.file2 -/home/vscode/.file3 -` - framework.ExpectEqual(out, expectedOutput, "should match") - }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should start a new workspace with dotfiles - install script", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--dotfiles", "https://github.com/loft-sh/example-dotfiles", "--dotfiles-script", "install-example") - framework.ExpectNoError(err) - - out, err := f.DevPodSSH(ctx, tempDir, "ls /tmp/worked") - framework.ExpectNoError(err) - - expectedOutput := "/tmp/worked\n" - - framework.ExpectEqual(out, expectedOutput, "should match") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with custom image", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--devcontainer-image", "mcr.microsoft.com/vscode/devcontainers/base:alpine") - framework.ExpectNoError(err) - - out, err := f.DevPodSSH(ctx, tempDir, "grep ^ID= /etc/os-release") - framework.ExpectNoError(err) - - expectedOutput := "ID=alpine\n" - unexpectedOutput := "ID=debian\n" - - framework.ExpectEqual(out, expectedOutput, "should match") - framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") - }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should start a new workspace with custom image and skip building", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-with-multi-stage-build") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--devcontainer-image", "mcr.microsoft.com/vscode/devcontainers/base:alpine") - framework.ExpectNoError(err) - - out, err := f.DevPodSSH(ctx, tempDir, "grep ^ID= /etc/os-release") - framework.ExpectNoError(err) - - expectedOutput := "ID=alpine\n" - unexpectedOutput := "ID=debian\n" - - framework.ExpectEqual(out, expectedOutput, "should match") - framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.Context("should start a workspace from a Dockerfile build", func() { - ginkgo.It("should rebuild image in case of changes in files in build context", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-dockerfile-buildcontext") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - container, err := dockerHelper.FindDevContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) - framework.ExpectNoError(err) - - image1 := container.Config.LegacyImage - - scriptFile, err := os.OpenFile(tempDir+"/scripts/alias.sh", - os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) - framework.ExpectNoError(err) - - defer scriptFile.Close() - - ginkgo.By("Changing a file within the context") - _, err = scriptFile.Write([]byte("alias yr='date +%Y'")) - framework.ExpectNoError(err) - - ginkgo.By("Starting DevPod again with --recreate") - err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") - framework.ExpectNoError(err) - - container, err = dockerHelper.FindDevContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) - framework.ExpectNoError(err) - - image2 := container.Config.LegacyImage - - gomega.Expect(image2).ShouldNot(gomega.Equal(image1), "images should be different") - }, ginkgo.SpecTimeout(60*time.Second)) - ginkgo.It("should not rebuild image for changes in files mentioned in .dockerignore", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-dockerfile-buildcontext") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - container, err := dockerHelper.FindDevContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) - framework.ExpectNoError(err) - - image1 := container.Config.LegacyImage - - scriptFile, err := os.OpenFile(tempDir+"/scripts/install.sh", - os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600) - framework.ExpectNoError(err) - - defer scriptFile.Close() - - ginkgo.By("Changing a file within context") - _, err = scriptFile.Write([]byte("apt install python")) - framework.ExpectNoError(err) - - ginkgo.By("Starting DevPod again with --recreate") - err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") - framework.ExpectNoError(err) - - container, err = dockerHelper.FindDevContainer(ctx, []string{ - fmt.Sprintf("%s=%s", config.DockerIDLabel, workspace.UID), - }) - framework.ExpectNoError(err) - - image2 := container.Config.LegacyImage - - gomega.Expect(image2).Should(gomega.Equal(image1), "image should be same") - }, ginkgo.SpecTimeout(60*time.Second)) - }) - ginkgo.It("should use http headers to download feature", func(ctx context.Context) { - server := ghttp.NewServer() - - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-features-http-headers") - framework.ExpectNoError(err) - - featureArchiveFilePath := path.Join(tempDir, "devcontainer-feature-hello.tgz") - featureFiles := []string{path.Join(tempDir, "devcontainer-feature.json"), path.Join(tempDir, "install.sh")} - err = createTarGzArchive(featureArchiveFilePath, featureFiles) - framework.ExpectNoError(err) - - devContainerFileBuf, err := os.ReadFile(path.Join(tempDir, ".devcontainer.json")) - framework.ExpectNoError(err) - - output := strings.Replace(string(devContainerFileBuf), "#{server_url}", server.URL(), -1) - err = os.WriteFile(path.Join(tempDir, ".devcontainer.json"), []byte(output), 0644) - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - ginkgo.DeferCleanup(server.Close) - - respHeader := http.Header{} - respHeader.Set("Content-Disposition", "attachment; filename=devcontainer-feature-hello.tgz") - - featureArchiveFileBuf, err := os.ReadFile(featureArchiveFilePath) - framework.ExpectNoError(err) - - f := framework.NewDefaultFramework(initialDir + "/bin") - server.AppendHandlers( - ghttp.CombineHandlers( - ghttp.VerifyRequest("GET", "/devcontainer-feature-hello.tgz"), - ghttp.VerifyHeaderKV("Foo-Header", "Foo"), - ghttp.RespondWith(http.StatusOK, featureArchiveFileBuf, respHeader), - ), - ) - - _ = f.DevPodProviderDelete(ctx, "docker") - err = f.DevPodProviderAdd(ctx, "docker") - framework.ExpectNoError(err) - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - server.Close() - }, ginkgo.SpecTimeout(60*time.Second)) - }) - - ginkgo.Context("with docker-compose", func() { - ginkgo.It("should start a new workspace with root folder configuration", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) - - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") - - mount := containerDetail.Mounts[0] - gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) - gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) - gomega.Expect(mount.RW).To(gomega.BeTrue()) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with sub-folder configuration", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-subfolder") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) - - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") - - mount := containerDetail.Mounts[0] - gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) - gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) - gomega.Expect(mount.RW).To(gomega.BeTrue()) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with multiple services", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-multiple-services") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := composeHelper.GetProjectName(workspace.UID) - - appIDs, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(appIDs).To(gomega.HaveLen(1), "app container to be created") - - dbIDs, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "db"), - }) - framework.ExpectNoError(err) - gomega.Expect(dbIDs).To(gomega.HaveLen(1), "db container to be created") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with specific services", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-run-services") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := composeHelper.GetProjectName(workspace.UID) - - appIDs, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(appIDs).To(gomega.HaveLen(1), "app container to be created") - - dbIDs, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "db"), - }) - framework.ExpectNoError(err) - gomega.Expect(dbIDs).To(gomega.BeEmpty(), "db container not to be created") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with .devcontainer docker-compose overrides", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-overrides") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := composeHelper.GetProjectName(workspace.UID) - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, projectName), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) - - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.Mounts).To(gomega.HaveLen(1), "1 container volume mount") - - mount := containerDetail.Mounts[0] - gomega.Expect(mount.Source).To(gomega.Equal(tempDir)) - gomega.Expect(mount.Destination).To(gomega.Equal("/workspaces")) - gomega.Expect(mount.RW).To(gomega.BeTrue()) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with container environment variables set", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-container-env") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - err = f.ExecCommand(ctx, true, true, "BAR", []string{"ssh", "--command", "echo $FOO", projectName}) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with container user", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-container-user") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - err = f.ExecCommand(ctx, true, true, "root", []string{"ssh", "--command", "ps u -p 1", projectName}) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with privileged", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-privileged") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) - - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.HostConfig.Privileged).To(gomega.BeTrue(), "container run with privileged true") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with capAdd", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-capadd") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) - - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.HostConfig.CapAdd).To(gomega.ContainElement("SYS_PTRACE"), "image capabilities are not duplicated") - gomega.Expect(containerDetail.HostConfig.CapAdd).To(gomega.ContainElement("NET_ADMIN"), "devcontainer configuration can add capabilities") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with securityOpt", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-securityOpt") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) - - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.HostConfig.SecurityOpt).To(gomega.ContainElement("seccomp=unconfined"), "securityOpts contain seccomp=unconfined") - gomega.Expect(containerDetail.HostConfig.SecurityOpt).To(gomega.ContainElement("apparmor=unconfined"), "securityOpts contain apparmor=unconfined") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with override command", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-override-command") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - var containerDetails []types.ContainerJSON - err = dockerHelper.Inspect(ctx, ids, "container", &containerDetails) - framework.ExpectNoError(err) - - containerDetail := containerDetails[0] - gomega.Expect(containerDetail.Config.Entrypoint).NotTo(gomega.ContainElement("bash"), "overrides container entry point") - gomega.Expect(containerDetail.Config.Cmd).To(gomega.BeEmpty(), "overrides container command") - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with remote env", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-remote-env") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - err = f.ExecCommand(ctx, true, true, "/home/vscode/remote-env.out", []string{"ssh", "--command", "ls $HOME/remote-env.out", projectName}) - framework.ExpectNoError(err) - - err = f.ExecCommand(ctx, true, true, "BAR", []string{"ssh", "--command", "cat $HOME/remote-env.out", projectName}) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with remote user", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-remote-user") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - err = f.ExecCommand(ctx, true, true, "root", []string{"ssh", "--command", "cat $HOME/remote-user.out", projectName}) - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace and substitute devcontainer.json variables", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-variables") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - devContainerID, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/dev-container-id.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(devContainerID).NotTo(gomega.BeEmpty()) - - containerEnvPath, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-env-path.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerEnvPath).To(gomega.ContainSubstring("/usr/local/bin")) - - localEnvHome, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-env-home.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localEnvHome).To(gomega.Equal(os.Getenv("HOME"))) - - localWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localWorkspaceFolder).To(gomega.Equal(tempDir)) - - localWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/local-workspace-folder-basename.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(localWorkspaceFolderBasename).To(gomega.Equal(filepath.Base(tempDir))) - - containerWorkspaceFolder, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerWorkspaceFolder).To(gomega.Equal("/workspaces")) - - containerWorkspaceFolderBasename, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/container-workspace-folder-basename.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(containerWorkspaceFolderBasename).To(gomega.Equal("workspaces")) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with mounts", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-mounts") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - // Check for docker-compose container running - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - _, _, err = f.ExecCommandCapture(ctx, []string{"ssh", "--command", "touch /home/vscode/mnt1/foo.txt", projectName, "--user", "root"}) - framework.ExpectNoError(err) - - _, _, err = f.ExecCommandCapture(ctx, []string{"ssh", "--command", "echo -n BAR > /home/vscode/mnt1/foo.txt", projectName, "--user", "root"}) - framework.ExpectNoError(err) - - foo, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt1/foo.txt", projectName}) - framework.ExpectNoError(err) - gomega.Expect(foo).To(gomega.Equal("BAR")) - - bar, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/mnt2/bar.txt", projectName}) - framework.ExpectNoError(err) - gomega.Expect(bar).To(gomega.Equal("FOO")) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with multistage build", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-with-multi-stage-build") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Wait for devpod workspace to come online (deadline: 30s) - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - }, ginkgo.SpecTimeout(180*time.Second)) - - ginkgo.It("should start a new workspace with host:port forwardPorts", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-forward-ports") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - // Check for docker-compose container running - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - done := make(chan error) - - sshContext, sshCancel := context.WithCancel(context.Background()) - go func() { - cmd := exec.CommandContext(sshContext, "ssh", projectName+".devpod", "sleep", "10") - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Start(); err != nil { - done <- err - return - } - - if err := cmd.Wait(); err != nil { - done <- err - return - } - - done <- nil - }() - - gomega.Eventually(func(g gomega.Gomega) { - response, err := http.Get("http://localhost:8080") - g.Expect(err).NotTo(gomega.HaveOccurred()) - - body, err := io.ReadAll(response.Body) - g.Expect(err).NotTo(gomega.HaveOccurred()) - g.Expect(body).To(gomega.ContainSubstring("Thank you for using nginx.")) - }). - WithPolling(1 * time.Second). - WithTimeout(20 * time.Second). - Should(gomega.Succeed()) - - sshCancel() - err = <-done - - gomega.Expect(err).To(gomega.Or( - gomega.MatchError("signal: killed"), - gomega.MatchError(context.Canceled), - )) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with features", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-features") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir, "--debug") - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - vclusterVersionOutput, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "vcluster --version", projectName}) - framework.ExpectNoError(err) - gomega.Expect(vclusterVersionOutput).To(gomega.ContainSubstring("vcluster version 0.15.2")) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.It("should start a new workspace with env-file", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-env-file") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - devPodUpOutput, _, err := f.ExecCommandCapture(ctx, []string{"up", "--debug", "--ide", "none", tempDir}) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - gomega.Expect(devPodUpOutput).NotTo(gomega.ContainSubstring("Defaulting to a blank string.")) - }, ginkgo.SpecTimeout(60*time.Second)) - - ginkgo.Context("with lifecycle commands", func() { - ginkgo.It("should start a new workspace and execute array based lifecycle commands", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-lifecycle-array") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - projectName := workspace.ID - - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - initializeCommand, err := os.ReadFile(filepath.Join(tempDir, "initialize-command.out")) - framework.ExpectNoError(err) - gomega.Expect(initializeCommand).To(gomega.ContainSubstring("initializeCommand")) - - onCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/on-create-command.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(onCreateCommand).To(gomega.ContainSubstring("onCreateCommand")) - - updateContentCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/update-content-command.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(updateContentCommand).To(gomega.Equal("updateContentCommand")) - - postCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-create-command.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(postCreateCommand).To(gomega.Equal("postCreateCommand")) - - postStartCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-start-command.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(postStartCommand).To(gomega.Equal("postStartCommand")) - - postAttachCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-attach-command.out", projectName}) - framework.ExpectNoError(err) - gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) - }, ginkgo.SpecTimeout(60*time.Second)) - - //ginkgo.FIt("should start a new workspace and execute object based lifecycle commands", func(ctx context.Context) { - // tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-lifecycle-object") - // framework.ExpectNoError(err) - // ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - // - // f := framework.NewDefaultFramework(initialDir + "/bin") - // _ = f.DevPodProviderAdd(ctx, "docker"}) - // err = f.DevPodProviderUse(context.Background(), "docker") - // framework.ExpectNoError(err) - // - // err = f.DevPodUp(ctx, tempDir) - // framework.ExpectNoError(err) - // - // // Check for docker-compose container running - // projectName := composeHelper.ToProjectName(filepath.Base(tempDir)) - // ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), projectName) - // - // ids, err := dockerHelper.FindContainer(ctx, []string{ - // fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - // fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - // }) - // framework.ExpectNoError(err) - // gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - // - // initializeCommand, err := os.ReadFile(filepath.Join(tempDir, "initialize-command.out")) - // framework.ExpectNoError(err) - // gomega.Expect(initializeCommand).To(gomega.ContainSubstring("initializeCommand")) - // - // onCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/on-create-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(onCreateCommand).To(gomega.ContainSubstring("onCreateCommand")) - // - // updateContentCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/update-content-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(updateContentCommand).To(gomega.Equal("updateContentCommand")) - // - // postCreateCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-create-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(postCreateCommand).To(gomega.Equal("postCreateCommand")) - // - // postStartCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-start-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(postStartCommand).To(gomega.Equal("postStartCommand")) - // - // postAttachCommand, _, err := f.ExecCommandCapture(ctx, []string{"ssh", "--command", "cat $HOME/post-attach-command.out", projectName}) - // framework.ExpectNoError(err) - // gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) - //}, ginkgo.SpecTimeout(60*time.Second)) - }) - - ginkgo.Context("with --recreate", func() { - ginkgo.It("should NOT delete container when rebuild fails", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-rebuild-fail") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - ginkgo.By("Starting DevPod") - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ginkgo.By("Should start a docker-compose container") - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - ginkgo.By("Modifying .devcontainer.json with failing changes") - origPath := filepath.Join(tempDir, ".devcontainer.json") - err = os.Remove(origPath) - framework.ExpectNoError(err) - - failingConfig, err := os.Open(filepath.Join(tempDir, "fail.devcontainer.json")) - framework.ExpectNoError(err) - - newConfig, err := os.Create(origPath) - framework.ExpectNoError(err) - - _, err = io.Copy(newConfig, failingConfig) - framework.ExpectNoError(err) - - ginkgo.By("Starting DevPod again with --recreate") - err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") - framework.ExpectError(err) - - ginkgo.By("Should leave original container running") - ids2, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids2[0]).To(gomega.Equal(ids[0]), "Should use original container") - }) - - ginkgo.It("should delete container upon successful rebuild", func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker-compose-rebuild-success") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(ctx, "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - ginkgo.By("Starting DevPod") - err = f.DevPodUp(ctx, tempDir) - framework.ExpectNoError(err) - - workspace, err := f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - ginkgo.By("Should start a docker-compose container") - ids, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids).To(gomega.HaveLen(1), "1 compose container to be created") - - ginkgo.By("Starting DevPod again with --recreate") - err = f.DevPodUp(ctx, tempDir, "--debug", "--recreate") - framework.ExpectNoError(err) - - ginkgo.By("Should start a new docker-compose container on rebuild") - ids2, err := dockerHelper.FindContainer(ctx, []string{ - fmt.Sprintf("%s=%s", compose.ProjectLabel, composeHelper.GetProjectName(workspace.UID)), - fmt.Sprintf("%s=%s", compose.ServiceLabel, "app"), - }) - framework.ExpectNoError(err) - gomega.Expect(ids2[0]).NotTo(gomega.Equal(ids[0]), "Should restart container") - }) - }) - }) - }) }) }) From d3ebf20644ff6e46c99c6886ba9723cb001bcc21 Mon Sep 17 00:00:00 2001 From: Luca Di Maio Date: Fri, 17 Nov 2023 16:50:20 +0100 Subject: [PATCH 3/3] rebase on main Signed-off-by: Luca Di Maio --- .github/workflows/e2e-tests.yaml | 2 +- e2e/tests/ssh/ssh.go | 180 +++++++++++++++---------------- 2 files changed, 91 insertions(+), 91 deletions(-) diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index 3e1f112a3..f112bd770 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: true - max-parallel: 8 + max-parallel: 16 matrix: label: ["build", "ide", "integration", "machine", "machineprovider", "provider", "proxyprovider", "ssh", "up", "up-docker", "up-podman", "up-docker-compose"] diff --git a/e2e/tests/ssh/ssh.go b/e2e/tests/ssh/ssh.go index 10b1b71fc..5d40306fc 100644 --- a/e2e/tests/ssh/ssh.go +++ b/e2e/tests/ssh/ssh.go @@ -80,101 +80,101 @@ var _ = DevPodDescribe("devpod ssh test suite", func() { err = f.DevPodSSHGpgTestKey(devpodSSHCtx, tempDir) framework.ExpectNoError(err) }) - }) - ginkgo.It("should start a new workspace with a docker provider (default) and forward a port into it", func() { - tempDir, err := framework.CopyToTempDir("tests/ssh/testdata/forward-test") - framework.ExpectNoError(err) - defer framework.CleanupTempDir(initialDir, tempDir) - - f := framework.NewDefaultFramework(initialDir + "/bin") - _ = f.DevPodProviderAdd(ctx, "docker") - err = f.DevPodProviderUse(context.Background(), "docker") - framework.ExpectNoError(err) - - ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) - - // Create a new random number generator with a custom seed (e.g., current time) - source := rand.NewSource(time.Now().UnixNano()) - rng := rand.New(source) - - // Start up devpod workspace - devpodUpDeadline := time.Now().Add(5 * time.Minute) - devpodUpCtx, cancel := context.WithDeadline(context.Background(), devpodUpDeadline) - defer cancel() - err = f.DevPodUp(devpodUpCtx, tempDir) - framework.ExpectNoError(err) - - // Generate a random number for the server port between 50000 and 51000 - port := rng.Intn(1000) + 50000 - - fmt.Println("Running netcat server on port", port) - - devpodSSHDeadline := time.Now().Add(20 * time.Second) - devpodSSHCtx, cancelSSH := context.WithDeadline(context.Background(), devpodSSHDeadline) - defer cancelSSH() - - fmt.Println("Starting pong service") - // start a ping/pong service on the port - cmd := exec.CommandContext(ctx, f.DevpodBinDir+"/"+f.DevpodBinName, - []string{ - "ssh", tempDir, "--command", - "sh -c \"while true; do echo PONG | nc -n -lk -p " + strconv.Itoa(port) + "; done\"", - }..., - ) - err = cmd.Start() - framework.ExpectNoError(err) - - fmt.Println("Forwarding port", port) - // start ssh with port forwarding in background - go func() { - _ = f.DevpodPortTest(devpodSSHCtx, strconv.Itoa(port), tempDir) - }() - - fmt.Println("Waiting for port", port, "to be open") - // wait for port to be open - retries := 5 - out := "" - address := net.JoinHostPort("localhost", strconv.Itoa(port)) - for retries > 0 { - fmt.Println("retries left", retries) - - // wait 3s between retries - time.Sleep(time.Second * 3) - - conn, err := net.Dial("tcp", address) - if err != nil { - fmt.Println("port still closed") - retries = retries - 1 - } else { - if conn != nil { - defer conn.Close() - fmt.Println("connecting to", port) - - fmt.Println("sending PING") - _, err = conn.Write([]byte("PING")) - framework.ExpectNoError(err) - - fmt.Println("waiting for response") - out, err = bufio.NewReader(conn).ReadString('\n') - if err != nil { - fmt.Println("invalid response") - retries = retries - 1 - } else { - fmt.Println("received", out) + ginkgo.It("should start a new workspace with a docker provider (default) and forward a port into it", func() { + tempDir, err := framework.CopyToTempDir("tests/ssh/testdata/forward-test") + framework.ExpectNoError(err) + defer framework.CleanupTempDir(initialDir, tempDir) - break - } - } else { - fmt.Println("invalid connection") + f := framework.NewDefaultFramework(initialDir + "/bin") + _ = f.DevPodProviderAdd(ctx, "docker") + err = f.DevPodProviderUse(context.Background(), "docker") + framework.ExpectNoError(err) + + ginkgo.DeferCleanup(f.DevPodWorkspaceDelete, context.Background(), tempDir) + + // Create a new random number generator with a custom seed (e.g., current time) + source := rand.NewSource(time.Now().UnixNano()) + rng := rand.New(source) + + // Start up devpod workspace + devpodUpDeadline := time.Now().Add(5 * time.Minute) + devpodUpCtx, cancel := context.WithDeadline(context.Background(), devpodUpDeadline) + defer cancel() + err = f.DevPodUp(devpodUpCtx, tempDir) + framework.ExpectNoError(err) + + // Generate a random number for the server port between 50000 and 51000 + port := rng.Intn(1000) + 50000 + + fmt.Println("Running netcat server on port", port) + + devpodSSHDeadline := time.Now().Add(20 * time.Second) + devpodSSHCtx, cancelSSH := context.WithDeadline(context.Background(), devpodSSHDeadline) + defer cancelSSH() + + fmt.Println("Starting pong service") + // start a ping/pong service on the port + cmd := exec.CommandContext(ctx, f.DevpodBinDir+"/"+f.DevpodBinName, + []string{ + "ssh", tempDir, "--command", + "sh -c \"while true; do echo PONG | nc -n -lk -p " + strconv.Itoa(port) + "; done\"", + }..., + ) + err = cmd.Start() + framework.ExpectNoError(err) + + fmt.Println("Forwarding port", port) + // start ssh with port forwarding in background + go func() { + _ = f.DevpodPortTest(devpodSSHCtx, strconv.Itoa(port), tempDir) + }() + + fmt.Println("Waiting for port", port, "to be open") + // wait for port to be open + retries := 5 + out := "" + address := net.JoinHostPort("localhost", strconv.Itoa(port)) + for retries > 0 { + fmt.Println("retries left", retries) + + // wait 3s between retries + time.Sleep(time.Second * 3) + + conn, err := net.Dial("tcp", address) + if err != nil { + fmt.Println("port still closed") retries = retries - 1 + } else { + if conn != nil { + defer conn.Close() + fmt.Println("connecting to", port) + + fmt.Println("sending PING") + _, err = conn.Write([]byte("PING")) + framework.ExpectNoError(err) + + fmt.Println("waiting for response") + out, err = bufio.NewReader(conn).ReadString('\n') + if err != nil { + fmt.Println("invalid response") + retries = retries - 1 + } else { + fmt.Println("received", out) + + break + } + } else { + fmt.Println("invalid connection") + retries = retries - 1 + } } } - } - framework.ExpectNotEqual(retries, 0) + framework.ExpectNotEqual(retries, 0) - fmt.Println("Verifying output match") - framework.ExpectEqual(out, "PONG\n") - fmt.Println("Success") + fmt.Println("Verifying output match") + framework.ExpectEqual(out, "PONG\n") + fmt.Println("Success") + }) }) })