14
"github.com/containers/podman/v5/libpod/define"
15
"github.com/containers/storage"
16
"github.com/sirupsen/logrus"
17
bolt "go.etcd.io/bbolt"
21
idRegistryName = "id-registry"
22
nameRegistryName = "name-registry"
24
allCtrsName = "all-ctrs"
26
allPodsName = "allPods"
28
allVolsName = "allVolumes"
30
aliasesName = "aliases"
31
runtimeConfigName = "runtime-config"
32
volumeCtrsName = "volume-ctrs"
34
exitCodeName = "exit-code"
35
exitCodeTimeStampName = "exit-code-time-stamp"
39
dependenciesName = "dependencies"
40
volCtrDependencies = "vol-dependencies"
42
containersName = "containers"
44
networksName = "networks"
46
staticDirName = "static-dir"
47
tmpDirName = "tmp-dir"
48
runRootName = "run-root"
49
graphRootName = "graph-root"
50
graphDriverName = "graph-driver-name"
52
volPathName = "volume-path"
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)
72
exitCodeBkt = []byte(exitCodeName)
73
exitCodeTimeStampBkt = []byte(exitCodeTimeStampName)
75
configKey = []byte(configName)
76
stateKey = []byte(stateName)
77
netNSKey = []byte(netNSName)
78
containersBkt = []byte(containersName)
79
podIDKey = []byte(podIDName)
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)
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
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()
109
// We need to validate the following things
110
checks := []dbConfigValidation{
119
"libpod root directory (staticdir)",
120
filepath.Clean(rt.config.Engine.StaticDir),
126
"libpod temporary files directory (tmpdir)",
127
filepath.Clean(rt.config.Engine.TmpDir),
133
"storage temporary directory (runroot)",
134
filepath.Clean(rt.StorageConfig().RunRoot),
140
"storage graph root directory (graphroot)",
141
filepath.Clean(rt.StorageConfig().GraphRoot),
147
"storage graph driver",
148
rt.StorageConfig().GraphDriverName,
150
storeOpts.GraphDriverName,
155
rt.config.Engine.VolumePath,
162
// These fields were missing and will have to be recreated.
163
missingFields := []dbConfigValidation{}
165
// Let's try and validate read-only first
166
err = db.View(func(tx *bolt.Tx) error {
167
configBkt, err := getRuntimeConfigBucket(tx)
172
for _, check := range checks {
173
exists, err := readOnlyValidateConfig(configBkt, check)
178
missingFields = append(missingFields, check)
188
if len(missingFields) == 0 {
192
// Populate missing fields
193
return db.Update(func(tx *bolt.Tx) error {
194
configBkt, err := getRuntimeConfigBucket(tx)
199
for _, missing := range missingFields {
200
dbValue := []byte(missing.runtimeValue)
201
if missing.runtimeValue == "" && missing.defaultValue != "" {
202
dbValue = []byte(missing.defaultValue)
205
if err := configBkt.Put(missing.key, dbValue); err != nil {
206
return fmt.Errorf("updating %s in DB runtime config: %w", missing.name, err)
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
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)
228
// False return indicates missing key
232
dbValue := string(keyBytes)
233
ourValue := toCheck.runtimeValue
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.
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)
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)
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 {
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 {
270
return true, fmt.Errorf("database %s %q does not match our %s %q: %w",
271
toCheck.name, dbValue, toCheck.name, ourValue, define.ErrDBBadConfig)
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
286
db, err := bolt.Open(s.dbPath, 0600, nil)
288
return nil, fmt.Errorf("opening database %s: %w", s.dbPath, err)
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)
303
// Close a connection to the database.
304
// MUST be used in place of `db.Close()` to ensure proper unlocking of the
306
func (s *BoltState) closeDBCon(db *bolt.DB) error {
314
func getIDBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
315
bkt := tx.Bucket(idRegistryBkt)
317
return nil, fmt.Errorf("id registry bucket not found in DB: %w", define.ErrDBBadConfig)
322
func getNamesBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
323
bkt := tx.Bucket(nameRegistryBkt)
325
return nil, fmt.Errorf("name registry bucket not found in DB: %w", define.ErrDBBadConfig)
330
func getCtrBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
331
bkt := tx.Bucket(ctrBkt)
333
return nil, fmt.Errorf("containers bucket not found in DB: %w", define.ErrDBBadConfig)
338
func getAllCtrsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
339
bkt := tx.Bucket(allCtrsBkt)
341
return nil, fmt.Errorf("all containers bucket not found in DB: %w", define.ErrDBBadConfig)
346
func getPodBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
347
bkt := tx.Bucket(podBkt)
349
return nil, fmt.Errorf("pods bucket not found in DB: %w", define.ErrDBBadConfig)
354
func getAllPodsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
355
bkt := tx.Bucket(allPodsBkt)
357
return nil, fmt.Errorf("all pods bucket not found in DB: %w", define.ErrDBBadConfig)
362
func getVolBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
363
bkt := tx.Bucket(volBkt)
365
return nil, fmt.Errorf("volumes bucket not found in DB: %w", define.ErrDBBadConfig)
370
func getAllVolsBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
371
bkt := tx.Bucket(allVolsBkt)
373
return nil, fmt.Errorf("all volumes bucket not found in DB: %w", define.ErrDBBadConfig)
378
func getExecBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
379
bkt := tx.Bucket(execBkt)
381
return nil, fmt.Errorf("exec bucket not found in DB: %w", define.ErrDBBadConfig)
386
func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
387
bkt := tx.Bucket(runtimeConfigBkt)
389
return nil, fmt.Errorf("runtime configuration bucket not found in DB: %w", define.ErrDBBadConfig)
394
func getExitCodeBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
395
bkt := tx.Bucket(exitCodeBkt)
397
return nil, fmt.Errorf("exit-code container bucket not found in DB: %w", define.ErrDBBadConfig)
402
func getExitCodeTimeStampBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
403
bkt := tx.Bucket(exitCodeTimeStampBkt)
405
return nil, fmt.Errorf("exit-code time stamp bucket not found in DB: %w", define.ErrDBBadConfig)
410
func getVolumeContainersBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
411
bkt := tx.Bucket(volCtrsBkt)
413
return nil, fmt.Errorf("volume containers bucket not found in DB: %w", define.ErrDBBadConfig)
418
func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error {
419
ctrBkt := ctrsBkt.Bucket(id)
421
return fmt.Errorf("container %s not found in DB: %w", string(id), define.ErrNoSuchCtr)
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)
429
if err := json.Unmarshal(configBytes, config); err != nil {
430
return fmt.Errorf("unmarshalling container %s config: %w", string(id), err)
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
438
// indicate that the config was modified and should be written back to the db when possible
439
config.rewrite = true
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 {
450
return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr)
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)
458
if err := json.Unmarshal(newStateBytes, newState); err != nil {
459
return fmt.Errorf("unmarshalling container %s state: %w", ctr.ID(), err)
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)
468
// New state compiled successfully, swap it into the current state
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 {
479
if err := s.getContainerStateDB(id, ctr, ctrsBkt); err != nil {
485
lock, err := s.runtime.lockManager.RetrieveLock(ctr.config.LockID)
487
return fmt.Errorf("retrieving lock for container %s: %w", string(id), err)
491
if ctr.config.OCIRuntime == "" {
492
ctr.ociRuntime = s.runtime.defaultOCIRuntime
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]
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)
507
// The runtime lock should
508
// protect against concurrent
509
// modification of the map.
510
ociRuntime = newOCIRuntime
511
s.runtime.ociRuntimes[runtimeName] = ociRuntime
518
// Use a MissingRuntime implementation
519
ociRuntime = getMissingRuntime(runtimeName, s.runtime)
522
ctr.ociRuntime = ociRuntime
525
ctr.runtime = s.runtime
531
func (s *BoltState) getPodFromDB(id []byte, pod *Pod, podBkt *bolt.Bucket) error {
532
podDB := podBkt.Bucket(id)
534
return fmt.Errorf("pod with ID %s not found: %w", string(id), define.ErrNoSuchPod)
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)
542
if err := json.Unmarshal(podConfigBytes, pod.config); err != nil {
543
return fmt.Errorf("unmarshalling pod %s config from DB: %w", string(id), err)
547
lock, err := s.runtime.lockManager.RetrieveLock(pod.config.LockID)
549
return fmt.Errorf("retrieving lock for pod %s: %w", string(id), err)
553
pod.runtime = s.runtime
559
func (s *BoltState) getVolumeFromDB(name []byte, volume *Volume, volBkt *bolt.Bucket) error {
560
volDB := volBkt.Bucket(name)
562
return fmt.Errorf("volume with name %s not found: %w", string(name), define.ErrNoSuchVolume)
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)
570
if err := json.Unmarshal(volConfigBytes, volume.config); err != nil {
571
return fmt.Errorf("unmarshalling volume %s config from DB: %w", string(name), err)
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)
582
// Need this for UsesVolumeDriver() so set it now.
583
volume.runtime = s.runtime
585
// Retrieve volume driver
586
if volume.UsesVolumeDriver() {
587
plugin, err := s.runtime.getVolumePlugin(volume.config)
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)
596
volume.plugin = plugin
601
lock, err := s.runtime.lockManager.RetrieveLock(volume.config.LockID)
603
return fmt.Errorf("retrieving lock for volume %q: %w", string(name), err)
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
620
// JSON container structs to insert into DB
621
configJSON, err := json.Marshal(ctr.config)
623
return fmt.Errorf("marshalling container %s config to JSON: %w", ctr.ID(), err)
625
stateJSON, err := json.Marshal(ctr.state)
627
return fmt.Errorf("marshalling container %s state to JSON: %w", ctr.ID(), err)
629
dependsCtrs := ctr.Dependencies()
631
ctrID := []byte(ctr.ID())
632
ctrName := []byte(ctr.Name())
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
639
return fmt.Errorf("network names cannot be an empty string: %w", define.ErrInvalidArg)
641
if opts.InterfaceName == "" {
642
return fmt.Errorf("network interface name cannot be an empty string: %w", define.ErrInvalidArg)
644
optBytes, err := json.Marshal(opts)
646
return fmt.Errorf("marshalling network options JSON for container %s: %w", ctr.ID(), err)
648
networks[net] = optBytes
651
db, err := s.getDBCon()
655
defer s.deferredCloseDBCon(db)
657
err = db.Update(func(tx *bolt.Tx) error {
658
idsBucket, err := getIDBucket(tx)
663
namesBucket, err := getNamesBucket(tx)
668
ctrBucket, err := getCtrBucket(tx)
673
allCtrsBucket, err := getAllCtrsBucket(tx)
678
volBkt, err := getVolBucket(tx)
683
// If a pod was given, check if it exists
684
var podDB *bolt.Bucket
685
var podCtrs *bolt.Bucket
687
podBucket, err := getPodBucket(tx)
692
podID := []byte(pod.ID())
694
podDB = podBucket.Bucket(podID)
697
return fmt.Errorf("pod %s does not exist in database: %w", pod.ID(), define.ErrNoSuchPod)
699
podCtrs = podDB.Bucket(containersBkt)
701
return fmt.Errorf("pod %s does not have a containers bucket: %w", pod.ID(), define.ErrInternal)
705
// Check if we already have a container with the given ID and name
706
idExist := idsBucket.Get(ctrID)
708
err = define.ErrCtrExists
709
if allCtrsBucket.Get(idExist) == nil {
710
err = define.ErrPodExists
712
return fmt.Errorf("ID \"%s\" is in use: %w", ctr.ID(), err)
714
nameExist := namesBucket.Get(ctrName)
715
if nameExist != nil {
716
err = define.ErrCtrExists
717
if allCtrsBucket.Get(nameExist) == nil {
718
err = define.ErrPodExists
720
return fmt.Errorf("name \"%s\" is in use: %w", ctr.Name(), err)
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)
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)
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)
735
newCtrBkt, err := ctrBucket.CreateBucket(ctrID)
737
return fmt.Errorf("adding container %s bucket to DB: %w", ctr.ID(), err)
740
if err := newCtrBkt.Put(configKey, configJSON); err != nil {
741
return fmt.Errorf("adding container %s config to DB: %w", ctr.ID(), err)
743
if err := newCtrBkt.Put(stateKey, stateJSON); err != nil {
744
return fmt.Errorf("adding container %s state to DB: %w", ctr.ID(), err)
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)
751
if len(networks) > 0 {
752
ctrNetworksBkt, err := newCtrBkt.CreateBucket(networksBkt)
754
return fmt.Errorf("creating networks bucket for container %s: %w", ctr.ID(), err)
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)
763
if _, err := newCtrBkt.CreateBucket(dependenciesBkt); err != nil {
764
return fmt.Errorf("creating dependencies bucket for container %s: %w", ctr.ID(), err)
767
// Add dependencies for the container
768
for _, dependsCtr := range dependsCtrs {
769
depCtrID := []byte(dependsCtr)
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)
776
depCtrPod := depCtrBkt.Get(podIDKey)
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)
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)
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)
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)
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)
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)
807
// Add container to named volume dependencies buckets
808
for _, vol := range ctr.config.NamedVolumes {
809
volDB := volBkt.Bucket([]byte(vol.Name))
811
return fmt.Errorf("no volume with name %s found in database when adding container %s: %w", vol.Name, ctr.ID(), define.ErrNoSuchVolume)
814
ctrDepsBkt, err := volDB.CreateBucketIfNotExists(volDependenciesBkt)
816
return fmt.Errorf("creating volume %s dependencies bucket to add container %s: %w", vol.Name, ctr.ID(), err)
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)
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())
837
idsBucket, err := getIDBucket(tx)
842
namesBucket, err := getNamesBucket(tx)
847
ctrBucket, err := getCtrBucket(tx)
852
allCtrsBucket, err := getAllCtrsBucket(tx)
857
volBkt, err := getVolBucket(tx)
862
// Does the pod exist?
863
var podDB *bolt.Bucket
865
podBucket, err := getPodBucket(tx)
870
podID := []byte(pod.ID())
872
podDB = podBucket.Bucket(podID)
875
return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod)
879
// Does the container exist?
880
ctrExists := ctrBucket.Bucket(ctrID)
881
if ctrExists == nil {
883
return fmt.Errorf("no container with ID %s found in DB: %w", ctr.ID(), define.ErrNoSuchCtr)
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)
891
logrus.Errorf("Pod %s malformed in database, missing containers bucket!", pod.ID())
893
ctrInPod := podCtrs.Get(ctrID)
895
return fmt.Errorf("container %s is not in pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr)
897
if err := podCtrs.Delete(ctrID); err != nil {
898
return fmt.Errorf("removing container %s from pod %s: %w", ctr.ID(), pod.ID(), err)
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))
915
if len(sessions) > 0 {
916
return fmt.Errorf("container %s has active exec sessions: %s: %w", ctr.ID(), strings.Join(sessions, ", "), define.ErrExecSessionExists)
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)
926
err = ctrDepsBkt.ForEach(func(id, value []byte) error {
927
deps = append(deps, string(id))
935
return fmt.Errorf("container %s is a dependency of the following containers: %s: %w", ctr.ID(), strings.Join(deps, ", "), define.ErrDepExists)
938
if err := ctrBucket.DeleteBucket(ctrID); err != nil {
939
return fmt.Errorf("deleting container %s from DB: %w", ctr.ID(), define.ErrInternal)
942
if err := idsBucket.Delete(ctrID); err != nil {
943
return fmt.Errorf("deleting container %s ID in DB: %w", ctr.ID(), err)
946
if err := namesBucket.Delete(ctrName); err != nil {
947
return fmt.Errorf("deleting container %s name in DB: %w", ctr.ID(), err)
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)
953
depCtrs := ctr.Dependencies()
955
// Remove us from other container's dependencies
956
for _, depCtr := range depCtrs {
957
depCtrID := []byte(depCtr)
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
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())
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)
983
// Remove container from named volume dependencies buckets
984
for _, vol := range ctr.config.NamedVolumes {
985
volDB := volBkt.Bucket([]byte(vol.Name))
987
// Let's assume the volume was already deleted and
988
// continue to remove the container
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)
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)
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
1016
// Next, check if the full name was given
1018
fullID := namesBucket.Get([]byte(idOrName))
1020
// The name exists and maps to an ID.
1021
// However, we are not yet certain the ID is a
1023
ctrExists = ctrBucket.Bucket(fullID)
1024
if ctrExists != nil {
1025
// A container bucket matching the full ID was
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
1038
// We were not given a full container ID or name.
1039
// Search for partial ID matches.
1041
err := ctrBucket.ForEach(func(checkID, checkName []byte) error {
1042
if strings.HasPrefix(string(checkID), idOrName) {
1044
return fmt.Errorf("more than one result for container ID %s: %w", idOrName, define.ErrCtrExists)
1057
return nil, fmt.Errorf("%q is a pod, not a container: %w", idOrName, define.ErrNoSuchCtr)
1059
return nil, fmt.Errorf("no container with name or ID %q found: %w", idOrName, define.ErrNoSuchCtr)