summaryrefslogtreecommitdiffstats
path: root/pkg/container/docker_run_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel@debian.org>2024-10-20 23:07:42 +0200
committerDaniel Baumann <daniel@debian.org>2024-11-09 15:38:42 +0100
commit714c83b2736d7e308bc33c49057952490eb98be2 (patch)
tree1d9ba7035798368569cd49056f4d596efc908cd8 /pkg/container/docker_run_test.go
parentInitial commit. (diff)
downloadforgejo-act-upstream.tar.xz
forgejo-act-upstream.zip
Adding upstream version 1.21.4.HEADupstream/1.21.4upstreamdebian
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'pkg/container/docker_run_test.go')
-rw-r--r--pkg/container/docker_run_test.go246
1 files changed, 246 insertions, 0 deletions
diff --git a/pkg/container/docker_run_test.go b/pkg/container/docker_run_test.go
new file mode 100644
index 0000000..9e85fb7
--- /dev/null
+++ b/pkg/container/docker_run_test.go
@@ -0,0 +1,246 @@
+package container
+
+import (
+ "bufio"
+ "context"
+ "io"
+ "net"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/nektos/act/pkg/common"
+
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/api/types/container"
+ "github.com/docker/docker/client"
+ "github.com/sirupsen/logrus/hooks/test"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/mock"
+)
+
+func TestDocker(t *testing.T) {
+ ctx := context.Background()
+ client, err := GetDockerClient(ctx)
+ assert.NoError(t, err)
+ defer client.Close()
+
+ dockerBuild := NewDockerBuildExecutor(NewDockerBuildExecutorInput{
+ ContextDir: "testdata",
+ ImageTag: "envmergetest",
+ })
+
+ err = dockerBuild(ctx)
+ assert.NoError(t, err)
+
+ cr := &containerReference{
+ cli: client,
+ input: &NewContainerInput{
+ Image: "envmergetest",
+ },
+ }
+ env := map[string]string{
+ "PATH": "/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin",
+ "RANDOM_VAR": "WITH_VALUE",
+ "ANOTHER_VAR": "",
+ "CONFLICT_VAR": "I_EXIST_IN_MULTIPLE_PLACES",
+ }
+
+ envExecutor := cr.extractFromImageEnv(&env)
+ err = envExecutor(ctx)
+ assert.NoError(t, err)
+ assert.Equal(t, map[string]string{
+ "PATH": "/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin:/this/path/does/not/exists/anywhere:/this/either",
+ "RANDOM_VAR": "WITH_VALUE",
+ "ANOTHER_VAR": "",
+ "SOME_RANDOM_VAR": "",
+ "ANOTHER_ONE": "BUT_I_HAVE_VALUE",
+ "CONFLICT_VAR": "I_EXIST_IN_MULTIPLE_PLACES",
+ }, env)
+}
+
+type mockDockerClient struct {
+ client.APIClient
+ mock.Mock
+}
+
+func (m *mockDockerClient) ContainerExecCreate(ctx context.Context, id string, opts types.ExecConfig) (types.IDResponse, error) {
+ args := m.Called(ctx, id, opts)
+ return args.Get(0).(types.IDResponse), args.Error(1)
+}
+
+func (m *mockDockerClient) ContainerExecAttach(ctx context.Context, id string, opts types.ExecStartCheck) (types.HijackedResponse, error) {
+ args := m.Called(ctx, id, opts)
+ return args.Get(0).(types.HijackedResponse), args.Error(1)
+}
+
+func (m *mockDockerClient) ContainerExecInspect(ctx context.Context, execID string) (types.ContainerExecInspect, error) {
+ args := m.Called(ctx, execID)
+ return args.Get(0).(types.ContainerExecInspect), args.Error(1)
+}
+
+type endlessReader struct {
+ io.Reader
+}
+
+func (r endlessReader) Read(_ []byte) (n int, err error) {
+ return 1, nil
+}
+
+type mockConn struct {
+ net.Conn
+ mock.Mock
+}
+
+func (m *mockConn) Write(b []byte) (n int, err error) {
+ args := m.Called(b)
+ return args.Int(0), args.Error(1)
+}
+
+func (m *mockConn) Close() (err error) {
+ return nil
+}
+
+func TestDockerExecAbort(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+
+ conn := &mockConn{}
+ conn.On("Write", mock.AnythingOfType("[]uint8")).Return(1, nil)
+
+ client := &mockDockerClient{}
+ client.On("ContainerExecCreate", ctx, "123", mock.AnythingOfType("types.ExecConfig")).Return(types.IDResponse{ID: "id"}, nil)
+ client.On("ContainerExecAttach", ctx, "id", mock.AnythingOfType("types.ExecStartCheck")).Return(types.HijackedResponse{
+ Conn: conn,
+ Reader: bufio.NewReader(endlessReader{}),
+ }, nil)
+
+ cr := &containerReference{
+ id: "123",
+ cli: client,
+ input: &NewContainerInput{
+ Image: "image",
+ },
+ }
+
+ channel := make(chan error)
+
+ go func() {
+ channel <- cr.exec([]string{""}, map[string]string{}, "user", "workdir")(ctx)
+ }()
+
+ time.Sleep(500 * time.Millisecond)
+
+ cancel()
+
+ err := <-channel
+ assert.ErrorIs(t, err, context.Canceled)
+
+ conn.AssertExpectations(t)
+ client.AssertExpectations(t)
+}
+
+func TestDockerExecFailure(t *testing.T) {
+ ctx := context.Background()
+
+ conn := &mockConn{}
+
+ client := &mockDockerClient{}
+ client.On("ContainerExecCreate", ctx, "123", mock.AnythingOfType("types.ExecConfig")).Return(types.IDResponse{ID: "id"}, nil)
+ client.On("ContainerExecAttach", ctx, "id", mock.AnythingOfType("types.ExecStartCheck")).Return(types.HijackedResponse{
+ Conn: conn,
+ Reader: bufio.NewReader(strings.NewReader("output")),
+ }, nil)
+ client.On("ContainerExecInspect", ctx, "id").Return(types.ContainerExecInspect{
+ ExitCode: 1,
+ }, nil)
+
+ cr := &containerReference{
+ id: "123",
+ cli: client,
+ input: &NewContainerInput{
+ Image: "image",
+ },
+ }
+
+ err := cr.exec([]string{""}, map[string]string{}, "user", "workdir")(ctx)
+ assert.Error(t, err, "exit with `FAILURE`: 1")
+
+ conn.AssertExpectations(t)
+ client.AssertExpectations(t)
+}
+
+// Type assert containerReference implements ExecutionsEnvironment
+var _ ExecutionsEnvironment = &containerReference{}
+
+func TestCheckVolumes(t *testing.T) {
+ testCases := []struct {
+ desc string
+ validVolumes []string
+ binds []string
+ expectedBinds []string
+ }{
+ {
+ desc: "match all volumes",
+ validVolumes: []string{"**"},
+ binds: []string{
+ "shared_volume:/shared_volume",
+ "/home/test/data:/test_data",
+ "/etc/conf.d/base.json:/config/base.json",
+ "sql_data:/sql_data",
+ "/secrets/keys:/keys",
+ },
+ expectedBinds: []string{
+ "shared_volume:/shared_volume",
+ "/home/test/data:/test_data",
+ "/etc/conf.d/base.json:/config/base.json",
+ "sql_data:/sql_data",
+ "/secrets/keys:/keys",
+ },
+ },
+ {
+ desc: "no volumes can be matched",
+ validVolumes: []string{},
+ binds: []string{
+ "shared_volume:/shared_volume",
+ "/home/test/data:/test_data",
+ "/etc/conf.d/base.json:/config/base.json",
+ "sql_data:/sql_data",
+ "/secrets/keys:/keys",
+ },
+ expectedBinds: []string{},
+ },
+ {
+ desc: "only allowed volumes can be matched",
+ validVolumes: []string{
+ "shared_volume",
+ "/home/test/data",
+ "/etc/conf.d/*.json",
+ },
+ binds: []string{
+ "shared_volume:/shared_volume",
+ "/home/test/data:/test_data",
+ "/etc/conf.d/base.json:/config/base.json",
+ "sql_data:/sql_data",
+ "/secrets/keys:/keys",
+ },
+ expectedBinds: []string{
+ "shared_volume:/shared_volume",
+ "/home/test/data:/test_data",
+ "/etc/conf.d/base.json:/config/base.json",
+ },
+ },
+ }
+ for _, tc := range testCases {
+ t.Run(tc.desc, func(t *testing.T) {
+ logger, _ := test.NewNullLogger()
+ ctx := common.WithLogger(context.Background(), logger)
+ cr := &containerReference{
+ input: &NewContainerInput{
+ ValidVolumes: tc.validVolumes,
+ },
+ }
+ _, hostConf := cr.sanitizeConfig(ctx, &container.Config{}, &container.HostConfig{Binds: tc.binds})
+ assert.Equal(t, tc.expectedBinds, hostConf.Binds)
+ })
+ }
+}