mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(agent/agentcontainers): refactor Lister to ContainerCLI and implement new methods (#18243)
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
package agentcontainers_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/agent/agentcontainers"
|
||||
"github.com/coder/coder/v2/agent/agentexec"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
// TestIntegrationDockerCLI tests the DetectArchitecture, Copy, and
|
||||
// ExecAs methods using a real Docker container. All tests share a
|
||||
// single container to avoid setup overhead.
|
||||
//
|
||||
// Run manually with: CODER_TEST_USE_DOCKER=1 go test ./agent/agentcontainers -run TestIntegrationDockerCLI
|
||||
//
|
||||
//nolint:tparallel,paralleltest // Docker integration tests don't run in parallel to avoid flakiness.
|
||||
func TestIntegrationDockerCLI(t *testing.T) {
|
||||
if ctud, ok := os.LookupEnv("CODER_TEST_USE_DOCKER"); !ok || ctud != "1" {
|
||||
t.Skip("Set CODER_TEST_USE_DOCKER=1 to run this test")
|
||||
}
|
||||
|
||||
pool, err := dockertest.NewPool("")
|
||||
require.NoError(t, err, "Could not connect to docker")
|
||||
|
||||
// Start a simple busybox container for all subtests to share.
|
||||
ct, err := pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: "busybox",
|
||||
Tag: "latest",
|
||||
Cmd: []string{"sleep", "infinity"},
|
||||
}, func(config *docker.HostConfig) {
|
||||
config.AutoRemove = true
|
||||
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
|
||||
})
|
||||
require.NoError(t, err, "Could not start test docker container")
|
||||
t.Logf("Created container %q", ct.Container.Name)
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, pool.Purge(ct), "Could not purge resource %q", ct.Container.Name)
|
||||
t.Logf("Purged container %q", ct.Container.Name)
|
||||
})
|
||||
|
||||
// Wait for container to start.
|
||||
require.Eventually(t, func() bool {
|
||||
ct, ok := pool.ContainerByName(ct.Container.Name)
|
||||
return ok && ct.Container.State.Running
|
||||
}, testutil.WaitShort, testutil.IntervalSlow, "Container did not start in time")
|
||||
|
||||
dcli := agentcontainers.NewDockerCLI(agentexec.DefaultExecer)
|
||||
ctx := testutil.Context(t, testutil.WaitMedium) // Longer timeout for multiple subtests
|
||||
containerName := strings.TrimPrefix(ct.Container.Name, "/")
|
||||
|
||||
t.Run("DetectArchitecture", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
arch, err := dcli.DetectArchitecture(ctx, containerName)
|
||||
require.NoError(t, err, "DetectArchitecture failed")
|
||||
require.NotEmpty(t, arch, "arch has no content")
|
||||
require.Equal(t, runtime.GOARCH, arch, "architecture does not match runtime, did you run this test with a remote Docker socket?")
|
||||
|
||||
t.Logf("Detected architecture: %s", arch)
|
||||
})
|
||||
|
||||
t.Run("Copy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := "Help, I'm trapped!"
|
||||
tempFile := filepath.Join(t.TempDir(), "test-file.txt")
|
||||
err := os.WriteFile(tempFile, []byte(want), 0o600)
|
||||
require.NoError(t, err, "create test file failed")
|
||||
|
||||
destPath := "/tmp/copied-file.txt"
|
||||
err = dcli.Copy(ctx, containerName, tempFile, destPath)
|
||||
require.NoError(t, err, "Copy failed")
|
||||
|
||||
got, err := dcli.ExecAs(ctx, containerName, "", "cat", destPath)
|
||||
require.NoError(t, err, "ExecAs failed after Copy")
|
||||
require.Equal(t, want, string(got), "copied file content did not match original")
|
||||
|
||||
t.Logf("Successfully copied file from %s to container %s:%s", tempFile, containerName, destPath)
|
||||
})
|
||||
|
||||
t.Run("ExecAs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test ExecAs without specifying user (should use container's default).
|
||||
want := "root"
|
||||
got, err := dcli.ExecAs(ctx, containerName, "", "whoami")
|
||||
require.NoError(t, err, "ExecAs without user should succeed")
|
||||
require.Equal(t, want, string(got), "ExecAs without user should output expected string")
|
||||
|
||||
// Test ExecAs with numeric UID (non root).
|
||||
want = "1000"
|
||||
_, err = dcli.ExecAs(ctx, containerName, want, "whoami")
|
||||
require.Error(t, err, "ExecAs with UID 1000 should fail as user does not exist in busybox")
|
||||
require.Contains(t, err.Error(), "whoami: unknown uid 1000", "ExecAs with UID 1000 should return 'unknown uid' error")
|
||||
|
||||
// Test ExecAs with root user (should convert "root" to "0", which still outputs root due to passwd).
|
||||
want = "root"
|
||||
got, err = dcli.ExecAs(ctx, containerName, "root", "whoami")
|
||||
require.NoError(t, err, "ExecAs with root user should succeed")
|
||||
require.Equal(t, want, string(got), "ExecAs with root user should output expected string")
|
||||
|
||||
// Test ExecAs with numeric UID.
|
||||
want = "root"
|
||||
got, err = dcli.ExecAs(ctx, containerName, "0", "whoami")
|
||||
require.NoError(t, err, "ExecAs with UID 0 should succeed")
|
||||
require.Equal(t, want, string(got), "ExecAs with UID 0 should output expected string")
|
||||
|
||||
// Test ExecAs with multiple arguments.
|
||||
want = "multiple args test"
|
||||
got, err = dcli.ExecAs(ctx, containerName, "", "sh", "-c", "echo '"+want+"'")
|
||||
require.NoError(t, err, "ExecAs with multiple arguments should succeed")
|
||||
require.Equal(t, want, string(got), "ExecAs with multiple arguments should output expected string")
|
||||
|
||||
t.Logf("Successfully executed commands in container %s", containerName)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user