github.com/nektos/act@v0.2.83/pkg/container/docker_run_test.go (about) 1 package container 2 3 import ( 4 "bufio" 5 "bytes" 6 "context" 7 "fmt" 8 "io" 9 "net" 10 "strings" 11 "testing" 12 "time" 13 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/container" 16 "github.com/docker/docker/client" 17 "github.com/nektos/act/pkg/common" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/mock" 20 ) 21 22 func TestDocker(t *testing.T) { 23 ctx := context.Background() 24 client, err := GetDockerClient(ctx) 25 assert.NoError(t, err) 26 defer client.Close() 27 28 dockerBuild := NewDockerBuildExecutor(NewDockerBuildExecutorInput{ 29 ContextDir: "testdata", 30 ImageTag: "envmergetest", 31 }) 32 33 err = dockerBuild(ctx) 34 assert.NoError(t, err) 35 36 cr := &containerReference{ 37 cli: client, 38 input: &NewContainerInput{ 39 Image: "envmergetest", 40 }, 41 } 42 env := map[string]string{ 43 "PATH": "/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin", 44 "RANDOM_VAR": "WITH_VALUE", 45 "ANOTHER_VAR": "", 46 "CONFLICT_VAR": "I_EXIST_IN_MULTIPLE_PLACES", 47 } 48 49 envExecutor := cr.extractFromImageEnv(&env) 50 err = envExecutor(ctx) 51 assert.NoError(t, err) 52 assert.Equal(t, map[string]string{ 53 "PATH": "/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin:/this/path/does/not/exists/anywhere:/this/either", 54 "RANDOM_VAR": "WITH_VALUE", 55 "ANOTHER_VAR": "", 56 "SOME_RANDOM_VAR": "", 57 "ANOTHER_ONE": "BUT_I_HAVE_VALUE", 58 "CONFLICT_VAR": "I_EXIST_IN_MULTIPLE_PLACES", 59 }, env) 60 } 61 62 type mockDockerClient struct { 63 client.APIClient 64 mock.Mock 65 } 66 67 func (m *mockDockerClient) ContainerExecCreate(ctx context.Context, id string, opts container.ExecOptions) (container.ExecCreateResponse, error) { 68 args := m.Called(ctx, id, opts) 69 return args.Get(0).(container.ExecCreateResponse), args.Error(1) 70 } 71 72 func (m *mockDockerClient) ContainerExecAttach(ctx context.Context, id string, opts container.ExecStartOptions) (types.HijackedResponse, error) { 73 args := m.Called(ctx, id, opts) 74 return args.Get(0).(types.HijackedResponse), args.Error(1) 75 } 76 77 func (m *mockDockerClient) ContainerExecInspect(ctx context.Context, execID string) (container.ExecInspect, error) { 78 args := m.Called(ctx, execID) 79 return args.Get(0).(container.ExecInspect), args.Error(1) 80 } 81 82 func (m *mockDockerClient) CopyToContainer(ctx context.Context, id string, path string, content io.Reader, options container.CopyToContainerOptions) error { 83 args := m.Called(ctx, id, path, content, options) 84 return args.Error(0) 85 } 86 87 type endlessReader struct { 88 io.Reader 89 } 90 91 func (r endlessReader) Read(_ []byte) (n int, err error) { 92 return 1, nil 93 } 94 95 type mockConn struct { 96 net.Conn 97 mock.Mock 98 } 99 100 func (m *mockConn) Write(b []byte) (n int, err error) { 101 args := m.Called(b) 102 return args.Int(0), args.Error(1) 103 } 104 105 func (m *mockConn) Close() (err error) { 106 return nil 107 } 108 109 func TestDockerExecAbort(t *testing.T) { 110 ctx, cancel := context.WithCancel(context.Background()) 111 112 conn := &mockConn{} 113 conn.On("Write", mock.AnythingOfType("[]uint8")).Return(1, nil) 114 115 client := &mockDockerClient{} 116 client.On("ContainerExecCreate", ctx, "123", mock.AnythingOfType("container.ExecOptions")).Return(container.ExecCreateResponse{ID: "id"}, nil) 117 client.On("ContainerExecAttach", ctx, "id", mock.AnythingOfType("container.ExecStartOptions")).Return(types.HijackedResponse{ 118 Conn: conn, 119 Reader: bufio.NewReader(endlessReader{}), 120 }, nil) 121 122 cr := &containerReference{ 123 id: "123", 124 cli: client, 125 input: &NewContainerInput{ 126 Image: "image", 127 }, 128 } 129 130 channel := make(chan error) 131 132 go func() { 133 channel <- cr.exec([]string{""}, map[string]string{}, "user", "workdir")(ctx) 134 }() 135 136 time.Sleep(500 * time.Millisecond) 137 138 cancel() 139 140 err := <-channel 141 assert.ErrorIs(t, err, context.Canceled) 142 143 conn.AssertExpectations(t) 144 client.AssertExpectations(t) 145 } 146 147 func TestDockerExecFailure(t *testing.T) { 148 ctx := context.Background() 149 150 conn := &mockConn{} 151 152 client := &mockDockerClient{} 153 client.On("ContainerExecCreate", ctx, "123", mock.AnythingOfType("container.ExecOptions")).Return(container.ExecCreateResponse{ID: "id"}, nil) 154 client.On("ContainerExecAttach", ctx, "id", mock.AnythingOfType("container.ExecStartOptions")).Return(types.HijackedResponse{ 155 Conn: conn, 156 Reader: bufio.NewReader(strings.NewReader("output")), 157 }, nil) 158 client.On("ContainerExecInspect", ctx, "id").Return(container.ExecInspect{ 159 ExitCode: 1, 160 }, nil) 161 162 cr := &containerReference{ 163 id: "123", 164 cli: client, 165 input: &NewContainerInput{ 166 Image: "image", 167 }, 168 } 169 170 err := cr.exec([]string{""}, map[string]string{}, "user", "workdir")(ctx) 171 assert.Error(t, err, "exit with `FAILURE`: 1") 172 173 conn.AssertExpectations(t) 174 client.AssertExpectations(t) 175 } 176 177 func TestDockerCopyTarStream(t *testing.T) { 178 ctx := context.Background() 179 180 conn := &mockConn{} 181 182 client := &mockDockerClient{} 183 client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("container.CopyToContainerOptions")).Return(nil) 184 client.On("CopyToContainer", ctx, "123", "/var/run/act", mock.Anything, mock.AnythingOfType("container.CopyToContainerOptions")).Return(nil) 185 cr := &containerReference{ 186 id: "123", 187 cli: client, 188 input: &NewContainerInput{ 189 Image: "image", 190 }, 191 } 192 193 _ = cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{}) 194 195 conn.AssertExpectations(t) 196 client.AssertExpectations(t) 197 } 198 199 func TestDockerCopyTarStreamDryRun(t *testing.T) { 200 ctx := common.WithDryrun(context.Background(), true) 201 202 conn := &mockConn{} 203 204 client := &mockDockerClient{} 205 client.AssertNotCalled(t, "CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("container.CopyToContainerOptions")) 206 client.AssertNotCalled(t, "CopyToContainer", ctx, "123", "/var/run/act", mock.Anything, mock.AnythingOfType("container.CopyToContainerOptions")) 207 cr := &containerReference{ 208 id: "123", 209 cli: client, 210 input: &NewContainerInput{ 211 Image: "image", 212 }, 213 } 214 215 _ = cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{}) 216 217 conn.AssertExpectations(t) 218 client.AssertExpectations(t) 219 } 220 221 func TestDockerCopyTarStreamErrorInCopyFiles(t *testing.T) { 222 ctx := context.Background() 223 224 conn := &mockConn{} 225 226 merr := fmt.Errorf("Failure") 227 228 client := &mockDockerClient{} 229 client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("container.CopyToContainerOptions")).Return(merr) 230 client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("container.CopyToContainerOptions")).Return(merr) 231 cr := &containerReference{ 232 id: "123", 233 cli: client, 234 input: &NewContainerInput{ 235 Image: "image", 236 }, 237 } 238 239 err := cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{}) 240 assert.ErrorIs(t, err, merr) 241 242 conn.AssertExpectations(t) 243 client.AssertExpectations(t) 244 } 245 246 func TestDockerCopyTarStreamErrorInMkdir(t *testing.T) { 247 ctx := context.Background() 248 249 conn := &mockConn{} 250 251 merr := fmt.Errorf("Failure") 252 253 client := &mockDockerClient{} 254 client.On("CopyToContainer", ctx, "123", "/", mock.Anything, mock.AnythingOfType("container.CopyToContainerOptions")).Return(nil) 255 client.On("CopyToContainer", ctx, "123", "/var/run/act", mock.Anything, mock.AnythingOfType("container.CopyToContainerOptions")).Return(merr) 256 cr := &containerReference{ 257 id: "123", 258 cli: client, 259 input: &NewContainerInput{ 260 Image: "image", 261 }, 262 } 263 264 err := cr.CopyTarStream(ctx, "/var/run/act", &bytes.Buffer{}) 265 assert.ErrorIs(t, err, merr) 266 267 conn.AssertExpectations(t) 268 client.AssertExpectations(t) 269 } 270 271 // Type assert containerReference implements ExecutionsEnvironment 272 var _ ExecutionsEnvironment = &containerReference{}