6aea26b431
Commit e6907243af
applied a fix for situations
where the client was configured with API-version negotiation, but did not yet
negotiate a version.
However, the checkVersion() function that was implemented copied the semantics
of cli.NegotiateAPIVersion, which ignored connection failures with the
assumption that connection errors would still surface further down.
However, when using the result of a failed negotiation for NewVersionError,
an API version mismatch error would be produced, masking the actual connection
error.
This patch changes the signature of checkVersion to return unexpected errors,
including failures to connect to the API.
Before this patch:
docker -H unix:///no/such/socket.sock secret ls
"secret list" requires API version 1.25, but the Docker daemon API version is 1.24
With this patch applied:
docker -H unix:///no/such/socket.sock secret ls
Cannot connect to the Docker daemon at unix:///no/such/socket.sock. Is the docker daemon running?
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
193 lines
7.1 KiB
Go
193 lines
7.1 KiB
Go
package client // import "github.com/docker/docker/client"
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/distribution/reference"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/registry"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/docker/docker/api/types/versions"
|
|
"github.com/opencontainers/go-digest"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// ServiceCreate creates a new service.
|
|
func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (swarm.ServiceCreateResponse, error) {
|
|
var response swarm.ServiceCreateResponse
|
|
|
|
// Make sure we negotiated (if the client is configured to do so),
|
|
// as code below contains API-version specific handling of options.
|
|
//
|
|
// Normally, version-negotiation (if enabled) would not happen until
|
|
// the API request is made.
|
|
if err := cli.checkVersion(ctx); err != nil {
|
|
return response, err
|
|
}
|
|
|
|
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
|
|
if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
|
|
service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{}
|
|
}
|
|
|
|
if err := validateServiceSpec(service); err != nil {
|
|
return response, err
|
|
}
|
|
|
|
// ensure that the image is tagged
|
|
var resolveWarning string
|
|
switch {
|
|
case service.TaskTemplate.ContainerSpec != nil:
|
|
if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" {
|
|
service.TaskTemplate.ContainerSpec.Image = taggedImg
|
|
}
|
|
if options.QueryRegistry {
|
|
resolveWarning = resolveContainerSpecImage(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
|
|
}
|
|
case service.TaskTemplate.PluginSpec != nil:
|
|
if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" {
|
|
service.TaskTemplate.PluginSpec.Remote = taggedImg
|
|
}
|
|
if options.QueryRegistry {
|
|
resolveWarning = resolvePluginSpecRemote(ctx, cli, &service.TaskTemplate, options.EncodedRegistryAuth)
|
|
}
|
|
}
|
|
|
|
headers := http.Header{}
|
|
if versions.LessThan(cli.version, "1.30") {
|
|
// the custom "version" header was used by engine API before 20.10
|
|
// (API 1.30) to switch between client- and server-side lookup of
|
|
// image digests.
|
|
headers["version"] = []string{cli.version}
|
|
}
|
|
if options.EncodedRegistryAuth != "" {
|
|
headers[registry.AuthHeader] = []string{options.EncodedRegistryAuth}
|
|
}
|
|
resp, err := cli.post(ctx, "/services/create", nil, service, headers)
|
|
defer ensureReaderClosed(resp)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
|
|
err = json.NewDecoder(resp.body).Decode(&response)
|
|
if resolveWarning != "" {
|
|
response.Warnings = append(response.Warnings, resolveWarning)
|
|
}
|
|
|
|
return response, err
|
|
}
|
|
|
|
func resolveContainerSpecImage(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
|
|
var warning string
|
|
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.ContainerSpec.Image, encodedAuth); err != nil {
|
|
warning = 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
|
|
}
|
|
|
|
func resolvePluginSpecRemote(ctx context.Context, cli DistributionAPIClient, taskSpec *swarm.TaskSpec, encodedAuth string) string {
|
|
var warning string
|
|
if img, imgPlatforms, err := imageDigestAndPlatforms(ctx, cli, taskSpec.PluginSpec.Remote, encodedAuth); err != nil {
|
|
warning = 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
|
|
}
|
|
|
|
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
|
|
distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
|
|
var platforms []swarm.Platform
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
imageWithDigest := imageWithDigestString(image, distributionInspect.Descriptor.Digest)
|
|
|
|
if len(distributionInspect.Platforms) > 0 {
|
|
platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms))
|
|
for _, p := range distributionInspect.Platforms {
|
|
// clear architecture field for arm. This is a temporary patch to address
|
|
// https://github.com/docker/swarmkit/issues/2294. The issue is that while
|
|
// image manifests report "arm" as the architecture, the node reports
|
|
// something like "armv7l" (includes the variant), which causes arm images
|
|
// to stop working with swarm mode. This patch removes the architecture
|
|
// constraint for arm images to ensure tasks get scheduled.
|
|
arch := p.Architecture
|
|
if strings.ToLower(arch) == "arm" {
|
|
arch = ""
|
|
}
|
|
platforms = append(platforms, swarm.Platform{
|
|
Architecture: arch,
|
|
OS: p.OS,
|
|
})
|
|
}
|
|
}
|
|
return imageWithDigest, platforms, err
|
|
}
|
|
|
|
// imageWithDigestString takes an image string and a digest, and updates
|
|
// the image string if it didn't originally contain a digest. It returns
|
|
// image unmodified in other situations.
|
|
func imageWithDigestString(image string, dgst digest.Digest) string {
|
|
namedRef, err := reference.ParseNormalizedNamed(image)
|
|
if err == nil {
|
|
if _, isCanonical := namedRef.(reference.Canonical); !isCanonical {
|
|
// ensure that image gets a default tag if none is provided
|
|
img, err := reference.WithDigest(namedRef, dgst)
|
|
if err == nil {
|
|
return reference.FamiliarString(img)
|
|
}
|
|
}
|
|
}
|
|
return image
|
|
}
|
|
|
|
// imageWithTagString takes an image string, and returns a tagged image
|
|
// string, adding a 'latest' tag if one was not provided. It returns an
|
|
// empty string if a canonical reference was provided
|
|
func imageWithTagString(image string) string {
|
|
namedRef, err := reference.ParseNormalizedNamed(image)
|
|
if err == nil {
|
|
return reference.FamiliarString(reference.TagNameOnly(namedRef))
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// digestWarning constructs a formatted warning string using the
|
|
// image name that could not be pinned by digest. The formatting
|
|
// is hardcoded, but could me made smarter in the future
|
|
func digestWarning(image string) string {
|
|
return fmt.Sprintf("image %s could not be accessed on a registry to record\nits digest. Each node will access %s independently,\npossibly leading to different nodes running different\nversions of the image.\n", image, image)
|
|
}
|
|
|
|
func validateServiceSpec(s swarm.ServiceSpec) error {
|
|
if s.TaskTemplate.ContainerSpec != nil && s.TaskTemplate.PluginSpec != nil {
|
|
return errors.New("must not specify both a container spec and a plugin spec in the task template")
|
|
}
|
|
if s.TaskTemplate.PluginSpec != nil && s.TaskTemplate.Runtime != swarm.RuntimePlugin {
|
|
return errors.New("mismatched runtime with plugin spec")
|
|
}
|
|
if s.TaskTemplate.ContainerSpec != nil && (s.TaskTemplate.Runtime != "" && s.TaskTemplate.Runtime != swarm.RuntimeContainer) {
|
|
return errors.New("mismatched runtime with container spec")
|
|
}
|
|
return nil
|
|
}
|