podman

Форк
0
/
volumes.go 
678 строк · 20.3 Кб
1
package specgenutil
2

3
import (
4
	"errors"
5
	"fmt"
6
	"path"
7
	"path/filepath"
8
	"strings"
9

10
	"github.com/containers/common/pkg/config"
11
	"github.com/containers/common/pkg/parse"
12
	"github.com/containers/podman/v5/libpod/define"
13
	"github.com/containers/podman/v5/pkg/rootless"
14
	"github.com/containers/podman/v5/pkg/specgen"
15
	"github.com/containers/podman/v5/pkg/specgenutilexternal"
16
	"github.com/containers/podman/v5/pkg/util"
17
	spec "github.com/opencontainers/runtime-spec/specs-go"
18
)
19

20
var (
21
	errOptionArg = errors.New("must provide an argument for option")
22
	errNoDest    = errors.New("must set volume destination")
23
)
24

25
// Parse all volume-related options in the create config into a set of mounts
26
// and named volumes to add to the container.
27
// Handles --volumes, --mount, and --tmpfs flags.
28
// Does not handle image volumes, init, and --volumes-from flags.
29
// Can also add tmpfs mounts from read-only tmpfs.
30
// TODO: handle options parsing/processing via containers/storage/pkg/mount
31
func parseVolumes(rtc *config.Config, volumeFlag, mountFlag, tmpfsFlag []string) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, []*specgen.ImageVolume, error) {
32
	// Get mounts from the --mounts flag.
33
	// TODO: The runtime config part of this needs to move into pkg/specgen/generate to avoid querying containers.conf on the client.
34
	unifiedMounts, unifiedVolumes, unifiedImageVolumes, err := Mounts(mountFlag, rtc.Mounts())
35
	if err != nil {
36
		return nil, nil, nil, nil, err
37
	}
38

39
	// Next --volumes flag.
40
	volumeMounts, volumeVolumes, overlayVolumes, err := specgen.GenVolumeMounts(volumeFlag)
41
	if err != nil {
42
		return nil, nil, nil, nil, err
43
	}
44

45
	// Next --tmpfs flag.
46
	tmpfsMounts, err := getTmpfsMounts(tmpfsFlag)
47
	if err != nil {
48
		return nil, nil, nil, nil, err
49
	}
50

51
	// Unify mounts from --mount, --volume, --tmpfs.
52
	// Start with --volume.
53
	for dest, mount := range volumeMounts {
54
		if vol, ok := unifiedMounts[dest]; ok {
55
			if mount.Source == vol.Source &&
56
				specgen.StringSlicesEqual(vol.Options, mount.Options) {
57
				continue
58
			}
59
			return nil, nil, nil, nil, fmt.Errorf("%v: %w", dest, specgen.ErrDuplicateDest)
60
		}
61
		unifiedMounts[dest] = mount
62
	}
63
	for dest, volume := range volumeVolumes {
64
		if vol, ok := unifiedVolumes[dest]; ok {
65
			if volume.Name == vol.Name &&
66
				specgen.StringSlicesEqual(vol.Options, volume.Options) {
67
				continue
68
			}
69
			return nil, nil, nil, nil, fmt.Errorf("%v: %w", dest, specgen.ErrDuplicateDest)
70
		}
71
		unifiedVolumes[dest] = volume
72
	}
73
	// Now --tmpfs
74
	for dest, tmpfs := range tmpfsMounts {
75
		if vol, ok := unifiedMounts[dest]; ok {
76
			if vol.Type != define.TypeTmpfs {
77
				return nil, nil, nil, nil, fmt.Errorf("%v: %w", dest, specgen.ErrDuplicateDest)
78
			}
79
			continue
80
		}
81
		unifiedMounts[dest] = tmpfs
82
	}
83

84
	// Check for conflicts between named volumes, overlay & image volumes,
85
	// and mounts
86
	allMounts := make(map[string]bool)
87
	testAndSet := func(dest string) error {
88
		if _, ok := allMounts[dest]; ok {
89
			return fmt.Errorf("%v: %w", dest, specgen.ErrDuplicateDest)
90
		}
91
		allMounts[dest] = true
92
		return nil
93
	}
94
	for dest := range unifiedMounts {
95
		if err := testAndSet(dest); err != nil {
96
			return nil, nil, nil, nil, err
97
		}
98
	}
99
	for dest := range unifiedVolumes {
100
		if err := testAndSet(dest); err != nil {
101
			return nil, nil, nil, nil, err
102
		}
103
	}
104
	for dest := range overlayVolumes {
105
		if err := testAndSet(dest); err != nil {
106
			return nil, nil, nil, nil, err
107
		}
108
	}
109
	for dest := range unifiedImageVolumes {
110
		if err := testAndSet(dest); err != nil {
111
			return nil, nil, nil, nil, err
112
		}
113
	}
114

115
	// Final step: maps to arrays
116
	finalMounts := make([]spec.Mount, 0, len(unifiedMounts))
117
	for _, mount := range unifiedMounts {
118
		if mount.Type == define.TypeBind {
119
			absSrc, err := specgen.ConvertWinMountPath(mount.Source)
120
			if err != nil {
121
				return nil, nil, nil, nil, fmt.Errorf("getting absolute path of %s: %w", mount.Source, err)
122
			}
123
			mount.Source = absSrc
124
		}
125
		finalMounts = append(finalMounts, mount)
126
	}
127
	finalVolumes := make([]*specgen.NamedVolume, 0, len(unifiedVolumes))
128
	for _, volume := range unifiedVolumes {
129
		finalVolumes = append(finalVolumes, volume)
130
	}
131
	finalOverlayVolume := make([]*specgen.OverlayVolume, 0)
132
	for _, volume := range overlayVolumes {
133
		finalOverlayVolume = append(finalOverlayVolume, volume)
134
	}
135
	finalImageVolumes := make([]*specgen.ImageVolume, 0, len(unifiedImageVolumes))
136
	for _, volume := range unifiedImageVolumes {
137
		finalImageVolumes = append(finalImageVolumes, volume)
138
	}
139

140
	return finalMounts, finalVolumes, finalOverlayVolume, finalImageVolumes, nil
141
}
142

