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 }