podman

Форк
0
/
boltdb_state_internal.go 
1062 строки · 31.2 Кб
1
//go:build !remote
2

3
package libpod
4

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

14
	"github.com/containers/podman/v5/libpod/define"
15
	"github.com/containers/storage"
16
	"github.com/sirupsen/logrus"
17
	bolt "go.etcd.io/bbolt"
18
)
19

20
const (
21
	idRegistryName    = "id-registry"
22
	nameRegistryName  = "name-registry"
23
	ctrName           = "ctr"
24
	allCtrsName       = "all-ctrs"
25
	podName           = "pod"
26
	allPodsName       = "allPods"
27
	volName           = "vol"
28
	allVolsName       = "allVolumes"
29
	execName          = "exec"
30
	aliasesName       = "aliases"
31
	runtimeConfigName = "runtime-config"
32
	volumeCtrsName    = "volume-ctrs"
33

34
	exitCodeName          = "exit-code"
35
	exitCodeTimeStampName = "exit-code-time-stamp"
36

37
	configName         = "config"
38
	stateName          = "state"
39
	dependenciesName   = "dependencies"
40
	volCtrDependencies = "vol-dependencies"
41
	netNSName          = "netns"
42
	containersName     = "containers"
43
	podIDName          = "pod-id"
44
	networksName       = "networks"
45

46
	staticDirName   = "static-dir"
47
	tmpDirName      = "tmp-dir"
48
	runRootName     = "run-root"
49
	graphRootName   = "graph-root"
50
	graphDriverName = "graph-driver-name"
51
	osName          = "os"
52
	volPathName     = "volume-path"
53
)
54

55
var (
56
	idRegistryBkt      = []byte(idRegistryName)
57
	nameRegistryBkt    = []byte(nameRegistryName)
58
	ctrBkt             = []byte(ctrName)
59
	allCtrsBkt         = []byte(allCtrsName)
60
	podBkt             = []byte(podName)
61
	allPodsBkt         = []byte(allPodsName)
62
	volBkt             = []byte(volName)
63
	allVolsBkt         = []byte(allVolsName)
64
	execBkt            = []byte(execName)
65
	aliasesBkt         = []byte(aliasesName)
66
	runtimeConfigBkt   = []byte(runtimeConfigName)
67
	dependenciesBkt    = []byte(dependenciesName)
68
	volDependenciesBkt = []byte(volCtrDependencies)
69
	networksBkt        = []byte(networksName)
70
	volCtrsBkt         = []byte(volumeCtrsName)
71

72
	exitCodeBkt          = []byte(exitCodeName)
73
	exitCodeTimeStampBkt = []byte(exitCodeTimeStampName)
74

75
	configKey     = []byte(configName)
76
	stateKey      = []byte(stateName)
77
	netNSKey      = []byte(netNSName)
78
	containersBkt = []byte(containersName)
79
	podIDKey      = []byte(podIDName)
80

81
	staticDirKey   = []byte(staticDirName)
82
	tmpDirKey      = []byte(tmpDirName)
83
	runRootKey     = []byte(runRootName)
84
	graphRootKey   = []byte(graphRootName)
85
	graphDriverKey = []byte(graphDriverName)
86
	osKey          = []byte(osName)
87
	volPathKey     = []byte(volPathName)
88
)
89

90
// This represents a field in the runtime configuration that will be validated
91
// against the DB to ensure no configuration mismatches occur.
92
type dbConfigValidation struct {
93
	name         string // Only used for error messages
94
	runtimeValue string
95
	key          []byte
96
	defaultValue string
97
	isPath       bool
98
}
99