143
// Mounts takes user-provided input from the --mount flag as well as Mounts
144
// specified in containers.conf and creates OCI spec mounts and Libpod named volumes.
145
// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ...
146
// podman run --mount type=tmpfs,target=/dev/shm ...
147
// podman run --mount type=volume,source=test-volume, ...
148
func Mounts(mountFlag []string, configMounts []string) (map[string]spec.Mount, map[string]*specgen.NamedVolume, map[string]*specgen.ImageVolume, error) {
149
	finalMounts := make(map[string]spec.Mount)
150
	finalNamedVolumes := make(map[string]*specgen.NamedVolume)
151
	finalImageVolumes := make(map[string]*specgen.ImageVolume)
152
	parseMounts := func(mounts []string, ignoreDup bool) error {
153
		for _, mount := range mounts {
154
			// TODO: Docker defaults to "volume" if no mount type is specified.
155
			mountType, tokens, err := specgenutilexternal.FindMountType(mount)
156
			if err != nil {
157
				return err
158
			}
159
			switch mountType {
160
			case define.TypeBind:
161
				mount, err := getBindMount(tokens)
162
				if err != nil {
163
					return err
164
				}
165
				if _, ok := finalMounts[mount.Destination]; ok {
166
					if ignoreDup {
167
						continue
168
					}
169
					return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
170
				}
171
				finalMounts[mount.Destination] = mount
172
			case "glob":
173
				mounts, err := getGlobMounts(tokens)
174
				if err != nil {
175
					return err
176
				}
177
				for _, mount := range mounts {
178
					if _, ok := finalMounts[mount.Destination]; ok {
179
						if ignoreDup {
180
							continue
181
						}
182
						return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
183
					}
184
					finalMounts[mount.Destination] = mount
185
				}
186
			case define.TypeTmpfs, define.TypeRamfs:
187
				mount, err := parseMemoryMount(tokens, mountType)
188
				if err != nil {
189
					return err
190
				}
191
				if _, ok := finalMounts[mount.Destination]; ok {
192
					if ignoreDup {
193
						continue
194
					}
195
					return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
196
				}
197
				finalMounts[mount.Destination] = mount
198
			case define.TypeDevpts:
199
				mount, err := getDevptsMount(tokens)
200
				if err != nil {
201
					return err
202
				}
203
				if _, ok := finalMounts[mount.Destination]; ok {
204
					if ignoreDup {
205
						continue
206
					}
207
					return fmt.Errorf("%v: %w", mount.Destination, specgen.ErrDuplicateDest)
208
				}
209
				finalMounts[mount.Destination] = mount
210
			case "image":
211
				volume, err := getImageVolume(tokens)
212
				if err != nil {
213
					return err
214
				}
215
				if _, ok := finalImageVolumes[volume.Destination]; ok {
216
					if ignoreDup {
217
						continue
218
					}
219
					return fmt.Errorf("%v: %w", volume.Destination, specgen.ErrDuplicateDest)
220
				}
221
				finalImageVolumes[volume.Destination] = volume
222
			case "volume":
223
				volume, err := getNamedVolume(tokens)
224
				if err != nil {
225
					return err
226
				}
227
				if _, ok := finalNamedVolumes[volume.Dest]; ok {
228
					if ignoreDup {
229
						continue
230
					}
231
					return fmt.Errorf("%v: %w", volume.Dest, specgen.ErrDuplicateDest)
232
				}
233
				finalNamedVolumes[volume.Dest] = volume
234
			default:
235
				return fmt.Errorf("invalid filesystem type %q", mountType)
236
			}
237
		}
238
		return nil
239
	}
240

241
	// Parse mounts passed in from the user
242
	if err := parseMounts(mountFlag, false); err != nil {
243
		return nil, nil, nil, err
244
	}
245

246
	// If user specified a mount flag that conflicts with a containers.conf flag, then ignore
247
	// the duplicate. This means that the parsing of the containers.conf configMounts should always
248
	// happen second.
249
	if err := parseMounts(configMounts, true); err != nil {
250
		return nil, nil, nil, fmt.Errorf("parsing containers.conf mounts: %w", err)
251
	}
252

253
	return finalMounts, finalNamedVolumes, finalImageVolumes, nil
254
}
255

