podman

Форк
0
/
storage.go 
474 строки · 14.8 Кб
1
//go:build !remote
2

3
package generate
4

5
import (
6
	"context"
7
	"errors"
8
	"fmt"
9
	"io/fs"
10
	"path"
11
	"path/filepath"
12
	"strings"
13

14
	"github.com/containers/common/libimage"
15
	"github.com/containers/common/pkg/config"
16
	"github.com/containers/common/pkg/parse"
17
	"github.com/containers/podman/v5/libpod"
18
	"github.com/containers/podman/v5/libpod/define"
19
	"github.com/containers/podman/v5/pkg/specgen"
20
	"github.com/containers/podman/v5/pkg/util"
21
	"github.com/containers/storage/pkg/fileutils"
22
	spec "github.com/opencontainers/runtime-spec/specs-go"
23
	"github.com/sirupsen/logrus"
24
)
25

26
// Produce final mounts and named volumes for a container
27
func finalizeMounts(ctx context.Context, s *specgen.SpecGenerator, rt *libpod.Runtime, rtc *config.Config, img *libimage.Image) ([]spec.Mount, []*specgen.NamedVolume, []*specgen.OverlayVolume, error) {
28
	// Get image volumes
29
	baseMounts, baseVolumes, err := getImageVolumes(ctx, img, s)
30
	if err != nil {
31
		return nil, nil, nil, err
32
	}
33

34
	// Get volumes-from mounts
35
	volFromMounts, volFromVolumes, err := getVolumesFrom(s.VolumesFrom, rt)
36
	if err != nil {
37
		return nil, nil, nil, err
38
	}
39

40
	// Supersede from --volumes-from.
41
	for dest, mount := range volFromMounts {
42
		baseMounts[dest] = mount
43

44
		// Necessary to ensure that mounts override image volumes
45
		// Ref: https://github.com/containers/podman/issues/19529
46
		delete(baseVolumes, dest)
47
	}
48
	for dest, volume := range volFromVolumes {
49
		baseVolumes[dest] = volume
50

51
		// I don't think this can happen, but best to be safe.
52
		delete(baseMounts, dest)
53
	}
54

55
	// Need to make map forms of specgen mounts/volumes.
56
	unifiedMounts := map[string]spec.Mount{}
57
	unifiedVolumes := map[string]*specgen.NamedVolume{}
58
	unifiedOverlays := map[string]*specgen.OverlayVolume{}
59

60
	// Need to make map forms of specgen mounts/volumes.
61
	commonMounts, commonVolumes, commonOverlayVolumes, err := specgen.GenVolumeMounts(rtc.Volumes())
62
	if err != nil {
63
		return nil, nil, nil, err
64
	}
65

66
	for _, m := range s.Mounts {
67
		// Ensure that mount dest is clean, so that it can be
68
		// compared against named volumes and avoid duplicate mounts.
69
		if err = parse.ValidateVolumeCtrDir(m.Destination); err != nil {
70
			return nil, nil, nil, err
71
		}
72
		cleanDestination := filepath.Clean(m.Destination)
73
		if _, ok := unifiedMounts[cleanDestination]; ok {
74
			return nil, nil, nil, fmt.Errorf("%q: %w", cleanDestination, specgen.ErrDuplicateDest)
75
		}
76
		unifiedMounts[cleanDestination] = m
77
	}
78

79
	for _, m := range commonMounts {
80
		if err = parse.ValidateVolumeCtrDir(m.Destination); err != nil {
81
			return nil, nil, nil, err
82
		}
83
		cleanDestination := filepath.Clean(m.Destination)
84
		if _, ok := unifiedMounts[cleanDestination]; !ok {
85
			unifiedMounts[cleanDestination] = m
86
		}
87
	}
88

89
	for _, v := range s.Volumes {
90
		if err = parse.ValidateVolumeCtrDir(v.Dest); err != nil {
91
			return nil, nil, nil, err
92
		}
93
		cleanDestination := filepath.Clean(v.Dest)
94
		if _, ok := unifiedVolumes[cleanDestination]; ok {
95
			return nil, nil, nil, fmt.Errorf("conflict in specified volumes - multiple volumes at %q: %w", cleanDestination, specgen.ErrDuplicateDest)
96
		}
97
		unifiedVolumes[cleanDestination] = v
98
	}
99

100
	for _, v := range commonVolumes {
101
		if err = parse.ValidateVolumeCtrDir(v.Dest); err != nil {
102
			return nil, nil, nil, err
103
		}
104
		cleanDestination := filepath.Clean(v.Dest)
105
		if _, ok := unifiedVolumes[cleanDestination]; !ok {
106
			unifiedVolumes[cleanDestination] = v
107
		}
108
	}
109

110
	for _, v := range s.OverlayVolumes {
111
		if err = parse.ValidateVolumeCtrDir(v.Destination); err != nil {
112
			return nil, nil, nil, err
113
		}
114
		cleanDestination := filepath.Clean(v.Destination)
115
		if _, ok := unifiedOverlays[cleanDestination]; ok {
116
			return nil, nil, nil, fmt.Errorf("conflict in specified volumes - multiple volumes at %q: %w", cleanDestination, specgen.ErrDuplicateDest)
117
		}
118
		unifiedOverlays[cleanDestination] = v
119
	}
120

121
	for _, v := range commonOverlayVolumes {
122
		if err = parse.ValidateVolumeCtrDir(v.Destination); err != nil {
123
			return nil, nil, nil, err
124
		}
125
		cleanDestination := filepath.Clean(v.Destination)
126
		if _, ok := unifiedOverlays[cleanDestination]; !ok {
127
			unifiedOverlays[cleanDestination] = v
128
		}
129
	}
130

131
	// If requested, add container init binary
132
	if s.Init != nil && *s.Init {
133
		initPath := s.InitPath
134
		if initPath == "" {
135
			initPath, err = rtc.FindInitBinary()
136
			if err != nil {
137
				return nil, nil, nil, fmt.Errorf("lookup init binary: %w", err)
138
			}
139
		}
140
		initMount, err := addContainerInitBinary(s, initPath)
141
		if err != nil {
142
			return nil, nil, nil, err
143
		}
144
		if _, ok := unifiedMounts[initMount.Destination]; ok {
145
			return nil, nil, nil, fmt.Errorf("conflict with mount added by --init to %q: %w", initMount.Destination, specgen.ErrDuplicateDest)
146
		}
147
		unifiedMounts[initMount.Destination] = initMount
148
	}
149

150
	// Before superseding, we need to find volume mounts which conflict with
151
	// named volumes, and vice versa.
152
	// We'll delete the conflicts here as we supersede.
153
	for dest := range unifiedMounts {
154
		delete(baseVolumes, dest)
155
	}
156
	for dest := range unifiedVolumes {
157
		delete(baseMounts, dest)
158
	}
159

160
	// Supersede volumes-from/image volumes with unified volumes from above.
161
	// This is an unconditional replacement.
162
	for dest, mount := range unifiedMounts {
163
		baseMounts[dest] = mount
164
	}
165
	for dest, volume := range unifiedVolumes {
166
		baseVolumes[dest] = volume
167
	}
168

169
	// TODO: Investigate moving readonlyTmpfs into here. Would be more
170
	// correct.
171

172
	// Check for conflicts between named volumes and mounts
173
	for dest := range baseMounts {
174
		if _, ok := baseVolumes[dest]; ok {
175
			return nil, nil, nil, fmt.Errorf("baseMounts conflict at mount destination %v: %w", dest, specgen.ErrDuplicateDest)
176
		}
177
	}
178
	for dest := range baseVolumes {
179
		if _, ok := baseMounts[dest]; ok {
180
			return nil, nil, nil, fmt.Errorf("baseVolumes conflict at mount destination %v: %w", dest, specgen.ErrDuplicateDest)
181
		}
182
	}
183

184
	if s.ReadWriteTmpfs != nil && *s.ReadWriteTmpfs {
185
		runPath, err := imageRunPath(ctx, img)
186
		if err != nil {
187
			return nil, nil, nil, err
188
		}
189
		baseMounts = addReadWriteTmpfsMounts(baseMounts, s.Volumes, runPath)
190
	}
191

192
	// Final step: maps to arrays
193
	finalMounts := make([]spec.Mount, 0, len(baseMounts))
194
	for _, mount := range baseMounts {
195
		if mount.Type == define.TypeBind {
196
			absSrc, err := filepath.Abs(mount.Source)
197
			if err != nil {
198
				return nil, nil, nil, fmt.Errorf("getting absolute path of %s: %w", mount.Source, err)
199
			}
200
			mount.Source = absSrc
201
		}
202
		finalMounts = append(finalMounts, mount)
203
	}
204
	finalVolumes := make([]*specgen.NamedVolume, 0, len(baseVolumes))
205
	for _, volume := range baseVolumes {
206
		finalVolumes = append(finalVolumes, volume)
207
	}
208

209
	finalOverlays := make([]*specgen.OverlayVolume, 0, len(unifiedOverlays))
210
	for _, volume := range unifiedOverlays {
211
		finalOverlays = append(finalOverlays, volume)
212
	}
213

214
	return finalMounts, finalVolumes, finalOverlays, nil
215
}
216

