vendor: docker/cli v29.3.0, moby/api v1.54.0, moby/client v0.3.0
This also allows the client to connect with API v1.40 and up (Docker 19.03), which previously was API v1.43 and up (Docker 25.0 and up). Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
+5
@@ -73,6 +73,11 @@ type ImageProperties struct {
|
||||
// Required: true
|
||||
Platform ocispec.Platform `json:"Platform"`
|
||||
|
||||
// Identity holds information about the identity and origin of the image.
|
||||
// For image list responses, this can duplicate Build/Pull fields across
|
||||
// image manifests, because those parts of identity are image-level metadata.
|
||||
Identity *Identity `json:"Identity,omitempty"`
|
||||
|
||||
Size struct {
|
||||
// Unpacked is the size (in bytes) of the locally unpacked
|
||||
// (uncompressed) image content that's directly usable by the containers
|
||||
|
||||
+3
@@ -8,5 +8,8 @@ type Error struct {
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
if e == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return e.Message
|
||||
}
|
||||
|
||||
+22
@@ -1,6 +1,7 @@
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"net/netip"
|
||||
|
||||
"github.com/moby/moby/api/types/network"
|
||||
@@ -41,6 +42,27 @@ type PortConfig struct {
|
||||
PublishMode PortConfigPublishMode `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Compare returns the lexical ordering of p and other, and can be used
|
||||
// with [slices.SortFunc].
|
||||
//
|
||||
// The comparison is performed in the following priority order:
|
||||
// 1. PublishedPort (host port)
|
||||
// 2. TargetPort (container port)
|
||||
// 3. Protocol
|
||||
// 4. PublishMode
|
||||
func (p PortConfig) Compare(other PortConfig) int {
|
||||
if n := cmp.Compare(p.PublishedPort, other.PublishedPort); n != 0 {
|
||||
return n
|
||||
}
|
||||
if n := cmp.Compare(p.TargetPort, other.TargetPort); n != 0 {
|
||||
return n
|
||||
}
|
||||
if n := cmp.Compare(p.Protocol, other.Protocol); n != 0 {
|
||||
return n
|
||||
}
|
||||
return cmp.Compare(p.PublishMode, other.PublishMode)
|
||||
}
|
||||
|
||||
// PortConfigPublishMode represents the mode in which the port is to
|
||||
// be published.
|
||||
type PortConfigPublishMode string
|
||||
|
||||
+24
-12
@@ -1,21 +1,33 @@
|
||||
package types
|
||||
|
||||
// MediaType represents an HTTP media type (MIME type) used in API
|
||||
// Content-Type and Accept headers.
|
||||
//
|
||||
// In addition to standard media types (for example, "application/json"),
|
||||
// this package defines vendor-specific vendor media types for streaming
|
||||
// endpoints, such as raw TTY streams and multiplexed stdout/stderr streams.
|
||||
type MediaType = string
|
||||
|
||||
const (
|
||||
// MediaTypeRawStream is vendor specific MIME-Type set for raw TTY streams.
|
||||
MediaTypeRawStream = "application/vnd.docker.raw-stream"
|
||||
// MediaTypeRawStream is a vendor-specific media type for raw TTY streams.
|
||||
MediaTypeRawStream MediaType = "application/vnd.docker.raw-stream"
|
||||
|
||||
// MediaTypeMultiplexedStream is vendor specific MIME-Type set for stdin/stdout/stderr multiplexed streams.
|
||||
MediaTypeMultiplexedStream = "application/vnd.docker.multiplexed-stream"
|
||||
// MediaTypeMultiplexedStream is a vendor-specific media type for streams
|
||||
// where stdin, stdout, and stderr are multiplexed into a single byte stream.
|
||||
//
|
||||
// Use stdcopy.StdCopy (https://pkg.go.dev/github.com/moby/moby/api/pkg/stdcopy)
|
||||
// to demultiplex the stream.
|
||||
MediaTypeMultiplexedStream MediaType = "application/vnd.docker.multiplexed-stream"
|
||||
|
||||
// MediaTypeJSON is the MIME-Type for JSON objects.
|
||||
MediaTypeJSON = "application/json"
|
||||
// MediaTypeJSON is the media type for JSON objects.
|
||||
MediaTypeJSON MediaType = "application/json"
|
||||
|
||||
// MediaTypeNDJSON is the MIME-Type for Newline Delimited JSON objects streams (https://github.com/ndjson/ndjson-spec).
|
||||
MediaTypeNDJSON = "application/x-ndjson"
|
||||
// MediaTypeNDJSON is the media type for newline-delimited JSON streams (https://github.com/ndjson/ndjson-spec).
|
||||
MediaTypeNDJSON MediaType = "application/x-ndjson"
|
||||
|
||||
// MediaTypeJSONLines is the MIME-Type for JSONLines objects streams (https://jsonlines.org/).
|
||||
MediaTypeJSONLines = "application/jsonl"
|
||||
// MediaTypeJSONLines is the media type for JSON Lines streams (https://jsonlines.org/).
|
||||
MediaTypeJSONLines MediaType = "application/jsonl"
|
||||
|
||||
// MediaTypeJSONSequence is the MIME-Type for JSON Text Sequences (RFC7464).
|
||||
MediaTypeJSONSequence = "application/json-seq"
|
||||
// MediaTypeJSONSequence is the media type for JSON text sequences (RFC 7464).
|
||||
MediaTypeJSONSequence MediaType = "application/json-seq"
|
||||
)
|
||||
|
||||
+3
-6
@@ -107,11 +107,11 @@ const DummyHost = "api.moby.localhost"
|
||||
// overriding the version and disable API-version negotiation.
|
||||
//
|
||||
// This version may be lower than the version of the api library module used.
|
||||
const MaxAPIVersion = "1.53"
|
||||
const MaxAPIVersion = "1.54"
|
||||
|
||||
// MinAPIVersion is the minimum API version supported by the client. API versions
|
||||
// below this version are not considered when performing API-version negotiation.
|
||||
const MinAPIVersion = "1.44"
|
||||
const MinAPIVersion = "1.40"
|
||||
|
||||
// Ensure that Client always implements APIClient.
|
||||
var _ APIClient = &Client{}
|
||||
@@ -179,10 +179,7 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
||||
// [WithAPIVersionFromEnv] to configure the client with a fixed API version
|
||||
// and disable API version negotiation.
|
||||
//
|
||||
// cli, err := client.New(
|
||||
// client.FromEnv,
|
||||
// client.WithAPIVersionNegotiation(),
|
||||
// )
|
||||
// cli, err := client.New(client.FromEnv)
|
||||
func New(ops ...Opt) (*Client, error) {
|
||||
hostURL, err := ParseHostURL(DefaultDockerHost)
|
||||
if err != nil {
|
||||
|
||||
+17
-1
@@ -97,7 +97,23 @@ func (cli *Client) ContainerLogs(ctx context.Context, containerID string, option
|
||||
if options.Follow {
|
||||
query.Set("follow", "1")
|
||||
}
|
||||
query.Set("tail", options.Tail)
|
||||
|
||||
switch options.Tail {
|
||||
case "", "all":
|
||||
// don't send option; default is to show all logs.
|
||||
//
|
||||
// The default on the daemon-side is to show all logs; account for
|
||||
// some special values. The CLI may set a magic "all" value that's
|
||||
// used as default.
|
||||
//
|
||||
// Given that the default is to show all logs, we can ignore these
|
||||
// values, and don't send "tail".
|
||||
//
|
||||
// see https://github.com/moby/moby/blob/0df791cb72b568eeadba2267fe9a5040d12b0487/daemon/logs.go#L75-L78
|
||||
// see https://github.com/moby/moby/blob/4d20b6fe56dfb2b06f4a5dd1f32913215a9c317b/daemon/cluster/services.go#L425-L449
|
||||
default:
|
||||
query.Set("tail", options.Tail)
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/containers/"+containerID+"/logs", query, nil)
|
||||
if err != nil {
|
||||
|
||||
+8
@@ -41,6 +41,14 @@ func (cli *Client) ImageList(ctx context.Context, options ImageListOptions) (Ima
|
||||
query.Set("manifests", "1")
|
||||
}
|
||||
}
|
||||
if options.Identity {
|
||||
if err := cli.requiresVersion(ctx, "1.54", "identity"); err != nil {
|
||||
return ImageListResult{}, err
|
||||
}
|
||||
// Identity data in image list is scoped to manifests.
|
||||
query.Set("manifests", "1")
|
||||
query.Set("identity", "1")
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/images/json", query, nil)
|
||||
defer ensureReaderClosed(resp)
|
||||
|
||||
+3
@@ -16,6 +16,9 @@ type ImageListOptions struct {
|
||||
|
||||
// Manifests indicates whether the image manifests should be returned.
|
||||
Manifests bool
|
||||
|
||||
// Identity indicates whether image identity information should be returned.
|
||||
Identity bool
|
||||
}
|
||||
|
||||
// ImageListResult holds the result from ImageList.
|
||||
|
||||
+9
-7
@@ -11,23 +11,23 @@ import (
|
||||
"github.com/moby/moby/api/types/jsonstream"
|
||||
)
|
||||
|
||||
func NewJSONMessageStream(rc io.ReadCloser) stream {
|
||||
func NewJSONMessageStream(rc io.ReadCloser) Stream {
|
||||
if rc == nil {
|
||||
panic("nil io.ReadCloser")
|
||||
}
|
||||
return stream{
|
||||
return Stream{
|
||||
rc: rc,
|
||||
close: sync.OnceValue(rc.Close),
|
||||
}
|
||||
}
|
||||
|
||||
type stream struct {
|
||||
type Stream struct {
|
||||
rc io.ReadCloser
|
||||
close func() error
|
||||
}
|
||||
|
||||
// Read implements io.ReadCloser
|
||||
func (r stream) Read(p []byte) (n int, err error) {
|
||||
func (r Stream) Read(p []byte) (n int, err error) {
|
||||
if r.rc == nil {
|
||||
return 0, io.EOF
|
||||
}
|
||||
@@ -35,16 +35,18 @@ func (r stream) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
// Close implements io.ReadCloser
|
||||
func (r stream) Close() error {
|
||||
func (r Stream) Close() error {
|
||||
if r.close == nil {
|
||||
return nil
|
||||
}
|
||||
return r.close()
|
||||
}
|
||||
|
||||
var _ io.ReadCloser = Stream{}
|
||||
|
||||
// JSONMessages decodes the response stream as a sequence of JSONMessages.
|
||||
// if stream ends or context is cancelled, the underlying [io.Reader] is closed.
|
||||
func (r stream) JSONMessages(ctx context.Context) iter.Seq2[jsonstream.Message, error] {
|
||||
func (r Stream) JSONMessages(ctx context.Context) iter.Seq2[jsonstream.Message, error] {
|
||||
stop := context.AfterFunc(ctx, func() {
|
||||
_ = r.Close()
|
||||
})
|
||||
@@ -72,7 +74,7 @@ func (r stream) JSONMessages(ctx context.Context) iter.Seq2[jsonstream.Message,
|
||||
}
|
||||
|
||||
// Wait waits for operation to complete and detects errors reported as JSONMessage
|
||||
func (r stream) Wait(ctx context.Context) error {
|
||||
func (r Stream) Wait(ctx context.Context) error {
|
||||
for _, err := range r.JSONMessages(ctx) {
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+4
-4
@@ -17,7 +17,7 @@ import (
|
||||
var timeNow = time.Now // For overriding in tests.
|
||||
|
||||
// RFC3339NanoFixed is time.RFC3339Nano with nanoseconds padded using zeros to
|
||||
// ensure the formatted time isalways the same number of characters.
|
||||
// ensure the formatted time is always the same number of characters.
|
||||
const RFC3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
|
||||
|
||||
func RenderTUIProgress(p jsonstream.Progress, width uint16) string {
|
||||
@@ -144,14 +144,14 @@ func Display(jm jsonstream.Message, out io.Writer, isTerminal bool, width uint16
|
||||
return nil
|
||||
}
|
||||
|
||||
type JSONMessagesStream iter.Seq2[jsonstream.Message, error]
|
||||
type JSONMessagesStream = iter.Seq2[jsonstream.Message, error]
|
||||
|
||||
// DisplayJSONMessagesStream reads a JSON message stream from in, and writes
|
||||
// each [JSONMessage] to out.
|
||||
// see DisplayJSONMessages for details
|
||||
func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error {
|
||||
dec := json.NewDecoder(in)
|
||||
var f JSONMessagesStream = func(yield func(jsonstream.Message, error) bool) {
|
||||
f := func(yield func(jsonstream.Message, error) bool) {
|
||||
for {
|
||||
var jm jsonstream.Message
|
||||
err := dec.Decode(&jm)
|
||||
@@ -183,7 +183,7 @@ func DisplayJSONMessagesStream(in io.Reader, out io.Writer, terminalFd uintptr,
|
||||
// - auxCallback allows handling the [JSONMessage.Aux] field. It is
|
||||
// called if a JSONMessage contains an Aux field, in which case
|
||||
// DisplayJSONMessagesStream does not present the JSONMessage.
|
||||
func DisplayJSONMessages(messages JSONMessagesStream, out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error {
|
||||
func DisplayJSONMessages(messages iter.Seq2[jsonstream.Message, error], out io.Writer, terminalFd uintptr, isTerminal bool, auxCallback func(jsonstream.Message)) error {
|
||||
ids := make(map[string]uint)
|
||||
var width uint16 = 200
|
||||
if isTerminal {
|
||||
|
||||
+12
-9
@@ -166,12 +166,12 @@ func (cli *Client) doRequest(req *http.Request) (*http.Response, error) {
|
||||
}
|
||||
|
||||
if errors.Is(err, os.ErrPermission) {
|
||||
// Don't include request errors ("Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.51/version"),
|
||||
// Don't include request errors (Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.51/version"),
|
||||
// which are irrelevant if we weren't able to connect.
|
||||
return nil, errConnectionFailed{fmt.Errorf("permission denied while trying to connect to the docker API at %v", cli.host)}
|
||||
}
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
// Unwrap the error to remove request errors ("Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.51/version"),
|
||||
// Unwrap the error to remove request errors (Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.51/version"),
|
||||
// which are irrelevant if we weren't able to connect.
|
||||
err = errors.Unwrap(err)
|
||||
return nil, errConnectionFailed{fmt.Errorf("failed to connect to the docker API at %v; check if the path is correct and if the daemon is running: %w", cli.host, err)}
|
||||
@@ -195,13 +195,16 @@ func (cli *Client) doRequest(req *http.Request) (*http.Response, error) {
|
||||
// Although there's not a strongly typed error for this in go-winio,
|
||||
// lots of people are using the default configuration for the docker
|
||||
// daemon on Windows where the daemon is listening on a named pipe
|
||||
// `//./pipe/docker_engine, and the client must be running elevated.
|
||||
// Give users a clue rather than the not-overly useful message
|
||||
// such as `error during connect: Get http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.26/info:
|
||||
// open //./pipe/docker_engine: The system cannot find the file specified.`.
|
||||
// ("//./pipe/docker_engine"), and the client must be running elevated.
|
||||
//
|
||||
// Give users a clue rather than the not-overly useful message such as;
|
||||
//
|
||||
// open //./pipe/docker_engine: The system cannot find the file specified.
|
||||
//
|
||||
// Note we can't string compare "The system cannot find the file specified" as
|
||||
// this is localised - for example in French the error would be
|
||||
// `open //./pipe/docker_engine: Le fichier spécifié est introuvable.`
|
||||
// this is localized; for example. in French the error would be;
|
||||
//
|
||||
// open //./pipe/docker_engine: Le fichier spécifié est introuvable.
|
||||
if strings.Contains(err.Error(), `open //./pipe/docker_engine`) {
|
||||
// Checks if client is running with elevated privileges
|
||||
if f, elevatedErr := os.Open(`\\.\PHYSICALDRIVE0`); elevatedErr != nil {
|
||||
@@ -297,7 +300,7 @@ func checkResponseErr(serverResp *http.Response) (retErr error) {
|
||||
} else {
|
||||
// Fall back to returning the response as-is for situations where a
|
||||
// plain text error is returned. This branch may also catch
|
||||
// situations where a proxy is involved, returning a HTML response.
|
||||
// situations where a proxy is involved, returning an HTML response.
|
||||
daemonErr = errors.New(strings.TrimSpace(string(body)))
|
||||
}
|
||||
return fmt.Errorf("Error response from daemon: %w", daemonErr)
|
||||
|
||||
+16
-1
@@ -70,7 +70,22 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options Se
|
||||
if options.Follow {
|
||||
query.Set("follow", "1")
|
||||
}
|
||||
query.Set("tail", options.Tail)
|
||||
switch options.Tail {
|
||||
case "", "all":
|
||||
// don't send option; default is to show all logs.
|
||||
//
|
||||
// The default on the daemon-side is to show all logs; account for
|
||||
// some special values. The CLI may set a magic "all" value that's
|
||||
// used as default.
|
||||
//
|
||||
// Given that the default is to show all logs, we can ignore these
|
||||
// values, and don't send "tail".
|
||||
//
|
||||
// see https://github.com/moby/moby/blob/0df791cb72b568eeadba2267fe9a5040d12b0487/daemon/logs.go#L75-L78
|
||||
// see https://github.com/moby/moby/blob/4d20b6fe56dfb2b06f4a5dd1f32913215a9c317b/daemon/cluster/services.go#L425-L449
|
||||
default:
|
||||
query.Set("tail", options.Tail)
|
||||
}
|
||||
|
||||
resp, err := cli.get(ctx, "/services/"+serviceID+"/logs", query, nil)
|
||||
if err != nil {
|
||||
|
||||
+3
-3
@@ -45,9 +45,9 @@ func (cli *Client) Events(ctx context.Context, options EventsListOptions) Events
|
||||
}
|
||||
|
||||
headers := http.Header{}
|
||||
headers.Add("Accept", types.MediaTypeJSONSequence)
|
||||
headers.Add("Accept", types.MediaTypeJSONLines)
|
||||
headers.Add("Accept", types.MediaTypeNDJSON)
|
||||
headers.Add("Accept", types.MediaTypeJSONLines) // Implicit q=1.0; in case server doesn't parse correctly.
|
||||
headers.Add("Accept", types.MediaTypeNDJSON+";q=0.9")
|
||||
headers.Add("Accept", types.MediaTypeJSONSequence+";q=0.5")
|
||||
resp, err := cli.get(ctx, "/events", query, headers)
|
||||
if err != nil {
|
||||
close(started)
|
||||
|
||||
Reference in New Issue
Block a user