256
func parseMountOptions(mountType string, args []string) (*spec.Mount, error) {
257
	var setTmpcopyup, setRORW, setSuid, setDev, setExec, setRelabel, setOwnership, setSwap bool
258

259
	mnt := spec.Mount{}
260
	for _, arg := range args {
261
		name, value, hasValue := strings.Cut(arg, "=")
262
		switch name {
263
		case "bind-nonrecursive":
264
			if mountType != define.TypeBind {
265
				return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType)
266
			}
267
			mnt.Options = append(mnt.Options, define.TypeBind)
268
		case "bind-propagation":
269
			if mountType != define.TypeBind {
270
				return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType)
271
			}
272
			if !hasValue {
273
				return nil, fmt.Errorf("%v: %w", name, errOptionArg)
274
			}
275
			mnt.Options = append(mnt.Options, value)
276
		case "consistency":
277
			// Often used on MACs and mistakenly on Linux platforms.
278
			// Since Docker ignores this option so shall we.
279
			continue
280
		case "idmap":
281
			if hasValue {
282
				mnt.Options = append(mnt.Options, fmt.Sprintf("idmap=%s", value))
283
			} else {
284
				mnt.Options = append(mnt.Options, "idmap")
285
			}
286
		case "readonly", "ro", "rw":
287
			if setRORW {
288
				return nil, fmt.Errorf("cannot pass 'readonly', 'ro', or 'rw' mnt.Options more than once: %w", errOptionArg)
289
			}
290
			setRORW = true
291
			// Can be formatted as one of:
292
			// readonly
293
			// readonly=[true|false]
294
			// ro
295
			// ro=[true|false]
296
			// rw
297
			// rw=[true|false]
298
			if name == "readonly" {
299
				name = "ro"
300
			}
301
			if hasValue {
302
				switch strings.ToLower(value) {
303
				case "true":
304
					mnt.Options = append(mnt.Options, name)
305
				case "false":
306
					// Set the opposite only for rw
307
					// ro's opposite is the default
308
					if name == "rw" {
309
						mnt.Options = append(mnt.Options, "ro")
310
					}
311
				}
312
			} else {
313
				mnt.Options = append(mnt.Options, name)
314
			}
315
		case "nodev", "dev":
316
			if setDev {
317
				return nil, fmt.Errorf("cannot pass 'nodev' and 'dev' mnt.Options more than once: %w", errOptionArg)
318
			}
319
			setDev = true
320
			mnt.Options = append(mnt.Options, name)
321
		case "noexec", "exec":
322
			if setExec {
323
				return nil, fmt.Errorf("cannot pass 'noexec' and 'exec' mnt.Options more than once: %w", errOptionArg)
324
			}
325
			setExec = true
326
			mnt.Options = append(mnt.Options, name)
327
		case "nosuid", "suid":
328
			if setSuid {
329
				return nil, fmt.Errorf("cannot pass 'nosuid' and 'suid' mnt.Options more than once: %w", errOptionArg)
330
			}
331
			setSuid = true
332
			mnt.Options = append(mnt.Options, name)
333
		case "noswap":
334
			if setSwap {
335
				return nil, fmt.Errorf("cannot pass 'noswap' mnt.Options more than once: %w", errOptionArg)
336
			}
337
			if rootless.IsRootless() {
338
				return nil, fmt.Errorf("the 'noswap' option is only allowed with rootful tmpfs mounts: %w", errOptionArg)
339
			}
340
			setSwap = true
341
			mnt.Options = append(mnt.Options, name)
342
		case "relabel":
343
			if setRelabel {
344
				return nil, fmt.Errorf("cannot pass 'relabel' option more than once: %w", errOptionArg)
345
			}
346
			setRelabel = true
347
			if !hasValue {
348
				return nil, fmt.Errorf("%s mount option must be 'private' or 'shared': %w", name, util.ErrBadMntOption)
349
			}
350
			switch value {
351
			case "private":
352
				mnt.Options = append(mnt.Options, "Z")
353
			case "shared":
354
				mnt.Options = append(mnt.Options, "z")
355
			default:
356
				return nil, fmt.Errorf("%s mount option must be 'private' or 'shared': %w", name, util.ErrBadMntOption)
357
			}
358
		case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable", "Z", "z", "no-dereference":
359
			mnt.Options = append(mnt.Options, name)
360
		case "src", "source":
361
			if mountType == define.TypeTmpfs {
362
				return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType)
363
			}