100
// Check if the configuration of the database is compatible with the
101
// configuration of the runtime opening it
102
// If there is no runtime configuration loaded, load our own
103
func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error {
104
	storeOpts, err := storage.DefaultStoreOptions()
105
	if err != nil {
106
		return err
107
	}
108

109
	// We need to validate the following things
110
	checks := []dbConfigValidation{
111
		{
112
			"OS",
113
			runtime.GOOS,
114
			osKey,
115
			runtime.GOOS,
116
			false,
117
		},
118
		{
119
			"libpod root directory (staticdir)",
120
			filepath.Clean(rt.config.Engine.StaticDir),
121
			staticDirKey,
122
			"",
123
			true,
124
		},
125
		{
126
			"libpod temporary files directory (tmpdir)",
127
			filepath.Clean(rt.config.Engine.TmpDir),
128
			tmpDirKey,
129
			"",
130
			true,
131
		},
132
		{
133
			"storage temporary directory (runroot)",
134
			filepath.Clean(rt.StorageConfig().RunRoot),
135
			runRootKey,
136
			storeOpts.RunRoot,
137
			true,
138
		},
139
		{
140
			"storage graph root directory (graphroot)",
141
			filepath.Clean(rt.StorageConfig().GraphRoot),
142
			graphRootKey,
143
			storeOpts.GraphRoot,
144
			true,
145
		},
146
		{
147
			"storage graph driver",
148
			rt.StorageConfig().GraphDriverName,
149
			graphDriverKey,
150
			storeOpts.GraphDriverName,
151
			false,
152
		},
153
		{
154
			"volume path",
155
			rt.config.Engine.VolumePath,
156
			volPathKey,
157
			"",
158
			true,
159
		},
160
	}
161

162
	// These fields were missing and will have to be recreated.
163
	missingFields := []dbConfigValidation{}
164

165
	// Let's try and validate read-only first
166
	err = db.View(func(tx *bolt.Tx) error {
167
		configBkt, err := getRuntimeConfigBucket(tx)
168
		if err != nil {
169
			return err
170
		}
171

172
		for _, check := range checks {
173
			exists, err := readOnlyValidateConfig(configBkt, check)
174
			if err != nil {
175
				return err
176
			}
177
			if !exists {
178
				missingFields = append(missingFields, check)
179
			}
180
		}
181

182
		return nil
183
	})
184
	if err != nil {
185
		return err
186
	}
187

188
	if len(missingFields) == 0 {
189
		return nil
190
	}
191

192
	// Populate missing fields
193
	return db.Update(func(tx *bolt.Tx) error {
194
		configBkt, err := getRuntimeConfigBucket(tx)
195
		if err != nil {
196
			return err
197
		}
198

199
		for _, missing := range missingFields {
200
			dbValue := []byte(missing.runtimeValue)
201
			if missing.runtimeValue == "" && missing.defaultValue != "" {
202
				dbValue = []byte(missing.defaultValue)
203
			}
204

205
			if err := configBkt.Put(missing.key, dbValue); err != nil {
206
				return fmt.Errorf("updating %s in DB runtime config: %w", missing.name, err)
207
			}
208
		}
209

210
		return nil
211
	})
212
}
213

214
// Attempt a read-only validation of a configuration entry in the DB against an
215
// element of the current runtime configuration.
216
// If the configuration key in question does not exist, (false, nil) will be
217
// returned.
218
// If the configuration key does exist, and matches the runtime configuration
219
// successfully, (true, nil) is returned.
220
// An error is only returned when validation fails.
221
// if the given runtimeValue or value retrieved from the database are empty,
222
// and defaultValue is not, defaultValue will be checked instead. This ensures
223
// that we will not fail on configuration changes in c/storage (where we may
224
// pass the empty string to use defaults).
225
func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bool, error) {
226
	keyBytes := bucket.Get(toCheck.key)
227
	if keyBytes == nil {
228
		// False return indicates missing key
229
		return false, nil
230
	}
231

232
	dbValue := string(keyBytes)
233
	ourValue := toCheck.runtimeValue
234

235
	// Tolerate symlinks when possible - most relevant for OStree systems
236
	// and rootless containers, where we want to put containers in /home,
237
	// which is symlinked to /var/home.
238
	if toCheck.isPath {
239
		if dbValue != "" {
240
			// Ignore ENOENT on both, on a fresh system some paths
241
			// may not exist this early in Libpod init.
242
			dbVal, err := filepath.EvalSymlinks(dbValue)
243
			if err != nil && !errors.Is(err, fs.ErrNotExist) {
244
				return false, fmt.Errorf("evaluating symlinks on DB %s path %q: %w", toCheck.name, dbValue, err)
245
			}
246
			dbValue = dbVal
247
		}
248
		if ourValue != "" {
249
			ourVal, err := filepath.EvalSymlinks(ourValue)
250
			if err != nil && !errors.Is(err, fs.ErrNotExist) {
251
				return false, fmt.Errorf("evaluating symlinks on configured %s path %q: %w", toCheck.name, ourValue, err)
252
			}
253
			ourValue = ourVal
254
		}
255
	}
256

257
	if ourValue != dbValue {
258
		// If the runtime value is the empty string and default is not,
259
		// check against default.
260
		if ourValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue {
261
			return true, nil
262
		}
263

264
		// If the DB value is the empty string, check that the runtime
265
		// value is the default.
266
		if dbValue == "" && toCheck.defaultValue != "" && ourValue == toCheck.defaultValue {
267
			return true, nil
268
		}
269

270
		return true, fmt.Errorf("database %s %q does not match our %s %q: %w",
271
			toCheck.name, dbValue, toCheck.name, ourValue, define.ErrDBBadConfig)
272
	}
273

274
	return true, nil
275
}
276

277
// Open a connection to the database.
278
// Must be paired with a `defer closeDBCon()` on the returned database, to
279
// ensure the state is properly unlocked
280
func (s *BoltState) getDBCon() (*bolt.DB, error) {
281
	// We need an in-memory lock to avoid issues around POSIX file advisory
282
	// locks as described in the link below:
283
	// https://www.sqlite.org/src/artifact/c230a7a24?ln=994-1081
284
	s.dbLock.Lock()
285

286
	db, err := bolt.Open(s.dbPath, 0600, nil)
287
	if err != nil {
288
		return nil, fmt.Errorf("opening database %s: %w", s.dbPath, err)
289
	}
290

291
	return db, nil
292
}
293

