github.com/noxiouz/docker@v0.7.3-0.20160629055221-3d231c78e8c5/api/client/service/opts.go (about)

     1  package service
     2  
     3  import (
     4  	"encoding/csv"
     5  	"fmt"
     6  	"math/big"
     7  	"strconv"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/docker/docker/opts"
    12  	runconfigopts "github.com/docker/docker/runconfig/opts"
    13  	"github.com/docker/engine-api/types/swarm"
    14  	"github.com/docker/go-connections/nat"
    15  	units "github.com/docker/go-units"
    16  	"github.com/spf13/cobra"
    17  )
    18  
    19  var (
    20  	// DefaultReplicas is the default replicas to use for a replicated service
    21  	DefaultReplicas uint64 = 1
    22  )
    23  
    24  type int64Value interface {
    25  	Value() int64
    26  }
    27  
    28  type memBytes int64
    29  
    30  func (m *memBytes) String() string {
    31  	return units.BytesSize(float64(m.Value()))
    32  }
    33  
    34  func (m *memBytes) Set(value string) error {
    35  	val, err := units.RAMInBytes(value)
    36  	*m = memBytes(val)
    37  	return err
    38  }
    39  
    40  func (m *memBytes) Type() string {
    41  	return "MemoryBytes"
    42  }
    43  
    44  func (m *memBytes) Value() int64 {
    45  	return int64(*m)
    46  }
    47  
    48  type nanoCPUs int64
    49  
    50  func (c *nanoCPUs) String() string {
    51  	return big.NewRat(c.Value(), 1e9).FloatString(3)
    52  }
    53  
    54  func (c *nanoCPUs) Set(value string) error {
    55  	cpu, ok := new(big.Rat).SetString(value)
    56  	if !ok {
    57  		return fmt.Errorf("Failed to parse %v as a rational number", value)
    58  	}
    59  	nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
    60  	if !nano.IsInt() {
    61  		return fmt.Errorf("value is too precise")
    62  	}
    63  	*c = nanoCPUs(nano.Num().Int64())
    64  	return nil
    65  }
    66  
    67  func (c *nanoCPUs) Type() string {
    68  	return "NanoCPUs"
    69  }
    70  
    71  func (c *nanoCPUs) Value() int64 {
    72  	return int64(*c)
    73  }
    74  
    75  // DurationOpt is an option type for time.Duration that uses a pointer. This
    76  // allows us to get nil values outside, instead of defaulting to 0
    77  type DurationOpt struct {
    78  	value *time.Duration
    79  }
    80  
    81  // Set a new value on the option
    82  func (d *DurationOpt) Set(s string) error {
    83  	v, err := time.ParseDuration(s)
    84  	d.value = &v
    85  	return err
    86  }
    87  
    88  // Type returns the type of this option
    89  func (d *DurationOpt) Type() string {
    90  	return "duration-ptr"
    91  }
    92  
    93  // String returns a string repr of this option
    94  func (d *DurationOpt) String() string {
    95  	if d.value != nil {
    96  		return d.value.String()
    97  	}
    98  	return "none"
    99  }
   100  
   101  // Value returns the time.Duration
   102  func (d *DurationOpt) Value() *time.Duration {
   103  	return d.value
   104  }
   105  
   106  // Uint64Opt represents a uint64.
   107  type Uint64Opt struct {
   108  	value *uint64
   109  }
   110  
   111  // Set a new value on the option
   112  func (i *Uint64Opt) Set(s string) error {
   113  	v, err := strconv.ParseUint(s, 0, 64)
   114  	i.value = &v
   115  	return err
   116  }
   117  
   118  // Type returns the type of this option
   119  func (i *Uint64Opt) Type() string {
   120  	return "uint64-ptr"
   121  }
   122  
   123  // String returns a string repr of this option
   124  func (i *Uint64Opt) String() string {
   125  	if i.value != nil {
   126  		return fmt.Sprintf("%v", *i.value)
   127  	}
   128  	return "none"
   129  }
   130  
   131  // Value returns the uint64
   132  func (i *Uint64Opt) Value() *uint64 {
   133  	return i.value
   134  }
   135  
   136  // MountOpt is a Value type for parsing mounts
   137  type MountOpt struct {
   138  	values []swarm.Mount
   139  }
   140  
   141  // Set a new mount value
   142  func (m *MountOpt) Set(value string) error {
   143  	csvReader := csv.NewReader(strings.NewReader(value))
   144  	fields, err := csvReader.Read()
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	mount := swarm.Mount{}
   150  
   151  	volumeOptions := func() *swarm.VolumeOptions {
   152  		if mount.VolumeOptions == nil {
   153  			mount.VolumeOptions = &swarm.VolumeOptions{
   154  				Labels: make(map[string]string),
   155  			}
   156  		}
   157  		if mount.VolumeOptions.DriverConfig == nil {
   158  			mount.VolumeOptions.DriverConfig = &swarm.Driver{}
   159  		}
   160  		return mount.VolumeOptions
   161  	}
   162  
   163  	bindOptions := func() *swarm.BindOptions {
   164  		if mount.BindOptions == nil {
   165  			mount.BindOptions = new(swarm.BindOptions)
   166  		}
   167  		return mount.BindOptions
   168  	}
   169  
   170  	setValueOnMap := func(target map[string]string, value string) {
   171  		parts := strings.SplitN(value, "=", 2)
   172  		if len(parts) == 1 {
   173  			target[value] = ""
   174  		} else {
   175  			target[parts[0]] = parts[1]
   176  		}
   177  	}
   178  
   179  	for _, field := range fields {
   180  		parts := strings.SplitN(field, "=", 2)
   181  		if len(parts) == 1 && strings.ToLower(parts[0]) == "writable" {
   182  			mount.Writable = true
   183  			continue
   184  		}
   185  
   186  		if len(parts) != 2 {
   187  			return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
   188  		}
   189  
   190  		key, value := parts[0], parts[1]
   191  		switch strings.ToLower(key) {
   192  		case "type":
   193  			mount.Type = swarm.MountType(strings.ToUpper(value))
   194  		case "source":
   195  			mount.Source = value
   196  		case "target":
   197  			mount.Target = value
   198  		case "writable":
   199  			mount.Writable, err = strconv.ParseBool(value)
   200  			if err != nil {
   201  				return fmt.Errorf("invalid value for writable: %s", value)
   202  			}
   203  		case "bind-propagation":
   204  			bindOptions().Propagation = swarm.MountPropagation(strings.ToUpper(value))
   205  		case "volume-populate":
   206  			volumeOptions().Populate, err = strconv.ParseBool(value)
   207  			if err != nil {
   208  				return fmt.Errorf("invalid value for populate: %s", value)
   209  			}
   210  		case "volume-label":
   211  			setValueOnMap(volumeOptions().Labels, value)
   212  		case "volume-driver":
   213  			volumeOptions().DriverConfig.Name = value
   214  		case "volume-driver-opt":
   215  			if volumeOptions().DriverConfig.Options == nil {
   216  				volumeOptions().DriverConfig.Options = make(map[string]string)
   217  			}
   218  			setValueOnMap(volumeOptions().DriverConfig.Options, value)
   219  		default:
   220  			return fmt.Errorf("unexpected key '%s' in '%s'", key, value)
   221  		}
   222  	}
   223  
   224  	if mount.Type == "" {
   225  		return fmt.Errorf("type is required")
   226  	}
   227  
   228  	if mount.Target == "" {
   229  		return fmt.Errorf("target is required")
   230  	}
   231  
   232  	m.values = append(m.values, mount)
   233  	return nil
   234  }
   235  
   236  // Type returns the type of this option
   237  func (m *MountOpt) Type() string {
   238  	return "mount"
   239  }
   240  
   241  // String returns a string repr of this option
   242  func (m *MountOpt) String() string {
   243  	mounts := []string{}
   244  	for _, mount := range m.values {
   245  		repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
   246  		mounts = append(mounts, repr)
   247  	}
   248  	return strings.Join(mounts, ", ")
   249  }
   250  
   251  // Value returns the mounts
   252  func (m *MountOpt) Value() []swarm.Mount {
   253  	return m.values
   254  }
   255  
   256  type updateOptions struct {
   257  	parallelism uint64
   258  	delay       time.Duration
   259  }
   260  
   261  type resourceOptions struct {
   262  	limitCPU      nanoCPUs
   263  	limitMemBytes memBytes
   264  	resCPU        nanoCPUs
   265  	resMemBytes   memBytes
   266  }
   267  
   268  func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
   269  	return &swarm.ResourceRequirements{
   270  		Limits: &swarm.Resources{
   271  			NanoCPUs:    r.limitCPU.Value(),
   272  			MemoryBytes: r.limitMemBytes.Value(),
   273  		},
   274  		Reservations: &swarm.Resources{
   275  			NanoCPUs:    r.resCPU.Value(),
   276  			MemoryBytes: r.resMemBytes.Value(),
   277  		},
   278  	}
   279  }
   280  
   281  type restartPolicyOptions struct {
   282  	condition   string
   283  	delay       DurationOpt
   284  	maxAttempts Uint64Opt
   285  	window      DurationOpt
   286  }
   287  
   288  func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
   289  	return &swarm.RestartPolicy{
   290  		Condition:   swarm.RestartPolicyCondition(r.condition),
   291  		Delay:       r.delay.Value(),
   292  		MaxAttempts: r.maxAttempts.Value(),
   293  		Window:      r.window.Value(),
   294  	}
   295  }
   296  
   297  func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
   298  	nets := []swarm.NetworkAttachmentConfig{}
   299  	for _, network := range networks {
   300  		nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
   301  	}
   302  	return nets
   303  }
   304  
   305  type endpointOptions struct {
   306  	mode  string
   307  	ports opts.ListOpts
   308  }
   309  
   310  func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
   311  	portConfigs := []swarm.PortConfig{}
   312  	// We can ignore errors because the format was already validated by ValidatePort
   313  	ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll())
   314  
   315  	for port := range ports {
   316  		portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...)
   317  	}
   318  
   319  	return &swarm.EndpointSpec{
   320  		Mode:  swarm.ResolutionMode(strings.ToLower(e.mode)),
   321  		Ports: portConfigs,
   322  	}
   323  }
   324  
   325  func convertPortToPortConfig(
   326  	port nat.Port,
   327  	portBindings map[nat.Port][]nat.PortBinding,
   328  ) []swarm.PortConfig {
   329  	ports := []swarm.PortConfig{}
   330  
   331  	for _, binding := range portBindings[port] {
   332  		hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
   333  		ports = append(ports, swarm.PortConfig{
   334  			//TODO Name: ?
   335  			Protocol:      swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
   336  			TargetPort:    uint32(port.Int()),
   337  			PublishedPort: uint32(hostPort),
   338  		})
   339  	}
   340  	return ports
   341  }
   342  
   343  // ValidatePort validates a string is in the expected format for a port definition
   344  func ValidatePort(value string) (string, error) {
   345  	portMappings, err := nat.ParsePortSpec(value)
   346  	for _, portMapping := range portMappings {
   347  		if portMapping.Binding.HostIP != "" {
   348  			return "", fmt.Errorf("HostIP is not supported by a service.")
   349  		}
   350  	}
   351  	return value, err
   352  }
   353  
   354  type serviceOptions struct {
   355  	name    string
   356  	labels  opts.ListOpts
   357  	image   string
   358  	command []string
   359  	args    []string
   360  	env     opts.ListOpts
   361  	workdir string
   362  	user    string
   363  	mounts  MountOpt
   364  
   365  	resources resourceOptions
   366  	stopGrace DurationOpt
   367  
   368  	replicas Uint64Opt
   369  	mode     string
   370  
   371  	restartPolicy restartPolicyOptions
   372  	constraints   []string
   373  	update        updateOptions
   374  	networks      []string
   375  	endpoint      endpointOptions
   376  }
   377  
   378  func newServiceOptions() *serviceOptions {
   379  	return &serviceOptions{
   380  		labels: opts.NewListOpts(runconfigopts.ValidateEnv),
   381  		env:    opts.NewListOpts(runconfigopts.ValidateEnv),
   382  		endpoint: endpointOptions{
   383  			ports: opts.NewListOpts(ValidatePort),
   384  		},
   385  	}
   386  }
   387  
   388  func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
   389  	var service swarm.ServiceSpec
   390  
   391  	service = swarm.ServiceSpec{
   392  		Annotations: swarm.Annotations{
   393  			Name:   opts.name,
   394  			Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
   395  		},
   396  		TaskTemplate: swarm.TaskSpec{
   397  			ContainerSpec: swarm.ContainerSpec{
   398  				Image:           opts.image,
   399  				Command:         opts.command,
   400  				Args:            opts.args,
   401  				Env:             opts.env.GetAll(),
   402  				Dir:             opts.workdir,
   403  				User:            opts.user,
   404  				Mounts:          opts.mounts.Value(),
   405  				StopGracePeriod: opts.stopGrace.Value(),
   406  			},
   407  			Resources:     opts.resources.ToResourceRequirements(),
   408  			RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
   409  			Placement: &swarm.Placement{
   410  				Constraints: opts.constraints,
   411  			},
   412  		},
   413  		Mode: swarm.ServiceMode{},
   414  		UpdateConfig: &swarm.UpdateConfig{
   415  			Parallelism: opts.update.parallelism,
   416  			Delay:       opts.update.delay,
   417  		},
   418  		Networks:     convertNetworks(opts.networks),
   419  		EndpointSpec: opts.endpoint.ToEndpointSpec(),
   420  	}
   421  
   422  	switch opts.mode {
   423  	case "global":
   424  		if opts.replicas.Value() != nil {
   425  			return service, fmt.Errorf("replicas can only be used with replicated mode")
   426  		}
   427  
   428  		service.Mode.Global = &swarm.GlobalService{}
   429  	case "replicated":
   430  		service.Mode.Replicated = &swarm.ReplicatedService{
   431  			Replicas: opts.replicas.Value(),
   432  		}
   433  	default:
   434  		return service, fmt.Errorf("Unknown mode: %s", opts.mode)
   435  	}
   436  	return service, nil
   437  }
   438  
   439  // addServiceFlags adds all flags that are common to both `create` and `update.
   440  // Any flags that are not common are added separately in the individual command
   441  func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
   442  	flags := cmd.Flags()
   443  	flags.StringVar(&opts.name, flagName, "", "Service name")
   444  	flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
   445  
   446  	flags.VarP(&opts.env, "env", "e", "Set environment variables")
   447  	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
   448  	flags.StringVarP(&opts.user, flagUser, "u", "", "Username or UID")
   449  	flags.VarP(&opts.mounts, flagMount, "m", "Attach a mount to the service")
   450  
   451  	flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
   452  	flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
   453  	flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
   454  	flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
   455  	flags.Var(&opts.stopGrace, flagStopGracePeriod, "Time to wait before force killing a container")
   456  
   457  	flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
   458  
   459  	flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on_failure, or any)")
   460  	flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts")
   461  	flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
   462  	flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evaluate the restart policy")
   463  
   464  	flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints")
   465  
   466  	flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 0, "Maximum number of tasks updated simultaneously")
   467  	flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates")
   468  
   469  	flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
   470  	flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode(Valid values: vip, dnsrr)")
   471  	flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
   472  }
   473  
   474  const (
   475  	flagConstraint         = "constraint"
   476  	flagEndpointMode       = "endpoint-mode"
   477  	flagLabel              = "label"
   478  	flagLimitCPU           = "limit-cpu"
   479  	flagLimitMemory        = "limit-memory"
   480  	flagMode               = "mode"
   481  	flagMount              = "mount"
   482  	flagName               = "name"
   483  	flagNetwork            = "network"
   484  	flagPublish            = "publish"
   485  	flagReplicas           = "replicas"
   486  	flagReserveCPU         = "reserve-cpu"
   487  	flagReserveMemory      = "reserve-memory"
   488  	flagRestartCondition   = "restart-condition"
   489  	flagRestartDelay       = "restart-delay"
   490  	flagRestartMaxAttempts = "restart-max-attempts"
   491  	flagRestartWindow      = "restart-window"
   492  	flagStopGracePeriod    = "stop-grace-period"
   493  	flagUpdateDelay        = "update-delay"
   494  	flagUpdateParallelism  = "update-parallelism"
   495  	flagUser               = "user"
   496  )