github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/swarm/opts.go (about)

     1  package swarm
     2  
     3  import (
     4  	"encoding/csv"
     5  	"encoding/pem"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/docker/cli/opts"
    12  	"github.com/docker/docker/api/types/swarm"
    13  	"github.com/pkg/errors"
    14  	"github.com/spf13/pflag"
    15  )
    16  
    17  const (
    18  	defaultListenAddr = "0.0.0.0:2377"
    19  
    20  	flagCertExpiry                = "cert-expiry"
    21  	flagDispatcherHeartbeat       = "dispatcher-heartbeat"
    22  	flagListenAddr                = "listen-addr"
    23  	flagAdvertiseAddr             = "advertise-addr"
    24  	flagDataPathAddr              = "data-path-addr"
    25  	flagDataPathPort              = "data-path-port"
    26  	flagDefaultAddrPool           = "default-addr-pool"
    27  	flagDefaultAddrPoolMaskLength = "default-addr-pool-mask-length"
    28  	flagQuiet                     = "quiet"
    29  	flagRotate                    = "rotate"
    30  	flagToken                     = "token"
    31  	flagTaskHistoryLimit          = "task-history-limit"
    32  	flagExternalCA                = "external-ca"
    33  	flagMaxSnapshots              = "max-snapshots"
    34  	flagSnapshotInterval          = "snapshot-interval"
    35  	flagAutolock                  = "autolock"
    36  	flagAvailability              = "availability"
    37  	flagCACert                    = "ca-cert"
    38  	flagCAKey                     = "ca-key"
    39  )
    40  
    41  type swarmOptions struct {
    42  	swarmCAOptions
    43  	taskHistoryLimit    int64
    44  	dispatcherHeartbeat time.Duration
    45  	maxSnapshots        uint64
    46  	snapshotInterval    uint64
    47  	autolock            bool
    48  }
    49  
    50  // NodeAddrOption is a pflag.Value for listening addresses
    51  type NodeAddrOption struct {
    52  	addr string
    53  }
    54  
    55  // String prints the representation of this flag
    56  func (a *NodeAddrOption) String() string {
    57  	return a.Value()
    58  }
    59  
    60  // Set the value for this flag
    61  func (a *NodeAddrOption) Set(value string) error {
    62  	addr, err := opts.ParseTCPAddr(value, a.addr)
    63  	if err != nil {
    64  		return err
    65  	}
    66  	a.addr = addr
    67  	return nil
    68  }
    69  
    70  // Type returns the type of this flag
    71  func (a *NodeAddrOption) Type() string {
    72  	return "node-addr"
    73  }
    74  
    75  // Value returns the value of this option as addr:port
    76  func (a *NodeAddrOption) Value() string {
    77  	return strings.TrimPrefix(a.addr, "tcp://")
    78  }
    79  
    80  // NewNodeAddrOption returns a new node address option
    81  func NewNodeAddrOption(addr string) NodeAddrOption {
    82  	return NodeAddrOption{addr}
    83  }
    84  
    85  // NewListenAddrOption returns a NodeAddrOption with default values
    86  func NewListenAddrOption() NodeAddrOption {
    87  	return NewNodeAddrOption(defaultListenAddr)
    88  }
    89  
    90  // ExternalCAOption is a Value type for parsing external CA specifications.
    91  type ExternalCAOption struct {
    92  	values []*swarm.ExternalCA
    93  }
    94  
    95  // Set parses an external CA option.
    96  func (m *ExternalCAOption) Set(value string) error {
    97  	parsed, err := parseExternalCA(value)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	m.values = append(m.values, parsed)
   103  	return nil
   104  }
   105  
   106  // Type returns the type of this option.
   107  func (m *ExternalCAOption) Type() string {
   108  	return "external-ca"
   109  }
   110  
   111  // String returns a string repr of this option.
   112  func (m *ExternalCAOption) String() string {
   113  	externalCAs := []string{}
   114  	for _, externalCA := range m.values {
   115  		repr := fmt.Sprintf("%s: %s", externalCA.Protocol, externalCA.URL)
   116  		externalCAs = append(externalCAs, repr)
   117  	}
   118  	return strings.Join(externalCAs, ", ")
   119  }
   120  
   121  // Value returns the external CAs
   122  func (m *ExternalCAOption) Value() []*swarm.ExternalCA {
   123  	return m.values
   124  }
   125  
   126  // PEMFile represents the path to a pem-formatted file
   127  type PEMFile struct {
   128  	path, contents string
   129  }
   130  
   131  // Type returns the type of this option.
   132  func (p *PEMFile) Type() string {
   133  	return "pem-file"
   134  }
   135  
   136  // String returns the path to the pem file
   137  func (p *PEMFile) String() string {
   138  	return p.path
   139  }
   140  
   141  // Set parses a root rotation option
   142  func (p *PEMFile) Set(value string) error {
   143  	contents, err := ioutil.ReadFile(value)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	if pemBlock, _ := pem.Decode(contents); pemBlock == nil {
   148  		return errors.New("file contents must be in PEM format")
   149  	}
   150  	p.contents, p.path = string(contents), value
   151  	return nil
   152  }
   153  
   154  // Contents returns the contents of the PEM file
   155  func (p *PEMFile) Contents() string {
   156  	return p.contents
   157  }
   158  
   159  // parseExternalCA parses an external CA specification from the command line,
   160  // such as protocol=cfssl,url=https://example.com.
   161  func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
   162  	csvReader := csv.NewReader(strings.NewReader(caSpec))
   163  	fields, err := csvReader.Read()
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  
   168  	externalCA := swarm.ExternalCA{
   169  		Options: make(map[string]string),
   170  	}
   171  
   172  	var (
   173  		hasProtocol bool
   174  		hasURL      bool
   175  	)
   176  
   177  	for _, field := range fields {
   178  		parts := strings.SplitN(field, "=", 2)
   179  
   180  		if len(parts) != 2 {
   181  			return nil, errors.Errorf("invalid field '%s' must be a key=value pair", field)
   182  		}
   183  
   184  		key, value := parts[0], parts[1]
   185  
   186  		switch strings.ToLower(key) {
   187  		case "protocol":
   188  			hasProtocol = true
   189  			if strings.ToLower(value) == string(swarm.ExternalCAProtocolCFSSL) {
   190  				externalCA.Protocol = swarm.ExternalCAProtocolCFSSL
   191  			} else {
   192  				return nil, errors.Errorf("unrecognized external CA protocol %s", value)
   193  			}
   194  		case "url":
   195  			hasURL = true
   196  			externalCA.URL = value
   197  		case "cacert":
   198  			cacontents, err := ioutil.ReadFile(value)
   199  			if err != nil {
   200  				return nil, errors.Wrap(err, "unable to read CA cert for external CA")
   201  			}
   202  			if pemBlock, _ := pem.Decode(cacontents); pemBlock == nil {
   203  				return nil, errors.New("CA cert for external CA must be in PEM format")
   204  			}
   205  			externalCA.CACert = string(cacontents)
   206  		default:
   207  			externalCA.Options[key] = value
   208  		}
   209  	}
   210  
   211  	if !hasProtocol {
   212  		return nil, errors.New("the external-ca option needs a protocol= parameter")
   213  	}
   214  	if !hasURL {
   215  		return nil, errors.New("the external-ca option needs a url= parameter")
   216  	}
   217  
   218  	return &externalCA, nil
   219  }
   220  
   221  func addSwarmCAFlags(flags *pflag.FlagSet, opts *swarmCAOptions) {
   222  	flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, 90*24*time.Hour, "Validity period for node certificates (ns|us|ms|s|m|h)")
   223  	flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
   224  }
   225  
   226  func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
   227  	flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 5, "Task history retention limit")
   228  	flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, 5*time.Second, "Dispatcher heartbeat period (ns|us|ms|s|m|h)")
   229  	flags.Uint64Var(&opts.maxSnapshots, flagMaxSnapshots, 0, "Number of additional Raft snapshots to retain")
   230  	flags.SetAnnotation(flagMaxSnapshots, "version", []string{"1.25"})
   231  	flags.Uint64Var(&opts.snapshotInterval, flagSnapshotInterval, 10000, "Number of log entries between Raft snapshots")
   232  	flags.SetAnnotation(flagSnapshotInterval, "version", []string{"1.25"})
   233  	addSwarmCAFlags(flags, &opts.swarmCAOptions)
   234  }
   235  
   236  func (opts *swarmOptions) mergeSwarmSpec(spec *swarm.Spec, flags *pflag.FlagSet, caCert string) {
   237  	if flags.Changed(flagTaskHistoryLimit) {
   238  		spec.Orchestration.TaskHistoryRetentionLimit = &opts.taskHistoryLimit
   239  	}
   240  	if flags.Changed(flagDispatcherHeartbeat) {
   241  		spec.Dispatcher.HeartbeatPeriod = opts.dispatcherHeartbeat
   242  	}
   243  	if flags.Changed(flagMaxSnapshots) {
   244  		spec.Raft.KeepOldSnapshots = &opts.maxSnapshots
   245  	}
   246  	if flags.Changed(flagSnapshotInterval) {
   247  		spec.Raft.SnapshotInterval = opts.snapshotInterval
   248  	}
   249  	if flags.Changed(flagAutolock) {
   250  		spec.EncryptionConfig.AutoLockManagers = opts.autolock
   251  	}
   252  	opts.mergeSwarmSpecCAFlags(spec, flags, caCert)
   253  }
   254  
   255  type swarmCAOptions struct {
   256  	nodeCertExpiry time.Duration
   257  	externalCA     ExternalCAOption
   258  }
   259  
   260  func (opts *swarmCAOptions) mergeSwarmSpecCAFlags(spec *swarm.Spec, flags *pflag.FlagSet, caCert string) {
   261  	if flags.Changed(flagCertExpiry) {
   262  		spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
   263  	}
   264  	if flags.Changed(flagExternalCA) {
   265  		spec.CAConfig.ExternalCAs = opts.externalCA.Value()
   266  		for _, ca := range spec.CAConfig.ExternalCAs {
   267  			ca.CACert = caCert
   268  		}
   269  	}
   270  }
   271  
   272  func (opts *swarmOptions) ToSpec(flags *pflag.FlagSet) swarm.Spec {
   273  	var spec swarm.Spec
   274  	opts.mergeSwarmSpec(&spec, flags, "")
   275  	return spec
   276  }