217
// Get image volumes from the given image
218
func getImageVolumes(ctx context.Context, img *libimage.Image, s *specgen.SpecGenerator) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
219
	mounts := make(map[string]spec.Mount)
220
	volumes := make(map[string]*specgen.NamedVolume)
221

222
	mode := strings.ToLower(s.ImageVolumeMode)
223

224
	// Image may be nil (rootfs in use), or image volume mode may be ignore.
225
	if img == nil || mode == "ignore" {
226
		return mounts, volumes, nil
227
	}
228

229
	inspect, err := img.Inspect(ctx, nil)
230
	if err != nil {
231
		return nil, nil, fmt.Errorf("inspecting image to get image volumes: %w", err)
232
	}
233
	for volume := range inspect.Config.Volumes {
234
		logrus.Debugf("Image has volume at %q", volume)
235
		cleanDest := filepath.Clean(volume)
236
		switch mode {
237
		case "", "anonymous":
238
			// Anonymous volumes have no name.
239
			newVol := new(specgen.NamedVolume)
240
			newVol.Dest = cleanDest
241
			newVol.Options = []string{"rprivate", "rw", "nodev", "exec"}
242
			volumes[cleanDest] = newVol
243
			logrus.Debugf("Adding anonymous image volume at %q", cleanDest)
244
		case define.TypeTmpfs:
245
			mount := spec.Mount{
246
				Destination: cleanDest,
247
				Source:      define.TypeTmpfs,
248
				Type:        define.TypeTmpfs,
249
				Options:     []string{"rprivate", "rw", "nodev", "exec"},
250
			}
251
			mounts[cleanDest] = mount
252
			logrus.Debugf("Adding tmpfs image volume at %q", cleanDest)
253
		}
254
	}
