client: refactor Events, Info, RegistryLogin

Co-authored-by: Claude <noreply@anthropic.com>
Signed-off-by: Austin Vazquez <austin.vazquez@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Austin Vazquez
2025-10-20 15:11:53 -05:00
committed by Sebastiaan van Stijn
parent c438b3fbbf
commit e46058cbae
32 changed files with 181 additions and 77 deletions

View File

@@ -7,9 +7,7 @@ import (
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/system"
)
@@ -176,9 +174,9 @@ type SwarmAPIClient interface {
// SystemAPIClient defines API client methods for the system
type SystemAPIClient interface {
Events(ctx context.Context, options EventsListOptions) (<-chan events.Message, <-chan error)
Info(ctx context.Context) (system.Info, error)
RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
Events(ctx context.Context, options EventsListOptions) EventsResult
Info(ctx context.Context, options InfoOptions) (SystemInfoResult, error)
RegistryLogin(ctx context.Context, auth RegistryLoginOptions) (RegistryLoginResult, error)
DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
Ping(ctx context.Context, options PingOptions) (PingResult, error)
}

View File

@@ -408,13 +408,13 @@ func TestNegotiateAPIVersionAutomatic(t *testing.T) {
// First request should trigger negotiation
pingVersion = "1.50"
expected = "1.50"
_, _ = client.Info(ctx)
_, _ = client.Info(ctx, InfoOptions{})
assert.Check(t, is.Equal(client.ClientVersion(), expected))
// Once successfully negotiated, subsequent requests should not re-negotiate
pingVersion = "1.49"
expected = "1.50"
_, _ = client.Info(ctx)
_, _ = client.Info(ctx, InfoOptions{})
assert.Check(t, is.Equal(client.ClientVersion(), expected))
}

View File

@@ -8,17 +8,38 @@ import (
"github.com/moby/moby/api/types/registry"
)
type RegistryLoginOptions struct {
Username string
Password string
ServerAddress string
IdentityToken string
RegistryToken string
}
// RegistryLoginResult holds the result of a RegistryLogin query.
type RegistryLoginResult struct {
Auth registry.AuthenticateOKBody
}
// RegistryLogin authenticates the docker server with a given docker registry.
// It returns unauthorizedError when the authentication fails.
func (cli *Client) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) {
func (cli *Client) RegistryLogin(ctx context.Context, options RegistryLoginOptions) (RegistryLoginResult, error) {
auth := registry.AuthConfig{
Username: options.Username,
Password: options.Password,
ServerAddress: options.ServerAddress,
IdentityToken: options.IdentityToken,
RegistryToken: options.RegistryToken,
}
resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
defer ensureReaderClosed(resp)
if err != nil {
return registry.AuthenticateOKBody{}, err
return RegistryLoginResult{}, err
}
var response registry.AuthenticateOKBody
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return RegistryLoginResult{Auth: response}, err
}

View File

@@ -19,11 +19,17 @@ type EventsListOptions struct {
Filters Filters
}
// EventsResult holds the result of an Events query.
type EventsResult struct {
Messages <-chan events.Message
Err <-chan error
}
// Events returns a stream of events in the daemon. It's up to the caller to close the stream
// by cancelling the context. Once the stream has been completely read an [io.EOF] error is
// sent over the error channel. If an error is sent, all processing is stopped. It's up
// to the caller to reopen the stream in the event of an error by reinvoking this method.
func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-chan events.Message, <-chan error) {
func (cli *Client) Events(ctx context.Context, options EventsListOptions) EventsResult {
messages := make(chan events.Message)
errs := make(chan error, 1)
@@ -76,7 +82,10 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
}()
<-started
return messages, errs
return EventsResult{
Messages: messages,
Err: errs,
}
}
func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {

View File

@@ -37,8 +37,8 @@ func TestEventsErrorInOptions(t *testing.T) {
for _, tc := range errorCases {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, errs := client.Events(context.Background(), tc.options)
err = <-errs
events := client.Events(context.Background(), tc.options)
err = <-events.Err
assert.Check(t, is.ErrorContains(err, tc.expectedError))
}
}
@@ -46,8 +46,8 @@ func TestEventsErrorInOptions(t *testing.T) {
func TestEventsErrorFromServer(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, errs := client.Events(context.Background(), EventsListOptions{})
err = <-errs
events := client.Events(context.Background(), EventsListOptions{})
err = <-events.Err
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
@@ -133,18 +133,18 @@ func TestEvents(t *testing.T) {
}))
assert.NilError(t, err)
messages, errs := client.Events(context.Background(), eventsCase.options)
events := client.Events(context.Background(), eventsCase.options)
loop:
for {
select {
case err := <-errs:
case err := <-events.Err:
if err != nil && !errors.Is(err, io.EOF) {
t.Fatal(err)
}
break loop
case e := <-messages:
case e := <-events.Messages:
_, ok := eventsCase.expectedEvents[e.Actor.ID]
assert.Check(t, ok, "event received not expected with action %s & id %s", e.Action, e.Actor.ID)
}

View File

@@ -9,18 +9,26 @@ import (
"github.com/moby/moby/api/types/system"
)
type InfoOptions struct {
// No options currently; placeholder for future use
}
type SystemInfoResult struct {
Info system.Info
}
// Info returns information about the docker server.
func (cli *Client) Info(ctx context.Context) (system.Info, error) {
var info system.Info
func (cli *Client) Info(ctx context.Context, options InfoOptions) (SystemInfoResult, error) {
resp, err := cli.get(ctx, "/info", url.Values{}, nil)
defer ensureReaderClosed(resp)
if err != nil {
return info, err
return SystemInfoResult{}, err
}
var info system.Info
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return info, fmt.Errorf("Error reading remote info: %v", err)
return SystemInfoResult{}, fmt.Errorf("Error reading remote info: %v", err)
}
return info, nil
return SystemInfoResult{Info: info}, nil
}

View File

@@ -14,14 +14,14 @@ import (
func TestInfoServerError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(errorMock(http.StatusInternalServerError, "Server error")))
assert.NilError(t, err)
_, err = client.Info(context.Background())
_, err = client.Info(context.Background(), InfoOptions{})
assert.Check(t, is.ErrorType(err, cerrdefs.IsInternal))
}
func TestInfoInvalidResponseJSONError(t *testing.T) {
client, err := NewClientWithOpts(WithMockClient(mockResponse(http.StatusOK, nil, "invalid json")))
assert.NilError(t, err)
_, err = client.Info(context.Background())
_, err = client.Info(context.Background(), InfoOptions{})
assert.Check(t, is.ErrorContains(err, "invalid character"))
}
@@ -38,8 +38,9 @@ func TestInfo(t *testing.T) {
}))
assert.NilError(t, err)
info, err := client.Info(context.Background())
result, err := client.Info(context.Background(), InfoOptions{})
assert.NilError(t, err)
info := result.Info
assert.Check(t, is.Equal(info.ID, "daemonID"))
assert.Check(t, is.Equal(info.Containers, 3))
@@ -68,8 +69,9 @@ func TestInfoWithDiscoveredDevices(t *testing.T) {
}))
assert.NilError(t, err)
info, err := client.Info(context.Background())
result, err := client.Info(context.Background(), InfoOptions{})
assert.NilError(t, err)
info := result.Info
assert.Check(t, is.Equal(info.ID, "daemonID"))
assert.Check(t, is.Equal(info.Containers, 3))

