client: refactor ContainerTop to wrap options and results

Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
This commit is contained in:
Austin Vazquez
2025-10-28 18:26:08 -05:00
parent 4ce86e2c9b
commit ec22a1e5b2
6 changed files with 44 additions and 20 deletions

View File

@@ -73,7 +73,7 @@ type ContainerAPIClient interface {
ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error) ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error)
ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error) ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error)
ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, error) ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, error)
ContainerTop(ctx context.Context, container string, arguments []string) (container.TopResponse, error) ContainerTop(ctx context.Context, container string, options ContainerTopOptions) (ContainerTopResult, error)
ContainerUnpause(ctx context.Context, container string, options ContainerUnPauseOptions) (ContainerUnPauseResult, error) ContainerUnpause(ctx context.Context, container string, options ContainerUnPauseOptions) (ContainerUnPauseResult, error)
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error) ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error)
ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)

View File

@@ -9,25 +9,36 @@ import (
"github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/container"
) )
// ContainerTopOptions defines options for container top operations.
type ContainerTopOptions struct {
Arguments []string
}
// ContainerTopResult represents the result of a ContainerTop operation.
type ContainerTopResult struct {
Processes [][]string
Titles []string
}
// ContainerTop shows process information from within a container. // ContainerTop shows process information from within a container.
func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.TopResponse, error) { func (cli *Client) ContainerTop(ctx context.Context, containerID string, options ContainerTopOptions) (ContainerTopResult, error) {
containerID, err := trimID("container", containerID) containerID, err := trimID("container", containerID)
if err != nil { if err != nil {
return container.TopResponse{}, err return ContainerTopResult{}, err
} }
query := url.Values{} query := url.Values{}
if len(arguments) > 0 { if len(options.Arguments) > 0 {
query.Set("ps_args", strings.Join(arguments, " ")) query.Set("ps_args", strings.Join(options.Arguments, " "))
} }
resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil) resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return container.TopResponse{}, err return ContainerTopResult{}, err
} }
var response container.TopResponse var response container.TopResponse
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&response)
return response, err return ContainerTopResult{Processes: response.Processes, Titles: response.Titles}, err
} }

View File

@@ -15,14 +15,14 @@ import (
func TestContainerTopError(t *testing.T) { func TestContainerTopError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error"))) client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err) assert.NilError(t, err)
_, err = client.ContainerTop(context.Background(), "nothing", []string{}) _, err = client.ContainerTop(context.Background(), "nothing", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
_, err = client.ContainerTop(context.Background(), "", []string{}) _, err = client.ContainerTop(context.Background(), "", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
_, err = client.ContainerTop(context.Background(), " ", []string{}) _, err = client.ContainerTop(context.Background(), " ", ContainerTopOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument)) assert.Check(t, is.ErrorType(err, cerrdefs.IsInvalidArgument))
assert.Check(t, is.ErrorContains(err, "value is empty")) assert.Check(t, is.ErrorContains(err, "value is empty"))
} }
@@ -54,7 +54,9 @@ func TestContainerTop(t *testing.T) {
})) }))
assert.NilError(t, err) assert.NilError(t, err)
processList, err := client.ContainerTop(context.Background(), "container_id", []string{"arg1", "arg2"}) processList, err := client.ContainerTop(context.Background(), "container_id", ContainerTopOptions{
Arguments: []string{"arg1", "arg2"},
})
assert.NilError(t, err) assert.NilError(t, err)
assert.Check(t, is.DeepEqual(expectedProcesses, processList.Processes)) assert.Check(t, is.DeepEqual(expectedProcesses, processList.Processes))
assert.Check(t, is.DeepEqual(expectedTitles, processList.Titles)) assert.Check(t, is.DeepEqual(expectedTitles, processList.Titles))

View File

