From 6236b6ed4099d3ec3836b674ec23dfdcf4b6dec1 Mon Sep 17 00:00:00 2001 From: Luca Di Maio Date: Mon, 4 Dec 2023 12:54:02 +0100 Subject: [PATCH] tests: add windows self hosted runner, add win tests in pipeline Signed-off-by: Luca Di Maio --- .github/workflows/e2e-tests.yaml | 45 ++++++- .github/workflows/e2e-win-fill-tests.yaml | 53 ++++++++ e2e/framework/util.go | 2 +- e2e/tests/up/docker.go | 123 ----------------- e2e/tests/up/docker_build.go | 153 ++++++++++++++++++++++ e2e/tests/up/docker_compose.go | 113 ---------------- e2e/tests/up/docker_compose_build.go | 148 +++++++++++++++++++++ 7 files changed, 398 insertions(+), 239 deletions(-) create mode 100644 .github/workflows/e2e-win-fill-tests.yaml create mode 100644 e2e/tests/up/docker_build.go create mode 100644 e2e/tests/up/docker_compose_build.go diff --git a/.github/workflows/e2e-tests.yaml b/.github/workflows/e2e-tests.yaml index 9ac501e13..432b0ba07 100644 --- a/.github/workflows/e2e-tests.yaml +++ b/.github/workflows/e2e-tests.yaml @@ -1,6 +1,8 @@ name: E2E tests on: + release: + types: [prereleased] pull_request: branches: - main @@ -20,7 +22,6 @@ env: GO111MODULE: on GOFLAGS: -mod=vendor -# windows blocklist: "up-podman", "integration", "machineprovider", "up" jobs: test-e2e: runs-on: ubuntu-latest @@ -28,7 +29,7 @@ jobs: fail-fast: true max-parallel: 16 matrix: - label: ["build", "ide", "integration", "machine", "machineprovider", "provider", "proxyprovider", "ssh", "up", "up-docker", "up-podman", "up-docker-compose"] + label: ["build", "ide", "integration", "machine", "machineprovider", "provider", "proxyprovider", "ssh", "up", "up-docker", "up-podman", "up-docker-compose", "up-docker-build", "up-docker-compose-build"] steps: - name: Checkout repo @@ -63,3 +64,43 @@ jobs: working-directory: ./e2e run: | sudo KUBECONFIG=/home/runner/.kube/config go test -v -ginkgo.v -timeout 3600s --ginkgo.label-filter=${{ matrix.label }} + + test-e2e-windows: + runs-on: self-hosted-windows + # We run this only on PRs, for pre-releases we run the full separate workflow + if: ${{ github.event_name == 'pull_request' }} + strategy: + fail-fast: true + max-parallel: 1 + matrix: + label: ["build", "ide", "ssh", "up-docker", "up-docker-build", "up-docker-compose" ] + + steps: + - name: Git set line ending + run: | + git config --global core.autocrlf false + + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.20.5 + + - name: Build binary and copy to the E2E directory + run: | + go build -ldflags "-s -w" -o devpod-windows-amd64.exe + mkdir e2e\bin + cp devpod-windows-amd64.exe e2e\bin\ + + - name: E2E test + working-directory: .\e2e + run: | + go run github.com/onsi/ginkgo/v2/ginkgo -r -p --timeout=3600s --label-filter=${{ matrix.label }} + + - name: Container cleanup + if: ${{ always() }} + run: | + Remove-Item -Recurse C:\Users\loft-user\.devpod\ + sh -c "docker ps -a | cut -d' ' -f1 | tail -n+2 | xargs docker rm -f || :" diff --git a/.github/workflows/e2e-win-fill-tests.yaml b/.github/workflows/e2e-win-fill-tests.yaml new file mode 100644 index 000000000..226ed793d --- /dev/null +++ b/.github/workflows/e2e-win-fill-tests.yaml @@ -0,0 +1,53 @@ +name: E2E Win full tests + +on: + release: + types: [prereleased] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + GO111MODULE: on + GOFLAGS: -mod=vendor + +jobs: + test-e2e-windows: + runs-on: self-hosted-windows + strategy: + fail-fast: true + max-parallel: 1 + matrix: + # windows blocklist: "up-podman", "integration", "machineprovider", "proxyprovider", "up" + label: ["build", "ide", "machine", "provider", "ssh", "up-docker", "up-docker-compose", "up-docker-build", "up-docker-compose-build"] + + steps: + - name: Git set line ending + run: | + git config --global core.autocrlf false + + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.20.5 + + - name: Build binary and copy to the E2E directory + run: | + go build -ldflags "-s -w" -o devpod-windows-amd64.exe + mkdir e2e\bin + cp devpod-windows-amd64.exe e2e\bin\ + + - name: E2E test + working-directory: .\e2e + run: | + go run github.com/onsi/ginkgo/v2/ginkgo -r -p --timeout=3600s --label-filter=${{ matrix.label }} + + - name: Container cleanup + if: ${{ always() }} + run: | + Remove-Item -Recurse C:\Users\loft-user\.devpod\ + sh -c "docker ps -a | cut -d' ' -f1 | tail -n+2 | xargs docker rm -f || :" diff --git a/e2e/framework/util.go b/e2e/framework/util.go index 369620faf..c7241ab8f 100644 --- a/e2e/framework/util.go +++ b/e2e/framework/util.go @@ -13,7 +13,7 @@ import ( func GetTimeout() time.Duration { if runtime.GOOS == "windows" { - return 300 * time.Second + return 600 * time.Second } return 60 * time.Second diff --git a/e2e/tests/up/docker.go b/e2e/tests/up/docker.go index 3bc9b0f53..c695492ef 100644 --- a/e2e/tests/up/docker.go +++ b/e2e/tests/up/docker.go @@ -141,23 +141,6 @@ var _ = DevPodDescribe("devpod up test suite", func() { gomega.Expect(bar).To(gomega.Equal("FOO")) }, ginkgo.SpecTimeout(framework.GetTimeout())) - 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(framework.GetTimeout()*3)) - 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") @@ -288,112 +271,6 @@ var _ = DevPodDescribe("devpod up test suite", func() { framework.ExpectNotEqual(out, unexpectedOutput, "should NOT match") }, ginkgo.SpecTimeout(framework.GetTimeout())) - 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(framework.GetTimeout())) - 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(framework.GetTimeout())) - }) ginkgo.It("should use http headers to download feature", func(ctx context.Context) { server := ghttp.NewServer() diff --git a/e2e/tests/up/docker_build.go b/e2e/tests/up/docker_build.go new file mode 100644 index 000000000..c8cceb69c --- /dev/null +++ b/e2e/tests/up/docker_build.go @@ -0,0 +1,153 @@ +package up + +import ( + "context" + "fmt" + "os" + + "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" +) + +var _ = DevPodDescribe("devpod up test suite", func() { + ginkgo.Context("testing up command", ginkgo.Label("up-docker-build"), 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 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(framework.GetTimeout()*3)) + 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(framework.GetTimeout())) + 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(framework.GetTimeout())) + }) + }) + }) +}) diff --git a/e2e/tests/up/docker_compose.go b/e2e/tests/up/docker_compose.go index eaf9c3957..2fed224ff 100644 --- a/e2e/tests/up/docker_compose.go +++ b/e2e/tests/up/docker_compose.go @@ -574,23 +574,6 @@ var _ = DevPodDescribe("devpod up test suite", func() { gomega.Expect(bar).To(gomega.Equal("FOO")) }, ginkgo.SpecTimeout(framework.GetTimeout())) - 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(framework.GetTimeout()*3)) - 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) @@ -820,102 +803,6 @@ var _ = DevPodDescribe("devpod up test suite", func() { // gomega.Expect(postAttachCommand).To(gomega.Equal("postAttachCommand")) //}, ginkgo.SpecTimeout(framework.GetTimeout())) }) - - 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/docker_compose_build.go b/e2e/tests/up/docker_compose_build.go new file mode 100644 index 000000000..8e401d931 --- /dev/null +++ b/e2e/tests/up/docker_compose_build.go @@ -0,0 +1,148 @@ +package up + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + + "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-build"), 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 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(framework.GetTimeout()*3)) + + 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") + }) + }) + }) + }) +})