github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/cli/command/service/opts.go (about)

     1  package service
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/docker/docker/api/types/container"
    10  	"github.com/docker/docker/api/types/swarm"
    11  	"github.com/docker/docker/opts"
    12  	runconfigopts "github.com/docker/docker/runconfig/opts"
    13  	"github.com/spf13/cobra"
    14  )
    15  
    16  type int64Value interface {
    17  	Value() int64
    18  }
    19  
    20  // PositiveDurationOpt is an option type for time.Duration that uses a pointer.
    21  // It bahave similarly to DurationOpt but only allows positive duration values.
    22  type PositiveDurationOpt struct {
    23  	DurationOpt
    24  }
    25  
    26  // Set a new value on the option. Setting a negative duration value will cause
    27  // an error to be returned.
    28  func (d *PositiveDurationOpt) Set(s string) error {
    29  	err := d.DurationOpt.Set(s)
    30  	if err != nil {
    31  		return err
    32  	}
    33  	if *d.DurationOpt.value < 0 {
    34  		return fmt.Errorf("duration cannot be negative")
    35  	}
    36  	return nil
    37  }
    38  
    39  // DurationOpt is an option type for time.Duration that uses a pointer. This
    40  // allows us to get nil values outside, instead of defaulting to 0
    41  type DurationOpt struct {
    42  	value *time.Duration
    43  }
    44  
    45  // Set a new value on the option
    46  func (d *DurationOpt) Set(s string) error {
    47  	v, err := time.ParseDuration(s)
    48  	d.value = &v
    49  	return err
    50  }
    51  
    52  // Type returns the type of this option, which will be displayed in `--help` output
    53  func (d *DurationOpt) Type() string {
    54  	return "duration"
    55  }
    56  
    57  // String returns a string repr of this option
    58  func (d *DurationOpt) String() string {
    59  	if d.value != nil {
    60  		return d.value.String()
    61  	}
    62  	return ""
    63  }
    64  
    65  // Value returns the time.Duration
    66  func (d *DurationOpt) Value() *time.Duration {
    67  	return d.value
    68  }
    69  
    70  // Uint64Opt represents a uint64.
    71  type Uint64Opt struct {
    72  	value *uint64
    73  }
    74  
    75  // Set a new value on the option
    76  func (i *Uint64Opt) Set(s string) error {
    77  	v, err := strconv.ParseUint(s, 0, 64)
    78  	i.value = &v
    79  	return err
    80  }
    81  
    82  // Type returns the type of this option, which will be displayed in `--help` output
    83  func (i *Uint64Opt) Type() string {
    84  	return "uint"
    85  }
    86  
    87  // String returns a string repr of this option
    88  func (i *Uint64Opt) String() string {
    89  	if i.value != nil {
    90  		return fmt.Sprintf("%v", *i.value)
    91  	}
    92  	return ""
    93  }
    94  
    95  // Value returns the uint64
    96  func (i *Uint64Opt) Value() *uint64 {
    97  	return i.value
    98  }
    99  
   100  type floatValue float32
   101  
   102  func (f *floatValue) Set(s string) error {
   103  	v, err := strconv.ParseFloat(s, 32)
   104  	*f = floatValue(v)
   105  	return err
   106  }
   107  
   108  func (f *floatValue) Type() string {
   109  	return "float"
   110  }
   111  
   112  func (f *floatValue) String() string {
   113  	return strconv.FormatFloat(float64(*f), 'g', -1, 32)
   114  }
   115  
   116  func (f *floatValue) Value() float32 {
   117  	return float32(*f)
   118  }
   119  
   120  type updateOptions struct {
   121  	parallelism     uint64
   122  	delay           time.Duration
   123  	monitor         time.Duration
   124  	onFailure       string
   125  	maxFailureRatio floatValue
   126  }
   127  
   128  type resourceOptions struct {
   129  	limitCPU      opts.NanoCPUs
   130  	limitMemBytes opts.MemBytes
   131  	resCPU        opts.NanoCPUs
   132  	resMemBytes   opts.MemBytes
   133  }
   134  
   135  func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
   136  	return &swarm.ResourceRequirements{
   137  		Limits: &swarm.Resources{
   138  			NanoCPUs:    r.limitCPU.Value(),
   139  			MemoryBytes: r.limitMemBytes.Value(),
   140  		},
   141  		Reservations: &swarm.Resources{
   142  			NanoCPUs:    r.resCPU.Value(),
   143  			MemoryBytes: r.resMemBytes.Value(),
   144  		},
   145  	}
   146  }
   147  
   148  type restartPolicyOptions struct {
   149  	condition   string
   150  	delay       DurationOpt
   151  	maxAttempts Uint64Opt
   152  	window      DurationOpt
   153  }
   154  
   155  func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
   156  	return &swarm.RestartPolicy{
   157  		Condition:   swarm.RestartPolicyCondition(r.condition),
   158  		Delay:       r.delay.Value(),
   159  		MaxAttempts: r.maxAttempts.Value(),
   160  		Window:      r.window.Value(),
   161  	}
   162  }
   163  
   164  func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
   165  	nets := []swarm.NetworkAttachmentConfig{}
   166  	for _, network := range networks {
   167  		nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
   168  	}
   169  	return nets
   170  }
   171  
   172  type endpointOptions struct {
   173  	mode         string
   174  	publishPorts opts.PortOpt
   175  }
   176  
   177  func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
   178  	return &swarm.EndpointSpec{
   179  		Mode:  swarm.ResolutionMode(strings.ToLower(e.mode)),
   180  		Ports: e.publishPorts.Value(),
   181  	}
   182  }
   183  
   184  type logDriverOptions struct {
   185  	name string
   186  	opts opts.ListOpts
   187  }
   188  
   189  func newLogDriverOptions() logDriverOptions {
   190  	return logDriverOptions{opts: opts.NewListOpts(opts.ValidateEnv)}
   191  }
   192  
   193  func (ldo *logDriverOptions) toLogDriver() *swarm.Driver {
   194  	if ldo.name == "" {
   195  		return nil
   196  	}
   197  
   198  	// set the log driver only if specified.
   199  	return &swarm.Driver{
   200  		Name:    ldo.name,
   201  		Options: runconfigopts.ConvertKVStringsToMap(ldo.opts.GetAll()),
   202  	}
   203  }
   204  
   205  type healthCheckOptions struct {
   206  	cmd           string
   207  	interval      PositiveDurationOpt
   208  	timeout       PositiveDurationOpt
   209  	retries       int
   210  	noHealthcheck bool
   211  }
   212  
   213  func (opts *healthCheckOptions) toHealthConfig() (*container.HealthConfig, error) {
   214  	var healthConfig *container.HealthConfig
   215  	haveHealthSettings := opts.cmd != "" ||
   216  		opts.interval.Value() != nil ||
   217  		opts.timeout.Value() != nil ||
   218  		opts.retries != 0
   219  	if opts.noHealthcheck {
   220  		if haveHealthSettings {
   221  			return nil, fmt.Errorf("--%s conflicts with --health-* options", flagNoHealthcheck)
   222  		}
   223  		healthConfig = &container.HealthConfig{Test: []string{"NONE"}}
   224  	} else if haveHealthSettings {
   225  		var test []string
   226  		if opts.cmd != "" {
   227  			test = []string{"CMD-SHELL", opts.cmd}
   228  		}
   229  		var interval, timeout time.Duration
   230  		if ptr := opts.interval.Value(); ptr != nil {
   231  			interval = *ptr
   232  		}
   233  		if ptr := opts.timeout.Value(); ptr != nil {
   234  			timeout = *ptr
   235  		}
   236  		healthConfig = &container.HealthConfig{
   237  			Test:     test,
   238  			Interval: interval,
   239  			Timeout:  timeout,
   240  			Retries:  opts.retries,
   241  		}
   242  	}
   243  	return healthConfig, nil
   244  }
   245  
   246  // convertExtraHostsToSwarmHosts converts an array of extra hosts in cli
   247  //     <host>:<ip>
   248  // into a swarmkit host format:
   249  //     IP_address canonical_hostname [aliases...]
   250  // This assumes input value (<host>:<ip>) has already been validated
   251  func convertExtraHostsToSwarmHosts(extraHosts []string) []string {
   252  	hosts := []string{}
   253  	for _, extraHost := range extraHosts {
   254  		parts := strings.SplitN(extraHost, ":", 2)
   255  		hosts = append(hosts, fmt.Sprintf("%s %s", parts[1], parts[0]))
   256  	}
   257  	return hosts
   258  }
   259  
   260  type serviceOptions struct {
   261  	name            string
   262  	labels          opts.ListOpts
   263  	containerLabels opts.ListOpts
   264  	image           string
   265  	args            []string
   266  	hostname        string
   267  	env             opts.ListOpts
   268  	envFile         opts.ListOpts
   269  	workdir         string
   270  	user            string
   271  	groups          opts.ListOpts
   272  	tty             bool
   273  	readOnly        bool
   274  	mounts          opts.MountOpt
   275  	dns             opts.ListOpts
   276  	dnsSearch       opts.ListOpts
   277  	dnsOption       opts.ListOpts
   278  	hosts           opts.ListOpts
   279  
   280  	resources resourceOptions
   281  	stopGrace DurationOpt
   282  
   283  	replicas Uint64Opt
   284  	mode     string
   285  
   286  	restartPolicy restartPolicyOptions
   287  	constraints   opts.ListOpts
   288  	update        updateOptions
   289  	networks      opts.ListOpts
   290  	endpoint      endpointOptions
   291  
   292  	registryAuth bool
   293  
   294  	logDriver logDriverOptions
   295  
   296  	healthcheck healthCheckOptions
   297  	secrets     opts.SecretOpt
   298  }
   299  
   300  func newServiceOptions() *serviceOptions {
   301  	return &serviceOptions{
   302  		labels:          opts.NewListOpts(opts.ValidateEnv),
   303  		constraints:     opts.NewListOpts(nil),
   304  		containerLabels: opts.NewListOpts(opts.ValidateEnv),
   305  		env:             opts.NewListOpts(opts.ValidateEnv),
   306  		envFile:         opts.NewListOpts(nil),
   307  		groups:          opts.NewListOpts(nil),
   308  		logDriver:       newLogDriverOptions(),
   309  		dns:             opts.NewListOpts(opts.ValidateIPAddress),
   310  		dnsOption:       opts.NewListOpts(nil),
   311  		dnsSearch:       opts.NewListOpts(opts.ValidateDNSSearch),
   312  		hosts:           opts.NewListOpts(opts.ValidateExtraHost),
   313  		networks:        opts.NewListOpts(nil),
   314  	}
   315  }
   316  
   317  func (opts *serviceOptions) ToServiceMode() (swarm.ServiceMode, error) {
   318  	serviceMode := swarm.ServiceMode{}
   319  	switch opts.mode {
   320  	case "global":
   321  		if opts.replicas.Value() != nil {
   322  			return serviceMode, fmt.Errorf("replicas can only be used with replicated mode")
   323  		}
   324  
   325  		serviceMode.Global = &swarm.GlobalService{}
   326  	case "replicated":
   327  		serviceMode.Replicated = &swarm.ReplicatedService{
   328  			Replicas: opts.replicas.Value(),
   329  		}
   330  	default:
   331  		return serviceMode, fmt.Errorf("Unknown mode: %s, only replicated and global supported", opts.mode)
   332  	}
   333  	return serviceMode, nil
   334  }
   335  
   336  func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
   337  	var service swarm.ServiceSpec
   338  
   339  	envVariables, err := runconfigopts.ReadKVStrings(opts.envFile.GetAll(), opts.env.GetAll())
   340  	if err != nil {
   341  		return service, err
   342  	}
   343  
   344  	currentEnv := make([]string, 0, len(envVariables))
   345  	for _, env := range envVariables { // need to process each var, in order
   346  		k := strings.SplitN(env, "=", 2)[0]
   347  		for i, current := range currentEnv { // remove duplicates
   348  			if current == env {
   349  				continue // no update required, may hide this behind flag to preserve order of envVariables
   350  			}
   351  			if strings.HasPrefix(current, k+"=") {
   352  				currentEnv = append(currentEnv[:i], currentEnv[i+1:]...)
   353  			}
   354  		}
   355  		currentEnv = append(currentEnv, env)
   356  	}
   357  
   358  	healthConfig, err := opts.healthcheck.toHealthConfig()
   359  	if err != nil {
   360  		return service, err
   361  	}
   362  
   363  	serviceMode, err := opts.ToServiceMode()
   364  	if err != nil {
   365  		return service, err
   366  	}
   367  
   368  	service = swarm.ServiceSpec{
   369  		Annotations: swarm.Annotations{
   370  			Name:   opts.name,
   371  			Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
   372  		},
   373  		TaskTemplate: swarm.TaskSpec{
   374  			ContainerSpec: swarm.ContainerSpec{
   375  				Image:    opts.image,
   376  				Args:     opts.args,
   377  				Env:      currentEnv,
   378  				Hostname: opts.hostname,
   379  				Labels:   runconfigopts.ConvertKVStringsToMap(opts.containerLabels.GetAll()),
   380  				Dir:      opts.workdir,
   381  				User:     opts.user,
   382  				Groups:   opts.groups.GetAll(),
   383  				TTY:      opts.tty,
   384  				ReadOnly: opts.readOnly,
   385  				Mounts:   opts.mounts.Value(),
   386  				DNSConfig: &swarm.DNSConfig{
   387  					Nameservers: opts.dns.GetAll(),
   388  					Search:      opts.dnsSearch.GetAll(),
   389  					Options:     opts.dnsOption.GetAll(),
   390  				},
   391  				Hosts:           convertExtraHostsToSwarmHosts(opts.hosts.GetAll()),
   392  				StopGracePeriod: opts.stopGrace.Value(),
   393  				Secrets:         nil,
   394  				Healthcheck:     healthConfig,
   395  			},
   396  			Networks:      convertNetworks(opts.networks.GetAll()),
   397  			Resources:     opts.resources.ToResourceRequirements(),
   398  			RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
   399  			Placement: &swarm.Placement{
   400  				Constraints: opts.constraints.GetAll(),
   401  			},
   402  			LogDriver: opts.logDriver.toLogDriver(),
   403  		},
   404  		Networks: convertNetworks(opts.networks.GetAll()),
   405  		Mode:     serviceMode,
   406  		UpdateConfig: &swarm.UpdateConfig{
   407  			Parallelism:     opts.update.parallelism,
   408  			Delay:           opts.update.delay,
   409  			Monitor:         opts.update.monitor,
   410  			FailureAction:   opts.update.onFailure,
   411  			MaxFailureRatio: opts.update.maxFailureRatio.Value(),
   412  		},
   413  		EndpointSpec: opts.endpoint.ToEndpointSpec(),
   414  	}
   415  
   416  	return service, nil
   417  }
   418  
   419  // addServiceFlags adds all flags that are common to both `create` and `update`.
   420  // Any flags that are not common are added separately in the individual command
   421  func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
   422  	flags := cmd.Flags()
   423  
   424  	flags.StringVarP(&opts.workdir, flagWorkdir, "w", "", "Working directory inside the container")
   425  	flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
   426  	flags.StringVar(&opts.hostname, flagHostname, "", "Container hostname")
   427  	flags.SetAnnotation(flagHostname, "version", []string{"1.25"})
   428  
   429  	flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
   430  	flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
   431  	flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
   432  	flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
   433  	flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container (ns|us|ms|s|m|h)")
   434  
   435  	flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
   436  
   437  	flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", `Restart when condition is met ("none"|"on-failure"|"any")`)
   438  	flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts (ns|us|ms|s|m|h)")
   439  	flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
   440  	flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy (ns|us|ms|s|m|h)")
   441  
   442  	flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously (0 to update all at once)")
   443  	flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates (ns|us|ms|s|m|h) (default 0s)")
   444  	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)")
   445  	flags.SetAnnotation(flagUpdateMonitor, "version", []string{"1.25"})
   446  	flags.StringVar(&opts.update.onFailure, flagUpdateFailureAction, "pause", `Action on update failure ("pause"|"continue")`)
   447  	flags.Var(&opts.update.maxFailureRatio, flagUpdateMaxFailureRatio, "Failure rate to tolerate during an update")
   448  	flags.SetAnnotation(flagUpdateMaxFailureRatio, "version", []string{"1.25"})
   449  
   450  	flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "vip", "Endpoint mode (vip or dnsrr)")
   451  
   452  	flags.BoolVar(&opts.registryAuth, flagRegistryAuth, false, "Send registry authentication details to swarm agents")
   453  
   454  	flags.StringVar(&opts.logDriver.name, flagLogDriver, "", "Logging driver for service")
   455  	flags.Var(&opts.logDriver.opts, flagLogOpt, "Logging driver options")
   456  
   457  	flags.StringVar(&opts.healthcheck.cmd, flagHealthCmd, "", "Command to run to check health")
   458  	flags.SetAnnotation(flagHealthCmd, "version", []string{"1.25"})
   459  	flags.Var(&opts.healthcheck.interval, flagHealthInterval, "Time between running the check (ns|us|ms|s|m|h)")
   460  	flags.SetAnnotation(flagHealthInterval, "version", []string{"1.25"})
   461  	flags.Var(&opts.healthcheck.timeout, flagHealthTimeout, "Maximum time to allow one check to run (ns|us|ms|s|m|h)")
   462  	flags.SetAnnotation(flagHealthTimeout, "version", []string{"1.25"})
   463  	flags.IntVar(&opts.healthcheck.retries, flagHealthRetries, 0, "Consecutive failures needed to report unhealthy")
   464  	flags.SetAnnotation(flagHealthRetries, "version", []string{"1.25"})
   465  	flags.BoolVar(&opts.healthcheck.noHealthcheck, flagNoHealthcheck, false, "Disable any container-specified HEALTHCHECK")
   466  	flags.SetAnnotation(flagNoHealthcheck, "version", []string{"1.25"})
   467  
   468  	flags.BoolVarP(&opts.tty, flagTTY, "t", false, "Allocate a pseudo-TTY")
   469  	flags.SetAnnotation(flagTTY, "version", []string{"1.25"})
   470  
   471  	flags.BoolVar(&opts.readOnly, flagReadOnly, false, "Mount the container's root filesystem as read only")
   472  	flags.SetAnnotation(flagReadOnly, "version", []string{"1.27"})
   473  }
   474  
   475  const (
   476  	flagConstraint            = "constraint"
   477  	flagConstraintRemove      = "constraint-rm"
   478  	flagConstraintAdd         = "constraint-add"
   479  	flagContainerLabel        = "container-label"
   480  	flagContainerLabelRemove  = "container-label-rm"
   481  	flagContainerLabelAdd     = "container-label-add"
   482  	flagDNS                   = "dns"
   483  	flagDNSRemove             = "dns-rm"
   484  	flagDNSAdd                = "dns-add"
   485  	flagDNSOption             = "dns-option"
   486  	flagDNSOptionRemove       = "dns-option-rm"
   487  	flagDNSOptionAdd          = "dns-option-add"
   488  	flagDNSSearch             = "dns-search"
   489  	flagDNSSearchRemove       = "dns-search-rm"
   490  	flagDNSSearchAdd          = "dns-search-add"
   491  	flagEndpointMode          = "endpoint-mode"
   492  	flagHost                  = "host"
   493  	flagHostAdd               = "host-add"
   494  	flagHostRemove            = "host-rm"
   495  	flagHostname              = "hostname"
   496  	flagEnv                   = "env"
   497  	flagEnvFile               = "env-file"
   498  	flagEnvRemove             = "env-rm"
   499  	flagEnvAdd                = "env-add"
   500  	flagGroup                 = "group"
   501  	flagGroupAdd              = "group-add"
   502  	flagGroupRemove           = "group-rm"
   503  	flagLabel                 = "label"
   504  	flagLabelRemove           = "label-rm"
   505  	flagLabelAdd              = "label-add"
   506  	flagLimitCPU              = "limit-cpu"
   507  	flagLimitMemory           = "limit-memory"
   508  	flagMode                  = "mode"
   509  	flagMount                 = "mount"
   510  	flagMountRemove           = "mount-rm"
   511  	flagMountAdd              = "mount-add"
   512  	flagName                  = "name"
   513  	flagNetwork               = "network"
   514  	flagPublish               = "publish"
   515  	flagPublishRemove         = "publish-rm"
   516  	flagPublishAdd            = "publish-add"
   517  	flagReadOnly              = "read-only"
   518  	flagReplicas              = "replicas"
   519  	flagReserveCPU            = "reserve-cpu"
   520  	flagReserveMemory         = "reserve-memory"
   521  	flagRestartCondition      = "restart-condition"
   522  	flagRestartDelay          = "restart-delay"
   523  	flagRestartMaxAttempts    = "restart-max-attempts"
   524  	flagRestartWindow         = "restart-window"
   525  	flagStopGracePeriod       = "stop-grace-period"
   526  	flagTTY                   = "tty"
   527  	flagUpdateDelay           = "update-delay"
   528  	flagUpdateFailureAction   = "update-failure-action"
   529  	flagUpdateMaxFailureRatio = "update-max-failure-ratio"
   530  	flagUpdateMonitor         = "update-monitor"
   531  	flagUpdateParallelism     = "update-parallelism"
   532  	flagUser                  = "user"
   533  	flagWorkdir               = "workdir"
   534  	flagRegistryAuth          = "with-registry-auth"
   535  	flagLogDriver             = "log-driver"
   536  	flagLogOpt                = "log-opt"
   537  	flagHealthCmd             = "health-cmd"
   538  	flagHealthInterval        = "health-interval"
   539  	flagHealthRetries         = "health-retries"
   540  	flagHealthTimeout         = "health-timeout"
   541  	flagNoHealthcheck         = "no-healthcheck"
   542  	flagSecret                = "secret"
   543  	flagSecretAdd             = "secret-add"
   544  	flagSecretRemove          = "secret-rm"
   545  )