2016-09-08 13:11:39 -04:00
package service
import (
"fmt"
"strconv"
"strings"
"time"
2016-10-13 14:28:32 -04:00
"github.com/docker/docker/api/types/container"
2016-09-08 13:11:39 -04:00
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)
type int64Value interface {
Value ( ) int64
}
type memBytes int64
func ( m * memBytes ) String ( ) string {
return units . BytesSize ( float64 ( m . Value ( ) ) )
}
func ( m * memBytes ) Set ( value string ) error {
val , err := units . RAMInBytes ( value )
* m = memBytes ( val )
return err
}
func ( m * memBytes ) Type ( ) string {
2016-11-08 10:06:07 -05:00
return "bytes"
2016-09-08 13:11:39 -04:00
}
func ( m * memBytes ) Value ( ) int64 {
return int64 ( * m )
}
2016-10-13 14:28:32 -04:00
// PositiveDurationOpt is an option type for time.Duration that uses a pointer.
// It bahave similarly to DurationOpt but only allows positive duration values.
type PositiveDurationOpt struct {
DurationOpt
}
// Set a new value on the option. Setting a negative duration value will cause
// an error to be returned.
func ( d * PositiveDurationOpt ) Set ( s string ) error {
err := d . DurationOpt . Set ( s )
if err != nil {
return err
}
if * d . DurationOpt . value < 0 {
return fmt . Errorf ( "duration cannot be negative" )
}
return nil
}
2016-09-08 13:11:39 -04:00
// DurationOpt is an option type for time.Duration that uses a pointer. This
// allows us to get nil values outside, instead of defaulting to 0
type DurationOpt struct {
value * time . Duration
}
// Set a new value on the option
func ( d * DurationOpt ) Set ( s string ) error {
v , err := time . ParseDuration ( s )
d . value = & v
return err
}
2016-11-08 10:06:07 -05:00
// Type returns the type of this option, which will be displayed in `--help` output
2016-09-08 13:11:39 -04:00
func ( d * DurationOpt ) Type ( ) string {
2016-11-08 10:06:07 -05:00
return "duration"
2016-09-08 13:11:39 -04:00
}
// String returns a string repr of this option
func ( d * DurationOpt ) String ( ) string {
if d . value != nil {
return d . value . String ( )
}
2016-12-15 09:12:33 -05:00
return ""
2016-09-08 13:11:39 -04:00
}
// Value returns the time.Duration
func ( d * DurationOpt ) Value ( ) * time . Duration {
return d . value
}
// Uint64Opt represents a uint64.
type Uint64Opt struct {
value * uint64
}
// Set a new value on the option
func ( i * Uint64Opt ) Set ( s string ) error {
v , err := strconv . ParseUint ( s , 0 , 64 )
i . value = & v
return err
}
2016-11-08 10:06:07 -05:00
// Type returns the type of this option, which will be displayed in `--help` output
2016-09-08 13:11:39 -04:00
func ( i * Uint64Opt ) Type ( ) string {
2016-11-08 10:06:07 -05:00
return "uint"
2016-09-08 13:11:39 -04:00
}
// String returns a string repr of this option
func ( i * Uint64Opt ) String ( ) string {
if i . value != nil {
return fmt . Sprintf ( "%v" , * i . value )
}
2016-12-15 09:12:33 -05:00
return ""
2016-09-08 13:11:39 -04:00
}
// Value returns the uint64
func ( i * Uint64Opt ) Value ( ) * uint64 {
return i . value
}
2016-11-08 10:06:07 -05:00
type floatValue float32
func ( f * floatValue ) Set ( s string ) error {
v , err := strconv . ParseFloat ( s , 32 )
* f = floatValue ( v )
return err
}
func ( f * floatValue ) Type ( ) string {
return "float"
}
func ( f * floatValue ) String ( ) string {
return strconv . FormatFloat ( float64 ( * f ) , 'g' , - 1 , 32 )
}
func ( f * floatValue ) Value ( ) float32 {
return float32 ( * f )
}
2016-09-08 13:11:39 -04:00
type updateOptions struct {
2016-09-02 17:12:05 -04:00
parallelism uint64
delay time . Duration
monitor time . Duration
onFailure string
2016-11-08 10:06:07 -05:00
maxFailureRatio floatValue
2016-09-08 13:11:39 -04:00
}
type resourceOptions struct {
2016-11-01 13:12:29 -04:00
limitCPU opts . NanoCPUs
2016-09-08 13:11:39 -04:00
limitMemBytes memBytes
2016-11-01 13:12:29 -04:00
resCPU opts . NanoCPUs
2016-09-08 13:11:39 -04:00
resMemBytes memBytes
}
func ( r * resourceOptions ) ToResourceRequirements ( ) * swarm . ResourceRequirements {
return & swarm . ResourceRequirements {
Limits : & swarm . Resources {
NanoCPUs : r . limitCPU . Value ( ) ,
MemoryBytes : r . limitMemBytes . Value ( ) ,
} ,
Reservations : & swarm . Resources {
NanoCPUs : r . resCPU . Value ( ) ,
MemoryBytes : r . resMemBytes . Value ( ) ,
} ,
}
}
type restartPolicyOptions struct {
condition string
delay DurationOpt
maxAttempts Uint64Opt
window DurationOpt
}
func ( r * restartPolicyOptions ) ToRestartPolicy ( ) * swarm . RestartPolicy {
return & swarm . RestartPolicy {
Condition : swarm . RestartPolicyCondition ( r . condition ) ,
Delay : r . delay . Value ( ) ,
MaxAttempts : r . maxAttempts . Value ( ) ,
Window : r . window . Value ( ) ,
}
}
func convertNetworks ( networks [ ] string ) [ ] swarm . NetworkAttachmentConfig {
nets := [ ] swarm . NetworkAttachmentConfig { }
for _ , network := range networks {
nets = append ( nets , swarm . NetworkAttachmentConfig { Target : network } )
}
return nets
}
type endpointOptions struct {
2016-12-08 16:32:10 -05:00
mode string
publishPorts opts . PortOpt
2016-09-08 13:11:39 -04:00
}
func ( e * endpointOptions ) ToEndpointSpec ( ) * swarm . EndpointSpec {
return & swarm . EndpointSpec {
Mode : swarm . ResolutionMode ( strings . ToLower ( e . mode ) ) ,
2016-12-08 16:32:10 -05:00
Ports : e . publishPorts . Value ( ) ,
2016-09-08 13:11:39 -04:00
}
}
type logDriverOptions struct {
name string
opts opts . ListOpts
}
func newLogDriverOptions ( ) logDriverOptions {
2016-12-23 14:09:12 -05:00
return logDriverOptions { opts : opts . NewListOpts ( opts . ValidateEnv ) }
2016-09-08 13:11:39 -04:00
}
func ( ldo * logDriverOptions ) toLogDriver ( ) * swarm . Driver {
if ldo . name == "" {
return nil
}
// set the log driver only if specified.
return & swarm . Driver {
Name : ldo . name ,
Options : runconfigopts . ConvertKVStringsToMap ( ldo . opts . GetAll ( ) ) ,
}
}
2016-10-13 14:28:32 -04:00
type healthCheckOptions struct {
cmd string
interval PositiveDurationOpt
timeout PositiveDurationOpt
retries int
noHealthcheck bool
}
func ( opts * healthCheckOptions ) toHealthConfig ( ) ( * container . HealthConfig , error ) {
var healthConfig * container . HealthConfig
haveHealthSettings := opts . cmd != "" ||
opts . interval . Value ( ) != nil ||
opts . timeout . Value ( ) != nil ||
opts . retries != 0
if opts . noHealthcheck {
if haveHealthSettings {
return nil , fmt . Errorf ( "--%s conflicts with --health-* options" , flagNoHealthcheck )
}
healthConfig = & container . HealthConfig { Test : [ ] string { "NONE" } }
} else if haveHealthSettings {
var test [ ] string
if opts . cmd != "" {
test = [ ] string { "CMD-SHELL" , opts . cmd }
}
var interval , timeout time . Duration
if ptr := opts . interval . Value ( ) ; ptr != nil {
interval = * ptr
}
if ptr := opts . timeout . Value ( ) ; ptr != nil {
timeout = * ptr
}
healthConfig = & container . HealthConfig {
Test : test ,
Interval : interval ,
Timeout : timeout ,
Retries : opts . retries ,
}
}
return healthConfig , nil
}
2016-09-08 13:11:39 -04:00
// ValidatePort validates a string is in the expected format for a port definition
func ValidatePort ( value string ) ( string , error ) {
portMappings , err := nat . ParsePortSpec ( value )
for _ , portMapping := range portMappings {
if portMapping . Binding . HostIP != "" {
return "" , fmt . Errorf ( "HostIP is not supported by a service." )
}
}
return value , err
}
2016-11-03 11:05:00 -04:00
// convertExtraHostsToSwarmHosts converts an array of extra hosts in cli
// <host>:<ip>
// into a swarmkit host format:
// IP_address canonical_hostname [aliases...]
// This assumes input value (<host>:<ip>) has already been validated
func convertExtraHostsToSwarmHosts ( extraHosts [ ] string ) [ ] string {
hosts := [ ] string { }
for _ , extraHost := range extraHosts {
parts := strings . SplitN ( extraHost , ":" , 2 )
hosts = append ( hosts , fmt . Sprintf ( "%s %s" , parts [ 1 ] , parts [ 0 ] ) )
}
return hosts
}
2016-09-08 13:11:39 -04:00
type serviceOptions struct {
name string
labels opts . ListOpts
containerLabels opts . ListOpts
image string
args [ ] string
2016-10-27 07:44:19 -04:00
hostname string
2016-09-08 13:11:39 -04:00
env opts . ListOpts
2016-07-20 02:58:32 -04:00
envFile opts . ListOpts
2016-09-08 13:11:39 -04:00
workdir string
user string
2016-11-08 10:06:07 -05:00
groups opts . ListOpts
2016-11-04 14:31:44 -04:00
tty bool
2016-10-24 23:26:54 -04:00
mounts opts . MountOpt
2016-10-19 20:07:44 -04:00
dns opts . ListOpts
dnsSearch opts . ListOpts
2016-11-08 21:29:10 -05:00
dnsOption opts . ListOpts
2016-11-03 11:05:00 -04:00
hosts opts . ListOpts
2016-09-08 13:11:39 -04:00
resources resourceOptions
stopGrace DurationOpt
replicas Uint64Opt
mode string
restartPolicy restartPolicyOptions
2016-11-08 10:06:07 -05:00
constraints opts . ListOpts
2016-09-08 13:11:39 -04:00
update updateOptions
2016-11-08 10:06:07 -05:00
networks opts . ListOpts
2016-09-08 13:11:39 -04:00
endpoint endpointOptions
registryAuth bool
logDriver logDriverOptions
2016-10-13 14:28:32 -04:00
healthcheck healthCheckOptions
2016-11-03 11:08:22 -04:00
secrets opts . SecretOpt
2016-09-08 13:11:39 -04:00
}
func newServiceOptions ( ) * serviceOptions {
return & serviceOptions {
2016-12-23 14:09:12 -05:00
labels : opts . NewListOpts ( opts . ValidateEnv ) ,
2016-11-08 10:06:07 -05:00
constraints : opts . NewListOpts ( nil ) ,
2016-12-23 14:09:12 -05:00
containerLabels : opts . NewListOpts ( opts . ValidateEnv ) ,
env : opts . NewListOpts ( opts . ValidateEnv ) ,
2016-07-20 02:58:32 -04:00
envFile : opts . NewListOpts ( nil ) ,
2016-12-08 16:32:10 -05:00
groups : opts . NewListOpts ( nil ) ,
logDriver : newLogDriverOptions ( ) ,
dns : opts . NewListOpts ( opts . ValidateIPAddress ) ,
dnsOption : opts . NewListOpts ( nil ) ,
dnsSearch : opts . NewListOpts ( opts . ValidateDNSSearch ) ,
2016-12-23 14:09:12 -05:00
hosts : opts . NewListOpts ( opts . ValidateExtraHost ) ,
2016-12-08 16:32:10 -05:00
networks : opts . NewListOpts ( nil ) ,
2016-09-08 13:11:39 -04:00
}
}
func ( opts * serviceOptions ) ToService ( ) ( swarm . ServiceSpec , error ) {
var service swarm . ServiceSpec
2016-07-20 02:58:32 -04:00
envVariables , err := runconfigopts . ReadKVStrings ( opts . envFile . GetAll ( ) , opts . env . GetAll ( ) )
if err != nil {
return service , err
}
currentEnv := make ( [ ] string , 0 , len ( envVariables ) )
for _ , env := range envVariables { // need to process each var, in order
k := strings . SplitN ( env , "=" , 2 ) [ 0 ]
for i , current := range currentEnv { // remove duplicates
if current == env {
continue // no update required, may hide this behind flag to preserve order of envVariables
}
if strings . HasPrefix ( current , k + "=" ) {
currentEnv = append ( currentEnv [ : i ] , currentEnv [ i + 1 : ] ... )
}
}
currentEnv = append ( currentEnv , env )
}
2016-09-08 13:11:39 -04:00
service = swarm . ServiceSpec {
Annotations : swarm . Annotations {
Name : opts . name ,
Labels : runconfigopts . ConvertKVStringsToMap ( opts . labels . GetAll ( ) ) ,
} ,
TaskTemplate : swarm . TaskSpec {
ContainerSpec : swarm . ContainerSpec {
2016-10-19 20:07:44 -04:00
Image : opts . image ,
Args : opts . args ,
Env : currentEnv ,
Hostname : opts . hostname ,
Labels : runconfigopts . ConvertKVStringsToMap ( opts . containerLabels . GetAll ( ) ) ,
Dir : opts . workdir ,
User : opts . user ,
2016-11-08 10:06:07 -05:00
Groups : opts . groups . GetAll ( ) ,
2016-10-19 20:07:44 -04:00
TTY : opts . tty ,
Mounts : opts . mounts . Value ( ) ,
DNSConfig : & swarm . DNSConfig {
Nameservers : opts . dns . GetAll ( ) ,
Search : opts . dnsSearch . GetAll ( ) ,
2016-11-08 21:29:10 -05:00
Options : opts . dnsOption . GetAll ( ) ,
2016-10-19 20:07:44 -04:00
} ,
2016-11-03 11:05:00 -04:00
Hosts : convertExtraHostsToSwarmHosts ( opts . hosts . GetAll ( ) ) ,
2016-09-08 13:11:39 -04:00
StopGracePeriod : opts . stopGrace . Value ( ) ,
2016-10-27 03:41:32 -04:00
Secrets : nil ,
2016-09-08 13:11:39 -04:00
} ,
2016-11-08 10:06:07 -05:00
Networks : convertNetworks ( opts . networks . GetAll ( ) ) ,
2016-09-08 13:11:39 -04:00
Resources : opts . resources . ToResourceRequirements ( ) ,
RestartPolicy : opts . restartPolicy . ToRestartPolicy ( ) ,
Placement : & swarm . Placement {
2016-11-08 10:06:07 -05:00
Constraints : opts . constraints . GetAll ( ) ,
2016-09-08 13:11:39 -04:00
} ,
LogDriver : opts . logDriver . toLogDriver ( ) ,
} ,
2016-11-08 10:06:07 -05:00
Networks : convertNetworks ( opts . networks . GetAll ( ) ) ,
2016-09-08 13:11:39 -04:00
Mode : swarm . ServiceMode { } ,
UpdateConfig : & swarm . UpdateConfig {
2016-09-02 17:12:05 -04:00
Parallelism : opts . update . parallelism ,
Delay : opts . update . delay ,
Monitor : opts . update . monitor ,
FailureAction : opts . update . onFailure ,
2016-11-08 10:06:07 -05:00
MaxFailureRatio : opts . update . maxFailureRatio . Value ( ) ,
2016-09-08 13:11:39 -04:00
} ,
EndpointSpec : opts . endpoint . ToEndpointSpec ( ) ,
}
2016-10-13 14:28:32 -04:00
healthConfig , err := opts . healthcheck . toHealthConfig ( )
if err != nil {
return service , err
}
service . TaskTemplate . ContainerSpec . Healthcheck = healthConfig
2016-09-08 13:11:39 -04:00
switch opts . mode {
case "global" :
if opts . replicas . Value ( ) != nil {
return service , fmt . Errorf ( "replicas can only be used with replicated mode" )
}
service . Mode . Global = & swarm . GlobalService { }
case "replicated" :
service . Mode . Replicated = & swarm . ReplicatedService {
Replicas : opts . replicas . Value ( ) ,
}
default :
return service , fmt . Errorf ( "Unknown mode: %s" , opts . mode )
}
return service , nil
}
// addServiceFlags adds all flags that are common to both `create` and `update`.
// Any flags that are not common are added separately in the individual command
func addServiceFlags ( cmd * cobra . Command , opts * serviceOptions ) {
flags := cmd . Flags ( )
flags . StringVarP ( & opts . workdir , flagWorkdir , "w" , "" , "Working directory inside the container" )
flags . StringVarP ( & opts . user , flagUser , "u" , "" , "Username or UID (format: <name|uid>[:<group|gid>])" )
2016-11-23 14:42:56 -05:00
flags . StringVar ( & opts . hostname , flagHostname , "" , "Container hostname" )
2016-09-08 13:11:39 -04:00
flags . Var ( & opts . resources . limitCPU , flagLimitCPU , "Limit CPUs" )
flags . Var ( & opts . resources . limitMemBytes , flagLimitMemory , "Limit Memory" )
flags . Var ( & opts . resources . resCPU , flagReserveCPU , "Reserve CPUs" )
flags . Var ( & opts . resources . resMemBytes , flagReserveMemory , "Reserve Memory" )
2016-11-11 20:44:42 -05:00
flags . Var ( & opts . stopGrace , flagStopGracePeriod , "Time to wait before force killing a container (ns|us|ms|s|m|h)" )
2016-09-08 13:11:39 -04:00
flags . Var ( & opts . replicas , flagReplicas , "Number of tasks" )
flags . StringVar ( & opts . restartPolicy . condition , flagRestartCondition , "" , "Restart when condition is met (none, on-failure, or any)" )
2016-11-11 20:44:42 -05:00
flags . Var ( & opts . restartPolicy . delay , flagRestartDelay , "Delay between restart attempts (ns|us|ms|s|m|h)" )
2016-09-08 13:11:39 -04:00
flags . Var ( & opts . restartPolicy . maxAttempts , flagRestartMaxAttempts , "Maximum number of restarts before giving up" )
2016-11-11 20:44:42 -05:00
flags . Var ( & opts . restartPolicy . window , flagRestartWindow , "Window used to evaluate the restart policy (ns|us|ms|s|m|h)" )
2016-09-08 13:11:39 -04:00
flags . Uint64Var ( & opts . update . parallelism , flagUpdateParallelism , 1 , "Maximum number of tasks updated simultaneously (0 to update all at once)" )
2016-10-25 08:22:07 -04:00
flags . DurationVar ( & opts . update . delay , flagUpdateDelay , time . Duration ( 0 ) , "Delay between updates (ns|us|ms|s|m|h) (default 0s)" )
flags . DurationVar ( & opts . update . monitor , flagUpdateMonitor , time . Duration ( 0 ) , "Duration after each task update to monitor for failure (ns|us|ms|s|m|h) (default 0s)" )
2016-09-08 13:11:39 -04:00
flags . StringVar ( & opts . update . onFailure , flagUpdateFailureAction , "pause" , "Action on update failure (pause|continue)" )
2016-11-08 10:06:07 -05:00
flags . Var ( & opts . update . maxFailureRatio , flagUpdateMaxFailureRatio , "Failure rate to tolerate during an update" )
2016-09-08 13:11:39 -04:00
flags . StringVar ( & opts . endpoint . mode , flagEndpointMode , "" , "Endpoint mode (vip or dnsrr)" )
flags . BoolVar ( & opts . registryAuth , flagRegistryAuth , false , "Send registry authentication details to swarm agents" )
flags . StringVar ( & opts . logDriver . name , flagLogDriver , "" , "Logging driver for service" )
flags . Var ( & opts . logDriver . opts , flagLogOpt , "Logging driver options" )
2016-10-13 14:28:32 -04:00
flags . StringVar ( & opts . healthcheck . cmd , flagHealthCmd , "" , "Command to run to check health" )
2016-11-11 20:44:42 -05:00
flags . Var ( & opts . healthcheck . interval , flagHealthInterval , "Time between running the check (ns|us|ms|s|m|h)" )
flags . Var ( & opts . healthcheck . timeout , flagHealthTimeout , "Maximum time to allow one check to run (ns|us|ms|s|m|h)" )
2016-10-13 14:28:32 -04:00
flags . IntVar ( & opts . healthcheck . retries , flagHealthRetries , 0 , "Consecutive failures needed to report unhealthy" )
flags . BoolVar ( & opts . healthcheck . noHealthcheck , flagNoHealthcheck , false , "Disable any container-specified HEALTHCHECK" )
2016-11-04 14:31:44 -04:00
flags . BoolVarP ( & opts . tty , flagTTY , "t" , false , "Allocate a pseudo-TTY" )
2016-09-08 13:11:39 -04:00
}
const (
2016-09-02 17:12:05 -04:00
flagConstraint = "constraint"
flagConstraintRemove = "constraint-rm"
flagConstraintAdd = "constraint-add"
flagContainerLabel = "container-label"
flagContainerLabelRemove = "container-label-rm"
flagContainerLabelAdd = "container-label-add"
2016-10-19 20:07:44 -04:00
flagDNS = "dns"
2016-10-26 23:05:39 -04:00
flagDNSRemove = "dns-rm"
flagDNSAdd = "dns-add"
2016-11-08 21:29:10 -05:00
flagDNSOption = "dns-option"
flagDNSOptionRemove = "dns-option-rm"
flagDNSOptionAdd = "dns-option-add"
2016-10-19 20:07:44 -04:00
flagDNSSearch = "dns-search"
2016-10-26 23:05:39 -04:00
flagDNSSearchRemove = "dns-search-rm"
flagDNSSearchAdd = "dns-search-add"
2016-09-02 17:12:05 -04:00
flagEndpointMode = "endpoint-mode"
2016-11-03 11:05:00 -04:00
flagHost = "host"
flagHostAdd = "host-add"
flagHostRemove = "host-rm"
2016-10-27 07:44:19 -04:00
flagHostname = "hostname"
2016-09-02 17:12:05 -04:00
flagEnv = "env"
2016-07-20 02:58:32 -04:00
flagEnvFile = "env-file"
2016-09-02 17:12:05 -04:00
flagEnvRemove = "env-rm"
flagEnvAdd = "env-add"
2016-10-26 15:46:40 -04:00
flagGroup = "group"
2016-09-02 17:12:05 -04:00
flagGroupAdd = "group-add"
flagGroupRemove = "group-rm"
flagLabel = "label"
flagLabelRemove = "label-rm"
flagLabelAdd = "label-add"
flagLimitCPU = "limit-cpu"
flagLimitMemory = "limit-memory"
flagMode = "mode"
flagMount = "mount"
flagMountRemove = "mount-rm"
flagMountAdd = "mount-add"
flagName = "name"
flagNetwork = "network"
flagPublish = "publish"
flagPublishRemove = "publish-rm"
flagPublishAdd = "publish-add"
flagReplicas = "replicas"
flagReserveCPU = "reserve-cpu"
flagReserveMemory = "reserve-memory"
flagRestartCondition = "restart-condition"
flagRestartDelay = "restart-delay"
flagRestartMaxAttempts = "restart-max-attempts"
flagRestartWindow = "restart-window"
flagStopGracePeriod = "stop-grace-period"
2016-11-04 14:31:44 -04:00
flagTTY = "tty"
2016-09-02 17:12:05 -04:00
flagUpdateDelay = "update-delay"
flagUpdateFailureAction = "update-failure-action"
flagUpdateMaxFailureRatio = "update-max-failure-ratio"
flagUpdateMonitor = "update-monitor"
flagUpdateParallelism = "update-parallelism"
flagUser = "user"
flagWorkdir = "workdir"
flagRegistryAuth = "with-registry-auth"
flagLogDriver = "log-driver"
flagLogOpt = "log-opt"
2016-10-13 14:28:32 -04:00
flagHealthCmd = "health-cmd"
flagHealthInterval = "health-interval"
flagHealthRetries = "health-retries"
flagHealthTimeout = "health-timeout"
flagNoHealthcheck = "no-healthcheck"
2016-10-19 12:22:02 -04:00
flagSecret = "secret"
2016-10-27 18:51:02 -04:00
flagSecretAdd = "secret-add"
flagSecretRemove = "secret-rm"
2016-09-08 13:11:39 -04:00
)