vendor: docker/cli v29.1.1, moby/client v0.2.1
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
@@ -16,7 +16,7 @@ require (
|
|||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/docker/cli v29.0.1+incompatible
|
github.com/docker/cli v29.1.1+incompatible
|
||||||
github.com/docker/cli-docs-tool v0.10.0
|
github.com/docker/cli-docs-tool v0.10.0
|
||||||
github.com/docker/docker v28.5.2+incompatible
|
github.com/docker/docker v28.5.2+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
@@ -32,7 +32,7 @@ require (
|
|||||||
github.com/moby/buildkit v0.26.2
|
github.com/moby/buildkit v0.26.2
|
||||||
github.com/moby/go-archive v0.1.0
|
github.com/moby/go-archive v0.1.0
|
||||||
github.com/moby/moby/api v1.52.0
|
github.com/moby/moby/api v1.52.0
|
||||||
github.com/moby/moby/client v0.1.0
|
github.com/moby/moby/client v0.2.1
|
||||||
github.com/moby/sys/atomicwriter v0.1.0
|
github.com/moby/sys/atomicwriter v0.1.0
|
||||||
github.com/moby/sys/mountinfo v0.7.2
|
github.com/moby/sys/mountinfo v0.7.2
|
||||||
github.com/morikuni/aec v1.0.0
|
github.com/morikuni/aec v1.0.0
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
|
||||||
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/docker/cli v29.0.1+incompatible h1:EnvMEAR9Ro5xQEKbMitlabj5vCDY0vwcDyY/Lsow7FQ=
|
github.com/docker/cli v29.1.1+incompatible h1:gGQk5qx62yPKRm3bUdKBzmDBSQzp17hlSLbV1F7jjys=
|
||||||
github.com/docker/cli v29.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v29.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU=
|
github.com/docker/cli-docs-tool v0.10.0 h1:bOD6mKynPQgojQi3s2jgcUWGp/Ebqy1SeCr9VfKQLLU=
|
||||||
github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U=
|
github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09fzRHP4aX1qwp1U=
|
||||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
@@ -218,8 +218,8 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
|||||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||||
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
||||||
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||||
github.com/moby/moby/client v0.1.0 h1:nt+hn6O9cyJQqq5UWnFGqsZRTS/JirUqzPjEl0Bdc/8=
|
github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k=
|
||||||
github.com/moby/moby/client v0.1.0/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
|
github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
|
||||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||||
|
|||||||
+1
-1
@@ -134,7 +134,7 @@ func (ep *Endpoint) ClientOpts() ([]client.Opt, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, client.WithVersionFromEnv(), client.WithAPIVersionNegotiation())
|
result = append(result, client.WithAPIVersionFromEnv())
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-4
@@ -23,19 +23,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
|
// Create a new client that handles common environment variables
|
||||||
|
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
|
||||||
|
// API-version negotiation to allow downgrading the API version
|
||||||
|
// when connecting with an older daemon version.
|
||||||
|
apiClient, err := client.New(client.FromEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer apiClient.Close()
|
defer apiClient.Close()
|
||||||
|
|
||||||
containers, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{All: true})
|
// List all containers (both stopped and running).
|
||||||
|
result, err := apiClient.ContainerList(context.Background(), client.ContainerListOptions{
|
||||||
|
All: true,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ctr := range containers {
|
// Print each container's ID, status and the image it was created from.
|
||||||
fmt.Printf("%s %s (status: %s)\n", ctr.ID, ctr.Image, ctr.Status)
|
fmt.Printf("%s %-22s %s\n", "ID", "STATUS", "IMAGE")
|
||||||
|
for _, ctr := range result.Items {
|
||||||
|
fmt.Printf("%s %-22s %s\n", ctr.ID, ctr.Status, ctr.Image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
+50
-32
@@ -8,10 +8,8 @@ https://docs.docker.com/reference/api/engine/
|
|||||||
|
|
||||||
You use the library by constructing a client object using [New]
|
You use the library by constructing a client object using [New]
|
||||||
and calling methods on it. The client can be configured from environment
|
and calling methods on it. The client can be configured from environment
|
||||||
variables by passing the [FromEnv] option, and the [WithAPIVersionNegotiation]
|
variables by passing the [FromEnv] option. Other options can be configured
|
||||||
option to allow downgrading the API version used when connecting with an older
|
manually by passing any of the available [Opt] options.
|
||||||
daemon version. Other options cen be configured manually by passing any of
|
|
||||||
the available [Opt] options.
|
|
||||||
|
|
||||||
For example, to list running containers (the equivalent of "docker ps"):
|
For example, to list running containers (the equivalent of "docker ps"):
|
||||||
|
|
||||||
@@ -30,7 +28,7 @@ For example, to list running containers (the equivalent of "docker ps"):
|
|||||||
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
|
// for configuration (DOCKER_HOST, DOCKER_API_VERSION), and does
|
||||||
// API-version negotiation to allow downgrading the API version
|
// API-version negotiation to allow downgrading the API version
|
||||||
// when connecting with an older daemon version.
|
// when connecting with an older daemon version.
|
||||||
apiClient, err := client.New(client.FromEnv, client.WithAPIVersionNegotiation())
|
apiClient, err := client.New(client.FromEnv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -103,18 +101,16 @@ import (
|
|||||||
const DummyHost = "api.moby.localhost"
|
const DummyHost = "api.moby.localhost"
|
||||||
|
|
||||||
// MaxAPIVersion is the highest REST API version supported by the client.
|
// MaxAPIVersion is the highest REST API version supported by the client.
|
||||||
// If API-version negotiation is enabled (see [WithAPIVersionNegotiation],
|
// If API-version negotiation is enabled, the client may downgrade its API version.
|
||||||
// [Client.NegotiateAPIVersion]), the client may downgrade its API version.
|
// Similarly, the [WithAPIVersion] and [WithAPIVersionFromEnv] options allow
|
||||||
// Similarly, the [WithVersion] and [WithVersionFromEnv] allow overriding
|
// overriding the version and disable API-version negotiation.
|
||||||
// the version.
|
|
||||||
//
|
//
|
||||||
// This version may be lower than the version of the api library module used.
|
// This version may be lower than the version of the api library module used.
|
||||||
const MaxAPIVersion = "1.52"
|
const MaxAPIVersion = "1.52"
|
||||||
|
|
||||||
// fallbackAPIVersion is the version to fall back to if API-version negotiation
|
// MinAPIVersion is the minimum API version supported by the client. API versions
|
||||||
// fails. API versions below this version are not supported by the client,
|
// below this version are not considered when performing API-version negotiation.
|
||||||
// and not considered when negotiating.
|
const MinAPIVersion = "1.44"
|
||||||
const fallbackAPIVersion = "1.44"
|
|
||||||
|
|
||||||
// Ensure that Client always implements APIClient.
|
// Ensure that Client always implements APIClient.
|
||||||
var _ APIClient = &Client{}
|
var _ APIClient = &Client{}
|
||||||
@@ -174,8 +170,13 @@ func NewClientWithOpts(ops ...Opt) (*Client, error) {
|
|||||||
// It takes an optional list of [Opt] functional arguments, which are applied in
|
// It takes an optional list of [Opt] functional arguments, which are applied in
|
||||||
// the order they're provided, which allows modifying the defaults when creating
|
// the order they're provided, which allows modifying the defaults when creating
|
||||||
// the client. For example, the following initializes a client that configures
|
// the client. For example, the following initializes a client that configures
|
||||||
// itself with values from environment variables ([FromEnv]), and has automatic
|
// itself with values from environment variables ([FromEnv]).
|
||||||
// API version negotiation enabled ([WithAPIVersionNegotiation]).
|
//
|
||||||
|
// By default, the client automatically negotiates the API version to use when
|
||||||
|
// making requests. API version negotiation is performed on the first request;
|
||||||
|
// subsequent requests do not re-negotiate. Use [WithAPIVersion] or
|
||||||
|
// [WithAPIVersionFromEnv] to configure the client with a fixed API version
|
||||||
|
// and disable API version negotiation.
|
||||||
//
|
//
|
||||||
// cli, err := client.New(
|
// cli, err := client.New(
|
||||||
// client.FromEnv,
|
// client.FromEnv,
|
||||||
@@ -213,6 +214,12 @@ func New(ops ...Opt) (*Client, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.envAPIVersion != "" {
|
||||||
|
c.setAPIVersion(cfg.envAPIVersion)
|
||||||
|
} else if cfg.manualAPIVersion != "" {
|
||||||
|
c.setAPIVersion(cfg.manualAPIVersion)
|
||||||
|
}
|
||||||
|
|
||||||
if tr, ok := c.client.Transport.(*http.Transport); ok {
|
if tr, ok := c.client.Transport.(*http.Transport); ok {
|
||||||
// Store the base transport before we wrap it in tracing libs below
|
// Store the base transport before we wrap it in tracing libs below
|
||||||
// This is used, as an example, to close idle connections when the client is closed
|
// This is used, as an example, to close idle connections when the client is closed
|
||||||
@@ -278,7 +285,7 @@ func (cli *Client) Close() error {
|
|||||||
// be negotiated when making the actual requests, and for which cases
|
// be negotiated when making the actual requests, and for which cases
|
||||||
// we cannot do the negotiation lazily.
|
// we cannot do the negotiation lazily.
|
||||||
func (cli *Client) checkVersion(ctx context.Context) error {
|
func (cli *Client) checkVersion(ctx context.Context) error {
|
||||||
if cli.manualOverride || !cli.negotiateVersion || cli.negotiated.Load() {
|
if cli.negotiated.Load() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
_, err := cli.Ping(ctx, PingOptions{
|
_, err := cli.Ping(ctx, PingOptions{
|
||||||
@@ -306,36 +313,47 @@ func (cli *Client) ClientVersion() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// negotiateAPIVersion updates the version to match the API version from
|
// negotiateAPIVersion updates the version to match the API version from
|
||||||
// the ping response. It falls back to the lowest version supported if the
|
// the ping response.
|
||||||
// API version is empty, or returns an error if the API version is lower than
|
//
|
||||||
// the lowest supported API version, in which case the version is not modified.
|
// It returns an error if version is invalid, or lower than the minimum
|
||||||
|
// supported API version in which case the client's API version is not
|
||||||
|
// updated, and negotiation is not marked as completed.
|
||||||
func (cli *Client) negotiateAPIVersion(pingVersion string) error {
|
func (cli *Client) negotiateAPIVersion(pingVersion string) error {
|
||||||
pingVersion = strings.TrimPrefix(pingVersion, "v")
|
var err error
|
||||||
if pingVersion == "" {
|
pingVersion, err = parseAPIVersion(pingVersion)
|
||||||
// TODO(thaJeztah): consider returning an error on empty value or not falling back; see https://github.com/moby/moby/pull/51119#discussion_r2413148487
|
if err != nil {
|
||||||
pingVersion = fallbackAPIVersion
|
return err
|
||||||
} else if versions.LessThan(pingVersion, fallbackAPIVersion) {
|
}
|
||||||
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, fallbackAPIVersion))
|
|
||||||
|
if versions.LessThan(pingVersion, MinAPIVersion) {
|
||||||
|
return cerrdefs.ErrInvalidArgument.WithMessage(fmt.Sprintf("API version %s is not supported by this client: the minimum supported API version is %s", pingVersion, MinAPIVersion))
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the client is not initialized with a version, start with the latest supported version
|
// if the client is not initialized with a version, start with the latest supported version
|
||||||
if cli.version == "" {
|
negotiatedVersion := cli.version
|
||||||
cli.version = MaxAPIVersion
|
if negotiatedVersion == "" {
|
||||||
|
negotiatedVersion = MaxAPIVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// if server version is lower than the client version, downgrade
|
// if server version is lower than the client version, downgrade
|
||||||
if versions.LessThan(pingVersion, cli.version) {
|
if versions.LessThan(pingVersion, negotiatedVersion) {
|
||||||
cli.version = pingVersion
|
negotiatedVersion = pingVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the results, so that automatic API version negotiation (if enabled)
|
// Store the results, so that automatic API version negotiation (if enabled)
|
||||||
// won't be performed on the next request.
|
// won't be performed on the next request.
|
||||||
if cli.negotiateVersion {
|
cli.setAPIVersion(negotiatedVersion)
|
||||||
cli.negotiated.Store(true)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setAPIVersion sets the client's API version and marks API version negotiation
|
||||||
|
// as completed, so that automatic API version negotiation (if enabled) won't
|
||||||
|
// be performed on the next request.
|
||||||
|
func (cli *Client) setAPIVersion(version string) {
|
||||||
|
cli.version = version
|
||||||
|
cli.negotiated.Store(true)
|
||||||
|
}
|
||||||
|
|
||||||
// DaemonHost returns the host address used by the client
|
// DaemonHost returns the host address used by the client
|
||||||
func (cli *Client) DaemonHost() string {
|
func (cli *Client) DaemonHost() string {
|
||||||
return cli.host
|
return cli.host
|
||||||
|
|||||||
vendor/github.com/moby/moby/client/options.go → vendor/github.com/moby/moby/client/client_options.go
Generated
Vendored
+73
-28
@@ -38,14 +38,22 @@ type clientConfig struct {
|
|||||||
userAgent *string
|
userAgent *string
|
||||||
// custom HTTP headers configured by users.
|
// custom HTTP headers configured by users.
|
||||||
customHTTPHeaders map[string]string
|
customHTTPHeaders map[string]string
|
||||||
// manualOverride is set to true when the version was set by users.
|
|
||||||
manualOverride bool
|
|
||||||
|
|
||||||
// negotiateVersion indicates if the client should automatically negotiate
|
// manualAPIVersion contains the API version set by users. This field
|
||||||
// the API version to use when making requests. API version negotiation is
|
// will only be non-empty if a valid-formed version was set through
|
||||||
// performed on the first request, after which negotiated is set to "true"
|
// [WithAPIVersion].
|
||||||
// so that subsequent requests do not re-negotiate.
|
//
|
||||||
negotiateVersion bool
|
// If both manualAPIVersion and envAPIVersion are set, manualAPIVersion
|
||||||
|
// takes precedence. Either field disables API-version negotiation.
|
||||||
|
manualAPIVersion string
|
||||||
|
|
||||||
|
// envAPIVersion contains the API version set by users. This field
|
||||||
|
// will only be non-empty if a valid-formed version was set through
|
||||||
|
// [WithAPIVersionFromEnv].
|
||||||
|
//
|
||||||
|
// If both manualAPIVersion and envAPIVersion are set, manualAPIVersion
|
||||||
|
// takes precedence. Either field disables API-version negotiation.
|
||||||
|
envAPIVersion string
|
||||||
|
|
||||||
// traceOpts is a list of options to configure the tracing span.
|
// traceOpts is a list of options to configure the tracing span.
|
||||||
traceOpts []otelhttp.Option
|
traceOpts []otelhttp.Option
|
||||||
@@ -56,7 +64,7 @@ type Opt func(*clientConfig) error
|
|||||||
|
|
||||||
// FromEnv configures the client with values from environment variables. It
|
// FromEnv configures the client with values from environment variables. It
|
||||||
// is the equivalent of using the [WithTLSClientConfigFromEnv], [WithHostFromEnv],
|
// is the equivalent of using the [WithTLSClientConfigFromEnv], [WithHostFromEnv],
|
||||||
// and [WithVersionFromEnv] options.
|
// and [WithAPIVersionFromEnv] options.
|
||||||
//
|
//
|
||||||
// FromEnv uses the following environment variables:
|
// FromEnv uses the following environment variables:
|
||||||
//
|
//
|
||||||
@@ -71,7 +79,7 @@ func FromEnv(c *clientConfig) error {
|
|||||||
ops := []Opt{
|
ops := []Opt{
|
||||||
WithTLSClientConfigFromEnv(),
|
WithTLSClientConfigFromEnv(),
|
||||||
WithHostFromEnv(),
|
WithHostFromEnv(),
|
||||||
WithVersionFromEnv(),
|
WithAPIVersionFromEnv(),
|
||||||
}
|
}
|
||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
if err := op(c); err != nil {
|
if err := op(c); err != nil {
|
||||||
@@ -241,18 +249,59 @@ func WithTLSClientConfigFromEnv() Opt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithVersion overrides the client version with the specified one. If an empty
|
// WithAPIVersion overrides the client's API version with the specified one,
|
||||||
// version is provided, the value is ignored to allow version negotiation
|
// and disables API version negotiation. If an empty version is provided,
|
||||||
// (see [WithAPIVersionNegotiation]).
|
// this option is ignored to allow version negotiation. The given version
|
||||||
|
// should be formatted "<major>.<minor>" (for example, "1.52"). It returns
|
||||||
|
// an error if the given value not in the correct format.
|
||||||
//
|
//
|
||||||
// WithVersion does not validate if the client supports the given version,
|
// WithAPIVersion does not validate if the client supports the given version,
|
||||||
// and callers should verify if the version is in the correct format and
|
// and callers should verify if the version lower than the maximum supported
|
||||||
// lower than the maximum supported version as defined by [MaxAPIVersion].
|
// version as defined by [MaxAPIVersion].
|
||||||
func WithVersion(version string) Opt {
|
//
|
||||||
|
// [WithAPIVersionFromEnv] takes precedence if [WithAPIVersion] and
|
||||||
|
// [WithAPIVersionFromEnv] are both set.
|
||||||
|
func WithAPIVersion(version string) Opt {
|
||||||
return func(c *clientConfig) error {
|
return func(c *clientConfig) error {
|
||||||
if v := strings.TrimPrefix(version, "v"); v != "" {
|
version = strings.TrimSpace(version)
|
||||||
c.version = v
|
if val := strings.TrimPrefix(version, "v"); val != "" {
|
||||||
c.manualOverride = true
|
ver, err := parseAPIVersion(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid API version (%s): %w", version, err)
|
||||||
|
}
|
||||||
|
c.manualAPIVersion = ver
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithVersion overrides the client version with the specified one.
|
||||||
|
//
|
||||||
|
// Deprecated: use [WithAPIVersion] instead.
|
||||||
|
func WithVersion(version string) Opt {
|
||||||
|
return WithAPIVersion(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithAPIVersionFromEnv overrides the client version with the version specified in
|
||||||
|
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
|
||||||
|
// If DOCKER_API_VERSION is not set, or set to an empty value, the version
|
||||||
|
// is not modified.
|
||||||
|
//
|
||||||
|
// WithAPIVersion does not validate if the client supports the given version,
|
||||||
|
// and callers should verify if the version lower than the maximum supported
|
||||||
|
// version as defined by [MaxAPIVersion].
|
||||||
|
//
|
||||||
|
// [WithAPIVersionFromEnv] takes precedence if [WithAPIVersion] and
|
||||||
|
// [WithAPIVersionFromEnv] are both set.
|
||||||
|
func WithAPIVersionFromEnv() Opt {
|
||||||
|
return func(c *clientConfig) error {
|
||||||
|
version := strings.TrimSpace(os.Getenv(EnvOverrideAPIVersion))
|
||||||
|
if val := strings.TrimPrefix(version, "v"); val != "" {
|
||||||
|
ver, err := parseAPIVersion(val)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid API version (%s): %w", version, err)
|
||||||
|
}
|
||||||
|
c.envAPIVersion = ver
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -260,25 +309,21 @@ func WithVersion(version string) Opt {
|
|||||||
|
|
||||||
// WithVersionFromEnv overrides the client version with the version specified in
|
// WithVersionFromEnv overrides the client version with the version specified in
|
||||||
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
|
// the DOCKER_API_VERSION ([EnvOverrideAPIVersion]) environment variable.
|
||||||
// If DOCKER_API_VERSION is not set, or set to an empty value, the version
|
|
||||||
// is not modified.
|
|
||||||
//
|
//
|
||||||
// WithVersion does not validate if the client supports the given version,
|
// Deprecated: use [WithAPIVersionFromEnv] instead.
|
||||||
// and callers should verify if the version is in the correct format and
|
|
||||||
// lower than the maximum supported version as defined by [MaxAPIVersion].
|
|
||||||
func WithVersionFromEnv() Opt {
|
func WithVersionFromEnv() Opt {
|
||||||
return func(c *clientConfig) error {
|
return WithAPIVersionFromEnv()
|
||||||
return WithVersion(os.Getenv(EnvOverrideAPIVersion))(c)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithAPIVersionNegotiation enables automatic API version negotiation for the client.
|
// WithAPIVersionNegotiation enables automatic API version negotiation for the client.
|
||||||
// With this option enabled, the client automatically negotiates the API version
|
// With this option enabled, the client automatically negotiates the API version
|
||||||
// to use when making requests. API version negotiation is performed on the first
|
// to use when making requests. API version negotiation is performed on the first
|
||||||
// request; subsequent requests do not re-negotiate.
|
// request; subsequent requests do not re-negotiate.
|
||||||
|
//
|
||||||
|
// Deprecated: API-version negotiation is now enabled by default. Use [WithAPIVersion]
|
||||||
|
// or [WithAPIVersionFromEnv] to disable API version negotiation.
|
||||||
func WithAPIVersionNegotiation() Opt {
|
func WithAPIVersionNegotiation() Opt {
|
||||||
return func(c *clientConfig) error {
|
return func(c *clientConfig) error {
|
||||||
c.negotiateVersion = true
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-1
@@ -13,7 +13,7 @@ const (
|
|||||||
// be used to override the API version to use. Value must be
|
// be used to override the API version to use. Value must be
|
||||||
// formatted as MAJOR.MINOR, for example, "1.19".
|
// formatted as MAJOR.MINOR, for example, "1.19".
|
||||||
//
|
//
|
||||||
// This env-var is read by [FromEnv] and [WithVersionFromEnv] and when set to a
|
// This env-var is read by [FromEnv] and [WithAPIVersionFromEnv] and when set to a
|
||||||
// non-empty value, takes precedence over API version negotiation.
|
// non-empty value, takes precedence over API version negotiation.
|
||||||
//
|
//
|
||||||
// This environment variable should be used for debugging purposes only, as
|
// This environment variable should be used for debugging purposes only, as
|
||||||
|
|||||||
+24
-9
@@ -20,7 +20,7 @@ type PingOptions struct {
|
|||||||
//
|
//
|
||||||
// If a manual override is in place, either through the "DOCKER_API_VERSION"
|
// If a manual override is in place, either through the "DOCKER_API_VERSION"
|
||||||
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
|
// ([EnvOverrideAPIVersion]) environment variable, or if the client is initialized
|
||||||
// with a fixed version ([WithVersion]), no negotiation is performed.
|
// with a fixed version ([WithAPIVersion]), no negotiation is performed.
|
||||||
//
|
//
|
||||||
// If the API server's ping response does not contain an API version, or if the
|
// If the API server's ping response does not contain an API version, or if the
|
||||||
// client did not get a successful ping response, it assumes it is connected with
|
// client did not get a successful ping response, it assumes it is connected with
|
||||||
@@ -29,9 +29,8 @@ type PingOptions struct {
|
|||||||
NegotiateAPIVersion bool
|
NegotiateAPIVersion bool
|
||||||
|
|
||||||
// ForceNegotiate forces the client to re-negotiate the API version, even if
|
// ForceNegotiate forces the client to re-negotiate the API version, even if
|
||||||
// API-version negotiation already happened. This option cannot be
|
// API-version negotiation already happened or it the client is configured
|
||||||
// used if the client is configured with a fixed version using (using
|
// with a fixed version (using [WithAPIVersion] or [WithAPIVersionFromEnv]).
|
||||||
// [WithVersion] or [WithVersionFromEnv]).
|
|
||||||
//
|
//
|
||||||
// This option has no effect if NegotiateAPIVersion is not set.
|
// This option has no effect if NegotiateAPIVersion is not set.
|
||||||
ForceNegotiate bool
|
ForceNegotiate bool
|
||||||
@@ -72,10 +71,12 @@ type SwarmStatus struct {
|
|||||||
// for other non-success status codes, failing to connect to the API, or failing
|
// for other non-success status codes, failing to connect to the API, or failing
|
||||||
// to parse the API response.
|
// to parse the API response.
|
||||||
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
|
func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, error) {
|
||||||
if cli.manualOverride {
|
if !options.NegotiateAPIVersion {
|
||||||
|
// No API version negotiation needed; just return ping response.
|
||||||
return cli.ping(ctx)
|
return cli.ping(ctx)
|
||||||
}
|
}
|
||||||
if !options.NegotiateAPIVersion && !cli.negotiateVersion {
|
if cli.negotiated.Load() && !options.ForceNegotiate {
|
||||||
|
// API version was already negotiated or manually set.
|
||||||
return cli.ping(ctx)
|
return cli.ping(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,10 +86,19 @@ func (cli *Client) Ping(ctx context.Context, options PingOptions) (PingResult, e
|
|||||||
|
|
||||||
ping, err := cli.ping(ctx)
|
ping, err := cli.ping(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.ping(ctx)
|
return ping, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.negotiated.Load() && !options.ForceNegotiate {
|
if cli.negotiated.Load() && !options.ForceNegotiate {
|
||||||
|
// API version was already negotiated or manually set.
|
||||||
|
//
|
||||||
|
// We check cli.negotiated again under lock, to account for race
|
||||||
|
// conditions with the check at the start of this function.
|
||||||
|
return ping, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ping.APIVersion == "" {
|
||||||
|
cli.setAPIVersion(MaxAPIVersion)
|
||||||
return ping, nil
|
return ping, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,10 +122,15 @@ func (cli *Client) ping(ctx context.Context) (PingResult, error) {
|
|||||||
// response-body to get error details from.
|
// response-body to get error details from.
|
||||||
return newPingResult(resp), nil
|
return newPingResult(resp), nil
|
||||||
}
|
}
|
||||||
|
// close to allow reusing connection.
|
||||||
|
ensureReaderClosed(resp)
|
||||||
|
|
||||||
// HEAD failed or returned a non-OK status; fallback to GET.
|
// HEAD failed or returned a non-OK status; fallback to GET.
|
||||||
req.Method = http.MethodGet
|
req2, err := cli.buildRequest(ctx, http.MethodGet, path.Join(cli.basePath, "/_ping"), nil, nil)
|
||||||
resp, err = cli.doRequest(req)
|
if err != nil {
|
||||||
|
return PingResult{}, err
|
||||||
|
}
|
||||||
|
resp, err = cli.doRequest(req2)
|
||||||
defer ensureReaderClosed(resp)
|
defer ensureReaderClosed(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Failed to connect.
|
// Failed to connect.
|
||||||
|
|||||||
+50
-34
@@ -67,31 +67,22 @@ func (cli *Client) delete(ctx context.Context, path string, query url.Values, he
|
|||||||
// prepareJSONRequest encodes the given body to JSON and returns it as an [io.Reader], and sets the Content-Type
|
// prepareJSONRequest encodes the given body to JSON and returns it as an [io.Reader], and sets the Content-Type
|
||||||
// header. If body is nil, or a nil-interface, a "nil" body is returned without
|
// header. If body is nil, or a nil-interface, a "nil" body is returned without
|
||||||
// error.
|
// error.
|
||||||
//
|
|
||||||
// TODO(thaJeztah): should this return an error if a different Content-Type is already set?
|
|
||||||
// TODO(thaJeztah): is "nil" the appropriate approach for an empty body, or should we use [http.NoBody] (or similar)?
|
|
||||||
func prepareJSONRequest(body any, headers http.Header) (io.Reader, http.Header, error) {
|
func prepareJSONRequest(body any, headers http.Header) (io.Reader, http.Header, error) {
|
||||||
if body == nil {
|
|
||||||
return nil, headers, nil
|
|
||||||
}
|
|
||||||
// encoding/json encodes a nil pointer as the JSON document `null`,
|
|
||||||
// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
|
|
||||||
// That is almost certainly not what the caller intended as the request body.
|
|
||||||
//
|
|
||||||
// TODO(thaJeztah): consider moving this to jsonEncode, which would also allow returning an (empty) reader instead of nil.
|
|
||||||
if reflect.TypeOf(body).Kind() == reflect.Ptr && reflect.ValueOf(body).IsNil() {
|
|
||||||
return nil, headers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonBody, err := jsonEncode(body)
|
jsonBody, err := jsonEncode(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, headers, err
|
return nil, headers, err
|
||||||
}
|
}
|
||||||
|
if jsonBody == nil || jsonBody == http.NoBody {
|
||||||
|
// no content-type is set on empty requests.
|
||||||
|
return jsonBody, headers, nil
|
||||||
|
}
|
||||||
|
|
||||||
hdr := http.Header{}
|
hdr := http.Header{}
|
||||||
if headers != nil {
|
if headers != nil {
|
||||||
hdr = headers.Clone()
|
hdr = headers.Clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(thaJeztah): should this return an error if a different Content-Type is already set?
|
||||||
hdr.Set("Content-Type", "application/json")
|
hdr.Set("Content-Type", "application/json")
|
||||||
return jsonBody, hdr, nil
|
return jsonBody, hdr, nil
|
||||||
}
|
}
|
||||||
@@ -110,9 +101,6 @@ func (cli *Client) buildRequest(ctx context.Context, method, path string, body i
|
|||||||
req.Host = DummyHost
|
req.Host = DummyHost
|
||||||
}
|
}
|
||||||
|
|
||||||
if body != nil && req.Header.Get("Content-Type") == "" {
|
|
||||||
req.Header.Set("Content-Type", "text/plain")
|
|
||||||
}
|
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +236,11 @@ func checkResponseErr(serverResp *http.Response) (retErr error) {
|
|||||||
if statusMsg == "" {
|
if statusMsg == "" {
|
||||||
statusMsg = http.StatusText(serverResp.StatusCode)
|
statusMsg = http.StatusText(serverResp.StatusCode)
|
||||||
}
|
}
|
||||||
if serverResp.Body != nil {
|
var reqMethod string
|
||||||
|
if serverResp.Request != nil {
|
||||||
|
reqMethod = serverResp.Request.Method
|
||||||
|
}
|
||||||
|
if serverResp.Body != nil && reqMethod != http.MethodHead {
|
||||||
bodyMax := 1 * 1024 * 1024 // 1 MiB
|
bodyMax := 1 * 1024 * 1024 // 1 MiB
|
||||||
bodyR := &io.LimitedReader{
|
bodyR := &io.LimitedReader{
|
||||||
R: serverResp.Body,
|
R: serverResp.Body,
|
||||||
@@ -333,25 +325,49 @@ func (cli *Client) addHeaders(req *http.Request, headers http.Header) *http.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
func jsonEncode(data any) (io.Reader, error) {
|
func jsonEncode(data any) (io.Reader, error) {
|
||||||
var params bytes.Buffer
|
switch x := data.(type) {
|
||||||
if data != nil {
|
case nil:
|
||||||
if err := json.NewEncoder(¶ms).Encode(data); err != nil {
|
return http.NoBody, nil
|
||||||
return nil, err
|
case io.Reader:
|
||||||
|
// http.NoBody or other readers
|
||||||
|
return x, nil
|
||||||
|
case json.RawMessage:
|
||||||
|
if len(x) == 0 {
|
||||||
|
return http.NoBody, nil
|
||||||
}
|
}
|
||||||
|
return bytes.NewReader(x), nil
|
||||||
}
|
}
|
||||||
return ¶ms, nil
|
|
||||||
|
// encoding/json encodes a nil pointer as the JSON document `null`,
|
||||||
|
// irrespective of whether the type implements json.Marshaler or encoding.TextMarshaler.
|
||||||
|
// That is almost certainly not what the caller intended as the request body.
|
||||||
|
if v := reflect.ValueOf(data); v.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
|
return http.NoBody, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bytes.NewReader(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureReaderClosed(response *http.Response) {
|
func ensureReaderClosed(response *http.Response) {
|
||||||
if response != nil && response.Body != nil {
|
if response == nil || response.Body == nil {
|
||||||
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
|
return
|
||||||
// see https://github.com/google/go-github/pull/317/files#r57536827
|
|
||||||
//
|
|
||||||
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
|
|
||||||
// and check if context-cancellation should handle this as well. If still needed, consider
|
|
||||||
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
|
|
||||||
// methods.
|
|
||||||
_, _ = io.CopyN(io.Discard, response.Body, 512)
|
|
||||||
_ = response.Body.Close()
|
|
||||||
}
|
}
|
||||||
|
if response.ContentLength == 0 || (response.Request != nil && response.Request.Method == http.MethodHead) {
|
||||||
|
// No need to drain head requests or zero-length responses.
|
||||||
|
_ = response.Body.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Drain up to 512 bytes and close the body to let the Transport reuse the connection
|
||||||
|
// see https://github.com/google/go-github/pull/317/files#r57536827
|
||||||
|
//
|
||||||
|
// TODO(thaJeztah): see if this optimization is still needed, or already implemented in stdlib,
|
||||||
|
// and check if context-cancellation should handle this as well. If still needed, consider
|
||||||
|
// wrapping response.Body, or returning a "closer()" from [Client.sendRequest] and related
|
||||||
|
// methods.
|
||||||
|
_, _ = io.CopyN(io.Discard, response.Body, 512)
|
||||||
|
_ = response.Body.Close()
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-28
@@ -59,16 +59,18 @@ func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptio
|
|||||||
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
|
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
|
||||||
}
|
}
|
||||||
if options.QueryRegistry {
|
if options.QueryRegistry {
|
||||||
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
|
if warning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
|
||||||
warnings = append(warnings, resolveWarning)
|
warnings = append(warnings, warning)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case options.Spec.TaskTemplate.PluginSpec != nil:
|
case options.Spec.TaskTemplate.PluginSpec != nil:
|
||||||
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
|
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
|
||||||
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
|
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
|
||||||
}
|
}
|
||||||
if options.QueryRegistry {
|
if options.QueryRegistry {
|
||||||
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
|
if warning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
|
||||||
warnings = append(warnings, resolveWarning)
|
warnings = append(warnings, warning)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,35 +95,33 @@ func (cli *Client) ServiceCreate(ctx context.Context, options ServiceCreateOptio
|
|||||||
}
|
}
|
||||||
|
|
||||||
func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
|
func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
|
||||||
var warning string
|
img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth)
|
||||||
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth); err != nil {
|
if err != nil {
|
||||||
warning = digestWarning(taskSpec.ContainerSpec.Image)
|
return digestWarning(taskSpec.ContainerSpec.Image)
|
||||||
} else {
|
|
||||||
taskSpec.ContainerSpec.Image = img
|
|
||||||
if len(imgPlatforms) > 0 {
|
|
||||||
if taskSpec.Placement == nil {
|
|
||||||
taskSpec.Placement = &swarm.Placement{}
|
|
||||||
}
|
|
||||||
taskSpec.Placement.Platforms = imgPlatforms
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return warning
|
taskSpec.ContainerSpec.Image = img
|
||||||
|
if len(imgPlatforms) > 0 {
|
||||||
|
if taskSpec.Placement == nil {
|
||||||
|
taskSpec.Placement = &swarm.Placement{}
|
||||||
|
}
|
||||||
|
taskSpec.Placement.Platforms = imgPlatforms
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
|
func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
|
||||||
var warning string
|
img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth)
|
||||||
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth); err != nil {
|
if err != nil {
|
||||||
warning = digestWarning(taskSpec.PluginSpec.Remote)
|
return digestWarning(taskSpec.PluginSpec.Remote)
|
||||||
} else {
|
|
||||||
taskSpec.PluginSpec.Remote = img
|
|
||||||
if len(imgPlatforms) > 0 {
|
|
||||||
if taskSpec.Placement == nil {
|
|
||||||
taskSpec.Placement = &swarm.Placement{}
|
|
||||||
}
|
|
||||||
taskSpec.Placement.Platforms = imgPlatforms
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return warning
|
taskSpec.PluginSpec.Remote = img
|
||||||
|
if len(imgPlatforms) > 0 {
|
||||||
|
if taskSpec.Placement == nil {
|
||||||
|
taskSpec.Placement = &swarm.Placement{}
|
||||||
|
}
|
||||||
|
taskSpec.Placement.Platforms = imgPlatforms
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
|
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
|
||||||
|
|||||||
+6
-4
@@ -82,16 +82,18 @@ func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, options
|
|||||||
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
|
options.Spec.TaskTemplate.ContainerSpec.Image = taggedImg
|
||||||
}
|
}
|
||||||
if options.QueryRegistry {
|
if options.QueryRegistry {
|
||||||
resolveWarning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
|
if warning := resolveContainerSpecImage(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
|
||||||
warnings = append(warnings, resolveWarning)
|
warnings = append(warnings, warning)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case options.Spec.TaskTemplate.PluginSpec != nil:
|
case options.Spec.TaskTemplate.PluginSpec != nil:
|
||||||
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
|
if taggedImg := imageWithTagString(options.Spec.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
|
||||||
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
|
options.Spec.TaskTemplate.PluginSpec.Remote = taggedImg
|
||||||
}
|
}
|
||||||
if options.QueryRegistry {
|
if options.QueryRegistry {
|
||||||
resolveWarning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth)
|
if warning := resolvePluginSpecRemote(ctx, cli, &options.Spec.TaskTemplate, options.EncodedRegistryAuth); warning != "" {
|
||||||
warnings = append(warnings, resolveWarning)
|
warnings = append(warnings, warning)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+42
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -32,6 +33,47 @@ func trimID(objType, id string) (string, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseAPIVersion checks v to be a well-formed ("<major>.<minor>")
|
||||||
|
// API version. It returns an error if the value is empty or does not
|
||||||
|
// have the correct format, but does not validate if the API version is
|
||||||
|
// within the supported range ([MinAPIVersion] <= v <= [MaxAPIVersion]).
|
||||||
|
//
|
||||||
|
// It returns version after normalizing, or an error if validation failed.
|
||||||
|
func parseAPIVersion(version string) (string, error) {
|
||||||
|
if strings.TrimPrefix(strings.TrimSpace(version), "v") == "" {
|
||||||
|
return "", cerrdefs.ErrInvalidArgument.WithMessage("value is empty")
|
||||||
|
}
|
||||||
|
major, minor, err := parseMajorMinor(version)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d.%d", major, minor), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseMajorMinor is a helper for parseAPIVersion.
|
||||||
|
func parseMajorMinor(v string) (major, minor int, _ error) {
|
||||||
|
if strings.HasPrefix(v, "v") {
|
||||||
|
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("must be formatted <major>.<minor>")
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(v) == "" {
|
||||||
|
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("value is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
majVer, minVer, ok := strings.Cut(v, ".")
|
||||||
|
if !ok {
|
||||||
|
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("must be formatted <major>.<minor>")
|
||||||
|
}
|
||||||
|
major, err := strconv.Atoi(majVer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("invalid major version: must be formatted <major>.<minor>")
|
||||||
|
}
|
||||||
|
minor, err = strconv.Atoi(minVer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, cerrdefs.ErrInvalidArgument.WithMessage("invalid minor version: must be formatted <major>.<minor>")
|
||||||
|
}
|
||||||
|
return major, minor, nil
|
||||||
|
}
|
||||||
|
|
||||||
// encodePlatforms marshals the given platform(s) to JSON format, to
|
// encodePlatforms marshals the given platform(s) to JSON format, to
|
||||||
// be used for query-parameters for filtering / selecting platforms.
|
// be used for query-parameters for filtering / selecting platforms.
|
||||||
func encodePlatforms(platform ...ocispec.Platform) ([]string, error) {
|
func encodePlatforms(platform ...ocispec.Platform) ([]string, error) {
|
||||||
|
|||||||
Vendored
+2
-2
@@ -219,7 +219,7 @@ github.com/davecgh/go-spew/spew
|
|||||||
# github.com/distribution/reference v0.6.0
|
# github.com/distribution/reference v0.6.0
|
||||||
## explicit; go 1.20
|
## explicit; go 1.20
|
||||||
github.com/distribution/reference
|
github.com/distribution/reference
|
||||||
# github.com/docker/cli v29.0.1+incompatible
|
# github.com/docker/cli v29.1.1+incompatible
|
||||||
## explicit
|
## explicit
|
||||||
github.com/docker/cli/cli
|
github.com/docker/cli/cli
|
||||||
github.com/docker/cli/cli-plugins/metadata
|
github.com/docker/cli/cli-plugins/metadata
|
||||||
@@ -543,7 +543,7 @@ github.com/moby/moby/api/types/storage
|
|||||||
github.com/moby/moby/api/types/swarm
|
github.com/moby/moby/api/types/swarm
|
||||||
github.com/moby/moby/api/types/system
|
github.com/moby/moby/api/types/system
|
||||||
github.com/moby/moby/api/types/volume
|
github.com/moby/moby/api/types/volume
|
||||||
# github.com/moby/moby/client v0.1.0
|
# github.com/moby/moby/client v0.2.1
|
||||||
## explicit; go 1.24.0
|
## explicit; go 1.24.0
|
||||||
github.com/moby/moby/client
|
github.com/moby/moby/client
|
||||||
github.com/moby/moby/client/internal
|
github.com/moby/moby/client/internal
|
||||||
|
|||||||
Reference in New Issue
Block a user