github.com/kobeld/docker@v1.12.0-rc1/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 strconv.FormatInt(m.Value(), 10)
    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 strconv.FormatInt(c.Value(), 10)
    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  	setValueOnMap := func(target map[string]string, value string) {
   164  		parts := strings.SplitN(value, "=", 2)
   165  		if len(parts) == 1 {
   166  			target[value] = ""
   167  		} else {
   168  			target[parts[0]] = parts[1]
   169  		}
   170  	}
   171  
   172  	for _, field := range fields {
   173  		parts := strings.SplitN(field, "=", 2)
   174  		if len(parts) == 1 && strings.ToLower(parts[0]) == "writable" {
   175  			mount.Writable = true
   176  			continue
   177  		}
   178  
   179  		if len(parts) != 2 {
   180  			return fmt.Errorf("invald field '%s' must be a key=value pair", field)
   181  		}
   182  
   183  		key, value := parts[0], parts[1]
   184  		switch strings.ToLower(key) {
   185  		case "type":
   186  			mount.Type = swarm.MountType(strings.ToUpper(value))
   187  		case "source":
   188  			mount.Source = value
   189  		case "target":
   190  			mount.Target = value
   191  		case "writable":
   192  			mount.Writable, err = strconv.ParseBool(value)
   193  			if err != nil {
   194  				return fmt.Errorf("invald value for writable: %s", err.Error())
   195  			}
   196  		case "bind-propagation":
   197  			mount.BindOptions.Propagation = swarm.MountPropagation(strings.ToUpper(value))
   198  		case "volume-populate":
   199  			volumeOptions().Populate, err = strconv.ParseBool(value)
   200  			if err != nil {
   201  				return fmt.Errorf("invald value for populate: %s", err.Error())
   202  			}
   203  		case "volume-label":
   204  			setValueOnMap(volumeOptions().Labels, value)
   205  		case "volume-driver":
   206  			volumeOptions().DriverConfig.Name = value
   207  		case "volume-driver-opt":
   208  			if volumeOptions().DriverConfig.Options == nil {
   209  				volumeOptions().DriverConfig.Options = make(map[string]string)
   210  			}
   211  			setValueOnMap(volumeOptions().DriverConfig.Options, value)
   212  		default:
   213  			return fmt.Errorf("unexpected key '%s' in '%s'", key, value)
   214  		}
   215  	}
   216  
   217  	if mount.Type == "" {
   218  		return fmt.Errorf("type is required")
   219  	}
   220  
   221  	if mount.Target == "" {
   222  		return fmt.Errorf("target is required")
   223  	}
   224  
   225  	m.values = append(m.values, mount)
   226  	return nil
   227  }
   228  
   229  // Type returns the type of this option
   230  func (m *MountOpt) Type() string {
   231  	return "mount"
   232  }
   233  
   234  // String returns a string repr of this option
   235  func (m *MountOpt) String() string {
   236  	mounts := []string{}
   237  	for _, mount := range m.values {
   238  		mounts = append(mounts, fmt.Sprintf("%v", mount))
   239  	}
   240  	return strings.Join(mounts, ", ")
   241  }
   242  
   243  // Value returns the mounts
   244  func (m *MountOpt) Value() []swarm.Mount {
   245  	return m.values
   246  }
   247  
   248  type updateOptions struct {
   249  	parallelism uint64
   250  	delay       time.Duration
   251  }
   252  
   253  type resourceOptions struct {
   254  	limitCPU      nanoCPUs
   255  	limitMemBytes memBytes
   256  	resCPU        nanoCPUs
   257  	resMemBytes   memBytes
   258  }
   259  
   260  func (r *resourceOptions) ToResourceRequirements() *swarm.ResourceRequirements {
   261  	return &swarm.ResourceRequirements{
   262  		Limits: &swarm.Resources{
   263  			NanoCPUs:    r.limitCPU.Value(),
   264  			MemoryBytes: r.limitMemBytes.Value(),
   265  		},
   266  		Reservations: &swarm.Resources{
   267  			NanoCPUs:    r.resCPU.Value(),
   268  			MemoryBytes: r.resMemBytes.Value(),
   269  		},
   270  	}
   271  }
   272  
   273  type restartPolicyOptions struct {
   274  	condition   string
   275  	delay       DurationOpt
   276  	maxAttempts Uint64Opt
   277  	window      DurationOpt
   278  }
   279  
   280  func (r *restartPolicyOptions) ToRestartPolicy() *swarm.RestartPolicy {
   281  	return &swarm.RestartPolicy{
   282  		Condition:   swarm.RestartPolicyCondition(r.condition),
   283  		Delay:       r.delay.Value(),
   284  		MaxAttempts: r.maxAttempts.Value(),
   285  		Window:      r.window.Value(),
   286  	}
   287  }
   288  
   289  func convertNetworks(networks []string) []swarm.NetworkAttachmentConfig {
   290  	nets := []swarm.NetworkAttachmentConfig{}
   291  	for _, network := range networks {
   292  		nets = append(nets, swarm.NetworkAttachmentConfig{Target: network})
   293  	}
   294  	return nets
   295  }
   296  
   297  type endpointOptions struct {
   298  	mode  string
   299  	ports opts.ListOpts
   300  }
   301  
   302  func (e *endpointOptions) ToEndpointSpec() *swarm.EndpointSpec {
   303  	portConfigs := []swarm.PortConfig{}
   304  	// We can ignore errors because the format was already validated by ValidatePort
   305  	ports, portBindings, _ := nat.ParsePortSpecs(e.ports.GetAll())
   306  
   307  	for port := range ports {
   308  		portConfigs = append(portConfigs, convertPortToPortConfig(port, portBindings)...)
   309  	}
   310  
   311  	return &swarm.EndpointSpec{
   312  		Mode:  swarm.ResolutionMode(e.mode),
   313  		Ports: portConfigs,
   314  	}
   315  }
   316  
   317  func convertPortToPortConfig(
   318  	port nat.Port,
   319  	portBindings map[nat.Port][]nat.PortBinding,
   320  ) []swarm.PortConfig {
   321  	ports := []swarm.PortConfig{}
   322  
   323  	for _, binding := range portBindings[port] {
   324  		hostPort, _ := strconv.ParseUint(binding.HostPort, 10, 16)
   325  		ports = append(ports, swarm.PortConfig{
   326  			//TODO Name: ?
   327  			Protocol:      swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
   328  			TargetPort:    uint32(port.Int()),
   329  			PublishedPort: uint32(hostPort),
   330  		})
   331  	}
   332  	return ports
   333  }
   334  
   335  // ValidatePort validates a string is in the expected format for a port definition
   336  func ValidatePort(value string) (string, error) {
   337  	portMappings, err := nat.ParsePortSpec(value)
   338  	for _, portMapping := range portMappings {
   339  		if portMapping.Binding.HostIP != "" {
   340  			return "", fmt.Errorf("HostIP is not supported by a service.")
   341  		}
   342  	}
   343  	return value, err
   344  }
   345  
   346  type serviceOptions struct {
   347  	name    string
   348  	labels  opts.ListOpts
   349  	image   string
   350  	command []string
   351  	args    []string
   352  	env     opts.ListOpts
   353  	workdir string
   354  	user    string
   355  	mounts  MountOpt
   356  
   357  	resources resourceOptions
   358  	stopGrace DurationOpt
   359  
   360  	replicas Uint64Opt
   361  	mode     string
   362  
   363  	restartPolicy restartPolicyOptions
   364  	constraints   []string
   365  	update        updateOptions
   366  	networks      []string
   367  	endpoint      endpointOptions
   368  }
   369  
   370  func newServiceOptions() *serviceOptions {
   371  	return &serviceOptions{
   372  		labels: opts.NewListOpts(runconfigopts.ValidateEnv),
   373  		env:    opts.NewListOpts(runconfigopts.ValidateEnv),
   374  		endpoint: endpointOptions{
   375  			ports: opts.NewListOpts(ValidatePort),
   376  		},
   377  	}
   378  }
   379  
   380  func (opts *serviceOptions) ToService() (swarm.ServiceSpec, error) {
   381  	var service swarm.ServiceSpec
   382  
   383  	service = swarm.ServiceSpec{
   384  		Annotations: swarm.Annotations{
   385  			Name:   opts.name,
   386  			Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
   387  		},
   388  		TaskTemplate: swarm.TaskSpec{
   389  			ContainerSpec: swarm.ContainerSpec{
   390  				Image:           opts.image,
   391  				Command:         opts.command,
   392  				Args:            opts.args,
   393  				Env:             opts.env.GetAll(),
   394  				Dir:             opts.workdir,
   395  				User:            opts.user,
   396  				Mounts:          opts.mounts.Value(),
   397  				StopGracePeriod: opts.stopGrace.Value(),
   398  			},
   399  			Resources:     opts.resources.ToResourceRequirements(),
   400  			RestartPolicy: opts.restartPolicy.ToRestartPolicy(),
   401  			Placement: &swarm.Placement{
   402  				Constraints: opts.constraints,
   403  			},
   404  		},
   405  		Mode: swarm.ServiceMode{},
   406  		UpdateConfig: &swarm.UpdateConfig{
   407  			Parallelism: opts.update.parallelism,
   408  			Delay:       opts.update.delay,
   409  		},
   410  		Networks:     convertNetworks(opts.networks),
   411  		EndpointSpec: opts.endpoint.ToEndpointSpec(),
   412  	}
   413  
   414  	switch opts.mode {
   415  	case "global":
   416  		if opts.replicas.Value() != nil {
   417  			return service, fmt.Errorf("replicas can only be used with replicated mode")
   418  		}
   419  
   420  		service.Mode.Global = &swarm.GlobalService{}
   421  	case "replicated":
   422  		service.Mode.Replicated = &swarm.ReplicatedService{
   423  			Replicas: opts.replicas.Value(),
   424  		}
   425  	default:
   426  		return service, fmt.Errorf("Unknown mode: %s", opts.mode)
   427  	}
   428  	return service, nil
   429  }
   430  
   431  // addServiceFlags adds all flags that are common to both `create` and `update.
   432  // Any flags that are not common are added separately in the individual command
   433  func addServiceFlags(cmd *cobra.Command, opts *serviceOptions) {
   434  	flags := cmd.Flags()
   435  	flags.StringVar(&opts.name, flagName, "", "Service name")
   436  	flags.VarP(&opts.labels, flagLabel, "l", "Service labels")
   437  
   438  	flags.VarP(&opts.env, "env", "e", "Set environment variables")
   439  	flags.StringVarP(&opts.workdir, "workdir", "w", "", "Working directory inside the container")
   440  	flags.StringVarP(&opts.user, "user", "u", "", "Username or UID")
   441  	flags.VarP(&opts.mounts, flagMount, "m", "Attach a mount to the service")
   442  
   443  	flags.Var(&opts.resources.limitCPU, flagLimitCPU, "Limit CPUs")
   444  	flags.Var(&opts.resources.limitMemBytes, flagLimitMemory, "Limit Memory")
   445  	flags.Var(&opts.resources.resCPU, flagReserveCPU, "Reserve CPUs")
   446  	flags.Var(&opts.resources.resMemBytes, flagReserveMemory, "Reserve Memory")
   447  	flags.Var(&opts.stopGrace, "stop-grace-period", "Time to wait before force killing a container")
   448  
   449  	flags.StringVar(&opts.mode, flagMode, "replicated", "Service mode (replicated or global)")
   450  	flags.Var(&opts.replicas, flagReplicas, "Number of tasks")
   451  
   452  	flags.StringVar(&opts.restartPolicy.condition, flagRestartCondition, "", "Restart when condition is met (none, on_failure, or any)")
   453  	flags.Var(&opts.restartPolicy.delay, flagRestartDelay, "Delay between restart attempts")
   454  	flags.Var(&opts.restartPolicy.maxAttempts, flagRestartMaxAttempts, "Maximum number of restarts before giving up")
   455  	flags.Var(&opts.restartPolicy.window, flagRestartWindow, "Window used to evalulate the restart policy")
   456  
   457  	flags.StringSliceVar(&opts.constraints, flagConstraint, []string{}, "Placement constraints")
   458  
   459  	flags.Uint64Var(&opts.update.parallelism, flagUpdateParallelism, 1, "Maximum number of tasks updated simultaneously")
   460  	flags.DurationVar(&opts.update.delay, flagUpdateDelay, time.Duration(0), "Delay between updates")
   461  
   462  	flags.StringSliceVar(&opts.networks, flagNetwork, []string{}, "Network attachments")
   463  	flags.StringVar(&opts.endpoint.mode, flagEndpointMode, "", "Endpoint mode(Valid values: VIP, DNSRR)")
   464  	flags.VarP(&opts.endpoint.ports, flagPublish, "p", "Publish a port as a node port")
   465  }
   466  
   467  const (
   468  	flagConstraint         = "constraint"
   469  	flagName               = "name"
   470  	flagLabel              = "label"
   471  	flagLimitCPU           = "limit-cpu"
   472  	flagLimitMemory        = "limit-memory"
   473  	flagReserveCPU         = "reserve-cpu"
   474  	flagReserveMemory      = "reserve-memory"
   475  	flagMount              = "mount"
   476  	flagMode               = "mode"
   477  	flagReplicas           = "replicas"
   478  	flagPublish            = "publish"
   479  	flagNetwork            = "network"
   480  	flagRestartCondition   = "restart-condition"
   481  	flagRestartDelay       = "restart-delay"
   482  	flagRestartMaxAttempts = "restart-max-attempts"
   483  	flagRestartWindow      = "restart-window"
   484  	flagEndpointMode       = "endpoint-mode"
   485  	flagUpdateParallelism  = "update-parallelism"
   486  	flagUpdateDelay        = "update-delay"
   487  )