diff --git a/internal/test/assets/alpine.tgz b/internal/test/assets/alpine.tgz new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/internal/test/assets/alpine.tgz @@ -0,0 +1 @@ + diff --git a/internal/test/container.go b/internal/test/container.go index 574d999cf..8f8bfa9b5 100644 --- a/internal/test/container.go +++ b/internal/test/container.go @@ -124,6 +124,7 @@ func (configuration Configuration) commands() ([]string, error) { //counterfeiter:generate -o ./fakes/moby_client.go --fake-name MobyClient . mobyClient type mobyClient interface { DialHijack(ctx context.Context, url, proto string, meta map[string][]string) (net.Conn, error) + ImageLoad(ctx context.Context, input io.Reader, quiet bool) (types.ImageLoadResponse, error) ImageBuild(ctx context.Context, buildContext io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) Ping(ctx context.Context) (types.Ping, error) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specV1.Platform, containerName string) (container.CreateResponse, error) @@ -141,22 +142,45 @@ func runTestWithSession(ctx context.Context, logger *log.Logger, w io.Writer, do } var dockerfileTarball bytes.Buffer - if err := createDockerfileTarball(tar.NewWriter(&dockerfileTarball), dockerfile); err != nil { + if err = createDockerfileTarball(tar.NewWriter(&dockerfileTarball), dockerfile); err != nil { return err } - logger.Println("creating test image") - imageBuildResult, err := dockerDaemon.ImageBuild(ctx, &dockerfileTarball, types.ImageBuildOptions{ - Tags: []string{"kiln_test_dependencies:vmware"}, - Version: types.BuilderBuildKit, - SessionID: sessionID, - }) - if err != nil { - return fmt.Errorf("failed to build image: %w", err) - } + if configuration.ImagePath == "" { + logger.Println("creating test image") + imageBuildResult, err := dockerDaemon.ImageBuild(ctx, &dockerfileTarball, types.ImageBuildOptions{ + Tags: []string{"kiln_test_dependencies:vmware"}, + Version: types.BuilderBuildKit, + SessionID: sessionID, + }) + if err != nil { + return fmt.Errorf("failed to build image: %w", err) + } + if err = checkSSHPrivateKeyError(imageBuildResult.Body); err != nil { + return err + } + } else { + logger.Println("loading test image") + imageReader, err := os.Open(configuration.ImagePath) + if err != nil { + return fmt.Errorf("failed to read image '%s': %w", configuration.ImagePath, err) + } - if err := checkSSHPrivateKeyError(imageBuildResult.Body); err != nil { - return err + loadResponse, err := dockerDaemon.ImageLoad( + ctx, + imageReader, + true, + ) + if err != nil { + return fmt.Errorf("failed to import image: %w", err) + } + + respBytes, err := io.ReadAll(loadResponse.Body) + defer loadResponse.Body.Close() + if err != nil { + return fmt.Errorf(`failed to parse load image response: %w`, err) + } + logger.Printf("loaded image %s: \n%s\n", configuration.ImagePath, string(respBytes)) } parentDir := path.Dir(configuration.AbsoluteTileDirectory) diff --git a/internal/test/container_test.go b/internal/test/container_test.go index 0ab4091b7..331c42643 100644 --- a/internal/test/container_test.go +++ b/internal/test/container_test.go @@ -91,9 +91,8 @@ func TestConfiguration_commands(t *testing.T) { }) } } - func Test_configureSession(t *testing.T) { - t.Run("when ping fails", func(t *testing.T) { + t.Run(`when ping fails`, func(t *testing.T) { ctx := context.Background() logger := log.New(io.Discard, "", 0) @@ -108,6 +107,43 @@ func Test_configureSession(t *testing.T) { }) } +func Test_loadImage(t *testing.T) { + absoluteTileDirectory := filepath.Join(t.TempDir(), "test") + logger := log.New(io.Discard, "", 0) + t.Run("when loading a provided test image with a wrong path", func(t *testing.T) { + ctx := context.Background() + out := bytes.Buffer{} + configuration := Configuration{ + AbsoluteTileDirectory: absoluteTileDirectory, + ImagePath: "non-existing", + } + client := runTestWithSessionHelper(t, "", container.WaitResponse{ + StatusCode: 0, + }) + + err := runTestWithSession(ctx, logger, &out, client, configuration)("some-session-id") + require.ErrorContains(t, err, "failed to read image 'non-existing': open non-existing: no such file or directory") + + }) + t.Run(`when loading a provided test image with an existing path`, func(t *testing.T) { + ctx := context.Background() + out := bytes.Buffer{} + + configuration := Configuration{ + AbsoluteTileDirectory: absoluteTileDirectory, + ImagePath: `assets/alpine.tgz`, + } + + client := runTestWithSessionHelper(t, "", container.WaitResponse{ + StatusCode: 0, + }) + + err := runTestWithSession(ctx, logger, &out, client, configuration)("some-session-id") + require.NoError(t, err) + + }) +} + func Test_runTestWithSession(t *testing.T) { absoluteTileDirectory := filepath.Join(t.TempDir(), "test") logger := log.New(io.Discard, "", 0) @@ -200,6 +236,10 @@ func runTestWithSessionHelper(t *testing.T, logs string, response container.Wait client.ImageBuildReturns(types.ImageBuildResponse{ Body: io.NopCloser(strings.NewReader("")), }, nil) + client.ImageLoadReturns(types.ImageLoadResponse{ + Body: io.NopCloser(strings.NewReader("")), + }, nil) + client.ContainerStartReturns(nil) client.ContainerLogsReturns(io.NopCloser(strings.NewReader(logs)), nil) diff --git a/internal/test/fakes/moby_client.go b/internal/test/fakes/moby_client.go index 11e4ac597..084265d34 100644 --- a/internal/test/fakes/moby_client.go +++ b/internal/test/fakes/moby_client.go @@ -119,6 +119,37 @@ type MobyClient struct { result1 types.ImageBuildResponse result2 error } + ImageImportStub func(context.Context, types.ImageImportSource, string, types.ImageImportOptions) (io.ReadCloser, error) + imageImportMutex sync.RWMutex + imageImportArgsForCall []struct { + arg1 context.Context + arg2 types.ImageImportSource + arg3 string + arg4 types.ImageImportOptions + } + imageImportReturns struct { + result1 io.ReadCloser + result2 error + } + imageImportReturnsOnCall map[int]struct { + result1 io.ReadCloser + result2 error + } + ImageLoadStub func(context.Context, io.Reader, bool) (types.ImageLoadResponse, error) + imageLoadMutex sync.RWMutex + imageLoadArgsForCall []struct { + arg1 context.Context + arg2 io.Reader + arg3 bool + } + imageLoadReturns struct { + result1 types.ImageLoadResponse + result2 error + } + imageLoadReturnsOnCall map[int]struct { + result1 types.ImageLoadResponse + result2 error + } PingStub func(context.Context) (types.Ping, error) pingMutex sync.RWMutex pingArgsForCall []struct { @@ -596,6 +627,139 @@ func (fake *MobyClient) ImageBuildReturnsOnCall(i int, result1 types.ImageBuildR }{result1, result2} } +func (fake *MobyClient) ImageImport(arg1 context.Context, arg2 types.ImageImportSource, arg3 string, arg4 types.ImageImportOptions) (io.ReadCloser, error) { + fake.imageImportMutex.Lock() + ret, specificReturn := fake.imageImportReturnsOnCall[len(fake.imageImportArgsForCall)] + fake.imageImportArgsForCall = append(fake.imageImportArgsForCall, struct { + arg1 context.Context + arg2 types.ImageImportSource + arg3 string + arg4 types.ImageImportOptions + }{arg1, arg2, arg3, arg4}) + stub := fake.ImageImportStub + fakeReturns := fake.imageImportReturns + fake.recordInvocation("ImageImport", []interface{}{arg1, arg2, arg3, arg4}) + fake.imageImportMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3, arg4) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *MobyClient) ImageImportCallCount() int { + fake.imageImportMutex.RLock() + defer fake.imageImportMutex.RUnlock() + return len(fake.imageImportArgsForCall) +} + +func (fake *MobyClient) ImageImportCalls(stub func(context.Context, types.ImageImportSource, string, types.ImageImportOptions) (io.ReadCloser, error)) { + fake.imageImportMutex.Lock() + defer fake.imageImportMutex.Unlock() + fake.ImageImportStub = stub +} + +func (fake *MobyClient) ImageImportArgsForCall(i int) (context.Context, types.ImageImportSource, string, types.ImageImportOptions) { + fake.imageImportMutex.RLock() + defer fake.imageImportMutex.RUnlock() + argsForCall := fake.imageImportArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4 +} + +func (fake *MobyClient) ImageImportReturns(result1 io.ReadCloser, result2 error) { + fake.imageImportMutex.Lock() + defer fake.imageImportMutex.Unlock() + fake.ImageImportStub = nil + fake.imageImportReturns = struct { + result1 io.ReadCloser + result2 error + }{result1, result2} +} + +func (fake *MobyClient) ImageImportReturnsOnCall(i int, result1 io.ReadCloser, result2 error) { + fake.imageImportMutex.Lock() + defer fake.imageImportMutex.Unlock() + fake.ImageImportStub = nil + if fake.imageImportReturnsOnCall == nil { + fake.imageImportReturnsOnCall = make(map[int]struct { + result1 io.ReadCloser + result2 error + }) + } + fake.imageImportReturnsOnCall[i] = struct { + result1 io.ReadCloser + result2 error + }{result1, result2} +} + +func (fake *MobyClient) ImageLoad(arg1 context.Context, arg2 io.Reader, arg3 bool) (types.ImageLoadResponse, error) { + fake.imageLoadMutex.Lock() + ret, specificReturn := fake.imageLoadReturnsOnCall[len(fake.imageLoadArgsForCall)] + fake.imageLoadArgsForCall = append(fake.imageLoadArgsForCall, struct { + arg1 context.Context + arg2 io.Reader + arg3 bool + }{arg1, arg2, arg3}) + stub := fake.ImageLoadStub + fakeReturns := fake.imageLoadReturns + fake.recordInvocation("ImageLoad", []interface{}{arg1, arg2, arg3}) + fake.imageLoadMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *MobyClient) ImageLoadCallCount() int { + fake.imageLoadMutex.RLock() + defer fake.imageLoadMutex.RUnlock() + return len(fake.imageLoadArgsForCall) +} + +func (fake *MobyClient) ImageLoadCalls(stub func(context.Context, io.Reader, bool) (types.ImageLoadResponse, error)) { + fake.imageLoadMutex.Lock() + defer fake.imageLoadMutex.Unlock() + fake.ImageLoadStub = stub +} + +func (fake *MobyClient) ImageLoadArgsForCall(i int) (context.Context, io.Reader, bool) { + fake.imageLoadMutex.RLock() + defer fake.imageLoadMutex.RUnlock() + argsForCall := fake.imageLoadArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *MobyClient) ImageLoadReturns(result1 types.ImageLoadResponse, result2 error) { + fake.imageLoadMutex.Lock() + defer fake.imageLoadMutex.Unlock() + fake.ImageLoadStub = nil + fake.imageLoadReturns = struct { + result1 types.ImageLoadResponse + result2 error + }{result1, result2} +} + +func (fake *MobyClient) ImageLoadReturnsOnCall(i int, result1 types.ImageLoadResponse, result2 error) { + fake.imageLoadMutex.Lock() + defer fake.imageLoadMutex.Unlock() + fake.ImageLoadStub = nil + if fake.imageLoadReturnsOnCall == nil { + fake.imageLoadReturnsOnCall = make(map[int]struct { + result1 types.ImageLoadResponse + result2 error + }) + } + fake.imageLoadReturnsOnCall[i] = struct { + result1 types.ImageLoadResponse + result2 error + }{result1, result2} +} + func (fake *MobyClient) Ping(arg1 context.Context) (types.Ping, error) { fake.pingMutex.Lock() ret, specificReturn := fake.pingReturnsOnCall[len(fake.pingArgsForCall)] @@ -677,6 +841,10 @@ func (fake *MobyClient) Invocations() map[string][][]interface{} { defer fake.dialHijackMutex.RUnlock() fake.imageBuildMutex.RLock() defer fake.imageBuildMutex.RUnlock() + fake.imageImportMutex.RLock() + defer fake.imageImportMutex.RUnlock() + fake.imageLoadMutex.RLock() + defer fake.imageLoadMutex.RUnlock() fake.pingMutex.RLock() defer fake.pingMutex.RUnlock() copiedInvocations := map[string][][]interface{}{}