github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/client/service_create.go (about) 1 package client 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "strings" 7 8 "github.com/docker/distribution/reference" 9 "github.com/docker/docker/api/types" 10 "github.com/docker/docker/api/types/swarm" 11 digest "github.com/opencontainers/go-digest" 12 "github.com/pkg/errors" 13 "golang.org/x/net/context" 14 ) 15 16 // ServiceCreate creates a new Service. 17 func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec, options types.ServiceCreateOptions) (types.ServiceCreateResponse, error) { 18 var distErr error 19 20 headers := map[string][]string{ 21 "version": {cli.version}, 22 } 23 24 if options.EncodedRegistryAuth != "" { 25 headers["X-Registry-Auth"] = []string{options.EncodedRegistryAuth} 26 } 27 28 // Make sure containerSpec is not nil when no runtime is set or the runtime is set to container 29 if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) { 30 service.TaskTemplate.ContainerSpec = &swarm.ContainerSpec{} 31 } 32 33 if err := validateServiceSpec(service); err != nil { 34 return types.ServiceCreateResponse{}, err 35 } 36 37 // ensure that the image is tagged 38 var imgPlatforms []swarm.Platform 39 if service.TaskTemplate.ContainerSpec != nil { 40 if taggedImg := imageWithTagString(service.TaskTemplate.ContainerSpec.Image); taggedImg != "" { 41 service.TaskTemplate.ContainerSpec.Image = taggedImg 42 } 43 if options.QueryRegistry { 44 var img string 45 img, imgPlatforms, distErr = imageDigestAndPlatforms(ctx, cli, service.TaskTemplate.ContainerSpec.Image, options.EncodedRegistryAuth) 46 if img != "" { 47 service.TaskTemplate.ContainerSpec.Image = img 48 } 49 } 50 } 51 52 // ensure that the image is tagged 53 if service.TaskTemplate.PluginSpec != nil { 54 if taggedImg := imageWithTagString(service.TaskTemplate.PluginSpec.Remote); taggedImg != "" { 55 service.TaskTemplate.PluginSpec.Remote = taggedImg 56 } 57 if options.QueryRegistry { 58 var img string 59 img, imgPlatforms, distErr = imageDigestAndPlatforms(ctx, cli, service.TaskTemplate.PluginSpec.Remote, options.EncodedRegistryAuth) 60 if img != "" { 61 service.TaskTemplate.PluginSpec.Remote = img 62 } 63 } 64 } 65 66 if service.TaskTemplate.Placement == nil && len(imgPlatforms) > 0 { 67 service.TaskTemplate.Placement = &swarm.Placement{} 68 } 69 if len(imgPlatforms) > 0 { 70 service.TaskTemplate.Placement.Platforms = imgPlatforms 71 } 72 73 var response types.ServiceCreateResponse 74 resp, err := cli.post(ctx, "/services/create", nil, service, headers) 75 if err != nil { 76 return response, err 77 } 78 79 err = json.NewDecoder(resp.body).Decode(&response) 80 81 if distErr != nil { 82 response.Warnings = append(response.Warnings, digestWarning(service.TaskTemplate.ContainerSpec.Image)) 83 } 84 85 ensureReaderClosed(resp) 86 return response, err 87 } 88 89 func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) { 90 distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth) 91 var platforms []swarm.Platform 92 if err != nil { 93 return "", nil, err 94 } 95 96 imageWithDigest := imageWithDigestString(image, distributionInspect.Descriptor.Digest) 97 98 if len(distributionInspect.Platforms) > 0 { 99 platforms = make([]swarm.Platform, 0, len(distributionInspect.Platforms)) 100 for _, p := range distributionInspect.Platforms { 101 // clear architecture field for arm. This is a temporary patch to address 102 // https://github.com/docker/swarmkit/issues/2294. The issue is that while 103 // image manifests report "arm" as the architecture, the node reports 104 // something like "armv7l" (includes the variant), which causes arm images 105 // to stop working with swarm mode. This patch removes the architecture 106 // constraint for arm images to ensure tasks get scheduled. 107 arch := p.Architecture 108 if strings.ToLower(arch) == "arm" { 109 arch = "" 110 } 111 platforms = append(platforms, swarm.Platform{ 112 Architecture: arch, 113 OS: p.OS, 114 }) 115 } 116 } 117 return imageWithDigest, platforms, err 118 } 119 120 // imageWithDigestString takes an image string and a digest, and updates 121 // the image string if it didn't originally contain a digest. It returns 122 // an empty string if there are no updates. 123 func imageWithDigestString(image string, dgst digest.Digest) string { 124 namedRef, err := reference.ParseNormalizedNamed(image) 125 if err == nil { 126 if _, isCanonical := namedRef.(reference.Canonical); !isCanonical { 127 // ensure that image gets a default tag if none is provided 128 img, err := reference.WithDigest(namedRef, dgst) 129 if err == nil { 130 return reference.FamiliarString(img) 131 } 132 } 133 } 134 return "" 135 } 136 137 // imageWithTagString takes an image string, and returns a tagged image 138 // string, adding a 'latest' tag if one was not provided. It returns an 139 // emptry string if a canonical reference was provided 140 func imageWithTagString(image string) string { 141 namedRef, err := reference.ParseNormalizedNamed(image) 142 if err == nil { 143 return reference.FamiliarString(reference.TagNameOnly(namedRef)) 144 } 145 return "" 146 } 147 148 // digestWarning constructs a formatted warning string using the 149 // image name that could not be pinned by digest. The formatting 150 // is hardcoded, but could me made smarter in the future 151 func digestWarning(image string) string { 152 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) 153 } 154 155 func validateServiceSpec(s swarm.ServiceSpec) error { 156 if s.TaskTemplate.ContainerSpec != nil && s.TaskTemplate.PluginSpec != nil { 157 return errors.New("must not specify both a container spec and a plugin spec in the task template") 158 } 159 if s.TaskTemplate.PluginSpec != nil && s.TaskTemplate.Runtime != swarm.RuntimePlugin { 160 return errors.New("mismatched runtime with plugin spec") 161 } 162 if s.TaskTemplate.ContainerSpec != nil && (s.TaskTemplate.Runtime != "" && s.TaskTemplate.Runtime != swarm.RuntimeContainer) { 163 return errors.New("mismatched runtime with container spec") 164 } 165 return nil 166 }