255

256
	return mounts, volumes, nil
257
}
258

259
func getVolumesFrom(volumesFrom []string, runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*specgen.NamedVolume, error) {
260
	finalMounts := make(map[string]spec.Mount)
261
	finalNamedVolumes := make(map[string]*specgen.NamedVolume)
262

263
	for _, volume := range volumesFrom {
264
		var options []string
265

266
		idOrName, volOpts, hasVolOpts := strings.Cut(volume, ":")
267
		if hasVolOpts {
268
			splitOpts := strings.Split(volOpts, ",")
269
			setRORW := false
270
			setZ := false
271
			for _, opt := range splitOpts {
272
				switch opt {
273
				case "z":
274
					if setZ {
275
						return nil, nil, errors.New("cannot set :z more than once in mount options")
276
					}
277
					setZ = true
278
				case "ro", "rw":
279
					if setRORW {
280
						return nil, nil, errors.New("cannot set ro or rw options more than once")
281
					}
282
					setRORW = true
283
				default:
284
					return nil, nil, fmt.Errorf("invalid option %q specified - volumes from another container can only use z,ro,rw options", opt)
285
				}
286
			}
287
			options = splitOpts
288
		}
289

290
		ctr, err := runtime.LookupContainer(idOrName)
291
		if err != nil {
292
			return nil, nil, fmt.Errorf("looking up container %q for volumes-from: %w", idOrName, err)
293
		}
294

295
		logrus.Debugf("Adding volumes from container %s", ctr.ID())
296

297
		// Look up the container's user volumes. This gets us the
298
		// destinations of all mounts the user added to the container.
299
		userVolumesArr := ctr.UserVolumes()
300

301
		// We're going to need to access them a lot, so convert to a map
302
		// to reduce looping.
303
		// We'll also use the map to indicate if we missed any volumes along the way.
304
		userVolumes := make(map[string]bool)
305
		for _, dest := range userVolumesArr {
306
			userVolumes[dest] = false
307
		}
308

309
		// Now we get the container's spec and loop through its volumes
310
		// and append them in if we can find them.
311
		spec := ctr.ConfigNoCopy().Spec
312
		if spec == nil {
313
			return nil, nil, fmt.Errorf("retrieving container %s spec for volumes-from", ctr.ID())
314
		}
315
		for _, mnt := range spec.Mounts {
316
			if mnt.Type != define.TypeBind {
317
				continue
318
			}
319
			if _, exists := userVolumes[mnt.Destination]; exists {
320
				userVolumes[mnt.Destination] = true
321

322
				if len(options) != 0 {
323
					mnt.Options = options
324
				}
325

326
				if _, ok := finalMounts[mnt.Destination]; ok {
327
					logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID())
328
				}
329
				finalMounts[mnt.Destination] = mnt
330
			}
331
		}
332

333
		// We're done with the spec mounts. Add named volumes.
334
		// Add these unconditionally - none of them are automatically
335
		// part of the container, as some spec mounts are.
336
		namedVolumes := ctr.NamedVolumes()
337
		for _, namedVol := range namedVolumes {
338
			if _, exists := userVolumes[namedVol.Dest]; exists {
339
				userVolumes[namedVol.Dest] = true
340
			}
341

342
			if len(options) != 0 {
343
				namedVol.Options = options
344
			}
345

346
			if _, ok := finalMounts[namedVol.Dest]; ok {
347
				logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID())
348
			}
349
			if err = parse.ValidateVolumeCtrDir(namedVol.Dest); err != nil {
350
				return nil, nil, err
351
			}
352

353
			cleanDest := filepath.Clean(namedVol.Dest)
354
			newVol := new(specgen.NamedVolume)
355
			newVol.Dest = cleanDest
356
			newVol.Options = namedVol.Options
357
			newVol.Name = namedVol.Name
358

359
			finalNamedVolumes[namedVol.Dest] = newVol
360
		}