294
// deferredCloseDBCon closes the bolt db but instead of returning an
295
// error it logs the error. it is meant to be used within the confines
296
// of a defer statement only
297
func (s *BoltState) deferredCloseDBCon(db *bolt.DB) {
298
	if err := s.closeDBCon(db); err != nil {
299
		logrus.Errorf("Failed to close libpod db: %q", err)
300
	}
301
}
302

303
// Close a connection to the database.
304
// MUST be used in place of `db.Close()` to ensure proper unlocking of the
305
// state.
306
func (s *BoltState) closeDBCon(db *bolt.DB) error {
307
	err := db.Close()
308

309
	s.dbLock.Unlock()
310

311
	return err
312
}
313

314
func getIDBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
315
	bkt := tx.Bucket(idRegistryBkt)
316
	if bkt == nil {
317
		return nil, fmt.Errorf("id registry bucket not found in DB: %w", define.ErrDBBadConfig)
318
	}
319
	return bkt, nil
320
}
321

322
func getNamesBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
323
	bkt := tx.Bucket(nameRegistryBkt)
324
	if bkt == nil {
325
		return nil, fmt.Errorf("name registry bucket not found in DB: %w", define.ErrDBBadConfig)
326
	}
327
	return bkt, nil
328
}
329

330
func getCtrBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
331
	bkt := tx.Bucket(ctrBkt)
332
	if bkt == nil {
333
		return nil, fmt.Errorf("containers bucket not found in DB: %w", define.ErrDBBadConfig)
334
	}
335
	return bkt, nil
336
}
337

338
func getAllCtrsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
339
	bkt := tx.Bucket(allCtrsBkt)
340
	if bkt == nil {
341
		return nil, fmt.Errorf("all containers bucket not found in DB: %w", define.ErrDBBadConfig)
342
	}
343
	return bkt, nil
344
}
345

346
func getPodBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
347
	bkt := tx.Bucket(podBkt)
348
	if bkt == nil {
349
		return nil, fmt.Errorf("pods bucket not found in DB: %w", define.ErrDBBadConfig)
350
	}
351
	return bkt, nil
352
}
353

354
func getAllPodsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
355
	bkt := tx.Bucket(allPodsBkt)
356
	if bkt == nil {
357
		return nil, fmt.Errorf("all pods bucket not found in DB: %w", define.ErrDBBadConfig)
358
	}
359
	return bkt, nil
360
}
361

362
func getVolBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
363
	bkt := tx.Bucket(volBkt)
364
	if bkt == nil {
365
		return nil, fmt.Errorf("volumes bucket not found in DB: %w", define.ErrDBBadConfig)
366
	}
367
	return bkt, nil
368
}
369

370
func getAllVolsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
371
	bkt := tx.Bucket(allVolsBkt)
372
	if bkt == nil {
373
		return nil, fmt.Errorf("all volumes bucket not found in DB: %w", define.ErrDBBadConfig)
374
	}
375
	return bkt, nil
376
}
377

378
func getExecBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
379
	bkt := tx.Bucket(execBkt)
380
	if bkt == nil {
381
		return nil, fmt.Errorf("exec bucket not found in DB: %w", define.ErrDBBadConfig)
382
	}
383
	return bkt, nil
384
}
385

386
func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
387
	bkt := tx.Bucket(runtimeConfigBkt)
388
	if bkt == nil {
389
		return nil, fmt.Errorf("runtime configuration bucket not found in DB: %w", define.ErrDBBadConfig)
390
	}
391
	return bkt, nil
392
}
393

394
func getExitCodeBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
395
	bkt := tx.Bucket(exitCodeBkt)
396
	if bkt == nil {
397
		return nil, fmt.Errorf("exit-code container bucket not found in DB: %w", define.ErrDBBadConfig)
398
	}
399
	return bkt, nil
400
}
401

402
func getExitCodeTimeStampBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
403
	bkt := tx.Bucket(exitCodeTimeStampBkt)
404
	if bkt == nil {
405
		return nil, fmt.Errorf("exit-code time stamp bucket not found in DB: %w", define.ErrDBBadConfig)
406
	}
407
	return bkt, nil
408
}
409

410
func getVolumeContainersBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
411
	bkt := tx.Bucket(volCtrsBkt)
412
	if bkt == nil {
413
		return nil, fmt.Errorf("volume containers bucket not found in DB: %w", define.ErrDBBadConfig)
414
	}
415
	return bkt, nil
416
}
417

418
func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error {
419
	ctrBkt := ctrsBkt.Bucket(id)
420
	if ctrBkt == nil {
421
		return fmt.Errorf("container %s not found in DB: %w", string(id), define.ErrNoSuchCtr)
422
	}
423

424
	configBytes := ctrBkt.Get(configKey)
425
	if configBytes == nil {
426
		return fmt.Errorf("container %s missing config key in DB: %w", string(id), define.ErrInternal)
427
	}
428

429
	if err := json.Unmarshal(configBytes, config); err != nil {
430
		return fmt.Errorf("unmarshalling container %s config: %w", string(id), err)
431
	}
432

433
	// convert ports to the new format if needed
434
	if len(config.ContainerNetworkConfig.OldPortMappings) > 0 && len(config.ContainerNetworkConfig.PortMappings) == 0 {
435
		config.ContainerNetworkConfig.PortMappings = ocicniPortsToNetTypesPorts(config.ContainerNetworkConfig.OldPortMappings)
436
		// keep the OldPortMappings in case an user has to downgrade podman
437

438
		// indicate that the config was modified and should be written back to the db when possible
439
		config.rewrite = true
440
	}
441

442
	return nil
443
}
444

