diff options
Diffstat (limited to 'pkg/container/docker_run_test.go')
-rw-r--r-- | pkg/container/docker_run_test.go | 246 |
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) + }) + } +} |