feat: quit if mediaserver fails to start

This commit is contained in:
Rob Watson 2025-02-12 07:00:00 +01:00 committed by Rob Watson
parent 6e369a20ca
commit 1dfe7fea38
14 changed files with 1227 additions and 70 deletions

11
.mockery.yaml Normal file
View File

@ -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:

View File

@ -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)

192
container/container_test.go Normal file
View File

@ -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())
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

43
go.mod
View File

@ -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

98
go.sum
View File

@ -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=

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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
}