364
			if mnt.Source != "" {
365
				return nil, fmt.Errorf("cannot pass %q option more than once: %w", name, errOptionArg)
366
			}
367
			if !hasValue {
368
				return nil, fmt.Errorf("%v: %w", name, errOptionArg)
369
			}
370
			if len(value) == 0 {
371
				return nil, fmt.Errorf("host directory cannot be empty: %w", errOptionArg)
372
			}
373
			mnt.Source = value
374
		case "target", "dst", "destination":
375
			if mnt.Destination != "" {
376
				return nil, fmt.Errorf("cannot pass %q option more than once: %w", name, errOptionArg)
377
			}
378
			if !hasValue {
379
				return nil, fmt.Errorf("%v: %w", name, errOptionArg)
380
			}
381
			if err := parse.ValidateVolumeCtrDir(value); err != nil {
382
				return nil, err
383
			}
384
			mnt.Destination = unixPathClean(value)
385
		case "tmpcopyup", "notmpcopyup":
386
			if mountType != define.TypeTmpfs {
387
				return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType)
388
			}
389
			if setTmpcopyup {
390
				return nil, fmt.Errorf("cannot pass 'tmpcopyup' and 'notmpcopyup' mnt.Options more than once: %w", errOptionArg)
391
			}
392
			setTmpcopyup = true
393
			mnt.Options = append(mnt.Options, name)
394
		case "tmpfs-mode":
395
			if mountType != define.TypeTmpfs {
396
				return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType)
397
			}
398
			if !hasValue {
399
				return nil, fmt.Errorf("%v: %w", name, errOptionArg)
400
			}
401
			mnt.Options = append(mnt.Options, fmt.Sprintf("mode=%s", value))
402
		case "tmpfs-size":
403
			if mountType != define.TypeTmpfs {
404
				return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType)
405
			}
406
			if !hasValue {
407
				return nil, fmt.Errorf("%v: %w", name, errOptionArg)
408
			}
409
			mnt.Options = append(mnt.Options, fmt.Sprintf("size=%s", value))
410
		case "U", "chown":
411
			if setOwnership {
412
				return nil, fmt.Errorf("cannot pass 'U' or 'chown' option more than once: %w", errOptionArg)
413
			}
414
			ok, err := validChownFlag(value)
415
			if err != nil {
416
				return nil, err
417
			}
418
			if ok {
419
				mnt.Options = append(mnt.Options, "U")
420
			}
421
			setOwnership = true
422
		case "volume-label":
423
			if mountType != define.TypeVolume {
424
				return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType)
425
			}
