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