diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index 37cda2893..58726af3e 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -399,6 +399,16 @@ jobs: job-timeout-minutes: 20 flake-attempts: 2 + - label: up-provider-podman-rootless-exec + runner: ubuntu-latest + free-disk-space: false + install-kind: false + requires-secret: false + install-podman: rootless + test-timeout: 600s + job-timeout-minutes: 20 + flake-attempts: 2 + - label: up-provider-podman-rootless-lifecycle runner: ubuntu-latest free-disk-space: true @@ -449,6 +459,16 @@ jobs: job-timeout-minutes: 20 flake-attempts: 2 + - label: up-provider-podman-rootful-lifecycle-2 + runner: ubuntu-latest + free-disk-space: true + install-kind: false + requires-secret: false + install-podman: rootful + test-timeout: 600s + job-timeout-minutes: 20 + flake-attempts: 2 + - label: up-provider-podman-rootful-config runner: ubuntu-latest free-disk-space: true diff --git a/e2e/tests/up/provider_podman_rootful_lifecycle.go b/e2e/tests/up/provider_podman_rootful_lifecycle.go index f050289b2..0412b75e8 100644 --- a/e2e/tests/up/provider_podman_rootful_lifecycle.go +++ b/e2e/tests/up/provider_podman_rootful_lifecycle.go @@ -4,7 +4,6 @@ import ( "context" "os" "os/exec" - "path/filepath" "strings" "time" @@ -220,119 +219,6 @@ var _ = ginkgo.Describe( }, ginkgo.SpecTimeout(framework.TimeoutShort()), ) - - ginkgo.It( - "should run postAttachCommand on every attach", - func(ctx context.Context) { - tempDir, err := setupWorkspace( - "tests/up/testdata/docker-post-attach-every-time", - initialDir, - f, - ) - framework.ExpectNoError(err) - - err = f.DevsyUp(ctx, tempDir) - framework.ExpectNoError(err) - - gomega.Eventually(func() string { - out, err := f.DevsySSH( - ctx, tempDir, "cat $HOME/attach-count.out 2>/dev/null", - ) - if err != nil { - return "" - } - return strings.TrimSpace(out) - }).WithTimeout(15 * time.Second).WithPolling(1 * time.Second).Should( - gomega.Equal("1"), - ) - - err = f.DevsyUp(ctx, tempDir) - framework.ExpectNoError(err) - - gomega.Eventually(func() string { - out, err := f.DevsySSH( - ctx, tempDir, "cat $HOME/attach-count.out 2>/dev/null", - ) - if err != nil { - return "" - } - return strings.TrimSpace(out) - }).WithTimeout(15 * time.Second).WithPolling(1 * time.Second).Should( - gomega.Equal("2"), - ) - }, - ginkgo.SpecTimeout( - framework.TimeoutModerate(), - ), - ) - - ginkgo.It( - "should run initializeCommand with object syntax", - func(ctx context.Context) { - tempDir, err := setupWorkspaceAndUp( - ctx, - "tests/up/testdata/docker-initcmd-parallel", - initialDir, - f, - ) - framework.ExpectNoError(err) - - one, err := os.ReadFile( //nolint:gosec // G304 - filepath.Join(tempDir, "init-cmd-one.out"), - ) - framework.ExpectNoError(err) - gomega.Expect(string(one)).To(gomega.Equal("initCmdOne")) - - two, err := os.ReadFile( //nolint:gosec // G304 - filepath.Join(tempDir, "init-cmd-two.out"), - ) - framework.ExpectNoError(err) - gomega.Expect(string(two)).To(gomega.Equal("initCmdTwo")) - }, - ginkgo.SpecTimeout(framework.TimeoutShort()), - ) - - ginkgo.It( - "should inject secrets-file env into lifecycle commands", - func(ctx context.Context) { - tempDir, err := setupWorkspace( - "tests/up/testdata/docker-secrets-file", - initialDir, - f, - ) - framework.ExpectNoError(err) - - secretsDir, err := framework.CreateTempDir() - framework.ExpectNoError(err) - ginkgo.DeferCleanup(func() { _ = os.RemoveAll(secretsDir) }) - - secretsFile := filepath.Join(secretsDir, "secrets.json") - err = os.WriteFile( - secretsFile, - []byte( - `{"MY_SECRET":"test-value-12345","ANOTHER_SECRET":"second-secret-42"}`, - ), - 0o600, - ) - framework.ExpectNoError(err) - - err = f.DevsyUp(ctx, tempDir, "--secrets-file", secretsFile) - framework.ExpectNoError(err) - - out, err := f.DevsySSH(ctx, tempDir, "cat /tmp/secret-check.out") - framework.ExpectNoError(err) - gomega.Expect(strings.TrimSpace(out)). - To(gomega.Equal("test-value-12345")) - - out, err = f.DevsySSH( - ctx, tempDir, "cat /tmp/another-secret-check.out", - ) - framework.ExpectNoError(err) - gomega.Expect(strings.TrimSpace(out)). - To(gomega.Equal("second-secret-42")) - }, - ginkgo.SpecTimeout(framework.TimeoutShort()), - ) }) }) }, diff --git a/e2e/tests/up/provider_podman_rootful_lifecycle_2.go b/e2e/tests/up/provider_podman_rootful_lifecycle_2.go new file mode 100644 index 000000000..21f6db91c --- /dev/null +++ b/e2e/tests/up/provider_podman_rootful_lifecycle_2.go @@ -0,0 +1,187 @@ +package up + +import ( + "context" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + + "github.com/devsy-org/devsy/e2e/framework" + "github.com/devsy-org/devsy/pkg/docker" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +var _ = ginkgo.Describe( + "testing up command for podman provider", + ginkgo.Label("up-provider-podman-rootful-lifecycle-2"), + func() { + var initialDir string + + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) + }) + + ginkgo.Context("with rootful podman", func() { + var f *framework.Framework + + //nolint:dupl // shared rootful podman wrapper setup across split files + ginkgo.BeforeEach(func(ctx context.Context) { + wrapper, err := os.Create( //nolint:gosec // G304: test-controlled path + initialDir + "/bin/podman-rootful", + ) + framework.ExpectNoError(err) + + _, err = wrapper.WriteString("#!/bin/sh\nsudo podman \"$@\"\n") + if err != nil { + _ = wrapper.Close() + framework.ExpectNoError(err) + } + + err = wrapper.Close() + framework.ExpectNoError(err) + + // #nosec G302 -- wrapper script needs execute permission + err = os.Chmod(initialDir+"/bin/podman-rootful", 0o755) + framework.ExpectNoError(err) + + cmd := exec.CommandContext( //nolint:gosec // G204: test-controlled path + ctx, initialDir+"/bin/podman-rootful", "ps", + ) + docker.PrepareForGroupCancellation(cmd) + out, err := cmd.CombinedOutput() + framework.ExpectNoError(err, string(out)) + + ginkgo.DeferCleanup(func() { + _ = os.Remove(initialDir + "/bin/podman-rootful") + }) + + f, err = setupDockerProvider( + initialDir+"/bin", + initialDir+"/bin/podman-rootful", + ) + framework.ExpectNoError(err) + }, + ) + + ginkgo.Context("lifecycle commands", func() { //nolint:dupl + ginkgo.It( + "should run postAttachCommand on every attach", + func(ctx context.Context) { + tempDir, err := setupWorkspace( + "tests/up/testdata/docker-post-attach-every-time", + initialDir, + f, + ) + framework.ExpectNoError(err) + + err = f.DevsyUp(ctx, tempDir) + framework.ExpectNoError(err) + + gomega.Eventually(func() string { + out, err := f.DevsySSH( + ctx, tempDir, "cat $HOME/attach-count.out 2>/dev/null", + ) + if err != nil { + return "" + } + return strings.TrimSpace(out) + }).WithTimeout(15 * time.Second).WithPolling(1 * time.Second).Should( + gomega.Equal("1"), + ) + + err = f.DevsyUp(ctx, tempDir) + framework.ExpectNoError(err) + + gomega.Eventually(func() string { + out, err := f.DevsySSH( + ctx, tempDir, "cat $HOME/attach-count.out 2>/dev/null", + ) + if err != nil { + return "" + } + return strings.TrimSpace(out) + }).WithTimeout(15 * time.Second).WithPolling(1 * time.Second).Should( + gomega.Equal("2"), + ) + }, + ginkgo.SpecTimeout( + framework.TimeoutModerate(), + ), + ) + + ginkgo.It( + "should run initializeCommand with object syntax", + func(ctx context.Context) { + tempDir, err := setupWorkspaceAndUp( + ctx, + "tests/up/testdata/docker-initcmd-parallel", + initialDir, + f, + ) + framework.ExpectNoError(err) + + one, err := os.ReadFile( //nolint:gosec // G304 + filepath.Join(tempDir, "init-cmd-one.out"), + ) + framework.ExpectNoError(err) + gomega.Expect(string(one)).To(gomega.Equal("initCmdOne")) + + two, err := os.ReadFile( //nolint:gosec // G304 + filepath.Join(tempDir, "init-cmd-two.out"), + ) + framework.ExpectNoError(err) + gomega.Expect(string(two)).To(gomega.Equal("initCmdTwo")) + }, + ginkgo.SpecTimeout(framework.TimeoutShort()), + ) + + ginkgo.It( //nolint:dupl // mirrors rootless lifecycle secrets-file test + "should inject secrets-file env into lifecycle commands", + func(ctx context.Context) { + tempDir, err := setupWorkspace( + "tests/up/testdata/docker-secrets-file", + initialDir, + f, + ) + framework.ExpectNoError(err) + + secretsDir, err := framework.CreateTempDir() + framework.ExpectNoError(err) + ginkgo.DeferCleanup(func() { _ = os.RemoveAll(secretsDir) }) + + secretsFile := filepath.Join(secretsDir, "secrets.json") + err = os.WriteFile( + secretsFile, + []byte( + `{"MY_SECRET":"test-value-12345","ANOTHER_SECRET":"second-secret-42"}`, + ), + 0o600, + ) + framework.ExpectNoError(err) + + err = f.DevsyUp(ctx, tempDir, "--secrets-file", secretsFile) + framework.ExpectNoError(err) + + out, err := f.DevsySSH(ctx, tempDir, "cat /tmp/secret-check.out") + framework.ExpectNoError(err) + gomega.Expect(strings.TrimSpace(out)). + To(gomega.Equal("test-value-12345")) + + out, err = f.DevsySSH( + ctx, tempDir, "cat /tmp/another-secret-check.out", + ) + framework.ExpectNoError(err) + gomega.Expect(strings.TrimSpace(out)). + To(gomega.Equal("second-secret-42")) + }, + ginkgo.SpecTimeout(framework.TimeoutShort()), + ) + }) + }) + }, +) diff --git a/e2e/tests/up/provider_podman_rootless_basic.go b/e2e/tests/up/provider_podman_rootless_basic.go index 9ed047aca..88e5ceefb 100644 --- a/e2e/tests/up/provider_podman_rootless_basic.go +++ b/e2e/tests/up/provider_podman_rootless_basic.go @@ -107,69 +107,6 @@ var _ = ginkgo.Describe( ginkgo.SpecTimeout(framework.TimeoutShort()), ) }) - - ginkgo.Context("agent delivery", func() { - ginkgo.It( - "should deliver the agent binary and execute SSH commands", - func(ctx context.Context) { - tempDir, err := setupWorkspace("tests/up/testdata/docker", initialDir, f) - framework.ExpectNoError(err) - - err = f.DevsyUp(ctx, tempDir) - framework.ExpectNoError(err) - - err = f.DevsySSHEchoTestString(ctx, tempDir) - framework.ExpectNoError(err) - }, - ginkgo.SpecTimeout(framework.TimeoutShort()), - ) - }) - - ginkgo.Context("exec", func() { - ginkgo.It( - "should execute commands inside the container via SSH", - func(ctx context.Context) { - tempDir, err := setupWorkspace("tests/up/testdata/docker", initialDir, f) - framework.ExpectNoError(err) - - err = f.DevsyUp(ctx, tempDir) - framework.ExpectNoError(err) - - out, err := f.DevsySSH(ctx, tempDir, "echo -n hello-podman") - framework.ExpectNoError(err) - framework.ExpectEqual(out, "hello-podman") - - out, err = f.DevsySSH(ctx, tempDir, "pwd") - framework.ExpectNoError(err) - gomega.Expect(strings.TrimSpace(out)).NotTo(gomega.BeEmpty()) - }, - ginkgo.SpecTimeout(framework.TimeoutShort()), - ) - }) - - ginkgo.Context("cleanup", func() { - ginkgo.It( - "should delete workspace and clean up resources", - func(ctx context.Context) { - tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") - framework.ExpectNoError(err) - ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) - - err = f.DevsyUp(ctx, tempDir) - framework.ExpectNoError(err) - - _, err = f.FindWorkspace(ctx, tempDir) - framework.ExpectNoError(err) - - err = f.DevsyWorkspaceDelete(ctx, tempDir) - framework.ExpectNoError(err) - - _, err = f.FindWorkspace(ctx, tempDir) - framework.ExpectError(err) - }, - ginkgo.SpecTimeout(framework.TimeoutShort()), - ) - }) }) }, ) diff --git a/e2e/tests/up/provider_podman_rootless_exec.go b/e2e/tests/up/provider_podman_rootless_exec.go new file mode 100644 index 000000000..ab6c1f5a7 --- /dev/null +++ b/e2e/tests/up/provider_podman_rootless_exec.go @@ -0,0 +1,98 @@ +package up + +import ( + "context" + "os" + "strings" + + "github.com/devsy-org/devsy/e2e/framework" + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" +) + +var _ = ginkgo.Describe( + "testing up command for podman provider", + ginkgo.Label("up-provider-podman-rootless-exec"), + func() { + var initialDir string + + ginkgo.BeforeEach(func() { + var err error + initialDir, err = os.Getwd() + framework.ExpectNoError(err) + }) + + ginkgo.Context("with rootless podman", func() { + var f *framework.Framework + + ginkgo.BeforeEach(func(ctx context.Context) { + var err error + f, err = setupDockerProvider(initialDir+"/bin", "podman") + framework.ExpectNoError(err) + }) + + ginkgo.Context("agent delivery", func() { + ginkgo.It( + "should deliver the agent binary and execute SSH commands", + func(ctx context.Context) { + tempDir, err := setupWorkspace("tests/up/testdata/docker", initialDir, f) + framework.ExpectNoError(err) + + err = f.DevsyUp(ctx, tempDir) + framework.ExpectNoError(err) + + err = f.DevsySSHEchoTestString(ctx, tempDir) + framework.ExpectNoError(err) + }, + ginkgo.SpecTimeout(framework.TimeoutShort()), + ) + }) + + ginkgo.Context("exec", func() { + ginkgo.It( + "should execute commands inside the container via SSH", + func(ctx context.Context) { + tempDir, err := setupWorkspace("tests/up/testdata/docker", initialDir, f) + framework.ExpectNoError(err) + + err = f.DevsyUp(ctx, tempDir) + framework.ExpectNoError(err) + + out, err := f.DevsySSH(ctx, tempDir, "echo -n hello-podman") + framework.ExpectNoError(err) + framework.ExpectEqual(out, "hello-podman") + + out, err = f.DevsySSH(ctx, tempDir, "pwd") + framework.ExpectNoError(err) + gomega.Expect(strings.TrimSpace(out)).NotTo(gomega.BeEmpty()) + }, + ginkgo.SpecTimeout(framework.TimeoutShort()), + ) + }) + + ginkgo.Context("cleanup", func() { + ginkgo.It( + "should delete workspace and clean up resources", + func(ctx context.Context) { + tempDir, err := framework.CopyToTempDir("tests/up/testdata/docker") + framework.ExpectNoError(err) + ginkgo.DeferCleanup(framework.CleanupTempDir, initialDir, tempDir) + + err = f.DevsyUp(ctx, tempDir) + framework.ExpectNoError(err) + + _, err = f.FindWorkspace(ctx, tempDir) + framework.ExpectNoError(err) + + err = f.DevsyWorkspaceDelete(ctx, tempDir) + framework.ExpectNoError(err) + + _, err = f.FindWorkspace(ctx, tempDir) + framework.ExpectError(err) + }, + ginkgo.SpecTimeout(framework.TimeoutShort()), + ) + }) + }) + }, +) diff --git a/pkg/driver/docker/lifecycle_test.go b/pkg/driver/docker/lifecycle_test.go index 55169e206..a13942d6c 100644 --- a/pkg/driver/docker/lifecycle_test.go +++ b/pkg/driver/docker/lifecycle_test.go @@ -80,8 +80,8 @@ esac func withShortImageInspectPoll(t *testing.T) { t.Helper() origInterval, origTimeout := imageInspectPollInterval, imageInspectPollTimeout - imageInspectPollInterval = time.Millisecond - imageInspectPollTimeout = 20 * time.Millisecond + imageInspectPollInterval = 5 * time.Millisecond + imageInspectPollTimeout = 300 * time.Millisecond t.Cleanup(func() { imageInspectPollInterval, imageInspectPollTimeout = origInterval, origTimeout })