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