@@ -393,7 +393,7 @@ func (s *DockerAPISuite) TestContainerAPITop(c *testing.T) {
defer apiClient.Close() defer apiClient.Close()
// sort by comm[andline] to make sure order stays the same in case of PID rollover // sort by comm[andline] to make sure order stays the same in case of PID rollover
top, err := apiClient.ContainerTop(testutil.GetContext(c), id, []string{"aux", "--sort=comm"}) top, err := apiClient.ContainerTop(testutil.GetContext(c), id, client.ContainerTopOptions{Arguments: []string{"aux", "--sort=comm"}})
assert.NilError(c, err) assert.NilError(c, err)
assert.Equal(c, len(top.Titles), 11, fmt.Sprintf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles)) assert.Equal(c, len(top.Titles), 11, fmt.Sprintf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles))
@@ -414,7 +414,7 @@ func (s *DockerAPISuite) TestContainerAPITopWindows(c *testing.T) {
assert.NilError(c, err) assert.NilError(c, err)
defer apiClient.Close() defer apiClient.Close()
top, err := apiClient.ContainerTop(testutil.GetContext(c), id, nil) top, err := apiClient.ContainerTop(testutil.GetContext(c), id, client.ContainerTopOptions{})
assert.NilError(c, err) assert.NilError(c, err)
assert.Equal(c, len(top.Titles), 4, "expected 4 titles, found %d: %v", len(top.Titles), top.Titles) assert.Equal(c, len(top.Titles), 4, "expected 4 titles, found %d: %v", len(top.Titles), top.Titles)

View File

@@ -73,7 +73,7 @@ type ContainerAPIClient interface {
ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error) ContainerStats(ctx context.Context, container string, options ContainerStatsOptions) (ContainerStatsResult, error)
ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error) ContainerStart(ctx context.Context, container string, options ContainerStartOptions) (ContainerStartResult, error)
ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, error) ContainerStop(ctx context.Context, container string, options ContainerStopOptions) (ContainerStopResult, error)
ContainerTop(ctx context.Context, container string, arguments []string) (container.TopResponse, error) ContainerTop(ctx context.Context, container string, options ContainerTopOptions) (ContainerTopResult, error)
ContainerUnpause(ctx context.Context, container string, options ContainerUnPauseOptions) (ContainerUnPauseResult, error) ContainerUnpause(ctx context.Context, container string, options ContainerUnPauseOptions) (ContainerUnPauseResult, error)
ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error) ContainerUpdate(ctx context.Context, container string, updateConfig container.UpdateConfig) (container.UpdateResponse, error)
ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) ContainerWait(ctx context.Context, container string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error)

View File

@@ -9,25 +9,36 @@ import (
"github.com/moby/moby/api/types/container" "github.com/moby/moby/api/types/container"
) )
// ContainerTopOptions defines options for container top operations.
type ContainerTopOptions struct {
Arguments []string
}
// ContainerTopResult represents the result of a ContainerTop operation.
type ContainerTopResult struct {
Processes [][]string
Titles []string
}
// ContainerTop shows process information from within a container. // ContainerTop shows process information from within a container.
func (cli *Client) ContainerTop(ctx context.Context, containerID string, arguments []string) (container.TopResponse, error) { func (cli *Client) ContainerTop(ctx context.Context, containerID string, options ContainerTopOptions) (ContainerTopResult, error) {
containerID, err := trimID("container", containerID) containerID, err := trimID("container", containerID)
if err != nil { if err != nil {
return container.TopResponse{}, err return ContainerTopResult{}, err
} }
query := url.Values{} query := url.Values{}
if len(arguments) > 0 { if len(options.Arguments) > 0 {
query.Set("ps_args", strings.Join(arguments, " ")) query.Set("ps_args", strings.Join(options.Arguments, " "))
} }
resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil) resp, err := cli.get(ctx, "/containers/"+containerID+"/top", query, nil)
defer ensureReaderClosed(resp) defer ensureReaderClosed(resp)
if err != nil { if err != nil {
return container.TopResponse{}, err return ContainerTopResult{}, err
} }
var response container.TopResponse var response container.TopResponse
err = json.NewDecoder(resp.Body).Decode(&response) err = json.NewDecoder(resp.Body).Decode(&response)
return response, err return ContainerTopResult{Processes: response.Processes, Titles: response.Titles}, err
} }