2018-02-05 21:05:59 +00:00
package client // import "github.com/docker/docker/client"
2016-09-06 18:46:37 +00:00
import (
2018-04-19 22:30:59 +00:00
"context"
2016-09-06 18:46:37 +00:00
"encoding/json"
2017-04-05 22:43:17 +00:00
"fmt"
2023-07-10 14:44:59 +00:00
"net/http"
2017-07-07 22:10:15 +00:00
"strings"
2016-09-06 18:46:37 +00:00
2023-08-30 16:31:46 +00:00
"github.com/distribution/reference"
2016-09-06 18:46:37 +00:00
"github.com/docker/docker/api/types"
2021-08-26 19:08:38 +00:00
"github.com/docker/docker/api/types/registry"
2016-09-06 18:46:37 +00:00
"github.com/docker/docker/api/types/swarm"
2023-07-10 14:33:20 +00:00
"github.com/docker/docker/api/types/versions"
2022-03-04 13:49:42 +00:00
"github.com/opencontainers/go-digest"
2017-06-07 17:07:01 +00:00
"github.com/pkg/errors"
2016-09-06 18:46:37 +00:00
)
2021-02-16 15:07:44 +00:00
// ServiceCreate creates a new service.
2022-11-24 18:36:01 +00:00
func ( cli * Client ) ServiceCreate ( ctx context . Context , service swarm . ServiceSpec , options types . ServiceCreateOptions ) ( swarm . ServiceCreateResponse , error ) {
var response swarm . ServiceCreateResponse
2016-09-06 18:46:37 +00:00
2023-09-12 12:08:54 +00:00
// 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.
cli . checkVersion ( ctx )
2017-06-07 17:07:01 +00:00
// 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 {
2020-04-16 11:03:39 +00:00
return response , err
2017-05-18 22:00:25 +00:00
}
2017-06-07 17:07:01 +00:00
// ensure that the image is tagged
2020-04-16 11:03:39 +00:00
var resolveWarning string
switch {
case service . TaskTemplate . ContainerSpec != nil :
2017-06-07 17:07:01 +00:00
if taggedImg := imageWithTagString ( service . TaskTemplate . ContainerSpec . Image ) ; taggedImg != "" {
service . TaskTemplate . ContainerSpec . Image = taggedImg
}
if options . QueryRegistry {
2020-04-16 11:42:47 +00:00
resolveWarning = resolveContainerSpecImage ( ctx , cli , & service . TaskTemplate , options . EncodedRegistryAuth )
2017-04-05 22:43:17 +00:00
}
2020-04-16 11:03:39 +00:00
case service . TaskTemplate . PluginSpec != nil :
2017-06-07 17:07:01 +00:00
if taggedImg := imageWithTagString ( service . TaskTemplate . PluginSpec . Remote ) ; taggedImg != "" {
service . TaskTemplate . PluginSpec . Remote = taggedImg
}
if options . QueryRegistry {
2020-04-16 11:42:47 +00:00
resolveWarning = resolvePluginSpecRemote ( ctx , cli , & service . TaskTemplate , options . EncodedRegistryAuth )
2017-06-07 17:07:01 +00:00
}
}
2023-07-10 14:44:59 +00:00
headers := http . Header { }
2023-07-10 14:33:20 +00:00
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 }
}
2016-09-06 18:46:37 +00:00
resp , err := cli . post ( ctx , "/services/create" , nil , service , headers )
2019-02-11 12:26:12 +00:00
defer ensureReaderClosed ( resp )
2016-09-06 18:46:37 +00:00
if err != nil {
return response , err
}
err = json . NewDecoder ( resp . body ) . Decode ( & response )
2020-04-16 11:03:39 +00:00
if resolveWarning != "" {
response . Warnings = append ( response . Warnings , resolveWarning )
2017-04-05 22:43:17 +00:00
}
2016-09-06 18:46:37 +00:00
return response , err
}
2017-04-05 22:43:17 +00:00
2020-04-16 11:42:47 +00:00
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
}
2017-08-23 22:21:41 +00:00
func imageDigestAndPlatforms ( ctx context . Context , cli DistributionAPIClient , image , encodedAuth string ) ( string , [ ] swarm . Platform , error ) {
2017-06-07 17:07:01 +00:00
distributionInspect , err := cli . DistributionInspect ( ctx , image , encodedAuth )
var platforms [ ] swarm . Platform
if err != nil {
return "" , nil , err
}
2017-09-08 22:00:14 +00:00
imageWithDigest := imageWithDigestString ( image , distributionInspect . Descriptor . Digest )
2017-06-07 17:07:01 +00:00
if len ( distributionInspect . Platforms ) > 0 {
platforms = make ( [ ] swarm . Platform , 0 , len ( distributionInspect . Platforms ) )
for _ , p := range distributionInspect . Platforms {
2017-07-07 22:10:15 +00:00
// 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.
2017-09-08 22:00:14 +00:00
arch := p . Architecture
if strings . ToLower ( arch ) == "arm" {
2017-07-07 22:10:15 +00:00
arch = ""
}
2017-06-07 17:07:01 +00:00
platforms = append ( platforms , swarm . Platform {
2017-09-08 22:00:14 +00:00
Architecture : arch ,
2017-06-07 17:07:01 +00:00
OS : p . OS ,
} )
}
}
return imageWithDigest , platforms , err
}
2017-04-05 22:43:17 +00:00
// imageWithDigestString takes an image string and a digest, and updates
2017-05-18 22:00:25 +00:00
// the image string if it didn't originally contain a digest. It returns
2020-04-16 11:20:38 +00:00
// image unmodified in other situations.
2017-04-05 22:43:17 +00:00
func imageWithDigestString ( image string , dgst digest . Digest ) string {
2017-05-18 22:00:25 +00:00
namedRef , err := reference . ParseNormalizedNamed ( image )
2017-04-05 22:43:17 +00:00
if err == nil {
2017-05-18 22:00:25 +00:00
if _ , isCanonical := namedRef . ( reference . Canonical ) ; ! isCanonical {
// ensure that image gets a default tag if none is provided
2017-04-05 22:43:17 +00:00
img , err := reference . WithDigest ( namedRef , dgst )
if err == nil {
2017-05-18 22:00:25 +00:00
return reference . FamiliarString ( img )
2017-04-05 22:43:17 +00:00
}
}
}
2020-04-16 11:20:38 +00:00
return image
2017-04-05 22:43:17 +00:00
}
2017-05-18 22:00:25 +00:00
// imageWithTagString takes an image string, and returns a tagged image
// string, adding a 'latest' tag if one was not provided. It returns an
2018-05-16 01:15:43 +00:00
// empty string if a canonical reference was provided
2017-05-18 22:00:25 +00:00
func imageWithTagString ( image string ) string {
namedRef , err := reference . ParseNormalizedNamed ( image )
if err == nil {
return reference . FamiliarString ( reference . TagNameOnly ( namedRef ) )
}
return ""
}
2017-04-05 22:43:17 +00:00
// 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 )
}
2017-06-07 17:07:01 +00:00
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
}