445
func (s *BoltState) getContainerStateDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error {
446
	newState := new(ContainerState)
447
	ctrToUpdate := ctrsBkt.Bucket(id)
448
	if ctrToUpdate == nil {
449
		ctr.valid = false
450
		return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr)
451
	}
452

453
	newStateBytes := ctrToUpdate.Get(stateKey)
454
	if newStateBytes == nil {
455
		return fmt.Errorf("container %s does not have a state key in DB: %w", ctr.ID(), define.ErrInternal)
456
	}
457

458
	if err := json.Unmarshal(newStateBytes, newState); err != nil {
459
		return fmt.Errorf("unmarshalling container %s state: %w", ctr.ID(), err)
460
	}
461

462
	// backwards compat, previously we used an extra bucket for the netns so try to get it from there
463
	netNSBytes := ctrToUpdate.Get(netNSKey)
464
	if netNSBytes != nil && newState.NetNS == "" {
465
		newState.NetNS = string(netNSBytes)
466
	}
467

468
	// New state compiled successfully, swap it into the current state
469
	ctr.state = newState
470
	return nil
471
}
472

473
func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket, loadState bool) error {
474
	if err := s.getContainerConfigFromDB(id, ctr.config, ctrsBkt); err != nil {
475
		return err
476
	}
477

478
	if loadState {
479
		if err := s.getContainerStateDB(id, ctr, ctrsBkt); err != nil {
480
			return err
481
		}
482
	}
483

484
	// Get the lock
485
	lock, err := s.runtime.lockManager.RetrieveLock(ctr.config.LockID)
486
	if err != nil {
487
		return fmt.Errorf("retrieving lock for container %s: %w", string(id), err)
488
	}
489
	ctr.lock = lock
490

491
	if ctr.config.OCIRuntime == "" {
492
		ctr.ociRuntime = s.runtime.defaultOCIRuntime
493
	} else {
494
		// Handle legacy containers which might use a literal path for
495
		// their OCI runtime name.
496
		runtimeName := ctr.config.OCIRuntime
497
		ociRuntime, ok := s.runtime.ociRuntimes[runtimeName]
498
		if !ok {
499
			runtimeSet := false
500

501
			// If the path starts with a / and exists, make a new
502
			// OCI runtime for it using the full path.
503
			if strings.HasPrefix(runtimeName, "/") {
504
				if stat, err := os.Stat(runtimeName); err == nil && !stat.IsDir() {
505
					newOCIRuntime, err := newConmonOCIRuntime(runtimeName, []string{runtimeName}, s.runtime.conmonPath, s.runtime.runtimeFlags, s.runtime.config)
506
					if err == nil {
507
						// The runtime lock should
508
						// protect against concurrent
509
						// modification of the map.
510
						ociRuntime = newOCIRuntime
511
						s.runtime.ociRuntimes[runtimeName] = ociRuntime
512
						runtimeSet = true
513
					}
514
				}
515
			}
516

517
			if !runtimeSet {
518
				// Use a MissingRuntime implementation
519
				ociRuntime = getMissingRuntime(runtimeName, s.runtime)
520
			}
521
		}
522
		ctr.ociRuntime = ociRuntime
523
	}
524

525
	ctr.runtime = s.runtime
526
	ctr.valid = true
527

528
	return nil
529
}
530

531
func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error {
532
	podDB := podBkt.Bucket(id)
533
	if podDB == nil {
534
		return fmt.Errorf("pod with ID %s not found: %w", string(id), define.ErrNoSuchPod)
535
	}
536

537
	podConfigBytes := podDB.Get(configKey)
538
	if podConfigBytes == nil {
539
		return fmt.Errorf("pod %s is missing configuration key in DB: %w", string(id), define.ErrInternal)
540
	}
541

542
	if err := json.Unmarshal(podConfigBytes, pod.config); err != nil {
543
		return fmt.Errorf("unmarshalling pod %s config from DB: %w", string(id), err)
544
	}
545

546
	// Get the lock
547
	lock, err := s.runtime.lockManager.RetrieveLock(pod.config.LockID)
548
	if err != nil {
549
		return fmt.Errorf("retrieving lock for pod %s: %w", string(id), err)
550
	}
551
	pod.lock = lock
552

553
	pod.runtime = s.runtime
554
	pod.valid = true
555

556
	return nil
557
}
558