361

362
		// Check if we missed any volumes
363
		for volDest, found := range userVolumes {
364
			if !found {
365
				logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID())
366
			}
367
		}
368
	}
369

370
	return finalMounts, finalNamedVolumes, nil
371
}
372

373
// AddContainerInitBinary adds the init binary specified by path iff the
374
// container will run in a private PID namespace that is not shared with the
375
// host or another pre-existing container, where an init-like process is
376
// already running.
377
// This does *NOT* modify the container command - that must be done elsewhere.
378
func addContainerInitBinary(s *specgen.SpecGenerator, path string) (spec.Mount, error) {
379
	mount := spec.Mount{
380
		Destination: define.ContainerInitPath,
381
		Type:        define.TypeBind,
382
		Source:      path,
383
		Options:     append(define.BindOptions, "ro"),
384
	}
385

386
	if path == "" {
387
		return mount, errors.New("please specify a path to the container-init binary")
388
	}
389
	if !s.PidNS.IsPrivate() {
390
		return mount, errors.New("cannot add init binary as PID 1 (PID namespace isn't private)")
391
	}
392
	if s.Systemd == "always" {
393
		return mount, errors.New("cannot use container-init binary with systemd=always")
394
	}
395
	if err := fileutils.Exists(path); errors.Is(err, fs.ErrNotExist) {
396
		return mount, fmt.Errorf("container-init binary not found on the host: %w", err)
397
	}
398
	return mount, nil
399
}
400

401
// Supersede existing mounts in the spec with new, user-specified mounts.
402
// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
403
// one mount, and we already have /tmp/a and /tmp/b, should we remove
404
// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
405
func SupersedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount {
406
	if len(mounts) > 0 {
407
		// If we have overlappings mounts, remove them from the spec in favor of
408
		// the user-added volume mounts
409
		destinations := make(map[string]bool)
410
		for _, mount := range mounts {
411
			destinations[path.Clean(mount.Destination)] = true
412
		}
413
		// Copy all mounts from spec to defaultMounts, except for
414
		//  - mounts overridden by a user supplied mount;
415
		//  - all mounts under /dev if a user supplied /dev is present;
416
		mountDev := destinations["/dev"]
417
		for _, mount := range configMount {
418
			if _, ok := destinations[path.Clean(mount.Destination)]; !ok {
419
				if mountDev && strings.HasPrefix(mount.Destination, "/dev/") {
420
					// filter out everything under /dev if /dev is user-mounted
421
					continue
422
				}
423

424
				logrus.Debugf("Adding mount %s", mount.Destination)
425
				mounts = append(mounts, mount)
426
			}
427
		}
428
		return mounts
429
	}
430
	return configMount
431
}
432

433
func InitFSMounts(mounts []spec.Mount) error {
434
	for i, m := range mounts {
435
		switch {
436
		case m.Type == define.TypeBind:
437
			opts, err := util.ProcessOptions(m.Options, false, m.Source)
438
			if err != nil {
439
				return err
440
			}
441
			mounts[i].Options = opts
442
		case m.Type == define.TypeTmpfs && filepath.Clean(m.Destination) != "/dev":
443
			opts, err := util.ProcessOptions(m.Options, true, "")
444
			if err != nil {
445
				return err
446
			}
447
			mounts[i].Options = opts
448
		}
449
	}
450
	return nil
451
}
452

453
func addReadWriteTmpfsMounts(mounts map[string]spec.Mount, volumes []*specgen.NamedVolume, runPath string) map[string]spec.Mount {
454
	readonlyTmpfs := []string{"/tmp", "/var/tmp", runPath}
455
	options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"}
456
	for _, dest := range readonlyTmpfs {
457
		if _, ok := mounts[dest]; ok {
458
			continue
459
		}
460
		for _, m := range volumes {
461
			if m.Dest == dest {
462
				continue
463
			}
464
		}
465
		mnt := spec.Mount{
466
			Destination: dest,
467
			Type:        define.TypeTmpfs,
468
			Source:      define.TypeTmpfs,
469
			Options:     options,
470
		}
471
		mounts[dest] = mnt
472
	}
473
	return mounts
474
}
475

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

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

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

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