github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/cli/command/service/opts.go (about)

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