559
func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bucket) error {
560
	volDB := volBkt.Bucket(name)
561
	if volDB == nil {
562
		return fmt.Errorf("volume with name %s not found: %w", string(name), define.ErrNoSuchVolume)
563
	}
564

565
	volConfigBytes := volDB.Get(configKey)
566
	if volConfigBytes == nil {
567
		return fmt.Errorf("volume %s is missing configuration key in DB: %w", string(name), define.ErrInternal)
568
	}
569

570
	if err := json.Unmarshal(volConfigBytes, volume.config); err != nil {
571
		return fmt.Errorf("unmarshalling volume %s config from DB: %w", string(name), err)
572
	}
573

574
	// Volume state is allowed to be nil for legacy compatibility
575
	volStateBytes := volDB.Get(stateKey)
576
	if volStateBytes != nil {
577
		if err := json.Unmarshal(volStateBytes, volume.state); err != nil {
578
			return fmt.Errorf("unmarshalling volume %s state from DB: %w", string(name), err)
579
		}
580
	}
581

582
	// Need this for UsesVolumeDriver() so set it now.
583
	volume.runtime = s.runtime
584

585
	// Retrieve volume driver
586
	if volume.UsesVolumeDriver() {
587
		plugin, err := s.runtime.getVolumePlugin(volume.config)
588
		if err != nil {
589
			// We want to fail gracefully here, to ensure that we
590
			// can still remove volumes even if their plugin is
591
			// missing. Otherwise, we end up with volumes that
592
			// cannot even be retrieved from the database and will
593
			// cause things like `volume ls` to fail.
594
			logrus.Errorf("Volume %s uses volume plugin %s, but it cannot be accessed - some functionality may not be available: %v", volume.Name(), volume.config.Driver, err)
595
		} else {
596
			volume.plugin = plugin
597
		}
598
	}
599

600
	// Get the lock
601
	lock, err := s.runtime.lockManager.RetrieveLock(volume.config.LockID)
602
	if err != nil {
603
		return fmt.Errorf("retrieving lock for volume %q: %w", string(name), err)
604
	}
605
	volume.lock = lock
606

607
	volume.valid = true
608

609
	return nil
610
}
611

