mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
8e14882773
Signed-off-by: John Harris <john@johnharris.io>
262 lines
7 KiB
Go
262 lines
7 KiB
Go
package swarm
|
|
|
|
import (
|
|
"encoding/csv"
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/docker/opts"
|
|
"github.com/docker/engine-api/types/swarm"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
const (
|
|
defaultListenAddr = "0.0.0.0:2377"
|
|
|
|
worker = "WORKER"
|
|
manager = "MANAGER"
|
|
none = "NONE"
|
|
|
|
flagAutoAccept = "auto-accept"
|
|
flagCertExpiry = "cert-expiry"
|
|
flagDispatcherHeartbeat = "dispatcher-heartbeat"
|
|
flagListenAddr = "listen-addr"
|
|
flagSecret = "secret"
|
|
flagTaskHistoryLimit = "task-history-limit"
|
|
flagExternalCA = "external-ca"
|
|
)
|
|
|
|
var (
|
|
defaultPolicies = []swarm.Policy{
|
|
{Role: worker, Autoaccept: true},
|
|
{Role: manager, Autoaccept: false},
|
|
}
|
|
)
|
|
|
|
type swarmOptions struct {
|
|
autoAccept AutoAcceptOption
|
|
secret string
|
|
taskHistoryLimit int64
|
|
dispatcherHeartbeat time.Duration
|
|
nodeCertExpiry time.Duration
|
|
externalCA ExternalCAOption
|
|
}
|
|
|
|
// NodeAddrOption is a pflag.Value for listen and remote addresses
|
|
type NodeAddrOption struct {
|
|
addr string
|
|
}
|
|
|
|
// String prints the representation of this flag
|
|
func (a *NodeAddrOption) String() string {
|
|
return a.Value()
|
|
}
|
|
|
|
// Set the value for this flag
|
|
func (a *NodeAddrOption) Set(value string) error {
|
|
addr, err := opts.ParseTCPAddr(value, a.addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
a.addr = addr
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this flag
|
|
func (a *NodeAddrOption) Type() string {
|
|
return "node-addr"
|
|
}
|
|
|
|
// Value returns the value of this option as addr:port
|
|
func (a *NodeAddrOption) Value() string {
|
|
return strings.TrimPrefix(a.addr, "tcp://")
|
|
}
|
|
|
|
// NewNodeAddrOption returns a new node address option
|
|
func NewNodeAddrOption(addr string) NodeAddrOption {
|
|
return NodeAddrOption{addr}
|
|
}
|
|
|
|
// NewListenAddrOption returns a NodeAddrOption with default values
|
|
func NewListenAddrOption() NodeAddrOption {
|
|
return NewNodeAddrOption(defaultListenAddr)
|
|
}
|
|
|
|
// AutoAcceptOption is a value type for auto-accept policy
|
|
type AutoAcceptOption struct {
|
|
values map[string]struct{}
|
|
}
|
|
|
|
// String prints a string representation of this option
|
|
func (o *AutoAcceptOption) String() string {
|
|
keys := []string{}
|
|
for key := range o.values {
|
|
keys = append(keys, fmt.Sprintf("%s=true", strings.ToLower(key)))
|
|
}
|
|
return strings.Join(keys, ", ")
|
|
}
|
|
|
|
// Set sets a new value on this option
|
|
func (o *AutoAcceptOption) Set(acceptValues string) error {
|
|
for _, value := range strings.Split(acceptValues, ",") {
|
|
value = strings.ToUpper(value)
|
|
switch value {
|
|
case none, worker, manager:
|
|
o.values[value] = struct{}{}
|
|
default:
|
|
return fmt.Errorf("must be one / combination of %s, %s; or NONE", worker, manager)
|
|
}
|
|
}
|
|
// NONE must stand alone, so if any non-NONE setting exist with it, error with conflict
|
|
if o.isPresent(none) && len(o.values) > 1 {
|
|
return fmt.Errorf("value NONE cannot be specified alongside other node types")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this option
|
|
func (o *AutoAcceptOption) Type() string {
|
|
return "auto-accept"
|
|
}
|
|
|
|
// Policies returns a representation of this option for the api
|
|
func (o *AutoAcceptOption) Policies(secret *string) []swarm.Policy {
|
|
policies := []swarm.Policy{}
|
|
for _, p := range defaultPolicies {
|
|
if len(o.values) != 0 {
|
|
if _, ok := o.values[string(p.Role)]; ok {
|
|
p.Autoaccept = true
|
|
} else {
|
|
p.Autoaccept = false
|
|
}
|
|
}
|
|
p.Secret = secret
|
|
policies = append(policies, p)
|
|
}
|
|
return policies
|
|
}
|
|
|
|
// isPresent returns whether the key exists in the set or not
|
|
func (o *AutoAcceptOption) isPresent(key string) bool {
|
|
_, c := o.values[key]
|
|
return c
|
|
}
|
|
|
|
// NewAutoAcceptOption returns a new auto-accept option
|
|
func NewAutoAcceptOption() AutoAcceptOption {
|
|
return AutoAcceptOption{values: make(map[string]struct{})}
|
|
}
|
|
|
|
// ExternalCAOption is a Value type for parsing external CA specifications.
|
|
type ExternalCAOption struct {
|
|
values []*swarm.ExternalCA
|
|
}
|
|
|
|
// Set parses an external CA option.
|
|
func (m *ExternalCAOption) Set(value string) error {
|
|
parsed, err := parseExternalCA(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
m.values = append(m.values, parsed)
|
|
return nil
|
|
}
|
|
|
|
// Type returns the type of this option.
|
|
func (m *ExternalCAOption) Type() string {
|
|
return "external-ca"
|
|
}
|
|
|
|
// String returns a string repr of this option.
|
|
func (m *ExternalCAOption) String() string {
|
|
externalCAs := []string{}
|
|
for _, externalCA := range m.values {
|
|
repr := fmt.Sprintf("%s: %s", externalCA.Protocol, externalCA.URL)
|
|
externalCAs = append(externalCAs, repr)
|
|
}
|
|
return strings.Join(externalCAs, ", ")
|
|
}
|
|
|
|
// Value returns the external CAs
|
|
func (m *ExternalCAOption) Value() []*swarm.ExternalCA {
|
|
return m.values
|
|
}
|
|
|
|
// parseExternalCA parses an external CA specification from the command line,
|
|
// such as protocol=cfssl,url=https://example.com.
|
|
func parseExternalCA(caSpec string) (*swarm.ExternalCA, error) {
|
|
csvReader := csv.NewReader(strings.NewReader(caSpec))
|
|
fields, err := csvReader.Read()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
externalCA := swarm.ExternalCA{
|
|
Options: make(map[string]string),
|
|
}
|
|
|
|
var (
|
|
hasProtocol bool
|
|
hasURL bool
|
|
)
|
|
|
|
for _, field := range fields {
|
|
parts := strings.SplitN(field, "=", 2)
|
|
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("invalid field '%s' must be a key=value pair", field)
|
|
}
|
|
|
|
key, value := parts[0], parts[1]
|
|
|
|
switch strings.ToLower(key) {
|
|
case "protocol":
|
|
hasProtocol = true
|
|
if strings.ToLower(value) == string(swarm.ExternalCAProtocolCFSSL) {
|
|
externalCA.Protocol = swarm.ExternalCAProtocolCFSSL
|
|
} else {
|
|
return nil, fmt.Errorf("unrecognized external CA protocol %s", value)
|
|
}
|
|
case "url":
|
|
hasURL = true
|
|
externalCA.URL = value
|
|
default:
|
|
externalCA.Options[key] = value
|
|
}
|
|
}
|
|
|
|
if !hasProtocol {
|
|
return nil, errors.New("the external-ca option needs a protocol= parameter")
|
|
}
|
|
if !hasURL {
|
|
return nil, errors.New("the external-ca option needs a url= parameter")
|
|
}
|
|
|
|
return &externalCA, nil
|
|
}
|
|
|
|
func addSwarmFlags(flags *pflag.FlagSet, opts *swarmOptions) {
|
|
flags.Var(&opts.autoAccept, flagAutoAccept, "Auto acceptance policy (worker, manager or none)")
|
|
flags.StringVar(&opts.secret, flagSecret, "", "Set secret value needed to join a cluster")
|
|
flags.Int64Var(&opts.taskHistoryLimit, flagTaskHistoryLimit, 10, "Task history retention limit")
|
|
flags.DurationVar(&opts.dispatcherHeartbeat, flagDispatcherHeartbeat, time.Duration(5*time.Second), "Dispatcher heartbeat period")
|
|
flags.DurationVar(&opts.nodeCertExpiry, flagCertExpiry, time.Duration(90*24*time.Hour), "Validity period for node certificates")
|
|
flags.Var(&opts.externalCA, flagExternalCA, "Specifications of one or more certificate signing endpoints")
|
|
}
|
|
|
|
func (opts *swarmOptions) ToSpec() swarm.Spec {
|
|
spec := swarm.Spec{}
|
|
if opts.secret != "" {
|
|
spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(&opts.secret)
|
|
} else {
|
|
spec.AcceptancePolicy.Policies = opts.autoAccept.Policies(nil)
|
|
}
|
|
spec.Orchestration.TaskHistoryRetentionLimit = opts.taskHistoryLimit
|
|
spec.Dispatcher.HeartbeatPeriod = uint64(opts.dispatcherHeartbeat.Nanoseconds())
|
|
spec.CAConfig.NodeCertExpiry = opts.nodeCertExpiry
|
|
spec.CAConfig.ExternalCAs = opts.externalCA.Value()
|
|
return spec
|
|
}
|