2016-09-08 13:11:39 -04:00
package service
import (
"fmt"
2017-03-23 20:51:57 -04:00
"sort"
2016-09-08 13:11:39 -04:00
"strconv"
"strings"
"time"
2017-05-15 08:45:19 -04:00
"github.com/docker/cli/opts"
2017-06-12 22:53:53 -04:00
"github.com/docker/docker/api/types"
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"
2017-05-08 13:51:30 -04:00
"github.com/docker/docker/client"
2017-03-30 21:35:04 -04:00
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/api/defaults"
2016-12-07 14:37:55 -05:00
shlex "github.com/flynn-archive/go-shlex"
2017-03-30 21:35:04 -04:00
gogotypes "github.com/gogo/protobuf/types"
2017-03-09 13:23:45 -05:00
"github.com/pkg/errors"
2017-03-15 16:49:52 -04:00
"github.com/spf13/pflag"
2017-03-23 20:51:57 -04:00
"golang.org/x/net/context"
2016-09-08 13:11:39 -04:00
)
type int64Value interface {
Value ( ) int64
}
// 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 )
}
2017-01-19 18:27:37 -05:00
// placementPrefOpts holds a list of placement preferences.
type placementPrefOpts struct {
prefs [ ] swarm . PlacementPreference
strings [ ] string
}
func ( opts * placementPrefOpts ) String ( ) string {
if len ( opts . strings ) == 0 {
return ""
}
return fmt . Sprintf ( "%v" , opts . strings )
}
// Set validates the input value and adds it to the internal slices.
// Note: in the future strategies other than "spread", may be supported,
// as well as additional comma-separated options.
func ( opts * placementPrefOpts ) Set ( value string ) error {
fields := strings . Split ( value , "=" )
if len ( fields ) != 2 {
return errors . New ( ` placement preference must be of the format "<strategy>=<arg>" ` )
}
if fields [ 0 ] != "spread" {
2017-03-09 13:23:45 -05:00
return errors . Errorf ( "unsupported placement preference %s (only spread is supported)" , fields [ 0 ] )
2017-01-19 18:27:37 -05:00
}
opts . prefs = append ( opts . prefs , swarm . PlacementPreference {
Spread : & swarm . SpreadOver {
SpreadDescriptor : fields [ 1 ] ,
} ,
} )
opts . strings = append ( opts . strings , value )
return nil
}
// Type returns a string name for this Option type
func ( opts * placementPrefOpts ) Type ( ) string {
return "pref"
}
2016-12-07 14:37:55 -05:00
// ShlexOpt is a flag Value which parses a string as a list of shell words
type ShlexOpt [ ] string
// Set the value
func ( s * ShlexOpt ) Set ( value string ) error {
valueSlice , err := shlex . Split ( value )
* s = ShlexOpt ( valueSlice )
return err
}
// Type returns the tyep of the value
func ( s * ShlexOpt ) Type ( ) string {
return "command"
}
func ( s * ShlexOpt ) String ( ) string {
2017-03-30 21:35:04 -04:00
if len ( * s ) == 0 {
return ""
}
2016-12-07 14:37:55 -05:00
return fmt . Sprint ( * s )
}
// Value returns the value as a string slice
func ( s * ShlexOpt ) Value ( ) [ ] string {
return [ ] string ( * s )
}
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
2017-01-18 14:38:19 -05:00
order string
2016-09-08 13:11:39 -04:00
}
2017-03-30 21:35:04 -04:00
func updateConfigFromDefaults ( defaultUpdateConfig * api . UpdateConfig ) * swarm . UpdateConfig {
defaultFailureAction := strings . ToLower ( api . UpdateConfig_FailureAction_name [ int32 ( defaultUpdateConfig . FailureAction ) ] )
defaultMonitor , _ := gogotypes . DurationFromProto ( defaultUpdateConfig . Monitor )
2017-02-15 19:04:30 -05:00
return & swarm . UpdateConfig {
2017-03-30 21:35:04 -04:00
Parallelism : defaultUpdateConfig . Parallelism ,
Delay : defaultUpdateConfig . Delay ,
Monitor : defaultMonitor ,
FailureAction : defaultFailureAction ,
MaxFailureRatio : defaultUpdateConfig . MaxFailureRatio ,
Order : defaultOrder ( defaultUpdateConfig . Order ) ,
}
}
func ( opts updateOptions ) updateConfig ( flags * pflag . FlagSet ) * swarm . UpdateConfig {
if ! anyChanged ( flags , flagUpdateParallelism , flagUpdateDelay , flagUpdateMonitor , flagUpdateFailureAction , flagUpdateMaxFailureRatio ) {
return nil
}
updateConfig := updateConfigFromDefaults ( defaults . Service . Update )
if flags . Changed ( flagUpdateParallelism ) {
updateConfig . Parallelism = opts . parallelism
}
if flags . Changed ( flagUpdateDelay ) {
updateConfig . Delay = opts . delay
}
if flags . Changed ( flagUpdateMonitor ) {
updateConfig . Monitor = opts . monitor
}
if flags . Changed ( flagUpdateFailureAction ) {
updateConfig . FailureAction = opts . onFailure
}
if flags . Changed ( flagUpdateMaxFailureRatio ) {
updateConfig . MaxFailureRatio = opts . maxFailureRatio . Value ( )
}
if flags . Changed ( flagUpdateOrder ) {
updateConfig . Order = opts . order
}
return updateConfig
}
func ( opts updateOptions ) rollbackConfig ( flags * pflag . FlagSet ) * swarm . UpdateConfig {
if ! anyChanged ( flags , flagRollbackParallelism , flagRollbackDelay , flagRollbackMonitor , flagRollbackFailureAction , flagRollbackMaxFailureRatio ) {
return nil
}
updateConfig := updateConfigFromDefaults ( defaults . Service . Rollback )
if flags . Changed ( flagRollbackParallelism ) {
updateConfig . Parallelism = opts . parallelism
}
if flags . Changed ( flagRollbackDelay ) {
updateConfig . Delay = opts . delay
}
if flags . Changed ( flagRollbackMonitor ) {
updateConfig . Monitor = opts . monitor
}
if flags . Changed ( flagRollbackFailureAction ) {
updateConfig . FailureAction = opts . onFailure
2017-02-15 19:04:30 -05:00
}
2017-03-30 21:35:04 -04:00
if flags . Changed ( flagRollbackMaxFailureRatio ) {
updateConfig . MaxFailureRatio = opts . maxFailureRatio . Value ( )
}
if flags . Changed ( flagRollbackOrder ) {
updateConfig . Order = opts . order
}
return updateConfig
2017-02-15 19:04:30 -05:00
}
2016-09-08 13:11:39 -04:00
type resourceOptions struct {
2017-11-08 11:33:36 -05:00
limitCPU opts . NanoCPUs
limitMemBytes opts . MemBytes
resCPU opts . NanoCPUs
resMemBytes opts . MemBytes
resGenericResources [ ] string
2016-09-08 13:11:39 -04:00
}
2017-11-08 11:33:36 -05:00
func ( r * resourceOptions ) ToResourceRequirements ( ) ( * swarm . ResourceRequirements , error ) {
generic , err := ParseGenericResources ( r . resGenericResources )
if err != nil {
return nil , err
}
2016-09-08 13:11:39 -04:00
return & swarm . ResourceRequirements {
Limits : & swarm . Resources {
NanoCPUs : r . limitCPU . Value ( ) ,
MemoryBytes : r . limitMemBytes . Value ( ) ,
} ,
Reservations : & swarm . Resources {
2017-11-08 11:33:36 -05:00
NanoCPUs : r . resCPU . Value ( ) ,
MemoryBytes : r . resMemBytes . Value ( ) ,
GenericResources : generic ,
2016-09-08 13:11:39 -04:00
} ,
2017-11-08 11:33:36 -05:00
} , nil
2016-09-08 13:11:39 -04:00
}
type restartPolicyOptions struct {
condition string
2017-05-16 11:49:40 -04:00
delay opts . DurationOpt
2016-09-08 13:11:39 -04:00
maxAttempts Uint64Opt
2017-05-16 11:49:40 -04:00
window opts . DurationOpt
2016-09-08 13:11:39 -04:00
}
2017-03-30 21:35:04 -04:00
func defaultRestartPolicy ( ) * swarm . RestartPolicy {
defaultMaxAttempts := defaults . Service . Task . Restart . MaxAttempts
rp := & swarm . RestartPolicy {
MaxAttempts : & defaultMaxAttempts ,
}
if defaults . Service . Task . Restart . Delay != nil {
defaultRestartDelay , _ := gogotypes . DurationFromProto ( defaults . Service . Task . Restart . Delay )
rp . Delay = & defaultRestartDelay
}
if defaults . Service . Task . Restart . Window != nil {
defaultRestartWindow , _ := gogotypes . DurationFromProto ( defaults . Service . Task . Restart . Window )
rp . Window = & defaultRestartWindow
}
rp . Condition = defaultRestartCondition ( )
return rp
}
func defaultRestartCondition ( ) swarm . RestartPolicyCondition {
switch defaults . Service . Task . Restart . Condition {
case api . RestartOnNone :
return "none"
case api . RestartOnFailure :
return "on-failure"
case api . RestartOnAny :
return "any"
default :
return ""
}
}
func defaultOrder ( order api . UpdateConfig_UpdateOrder ) string {
switch order {
case api . UpdateConfig_STOP_FIRST :
return "stop-first"
case api . UpdateConfig_START_FIRST :
return "start-first"
default :
return ""
}
}
func ( r * restartPolicyOptions ) ToRestartPolicy ( flags * pflag . FlagSet ) * swarm . RestartPolicy {
if ! anyChanged ( flags , flagRestartDelay , flagRestartMaxAttempts , flagRestartWindow , flagRestartCondition ) {
return nil
}
restartPolicy := defaultRestartPolicy ( )
if flags . Changed ( flagRestartDelay ) {
restartPolicy . Delay = r . delay . Value ( )
}
if flags . Changed ( flagRestartCondition ) {
restartPolicy . Condition = swarm . RestartPolicyCondition ( r . condition )
2016-09-08 13:11:39 -04:00
}
2017-03-30 21:35:04 -04:00
if flags . Changed ( flagRestartMaxAttempts ) {
restartPolicy . MaxAttempts = r . maxAttempts . Value ( )
}
if flags . Changed ( flagRestartWindow ) {
restartPolicy . Window = r . window . Value ( )
}
return restartPolicy
2016-09-08 13:11:39 -04:00
}
2017-03-29 18:55:21 -04:00
type credentialSpecOpt struct {
value * swarm . CredentialSpec
source string
}
func ( c * credentialSpecOpt ) Set ( value string ) error {
c . source = value
c . value = & swarm . CredentialSpec { }
switch {
case strings . HasPrefix ( value , "file://" ) :
c . value . File = strings . TrimPrefix ( value , "file://" )
case strings . HasPrefix ( value , "registry://" ) :
c . value . Registry = strings . TrimPrefix ( value , "registry://" )
default :
return errors . New ( "Invalid credential spec - value must be prefixed file:// or registry:// followed by a value" )
}
return nil
}
func ( c * credentialSpecOpt ) Type ( ) string {
return "credential-spec"
}
func ( c * credentialSpecOpt ) String ( ) string {
return c . source
}
func ( c * credentialSpecOpt ) Value ( ) * swarm . CredentialSpec {
return c . value
}
2017-05-09 19:29:04 -04:00
func convertNetworks ( ctx context . Context , apiClient client . NetworkAPIClient , networks opts . NetworkOpt ) ( [ ] swarm . NetworkAttachmentConfig , error ) {
var netAttach [ ] swarm . NetworkAttachmentConfig
for _ , net := range networks . Value ( ) {
networkIDOrName := net . Target
2017-06-12 22:53:53 -04:00
_ , err := apiClient . NetworkInspect ( ctx , networkIDOrName , types . NetworkInspectOptions { Scope : "swarm" } )
2017-03-23 20:51:57 -04:00
if err != nil {
return nil , err
}
2017-10-11 12:18:27 -04:00
netAttach = append ( netAttach , swarm . NetworkAttachmentConfig {
2017-06-21 00:11:59 -04:00
Target : net . Target ,
Aliases : net . Aliases ,
DriverOpts : net . DriverOpts ,
} )
2016-09-08 13:11:39 -04:00
}
2017-05-09 19:29:04 -04:00
sort . Sort ( byNetworkTarget ( netAttach ) )
return netAttach , nil
2016-09-08 13:11:39 -04:00
}
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 ,
2017-06-05 18:23:21 -04:00
Options : opts . ConvertKVStringsToMap ( ldo . opts . GetAll ( ) ) ,
2016-09-08 13:11:39 -04:00
}
}
2016-10-13 14:28:32 -04:00
type healthCheckOptions struct {
cmd string
2017-05-16 11:49:40 -04:00
interval opts . PositiveDurationOpt
timeout opts . PositiveDurationOpt
2016-10-13 14:28:32 -04:00
retries int
2017-05-16 11:49:40 -04:00
startPeriod opts . PositiveDurationOpt
2016-10-13 14:28:32 -04:00
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 {
2017-03-09 13:23:45 -05:00
return nil , errors . Errorf ( "--%s conflicts with --health-* options" , flagNoHealthcheck )
2016-10-13 14:28:32 -04:00
}
healthConfig = & container . HealthConfig { Test : [ ] string { "NONE" } }
} else if haveHealthSettings {
var test [ ] string
if opts . cmd != "" {
test = [ ] string { "CMD-SHELL" , opts . cmd }
}
2016-11-29 04:58:47 -05:00
var interval , timeout , startPeriod time . Duration
2016-10-13 14:28:32 -04:00
if ptr := opts . interval . Value ( ) ; ptr != nil {
interval = * ptr
}
if ptr := opts . timeout . Value ( ) ; ptr != nil {
timeout = * ptr
}
2016-11-29 04:58:47 -05:00
if ptr := opts . startPeriod . Value ( ) ; ptr != nil {
startPeriod = * ptr
}
2016-10-13 14:28:32 -04:00
healthConfig = & container . HealthConfig {
2016-11-29 04:58:47 -05:00
Test : test ,
Interval : interval ,
Timeout : timeout ,
Retries : opts . retries ,
StartPeriod : startPeriod ,
2016-10-13 14:28:32 -04:00
}
}
return healthConfig , nil
}
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 {
2017-02-16 20:05:36 -05:00
detach bool
quiet bool
2016-09-08 13:11:39 -04:00
name string
labels opts . ListOpts
containerLabels opts . ListOpts
image string
2016-12-07 14:37:55 -05:00
entrypoint ShlexOpt
2016-09-08 13:11:39 -04:00
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
2017-03-29 18:55:21 -04:00
credentialSpec credentialSpecOpt
2017-02-06 00:22:57 -05:00
stopSignal string
2016-11-04 14:31:44 -04:00
tty bool
2017-01-14 03:12:19 -05:00
readOnly 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
2017-05-16 11:49:40 -04:00
stopGrace opts . DurationOpt
2016-09-08 13:11:39 -04:00
replicas Uint64Opt
mode string
2017-01-19 18:27:37 -05:00
restartPolicy restartPolicyOptions
constraints opts . ListOpts
placementPrefs placementPrefOpts
update updateOptions
2017-02-15 19:04:30 -05:00
rollback updateOptions
2017-05-09 19:29:04 -04:00
networks opts . NetworkOpt
2017-01-19 18:27:37 -05:00
endpoint endpointOptions
2016-09-08 13:11:39 -04:00
2017-05-15 19:01:48 -04:00
registryAuth bool
noResolveImage bool
2016-09-08 13:11:39 -04:00
logDriver logDriverOptions
2016-10-13 14:28:32 -04:00
healthcheck healthCheckOptions
2016-11-03 11:08:22 -04:00
secrets opts . SecretOpt
2017-05-08 13:36:04 -04:00
configs opts . ConfigOpt
2017-11-17 09:31:13 -05:00
isolation string
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-09-08 13:11:39 -04:00
}
}
2017-06-05 18:23:21 -04:00
func ( options * serviceOptions ) ToServiceMode ( ) ( swarm . ServiceMode , error ) {
2017-01-16 03:58:23 -05:00
serviceMode := swarm . ServiceMode { }
2017-06-05 18:23:21 -04:00
switch options . mode {
2017-01-16 03:58:23 -05:00
case "global" :
2017-06-05 18:23:21 -04:00
if options . replicas . Value ( ) != nil {
2017-03-09 13:23:45 -05:00
return serviceMode , errors . Errorf ( "replicas can only be used with replicated mode" )
2017-01-16 03:58:23 -05:00
}
serviceMode . Global = & swarm . GlobalService { }
case "replicated" :
serviceMode . Replicated = & swarm . ReplicatedService {
2017-06-05 18:23:21 -04:00
Replicas : options . replicas . Value ( ) ,
2017-01-16 03:58:23 -05:00
}
default :
2017-06-05 18:23:21 -04:00
return serviceMode , errors . Errorf ( "Unknown mode: %s, only replicated and global supported" , options . mode )
2017-01-16 03:58:23 -05:00
}
return serviceMode , nil
}
2017-06-05 18:23:21 -04:00
func ( options * serviceOptions ) ToStopGracePeriod ( flags * pflag . FlagSet ) * time . Duration {
2017-03-30 21:35:04 -04:00
if flags . Changed ( flagStopGracePeriod ) {
2017-06-05 18:23:21 -04:00
return options . stopGrace . Value ( )
2017-03-30 21:35:04 -04:00
}
return nil
}
2017-06-05 18:23:21 -04:00
func ( options * serviceOptions ) ToService ( ctx context . Context , apiClient client . NetworkAPIClient , flags * pflag . FlagSet ) ( swarm . ServiceSpec , error ) {
2016-09-08 13:11:39 -04:00
var service swarm . ServiceSpec
2017-06-05 18:23:21 -04:00
envVariables , err := opts . ReadKVStrings ( options . envFile . GetAll ( ) , options . env . GetAll ( ) )
2016-07-20 02:58:32 -04:00
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 )
}
2017-06-05 18:23:21 -04:00
healthConfig , err := options . healthcheck . toHealthConfig ( )
2017-01-16 03:58:23 -05:00
if err != nil {
return service , err
}
2017-06-05 18:23:21 -04:00
serviceMode , err := options . ToServiceMode ( )
2017-01-16 03:58:23 -05:00
if err != nil {
return service , err
}
2017-06-05 18:23:21 -04:00
networks , err := convertNetworks ( ctx , apiClient , options . networks )
2017-03-23 20:51:57 -04:00
if err != nil {
return service , err
}
2017-11-08 11:33:36 -05:00
resources , err := options . resources . ToResourceRequirements ( )
if err != nil {
return service , err
}
2016-09-08 13:11:39 -04:00
service = swarm . ServiceSpec {
Annotations : swarm . Annotations {
2017-06-05 18:23:21 -04:00
Name : options . name ,
Labels : opts . ConvertKVStringsToMap ( options . labels . GetAll ( ) ) ,
2016-09-08 13:11:39 -04:00
} ,
TaskTemplate : swarm . TaskSpec {
2017-08-07 05:52:40 -04:00
ContainerSpec : & swarm . ContainerSpec {
2017-06-05 18:23:21 -04:00
Image : options . image ,
Args : options . args ,
Command : options . entrypoint . Value ( ) ,
2017-02-06 00:22:57 -05:00
Env : currentEnv ,
2017-06-05 18:23:21 -04:00
Hostname : options . hostname ,
Labels : opts . ConvertKVStringsToMap ( options . containerLabels . GetAll ( ) ) ,
Dir : options . workdir ,
User : options . user ,
Groups : options . groups . GetAll ( ) ,
StopSignal : options . stopSignal ,
TTY : options . tty ,
ReadOnly : options . readOnly ,
Mounts : options . mounts . Value ( ) ,
2016-10-19 20:07:44 -04:00
DNSConfig : & swarm . DNSConfig {
2017-06-05 18:23:21 -04:00
Nameservers : options . dns . GetAll ( ) ,
Search : options . dnsSearch . GetAll ( ) ,
Options : options . dnsOption . GetAll ( ) ,
2016-10-19 20:07:44 -04:00
} ,
2017-06-05 18:23:21 -04:00
Hosts : convertExtraHostsToSwarmHosts ( options . hosts . GetAll ( ) ) ,
StopGracePeriod : options . ToStopGracePeriod ( flags ) ,
2017-01-16 03:58:23 -05:00
Healthcheck : healthConfig ,
2017-11-17 09:31:13 -05:00
Isolation : container . Isolation ( options . isolation ) ,
2016-09-08 13:11:39 -04:00
} ,
2017-03-23 20:51:57 -04:00
Networks : networks ,
2017-11-08 11:33:36 -05:00
Resources : resources ,
2017-06-05 18:23:21 -04:00
RestartPolicy : options . restartPolicy . ToRestartPolicy ( flags ) ,
2016-09-08 13:11:39 -04:00
Placement : & swarm . Placement {
2017-06-05 18:23:21 -04:00
Constraints : options . constraints . GetAll ( ) ,
Preferences : options . placementPrefs . prefs ,
2016-09-08 13:11:39 -04:00
} ,
2017-06-05 18:23:21 -04:00
LogDriver : options . logDriver . toLogDriver ( ) ,
2016-09-08 13:11:39 -04:00
} ,
2017-02-15 19:04:30 -05:00
Mode : serviceMode ,
2017-06-05 18:23:21 -04:00
UpdateConfig : options . update . updateConfig ( flags ) ,
RollbackConfig : options . update . rollbackConfig ( flags ) ,
EndpointSpec : options . endpoint . ToEndpointSpec ( ) ,
2016-09-08 13:11:39 -04:00
}
2017-06-05 18:23:21 -04:00
if options . credentialSpec . Value ( ) != nil {
2017-03-29 18:55:21 -04:00
service . TaskTemplate . ContainerSpec . Privileges = & swarm . Privileges {
2017-06-05 18:23:21 -04:00
CredentialSpec : options . credentialSpec . Value ( ) ,
2017-03-29 18:55:21 -04:00
}
}
2016-09-08 13:11:39 -04:00
return service , nil
}
2017-03-30 21:35:04 -04:00
type flagDefaults map [ string ] interface { }
func ( fd flagDefaults ) getUint64 ( flagName string ) uint64 {
if val , ok := fd [ flagName ] . ( uint64 ) ; ok {
return val
}
return 0
}
func ( fd flagDefaults ) getString ( flagName string ) string {
if val , ok := fd [ flagName ] . ( string ) ; ok {
return val
}
return ""
}
func buildServiceDefaultFlagMapping ( ) flagDefaults {
defaultFlagValues := make ( map [ string ] interface { } )
defaultFlagValues [ flagStopGracePeriod ] , _ = gogotypes . DurationFromProto ( defaults . Service . Task . GetContainer ( ) . StopGracePeriod )
defaultFlagValues [ flagRestartCondition ] = ` " ` + defaultRestartCondition ( ) + ` " `
defaultFlagValues [ flagRestartDelay ] , _ = gogotypes . DurationFromProto ( defaults . Service . Task . Restart . Delay )
if defaults . Service . Task . Restart . MaxAttempts != 0 {
defaultFlagValues [ flagRestartMaxAttempts ] = defaults . Service . Task . Restart . MaxAttempts
}
defaultRestartWindow , _ := gogotypes . DurationFromProto ( defaults . Service . Task . Restart . Window )
if defaultRestartWindow != 0 {
defaultFlagValues [ flagRestartWindow ] = defaultRestartWindow
}
defaultFlagValues [ flagUpdateParallelism ] = defaults . Service . Update . Parallelism
defaultFlagValues [ flagUpdateDelay ] = defaults . Service . Update . Delay
defaultFlagValues [ flagUpdateMonitor ] , _ = gogotypes . DurationFromProto ( defaults . Service . Update . Monitor )
defaultFlagValues [ flagUpdateFailureAction ] = ` " ` + strings . ToLower ( api . UpdateConfig_FailureAction_name [ int32 ( defaults . Service . Update . FailureAction ) ] ) + ` " `
defaultFlagValues [ flagUpdateMaxFailureRatio ] = defaults . Service . Update . MaxFailureRatio
defaultFlagValues [ flagUpdateOrder ] = ` " ` + defaultOrder ( defaults . Service . Update . Order ) + ` " `
defaultFlagValues [ flagRollbackParallelism ] = defaults . Service . Rollback . Parallelism
defaultFlagValues [ flagRollbackDelay ] = defaults . Service . Rollback . Delay
defaultFlagValues [ flagRollbackMonitor ] , _ = gogotypes . DurationFromProto ( defaults . Service . Rollback . Monitor )
defaultFlagValues [ flagRollbackFailureAction ] = ` " ` + strings . ToLower ( api . UpdateConfig_FailureAction_name [ int32 ( defaults . Service . Rollback . FailureAction ) ] ) + ` " `
defaultFlagValues [ flagRollbackMaxFailureRatio ] = defaults . Service . Rollback . MaxFailureRatio
defaultFlagValues [ flagRollbackOrder ] = ` " ` + defaultOrder ( defaults . Service . Rollback . Order ) + ` " `
defaultFlagValues [ flagEndpointMode ] = "vip"
return defaultFlagValues
}
2017-06-26 21:19:36 -04:00
func addDetachFlag ( flags * pflag . FlagSet , detach * bool ) {
Use non-detached mode as default for service commands
Commit 330a0035334871d92207b583c1c36d52a244753f added a `--detach=false` option
to various service-related commands, with the intent to make this the default in
a future version (17.09).
This patch changes the default to use "interactive" (non-detached), allowing
users to override this by setting the `--detach` option.
To prevent problems when connecting to older daemon versions (17.05 and below,
see commit db60f255617fd90cb093813dcdfe7eec840eeff8), the detach option is
ignored for those versions, and detach is always true.
Before this change, a warning was printed to announce the upcoming default:
$ docker service create nginx:alpine
saxiyn3pe559d753730zr0xer
Since --detach=false was not specified, tasks will be created in the background.
In a future release, --detach=false will become the default.
After this change, no warning is printed, but `--detach` is disabled;
$ docker service create nginx:alpine
y9jujwzozi0hwgj5yaadzliq6
overall progress: 1 out of 1 tasks
1/1: running [==================================================>]
verify: Service converged
Setting the `--detach` flag makes the cli use the pre-17.06 behavior:
$ docker service create --detach nginx:alpine
280hjnzy0wzje5o56gr22a46n
Running against a 17.03 daemon, without specifying the `--detach` flag;
$ docker service create nginx:alpine
kqheg7ogj0kszoa34g4p73i8q
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-09-12 08:35:19 -04:00
flags . BoolVarP ( detach , flagDetach , "d" , false , "Exit immediately instead of waiting for the service to converge" )
2017-06-26 21:19:36 -04:00
flags . SetAnnotation ( flagDetach , "version" , [ ] string { "1.29" } )
}
2016-09-08 13:11:39 -04:00
// 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
2017-03-30 21:35:04 -04:00
func addServiceFlags ( flags * pflag . FlagSet , opts * serviceOptions , defaultFlagValues flagDefaults ) {
flagDesc := func ( flagName string , desc string ) string {
if defaultValue , ok := defaultFlagValues [ flagName ] ; ok {
return fmt . Sprintf ( "%s (default %v)" , desc , defaultValue )
}
return desc
}
2017-06-26 21:19:36 -04:00
addDetachFlag ( flags , & opts . detach )
flags . BoolVarP ( & opts . quiet , flagQuiet , "q" , false , "Suppress progress output" )
2017-02-16 20:05:36 -05:00
2016-09-08 13:11:39 -04:00
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>])" )
2017-03-29 18:55:21 -04:00
flags . Var ( & opts . credentialSpec , flagCredentialSpec , "Credential spec for managed service account (Windows only)" )
flags . SetAnnotation ( flagCredentialSpec , "version" , [ ] string { "1.29" } )
2016-11-23 14:42:56 -05:00
flags . StringVar ( & opts . hostname , flagHostname , "" , "Container hostname" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHostname , "version" , [ ] string { "1.25" } )
2016-12-07 14:37:55 -05:00
flags . Var ( & opts . entrypoint , flagEntrypoint , "Overwrite the default ENTRYPOINT of the image" )
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" )
2017-03-30 21:35:04 -04:00
flags . Var ( & opts . stopGrace , flagStopGracePeriod , flagDesc ( 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" )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . restartPolicy . condition , flagRestartCondition , "" , flagDesc ( flagRestartCondition , ` Restart when condition is met ("none"|"on-failure"|"any") ` ) )
flags . Var ( & opts . restartPolicy . delay , flagRestartDelay , flagDesc ( flagRestartDelay , "Delay between restart attempts (ns|us|ms|s|m|h)" ) )
flags . Var ( & opts . restartPolicy . maxAttempts , flagRestartMaxAttempts , flagDesc ( flagRestartMaxAttempts , "Maximum number of restarts before giving up" ) )
flags . Var ( & opts . restartPolicy . window , flagRestartWindow , flagDesc ( flagRestartWindow , "Window used to evaluate the restart policy (ns|us|ms|s|m|h)" ) )
2016-09-08 13:11:39 -04:00
2017-03-30 21:35:04 -04:00
flags . Uint64Var ( & opts . update . parallelism , flagUpdateParallelism , defaultFlagValues . getUint64 ( flagUpdateParallelism ) , "Maximum number of tasks updated simultaneously (0 to update all at once)" )
flags . DurationVar ( & opts . update . delay , flagUpdateDelay , 0 , flagDesc ( flagUpdateDelay , "Delay between updates (ns|us|ms|s|m|h)" ) )
flags . DurationVar ( & opts . update . monitor , flagUpdateMonitor , 0 , flagDesc ( flagUpdateMonitor , "Duration after each task update to monitor for failure (ns|us|ms|s|m|h)" ) )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagUpdateMonitor , "version" , [ ] string { "1.25" } )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . update . onFailure , flagUpdateFailureAction , "" , flagDesc ( flagUpdateFailureAction , ` Action on update failure ("pause"|"continue"|"rollback") ` ) )
flags . Var ( & opts . update . maxFailureRatio , flagUpdateMaxFailureRatio , flagDesc ( flagUpdateMaxFailureRatio , "Failure rate to tolerate during an update" ) )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagUpdateMaxFailureRatio , "version" , [ ] string { "1.25" } )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . update . order , flagUpdateOrder , "" , flagDesc ( flagUpdateOrder , ` Update order ("start-first"|"stop-first") ` ) )
2017-01-18 14:38:19 -05:00
flags . SetAnnotation ( flagUpdateOrder , "version" , [ ] string { "1.29" } )
2016-09-08 13:11:39 -04:00
2017-05-03 18:14:30 -04:00
flags . Uint64Var ( & opts . rollback . parallelism , flagRollbackParallelism , defaultFlagValues . getUint64 ( flagRollbackParallelism ) ,
"Maximum number of tasks rolled back simultaneously (0 to roll back all at once)" )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackParallelism , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . DurationVar ( & opts . rollback . delay , flagRollbackDelay , 0 , flagDesc ( flagRollbackDelay , "Delay between task rollbacks (ns|us|ms|s|m|h)" ) )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackDelay , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . DurationVar ( & opts . rollback . monitor , flagRollbackMonitor , 0 , flagDesc ( flagRollbackMonitor , "Duration after each task rollback to monitor for failure (ns|us|ms|s|m|h)" ) )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackMonitor , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . rollback . onFailure , flagRollbackFailureAction , "" , flagDesc ( flagRollbackFailureAction , ` Action on rollback failure ("pause"|"continue") ` ) )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackFailureAction , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . Var ( & opts . rollback . maxFailureRatio , flagRollbackMaxFailureRatio , flagDesc ( flagRollbackMaxFailureRatio , "Failure rate to tolerate during a rollback" ) )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagRollbackMaxFailureRatio , "version" , [ ] string { "1.28" } )
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . rollback . order , flagRollbackOrder , "" , flagDesc ( flagRollbackOrder , ` Rollback order ("start-first"|"stop-first") ` ) )
2017-01-18 14:38:19 -05:00
flags . SetAnnotation ( flagRollbackOrder , "version" , [ ] string { "1.29" } )
2017-02-15 19:04:30 -05:00
2017-03-30 21:35:04 -04:00
flags . StringVar ( & opts . endpoint . mode , flagEndpointMode , defaultFlagValues . getString ( flagEndpointMode ) , "Endpoint mode (vip or dnsrr)" )
2016-09-08 13:11:39 -04:00
flags . BoolVar ( & opts . registryAuth , flagRegistryAuth , false , "Send registry authentication details to swarm agents" )
2017-05-15 19:01:48 -04:00
flags . BoolVar ( & opts . noResolveImage , flagNoResolveImage , false , "Do not query the registry to resolve image digest and supported platforms" )
flags . SetAnnotation ( flagNoResolveImage , "version" , [ ] string { "1.30" } )
2016-09-08 13:11:39 -04:00
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" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHealthCmd , "version" , [ ] string { "1.25" } )
2017-04-10 17:12:44 -04:00
flags . Var ( & opts . healthcheck . interval , flagHealthInterval , "Time between running the check (ms|s|m|h)" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHealthInterval , "version" , [ ] string { "1.25" } )
2017-04-10 17:12:44 -04:00
flags . Var ( & opts . healthcheck . timeout , flagHealthTimeout , "Maximum time to allow one check to run (ms|s|m|h)" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHealthTimeout , "version" , [ ] string { "1.25" } )
2016-10-13 14:28:32 -04:00
flags . IntVar ( & opts . healthcheck . retries , flagHealthRetries , 0 , "Consecutive failures needed to report unhealthy" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagHealthRetries , "version" , [ ] string { "1.25" } )
2017-04-10 17:12:44 -04:00
flags . Var ( & opts . healthcheck . startPeriod , flagHealthStartPeriod , "Start period for the container to initialize before counting retries towards unstable (ms|s|m|h)" )
2016-11-29 04:58:47 -05:00
flags . SetAnnotation ( flagHealthStartPeriod , "version" , [ ] string { "1.29" } )
2016-10-13 14:28:32 -04:00
flags . BoolVar ( & opts . healthcheck . noHealthcheck , flagNoHealthcheck , false , "Disable any container-specified HEALTHCHECK" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagNoHealthcheck , "version" , [ ] string { "1.25" } )
2016-11-04 14:31:44 -04:00
flags . BoolVarP ( & opts . tty , flagTTY , "t" , false , "Allocate a pseudo-TTY" )
2017-01-16 11:57:26 -05:00
flags . SetAnnotation ( flagTTY , "version" , [ ] string { "1.25" } )
2017-01-14 03:12:19 -05:00
flags . BoolVar ( & opts . readOnly , flagReadOnly , false , "Mount the container's root filesystem as read only" )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagReadOnly , "version" , [ ] string { "1.28" } )
2017-02-06 00:22:57 -05:00
flags . StringVar ( & opts . stopSignal , flagStopSignal , "" , "Signal to stop the container" )
2017-03-13 21:31:48 -04:00
flags . SetAnnotation ( flagStopSignal , "version" , [ ] string { "1.28" } )
2017-11-17 09:31:13 -05:00
flags . StringVar ( & opts . isolation , flagIsolation , "" , "Service container isolation mode" )
flags . SetAnnotation ( flagIsolation , "version" , [ ] string { "1.35" } )
2016-09-08 13:11:39 -04:00
}
const (
2017-03-29 18:55:21 -04:00
flagCredentialSpec = "credential-spec"
2017-02-15 19:04:30 -05:00
flagPlacementPref = "placement-pref"
flagPlacementPrefAdd = "placement-pref-add"
flagPlacementPrefRemove = "placement-pref-rm"
flagConstraint = "constraint"
flagConstraintRemove = "constraint-rm"
flagConstraintAdd = "constraint-add"
flagContainerLabel = "container-label"
flagContainerLabelRemove = "container-label-rm"
flagContainerLabelAdd = "container-label-add"
2017-06-26 21:19:36 -04:00
flagDetach = "detach"
2017-02-15 19:04:30 -05:00
flagDNS = "dns"
flagDNSRemove = "dns-rm"
flagDNSAdd = "dns-add"
flagDNSOption = "dns-option"
flagDNSOptionRemove = "dns-option-rm"
flagDNSOptionAdd = "dns-option-add"
flagDNSSearch = "dns-search"
flagDNSSearchRemove = "dns-search-rm"
flagDNSSearchAdd = "dns-search-add"
flagEndpointMode = "endpoint-mode"
2016-12-07 14:37:55 -05:00
flagEntrypoint = "entrypoint"
2017-02-15 19:04:30 -05:00
flagEnv = "env"
flagEnvFile = "env-file"
flagEnvRemove = "env-rm"
flagEnvAdd = "env-add"
2017-11-17 17:05:44 -05:00
flagGenericResourcesRemove = "generic-resource-rm"
flagGenericResourcesAdd = "generic-resource-add"
2017-02-15 19:04:30 -05:00
flagGroup = "group"
flagGroupAdd = "group-add"
flagGroupRemove = "group-rm"
2017-06-26 21:19:36 -04:00
flagHost = "host"
flagHostAdd = "host-add"
flagHostRemove = "host-rm"
flagHostname = "hostname"
2017-02-15 19:04:30 -05:00
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"
2017-03-23 20:51:57 -04:00
flagNetworkAdd = "network-add"
flagNetworkRemove = "network-rm"
2017-02-15 19:04:30 -05:00
flagPublish = "publish"
flagPublishRemove = "publish-rm"
flagPublishAdd = "publish-add"
2017-06-26 21:19:36 -04:00
flagQuiet = "quiet"
2017-02-15 19:04:30 -05:00
flagReadOnly = "read-only"
flagReplicas = "replicas"
flagReserveCPU = "reserve-cpu"
flagReserveMemory = "reserve-memory"
flagRestartCondition = "restart-condition"
flagRestartDelay = "restart-delay"
flagRestartMaxAttempts = "restart-max-attempts"
flagRestartWindow = "restart-window"
2017-06-26 21:19:36 -04:00
flagRollback = "rollback"
2017-02-15 19:04:30 -05:00
flagRollbackDelay = "rollback-delay"
flagRollbackFailureAction = "rollback-failure-action"
flagRollbackMaxFailureRatio = "rollback-max-failure-ratio"
flagRollbackMonitor = "rollback-monitor"
2017-01-18 14:38:19 -05:00
flagRollbackOrder = "rollback-order"
2017-02-15 19:04:30 -05:00
flagRollbackParallelism = "rollback-parallelism"
flagStopGracePeriod = "stop-grace-period"
flagStopSignal = "stop-signal"
flagTTY = "tty"
flagUpdateDelay = "update-delay"
flagUpdateFailureAction = "update-failure-action"
flagUpdateMaxFailureRatio = "update-max-failure-ratio"
flagUpdateMonitor = "update-monitor"
2017-01-18 14:38:19 -05:00
flagUpdateOrder = "update-order"
2017-02-15 19:04:30 -05:00
flagUpdateParallelism = "update-parallelism"
flagUser = "user"
flagWorkdir = "workdir"
flagRegistryAuth = "with-registry-auth"
2017-05-15 19:01:48 -04:00
flagNoResolveImage = "no-resolve-image"
2017-02-15 19:04:30 -05:00
flagLogDriver = "log-driver"
flagLogOpt = "log-opt"
flagHealthCmd = "health-cmd"
flagHealthInterval = "health-interval"
flagHealthRetries = "health-retries"
flagHealthTimeout = "health-timeout"
2016-11-29 04:58:47 -05:00
flagHealthStartPeriod = "health-start-period"
2017-02-15 19:04:30 -05:00
flagNoHealthcheck = "no-healthcheck"
flagSecret = "secret"
flagSecretAdd = "secret-add"
flagSecretRemove = "secret-rm"
2017-05-08 13:36:04 -04:00
flagConfig = "config"
flagConfigAdd = "config-add"
flagConfigRemove = "config-rm"
2017-11-17 09:31:13 -05:00
flagIsolation = "isolation"
2016-09-08 13:11:39 -04:00
)