612
// Add a container to the DB
613
// If pod is not nil, the container is added to the pod as well
614
func (s *BoltState) addContainer(ctr *Container, pod *Pod) error {
615
	// Set the original networks to nil. We can save some space by not storing it in the config
616
	// since we store it in a different mutable bucket anyway.
617
	configNetworks := ctr.config.Networks
618
	ctr.config.Networks = nil
619

620
	// JSON container structs to insert into DB
621
	configJSON, err := json.Marshal(ctr.config)
622
	if err != nil {
623
		return fmt.Errorf("marshalling container %s config to JSON: %w", ctr.ID(), err)
624
	}
625
	stateJSON, err := json.Marshal(ctr.state)
626
	if err != nil {
627
		return fmt.Errorf("marshalling container %s state to JSON: %w", ctr.ID(), err)
628
	}
629
	dependsCtrs := ctr.Dependencies()
630

631
	ctrID := []byte(ctr.ID())
632
	ctrName := []byte(ctr.Name())
633

634
	// make sure to marshal the network options before we get the db lock
635
	networks := make(map[string][]byte, len(configNetworks))
636
	for net, opts := range configNetworks {
637
		// Check that we don't have any empty network names
638
		if net == "" {
639
			return fmt.Errorf("network names cannot be an empty string: %w", define.ErrInvalidArg)
640
		}
641
		if opts.InterfaceName == "" {
642
			return fmt.Errorf("network interface name cannot be an empty string: %w", define.ErrInvalidArg)
643
		}
644
		optBytes, err := json.Marshal(opts)
645
		if err != nil {
646
			return fmt.Errorf("marshalling network options JSON for container %s: %w", ctr.ID(), err)
647
		}
648
		networks[net] = optBytes
649
	}
650

651
	db, err := s.getDBCon()
652
	if err != nil {
653
		return err
654
	}
655
	defer s.deferredCloseDBCon(db)
656

657
	err = db.Update(func(tx *bolt.Tx) error {
658
		idsBucket, err := getIDBucket(tx)
659
		if err != nil {
660
			return err
661
		}
662

663
		namesBucket, err := getNamesBucket(tx)
664
		if err != nil {
665
			return err
666
		}
667

668
		ctrBucket, err := getCtrBucket(tx)
669
		if err != nil {
670
			return err
671
		}
672

673
		allCtrsBucket, err := getAllCtrsBucket(tx)
674
		if err != nil {
675
			return err
676
		}
677

678
		volBkt, err := getVolBucket(tx)
679
		if err != nil {
680
			return err
681
		}
682

683
		// If a pod was given, check if it exists
684
		var podDB *bolt.Bucket
685
		var podCtrs *bolt.Bucket
686
		if pod != nil {
687
			podBucket, err := getPodBucket(tx)
688
			if err != nil {
689
				return err
690
			}
691

692
			podID := []byte(pod.ID())
693

694
			podDB = podBucket.Bucket(podID)
695
			if podDB == nil {
696
				pod.valid = false
697
				return fmt.Errorf("pod %s does not exist in database: %w", pod.ID(), define.ErrNoSuchPod)
698
			}
699
			podCtrs = podDB.Bucket(containersBkt)
700
			if podCtrs == nil {
701
				return fmt.Errorf("pod %s does not have a containers bucket: %w", pod.ID(), define.ErrInternal)
702
			}
703
		}
704

705
		// Check if we already have a container with the given ID and name
706
		idExist := idsBucket.Get(ctrID)
707
		if idExist != nil {
708
			err = define.ErrCtrExists
709
			if allCtrsBucket.Get(idExist) == nil {
710
				err = define.ErrPodExists
711
			}
712
			return fmt.Errorf("ID \"%s\" is in use: %w", ctr.ID(), err)
713
		}
714
		nameExist := namesBucket.Get(ctrName)
715
		if nameExist != nil {
716
			err = define.ErrCtrExists
717
			if allCtrsBucket.Get(nameExist) == nil {
718
				err = define.ErrPodExists
719
			}
720
			return fmt.Errorf("name \"%s\" is in use: %w", ctr.Name(), err)
721
		}
722

723
		// No overlapping containers
724
		// Add the new container to the DB
725
		if err := idsBucket.Put(ctrID, ctrName); err != nil {
726
			return fmt.Errorf("adding container %s ID to DB: %w", ctr.ID(), err)
727
		}
728
		if err := namesBucket.Put(ctrName, ctrID); err != nil {
729
			return fmt.Errorf("adding container %s name (%s) to DB: %w", ctr.ID(), ctr.Name(), err)
730
		}
731
		if err := allCtrsBucket.Put(ctrID, ctrName); err != nil {
732
			return fmt.Errorf("adding container %s to all containers bucket in DB: %w", ctr.ID(), err)
733
		}
734

735
		newCtrBkt, err := ctrBucket.CreateBucket(ctrID)
736
		if err != nil {
737
			return fmt.Errorf("adding container %s bucket to DB: %w", ctr.ID(), err)
738
		}
739

740
		if err := newCtrBkt.Put(configKey, configJSON); err != nil {
741
			return fmt.Errorf("adding container %s config to DB: %w", ctr.ID(), err)
742
		}
743
		if err := newCtrBkt.Put(stateKey, stateJSON); err != nil {
744
			return fmt.Errorf("adding container %s state to DB: %w", ctr.ID(), err)
745
		}
746
		if pod != nil {
747
			if err := newCtrBkt.Put(podIDKey, []byte(pod.ID())); err != nil {
748
				return fmt.Errorf("adding container %s pod to DB: %w", ctr.ID(), err)
749
			}
750
		}
751
		if len(networks) > 0 {
752
			ctrNetworksBkt, err := newCtrBkt.CreateBucket(networksBkt)
753
			if err != nil {
754
				return fmt.Errorf("creating networks bucket for container %s: %w", ctr.ID(), err)
755
			}
756
			for network, opts := range networks {
757
				if err := ctrNetworksBkt.Put([]byte(network), opts); err != nil {
758
					return fmt.Errorf("adding network %q to networks bucket for container %s: %w", network, ctr.ID(), err)
759
				}
760
			}
761
		}
762

763
		if _, err := newCtrBkt.CreateBucket(dependenciesBkt); err != nil {
764
			return fmt.Errorf("creating dependencies bucket for container %s: %w", ctr.ID(), err)
765
		}
766

767
		// Add dependencies for the container
768
		for _, dependsCtr := range dependsCtrs {
769
			depCtrID := []byte(dependsCtr)
770

771
			depCtrBkt := ctrBucket.Bucket(depCtrID)
772
			if depCtrBkt == nil {
773
				return fmt.Errorf("container %s depends on container %s, but it does not exist in the DB: %w", ctr.ID(), dependsCtr, define.ErrNoSuchCtr)
774
			}
775

776
			depCtrPod := depCtrBkt.Get(podIDKey)
777
			if pod != nil {
778
				// If we're part of a pod, make sure the dependency is part of the same pod
779
				if depCtrPod == nil {
780
					return fmt.Errorf("container %s depends on container %s which is not in pod %s: %w", ctr.ID(), dependsCtr, pod.ID(), define.ErrInvalidArg)
781
				}
782

783
				if string(depCtrPod) != pod.ID() {
784
					return fmt.Errorf("container %s depends on container %s which is in a different pod (%s): %w", ctr.ID(), dependsCtr, string(depCtrPod), define.ErrInvalidArg)
785
				}
786
			} else if depCtrPod != nil {
787
				// If we're not part of a pod, we cannot depend on containers in a pod
788
				return fmt.Errorf("container %s depends on container %s which is in a pod - containers not in pods cannot depend on containers in pods: %w", ctr.ID(), dependsCtr, define.ErrInvalidArg)
789
			}
790

791
			depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt)
792
			if depCtrDependsBkt == nil {
793
				return fmt.Errorf("container %s does not have a dependencies bucket: %w", dependsCtr, define.ErrInternal)
794
			}
795
			if err := depCtrDependsBkt.Put(ctrID, ctrName); err != nil {
796
				return fmt.Errorf("adding ctr %s as dependency of container %s: %w", ctr.ID(), dependsCtr, err)
797
			}
798
		}
799

800
		// Add ctr to pod
801
		if pod != nil && podCtrs != nil {
802
			if err := podCtrs.Put(ctrID, ctrName); err != nil {
803
				return fmt.Errorf("adding container %s to pod %s: %w", ctr.ID(), pod.ID(), err)
804
			}
805
		}
806

807
		// Add container to named volume dependencies buckets
808
		for _, vol := range ctr.config.NamedVolumes {
809
			volDB := volBkt.Bucket([]byte(vol.Name))
810
			if volDB == nil {
811
				return fmt.Errorf("no volume with name %s found in database when adding container %s: %w", vol.Name, ctr.ID(), define.ErrNoSuchVolume)
812
			}
813

814
			ctrDepsBkt, err := volDB.CreateBucketIfNotExists(volDependenciesBkt)
815
			if err != nil {
816
				return fmt.Errorf("creating volume %s dependencies bucket to add container %s: %w", vol.Name, ctr.ID(), err)
817
			}
818
			if depExists := ctrDepsBkt.Get(ctrID); depExists == nil {
819
				if err := ctrDepsBkt.Put(ctrID, ctrID); err != nil {
820
					return fmt.Errorf("adding container %s to volume %s dependencies: %w", ctr.ID(), vol.Name, err)
821
				}
822
			}
823
		}
824

825
		return nil
826
	})
