From 1dfe7fea384c7fbe610053a0077f0c563f0f02c3 Mon Sep 17 00:00:00 2001 From: Rob Watson Date: Wed, 12 Feb 2025 07:00:00 +0100 Subject: [PATCH] feat: quit if mediaserver fails to start --- .mockery.yaml | 11 + container/container.go | 20 +- container/container_test.go | 192 +++++ container/events_test.go | 27 +- container/integration_test.go | 4 +- container/stats_test.go | 22 +- domain/types.go | 2 + .../mocks/container/dockerclient_mock.go | 810 ++++++++++++++++++ go.mod | 43 +- go.sum | 98 ++- mediaserver/actor.go | 19 +- terminal/actor.go | 15 + testhelpers/channel.go | 5 +- testhelpers/docker.go | 29 - 14 files changed, 1227 insertions(+), 70 deletions(-) create mode 100644 .mockery.yaml create mode 100644 container/container_test.go create mode 100644 generated/mocks/container/dockerclient_mock.go delete mode 100644 testhelpers/docker.go diff --git a/.mockery.yaml b/.mockery.yaml new file mode 100644 index 0000000..db437b7 --- /dev/null +++ b/.mockery.yaml @@ -0,0 +1,11 @@ +with-expecter: true +dir: "generated/mocks/{{ .InterfaceDirRelative }}" +filename: "{{ .InterfaceName | lower }}_mock.go" +mockname: "{{ .InterfaceName }}" +outpkg: "{{ .PackageName }}" +issue-845-fix: true +resolve-type-alias: false +packages: + git.netflux.io/rob/termstream/container: + interfaces: + DockerClient: diff --git a/container/container.go b/container/container.go index 454bb59..8e41f0d 100644 --- a/container/container.go +++ b/container/container.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "log/slog" + "maps" "strings" "sync" "time" @@ -156,8 +157,11 @@ func (a *Client) RunContainer(ctx context.Context, params RunContainerParams) (< _, _ = io.Copy(io.Discard, pullReader) _ = pullReader.Close() - params.ContainerConfig.Labels["app"] = "termstream" - params.ContainerConfig.Labels["app-id"] = a.id.String() + containerConfig := *params.ContainerConfig + containerConfig.Labels = make(map[string]string) + maps.Copy(containerConfig.Labels, params.ContainerConfig.Labels) + containerConfig.Labels["app"] = "termstream" + containerConfig.Labels["app-id"] = a.id.String() var name string if params.Name != "" { @@ -166,7 +170,7 @@ func (a *Client) RunContainer(ctx context.Context, params RunContainerParams) (< createResp, err := a.apiClient.ContainerCreate( ctx, - params.ContainerConfig, + &containerConfig, params.HostConfig, params.NetworkingConfig, nil, @@ -280,11 +284,15 @@ func (a *Client) runContainerLoop( state.TxRate = 0 state.RxSince = time.Time{} state.RestartCount++ - sendState() if !resp.restarting { + exitCode := int(resp.StatusCode) + state.ExitCode = &exitCode + sendState() return } + + sendState() case err := <-containerErrC: // TODO: error handling? if err != context.Canceled { @@ -331,10 +339,6 @@ func (a *Client) runContainerLoop( // Close closes the client, stopping and removing all running containers. func (a *Client) Close() error { - if a.ctx.Err() != nil { - return nil - } - a.cancel() ctx, cancel := context.WithTimeout(context.Background(), stopTimeout) diff --git a/container/container_test.go b/container/container_test.go new file mode 100644 index 0000000..b16a0d8 --- /dev/null +++ b/container/container_test.go @@ -0,0 +1,192 @@ +package container_test + +import ( + "bytes" + "errors" + "io" + "testing" + "time" + + "git.netflux.io/rob/termstream/container" + containermocks "git.netflux.io/rob/termstream/generated/mocks/container" + "git.netflux.io/rob/termstream/testhelpers" + "github.com/docker/docker/api/types" + dockercontainer "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/events" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/network" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func TestClientRunContainer(t *testing.T) { + logger := testhelpers.NewTestLogger() + + // channels returned by Docker's ContainerWait: + containerWaitC := make(chan dockercontainer.WaitResponse) + containerErrC := make(chan error) + + // channels returned by Docker's Events: + eventsC := make(chan events.Message) + eventsErrC := make(chan error) + + var dockerClient containermocks.DockerClient + defer dockerClient.AssertExpectations(t) + + dockerClient. + EXPECT(). + NetworkCreate(mock.Anything, mock.Anything, network.CreateOptions{Driver: "bridge"}). + Return(network.CreateResponse{ID: "test-network"}, nil) + dockerClient. + EXPECT(). + ImagePull(mock.Anything, "alpine", image.PullOptions{}). + Return(io.NopCloser(bytes.NewReader(nil)), nil) + dockerClient. + EXPECT(). + ContainerCreate(mock.Anything, mock.Anything, mock.Anything, mock.Anything, (*ocispec.Platform)(nil), mock.Anything). + Return(dockercontainer.CreateResponse{ID: "123"}, nil) + dockerClient. + EXPECT(). + NetworkConnect(mock.Anything, "test-network", "123", (*network.EndpointSettings)(nil)). + Return(nil) + dockerClient. + EXPECT(). + ContainerStart(mock.Anything, "123", dockercontainer.StartOptions{}). + Return(nil) + dockerClient. + EXPECT(). + ContainerStats(mock.Anything, "123", true). + Return(dockercontainer.StatsResponseReader{Body: io.NopCloser(bytes.NewReader(nil))}, nil) + dockerClient. + EXPECT(). + ContainerWait(mock.Anything, "123", dockercontainer.WaitConditionNextExit). + Return(containerWaitC, containerErrC) + dockerClient. + EXPECT(). + ContainerInspect(mock.Anything, "123"). + Return(types.ContainerJSON{ContainerJSONBase: &types.ContainerJSONBase{State: &types.ContainerState{Status: "exited"}}}, nil) + dockerClient. + EXPECT(). + Events(mock.Anything, events.ListOptions{Filters: filters.NewArgs(filters.Arg("container", "123"), filters.Arg("type", "container"))}). + Return(eventsC, eventsErrC) + + containerClient, err := container.NewClient(t.Context(), &dockerClient, logger) + require.NoError(t, err) + + containerStateC, errC := containerClient.RunContainer(t.Context(), container.RunContainerParams{ + Name: "test-run-container", + ChanSize: 1, + ContainerConfig: &dockercontainer.Config{Image: "alpine"}, + HostConfig: &dockercontainer.HostConfig{}, + }) + + done := make(chan struct{}) + go func() { + defer close(done) + + require.NoError(t, <-errC) + }() + + assert.Equal(t, "pulling", (<-containerStateC).State) + assert.Equal(t, "created", (<-containerStateC).State) + assert.Equal(t, "running", (<-containerStateC).State) + assert.Equal(t, "running", (<-containerStateC).State) + + // Enough time for events channel to receive a message: + time.Sleep(100 * time.Millisecond) + + containerWaitC <- dockercontainer.WaitResponse{StatusCode: 1} + + state := <-containerStateC + assert.Equal(t, "exited", state.State) + assert.Equal(t, "unhealthy", state.HealthState) + require.NotNil(t, state.ExitCode) + assert.Equal(t, 1, *state.ExitCode) + assert.Equal(t, 1, state.RestartCount) + + <-done +} + +func TestClientRunContainerErrorStartingContainer(t *testing.T) { + logger := testhelpers.NewTestLogger() + + var dockerClient containermocks.DockerClient + defer dockerClient.AssertExpectations(t) + + dockerClient. + EXPECT(). + NetworkCreate(mock.Anything, mock.Anything, network.CreateOptions{Driver: "bridge"}). + Return(network.CreateResponse{ID: "test-network"}, nil) + dockerClient. + EXPECT(). + ImagePull(mock.Anything, "alpine", image.PullOptions{}). + Return(io.NopCloser(bytes.NewReader(nil)), nil) + dockerClient. + EXPECT(). + ContainerCreate(mock.Anything, mock.Anything, mock.Anything, mock.Anything, (*ocispec.Platform)(nil), mock.Anything). + Return(dockercontainer.CreateResponse{ID: "123"}, nil) + dockerClient. + EXPECT(). + NetworkConnect(mock.Anything, "test-network", "123", (*network.EndpointSettings)(nil)). + Return(nil) + dockerClient. + EXPECT(). + ContainerStart(mock.Anything, "123", dockercontainer.StartOptions{}). + Return(errors.New("error starting container")) + + containerClient, err := container.NewClient(t.Context(), &dockerClient, logger) + require.NoError(t, err) + + containerStateC, errC := containerClient.RunContainer(t.Context(), container.RunContainerParams{ + Name: "test-run-container-error-starting", + ChanSize: 1, + ContainerConfig: &dockercontainer.Config{Image: "alpine"}, + HostConfig: &dockercontainer.HostConfig{}, + }) + + assert.Equal(t, "pulling", (<-containerStateC).State) + assert.Equal(t, "created", (<-containerStateC).State) + + err = <-errC + require.EqualError(t, err, "container start: error starting container") +} + +func TestClientClose(t *testing.T) { + logger := testhelpers.NewTestLogger() + + var dockerClient containermocks.DockerClient + defer dockerClient.AssertExpectations(t) + + dockerClient. + EXPECT(). + NetworkCreate(mock.Anything, mock.Anything, network.CreateOptions{Driver: "bridge"}). + Return(network.CreateResponse{ID: "test-network"}, nil) + dockerClient. + EXPECT(). + ContainerList(mock.Anything, mock.Anything). + Return([]types.Container{{ID: "123"}}, nil) + dockerClient. + EXPECT(). + ContainerStop(mock.Anything, "123", mock.Anything). + Return(nil) + dockerClient. + EXPECT(). + ContainerRemove(mock.Anything, "123", mock.Anything). + Return(nil) + dockerClient. + EXPECT(). + NetworkRemove(mock.Anything, "test-network"). + Return(nil) + dockerClient. + EXPECT(). + Close(). + Return(nil) + + containerClient, err := container.NewClient(t.Context(), &dockerClient, logger) + require.NoError(t, err) + + require.NoError(t, containerClient.Close()) +} diff --git a/container/events_test.go b/container/events_test.go index 964414c..b4bba19 100644 --- a/container/events_test.go +++ b/container/events_test.go @@ -5,27 +5,34 @@ import ( "io" "testing" + containermocks "git.netflux.io/rob/termstream/generated/mocks/container" "git.netflux.io/rob/termstream/testhelpers" "github.com/docker/docker/api/types/events" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) func TestHandleEvents(t *testing.T) { - var count int eventsC1 := make(chan events.Message) eventsC2 := make(chan events.Message) - eventsCFunc := func() <-chan events.Message { - if count > 0 { - return eventsC2 - } - - count++ - return eventsC1 - } errC := make(chan error) containerID := "b905f51b47242090ae504c184c7bc84d6274511ef763c1847039dcaa00a3ad27" - dockerClient := testhelpers.MockDockerClient{EventsResponse: eventsCFunc, EventsErr: errC} + + var dockerClient containermocks.DockerClient + defer dockerClient.AssertExpectations(t) + + dockerClient. + EXPECT(). + Events(mock.Anything, mock.Anything). + Return(eventsC1, errC). + Once() + dockerClient. + EXPECT(). + Events(mock.Anything, mock.Anything). + Return(eventsC2, errC). + Once() + logger := testhelpers.NewNopLogger() ch := make(chan events.Message) diff --git a/container/integration_test.go b/container/integration_test.go index d77eb20..0cb44b6 100644 --- a/container/integration_test.go +++ b/container/integration_test.go @@ -44,7 +44,7 @@ func TestClientStartStop(t *testing.T) { }, }) testhelpers.ChanDiscard(containerStateC) - testhelpers.ChanRequireNoError(ctx, t, errC) + testhelpers.ChanRequireNoError(t, errC) require.Eventually( t, @@ -191,7 +191,7 @@ func TestContainerRestart(t *testing.T) { RestartPolicy: typescontainer.RestartPolicy{Name: "always"}, }, }) - testhelpers.ChanRequireNoError(ctx, t, errC) + testhelpers.ChanRequireNoError(t, errC) containerState := <-containerStateC assert.Equal(t, "pulling", containerState.State) diff --git a/container/stats_test.go b/container/stats_test.go index 69b686b..161beb8 100644 --- a/container/stats_test.go +++ b/container/stats_test.go @@ -7,7 +7,9 @@ import ( "io" "testing" + containermocks "git.netflux.io/rob/termstream/generated/mocks/container" "git.netflux.io/rob/termstream/testhelpers" + dockercontainer "github.com/docker/docker/api/types/container" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,7 +23,15 @@ var statsWithRestartJSON []byte func TestHandleStats(t *testing.T) { pr, pw := io.Pipe() containerID := "b905f51b47242090ae504c184c7bc84d6274511ef763c1847039dcaa00a3ad27" - dockerClient := testhelpers.MockDockerClient{ContainerStatsResponse: pr} + + var dockerClient containermocks.DockerClient + defer dockerClient.AssertExpectations(t) + + dockerClient. + EXPECT(). + ContainerStats(t.Context(), containerID, true). + Return(dockercontainer.StatsResponseReader{Body: pr}, nil) + networkCountConfig := NetworkCountConfig{Rx: "eth0", Tx: "eth1"} logger := testhelpers.NewTestLogger() ch := make(chan stats) @@ -59,7 +69,15 @@ func TestHandleStats(t *testing.T) { func TestHandleStatsWithContainerRestart(t *testing.T) { pr, pw := io.Pipe() containerID := "d0adc747fb12b9ce2376408aed8538a0769de55aa9c239313f231d9d80402e39" - dockerClient := testhelpers.MockDockerClient{ContainerStatsResponse: pr} + + var dockerClient containermocks.DockerClient + defer dockerClient.AssertExpectations(t) + + dockerClient. + EXPECT(). + ContainerStats(t.Context(), containerID, true). + Return(dockercontainer.StatsResponseReader{Body: pr}, nil) + networkCountConfig := NetworkCountConfig{Rx: "eth1", Tx: "eth0"} logger := testhelpers.NewTestLogger() ch := make(chan stats) diff --git a/domain/types.go b/domain/types.go index 8093258..53e0008 100644 --- a/domain/types.go +++ b/domain/types.go @@ -15,6 +15,7 @@ type Source struct { Listeners int RTMPURL string RTMPInternalURL string + ExitReason string } type DestinationState int @@ -46,4 +47,5 @@ type Container struct { TxRate int RxSince time.Time RestartCount int + ExitCode *int } diff --git a/generated/mocks/container/dockerclient_mock.go b/generated/mocks/container/dockerclient_mock.go new file mode 100644 index 0000000..fe315d3 --- /dev/null +++ b/generated/mocks/container/dockerclient_mock.go @@ -0,0 +1,810 @@ +// Code generated by mockery v2.52.2. DO NOT EDIT. + +package container + +import ( + context "context" + + events "github.com/docker/docker/api/types/events" + image "github.com/docker/docker/api/types/image" + + io "io" + + mock "github.com/stretchr/testify/mock" + + network "github.com/docker/docker/api/types/network" + + types "github.com/docker/docker/api/types" + + typescontainer "github.com/docker/docker/api/types/container" + + v1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +// DockerClient is an autogenerated mock type for the DockerClient type +type DockerClient struct { + mock.Mock +} + +type DockerClient_Expecter struct { + mock *mock.Mock +} + +func (_m *DockerClient) EXPECT() *DockerClient_Expecter { + return &DockerClient_Expecter{mock: &_m.Mock} +} + +// Close provides a mock function with no fields +func (_m *DockerClient) Close() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Close") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DockerClient_Close_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Close' +type DockerClient_Close_Call struct { + *mock.Call +} + +// Close is a helper method to define mock.On call +func (_e *DockerClient_Expecter) Close() *DockerClient_Close_Call { + return &DockerClient_Close_Call{Call: _e.mock.On("Close")} +} + +func (_c *DockerClient_Close_Call) Run(run func()) *DockerClient_Close_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *DockerClient_Close_Call) Return(_a0 error) *DockerClient_Close_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DockerClient_Close_Call) RunAndReturn(run func() error) *DockerClient_Close_Call { + _c.Call.Return(run) + return _c +} + +// ContainerCreate provides a mock function with given fields: _a0, _a1, _a2, _a3, _a4, _a5 +func (_m *DockerClient) ContainerCreate(_a0 context.Context, _a1 *typescontainer.Config, _a2 *typescontainer.HostConfig, _a3 *network.NetworkingConfig, _a4 *v1.Platform, _a5 string) (typescontainer.CreateResponse, error) { + ret := _m.Called(_a0, _a1, _a2, _a3, _a4, _a5) + + if len(ret) == 0 { + panic("no return value specified for ContainerCreate") + } + + var r0 typescontainer.CreateResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *typescontainer.Config, *typescontainer.HostConfig, *network.NetworkingConfig, *v1.Platform, string) (typescontainer.CreateResponse, error)); ok { + return rf(_a0, _a1, _a2, _a3, _a4, _a5) + } + if rf, ok := ret.Get(0).(func(context.Context, *typescontainer.Config, *typescontainer.HostConfig, *network.NetworkingConfig, *v1.Platform, string) typescontainer.CreateResponse); ok { + r0 = rf(_a0, _a1, _a2, _a3, _a4, _a5) + } else { + r0 = ret.Get(0).(typescontainer.CreateResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context, *typescontainer.Config, *typescontainer.HostConfig, *network.NetworkingConfig, *v1.Platform, string) error); ok { + r1 = rf(_a0, _a1, _a2, _a3, _a4, _a5) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DockerClient_ContainerCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContainerCreate' +type DockerClient_ContainerCreate_Call struct { + *mock.Call +} + +// ContainerCreate is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *typescontainer.Config +// - _a2 *typescontainer.HostConfig +// - _a3 *network.NetworkingConfig +// - _a4 *v1.Platform +// - _a5 string +func (_e *DockerClient_Expecter) ContainerCreate(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}, _a4 interface{}, _a5 interface{}) *DockerClient_ContainerCreate_Call { + return &DockerClient_ContainerCreate_Call{Call: _e.mock.On("ContainerCreate", _a0, _a1, _a2, _a3, _a4, _a5)} +} + +func (_c *DockerClient_ContainerCreate_Call) Run(run func(_a0 context.Context, _a1 *typescontainer.Config, _a2 *typescontainer.HostConfig, _a3 *network.NetworkingConfig, _a4 *v1.Platform, _a5 string)) *DockerClient_ContainerCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*typescontainer.Config), args[2].(*typescontainer.HostConfig), args[3].(*network.NetworkingConfig), args[4].(*v1.Platform), args[5].(string)) + }) + return _c +} + +func (_c *DockerClient_ContainerCreate_Call) Return(_a0 typescontainer.CreateResponse, _a1 error) *DockerClient_ContainerCreate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DockerClient_ContainerCreate_Call) RunAndReturn(run func(context.Context, *typescontainer.Config, *typescontainer.HostConfig, *network.NetworkingConfig, *v1.Platform, string) (typescontainer.CreateResponse, error)) *DockerClient_ContainerCreate_Call { + _c.Call.Return(run) + return _c +} + +// ContainerInspect provides a mock function with given fields: _a0, _a1 +func (_m *DockerClient) ContainerInspect(_a0 context.Context, _a1 string) (types.ContainerJSON, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ContainerInspect") + } + + var r0 types.ContainerJSON + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string) (types.ContainerJSON, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, string) types.ContainerJSON); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Get(0).(types.ContainerJSON) + } + + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DockerClient_ContainerInspect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContainerInspect' +type DockerClient_ContainerInspect_Call struct { + *mock.Call +} + +// ContainerInspect is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +func (_e *DockerClient_Expecter) ContainerInspect(_a0 interface{}, _a1 interface{}) *DockerClient_ContainerInspect_Call { + return &DockerClient_ContainerInspect_Call{Call: _e.mock.On("ContainerInspect", _a0, _a1)} +} + +func (_c *DockerClient_ContainerInspect_Call) Run(run func(_a0 context.Context, _a1 string)) *DockerClient_ContainerInspect_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *DockerClient_ContainerInspect_Call) Return(_a0 types.ContainerJSON, _a1 error) *DockerClient_ContainerInspect_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DockerClient_ContainerInspect_Call) RunAndReturn(run func(context.Context, string) (types.ContainerJSON, error)) *DockerClient_ContainerInspect_Call { + _c.Call.Return(run) + return _c +} + +// ContainerList provides a mock function with given fields: _a0, _a1 +func (_m *DockerClient) ContainerList(_a0 context.Context, _a1 typescontainer.ListOptions) ([]types.Container, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for ContainerList") + } + + var r0 []types.Container + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, typescontainer.ListOptions) ([]types.Container, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, typescontainer.ListOptions) []types.Container); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]types.Container) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, typescontainer.ListOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DockerClient_ContainerList_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContainerList' +type DockerClient_ContainerList_Call struct { + *mock.Call +} + +// ContainerList is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 typescontainer.ListOptions +func (_e *DockerClient_Expecter) ContainerList(_a0 interface{}, _a1 interface{}) *DockerClient_ContainerList_Call { + return &DockerClient_ContainerList_Call{Call: _e.mock.On("ContainerList", _a0, _a1)} +} + +func (_c *DockerClient_ContainerList_Call) Run(run func(_a0 context.Context, _a1 typescontainer.ListOptions)) *DockerClient_ContainerList_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(typescontainer.ListOptions)) + }) + return _c +} + +func (_c *DockerClient_ContainerList_Call) Return(_a0 []types.Container, _a1 error) *DockerClient_ContainerList_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DockerClient_ContainerList_Call) RunAndReturn(run func(context.Context, typescontainer.ListOptions) ([]types.Container, error)) *DockerClient_ContainerList_Call { + _c.Call.Return(run) + return _c +} + +// ContainerRemove provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DockerClient) ContainerRemove(_a0 context.Context, _a1 string, _a2 typescontainer.RemoveOptions) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for ContainerRemove") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, typescontainer.RemoveOptions) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DockerClient_ContainerRemove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContainerRemove' +type DockerClient_ContainerRemove_Call struct { + *mock.Call +} + +// ContainerRemove is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 typescontainer.RemoveOptions +func (_e *DockerClient_Expecter) ContainerRemove(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DockerClient_ContainerRemove_Call { + return &DockerClient_ContainerRemove_Call{Call: _e.mock.On("ContainerRemove", _a0, _a1, _a2)} +} + +func (_c *DockerClient_ContainerRemove_Call) Run(run func(_a0 context.Context, _a1 string, _a2 typescontainer.RemoveOptions)) *DockerClient_ContainerRemove_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(typescontainer.RemoveOptions)) + }) + return _c +} + +func (_c *DockerClient_ContainerRemove_Call) Return(_a0 error) *DockerClient_ContainerRemove_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DockerClient_ContainerRemove_Call) RunAndReturn(run func(context.Context, string, typescontainer.RemoveOptions) error) *DockerClient_ContainerRemove_Call { + _c.Call.Return(run) + return _c +} + +// ContainerStart provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DockerClient) ContainerStart(_a0 context.Context, _a1 string, _a2 typescontainer.StartOptions) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for ContainerStart") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, typescontainer.StartOptions) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DockerClient_ContainerStart_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContainerStart' +type DockerClient_ContainerStart_Call struct { + *mock.Call +} + +// ContainerStart is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 typescontainer.StartOptions +func (_e *DockerClient_Expecter) ContainerStart(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DockerClient_ContainerStart_Call { + return &DockerClient_ContainerStart_Call{Call: _e.mock.On("ContainerStart", _a0, _a1, _a2)} +} + +func (_c *DockerClient_ContainerStart_Call) Run(run func(_a0 context.Context, _a1 string, _a2 typescontainer.StartOptions)) *DockerClient_ContainerStart_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(typescontainer.StartOptions)) + }) + return _c +} + +func (_c *DockerClient_ContainerStart_Call) Return(_a0 error) *DockerClient_ContainerStart_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DockerClient_ContainerStart_Call) RunAndReturn(run func(context.Context, string, typescontainer.StartOptions) error) *DockerClient_ContainerStart_Call { + _c.Call.Return(run) + return _c +} + +// ContainerStats provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DockerClient) ContainerStats(_a0 context.Context, _a1 string, _a2 bool) (typescontainer.StatsResponseReader, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for ContainerStats") + } + + var r0 typescontainer.StatsResponseReader + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, bool) (typescontainer.StatsResponseReader, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, string, bool) typescontainer.StatsResponseReader); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(typescontainer.StatsResponseReader) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, bool) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DockerClient_ContainerStats_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContainerStats' +type DockerClient_ContainerStats_Call struct { + *mock.Call +} + +// ContainerStats is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 bool +func (_e *DockerClient_Expecter) ContainerStats(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DockerClient_ContainerStats_Call { + return &DockerClient_ContainerStats_Call{Call: _e.mock.On("ContainerStats", _a0, _a1, _a2)} +} + +func (_c *DockerClient_ContainerStats_Call) Run(run func(_a0 context.Context, _a1 string, _a2 bool)) *DockerClient_ContainerStats_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(bool)) + }) + return _c +} + +func (_c *DockerClient_ContainerStats_Call) Return(_a0 typescontainer.StatsResponseReader, _a1 error) *DockerClient_ContainerStats_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DockerClient_ContainerStats_Call) RunAndReturn(run func(context.Context, string, bool) (typescontainer.StatsResponseReader, error)) *DockerClient_ContainerStats_Call { + _c.Call.Return(run) + return _c +} + +// ContainerStop provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DockerClient) ContainerStop(_a0 context.Context, _a1 string, _a2 typescontainer.StopOptions) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for ContainerStop") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, typescontainer.StopOptions) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DockerClient_ContainerStop_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContainerStop' +type DockerClient_ContainerStop_Call struct { + *mock.Call +} + +// ContainerStop is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 typescontainer.StopOptions +func (_e *DockerClient_Expecter) ContainerStop(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DockerClient_ContainerStop_Call { + return &DockerClient_ContainerStop_Call{Call: _e.mock.On("ContainerStop", _a0, _a1, _a2)} +} + +func (_c *DockerClient_ContainerStop_Call) Run(run func(_a0 context.Context, _a1 string, _a2 typescontainer.StopOptions)) *DockerClient_ContainerStop_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(typescontainer.StopOptions)) + }) + return _c +} + +func (_c *DockerClient_ContainerStop_Call) Return(_a0 error) *DockerClient_ContainerStop_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DockerClient_ContainerStop_Call) RunAndReturn(run func(context.Context, string, typescontainer.StopOptions) error) *DockerClient_ContainerStop_Call { + _c.Call.Return(run) + return _c +} + +// ContainerWait provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DockerClient) ContainerWait(_a0 context.Context, _a1 string, _a2 typescontainer.WaitCondition) (<-chan typescontainer.WaitResponse, <-chan error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for ContainerWait") + } + + var r0 <-chan typescontainer.WaitResponse + var r1 <-chan error + if rf, ok := ret.Get(0).(func(context.Context, string, typescontainer.WaitCondition) (<-chan typescontainer.WaitResponse, <-chan error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, string, typescontainer.WaitCondition) <-chan typescontainer.WaitResponse); ok { + r0 = rf(_a0, _a1, _a2) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan typescontainer.WaitResponse) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, typescontainer.WaitCondition) <-chan error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(<-chan error) + } + } + + return r0, r1 +} + +// DockerClient_ContainerWait_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ContainerWait' +type DockerClient_ContainerWait_Call struct { + *mock.Call +} + +// ContainerWait is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 typescontainer.WaitCondition +func (_e *DockerClient_Expecter) ContainerWait(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DockerClient_ContainerWait_Call { + return &DockerClient_ContainerWait_Call{Call: _e.mock.On("ContainerWait", _a0, _a1, _a2)} +} + +func (_c *DockerClient_ContainerWait_Call) Run(run func(_a0 context.Context, _a1 string, _a2 typescontainer.WaitCondition)) *DockerClient_ContainerWait_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(typescontainer.WaitCondition)) + }) + return _c +} + +func (_c *DockerClient_ContainerWait_Call) Return(_a0 <-chan typescontainer.WaitResponse, _a1 <-chan error) *DockerClient_ContainerWait_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DockerClient_ContainerWait_Call) RunAndReturn(run func(context.Context, string, typescontainer.WaitCondition) (<-chan typescontainer.WaitResponse, <-chan error)) *DockerClient_ContainerWait_Call { + _c.Call.Return(run) + return _c +} + +// Events provides a mock function with given fields: _a0, _a1 +func (_m *DockerClient) Events(_a0 context.Context, _a1 events.ListOptions) (<-chan events.Message, <-chan error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for Events") + } + + var r0 <-chan events.Message + var r1 <-chan error + if rf, ok := ret.Get(0).(func(context.Context, events.ListOptions) (<-chan events.Message, <-chan error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, events.ListOptions) <-chan events.Message); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(<-chan events.Message) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, events.ListOptions) <-chan error); ok { + r1 = rf(_a0, _a1) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(<-chan error) + } + } + + return r0, r1 +} + +// DockerClient_Events_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Events' +type DockerClient_Events_Call struct { + *mock.Call +} + +// Events is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 events.ListOptions +func (_e *DockerClient_Expecter) Events(_a0 interface{}, _a1 interface{}) *DockerClient_Events_Call { + return &DockerClient_Events_Call{Call: _e.mock.On("Events", _a0, _a1)} +} + +func (_c *DockerClient_Events_Call) Run(run func(_a0 context.Context, _a1 events.ListOptions)) *DockerClient_Events_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(events.ListOptions)) + }) + return _c +} + +func (_c *DockerClient_Events_Call) Return(_a0 <-chan events.Message, _a1 <-chan error) *DockerClient_Events_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DockerClient_Events_Call) RunAndReturn(run func(context.Context, events.ListOptions) (<-chan events.Message, <-chan error)) *DockerClient_Events_Call { + _c.Call.Return(run) + return _c +} + +// ImagePull provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DockerClient) ImagePull(_a0 context.Context, _a1 string, _a2 image.PullOptions) (io.ReadCloser, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for ImagePull") + } + + var r0 io.ReadCloser + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, image.PullOptions) (io.ReadCloser, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, string, image.PullOptions) io.ReadCloser); ok { + r0 = rf(_a0, _a1, _a2) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(io.ReadCloser) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, image.PullOptions) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DockerClient_ImagePull_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ImagePull' +type DockerClient_ImagePull_Call struct { + *mock.Call +} + +// ImagePull is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 image.PullOptions +func (_e *DockerClient_Expecter) ImagePull(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DockerClient_ImagePull_Call { + return &DockerClient_ImagePull_Call{Call: _e.mock.On("ImagePull", _a0, _a1, _a2)} +} + +func (_c *DockerClient_ImagePull_Call) Run(run func(_a0 context.Context, _a1 string, _a2 image.PullOptions)) *DockerClient_ImagePull_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(image.PullOptions)) + }) + return _c +} + +func (_c *DockerClient_ImagePull_Call) Return(_a0 io.ReadCloser, _a1 error) *DockerClient_ImagePull_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DockerClient_ImagePull_Call) RunAndReturn(run func(context.Context, string, image.PullOptions) (io.ReadCloser, error)) *DockerClient_ImagePull_Call { + _c.Call.Return(run) + return _c +} + +// NetworkConnect provides a mock function with given fields: _a0, _a1, _a2, _a3 +func (_m *DockerClient) NetworkConnect(_a0 context.Context, _a1 string, _a2 string, _a3 *network.EndpointSettings) error { + ret := _m.Called(_a0, _a1, _a2, _a3) + + if len(ret) == 0 { + panic("no return value specified for NetworkConnect") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, *network.EndpointSettings) error); ok { + r0 = rf(_a0, _a1, _a2, _a3) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DockerClient_NetworkConnect_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NetworkConnect' +type DockerClient_NetworkConnect_Call struct { + *mock.Call +} + +// NetworkConnect is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 string +// - _a3 *network.EndpointSettings +func (_e *DockerClient_Expecter) NetworkConnect(_a0 interface{}, _a1 interface{}, _a2 interface{}, _a3 interface{}) *DockerClient_NetworkConnect_Call { + return &DockerClient_NetworkConnect_Call{Call: _e.mock.On("NetworkConnect", _a0, _a1, _a2, _a3)} +} + +func (_c *DockerClient_NetworkConnect_Call) Run(run func(_a0 context.Context, _a1 string, _a2 string, _a3 *network.EndpointSettings)) *DockerClient_NetworkConnect_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string), args[3].(*network.EndpointSettings)) + }) + return _c +} + +func (_c *DockerClient_NetworkConnect_Call) Return(_a0 error) *DockerClient_NetworkConnect_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DockerClient_NetworkConnect_Call) RunAndReturn(run func(context.Context, string, string, *network.EndpointSettings) error) *DockerClient_NetworkConnect_Call { + _c.Call.Return(run) + return _c +} + +// NetworkCreate provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DockerClient) NetworkCreate(_a0 context.Context, _a1 string, _a2 network.CreateOptions) (network.CreateResponse, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for NetworkCreate") + } + + var r0 network.CreateResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, network.CreateOptions) (network.CreateResponse, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, string, network.CreateOptions) network.CreateResponse); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(network.CreateResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context, string, network.CreateOptions) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DockerClient_NetworkCreate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NetworkCreate' +type DockerClient_NetworkCreate_Call struct { + *mock.Call +} + +// NetworkCreate is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +// - _a2 network.CreateOptions +func (_e *DockerClient_Expecter) NetworkCreate(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DockerClient_NetworkCreate_Call { + return &DockerClient_NetworkCreate_Call{Call: _e.mock.On("NetworkCreate", _a0, _a1, _a2)} +} + +func (_c *DockerClient_NetworkCreate_Call) Run(run func(_a0 context.Context, _a1 string, _a2 network.CreateOptions)) *DockerClient_NetworkCreate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(network.CreateOptions)) + }) + return _c +} + +func (_c *DockerClient_NetworkCreate_Call) Return(_a0 network.CreateResponse, _a1 error) *DockerClient_NetworkCreate_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DockerClient_NetworkCreate_Call) RunAndReturn(run func(context.Context, string, network.CreateOptions) (network.CreateResponse, error)) *DockerClient_NetworkCreate_Call { + _c.Call.Return(run) + return _c +} + +// NetworkRemove provides a mock function with given fields: _a0, _a1 +func (_m *DockerClient) NetworkRemove(_a0 context.Context, _a1 string) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for NetworkRemove") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DockerClient_NetworkRemove_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'NetworkRemove' +type DockerClient_NetworkRemove_Call struct { + *mock.Call +} + +// NetworkRemove is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 string +func (_e *DockerClient_Expecter) NetworkRemove(_a0 interface{}, _a1 interface{}) *DockerClient_NetworkRemove_Call { + return &DockerClient_NetworkRemove_Call{Call: _e.mock.On("NetworkRemove", _a0, _a1)} +} + +func (_c *DockerClient_NetworkRemove_Call) Run(run func(_a0 context.Context, _a1 string)) *DockerClient_NetworkRemove_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *DockerClient_NetworkRemove_Call) Return(_a0 error) *DockerClient_NetworkRemove_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DockerClient_NetworkRemove_Call) RunAndReturn(run func(context.Context, string) error) *DockerClient_NetworkRemove_Call { + _c.Call.Return(run) + return _c +} + +// NewDockerClient creates a new instance of DockerClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewDockerClient(t interface { + mock.TestingT + Cleanup(func()) +}) *DockerClient { + mock := &DockerClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/go.mod b/go.mod index 04ae3e7..7c4f62b 100644 --- a/go.mod +++ b/go.mod @@ -15,24 +15,49 @@ require ( require ( github.com/Microsoft/go-winio v0.4.14 // indirect + github.com/chigopher/pathlib v0.19.1 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/gdamore/encoding v1.0.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/term v0.5.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rs/zerolog v1.33.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/cobra v1.8.1 // indirect + github.com/spf13/pflag v1.0.6 // indirect + github.com/spf13/viper v1.19.0 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/vektra/mockery/v2 v2.52.2 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect go.opentelemetry.io/otel v1.34.0 // indirect @@ -40,9 +65,17 @@ require ( go.opentelemetry.io/otel/metric v1.34.0 // indirect go.opentelemetry.io/otel/sdk v1.34.0 // indirect go.opentelemetry.io/otel/trace v1.34.0 // indirect - golang.org/x/sys v0.29.0 // indirect - golang.org/x/term v0.17.0 // indirect - golang.org/x/text v0.21.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac // indirect + golang.org/x/mod v0.23.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/term v0.29.0 // indirect + golang.org/x/text v0.22.0 // indirect golang.org/x/time v0.9.0 // indirect + golang.org/x/tools v0.30.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect gotest.tools/v3 v3.5.1 // indirect ) + +tool github.com/vektra/mockery/v2 diff --git a/go.sum b/go.sum index ac4767b..3315996 100644 --- a/go.sum +++ b/go.sum @@ -4,10 +4,15 @@ github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+q github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/chigopher/pathlib v0.19.1 h1:RoLlUJc0CqBGwq239cilyhxPNLXTK+HXoASGyGznx5A= +github.com/chigopher/pathlib v0.19.1/go.mod h1:tzC1dZLW8o33UQpWkNkhvPwL5n4yyFRFm/jL1YGWFvY= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U= @@ -18,6 +23,10 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= @@ -27,6 +36,7 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -35,6 +45,16 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -44,8 +64,21 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= @@ -56,11 +89,14 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57 h1:LmsF7Fk5jyEDhJk0fYIqdWNuTxSyid2W42A0L2YWjGE= github.com/rivo/tview v0.0.0-20241227133733-17b7edb88c57/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -69,13 +105,40 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/vektra/mockery/v2 v2.52.2 h1:8QfPKUIrq8P3Cs7G79Iu4Byd5wdhGCE0quIS27x7rQo= +github.com/vektra/mockery/v2 v2.52.2/go.mod h1:zGDY/f6bip0Yh13GQ5j7xa43fuEoYBa4ICHEaihisHw= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -97,14 +160,20 @@ go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac h1:l5+whBCLH3iH2ZNHYLbAe58bo7yrN4mVcnkHDYz5vvs= +golang.org/x/exp v0.0.0-20250210185358-939b2ce775ac/go.mod h1:hH+7mtFmImwwcMvScyxUhjuVHR3HGaDPMn9rMSUUbxo= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -112,13 +181,15 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -128,22 +199,26 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -152,10 +227,13 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f h1:gap6+3Gk41EItBuyi4XX/bp4oqJ3UwuIMl25yGinuAA= google.golang.org/genproto/googleapis/api v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:Ic02D47M+zbarjYYUlK57y316f2MoN0gjAwI3f2S95o= google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI= @@ -167,6 +245,8 @@ google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= diff --git a/mediaserver/actor.go b/mediaserver/actor.go index a7548ba..725c796 100644 --- a/mediaserver/actor.go +++ b/mediaserver/actor.go @@ -159,17 +159,32 @@ func (s *Actor) actorLoop(containerStateC <-chan domain.Container, errC <-chan e sendState() continue - case err := <-errC: + case err, ok := <-errC: + if !ok { + // The loop continues until the actor is closed. + // Avoid receiving duplicate close signals. + errC = nil + continue + } + if err != nil { s.logger.Error("Error from container client", "error", err, "id", shortID(s.state.Container.ID)) } fetchStateT.Stop() + // TODO: surface better error from container + if s.state.Container.ExitCode != nil { + s.state.ExitReason = fmt.Sprintf("Server process exited with code %d", *s.state.Container.ExitCode) + } else { + s.state.ExitReason = "Server process exited unexpectedly" + } + if s.state.Live { s.state.Live = false - sendState() } + + sendState() case <-fetchStateT.C: ingressState, err := s.fetchIngressState() if err != nil { diff --git a/terminal/actor.go b/terminal/actor.go index 15702f8..51b662c 100644 --- a/terminal/actor.go +++ b/terminal/actor.go @@ -128,6 +128,21 @@ func (a *Actor) actorLoop(ctx context.Context) { // SetState sets the state of the terminal user interface. func (a *Actor) SetState(state domain.AppState) { a.ch <- func() { + if state.Source.ExitReason != "" { + modal := tview.NewModal() + modal.SetText("Mediaserver error: " + state.Source.ExitReason). + AddButtons([]string{"Quit"}). + SetBackgroundColor(tcell.ColorBlack). + SetTextColor(tcell.ColorWhite). + SetDoneFunc(func(buttonIndex int, buttonLabel string) { + // TODO: improve app cleanup + a.app.Stop() + }) + modal.SetBorderStyle(tcell.StyleDefault.Background(tcell.ColorBlack).Foreground(tcell.ColorWhite)) + + a.app.SetRoot(modal, false) + } + a.redrawFromState(state) } } diff --git a/testhelpers/channel.go b/testhelpers/channel.go index fb020ce..1ecd001 100644 --- a/testhelpers/channel.go +++ b/testhelpers/channel.go @@ -1,7 +1,6 @@ package testhelpers import ( - "context" "log/slog" "testing" @@ -18,7 +17,7 @@ func ChanDiscard[T any](ch <-chan T) { } // ChanRequireNoError consumes a channel and asserts that no error is received. -func ChanRequireNoError(ctx context.Context, t testing.TB, ch <-chan error) { +func ChanRequireNoError(t testing.TB, ch <-chan error) { t.Helper() go func() { @@ -27,7 +26,7 @@ func ChanRequireNoError(ctx context.Context, t testing.TB, ch <-chan error) { case err := <-ch: require.NoError(t, err) return - case <-ctx.Done(): + case <-t.Context().Done(): return } } diff --git a/testhelpers/docker.go b/testhelpers/docker.go deleted file mode 100644 index 1038d7b..0000000 --- a/testhelpers/docker.go +++ /dev/null @@ -1,29 +0,0 @@ -package testhelpers - -import ( - "context" - "io" - - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/events" - "github.com/docker/docker/client" -) - -// MockDockerClient is a mock docker client. -// -// TODO: migrate to mockery. -type MockDockerClient struct { - *client.Client - - ContainerStatsResponse io.ReadCloser - EventsResponse func() <-chan events.Message - EventsErr <-chan error -} - -func (c *MockDockerClient) ContainerStats(context.Context, string, bool) (container.StatsResponseReader, error) { - return container.StatsResponseReader{Body: c.ContainerStatsResponse}, nil -} - -func (c *MockDockerClient) Events(context.Context, events.ListOptions) (<-chan events.Message, <-chan error) { - return c.EventsResponse(), c.EventsErr -}