2016-09-06 14:46:37 -04:00
package client
import (
"encoding/json"
2017-04-05 18:43:17 -04:00
"fmt"
2016-09-06 14:46:37 -04:00
2017-04-05 18:43:17 -04:00
"github.com/docker/distribution/reference"
2016-09-06 14:46:37 -04:00
"github.com/docker/docker/api/types"
2017-05-16 19:09:53 -04:00
registrytypes "github.com/docker/docker/api/types/registry"
2016-09-06 14:46:37 -04:00
"github.com/docker/docker/api/types/swarm"
2017-04-05 18:43:17 -04:00
"github.com/opencontainers/go-digest"
2016-09-06 14:46:37 -04: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 16:51:52 -04:00
var distErr error
headers := map [ string ] [ ] string {
"version" : { cli . version } ,
}
2016-09-06 14:46:37 -04:00
if options . EncodedRegistryAuth != "" {
2017-05-12 16:51:52 -04:00
headers [ "X-Registry-Auth" ] = [ ] string { options . EncodedRegistryAuth }
2016-09-06 14:46:37 -04:00
}
2017-05-18 18:00:25 -04:00
// ensure that the image is tagged
if taggedImg := imageWithTagString ( service . TaskTemplate . ContainerSpec . Image ) ; taggedImg != "" {
service . TaskTemplate . ContainerSpec . Image = taggedImg
}
2017-04-05 18:43:17 -04:00
// Contact the registry to retrieve digest and platform information
if options . QueryRegistry {
distributionInspect , err := cli . DistributionInspect ( ctx , service . TaskTemplate . ContainerSpec . Image , options . EncodedRegistryAuth )
distErr = err
if err == nil {
// now pin by digest if the image doesn't already contain a digest
2017-05-18 18:00:25 -04:00
if img := imageWithDigestString ( service . TaskTemplate . ContainerSpec . Image , distributionInspect . Descriptor . Digest ) ; img != "" {
2017-04-05 18:43:17 -04:00
service . TaskTemplate . ContainerSpec . Image = img
}
2017-05-16 19:09:53 -04:00
// add platforms that are compatible with the service
service . TaskTemplate . Placement = updateServicePlatforms ( service . TaskTemplate . Placement , distributionInspect )
2017-04-05 18:43:17 -04:00
}
}
2016-09-06 14:46:37 -04: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 18:43:17 -04:00
if distErr != nil {
response . Warnings = append ( response . Warnings , digestWarning ( service . TaskTemplate . ContainerSpec . Image ) )
}
2016-09-06 14:46:37 -04:00
ensureReaderClosed ( resp )
return response , err
}
2017-04-05 18:43:17 -04:00
// imageWithDigestString takes an image string and a digest, and updates
2017-05-18 18:00:25 -04: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 18:43:17 -04:00
func imageWithDigestString ( image string , dgst digest . Digest ) string {
2017-05-18 18:00:25 -04:00
namedRef , err := reference . ParseNormalizedNamed ( image )
2017-04-05 18:43:17 -04:00
if err == nil {
2017-05-18 18:00:25 -04:00
if _ , isCanonical := namedRef . ( reference . Canonical ) ; ! isCanonical {
// ensure that image gets a default tag if none is provided
2017-04-05 18:43:17 -04:00
img , err := reference . WithDigest ( namedRef , dgst )
if err == nil {
2017-05-18 18:00:25 -04:00
return reference . FamiliarString ( img )
2017-04-05 18:43:17 -04:00
}
}
}
return ""
}
2017-05-18 18:00:25 -04: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-05-16 19:09:53 -04:00
// updateServicePlatforms updates the Platforms in swarm.Placement to list
// all compatible platforms for the service, as found in distributionInspect
// and returns a pointer to the new or updated swarm.Placement struct
func updateServicePlatforms ( placement * swarm . Placement , distributionInspect registrytypes . DistributionInspect ) * swarm . Placement {
if placement == nil {
placement = & swarm . Placement { }
}
for _ , p := range distributionInspect . Platforms {
placement . Platforms = append ( placement . Platforms , swarm . Platform {
Architecture : p . Architecture ,
OS : p . OS ,
} )
}
return placement
}
2017-04-05 18:43:17 -04: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 )
}