827
	return err
828
}
829

830
// Remove a container from the DB
831
// If pod is not nil, the container is treated as belonging to a pod, and
832
// will be removed from the pod as well
833
func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error {
834
	ctrID := []byte(ctr.ID())
835
	ctrName := []byte(ctr.Name())
836

837
	idsBucket, err := getIDBucket(tx)
838
	if err != nil {
839
		return err
840
	}
841

842
	namesBucket, err := getNamesBucket(tx)
843
	if err != nil {
844
		return err
845
	}
846

847
	ctrBucket, err := getCtrBucket(tx)
848
	if err != nil {
849
		return err
850
	}
851

852
	allCtrsBucket, err := getAllCtrsBucket(tx)
853
	if err != nil {
854
		return err
855
	}
856

857
	volBkt, err := getVolBucket(tx)
858
	if err != nil {
859
		return err
860
	}
861

862
	// Does the pod exist?
863
	var podDB *bolt.Bucket
864
	if pod != nil {
865
		podBucket, err := getPodBucket(tx)
866
		if err != nil {
867
			return err
868
		}
869

870
		podID := []byte(pod.ID())
871

872
		podDB = podBucket.Bucket(podID)
873
		if podDB == nil {
874
			pod.valid = false
875
			return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod)
876
		}
877
	}
878

879
	// Does the container exist?
880
	ctrExists := ctrBucket.Bucket(ctrID)
881
	if ctrExists == nil {
882
		ctr.valid = false
883
		return fmt.Errorf("no container with ID %s found in DB: %w", ctr.ID(), define.ErrNoSuchCtr)
884
	}
885

