2016-09-06 18:46:37 +00:00
package client
import (
"encoding/json"
2017-04-05 22:43:17 +00:00
"fmt"
2017-07-07 22:10:15 +00:00
"strings"
2016-09-06 18:46:37 +00:00
2017-04-05 22:43:17 +00:00
"github.com/docker/distribution/reference"
2016-09-06 18:46:37 +00:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
2017-08-23 22:21:41 +00:00
digest "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
"golang.org/x/net/context"
)
// ServiceCreate creates a new Service.
func ( cli * Client ) ServiceCreate ( ctx context . Context , service swarm . ServiceSpec , options types . ServiceCreateOptions ) ( types . ServiceCreateResponse , error ) {
2017-05-12 20:51:52 +00:00
var distErr error
headers := map [ string ] [ ] string {
"version" : { cli . version } ,
}
2016-09-06 18:46:37 +00:00
if options . EncodedRegistryAuth != "" {
2017-05-12 20:51:52 +00:00
headers [ "X-Registry-Auth" ] = [ ] string { options . EncodedRegistryAuth }
2016-09-06 18:46:37 +00:00
}
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 {
return types . ServiceCreateResponse { } , err
2017-05-18 22:00:25 +00:00
}
2017-06-07 17:07:01 +00:00
// ensure that the image is tagged
var imgPlatforms [ ] swarm . Platform
if service . TaskTemplate . ContainerSpec != nil {
if taggedImg := imageWithTagString ( service . TaskTemplate . ContainerSpec . Image ) ; taggedImg != "" {
service . TaskTemplate . ContainerSpec . Image = taggedImg
}
if options . QueryRegistry {
var img string
img , imgPlatforms , distErr = imageDigestAndPlatforms ( ctx , cli , service . TaskTemplate . ContainerSpec . Image , options . EncodedRegistryAuth )
if img != "" {
2017-04-05 22:43:17 +00:00
service . TaskTemplate . ContainerSpec . Image = img
}
}
}
2017-06-07 17:07:01 +00:00
// ensure that the image is tagged
if service . TaskTemplate . PluginSpec != nil {
if taggedImg := imageWithTagString ( service . TaskTemplate . PluginSpec . Remote ) ; taggedImg != "" {
service . TaskTemplate . PluginSpec . Remote = taggedImg
}
if options . QueryRegistry {
var img string
img , imgPlatforms , distErr = imageDigestAndPlatforms ( ctx , cli , service . TaskTemplate . PluginSpec . Remote , options . EncodedRegistryAuth )
if img != "" {
service . TaskTemplate . PluginSpec . Remote = img
}
}
}
if service . TaskTemplate . Placement == nil && len ( imgPlatforms ) > 0 {
service . TaskTemplate . Placement = & swarm . Placement { }
}
if len ( imgPlatforms ) > 0 {
service . TaskTemplate . Placement . Platforms = imgPlatforms
}
2016-09-06 18:46:37 +00:00
var response types . ServiceCreateResponse
resp , err := cli . post ( ctx , "/services/create" , nil , service , headers )
if err != nil {
return response , err
}
err = json . NewDecoder ( resp . body ) . Decode ( & response )
2017-04-05 22:43:17 +00:00
if distErr != nil {
response . Warnings = append ( response . Warnings , digestWarning ( service . TaskTemplate . ContainerSpec . Image ) )
}
2016-09-06 18:46:37 +00:00
ensureReaderClosed ( resp )
return response , err
}
2017-04-05 22:43:17 +00:00
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 )
imageWithDigest := image
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 {
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.
arch := strings . ToLower ( p . Architecture )
if arch == "arm" {
arch = ""
}
2017-06-07 17:07:01 +00:00
platforms = append ( platforms , swarm . Platform {
Architecture : p . Architecture ,
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
// an empty string if there are no updates.
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
}
}
}
return ""
}
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
// emptry 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 ""
}
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
}