View File

@@ -756,9 +756,9 @@ func checkClusterHealth(t *testing.T, cl []*daemon.Daemon, managerCount, workerC
// check info in a poll.WaitOn(), because if the cluster doesn't have a leader, `info` will return an error
checkInfo := func(t *testing.T) (any, string) {
client := d.NewClientT(t)
daemonInfo, err := client.Info(ctx)
info = daemonInfo.Swarm
apiClient := d.NewClientT(t)
result, err := apiClient.Info(ctx, client.InfoOptions{})
info = result.Info.Swarm
return err, "cluster not ready in time"
}
poll.WaitOn(t, pollCheck(t, checkInfo, checker.IsNil()), poll.WithTimeout(defaultReconciliationTimeout))

View File

@@ -21,8 +21,9 @@ func (s *DockerCLIInfoSuite) TestInfoSecurityOptions(c *testing.T) {
apiClient, err := client.NewClientWithOpts(client.FromEnv)
assert.NilError(c, err)
defer apiClient.Close()
info, err := apiClient.Info(testutil.GetContext(c))
result, err := apiClient.Info(testutil.GetContext(c), client.InfoOptions{})
assert.NilError(c, err)
info := result.Info
if Apparmor() {
assert.Check(c, is.Contains(info.SecurityOptions, "name=apparmor"))

View File

@@ -54,8 +54,9 @@ func (s *DockerCLIPluginLogDriverSuite) TestPluginLogDriverInfoList(c *testing.T
assert.NilError(c, err)
defer apiClient.Close()
info, err := apiClient.Info(testutil.GetContext(c))
result, err := apiClient.Info(testutil.GetContext(c), client.InfoOptions{})
assert.NilError(c, err)
info := result.Info
drivers := strings.Join(info.Plugins.Log, " ")
assert.Assert(c, is.Contains(drivers, "json-file"))

View File

@@ -200,8 +200,9 @@ func daemonTime(t *testing.T) time.Time {
assert.NilError(t, err)
defer apiClient.Close()
info, err := apiClient.Info(testutil.GetContext(t))
result, err := apiClient.Info(testutil.GetContext(t), client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
assert.Assert(t, err == nil, "invalid time format in GET /info response")
@@ -286,11 +287,11 @@ func minimalBaseImage() string {
}
func getGoroutineNumber(ctx context.Context, apiClient client.APIClient) (int, error) {
info, err := apiClient.Info(ctx)
result, err := apiClient.Info(ctx, client.InfoOptions{})
if err != nil {
return 0, err
}
return info.NGoroutines, nil
return result.Info.NGoroutines, nil
}
func waitForStableGoroutineCount(ctx context.Context, t poll.TestingT, apiClient client.APIClient) int {

View File

@@ -740,10 +740,12 @@ func TestBuildEmitsImageCreateEvent(t *testing.T) {
assert.NilError(t, err)
buildLogs := out.String()
eventsChan, errs := apiClient.Events(ctx, client.EventsListOptions{
result := apiClient.Events(ctx, client.EventsListOptions{
Since: since.Format(time.RFC3339Nano),
Until: time.Now().Format(time.RFC3339Nano),
})
eventsChan := result.Messages
errs := result.Err
var eventsReceived []string
imageCreateEvts := 0

View File

@@ -188,8 +188,9 @@ func TestCDIInfoDiscoveredDevices(t *testing.T) {
defer d.Stop(t)
c := d.NewClientT(t)
info, err := c.Info(ctx)
result, err := c.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
assert.Check(t, is.Len(info.CDISpecDirs, 1))
assert.Check(t, is.Equal(info.CDISpecDirs[0], cdiDir))

View File

@@ -807,9 +807,10 @@ func TestContainerdContainerImageInfo(t *testing.T) {
apiClient := testEnv.APIClient()
defer apiClient.Close()
info, err := apiClient.Info(ctx)
result, err := apiClient.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
skip.If(t, info.Containerd == nil, "requires containerd")
// Currently a containerd container is only created when the container is started.

View File

@@ -98,10 +98,11 @@ func TestMountDaemonRoot(t *testing.T) {
ctx := setupTest(t)
apiClient := testEnv.APIClient()
info, err := apiClient.Info(ctx)
result, err := apiClient.Info(ctx, client.InfoOptions{})
if err != nil {
t.Fatal(err)
}
info := result.Info
for _, test := range []struct {
desc string

View File

@@ -39,11 +39,13 @@ func TestPause(t *testing.T) {
until := request.DaemonUnixTime(ctx, t, apiClient, testEnv)
messages, errs := apiClient.Events(ctx, client.EventsListOptions{
result := apiClient.Events(ctx, client.EventsListOptions{
Since: since,
Until: until,
Filters: make(client.Filters).Add(string(events.ContainerEventType), cID),
})
messages := result.Messages
errs := result.Err
assert.Check(t, is.DeepEqual([]events.Action{events.ActionPause, events.ActionUnPause}, getEventActions(t, messages, errs)))
}

View File

@@ -247,9 +247,11 @@ func TestContainerRestartWithCancelledRequest(t *testing.T) {
}()
// Start listening for events.
messages, errs := apiClient.Events(ctx, client.EventsListOptions{
result := apiClient.Events(ctx, client.EventsListOptions{
Filters: make(client.Filters).Add("container", cID).Add("event", string(events.ActionRestart)),
})
messages := result.Messages
errs := result.Err
// Make restart request, but cancel the request before the container
// is (forcibly) killed.

View File

@@ -21,9 +21,10 @@ func TestStats(t *testing.T) {
ctx := setupTest(t)
apiClient := testEnv.APIClient()
info, err := apiClient.Info(ctx)
result, err := apiClient.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
cID := container.Run(ctx, t, apiClient)
t.Run("no-stream", func(t *testing.T) {
resp, err := apiClient.ContainerStats(ctx, cID, client.ContainerStatsOptions{

View File

@@ -70,9 +70,9 @@ func CheckGoroutineCount(ctx context.Context, apiClient client.SystemAPIClient,
}
func getGoroutineNumber(ctx context.Context, apiClient client.SystemAPIClient) (int, error) {
info, err := apiClient.Info(ctx)
result, err := apiClient.Info(ctx, client.InfoOptions{})
if err != nil {
return 0, err
}
return info.NGoroutines, nil
return result.Info.NGoroutines, nil
}

View File

@@ -31,8 +31,9 @@ func TestInfoFirewallBackend(t *testing.T) {
if !testEnv.IsRootless() && networking.FirewalldRunning() {
expDriver += "+firewalld"
}
info, err := c.Info(ctx)
result, err := c.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
assert.Assert(t, info.FirewallBackend != nil, "expected firewall backend in info response")
t.Log("FirewallBackend: Driver:", info.FirewallBackend.Driver)
for _, kv := range info.FirewallBackend.Info {
@@ -43,8 +44,9 @@ func TestInfoFirewallBackend(t *testing.T) {
// Check FirewallBackend is omitted for API <= 1.48.
t.Run("api 1.48", func(t *testing.T) {
c148 := request.NewAPIClient(t, client.WithVersion("1.48"))
info148, err := c148.Info(ctx)
result, err := c148.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info148 := result.Info
assert.Check(t, is.Nil(info148.FirewallBackend))
})
}

View File

@@ -276,9 +276,10 @@ func systemTime(ctx context.Context, t *testing.T, apiClient client.APIClient, t
return time.Now()
}
info, err := apiClient.Info(ctx)
result, err := apiClient.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
assert.NilError(t, err, "invalid time format in GET /info response")
return dt
@@ -289,9 +290,9 @@ func systemEventsSince(ctx context.Context, apiClient client.APIClient, since st
Since: since,
}
ctx, cancel := context.WithCancel(ctx)
events, errs := apiClient.Events(ctx, eventOptions)
result := apiClient.Events(ctx, eventOptions)
return events, errs, cancel
return result.Messages, result.Err, cancel
}
func TestAuthZPluginErrorResponse(t *testing.T) {

View File

@@ -30,9 +30,11 @@ func TestEventsExecDie(t *testing.T) {
})
assert.NilError(t, err)
msg, errs := apiClient.Events(ctx, client.EventsListOptions{
result := apiClient.Events(ctx, client.EventsListOptions{
Filters: make(client.Filters).Add("container", cID).Add("event", string(events.ActionExecDie)),
})
msg := result.Messages
errs := result.Err
_, err = apiClient.ExecStart(ctx, res.ID, client.ExecStartOptions{
Detach: true,
@@ -107,11 +109,13 @@ func TestEventsVolumeCreate(t *testing.T) {
Add("type", "volume").
Add("event", "create").
Add("volume", volName)
messages, errs := apiClient.Events(ctx, client.EventsListOptions{
result := apiClient.Events(ctx, client.EventsListOptions{
Since: since,
Until: request.DaemonUnixTime(ctx, t, apiClient, testEnv),
Filters: filter,
})
messages := result.Messages
errs := result.Err
volEvents, err := getEvents(messages, errs)
assert.NilError(t, err)
@@ -123,11 +127,13 @@ func TestEventsVolumeCreate(t *testing.T) {
Target: "/tmp/foo",
}))
messages, errs = apiClient.Events(ctx, client.EventsListOptions{
result = apiClient.Events(ctx, client.EventsListOptions{
Since: since,
Until: request.DaemonUnixTime(ctx, t, apiClient, testEnv),
Filters: filter,
})
messages = result.Messages
errs = result.Err
volEvents, err = getEvents(messages, errs)
assert.NilError(t, err)

View File

@@ -6,6 +6,7 @@ import (
"testing"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/client"
"github.com/moby/moby/v2/internal/testutil"
"github.com/moby/moby/v2/internal/testutil/daemon"
"gotest.tools/v3/assert"
@@ -17,9 +18,10 @@ func TestInfoAPI(t *testing.T) {
ctx := setupTest(t)
apiClient := testEnv.APIClient()
info, err := apiClient.Info(ctx)
result, err := apiClient.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
// TODO(thaJeztah): make sure we have other tests that run a local daemon and check other fields based on known state.
assert.Check(t, info.ID != "")
assert.Check(t, is.Equal(info.Containers, info.ContainersRunning+info.ContainersPaused+info.ContainersStopped))
@@ -51,9 +53,11 @@ func TestInfoAPIWarnings(t *testing.T) {
d.Start(t, "-H=0.0.0.0:23756", "-H="+d.Sock())
defer d.Stop(t)
info, err := c.Info(ctx)
result, err := c.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
stringsToCheck := []string{
"Access to the remote API is equivalent to root access",
"http://0.0.0.0:23756",

View File

@@ -4,7 +4,7 @@ import (
"fmt"
"testing"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/client"
registrypkg "github.com/moby/moby/v2/daemon/pkg/registry"
"github.com/moby/moby/v2/integration/internal/requirement"
"gotest.tools/v3/assert"
@@ -19,7 +19,7 @@ func TestLoginFailsWithBadCredentials(t *testing.T) {
ctx := setupTest(t)
apiClient := testEnv.APIClient()
_, err := apiClient.RegistryLogin(ctx, registry.AuthConfig{
_, err := apiClient.RegistryLogin(ctx, client.RegistryLoginOptions{
Username: "no-user",
Password: "no-password",
})

View File

@@ -985,8 +985,9 @@ func (d *Daemon) queryRootDir() (string, error) {
func (d *Daemon) Info(t testing.TB) system.Info {
t.Helper()
c := d.NewClientT(t)
info, err := c.Info(context.Background())
result, err := c.Info(context.Background(), client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
assert.NilError(t, c.Close())
return info
}

View File

@@ -162,8 +162,9 @@ func (d *Daemon) SwarmLeave(ctx context.Context, t testing.TB, force bool) error
func (d *Daemon) SwarmInfo(ctx context.Context, t testing.TB) swarm.Info {
t.Helper()
cli := d.NewClientT(t)
info, err := cli.Info(ctx)
result, err := cli.Info(ctx, client.InfoOptions{})
assert.NilError(t, err, "get swarm info")
info := result.Info
return info.Swarm
}

View File

@@ -46,10 +46,11 @@ func New(ctx context.Context) (*Execution, error) {
// FromClient creates a new Execution environment from the passed in client
func FromClient(ctx context.Context, c *client.Client) (*Execution, error) {
info, err := c.Info(ctx)
result, err := c.Info(ctx, client.InfoOptions{})
if err != nil {
return nil, errors.Wrapf(err, "failed to get info from daemon")
}
info := result.Info
v, err := c.ServerVersion(context.Background())
if err != nil {
return nil, errors.Wrapf(err, "failed to get version info from daemon")

View File

@@ -33,14 +33,15 @@ func NewAPIClient(t testing.TB, ops ...client.Opt) client.APIClient {
}
// DaemonTime provides the current time on the daemon host
func DaemonTime(ctx context.Context, t testing.TB, client client.APIClient, testEnv *environment.Execution) time.Time {
func DaemonTime(ctx context.Context, t testing.TB, apiClient client.APIClient, testEnv *environment.Execution) time.Time {
t.Helper()
if testEnv.IsLocalDaemon() {
return time.Now()
}
info, err := client.Info(ctx)
result, err := apiClient.Info(ctx, client.InfoOptions{})
assert.NilError(t, err)
info := result.Info
dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
assert.NilError(t, err, "invalid time format in GET /info response")

View File

@@ -7,9 +7,7 @@ import (
"github.com/moby/moby/api/types"
"github.com/moby/moby/api/types/container"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/network"
"github.com/moby/moby/api/types/registry"
"github.com/moby/moby/api/types/system"
)
@@ -176,9 +174,9 @@ type SwarmAPIClient interface {
// SystemAPIClient defines API client methods for the system
type SystemAPIClient interface {
Events(ctx context.Context, options EventsListOptions) (<-chan events.Message, <-chan error)
Info(ctx context.Context) (system.Info, error)
RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error)
Events(ctx context.Context, options EventsListOptions) EventsResult
Info(ctx context.Context, options InfoOptions) (SystemInfoResult, error)
RegistryLogin(ctx context.Context, auth RegistryLoginOptions) (RegistryLoginResult, error)
DiskUsage(ctx context.Context, options DiskUsageOptions) (system.DiskUsage, error)
Ping(ctx context.Context, options PingOptions) (PingResult, error)
}

View File

@@ -8,17 +8,38 @@ import (
"github.com/moby/moby/api/types/registry"
)
type RegistryLoginOptions struct {
Username string
Password string
ServerAddress string
IdentityToken string
RegistryToken string
}
// RegistryLoginResult holds the result of a RegistryLogin query.
type RegistryLoginResult struct {
Auth registry.AuthenticateOKBody
}
// RegistryLogin authenticates the docker server with a given docker registry.
// It returns unauthorizedError when the authentication fails.
func (cli *Client) RegistryLogin(ctx context.Context, auth registry.AuthConfig) (registry.AuthenticateOKBody, error) {
func (cli *Client) RegistryLogin(ctx context.Context, options RegistryLoginOptions) (RegistryLoginResult, error) {
auth := registry.AuthConfig{
Username: options.Username,
Password: options.Password,
ServerAddress: options.ServerAddress,
IdentityToken: options.IdentityToken,
RegistryToken: options.RegistryToken,
}
resp, err := cli.post(ctx, "/auth", url.Values{}, auth, nil)
defer ensureReaderClosed(resp)
if err != nil {
return registry.AuthenticateOKBody{}, err
return RegistryLoginResult{}, err
}
var response registry.AuthenticateOKBody
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
return RegistryLoginResult{Auth: response}, err
}

View File

@@ -19,11 +19,17 @@ type EventsListOptions struct {
Filters Filters
}
// EventsResult holds the result of an Events query.
type EventsResult struct {
Messages <-chan events.Message
Err <-chan error
}
// Events returns a stream of events in the daemon. It's up to the caller to close the stream
// by cancelling the context. Once the stream has been completely read an [io.EOF] error is
// sent over the error channel. If an error is sent, all processing is stopped. It's up
// to the caller to reopen the stream in the event of an error by reinvoking this method.
func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-chan events.Message, <-chan error) {
func (cli *Client) Events(ctx context.Context, options EventsListOptions) EventsResult {
messages := make(chan events.Message)
errs := make(chan error, 1)
@@ -76,7 +82,10 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) (<-cha
}()
<-started
return messages, errs
return EventsResult{
Messages: messages,
Err: errs,
}
}
func buildEventsQueryParams(options EventsListOptions) (url.Values, error) {

View File

@@ -9,18 +9,26 @@ import (
"github.com/moby/moby/api/types/system"
)
type InfoOptions struct {
// No options currently; placeholder for future use
}
type SystemInfoResult struct {
Info system.Info
}
// Info returns information about the docker server.
func (cli *Client) Info(ctx context.Context) (system.Info, error) {
var info system.Info
func (cli *Client) Info(ctx context.Context, options InfoOptions) (SystemInfoResult, error) {
resp, err := cli.get(ctx, "/info", url.Values{}, nil)
defer ensureReaderClosed(resp)
if err != nil {
return info, err
return SystemInfoResult{}, err
}
var info system.Info
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return info, fmt.Errorf("Error reading remote info: %v", err)
return SystemInfoResult{}, fmt.Errorf("Error reading remote info: %v", err)
}
return info, nil
return SystemInfoResult{Info: info}, nil
}