886
	if podDB != nil && pod != nil {
887
		// Check if the container is in the pod, remove it if it is
888
		podCtrs := podDB.Bucket(containersBkt)
889
		if podCtrs == nil {
890
			// Malformed pod
891
			logrus.Errorf("Pod %s malformed in database, missing containers bucket!", pod.ID())
892
		} else {
893
			ctrInPod := podCtrs.Get(ctrID)
894
			if ctrInPod == nil {
895
				return fmt.Errorf("container %s is not in pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr)
896
			}
897
			if err := podCtrs.Delete(ctrID); err != nil {
898
				return fmt.Errorf("removing container %s from pod %s: %w", ctr.ID(), pod.ID(), err)
899
			}
900
		}
901
	}
902

903
	// Does the container have exec sessions?
904
	ctrExecSessionsBkt := ctrExists.Bucket(execBkt)
905
	if ctrExecSessionsBkt != nil {
906
		sessions := []string{}
907
		err = ctrExecSessionsBkt.ForEach(func(id, value []byte) error {
908
			sessions = append(sessions, string(id))
909

910
			return nil
911
		})
912
		if err != nil {
913
			return err
914
		}
915
		if len(sessions) > 0 {
916
			return fmt.Errorf("container %s has active exec sessions: %s: %w", ctr.ID(), strings.Join(sessions, ", "), define.ErrExecSessionExists)
917
		}
918
	}
919

920
	// Does the container have dependencies?
921
	ctrDepsBkt := ctrExists.Bucket(dependenciesBkt)
922
	if ctrDepsBkt == nil {
923
		return fmt.Errorf("container %s does not have a dependencies bucket: %w", ctr.ID(), define.ErrInternal)
924
	}
925
	deps := []string{}
926
	err = ctrDepsBkt.ForEach(func(id, value []byte) error {
927
		deps = append(deps, string(id))
928

929
		return nil
930
	})
931
	if err != nil {
932
		return err
933
	}
934
	if len(deps) != 0 {
935
		return fmt.Errorf("container %s is a dependency of the following containers: %s: %w", ctr.ID(), strings.Join(deps, ", "), define.ErrDepExists)
936
	}
937

938
	if err := ctrBucket.DeleteBucket(ctrID); err != nil {
939
		return fmt.Errorf("deleting container %s from DB: %w", ctr.ID(), define.ErrInternal)
940
	}
941

942
	if err := idsBucket.Delete(ctrID); err != nil {
943
		return fmt.Errorf("deleting container %s ID in DB: %w", ctr.ID(), err)
944
	}
945

946
	if err := namesBucket.Delete(ctrName); err != nil {
947
		return fmt.Errorf("deleting container %s name in DB: %w", ctr.ID(), err)
948
	}
949
	if err := allCtrsBucket.Delete(ctrID); err != nil {
950
		return fmt.Errorf("deleting container %s from all containers bucket in DB: %w", ctr.ID(), err)
951
	}
952

953
	depCtrs := ctr.Dependencies()
954

955
	// Remove us from other container's dependencies
956
	for _, depCtr := range depCtrs {
957
		depCtrID := []byte(depCtr)
958

959
		depCtrBkt := ctrBucket.Bucket(depCtrID)
960
		if depCtrBkt == nil {
961
			// The dependent container has been removed
962
			// This should not be possible, and means the
963
			// state is inconsistent, but don't error
964
			// The container with inconsistent state is the
965
			// one being removed
966
			continue
967
		}
968

969
		depCtrDependsBkt := depCtrBkt.Bucket(dependenciesBkt)
970
		if depCtrDependsBkt == nil {
971
			// This is more serious - another container in
972
			// the state is inconsistent
973
			// Log it, continue removing
974
			logrus.Errorf("Container %s is missing dependencies bucket in DB", ctr.ID())
975
			continue
976
		}
977

978
		if err := depCtrDependsBkt.Delete(ctrID); err != nil {
979
			return fmt.Errorf("removing container %s as a dependency of container %s: %w", ctr.ID(), depCtr, err)
980
		}
981
	}
982

983
	// Remove container from named volume dependencies buckets
984
	for _, vol := range ctr.config.NamedVolumes {
985
		volDB := volBkt.Bucket([]byte(vol.Name))
986
		if volDB == nil {
987
			// Let's assume the volume was already deleted and
988
			// continue to remove the container
989
			continue
990
		}
991

992
		ctrDepsBkt := volDB.Bucket(volDependenciesBkt)
993
		if ctrDepsBkt == nil {
994
			return fmt.Errorf("volume %s is missing container dependencies bucket, cannot remove container %s from dependencies: %w", vol.Name, ctr.ID(), define.ErrInternal)
995
		}
996
		if depExists := ctrDepsBkt.Get(ctrID); depExists == nil {
997
			if err := ctrDepsBkt.Delete(ctrID); err != nil {
998
				return fmt.Errorf("deleting container %s dependency on volume %s: %w", ctr.ID(), vol.Name, err)
999
			}
1000
		}
1001
	}
1002

1003
	return nil
1004
}
1005

1006
// lookupContainerID retrieves a container ID from the state by full or unique
1007
// partial ID or name.
1008
func (s *BoltState) lookupContainerID(idOrName string, ctrBucket, namesBucket *bolt.Bucket) ([]byte, error) {
1009
	// First, check if the ID given was the actual container ID
1010
	ctrExists := ctrBucket.Bucket([]byte(idOrName))
1011
	if ctrExists != nil {
1012
		// A full container ID was given.
1013
		return []byte(idOrName), nil
1014
	}
1015

1016
	// Next, check if the full name was given
1017
	isPod := false
1018
	fullID := namesBucket.Get([]byte(idOrName))
1019
	if fullID != nil {
1020
		// The name exists and maps to an ID.
1021
		// However, we are not yet certain the ID is a
1022
		// container.
1023
		ctrExists = ctrBucket.Bucket(fullID)
1024
		if ctrExists != nil {
1025
			// A container bucket matching the full ID was
1026
			// found.
1027
			return fullID, nil
1028
		}
1029
		// Don't error if we have a name match but it's not a
1030
		// container - there's a chance we have a container with
1031
		// an ID starting with those characters.
1032
		// However, so we can return a good error, note whether
1033
		// this is a pod.
1034
		isPod = true
1035
	}
1036

1037
	var id []byte
1038
	// We were not given a full container ID or name.
1039
	// Search for partial ID matches.
1040
	exists := false
1041
	err := ctrBucket.ForEach(func(checkID, checkName []byte) error {
1042
		if strings.HasPrefix(string(checkID), idOrName) {
1043
			if exists {
1044
				return fmt.Errorf("more than one result for container ID %s: %w", idOrName, define.ErrCtrExists)
1045
			}
1046
			id = checkID
1047
			exists = true
1048
		}
1049

1050
		return nil
1051
	})
1052

1053
	if err != nil {
1054
		return nil, err
1055
	} else if !exists {
1056
		if isPod {
1057
			return nil, fmt.Errorf("%q is a pod, not a container: %w", idOrName, define.ErrNoSuchCtr)
1058
		}
1059
		return nil, fmt.Errorf("no container with name or ID %q found: %w", idOrName, define.ErrNoSuchCtr)
1060
	}
1061
	return id, nil
1062
}
1063

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

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

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

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