2016-09-08 13:11:39 -04:00
package stack
import (
2016-11-21 15:03:43 -05:00
"errors"
2016-09-08 13:11:39 -04:00
"fmt"
2016-11-02 14:57:40 -04:00
"io/ioutil"
"os"
2016-10-31 15:43:47 -04:00
"sort"
2016-10-25 17:41:45 -04:00
"strings"
2016-11-18 09:09:13 -05:00
"time"
2016-09-08 13:11:39 -04:00
"github.com/spf13/cobra"
"golang.org/x/net/context"
2016-11-02 14:57:40 -04:00
"github.com/aanand/compose-file/loader"
composetypes "github.com/aanand/compose-file/types"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api/types"
2016-11-18 09:09:13 -05:00
"github.com/docker/docker/api/types/container"
2016-10-25 17:41:45 -04:00
"github.com/docker/docker/api/types/mount"
2016-11-02 14:57:40 -04:00
networktypes "github.com/docker/docker/api/types/network"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/cli"
2016-09-08 14:54:01 -04:00
"github.com/docker/docker/cli/command"
2016-11-02 14:57:40 -04:00
servicecmd "github.com/docker/docker/cli/command/service"
2016-11-28 11:38:41 -05:00
dockerclient "github.com/docker/docker/client"
2016-10-28 17:30:20 -04:00
"github.com/docker/docker/opts"
2016-11-04 16:55:24 -04:00
runconfigopts "github.com/docker/docker/runconfig/opts"
2016-11-02 14:57:40 -04:00
"github.com/docker/go-connections/nat"
2016-09-08 13:11:39 -04:00
)
const (
defaultNetworkDriver = "overlay"
)
type deployOptions struct {
2016-11-08 12:05:23 -05:00
bundlefile string
2016-11-02 14:57:40 -04:00
composefile string
2016-09-08 13:11:39 -04:00
namespace string
sendRegistryAuth bool
}
2016-09-08 14:54:01 -04:00
func newDeployCommand ( dockerCli * command . DockerCli ) * cobra . Command {
2016-09-08 13:11:39 -04:00
var opts deployOptions
cmd := & cobra . Command {
Use : "deploy [OPTIONS] STACK" ,
Aliases : [ ] string { "up" } ,
2016-11-02 14:57:40 -04:00
Short : "Deploy a new stack or update an existing stack" ,
2016-09-08 13:11:39 -04:00
Args : cli . ExactArgs ( 1 ) ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2016-11-02 14:57:40 -04:00
opts . namespace = args [ 0 ]
2016-09-08 13:11:39 -04:00
return runDeploy ( dockerCli , opts )
} ,
}
flags := cmd . Flags ( )
2016-11-08 12:05:23 -05:00
addBundlefileFlag ( & opts . bundlefile , flags )
2016-11-02 14:57:40 -04:00
addComposefileFlag ( & opts . composefile , flags )
2016-09-08 13:11:39 -04:00
addRegistryAuthFlag ( & opts . sendRegistryAuth , flags )
return cmd
}
2016-09-08 14:54:01 -04:00
func runDeploy ( dockerCli * command . DockerCli , opts deployOptions ) error {
2016-11-21 15:03:43 -05:00
ctx := context . Background ( )
2016-11-08 15:20:16 -05:00
switch {
case opts . bundlefile == "" && opts . composefile == "" :
2016-11-08 12:05:23 -05:00
return fmt . Errorf ( "Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file)." )
2016-11-08 15:20:16 -05:00
case opts . bundlefile != "" && opts . composefile != "" :
2016-11-08 12:05:23 -05:00
return fmt . Errorf ( "You cannot specify both a bundle file and a Compose file." )
2016-11-08 15:20:16 -05:00
case opts . bundlefile != "" :
2016-11-21 15:03:43 -05:00
return deployBundle ( ctx , dockerCli , opts )
2016-11-08 15:20:16 -05:00
default :
2016-11-21 15:03:43 -05:00
return deployCompose ( ctx , dockerCli , opts )
}
}
// checkDaemonIsSwarmManager does an Info API call to verify that the daemon is
// a swarm manager. This is necessary because we must create networks before we
// create services, but the API call for creating a network does not return a
// proper status code when it can't create a network in the "global" scope.
func checkDaemonIsSwarmManager ( ctx context . Context , dockerCli * command . DockerCli ) error {
info , err := dockerCli . Client ( ) . Info ( ctx )
if err != nil {
return err
}
if ! info . Swarm . ControlAvailable {
return errors . New ( "This node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again." )
2016-11-08 12:05:23 -05:00
}
2016-11-21 15:03:43 -05:00
return nil
2016-11-08 12:05:23 -05:00
}
2016-11-21 15:03:43 -05:00
func deployCompose ( ctx context . Context , dockerCli * command . DockerCli , opts deployOptions ) error {
2016-11-02 14:57:40 -04:00
configDetails , err := getConfigDetails ( opts )
2016-09-08 13:11:39 -04:00
if err != nil {
return err
}
2016-11-02 14:57:40 -04:00
config , err := loader . Load ( configDetails )
2016-09-08 13:11:39 -04:00
if err != nil {
2016-10-31 15:43:47 -04:00
if fpe , ok := err . ( * loader . ForbiddenPropertiesError ) ; ok {
return fmt . Errorf ( "Compose file contains unsupported options:\n\n%s\n" ,
propertyWarnings ( fpe . Properties ) )
}
2016-09-08 13:11:39 -04:00
return err
}
2016-10-31 15:43:47 -04:00
unsupportedProperties := loader . GetUnsupportedProperties ( configDetails )
if len ( unsupportedProperties ) > 0 {
2016-11-02 12:19:37 -04:00
fmt . Fprintf ( dockerCli . Err ( ) , "Ignoring unsupported options: %s\n\n" ,
2016-10-31 15:43:47 -04:00
strings . Join ( unsupportedProperties , ", " ) )
}
deprecatedProperties := loader . GetDeprecatedProperties ( configDetails )
if len ( deprecatedProperties ) > 0 {
2016-11-02 12:19:37 -04:00
fmt . Fprintf ( dockerCli . Err ( ) , "Ignoring deprecated options:\n\n%s\n\n" ,
2016-10-31 15:43:47 -04:00
propertyWarnings ( deprecatedProperties ) )
}
2016-11-21 15:03:43 -05:00
if err := checkDaemonIsSwarmManager ( ctx , dockerCli ) ; err != nil {
return err
}
2016-11-03 19:50:03 -04:00
namespace := namespace { name : opts . namespace }
2016-11-04 16:55:24 -04:00
2016-11-28 11:38:41 -05:00
networks , externalNetworks := convertNetworks ( namespace , config . Networks )
if err := validateExternalNetworks ( ctx , dockerCli , externalNetworks ) ; err != nil {
return err
}
2016-11-08 12:05:23 -05:00
if err := createNetworks ( ctx , dockerCli , namespace , networks ) ; err != nil {
return err
2016-11-04 16:55:24 -04:00
}
2016-11-08 12:05:23 -05:00
services , err := convertServices ( namespace , config )
if err != nil {
2016-09-08 13:11:39 -04:00
return err
}
2016-11-08 12:05:23 -05:00
return deployServices ( ctx , dockerCli , services , namespace , opts . sendRegistryAuth )
2016-09-08 13:11:39 -04:00
}
2016-10-31 15:43:47 -04:00
func propertyWarnings ( properties map [ string ] string ) string {
var msgs [ ] string
for name , description := range properties {
msgs = append ( msgs , fmt . Sprintf ( "%s: %s" , name , description ) )
}
sort . Strings ( msgs )
return strings . Join ( msgs , "\n\n" )
}
2016-11-02 14:57:40 -04:00
func getConfigDetails ( opts deployOptions ) ( composetypes . ConfigDetails , error ) {
var details composetypes . ConfigDetails
var err error
details . WorkingDir , err = os . Getwd ( )
if err != nil {
return details , err
}
configFile , err := getConfigFile ( opts . composefile )
if err != nil {
return details , err
2016-09-08 13:11:39 -04:00
}
2016-11-02 14:57:40 -04:00
// TODO: support multiple files
details . ConfigFiles = [ ] composetypes . ConfigFile { * configFile }
return details , nil
}
2016-09-08 13:11:39 -04:00
2016-11-02 14:57:40 -04:00
func getConfigFile ( filename string ) ( * composetypes . ConfigFile , error ) {
bytes , err := ioutil . ReadFile ( filename )
if err != nil {
return nil , err
2016-09-08 13:11:39 -04:00
}
2016-10-25 17:41:45 -04:00
config , err := loader . ParseYAML ( bytes )
if err != nil {
return nil , err
}
return & composetypes . ConfigFile {
Filename : filename ,
Config : config ,
} , nil
2016-09-08 13:11:39 -04:00
}
2016-11-08 12:05:23 -05:00
func convertNetworks (
2016-11-03 19:50:03 -04:00
namespace namespace ,
2016-11-08 12:05:23 -05:00
networks map [ string ] composetypes . NetworkConfig ,
2016-11-28 11:38:41 -05:00
) ( map [ string ] types . NetworkCreate , [ ] string ) {
2016-11-08 12:05:23 -05:00
if networks == nil {
networks = make ( map [ string ] composetypes . NetworkConfig )
2016-09-08 13:11:39 -04:00
}
2016-11-03 19:40:48 -04:00
// TODO: only add default network if it's used
networks [ "default" ] = composetypes . NetworkConfig { }
2016-11-28 11:38:41 -05:00
externalNetworks := [ ] string { }
2016-11-08 12:05:23 -05:00
result := make ( map [ string ] types . NetworkCreate )
2016-11-02 14:57:40 -04:00
for internalName , network := range networks {
2016-11-28 11:38:41 -05:00
if network . External . External {
externalNetworks = append ( externalNetworks , network . External . Name )
2016-11-02 14:57:40 -04:00
continue
}
2016-09-08 13:11:39 -04:00
2016-11-02 14:57:40 -04:00
createOpts := types . NetworkCreate {
2016-11-03 19:50:03 -04:00
Labels : getStackLabels ( namespace . name , network . Labels ) ,
2016-11-02 14:57:40 -04:00
Driver : network . Driver ,
Options : network . DriverOpts ,
}
2016-11-10 16:22:31 -05:00
if network . Ipam . Driver != "" || len ( network . Ipam . Config ) > 0 {
createOpts . IPAM = & networktypes . IPAM { }
}
2016-11-02 14:57:40 -04:00
if network . Ipam . Driver != "" {
2016-11-10 16:22:31 -05:00
createOpts . IPAM . Driver = network . Ipam . Driver
}
for _ , ipamConfig := range network . Ipam . Config {
config := networktypes . IPAMConfig {
Subnet : ipamConfig . Subnet ,
2016-11-02 14:57:40 -04:00
}
2016-11-10 16:22:31 -05:00
createOpts . IPAM . Config = append ( createOpts . IPAM . Config , config )
2016-11-02 14:57:40 -04:00
}
2016-11-08 12:05:23 -05:00
result [ internalName ] = createOpts
}
2016-11-28 11:38:41 -05:00
return result , externalNetworks
}
func validateExternalNetworks (
ctx context . Context ,
dockerCli * command . DockerCli ,
externalNetworks [ ] string ) error {
client := dockerCli . Client ( )
for _ , networkName := range externalNetworks {
network , err := client . NetworkInspect ( ctx , networkName )
if err != nil {
if dockerclient . IsErrNetworkNotFound ( err ) {
return fmt . Errorf ( "network %q is declared as external, but could not be found. You need to create the network before the stack is deployed (with overlay driver)" , networkName )
}
return err
}
if network . Scope != "swarm" {
return fmt . Errorf ( "network %q is declared as external, but it is not in the right scope: %q instead of %q" , networkName , network . Scope , "swarm" )
}
}
return nil
2016-11-08 12:05:23 -05:00
}
func createNetworks (
ctx context . Context ,
dockerCli * command . DockerCli ,
namespace namespace ,
networks map [ string ] types . NetworkCreate ,
) error {
client := dockerCli . Client ( )
2016-11-28 11:38:41 -05:00
existingNetworks , err := getStackNetworks ( ctx , client , namespace . name )
2016-11-08 12:05:23 -05:00
if err != nil {
return err
}
existingNetworkMap := make ( map [ string ] types . NetworkResource )
for _ , network := range existingNetworks {
existingNetworkMap [ network . Name ] = network
}
for internalName , createOpts := range networks {
name := namespace . scope ( internalName )
if _ , exists := existingNetworkMap [ name ] ; exists {
continue
}
2016-11-02 14:57:40 -04:00
if createOpts . Driver == "" {
createOpts . Driver = defaultNetworkDriver
}
2016-09-08 13:11:39 -04:00
fmt . Fprintf ( dockerCli . Out ( ) , "Creating network %s\n" , name )
if _ , err := client . NetworkCreate ( ctx , name , createOpts ) ; err != nil {
return err
}
}
2016-11-08 12:05:23 -05:00
2016-09-08 13:11:39 -04:00
return nil
}
2016-11-08 12:05:23 -05:00
func convertServiceNetworks (
2016-11-02 14:57:40 -04:00
networks map [ string ] * composetypes . ServiceNetworkConfig ,
2016-11-28 11:38:41 -05:00
networkConfigs map [ string ] composetypes . NetworkConfig ,
2016-11-03 19:50:03 -04:00
namespace namespace ,
2016-11-02 14:57:40 -04:00
name string ,
2016-11-28 11:38:41 -05:00
) ( [ ] swarm . NetworkAttachmentConfig , error ) {
2016-11-03 19:40:48 -04:00
if len ( networks ) == 0 {
return [ ] swarm . NetworkAttachmentConfig {
{
2016-11-03 19:50:03 -04:00
Target : namespace . scope ( "default" ) ,
2016-11-03 19:40:48 -04:00
Aliases : [ ] string { name } ,
} ,
2016-11-28 11:38:41 -05:00
} , nil
2016-11-03 19:40:48 -04:00
}
2016-09-08 13:11:39 -04:00
nets := [ ] swarm . NetworkAttachmentConfig { }
2016-11-02 14:57:40 -04:00
for networkName , network := range networks {
2016-11-28 11:38:41 -05:00
networkConfig , ok := networkConfigs [ networkName ]
if ! ok {
return [ ] swarm . NetworkAttachmentConfig { } , fmt . Errorf ( "invalid network: %s" , networkName )
}
2016-11-21 11:59:29 -05:00
var aliases [ ] string
if network != nil {
aliases = network . Aliases
}
2016-11-28 11:38:41 -05:00
target := namespace . scope ( networkName )
if networkConfig . External . External {
target = networkName
}
2016-09-08 13:11:39 -04:00
nets = append ( nets , swarm . NetworkAttachmentConfig {
2016-11-28 11:38:41 -05:00
Target : target ,
2016-11-21 11:59:29 -05:00
Aliases : append ( aliases , name ) ,
2016-09-08 13:11:39 -04:00
} )
}
2016-11-28 11:38:41 -05:00
return nets , nil
2016-09-08 13:11:39 -04:00
}
2016-10-25 17:41:45 -04:00
func convertVolumes (
serviceVolumes [ ] string ,
stackVolumes map [ string ] composetypes . VolumeConfig ,
2016-11-03 19:50:03 -04:00
namespace namespace ,
2016-10-25 17:41:45 -04:00
) ( [ ] mount . Mount , error ) {
var mounts [ ] mount . Mount
2016-11-10 11:33:00 -05:00
for _ , volumeSpec := range serviceVolumes {
mount , err := convertVolumeToMount ( volumeSpec , stackVolumes , namespace )
if err != nil {
return nil , err
2016-10-25 17:41:45 -04:00
}
2016-11-10 11:33:00 -05:00
mounts = append ( mounts , mount )
}
return mounts , nil
}
2016-10-25 17:41:45 -04:00
2016-11-10 11:33:00 -05:00
func convertVolumeToMount (
volumeSpec string ,
stackVolumes map [ string ] composetypes . VolumeConfig ,
namespace namespace ,
) ( mount . Mount , error ) {
var source , target string
var mode [ ] string
// TODO: split Windows path mappings properly
parts := strings . SplitN ( volumeSpec , ":" , 3 )
switch len ( parts ) {
case 3 :
source = parts [ 0 ]
target = parts [ 1 ]
mode = strings . Split ( parts [ 2 ] , "," )
case 2 :
source = parts [ 0 ]
target = parts [ 1 ]
case 1 :
target = parts [ 0 ]
default :
return mount . Mount { } , fmt . Errorf ( "invald volume: %s" , volumeSpec )
}
2016-10-25 17:41:45 -04:00
2016-11-10 11:33:00 -05:00
// TODO: catch Windows paths here
if strings . HasPrefix ( source , "/" ) {
return mount . Mount {
Type : mount . TypeBind ,
Source : source ,
Target : target ,
ReadOnly : isReadOnly ( mode ) ,
BindOptions : getBindOptions ( mode ) ,
} , nil
}
stackVolume , exists := stackVolumes [ source ]
if ! exists {
return mount . Mount { } , fmt . Errorf ( "undefined volume: %s" , source )
}
2016-10-25 17:41:45 -04:00
2016-11-10 11:33:00 -05:00
var volumeOptions * mount . VolumeOptions
if stackVolume . External . Name != "" {
source = stackVolume . External . Name
} else {
volumeOptions = & mount . VolumeOptions {
2016-11-28 18:02:39 -05:00
Labels : getStackLabels ( namespace . name , stackVolume . Labels ) ,
2016-11-10 11:33:00 -05:00
NoCopy : isNoCopy ( mode ) ,
}
if stackVolume . Driver != "" {
volumeOptions . DriverConfig = & mount . Driver {
Name : stackVolume . Driver ,
Options : stackVolume . DriverOpts ,
2016-10-25 17:41:45 -04:00
}
}
2016-11-10 11:33:00 -05:00
source = namespace . scope ( source )
}
return mount . Mount {
Type : mount . TypeVolume ,
Source : source ,
Target : target ,
ReadOnly : isReadOnly ( mode ) ,
VolumeOptions : volumeOptions ,
} , nil
}
2016-10-25 17:41:45 -04:00
2016-11-10 11:33:00 -05:00
func modeHas ( mode [ ] string , field string ) bool {
for _ , item := range mode {
if item == field {
return true
}
2016-10-25 17:41:45 -04:00
}
2016-11-10 11:33:00 -05:00
return false
}
2016-10-25 17:41:45 -04:00
2016-11-10 11:33:00 -05:00
func isReadOnly ( mode [ ] string ) bool {
return modeHas ( mode , "ro" )
}
func isNoCopy ( mode [ ] string ) bool {
return modeHas ( mode , "nocopy" )
}
func getBindOptions ( mode [ ] string ) * mount . BindOptions {
for _ , item := range mode {
if strings . Contains ( item , "private" ) || strings . Contains ( item , "shared" ) || strings . Contains ( item , "slave" ) {
return & mount . BindOptions { Propagation : mount . Propagation ( item ) }
}
}
return nil
2016-10-25 17:41:45 -04:00
}
2016-09-08 13:11:39 -04:00
func deployServices (
ctx context . Context ,
2016-09-08 14:54:01 -04:00
dockerCli * command . DockerCli ,
2016-11-08 12:05:23 -05:00
services map [ string ] swarm . ServiceSpec ,
2016-11-03 19:50:03 -04:00
namespace namespace ,
2016-09-08 13:11:39 -04:00
sendAuth bool ,
) error {
apiClient := dockerCli . Client ( )
out := dockerCli . Out ( )
2016-11-03 19:50:03 -04:00
existingServices , err := getServices ( ctx , apiClient , namespace . name )
2016-09-08 13:11:39 -04:00
if err != nil {
return err
}
existingServiceMap := make ( map [ string ] swarm . Service )
for _ , service := range existingServices {
existingServiceMap [ service . Spec . Name ] = service
}
2016-11-08 12:05:23 -05:00
for internalName , serviceSpec := range services {
name := namespace . scope ( internalName )
2016-09-08 13:11:39 -04:00
encodedAuth := ""
if sendAuth {
// Retrieve encoded auth token from the image reference
image := serviceSpec . TaskTemplate . ContainerSpec . Image
2016-09-09 15:38:00 -04:00
encodedAuth , err = command . RetrieveAuthTokenFromImage ( ctx , dockerCli , image )
2016-09-08 13:11:39 -04:00
if err != nil {
return err
}
}
if service , exists := existingServiceMap [ name ] ; exists {
fmt . Fprintf ( out , "Updating service %s (id: %s)\n" , name , service . ID )
updateOpts := types . ServiceUpdateOptions { }
if sendAuth {
updateOpts . EncodedRegistryAuth = encodedAuth
}
2016-11-14 21:08:24 -05:00
response , err := apiClient . ServiceUpdate (
2016-09-08 13:11:39 -04:00
ctx ,
service . ID ,
service . Version ,
serviceSpec ,
updateOpts ,
2016-11-14 21:08:24 -05:00
)
if err != nil {
2016-09-08 13:11:39 -04:00
return err
}
2016-11-14 21:08:24 -05:00
for _ , warning := range response . Warnings {
fmt . Fprintln ( dockerCli . Err ( ) , warning )
}
2016-09-08 13:11:39 -04:00
} else {
fmt . Fprintf ( out , "Creating service %s\n" , name )
createOpts := types . ServiceCreateOptions { }
if sendAuth {
createOpts . EncodedRegistryAuth = encodedAuth
}
if _ , err := apiClient . ServiceCreate ( ctx , serviceSpec , createOpts ) ; err != nil {
return err
}
}
}
return nil
}
2016-11-02 14:57:40 -04:00
2016-11-08 12:05:23 -05:00
func convertServices (
namespace namespace ,
config * composetypes . Config ,
) ( map [ string ] swarm . ServiceSpec , error ) {
result := make ( map [ string ] swarm . ServiceSpec )
services := config . Services
volumes := config . Volumes
2016-11-28 11:38:41 -05:00
networks := config . Networks
2016-11-08 12:05:23 -05:00
for _ , service := range services {
2016-11-28 11:38:41 -05:00
serviceSpec , err := convertService ( namespace , service , networks , volumes )
2016-11-08 12:05:23 -05:00
if err != nil {
return nil , err
}
result [ service . Name ] = serviceSpec
}
return result , nil
}
2016-11-02 14:57:40 -04:00
func convertService (
2016-11-03 19:50:03 -04:00
namespace namespace ,
2016-11-02 14:57:40 -04:00
service composetypes . ServiceConfig ,
2016-11-28 11:38:41 -05:00
networkConfigs map [ string ] composetypes . NetworkConfig ,
2016-11-02 14:57:40 -04:00
volumes map [ string ] composetypes . VolumeConfig ,
) ( swarm . ServiceSpec , error ) {
2016-11-03 19:50:03 -04:00
name := namespace . scope ( service . Name )
2016-11-02 14:57:40 -04:00
endpoint , err := convertEndpointSpec ( service . Ports )
if err != nil {
return swarm . ServiceSpec { } , err
}
mode , err := convertDeployMode ( service . Deploy . Mode , service . Deploy . Replicas )
if err != nil {
return swarm . ServiceSpec { } , err
}
2016-10-25 17:41:45 -04:00
mounts , err := convertVolumes ( service . Volumes , volumes , namespace )
if err != nil {
2016-11-10 11:33:00 -05:00
// TODO: better error message (include service name)
2016-10-25 17:41:45 -04:00
return swarm . ServiceSpec { } , err
}
2016-10-28 17:30:20 -04:00
resources , err := convertResources ( service . Deploy . Resources )
if err != nil {
return swarm . ServiceSpec { } , err
}
restartPolicy , err := convertRestartPolicy (
service . Restart , service . Deploy . RestartPolicy )
if err != nil {
return swarm . ServiceSpec { } , err
}
2016-11-18 09:09:13 -05:00
healthcheck , err := convertHealthcheck ( service . HealthCheck )
if err != nil {
return swarm . ServiceSpec { } , err
}
2016-11-28 11:38:41 -05:00
networks , err := convertServiceNetworks ( service . Networks , networkConfigs , namespace , service . Name )
if err != nil {
return swarm . ServiceSpec { } , err
}
2016-12-05 09:18:36 -05:00
var logDriver * swarm . Driver
if service . Logging != nil {
logDriver = & swarm . Driver {
Name : service . Logging . Driver ,
Options : service . Logging . Options ,
}
}
2016-11-02 14:57:40 -04:00
serviceSpec := swarm . ServiceSpec {
Annotations : swarm . Annotations {
Name : name ,
2016-11-03 19:50:03 -04:00
Labels : getStackLabels ( namespace . name , service . Deploy . Labels ) ,
2016-11-02 14:57:40 -04:00
} ,
TaskTemplate : swarm . TaskSpec {
ContainerSpec : swarm . ContainerSpec {
2016-10-28 17:30:20 -04:00
Image : service . Image ,
Command : service . Entrypoint ,
Args : service . Command ,
Hostname : service . Hostname ,
2016-11-11 08:19:41 -05:00
Hosts : convertExtraHosts ( service . ExtraHosts ) ,
2016-11-18 09:09:13 -05:00
Healthcheck : healthcheck ,
2016-10-28 17:30:20 -04:00
Env : convertEnvironment ( service . Environment ) ,
2016-11-03 19:50:03 -04:00
Labels : getStackLabels ( namespace . name , service . Labels ) ,
2016-10-28 17:30:20 -04:00
Dir : service . WorkingDir ,
User : service . User ,
Mounts : mounts ,
StopGracePeriod : service . StopGracePeriod ,
2016-11-11 05:27:21 -05:00
TTY : service . Tty ,
2016-11-11 09:15:10 -05:00
OpenStdin : service . StdinOpen ,
2016-11-02 14:57:40 -04:00
} ,
2016-12-05 09:18:36 -05:00
LogDriver : logDriver ,
2016-10-28 17:30:20 -04:00
Resources : resources ,
RestartPolicy : restartPolicy ,
2016-11-02 14:57:40 -04:00
Placement : & swarm . Placement {
Constraints : service . Deploy . Placement . Constraints ,
} ,
} ,
EndpointSpec : endpoint ,
Mode : mode ,
2016-11-28 11:38:41 -05:00
Networks : networks ,
2016-10-28 17:30:20 -04:00
UpdateConfig : convertUpdateConfig ( service . Deploy . UpdateConfig ) ,
2016-11-02 14:57:40 -04:00
}
2016-10-28 17:30:20 -04:00
return serviceSpec , nil
}
2016-11-11 08:19:41 -05:00
func convertExtraHosts ( extraHosts map [ string ] string ) [ ] string {
hosts := [ ] string { }
for host , ip := range extraHosts {
2016-11-18 16:04:27 -05:00
hosts = append ( hosts , fmt . Sprintf ( "%s %s" , ip , host ) )
2016-11-11 08:19:41 -05:00
}
return hosts
}
2016-11-18 09:09:13 -05:00
func convertHealthcheck ( healthcheck * composetypes . HealthCheckConfig ) ( * container . HealthConfig , error ) {
if healthcheck == nil {
return nil , nil
}
var (
err error
timeout , interval time . Duration
retries int
)
if healthcheck . Disable {
if len ( healthcheck . Test ) != 0 {
return nil , fmt . Errorf ( "command and disable key can't be set at the same time" )
}
return & container . HealthConfig {
Test : [ ] string { "NONE" } ,
} , nil
}
if healthcheck . Timeout != "" {
timeout , err = time . ParseDuration ( healthcheck . Timeout )
if err != nil {
return nil , err
}
}
if healthcheck . Interval != "" {
interval , err = time . ParseDuration ( healthcheck . Interval )
if err != nil {
return nil , err
}
}
if healthcheck . Retries != nil {
retries = int ( * healthcheck . Retries )
}
return & container . HealthConfig {
Test : healthcheck . Test ,
Timeout : timeout ,
Interval : interval ,
Retries : retries ,
} , nil
}
2016-10-28 17:30:20 -04:00
func convertRestartPolicy ( restart string , source * composetypes . RestartPolicy ) ( * swarm . RestartPolicy , error ) {
// TODO: log if restart is being ignored
if source == nil {
policy , err := runconfigopts . ParseRestartPolicy ( restart )
2016-11-02 14:57:40 -04:00
if err != nil {
2016-10-28 17:30:20 -04:00
return nil , err
}
// TODO: is this an accurate convertion?
switch {
2016-11-10 16:22:31 -05:00
case policy . IsNone ( ) :
2016-10-28 17:30:20 -04:00
return nil , nil
2016-11-10 16:22:31 -05:00
case policy . IsAlways ( ) , policy . IsUnlessStopped ( ) :
return & swarm . RestartPolicy {
Condition : swarm . RestartPolicyConditionAny ,
} , nil
2016-10-28 17:30:20 -04:00
case policy . IsOnFailure ( ) :
attempts := uint64 ( policy . MaximumRetryCount )
return & swarm . RestartPolicy {
Condition : swarm . RestartPolicyConditionOnFailure ,
MaxAttempts : & attempts ,
} , nil
2016-11-02 14:57:40 -04:00
}
}
2016-10-28 17:30:20 -04:00
return & swarm . RestartPolicy {
Condition : swarm . RestartPolicyCondition ( source . Condition ) ,
Delay : source . Delay ,
2016-11-02 12:19:37 -04:00
MaxAttempts : source . MaxAttempts ,
2016-10-28 17:30:20 -04:00
Window : source . Window ,
} , nil
}
2016-11-02 14:57:40 -04:00
2016-10-28 17:30:20 -04:00
func convertUpdateConfig ( source * composetypes . UpdateConfig ) * swarm . UpdateConfig {
if source == nil {
return nil
}
2016-11-18 10:15:29 -05:00
parallel := uint64 ( 1 )
if source . Parallelism != nil {
parallel = * source . Parallelism
}
2016-10-28 17:30:20 -04:00
return & swarm . UpdateConfig {
2016-11-18 10:15:29 -05:00
Parallelism : parallel ,
2016-10-28 17:30:20 -04:00
Delay : source . Delay ,
FailureAction : source . FailureAction ,
Monitor : source . Monitor ,
MaxFailureRatio : source . MaxFailureRatio ,
}
}
func convertResources ( source composetypes . Resources ) ( * swarm . ResourceRequirements , error ) {
resources := & swarm . ResourceRequirements { }
if source . Limits != nil {
cpus , err := opts . ParseCPUs ( source . Limits . NanoCPUs )
if err != nil {
return nil , err
}
resources . Limits = & swarm . Resources {
NanoCPUs : cpus ,
MemoryBytes : int64 ( source . Limits . MemoryBytes ) ,
}
}
if source . Reservations != nil {
cpus , err := opts . ParseCPUs ( source . Reservations . NanoCPUs )
if err != nil {
return nil , err
}
resources . Reservations = & swarm . Resources {
NanoCPUs : cpus ,
MemoryBytes : int64 ( source . Reservations . MemoryBytes ) ,
}
}
return resources , nil
2016-11-02 14:57:40 -04:00
}
func convertEndpointSpec ( source [ ] string ) ( * swarm . EndpointSpec , error ) {
portConfigs := [ ] swarm . PortConfig { }
ports , portBindings , err := nat . ParsePortSpecs ( source )
if err != nil {
return nil , err
}
for port := range ports {
portConfigs = append (
portConfigs ,
servicecmd . ConvertPortToPortConfig ( port , portBindings ) ... )
}
return & swarm . EndpointSpec { Ports : portConfigs } , nil
}
func convertEnvironment ( source map [ string ] string ) [ ] string {
var output [ ] string
for name , value := range source {
output = append ( output , fmt . Sprintf ( "%s=%s" , name , value ) )
}
return output
}
2016-10-28 17:30:20 -04:00
func convertDeployMode ( mode string , replicas * uint64 ) ( swarm . ServiceMode , error ) {
2016-11-02 14:57:40 -04:00
serviceMode := swarm . ServiceMode { }
switch mode {
case "global" :
2016-10-28 17:30:20 -04:00
if replicas != nil {
2016-11-02 14:57:40 -04:00
return serviceMode , fmt . Errorf ( "replicas can only be used with replicated mode" )
}
serviceMode . Global = & swarm . GlobalService { }
2016-11-02 09:10:34 -04:00
case "replicated" , "" :
2016-10-28 17:30:20 -04:00
serviceMode . Replicated = & swarm . ReplicatedService { Replicas : replicas }
2016-11-02 14:57:40 -04:00
default :
return serviceMode , fmt . Errorf ( "Unknown mode: %s" , mode )
}
return serviceMode , nil
}