426
			return nil, fmt.Errorf("the --volume-label option is not presently implemented")
427
		case "volume-opt":
428
			if mountType != define.TypeVolume {
429
				return nil, fmt.Errorf("%q option not supported for %q mount types", name, mountType)
430
			}
431
			mnt.Options = append(mnt.Options, arg)
432
		default:
433
			return nil, fmt.Errorf("%s: %w", name, util.ErrBadMntOption)
434
		}
435
	}
436
	if mountType != "glob" && len(mnt.Destination) == 0 {
437
		return nil, errNoDest
438
	}
439
	return &mnt, nil
440
}
441

442
// Parse glob mounts entry from the --mount flag.
443
func getGlobMounts(args []string) ([]spec.Mount, error) {
444
	mounts := []spec.Mount{}
445

446
	mnt, err := parseMountOptions("glob", args)
447
	if err != nil {
448
		return nil, err
449
	}
450

451
	globs, err := filepath.Glob(mnt.Source)
452
	if err != nil {
453
		return nil, err
454
	}
455
	if len(globs) == 0 {
456
		return nil, fmt.Errorf("no file paths matching glob %q", mnt.Source)
457
	}
458

459
	options, err := parse.ValidateVolumeOpts(mnt.Options)
460
	if err != nil {
461
		return nil, err
462
	}
463
	for _, src := range globs {
464
		var newMount spec.Mount
465
		newMount.Type = define.TypeBind
466
		newMount.Options = options
467
		newMount.Source = src
468
		if len(mnt.Destination) == 0 {
469
			newMount.Destination = src
470
		} else {
471
			newMount.Destination = filepath.Join(mnt.Destination, filepath.Base(src))
472
		}
473
		mounts = append(mounts, newMount)
474
	}
475

476
	return mounts, nil
477
}
478

479
// Parse a single bind mount entry from the --mount flag.
480
func getBindMount(args []string) (spec.Mount, error) {
481
	newMount := spec.Mount{
482
		Type: define.TypeBind,
483
	}
484
	var err error
485
	mnt, err := parseMountOptions(newMount.Type, args)
486
	if err != nil {
487
		return newMount, err
488
	}
489

490
	if len(mnt.Destination) == 0 {
491
		return newMount, errNoDest
492
	}
493

494
	if len(mnt.Source) == 0 {
495
		mnt.Source = mnt.Destination
496
	}
497

498
	options, err := parse.ValidateVolumeOpts(mnt.Options)
499
	if err != nil {
500
		return newMount, err
501
	}
502
	newMount.Source = mnt.Source
503
	newMount.Destination = mnt.Destination
504
	newMount.Options = options
505
	return newMount, nil
506
}
507

508
// Parse a single tmpfs/ramfs mount entry from the --mount flag
509
func parseMemoryMount(args []string, mountType string) (spec.Mount, error) {
510
	newMount := spec.Mount{
511
		Type:   mountType,
512
		Source: mountType,
513
	}
514

515
	var err error
516
	mnt, err := parseMountOptions(newMount.Type, args)
517
	if err != nil {
518
		return newMount, err
519
	}
520
	if len(mnt.Destination) == 0 {
521
		return newMount, errNoDest
522
	}
523
	newMount.Destination = mnt.Destination
524
	newMount.Options = mnt.Options
525
	return newMount, nil
526
}
527

528
// Parse a single devpts mount entry from the --mount flag
529
func getDevptsMount(args []string) (spec.Mount, error) {
530
	newMount := spec.Mount{
531
		Type:   define.TypeDevpts,
532
		Source: define.TypeDevpts,
533
	}
534

535
	var setDest bool
536

537
	for _, arg := range args {
538
		name, value, hasValue := strings.Cut(arg, "=")
539
		switch name {
540
		case "uid", "gid", "mode", "ptxmode", "newinstance", "max":
541
			newMount.Options = append(newMount.Options, arg)
542
		case "target", "dst", "destination":
543
			if !hasValue {
544
				return newMount, fmt.Errorf("%v: %w", name, errOptionArg)
545
			}
546
			if err := parse.ValidateVolumeCtrDir(value); err != nil {
547
				return newMount, err
548
			}
549
			newMount.Destination = unixPathClean(value)
550
			setDest = true
551
		default:
552
			return newMount, fmt.Errorf("%s: %w", name, util.ErrBadMntOption)
553
		}
554
	}
555

556
	if !setDest {
557
		return newMount, errNoDest
558
	}
559

560
	return newMount, nil
561
}
562

