1 package opts
2
3 import (
4 "fmt"
5 "math/big"
6 "net"
7 "path"
8 "regexp"
9 "strings"
10
11 "github.com/docker/docker/api/types/filters"
12 units "github.com/docker/go-units"
13 "github.com/pkg/errors"
14 )
15
16 var (
17 alphaRegexp = regexp.MustCompile(`[a-zA-Z]`)
18 domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
19 )
20
21
22 type ListOpts struct {
23 values *[]string
24 validator ValidatorFctType
25 }
26
27
28 func NewListOpts(validator ValidatorFctType) ListOpts {
29 var values []string
30 return *NewListOptsRef(&values, validator)
31 }
32
33
34 func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts {
35 return &ListOpts{
36 values: values,
37 validator: validator,
38 }
39 }
40
41 func (opts *ListOpts) String() string {
42 if len(*opts.values) == 0 {
43 return ""
44 }
45 return fmt.Sprintf("%v", *opts.values)
46 }
47
48
49
50 func (opts *ListOpts) Set(value string) error {
51 if opts.validator != nil {
52 v, err := opts.validator(value)
53 if err != nil {
54 return err
55 }
56 value = v
57 }
58 *opts.values = append(*opts.values, value)
59 return nil
60 }
61
62
63 func (opts *ListOpts) Delete(key string) {
64 for i, k := range *opts.values {
65 if k == key {
66 *opts.values = append((*opts.values)[:i], (*opts.values)[i+1:]...)
67 return
68 }
69 }
70 }
71
72
73
74 func (opts *ListOpts) GetMap() map[string]struct{} {
75 ret := make(map[string]struct{})
76 for _, k := range *opts.values {
77 ret[k] = struct{}{}
78 }
79 return ret
80 }
81
82
83 func (opts *ListOpts) GetAll() []string {
84 return *opts.values
85 }
86
87
88
89 func (opts *ListOpts) GetAllOrEmpty() []string {
90 v := *opts.values
91 if v == nil {
92 return make([]string, 0)
93 }
94 return v
95 }
96
97
98 func (opts *ListOpts) Get(key string) bool {
99 for _, k := range *opts.values {
100 if k == key {
101 return true
102 }
103 }
104 return false
105 }
106
107
108 func (opts *ListOpts) Len() int {
109 return len(*opts.values)
110 }
111
112
113 func (opts *ListOpts) Type() string {
114 return "list"
115 }
116
117
118 func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts {
119 opts.validator = validator
120 return opts
121 }
122
123
124
125 type NamedOption interface {
126 Name() string
127 }
128
129
130
131
132 type NamedListOpts struct {
133 name string
134 ListOpts
135 }
136
137 var _ NamedOption = &NamedListOpts{}
138
139
140 func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts {
141 return &NamedListOpts{
142 name: name,
143 ListOpts: *NewListOptsRef(values, validator),
144 }
145 }
146
147
148 func (o *NamedListOpts) Name() string {
149 return o.name
150 }
151
152
153 type MapOpts struct {
154 values map[string]string
155 validator ValidatorFctType
156 }
157
158
159
160 func (opts *MapOpts) Set(value string) error {
161 if opts.validator != nil {
162 v, err := opts.validator(value)
163 if err != nil {
164 return err
165 }
166 value = v
167 }
168 k, v, _ := strings.Cut(value, "=")
169 opts.values[k] = v
170 return nil
171 }
172
173
174 func (opts *MapOpts) GetAll() map[string]string {
175 return opts.values
176 }
177
178 func (opts *MapOpts) String() string {
179 return fmt.Sprintf("%v", opts.values)
180 }
181
182
183 func (opts *MapOpts) Type() string {
184 return "map"
185 }
186
187
188 func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
189 if values == nil {
190 values = make(map[string]string)
191 }
192 return &MapOpts{
193 values: values,
194 validator: validator,
195 }
196 }
197
198
199
200
201 type NamedMapOpts struct {
202 name string
203 MapOpts
204 }
205
206 var _ NamedOption = &NamedMapOpts{}
207
208
209 func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts {
210 return &NamedMapOpts{
211 name: name,
212 MapOpts: *NewMapOpts(values, validator),
213 }
214 }
215
216
217 func (o *NamedMapOpts) Name() string {
218 return o.name
219 }
220
221
222 type ValidatorFctType func(val string) (string, error)
223
224
225 type ValidatorFctListType func(val string) ([]string, error)
226
227
228
229
230
231
232
233 func ValidateIPAddress(val string) (string, error) {
234 if ip := net.ParseIP(strings.TrimSpace(val)); ip != nil {
235 return ip.String(), nil
236 }
237 return "", fmt.Errorf("IP address is not correctly formatted: %s", val)
238 }
239
240
241 func ValidateMACAddress(val string) (string, error) {
242 _, err := net.ParseMAC(strings.TrimSpace(val))
243 if err != nil {
244 return "", err
245 }
246 return val, nil
247 }
248
249
250
251 func ValidateDNSSearch(val string) (string, error) {
252 if val = strings.Trim(val, " "); val == "." {
253 return val, nil
254 }
255 return validateDomain(val)
256 }
257
258 func validateDomain(val string) (string, error) {
259 if alphaRegexp.FindString(val) == "" {
260 return "", fmt.Errorf("%s is not a valid domain", val)
261 }
262 ns := domainRegexp.FindSubmatch([]byte(val))
263 if len(ns) > 0 && len(ns[1]) < 255 {
264 return string(ns[1]), nil
265 }
266 return "", fmt.Errorf("%s is not a valid domain", val)
267 }
268
269
270
271
272
273
274
275
276
277
278
279
280 func ValidateLabel(value string) (string, error) {
281 key, _, _ := strings.Cut(value, "=")
282 key = strings.TrimLeft(key, whiteSpaces)
283 if key == "" {
284 return "", fmt.Errorf("invalid label '%s': empty name", value)
285 }
286 if strings.ContainsAny(key, whiteSpaces) {
287 return "", fmt.Errorf("label '%s' contains whitespaces", key)
288 }
289 return value, nil
290 }
291
292
293 func ValidateSysctl(val string) (string, error) {
294 validSysctlMap := map[string]bool{
295 "kernel.msgmax": true,
296 "kernel.msgmnb": true,
297 "kernel.msgmni": true,
298 "kernel.sem": true,
299 "kernel.shmall": true,
300 "kernel.shmmax": true,
301 "kernel.shmmni": true,
302 "kernel.shm_rmid_forced": true,
303 }
304 validSysctlPrefixes := []string{
305 "net.",
306 "fs.mqueue.",
307 }
308 k, _, ok := strings.Cut(val, "=")
309 if !ok || k == "" {
310 return "", fmt.Errorf("sysctl '%s' is not allowed", val)
311 }
312 if validSysctlMap[k] {
313 return val, nil
314 }
315 for _, vp := range validSysctlPrefixes {
316 if strings.HasPrefix(k, vp) {
317 return val, nil
318 }
319 }
320 return "", fmt.Errorf("sysctl '%s' is not allowed", val)
321 }
322
323
324 type FilterOpt struct {
325 filter filters.Args
326 }
327
328
329 func NewFilterOpt() FilterOpt {
330 return FilterOpt{filter: filters.NewArgs()}
331 }
332
333 func (o *FilterOpt) String() string {
334 repr, err := filters.ToJSON(o.filter)
335 if err != nil {
336 return "invalid filters"
337 }
338 return repr
339 }
340
341
342 func (o *FilterOpt) Set(value string) error {
343 if value == "" {
344 return nil
345 }
346 if !strings.Contains(value, "=") {
347 return errors.New("bad format of filter (expected name=value)")
348 }
349 name, val, _ := strings.Cut(value, "=")
350
351
352 name = strings.ToLower(strings.TrimSpace(name))
353 val = strings.TrimSpace(val)
354 o.filter.Add(name, val)
355 return nil
356 }
357
358
359 func (o *FilterOpt) Type() string {
360 return "filter"
361 }
362
363
364 func (o *FilterOpt) Value() filters.Args {
365 return o.filter
366 }
367
368
369 type NanoCPUs int64
370
371
372 func (c *NanoCPUs) String() string {
373 if *c == 0 {
374 return ""
375 }
376 return big.NewRat(c.Value(), 1e9).FloatString(3)
377 }
378
379
380 func (c *NanoCPUs) Set(value string) error {
381 cpus, err := ParseCPUs(value)
382 *c = NanoCPUs(cpus)
383 return err
384 }
385
386
387 func (c *NanoCPUs) Type() string {
388 return "decimal"
389 }
390
391
392 func (c *NanoCPUs) Value() int64 {
393 return int64(*c)
394 }
395
396
397 func ParseCPUs(value string) (int64, error) {
398 cpu, ok := new(big.Rat).SetString(value)
399 if !ok {
400 return 0, fmt.Errorf("failed to parse %v as a rational number", value)
401 }
402 nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
403 if !nano.IsInt() {
404 return 0, fmt.Errorf("value is too precise")
405 }
406 return nano.Num().Int64(), nil
407 }
408
409
410 func ParseLink(val string) (string, string, error) {
411 if val == "" {
412 return "", "", fmt.Errorf("empty string specified for links")
413 }
414
415 arr := strings.SplitN(val, ":", 3)
416
417
418 if len(arr) > 2 {
419 return "", "", fmt.Errorf("bad format for links: %s", val)
420 }
421
422 if len(arr) == 1 {
423 return val, val, nil
424 }
425
426
427
428 if strings.HasPrefix(arr[0], "/") {
429
430 _, alias := path.Split(arr[1])
431 return arr[0][1:], alias, nil
432 }
433 return arr[0], arr[1], nil
434 }
435
436
437 func ValidateLink(val string) (string, error) {
438 _, _, err := ParseLink(val)
439 return val, err
440 }
441
442
443 type MemBytes int64
444
445
446 func (m *MemBytes) String() string {
447
448
449
450 if m.Value() != 0 {
451 return units.BytesSize(float64(m.Value()))
452 }
453 return "0"
454 }
455
456
457 func (m *MemBytes) Set(value string) error {
458 val, err := units.RAMInBytes(value)
459 *m = MemBytes(val)
460 return err
461 }
462
463
464 func (m *MemBytes) Type() string {
465 return "bytes"
466 }
467
468
469 func (m *MemBytes) Value() int64 {
470 return int64(*m)
471 }
472
473
474 func (m *MemBytes) UnmarshalJSON(s []byte) error {
475 if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' {
476 return fmt.Errorf("invalid size: %q", s)
477 }
478 val, err := units.RAMInBytes(string(s[1 : len(s)-1]))
479 *m = MemBytes(val)
480 return err
481 }
482
483
484
485 type MemSwapBytes int64
486
487
488 func (m *MemSwapBytes) Set(value string) error {
489 if value == "-1" {
490 *m = MemSwapBytes(-1)
491 return nil
492 }
493 val, err := units.RAMInBytes(value)
494 *m = MemSwapBytes(val)
495 return err
496 }
497
498
499 func (m *MemSwapBytes) Type() string {
500 return "bytes"
501 }
502
503
504 func (m *MemSwapBytes) Value() int64 {
505 return int64(*m)
506 }
507
508 func (m *MemSwapBytes) String() string {
509 b := MemBytes(*m)
510 return b.String()
511 }
512
513
514 func (m *MemSwapBytes) UnmarshalJSON(s []byte) error {
515 b := MemBytes(*m)
516 return b.UnmarshalJSON(s)
517 }
518
View as plain text