Merge pull request #50521 from thaJeztah/move_StatsResponseReader

api/types/container.StatsResponseReader: move to client
This commit is contained in:
Sebastiaan van Stijn
2025-07-28 12:31:33 +02:00
committed by GitHub
8 changed files with 59 additions and 60 deletions

View File

@@ -1,7 +1,6 @@
package container
import (
"io"
"os"
"time"
@@ -35,18 +34,6 @@ type CopyToContainerOptions struct {
CopyUIDGID bool
}
// StatsResponseReader wraps an io.ReadCloser to read (a stream of) stats
// for a container, as produced by the GET "/stats" endpoint.
//
// The OSType field is set to the server's platform to allow
// platform-specific handling of the response.
//
// TODO(thaJeztah): remove this wrapper, and make OSType part of [StatsResponse].
type StatsResponseReader struct {
Body io.ReadCloser `json:"body"`
OSType string `json:"ostype"`
}
// MountPoint represents a mount point configuration inside the container.
// This is used for reporting the mountpoints in use by a container.
type MountPoint struct {

View File

@@ -85,8 +85,8 @@ type ContainerAPIClient interface {
ContainerResize(ctx context.Context, container string, options container.ResizeOptions) error
ContainerRestart(ctx context.Context, container string, options container.StopOptions) error
ContainerStatPath(ctx context.Context, container, path string) (container.PathStat, error)
ContainerStats(ctx context.Context, container string, stream bool) (container.StatsResponseReader, error)
ContainerStatsOneShot(ctx context.Context, container string) (container.StatsResponseReader, error)
ContainerStats(ctx context.Context, container string, stream bool) (StatsResponseReader, error)
ContainerStatsOneShot(ctx context.Context, container string) (StatsResponseReader, error)
ContainerStart(ctx context.Context, container string, options container.StartOptions) error
ContainerStop(ctx context.Context, container string, options container.StopOptions) error
ContainerTop(ctx context.Context, container string, arguments []string) (container.TopResponse, error)

View File

@@ -2,17 +2,28 @@ package client
import (
"context"
"io"
"net/url"
"github.com/moby/moby/api/types/container"
)
// StatsResponseReader wraps an io.ReadCloser to read (a stream of) stats
// for a container, as produced by the GET "/stats" endpoint.
//
// The OSType field is set to the server's platform to allow
// platform-specific handling of the response.
//
// TODO(thaJeztah): remove this wrapper, and make OSType part of [github.com/moby/moby/api/types/container.StatsResponse].
type StatsResponseReader struct {
Body io.ReadCloser `json:"body"`
OSType string `json:"ostype"`
}
// ContainerStats returns near realtime stats for a given container.
// It's up to the caller to close the io.ReadCloser returned.
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) {
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (StatsResponseReader, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
return StatsResponseReader{}, err
}
query := url.Values{}
@@ -23,10 +34,10 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
if err != nil {
return container.StatsResponseReader{}, err
return StatsResponseReader{}, err
}
return container.StatsResponseReader{
return StatsResponseReader{
Body: resp.Body,
OSType: resp.Header.Get("Ostype"),
}, nil
@@ -34,10 +45,10 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
// ContainerStatsOneShot gets a single stat entry from a container.
// It differs from `ContainerStats` in that the API should not wait to prime the stats
func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (container.StatsResponseReader, error) {
func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (StatsResponseReader, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
return StatsResponseReader{}, err
}
query := url.Values{}
@@ -46,10 +57,10 @@ func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
if err != nil {
return container.StatsResponseReader{}, err
return StatsResponseReader{}, err
}
return container.StatsResponseReader{
return StatsResponseReader{
Body: resp.Body,
OSType: resp.Header.Get("Ostype"),
}, nil

View File

@@ -48,7 +48,7 @@ func TestContainerStats(t *testing.T) {
client := &Client{
client: newMockClient(func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, r.URL)
}
query := r.URL.Query()
@@ -65,7 +65,9 @@ func TestContainerStats(t *testing.T) {
}
resp, err := client.ContainerStats(context.Background(), "container_id", c.stream)
assert.NilError(t, err)
defer resp.Body.Close()
t.Cleanup(func() {
_ = resp.Body.Close()
})
content, err := io.ReadAll(resp.Body)
assert.NilError(t, err)
assert.Check(t, is.Equal(string(content), "response"))

View File

@@ -110,10 +110,10 @@ func (s *DockerAPISuite) TestContainerAPIGetExport(c *testing.T) {
found := false
for tarReader := tar.NewReader(body); ; {
h, err := tarReader.Next()
if err != nil && err == io.EOF {
if errors.Is(err, io.EOF) {
break
}
if h.Name == "test" {
if h != nil && h.Name == "test" {
found = true
break
}
@@ -149,7 +149,7 @@ func (s *DockerAPISuite) TestGetContainerStats(c *testing.T) {
runSleepingContainer(c, "--name", name)
type b struct {
stats container.StatsResponseReader
stats client.StatsResponseReader
err error
}
@@ -175,10 +175,11 @@ func (s *DockerAPISuite) TestGetContainerStats(c *testing.T) {
c.Fatal("stream was not closed after container was removed")
case sr := <-bc:
dec := json.NewDecoder(sr.stats.Body)
defer sr.stats.Body.Close()
var s *container.StatsResponse
// decode only one object from the stream
assert.NilError(c, dec.Decode(&s))
err := dec.Decode(&s)
_ = sr.stats.Body.Close()
assert.NilError(c, err)
}
}
@@ -253,7 +254,7 @@ func (s *DockerAPISuite) TestGetContainerStatsStream(c *testing.T) {
runSleepingContainer(c, "--name", name)
type b struct {
stats container.StatsResponseReader
stats client.StatsResponseReader
err error
}
@@ -294,7 +295,7 @@ func (s *DockerAPISuite) TestGetContainerStatsNoStream(c *testing.T) {
runSleepingContainer(c, "--name", name)
type b struct {
stats container.StatsResponseReader
stats client.StatsResponseReader
err error
}
@@ -1229,7 +1230,7 @@ func (s *DockerAPISuite) TestContainerAPIStatsWithNetworkDisabled(c *testing.T)
cli.WaitRun(c, name)
type b struct {
stats container.StatsResponseReader
stats client.StatsResponseReader
err error
}
bc := make(chan b, 1)

View File

@@ -1,7 +1,6 @@
package container
import (
"io"
"os"
"time"
@@ -35,18 +34,6 @@ type CopyToContainerOptions struct {
CopyUIDGID bool
}
// StatsResponseReader wraps an io.ReadCloser to read (a stream of) stats
// for a container, as produced by the GET "/stats" endpoint.
//
// The OSType field is set to the server's platform to allow
// platform-specific handling of the response.
//
// TODO(thaJeztah): remove this wrapper, and make OSType part of [StatsResponse].
type StatsResponseReader struct {
Body io.ReadCloser `json:"body"`
OSType string `json:"ostype"`
}
// MountPoint represents a mount point configuration inside the container.
// This is used for reporting the mountpoints in use by a container.
type MountPoint struct {

View File

@@ -85,8 +85,8 @@ type ContainerAPIClient interface {
ContainerResize(ctx context.Context, container string, options container.ResizeOptions) error
ContainerRestart(ctx context.Context, container string, options container.StopOptions) error
ContainerStatPath(ctx context.Context, container, path string) (container.PathStat, error)
ContainerStats(ctx context.Context, container string, stream bool) (container.StatsResponseReader, error)
ContainerStatsOneShot(ctx context.Context, container string) (container.StatsResponseReader, error)
ContainerStats(ctx context.Context, container string, stream bool) (StatsResponseReader, error)
ContainerStatsOneShot(ctx context.Context, container string) (StatsResponseReader, error)
ContainerStart(ctx context.Context, container string, options container.StartOptions) error
ContainerStop(ctx context.Context, container string, options container.StopOptions) error
ContainerTop(ctx context.Context, container string, arguments []string) (container.TopResponse, error)

View File

@@ -2,17 +2,28 @@ package client
import (
"context"
"io"
"net/url"
"github.com/moby/moby/api/types/container"
)
// StatsResponseReader wraps an io.ReadCloser to read (a stream of) stats
// for a container, as produced by the GET "/stats" endpoint.
//
// The OSType field is set to the server's platform to allow
// platform-specific handling of the response.
//
// TODO(thaJeztah): remove this wrapper, and make OSType part of [github.com/moby/moby/api/types/container.StatsResponse].
type StatsResponseReader struct {
Body io.ReadCloser `json:"body"`
OSType string `json:"ostype"`
}
// ContainerStats returns near realtime stats for a given container.
// It's up to the caller to close the io.ReadCloser returned.
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (container.StatsResponseReader, error) {
func (cli *Client) ContainerStats(ctx context.Context, containerID string, stream bool) (StatsResponseReader, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
return StatsResponseReader{}, err
}
query := url.Values{}
@@ -23,10 +34,10 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
if err != nil {
return container.StatsResponseReader{}, err
return StatsResponseReader{}, err
}
return container.StatsResponseReader{
return StatsResponseReader{
Body: resp.Body,
OSType: resp.Header.Get("Ostype"),
}, nil
@@ -34,10 +45,10 @@ func (cli *Client) ContainerStats(ctx context.Context, containerID string, strea
// ContainerStatsOneShot gets a single stat entry from a container.
// It differs from `ContainerStats` in that the API should not wait to prime the stats
func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (container.StatsResponseReader, error) {
func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string) (StatsResponseReader, error) {
containerID, err := trimID("container", containerID)
if err != nil {
return container.StatsResponseReader{}, err
return StatsResponseReader{}, err
}
query := url.Values{}
@@ -46,10 +57,10 @@ func (cli *Client) ContainerStatsOneShot(ctx context.Context, containerID string
resp, err := cli.get(ctx, "/containers/"+containerID+"/stats", query, nil)
if err != nil {
return container.StatsResponseReader{}, err
return StatsResponseReader{}, err
}
return container.StatsResponseReader{
return StatsResponseReader{
Body: resp.Body,
OSType: resp.Header.Get("Ostype"),
}, nil