2016-09-08 13:11:39 -04:00
package service
import (
2016-11-01 22:28:32 -04:00
"encoding/csv"
2016-09-08 13:11:39 -04:00
"fmt"
2016-11-01 22:28:32 -04:00
"os"
"path/filepath"
2016-09-08 13:11:39 -04:00
"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 ( )
}
return "none"
}
// 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 )
}
return "none"
}
// 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-11-01 22:28:32 -04:00
// SecretRequestSpec is a type for requesting secrets
type SecretRequestSpec struct {
source string
target string
uid string
gid string
mode os . FileMode
}
// SecretOpt is a Value type for parsing secrets
type SecretOpt struct {
values [ ] * SecretRequestSpec
}
// Set a new secret value
func ( o * SecretOpt ) Set ( value string ) error {
csvReader := csv . NewReader ( strings . NewReader ( value ) )
fields , err := csvReader . Read ( )
if err != nil {
return err
}
spec := & SecretRequestSpec {
source : "" ,
target : "" ,
uid : "0" ,
gid : "0" ,
mode : 0444 ,
}
for _ , field := range fields {
parts := strings . SplitN ( field , "=" , 2 )
key := strings . ToLower ( parts [ 0 ] )
if len ( parts ) != 2 {
return fmt . Errorf ( "invalid field '%s' must be a key=value pair" , field )
}
value := parts [ 1 ]
switch key {
case "source" :
spec . source = value
case "target" :
tDir , _ := filepath . Split ( value )
if tDir != "" {
return fmt . Errorf ( "target must not have a path" )
}
spec . target = value
case "uid" :
spec . uid = value
case "gid" :
spec . gid = value
case "mode" :
m , err := strconv . ParseUint ( value , 0 , 32 )
if err != nil {
return fmt . Errorf ( "invalid mode specified: %v" , err )
}
spec . mode = os . FileMode ( m )
default :
return fmt . Errorf ( "invalid field in secret request: %s" , key )
}
}
if spec . source == "" {
return fmt . Errorf ( "source is required" )
}
o . values = append ( o . values , spec )
return nil
}
// Type returns the type of this option
func ( o * SecretOpt ) Type ( ) string {
return "secret"
}
// String returns a string repr of this option
func ( o * SecretOpt ) String ( ) string {
secrets := [ ] string { }
for _ , secret := range o . values {
repr := fmt . Sprintf ( "%s -> %s" , secret . source , secret . target )
secrets = append ( secrets , repr )
}
return strings . Join ( secrets , ", " )
}
// Value returns the secret requests
func ( o * SecretOpt ) Value ( ) [ ] * SecretRequestSpec {
return o . values
}
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 {
mode string
ports opts . ListOpts
}
func ( e * endpointOptions ) ToEndpointSpec ( ) * swarm . EndpointSpec {
portConfigs := [ ] swarm . PortConfig { }
// We can ignore errors because the format was already validated by ValidatePort
ports , portBindings , _ := nat . ParsePortSpecs ( e . ports . GetAll ( ) )
for port := range ports {
portConfigs = append ( portConfigs , convertPortToPortConfig ( port , portBindings ) ... )
}
return & swarm . EndpointSpec {
Mode : swarm . ResolutionMode ( strings . ToLower ( e . mode ) ) ,
Ports : portConfigs ,
}
}
func convertPortToPortConfig (
port nat . Port ,
portBindings map [ nat . Port ] [ ] nat . PortBinding ,
) [ ] swarm . PortConfig {
ports := [ ] swarm . PortConfig { }
for _ , binding := range portBindings [ port ] {
hostPort , _ := strconv . ParseUint ( binding . HostPort , 10 , 16 )
ports = append ( ports , swarm . PortConfig {
//TODO Name: ?
Protocol : swarm . PortConfigProtocol ( strings . ToLower ( port . Proto ( ) ) ) ,
TargetPort : uint32 ( port . Int ( ) ) ,
PublishedPort : uint32 ( hostPort ) ,
} )
}
return ports
}
type logDriverOptions struct {
name string
opts opts . ListOpts
}
func newLogDriverOptions ( ) logDriverOptions {
return logDriverOptions { opts : opts . NewListOpts ( runconfigopts . ValidateEnv ) }
}
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
}
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
dnsOptions 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 {
labels : opts . NewListOpts ( runconfigopts . ValidateEnv ) ,
2016-11-08 10:06:07 -05:00
constraints : opts . NewListOpts ( nil ) ,
2016-09-08 13:11:39 -04:00
containerLabels : opts . NewListOpts ( runconfigopts . ValidateEnv ) ,
env : opts . NewListOpts ( runconfigopts . ValidateEnv ) ,
2016-07-20 02:58:32 -04:00
envFile : opts . NewListOpts ( nil ) ,
2016-09-08 13:11:39 -04:00
endpoint : endpointOptions {
ports : opts . NewListOpts ( ValidatePort ) ,
} ,
2016-11-08 10:06:07 -05:00
groups : opts . NewListOpts ( nil ) ,
2016-10-19 20:07:44 -04:00
logDriver : newLogDriverOptions ( ) ,
dns : opts . NewListOpts ( opts . ValidateIPAddress ) ,
dnsOptions : opts . NewListOpts ( nil ) ,
dnsSearch : opts . NewListOpts ( opts . ValidateDNSSearch ) ,
2016-11-08 10:06:07 -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 ( ) ,
Options : opts . dnsOptions . 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>])" )
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" )
flags . Var ( & opts . stopGrace , flagStopGracePeriod , "Time to wait before force killing a container" )
flags . Var ( & opts . replicas , flagReplicas , "Number of tasks" )
flags . StringVar ( & opts . restartPolicy . condition , flagRestartCondition , "" , "Restart when condition is met (none, on-failure, or any)" )
flags . Var ( & opts . restartPolicy . delay , flagRestartDelay , "Delay between restart attempts" )
flags . Var ( & opts . restartPolicy . maxAttempts , flagRestartMaxAttempts , "Maximum number of restarts before giving up" )
flags . Var ( & opts . restartPolicy . window , flagRestartWindow , "Window used to evaluate the restart policy" )
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" )
flags . Var ( & opts . healthcheck . interval , flagHealthInterval , "Time between running the check" )
flags . Var ( & opts . healthcheck . timeout , flagHealthTimeout , "Maximum time to allow one check to run" )
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"
flagDNSOptions = "dns-options"
flagDNSOptionsRemove = "dns-options-rm"
flagDNSOptionsAdd = "dns-options-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-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
)