563
// Parse a single volume mount entry from the --mount flag.
564
// Note that the volume-label option for named volumes is currently NOT supported.
565
// TODO: add support for --volume-label
566
func getNamedVolume(args []string) (*specgen.NamedVolume, error) {
567
	newVolume := new(specgen.NamedVolume)
568

569
	mnt, err := parseMountOptions(define.TypeVolume, args)
570
	if err != nil {
571
		return nil, err
572
	}
573
	if len(mnt.Destination) == 0 {
574
		return nil, errNoDest
575
	}
576
	newVolume.Options = mnt.Options
577
	newVolume.Name = mnt.Source
578
	newVolume.Dest = mnt.Destination
579
	return newVolume, nil
580
}
581

582
// Parse the arguments into an image volume. An image volume is a volume based
583
// on a container image.  The container image is first mounted on the host and
584
// is then bind-mounted into the container.  An ImageVolume is always mounted
585
// read-only.
586
func getImageVolume(args []string) (*specgen.ImageVolume, error) {
587
	newVolume := new(specgen.ImageVolume)
588

589
	for _, arg := range args {
590
		name, value, hasValue := strings.Cut(arg, "=")
591
		switch name {
592
		case "src", "source":
593
			if !hasValue {
594
				return nil, fmt.Errorf("%v: %w", name, errOptionArg)
595
			}
596
			newVolume.Source = value
597
		case "target", "dst", "destination":
598
			if !hasValue {
599
				return nil, fmt.Errorf("%v: %w", name, errOptionArg)
600
			}
601
			if err := parse.ValidateVolumeCtrDir(value); err != nil {
602
				return nil, err
603
			}
604
			newVolume.Destination = unixPathClean(value)
605
		case "rw", "readwrite":
606
			switch value {
607
			case "true":
608
				newVolume.ReadWrite = true
609
			case "false":
610
				// Nothing to do. RO is default.
611
			default:
612
				return nil, fmt.Errorf("invalid rw value %q: %w", value, util.ErrBadMntOption)
613
			}
614
		case "consistency":
615
			// Often used on MACs and mistakenly on Linux platforms.
616
			// Since Docker ignores this option so shall we.
617
			continue
618
		default:
619
			return nil, fmt.Errorf("%s: %w", name, util.ErrBadMntOption)
620
		}
621
	}
622

623
	if len(newVolume.Source)*len(newVolume.Destination) == 0 {
624
		return nil, errors.New("must set source and destination for image volume")
625
	}
626

627
	return newVolume, nil
628
}
629

630
// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
631
func getTmpfsMounts(tmpfsFlag []string) (map[string]spec.Mount, error) {
632
	m := make(map[string]spec.Mount)
633
	for _, i := range tmpfsFlag {
634
		// Default options if nothing passed
635
		var options []string
636
		spliti := strings.Split(i, ":")
637
		destPath := spliti[0]
638
		if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil {
639
			return nil, err
640
		}
641
		if len(spliti) > 1 {
642
			options = strings.Split(spliti[1], ",")
643
		}
644

645
		if vol, ok := m[destPath]; ok {
646
			if specgen.StringSlicesEqual(vol.Options, options) {
647
				continue
648
			}
649
			return nil, fmt.Errorf("%v: %w", destPath, specgen.ErrDuplicateDest)
650
		}
651
		mount := spec.Mount{
652
			Destination: unixPathClean(destPath),
653
			Type:        define.TypeTmpfs,
654
			Options:     options,
655
			Source:      define.TypeTmpfs,
656
		}
657
		m[destPath] = mount
658
	}
659
	return m, nil
660
}
661

662
// validChownFlag ensures that the U or chown flag is correctly used
663
func validChownFlag(value string) (bool, error) {
664
	// U=[true|false]
665
	switch {
666
	case strings.EqualFold(value, "true"), value == "":
667
		return true, nil
668
	case strings.EqualFold(value, "false"):
669
		return false, nil
670
	default:
671
		return false, fmt.Errorf("'U' or 'chown' must be set to true or false, instead received %q: %w", value, errOptionArg)
672
	}
673
}
674

675
// Use path instead of filepath to preserve Unix style paths on Windows
676
func unixPathClean(p string) string {
677
	return path.Clean(p)
678
}
679

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.