podman

Форк
0
/
sqlite_state.go 
2300 строк · 62.4 Кб
1
//go:build !remote
2

3
package libpod
4

5
import (
6
	"database/sql"
7
	"errors"
8
	"fmt"
9
	"io/fs"
10
	"os"
11
	"path/filepath"
12
	goruntime "runtime"
13
	"strings"
14
	"time"
15

16
	"github.com/containers/common/libnetwork/types"
17
	"github.com/containers/podman/v5/libpod/define"
18
	"github.com/containers/storage"
19
	"github.com/sirupsen/logrus"
20

21
	// SQLite backend for database/sql
22
	_ "github.com/mattn/go-sqlite3"
23
)
24

25
const schemaVersion = 1
26

27
// SQLiteState is a state implementation backed by a SQLite database
28
type SQLiteState struct {
29
	valid   bool
30
	conn    *sql.DB
31
	runtime *Runtime
32
}
33

34
const (
35
	// Deal with timezone automatically.
36
	sqliteOptionLocation = "_loc=auto"
37
	// Force an fsync after each transaction (https://www.sqlite.org/pragma.html#pragma_synchronous).
38
	sqliteOptionSynchronous = "&_sync=FULL"
39
	// Allow foreign keys (https://www.sqlite.org/pragma.html#pragma_foreign_keys).
40
	sqliteOptionForeignKeys = "&_foreign_keys=1"
41
	// Make sure that transactions happen exclusively.
42
	sqliteOptionTXLock = "&_txlock=exclusive"
43
	// Make sure busy timeout is set to high value to keep retrying when the db is locked.
44
	// Timeout is in ms, so set it to 100s to have enough time to retry the operations.
45
	sqliteOptionBusyTimeout = "&_busy_timeout=100000"
46

47
	// Assembled sqlite options used when opening the database.
48
	sqliteOptions = "db.sql?" +
49
		sqliteOptionLocation +
50
		sqliteOptionSynchronous +
51
		sqliteOptionForeignKeys +
52
		sqliteOptionTXLock +
53
		sqliteOptionBusyTimeout
54
)
55

56
// NewSqliteState creates a new SQLite-backed state database.
57
func NewSqliteState(runtime *Runtime) (_ State, defErr error) {
58
	logrus.Info("Using sqlite as database backend")
59
	state := new(SQLiteState)
60

61
	basePath := runtime.storageConfig.GraphRoot
62
	if runtime.storageConfig.TransientStore {
63
		basePath = runtime.storageConfig.RunRoot
64
	} else if !runtime.storageSet.StaticDirSet {
65
		basePath = runtime.config.Engine.StaticDir
66
	}
67

68
	// c/storage is set up *after* the DB - so even though we use the c/s
69
	// root (or, for transient, runroot) dir, we need to make the dir
70
	// ourselves.
71
	if err := os.MkdirAll(basePath, 0700); err != nil {
72
		return nil, fmt.Errorf("creating root directory: %w", err)
73
	}
74

75
	conn, err := sql.Open("sqlite3", filepath.Join(basePath, sqliteOptions))
76
	if err != nil {
77
		return nil, fmt.Errorf("initializing sqlite database: %w", err)
78
	}
79
	defer func() {
80
		if defErr != nil {
81
			if err := conn.Close(); err != nil {
82
				logrus.Errorf("Error closing SQLite DB connection: %v", err)
83
			}
84
		}
85
	}()
86

87
	if err := initSQLiteDB(conn); err != nil {
88
		return nil, err
89
	}
90

91
	state.conn = conn
92
	state.valid = true
93
	state.runtime = runtime
94

95
	return state, nil
96
}
97

98
// Close closes the state and prevents further use
99
func (s *SQLiteState) Close() error {
100
	if err := s.conn.Close(); err != nil {
101
		return err
102
	}
103

104
	s.valid = false
105
	return nil
106
}
107

108
// Refresh clears container and pod states after a reboot
109
func (s *SQLiteState) Refresh() (defErr error) {
110
	if !s.valid {
111
		return define.ErrDBClosed
112
	}
113

114
	// Retrieve all containers, pods, and volumes.
115
	// Maps are indexed by ID (or volume name) so we know which goes where,
116
	// and store the marshalled state JSON
117
	ctrStates := make(map[string]string)
118
	podStates := make(map[string]string)
119
	volumeStates := make(map[string]string)
120

121
	ctrRows, err := s.conn.Query("SELECT ID, JSON FROM ContainerState;")
122
	if err != nil {
123
		return fmt.Errorf("querying for container states: %w", err)
124
	}
125
	defer ctrRows.Close()
126

127
	for ctrRows.Next() {
128
		var (
129
			id, stateJSON string
130
		)
131
		if err := ctrRows.Scan(&id, &stateJSON); err != nil {
132
			return fmt.Errorf("scanning container state row: %w", err)
133
		}
134

135
		ctrState := new(ContainerState)
136

137
		if err := json.Unmarshal([]byte(stateJSON), ctrState); err != nil {
138
			return fmt.Errorf("unmarshalling container state json: %w", err)
139
		}
140

141
		// Refresh the state
142
		resetContainerState(ctrState)
143

144
		newJSON, err := json.Marshal(ctrState)
145
		if err != nil {
146
			return fmt.Errorf("marshalling container state json: %w", err)
147
		}
148

149
		ctrStates[id] = string(newJSON)
150
	}
151
	if err := ctrRows.Err(); err != nil {
152
		return err
153
	}
154

155
	podRows, err := s.conn.Query("SELECT ID, JSON FROM PodState;")
156
	if err != nil {
157
		return fmt.Errorf("querying for pod states: %w", err)
158
	}
159
	defer podRows.Close()
160

161
	for podRows.Next() {
162
		var (
163
			id, stateJSON string
164
		)
165
		if err := podRows.Scan(&id, &stateJSON); err != nil {
166
			return fmt.Errorf("scanning pod state row: %w", err)
167
		}
168

169
		podState := new(podState)
170

171
		if err := json.Unmarshal([]byte(stateJSON), podState); err != nil {
172
			return fmt.Errorf("unmarshalling pod state json: %w", err)
173
		}
174

175
		// Refresh the state
176
		resetPodState(podState)
177

178
		newJSON, err := json.Marshal(podState)
179
		if err != nil {
180
			return fmt.Errorf("marshalling pod state json: %w", err)
181
		}
182

183
		podStates[id] = string(newJSON)
184
	}
185
	if err := podRows.Err(); err != nil {
186
		return err
187
	}
188

189
	volRows, err := s.conn.Query("SELECT Name, JSON FROM VolumeState;")
190
	if err != nil {
191
		return fmt.Errorf("querying for volume states: %w", err)
192
	}
193
	defer volRows.Close()
194

195
	for volRows.Next() {
196
		var (
197
			name, stateJSON string
198
		)
199

200
		if err := volRows.Scan(&name, &stateJSON); err != nil {
201
			return fmt.Errorf("scanning volume state row: %w", err)
202
		}
203

204
		volState := new(VolumeState)
205

206
		if err := json.Unmarshal([]byte(stateJSON), volState); err != nil {
207
			return fmt.Errorf("unmarshalling volume state json: %w", err)
208
		}
209

210
		// Refresh the state
211
		resetVolumeState(volState)
212

213
		newJSON, err := json.Marshal(volState)
214
		if err != nil {
215
			return fmt.Errorf("marshalling volume state json: %w", err)
216
		}
217

218
		volumeStates[name] = string(newJSON)
219
	}
220
	if err := volRows.Err(); err != nil {
221
		return err
222
	}
223

224
	// Write updated states back to DB, and perform additional maintenance
225
	// (Remove exit codes and exec sessions)
226

227
	tx, err := s.conn.Begin()
228
	if err != nil {
229
		return fmt.Errorf("beginning refresh transaction: %w", err)
230
	}
231
	defer func() {
232
		if defErr != nil {
233
			if err := tx.Rollback(); err != nil {
234
				logrus.Errorf("Rolling back transaction to refresh database state: %v", err)
235
			}
236
		}
237
	}()
238

239
	for id, json := range ctrStates {
240
		if _, err := tx.Exec("UPDATE ContainerState SET JSON=? WHERE ID=?;", json, id); err != nil {
241
			return fmt.Errorf("updating container state: %w", err)
242
		}
243
	}
244
	for id, json := range podStates {
245
		if _, err := tx.Exec("UPDATE PodState SET JSON=? WHERE ID=?;", json, id); err != nil {
246
			return fmt.Errorf("updating pod state: %w", err)
247
		}
248
	}
249
	for name, json := range volumeStates {
250
		if _, err := tx.Exec("UPDATE VolumeState SET JSON=? WHERE Name=?;", json, name); err != nil {
251
			return fmt.Errorf("updating volume state: %w", err)
252
		}
253
	}
254

255
	if _, err := tx.Exec("DELETE FROM ContainerExitCode;"); err != nil {
256
		return fmt.Errorf("removing container exit codes: %w", err)
257
	}
258

259
	if _, err := tx.Exec("DELETE FROM ContainerExecSession;"); err != nil {
260
		return fmt.Errorf("removing container exec sessions: %w", err)
261
	}
262

263
	if err := tx.Commit(); err != nil {
264
		return fmt.Errorf("committing transaction: %w", err)
265
	}
266

267
	return nil
268
}
269

270
// GetDBConfig retrieves runtime configuration fields that were created when
271
// the database was first initialized
272
func (s *SQLiteState) GetDBConfig() (*DBConfig, error) {
273
	if !s.valid {
274
		return nil, define.ErrDBClosed
275
	}
276

277
	cfg := new(DBConfig)
278
	var staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumeDir string
279

280
	row := s.conn.QueryRow("SELECT StaticDir, TmpDir, GraphRoot, RunRoot, GraphDriver, VolumeDir FROM DBConfig;")
281

282
	if err := row.Scan(&staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumeDir); err != nil {
283
		if errors.Is(err, sql.ErrNoRows) {
284
			return cfg, nil
285
		}
286
		return nil, fmt.Errorf("retrieving DB config: %w", err)
287
	}
288

289
	cfg.LibpodRoot = staticDir
290
	cfg.LibpodTmp = tmpDir
291
	cfg.StorageRoot = graphRoot
292
	cfg.StorageTmp = runRoot
293
	cfg.GraphDriver = graphDriver
294
	cfg.VolumePath = volumeDir
295

296
	return cfg, nil
297
}
298

299
// ValidateDBConfig validates paths in the given runtime against the database
300
func (s *SQLiteState) ValidateDBConfig(runtime *Runtime) (defErr error) {
301
	if !s.valid {
302
		return define.ErrDBClosed
303
	}
304

305
	storeOpts, err := storage.DefaultStoreOptions()
306
	if err != nil {
307
		return err
308
	}
309

310
	const createRow = `
311
        INSERT INTO DBconfig VALUES (
312
                ?, ?, ?,
313
                ?, ?, ?,
314
                ?, ?, ?
315
        );`
316

317
	var (
318
		dbOS, staticDir, tmpDir, graphRoot, runRoot, graphDriver, volumePath string
319
		runtimeOS                                                            = goruntime.GOOS
320
		runtimeStaticDir                                                     = filepath.Clean(s.runtime.config.Engine.StaticDir)
321
		runtimeTmpDir                                                        = filepath.Clean(s.runtime.config.Engine.TmpDir)
322
		runtimeGraphRoot                                                     = filepath.Clean(s.runtime.StorageConfig().GraphRoot)
323
		runtimeRunRoot                                                       = filepath.Clean(s.runtime.StorageConfig().RunRoot)
324
		runtimeGraphDriver                                                   = s.runtime.StorageConfig().GraphDriverName
325
		runtimeVolumePath                                                    = filepath.Clean(s.runtime.config.Engine.VolumePath)
326
	)
327

328
	// Some fields may be empty, indicating they are set to the default.
329
	// If so, grab the default from c/storage for them.
330
	if runtimeGraphRoot == "" {
331
		runtimeGraphRoot = storeOpts.GraphRoot
332
	}
333
	if runtimeRunRoot == "" {
334
		runtimeRunRoot = storeOpts.RunRoot
335
	}
336
	if runtimeGraphDriver == "" {
337
		runtimeGraphDriver = storeOpts.GraphDriverName
338
	}
339

340
	// We have to do this in a transaction to ensure mutual exclusion.
341
	// Otherwise we have a race - multiple processes can be checking the
342
	// row's existence simultaneously, both try to create it, second one to
343
	// get the transaction lock gets an error.
344
	// TODO: The transaction isn't strictly necessary, and there's a (small)
345
	// chance it's a perf hit. If it is, we can move it entirely within the
346
	// `errors.Is()` block below, with extra validation to ensure the row
347
	// still does not exist (and, if it does, to retry this function).
348
	tx, err := s.conn.Begin()
349
	if err != nil {
350
		return fmt.Errorf("beginning database validation transaction: %w", err)
351
	}
352
	defer func() {
353
		if defErr != nil {
354
			if err := tx.Rollback(); err != nil {
355
				logrus.Errorf("Rolling back transaction to validate database: %v", err)
356
			}
357
		}
358
	}()
359

360
	row := tx.QueryRow("SELECT Os, StaticDir, TmpDir, GraphRoot, RunRoot, GraphDriver, VolumeDir FROM DBConfig;")
361

362
	if err := row.Scan(&dbOS, &staticDir, &tmpDir, &graphRoot, &runRoot, &graphDriver, &volumePath); err != nil {
363
		if errors.Is(err, sql.ErrNoRows) {
364
			if _, err := tx.Exec(createRow, 1, schemaVersion, runtimeOS,
365
				runtimeStaticDir, runtimeTmpDir, runtimeGraphRoot,
366
				runtimeRunRoot, runtimeGraphDriver, runtimeVolumePath); err != nil {
367
				return fmt.Errorf("adding DB config row: %w", err)
368
			}
369

370
			if err := tx.Commit(); err != nil {
371
				return fmt.Errorf("committing write of database validation row: %w", err)
372
			}
373

374
			return nil
375
		}
376

377
		return fmt.Errorf("retrieving DB config: %w", err)
378
	}
379

380
	checkField := func(fieldName, dbVal, ourVal string, isPath bool) error {
381
		if isPath {
382
			// Evaluate symlinks. Ignore ENOENT. No guarantee all
383
			// directories exist this early in Libpod init.
384
			if dbVal != "" {
385
				dbValClean, err := filepath.EvalSymlinks(dbVal)
386
				if err != nil && !errors.Is(err, fs.ErrNotExist) {
387
					return fmt.Errorf("cannot evaluate symlinks on DB %s path %q: %w", fieldName, dbVal, err)
388
				}
389
				dbVal = dbValClean
390
			}
391
			if ourVal != "" {
392
				ourValClean, err := filepath.EvalSymlinks(ourVal)
393
				if err != nil && !errors.Is(err, fs.ErrNotExist) {
394
					return fmt.Errorf("cannot evaluate symlinks on our %s path %q: %w", fieldName, ourVal, err)
395
				}
396
				ourVal = ourValClean
397
			}
398
		}
399

400
		if dbVal != ourVal {
401
			return fmt.Errorf("database %s %q does not match our %s %q: %w", fieldName, dbVal, fieldName, ourVal, define.ErrDBBadConfig)
402
		}
403

404
		return nil
405
	}
406

407
	if err := checkField("os", dbOS, runtimeOS, false); err != nil {
408
		return err
409
	}
410
	if err := checkField("static dir", staticDir, runtimeStaticDir, true); err != nil {
411
		return err
412
	}
413
	if err := checkField("tmp dir", tmpDir, runtimeTmpDir, true); err != nil {
414
		return err
415
	}
416
	if err := checkField("graph root", graphRoot, runtimeGraphRoot, true); err != nil {
417
		return err
418
	}
419
	if err := checkField("run root", runRoot, runtimeRunRoot, true); err != nil {
420
		return err
421
	}
422
	if err := checkField("graph driver", graphDriver, runtimeGraphDriver, false); err != nil {
423
		return err
424
	}
425
	if err := checkField("volume path", volumePath, runtimeVolumePath, true); err != nil {
426
		return err
427
	}
428

429
	if err := tx.Commit(); err != nil {
430
		return fmt.Errorf("committing database validation row: %w", err)
431
	}
432
	// Do not return any error after the commit call because the defer will
433
	// try to roll back the transaction which results in an logged error.
434

435
	return nil
436
}
437

438
// GetContainerName returns the name of the container associated with a given
439
// ID. Returns ErrNoSuchCtr if the ID does not exist.
440
func (s *SQLiteState) GetContainerName(id string) (string, error) {
441
	if id == "" {
442
		return "", define.ErrEmptyID
443
	}
444

445
	if !s.valid {
446
		return "", define.ErrDBClosed
447
	}
448

449
	var name string
450

451
	row := s.conn.QueryRow("SELECT Name FROM ContainerConfig WHERE ID=?;", id)
452
	if err := row.Scan(&name); err != nil {
453
		if errors.Is(err, sql.ErrNoRows) {
454
			return "", define.ErrNoSuchCtr
455
		}
456

457
		return "", fmt.Errorf("looking up container %s name: %w", id, err)
458
	}
459

460
	return name, nil
461
}
462

463
// GetPodName returns the name of the pod associated with a given ID.
464
// Returns ErrNoSuchPod if the ID does not exist.
465
func (s *SQLiteState) GetPodName(id string) (string, error) {
466
	if id == "" {
467
		return "", define.ErrEmptyID
468
	}
469

470
	if !s.valid {
471
		return "", define.ErrDBClosed
472
	}
473

474
	var name string
475

476
	row := s.conn.QueryRow("SELECT Name FROM PodConfig WHERE ID=?;", id)
477
	if err := row.Scan(&name); err != nil {
478
		if errors.Is(err, sql.ErrNoRows) {
479
			return "", define.ErrNoSuchPod
480
		}
481

482
		return "", fmt.Errorf("looking up pod %s name: %w", id, err)
483
	}
484

485
	return name, nil
486
}
487

488
// Container retrieves a single container from the state by its full ID
489
func (s *SQLiteState) Container(id string) (*Container, error) {
490
	if id == "" {
491
		return nil, define.ErrEmptyID
492
	}
493

494
	if !s.valid {
495
		return nil, define.ErrDBClosed
496
	}
497

498
	ctrConfig, err := s.getCtrConfig(id)
499
	if err != nil {
500
		return nil, err
501
	}
502

503
	ctr := new(Container)
504
	ctr.config = ctrConfig
505
	ctr.state = new(ContainerState)
506
	ctr.runtime = s.runtime
507

508
	if err := finalizeCtrSqlite(ctr); err != nil {
509
		return nil, err
510
	}
511

512
	return ctr, nil
513
}
514

515
// LookupContainerID retrieves a container ID from the state by full or unique
516
// partial ID or name
517
func (s *SQLiteState) LookupContainerID(idOrName string) (string, error) {
518
	if idOrName == "" {
519
		return "", define.ErrEmptyID
520
	}
521

522
	if !s.valid {
523
		return "", define.ErrDBClosed
524
	}
525

526
	rows, err := s.conn.Query("SELECT ID, Name FROM ContainerConfig WHERE ContainerConfig.Name=? OR (ContainerConfig.ID LIKE ?);", idOrName, idOrName+"%")
527
	if err != nil {
528
		return "", fmt.Errorf("looking up container %q in database: %w", idOrName, err)
529
	}
530
	defer rows.Close()
531

532
	var (
533
		id, name string
534
		resCount uint
535
	)
536
	for rows.Next() {
537
		if err := rows.Scan(&id, &name); err != nil {
538
			return "", fmt.Errorf("retrieving container %q ID from database: %w", idOrName, err)
539
		}
540
		if name == idOrName {
541
			return id, nil
542
		}
543
		resCount++
544
	}
545
	if err := rows.Err(); err != nil {
546
		return "", err
547
	}
548
	if resCount == 0 {
549
		return "", define.ErrNoSuchCtr
550
	} else if resCount > 1 {
551
		return "", fmt.Errorf("more than one result for container %q: %w", idOrName, define.ErrCtrExists)
552
	}
553

554
	return id, nil
555
}
556

557
// LookupContainer retrieves a container from the state by full or unique
558
// partial ID or name
559
func (s *SQLiteState) LookupContainer(idOrName string) (*Container, error) {
560
	if idOrName == "" {
561
		return nil, define.ErrEmptyID
562
	}
563

564
	if !s.valid {
565
		return nil, define.ErrDBClosed
566
	}
567

568
	rows, err := s.conn.Query("SELECT JSON, Name FROM ContainerConfig WHERE ContainerConfig.Name=? OR (ContainerConfig.ID LIKE ?);", idOrName, idOrName+"%")
569
	if err != nil {
570
		return nil, fmt.Errorf("looking up container %q in database: %w", idOrName, err)
571
	}
572
	defer rows.Close()
573

574
	var (
575
		rawJSON, name string
576
		exactName     bool
577
		resCount      uint
578
	)
579
	for rows.Next() {
580
		if err := rows.Scan(&rawJSON, &name); err != nil {
581
			return nil, fmt.Errorf("retrieving container %q ID from database: %w", idOrName, err)
582
		}
583
		if name == idOrName {
584
			exactName = true
585
			break
586
		}
587
		resCount++
588
	}
589
	if err := rows.Err(); err != nil {
590
		return nil, err
591
	}
592
	if !exactName {
593
		if resCount == 0 {
594
			return nil, fmt.Errorf("no container with name or ID %q found: %w", idOrName, define.ErrNoSuchCtr)
595
		} else if resCount > 1 {
596
			return nil, fmt.Errorf("more than one result for container %q: %w", idOrName, define.ErrCtrExists)
597
		}
598
	}
599

600
	ctr := new(Container)
601
	ctr.config = new(ContainerConfig)
602
	ctr.state = new(ContainerState)
603
	ctr.runtime = s.runtime
604

605
	if err := json.Unmarshal([]byte(rawJSON), ctr.config); err != nil {
606
		return nil, fmt.Errorf("unmarshalling container config JSON: %w", err)
607
	}
608

609
	if err := finalizeCtrSqlite(ctr); err != nil {
610
		return nil, err
611
	}
612

613
	return ctr, nil
614
}
615

616
// HasContainer checks if a container is present in the state
617
func (s *SQLiteState) HasContainer(id string) (bool, error) {
618
	if id == "" {
619
		return false, define.ErrEmptyID
620
	}
621

622
	if !s.valid {
623
		return false, define.ErrDBClosed
624
	}
625

626
	row := s.conn.QueryRow("SELECT 1 FROM ContainerConfig WHERE ID=?;", id)
627

628
	var check int
629
	if err := row.Scan(&check); err != nil {
630
		if errors.Is(err, sql.ErrNoRows) {
631
			return false, nil
632
		}
633
		return false, fmt.Errorf("looking up container %s in database: %w", id, err)
634
	} else if check != 1 {
635
		return false, fmt.Errorf("check digit for container %s lookup incorrect: %w", id, define.ErrInternal)
636
	}
637

638
	return true, nil
639
}
640

641
// AddContainer adds a container to the state
642
// The container being added cannot belong to a pod
643
func (s *SQLiteState) AddContainer(ctr *Container) error {
644
	if !s.valid {
645
		return define.ErrDBClosed
646
	}
647

648
	if !ctr.valid {
649
		return define.ErrCtrRemoved
650
	}
651

652
	if ctr.config.Pod != "" {
653
		return fmt.Errorf("cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod: %w", define.ErrInvalidArg)
654
	}
655

656
	return s.addContainer(ctr)
657
}
658

659
// RemoveContainer removes a container from the state
660
// Only removes containers not in pods - for containers that are a member of a
661
// pod, use RemoveContainerFromPod
662
func (s *SQLiteState) RemoveContainer(ctr *Container) error {
663
	if !s.valid {
664
		return define.ErrDBClosed
665
	}
666

667
	if ctr.config.Pod != "" {
668
		return fmt.Errorf("container %s is part of a pod, use RemoveContainerFromPod instead: %w", ctr.ID(), define.ErrPodExists)
669
	}
670

671
	return s.removeContainer(ctr)
672
}
673

674
// UpdateContainer updates a container's state from the database
675
func (s *SQLiteState) UpdateContainer(ctr *Container) error {
676
	if !s.valid {
677
		return define.ErrDBClosed
678
	}
679

680
	if !ctr.valid {
681
		return define.ErrCtrRemoved
682
	}
683

684
	row := s.conn.QueryRow("SELECT JSON FROM ContainerState WHERE ID=?;", ctr.ID())
685

686
	var rawJSON string
687
	if err := row.Scan(&rawJSON); err != nil {
688
		if errors.Is(err, sql.ErrNoRows) {
689
			// Container was removed
690
			ctr.valid = false
691
			return fmt.Errorf("no container with ID %s found in database: %w", ctr.ID(), define.ErrNoSuchCtr)
692
		}
693
	}
694

695
	newState := new(ContainerState)
696
	if err := json.Unmarshal([]byte(rawJSON), newState); err != nil {
697
		return fmt.Errorf("unmarshalling container %s state JSON: %w", ctr.ID(), err)
698
	}
699

700
	ctr.state = newState
701

702
	return nil
703
}
704

705
// SaveContainer saves a container's current state in the database
706
func (s *SQLiteState) SaveContainer(ctr *Container) (defErr error) {
707
	if !s.valid {
708
		return define.ErrDBClosed
709
	}
710

711
	if !ctr.valid {
712
		return define.ErrCtrRemoved
713
	}
714

715
	stateJSON, err := json.Marshal(ctr.state)
716
	if err != nil {
717
		return fmt.Errorf("marshalling container %s state JSON: %w", ctr.ID(), err)
718
	}
719

720
	tx, err := s.conn.Begin()
721
	if err != nil {
722
		return fmt.Errorf("beginning container %s save transaction: %w", ctr.ID(), err)
723
	}
724
	defer func() {
725
		if defErr != nil {
726
			if err := tx.Rollback(); err != nil {
727
				logrus.Errorf("Rolling back transaction to save container %s state: %v", ctr.ID(), err)
728
			}
729
		}
730
	}()
731

732
	result, err := tx.Exec("UPDATE ContainerState SET JSON=? WHERE ID=?;", stateJSON, ctr.ID())
733
	if err != nil {
734
		return fmt.Errorf("writing container %s state: %w", ctr.ID(), err)
735
	}
736
	rows, err := result.RowsAffected()
737
	if err != nil {
738
		return fmt.Errorf("retrieving container %s save rows affected: %w", ctr.ID(), err)
739
	}
740
	if rows == 0 {
741
		ctr.valid = false
742
		return define.ErrNoSuchCtr
743
	}
744

745
	if err := tx.Commit(); err != nil {
746
		return fmt.Errorf("committing container %s state: %w", ctr.ID(), err)
747
	}
748

749
	return nil
750
}
751

752
// ContainerInUse checks if other containers depend on the given container
753
// It returns a slice of the IDs of the containers depending on the given
754
// container. If the slice is empty, no containers depend on the given container
755
func (s *SQLiteState) ContainerInUse(ctr *Container) ([]string, error) {
756
	if !s.valid {
757
		return nil, define.ErrDBClosed
758
	}
759

760
	if !ctr.valid {
761
		return nil, define.ErrCtrRemoved
762
	}
763

764
	rows, err := s.conn.Query("SELECT ID FROM ContainerDependency WHERE DependencyID=?;", ctr.ID())
765
	if err != nil {
766
		return nil, fmt.Errorf("retrieving containers that depend on container %s: %w", ctr.ID(), err)
767
	}
768
	defer rows.Close()
769

770
	deps := []string{}
771
	for rows.Next() {
772
		var dep string
773
		if err := rows.Scan(&dep); err != nil {
774
			return nil, fmt.Errorf("reading containers that depend on %s: %w", ctr.ID(), err)
775
		}
776
		deps = append(deps, dep)
777
	}
778
	if err := rows.Err(); err != nil {
779
		return nil, err
780
	}
781

782
	return deps, nil
783
}
784

785
// AllContainers retrieves all the containers in the database
786
// If `loadState` is set, the containers' state will be loaded as well.
787
func (s *SQLiteState) AllContainers(loadState bool) ([]*Container, error) {
788
	if !s.valid {
789
		return nil, define.ErrDBClosed
790
	}
791

792
	ctrs := []*Container{}
793

794
	if loadState {
795
		rows, err := s.conn.Query("SELECT ContainerConfig.JSON, ContainerState.JSON AS StateJSON FROM ContainerConfig INNER JOIN ContainerState ON ContainerConfig.ID = ContainerState.ID;")
796
		if err != nil {
797
			return nil, fmt.Errorf("retrieving all containers from database: %w", err)
798
		}
799
		defer rows.Close()
800

801
		for rows.Next() {
802
			var configJSON, stateJSON string
803
			if err := rows.Scan(&configJSON, &stateJSON); err != nil {
804
				return nil, fmt.Errorf("scanning container from database: %w", err)
805
			}
806

807
			ctr := new(Container)
808
			ctr.config = new(ContainerConfig)
809
			ctr.state = new(ContainerState)
810
			ctr.runtime = s.runtime
811

812
			if err := json.Unmarshal([]byte(configJSON), ctr.config); err != nil {
813
				return nil, fmt.Errorf("unmarshalling container config: %w", err)
814
			}
815
			if err := json.Unmarshal([]byte(stateJSON), ctr.state); err != nil {
816
				return nil, fmt.Errorf("unmarshalling container %s state: %w", ctr.ID(), err)
817
			}
818

819
			ctrs = append(ctrs, ctr)
820
		}
821
		if err := rows.Err(); err != nil {
822
			return nil, err
823
		}
824
	} else {
825
		rows, err := s.conn.Query("SELECT JSON FROM ContainerConfig;")
826
		if err != nil {
827
			return nil, fmt.Errorf("retrieving all containers from database: %w", err)
828
		}
829
		defer rows.Close()
830

831
		for rows.Next() {
832
			var rawJSON string
833
			if err := rows.Scan(&rawJSON); err != nil {
834
				return nil, fmt.Errorf("scanning container from database: %w", err)
835
			}
836

837
			ctr := new(Container)
838
			ctr.config = new(ContainerConfig)
839
			ctr.state = new(ContainerState)
840
			ctr.runtime = s.runtime
841

842
			if err := json.Unmarshal([]byte(rawJSON), ctr.config); err != nil {
843
				return nil, fmt.Errorf("unmarshalling container config: %w", err)
844
			}
845

846
			ctrs = append(ctrs, ctr)
847
		}
848
		if err := rows.Err(); err != nil {
849
			return nil, err
850
		}
851
	}
852

853
	for _, ctr := range ctrs {
854
		if err := finalizeCtrSqlite(ctr); err != nil {
855
			return nil, err
856
		}
857
	}
858

859
	return ctrs, nil
860
}
861

862
// GetNetworks returns the networks this container is a part of.
863
func (s *SQLiteState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) {
864
	if !s.valid {
865
		return nil, define.ErrDBClosed
866
	}
867

868
	if !ctr.valid {
869
		return nil, define.ErrCtrRemoved
870
	}
871

872
	// if the network mode is not bridge return no networks
873
	if !ctr.config.NetMode.IsBridge() {
874
		return nil, nil
875
	}
876

877
	cfg, err := s.getCtrConfig(ctr.ID())
878
	if err != nil {
879
		if errors.Is(err, define.ErrNoSuchCtr) {
880
			ctr.valid = false
881
		}
882
		return nil, err
883
	}
884

885
	return cfg.Networks, nil
886
}
887

888
// NetworkConnect adds the given container to the given network. If aliases are
889
// specified, those will be added to the given network.
890
func (s *SQLiteState) NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error {
891
	return s.networkModify(ctr, network, opts, true, false)
892
}
893

894
// NetworkModify will allow you to set new options on an existing connected network
895
func (s *SQLiteState) NetworkModify(ctr *Container, network string, opts types.PerNetworkOptions) error {
896
	return s.networkModify(ctr, network, opts, false, false)
897
}
898

899
// NetworkDisconnect disconnects the container from the given network, also
900
// removing any aliases in the network.
901
func (s *SQLiteState) NetworkDisconnect(ctr *Container, network string) error {
902
	return s.networkModify(ctr, network, types.PerNetworkOptions{}, false, true)
903
}
904

905
// GetContainerConfig returns a container config from the database by full ID
906
func (s *SQLiteState) GetContainerConfig(id string) (*ContainerConfig, error) {
907
	if len(id) == 0 {
908
		return nil, define.ErrEmptyID
909
	}
910

911
	if !s.valid {
912
		return nil, define.ErrDBClosed
913
	}
914

915
	return s.getCtrConfig(id)
916
}
917

918
// AddContainerExitCode adds the exit code for the specified container to the database.
919
func (s *SQLiteState) AddContainerExitCode(id string, exitCode int32) (defErr error) {
920
	if len(id) == 0 {
921
		return define.ErrEmptyID
922
	}
923

924
	if !s.valid {
925
		return define.ErrDBClosed
926
	}
927

928
	tx, err := s.conn.Begin()
929
	if err != nil {
930
		return fmt.Errorf("beginning transaction to add exit code: %w", err)
931
	}
932
	defer func() {
933
		if defErr != nil {
934
			if err := tx.Rollback(); err != nil {
935
				logrus.Errorf("Rolling back transaction to add exit code: %v", err)
936
			}
937
		}
938
	}()
939

940
	if _, err := tx.Exec("INSERT OR REPLACE INTO ContainerExitCode VALUES (?, ?, ?);", id, time.Now().Unix(), exitCode); err != nil {
941
		return fmt.Errorf("adding container %s exit code %d: %w", id, exitCode, err)
942
	}
943

944
	if err := tx.Commit(); err != nil {
945
		return fmt.Errorf("committing transaction to add exit code: %w", err)
946
	}
947

948
	return nil
949
}
950

951
// GetContainerExitCode returns the exit code for the specified container.
952
func (s *SQLiteState) GetContainerExitCode(id string) (int32, error) {
953
	if len(id) == 0 {
954
		return -1, define.ErrEmptyID
955
	}
956

957
	if !s.valid {
958
		return -1, define.ErrDBClosed
959
	}
960

961
	row := s.conn.QueryRow("SELECT ExitCode FROM ContainerExitCode WHERE ID=?;", id)
962
	var exitCode int32 = -1
963
	if err := row.Scan(&exitCode); err != nil {
964
		if errors.Is(err, sql.ErrNoRows) {
965
			return -1, fmt.Errorf("getting exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode)
966
		}
967
		return -1, fmt.Errorf("scanning exit code of container %s: %w", id, err)
968
	}
969

970
	return exitCode, nil
971
}
972

973
// GetContainerExitCodeTimeStamp returns the time stamp when the exit code of
974
// the specified container was added to the database.
975
func (s *SQLiteState) GetContainerExitCodeTimeStamp(id string) (*time.Time, error) {
976
	if len(id) == 0 {
977
		return nil, define.ErrEmptyID
978
	}
979

980
	if !s.valid {
981
		return nil, define.ErrDBClosed
982
	}
983

984
	row := s.conn.QueryRow("SELECT Timestamp FROM ContainerExitCode WHERE ID=?;", id)
985

986
	var timestamp int64
987
	if err := row.Scan(&timestamp); err != nil {
988
		if errors.Is(err, sql.ErrNoRows) {
989
			return nil, fmt.Errorf("getting timestamp for exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode)
990
		}
991
		return nil, fmt.Errorf("scanning exit timestamp of container %s: %w", id, err)
992
	}
993

994
	result := time.Unix(timestamp, 0)
995

996
	return &result, nil
997
}
998

999
// PruneExitCodes removes exit codes older than 5 minutes unless the associated
1000
// container still exists.
1001
func (s *SQLiteState) PruneContainerExitCodes() (defErr error) {
1002
	if !s.valid {
1003
		return define.ErrDBClosed
1004
	}
1005

1006
	fiveMinsAgo := time.Now().Add(-5 * time.Minute).Unix()
1007

1008
	tx, err := s.conn.Begin()
1009
	if err != nil {
1010
		return fmt.Errorf("beginning transaction to remove old timestamps: %w", err)
1011
	}
1012
	defer func() {
1013
		if defErr != nil {
1014
			if err := tx.Rollback(); err != nil {
1015
				logrus.Errorf("Rolling back transaction to remove old timestamps: %v", err)
1016
			}
1017
		}
1018
	}()
1019

1020
	if _, err := tx.Exec("DELETE FROM ContainerExitCode WHERE (Timestamp <= ?) AND (ID NOT IN (SELECT ID FROM ContainerConfig))", fiveMinsAgo); err != nil {
1021
		return fmt.Errorf("removing exit codes with timestamps older than 5 minutes: %w", err)
1022
	}
1023

1024
	if err := tx.Commit(); err != nil {
1025
		return fmt.Errorf("committing transaction to remove old timestamps: %w", err)
1026
	}
1027

1028
	return nil
1029
}
1030

1031
// AddExecSession adds an exec session to the state.
1032
func (s *SQLiteState) AddExecSession(ctr *Container, session *ExecSession) (defErr error) {
1033
	if !s.valid {
1034
		return define.ErrDBClosed
1035
	}
1036

1037
	if !ctr.valid {
1038
		return define.ErrCtrRemoved
1039
	}
1040

1041
	tx, err := s.conn.Begin()
1042
	if err != nil {
1043
		return fmt.Errorf("beginning container %s exec session %s add transaction: %w", ctr.ID(), session.Id, err)
1044
	}
1045
	defer func() {
1046
		if defErr != nil {
1047
			if err := tx.Rollback(); err != nil {
1048
				logrus.Errorf("Rolling back transaction to add container %s exec session %s: %v", ctr.ID(), session.Id, err)
1049
			}
1050
		}
1051
	}()
1052

1053
	if _, err := tx.Exec("INSERT INTO ContainerExecSession VALUES (?, ?);", session.Id, ctr.ID()); err != nil {
1054
		return fmt.Errorf("adding container %s exec session %s to database: %w", ctr.ID(), session.Id, err)
1055
	}
1056

1057
	if err := tx.Commit(); err != nil {
1058
		return fmt.Errorf("committing container %s exec session %s addition: %w", ctr.ID(), session.Id, err)
1059
	}
1060

1061
	return nil
1062
}
1063

1064
// GetExecSession returns the ID of the container an exec session is associated
1065
// with.
1066
func (s *SQLiteState) GetExecSession(id string) (string, error) {
1067
	if !s.valid {
1068
		return "", define.ErrDBClosed
1069
	}
1070

1071
	if id == "" {
1072
		return "", define.ErrEmptyID
1073
	}
1074

1075
	row := s.conn.QueryRow("SELECT ContainerID FROM ContainerExecSession WHERE ID=?;", id)
1076

1077
	var ctrID string
1078
	if err := row.Scan(&ctrID); err != nil {
1079
		if errors.Is(err, sql.ErrNoRows) {
1080
			return "", fmt.Errorf("no exec session with ID %s found: %w", id, define.ErrNoSuchExecSession)
1081
		}
1082
		return "", fmt.Errorf("retrieving exec session %s from database: %w", id, err)
1083
	}
1084

1085
	return ctrID, nil
1086
}
1087

1088
// RemoveExecSession removes references to the given exec session in the
1089
// database.
1090
func (s *SQLiteState) RemoveExecSession(session *ExecSession) (defErr error) {
1091
	if !s.valid {
1092
		return define.ErrDBClosed
1093
	}
1094

1095
	tx, err := s.conn.Begin()
1096
	if err != nil {
1097
		return fmt.Errorf("beginning container %s exec session %s remove transaction: %w", session.ContainerId, session.Id, err)
1098
	}
1099
	defer func() {
1100
		if defErr != nil {
1101
			if err := tx.Rollback(); err != nil {
1102
				logrus.Errorf("Rolling back transaction to remove container %s exec session %s: %v", session.ContainerId, session.Id, err)
1103
			}
1104
		}
1105
	}()
1106

1107
	result, err := tx.Exec("DELETE FROM ContainerExecSession WHERE ID=?;", session.Id)
1108
	if err != nil {
1109
		return fmt.Errorf("removing container %s exec session %s from database: %w", session.ContainerId, session.Id, err)
1110
	}
1111
	rows, err := result.RowsAffected()
1112
	if err != nil {
1113
		return fmt.Errorf("retrieving container %s exec session %s removal rows modified: %w", session.ContainerId, session.Id, err)
1114
	}
1115
	if rows == 0 {
1116
		return define.ErrNoSuchExecSession
1117
	}
1118

1119
	if err := tx.Commit(); err != nil {
1120
		return fmt.Errorf("committing container %s exec session %s removal: %w", session.ContainerId, session.Id, err)
1121
	}
1122

1123
	return nil
1124
}
1125

1126
// GetContainerExecSessions retrieves the IDs of all exec sessions running in a
1127
// container that the database is aware of (IE, were added via AddExecSession).
1128
func (s *SQLiteState) GetContainerExecSessions(ctr *Container) ([]string, error) {
1129
	if !s.valid {
1130
		return nil, define.ErrDBClosed
1131
	}
1132

1133
	if !ctr.valid {
1134
		return nil, define.ErrCtrRemoved
1135
	}
1136

1137
	rows, err := s.conn.Query("SELECT ID FROM ContainerExecSession WHERE ContainerID=?;", ctr.ID())
1138
	if err != nil {
1139
		return nil, fmt.Errorf("querying container %s exec sessions: %w", ctr.ID(), err)
1140
	}
1141
	defer rows.Close()
1142

1143
	var sessions []string
1144
	for rows.Next() {
1145
		var session string
1146
		if err := rows.Scan(&session); err != nil {
1147
			return nil, fmt.Errorf("scanning container %s exec sessions row: %w", ctr.ID(), err)
1148
		}
1149
		sessions = append(sessions, session)
1150
	}
1151
	if err := rows.Err(); err != nil {
1152
		return nil, err
1153
	}
1154

1155
	return sessions, nil
1156
}
1157

1158
// RemoveContainerExecSessions removes all exec sessions attached to a given
1159
// container.
1160
func (s *SQLiteState) RemoveContainerExecSessions(ctr *Container) (defErr error) {
1161
	if !s.valid {
1162
		return define.ErrDBClosed
1163
	}
1164

1165
	if !ctr.valid {
1166
		return define.ErrCtrRemoved
1167
	}
1168

1169
	tx, err := s.conn.Begin()
1170
	if err != nil {
1171
		return fmt.Errorf("beginning container %s exec session removal transaction: %w", ctr.ID(), err)
1172
	}
1173
	defer func() {
1174
		if defErr != nil {
1175
			if err := tx.Rollback(); err != nil {
1176
				logrus.Errorf("Rolling back transaction to remove container %s exec sessions: %v", ctr.ID(), err)
1177
			}
1178
		}
1179
	}()
1180

1181
	if _, err := tx.Exec("DELETE FROM ContainerExecSession WHERE ContainerID=?;", ctr.ID()); err != nil {
1182
		return fmt.Errorf("removing container %s exec sessions from database: %w", ctr.ID(), err)
1183
	}
1184

1185
	if err := tx.Commit(); err != nil {
1186
		return fmt.Errorf("committing container %s exec session removal: %w", ctr.ID(), err)
1187
	}
1188

1189
	return nil
1190
}
1191

1192
// RewriteContainerConfig rewrites a container's configuration.
1193
// DO NOT USE TO: Change container dependencies, change pod membership, change
1194
// container ID.
1195
// WARNING: This function is DANGEROUS. Do not use without reading the full
1196
// comment on this function in state.go.
1197
// TODO: Once BoltDB is removed, this can be combined with SafeRewriteContainerConfig.
1198
func (s *SQLiteState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error {
1199
	if !s.valid {
1200
		return define.ErrDBClosed
1201
	}
1202

1203
	if !ctr.valid {
1204
		return define.ErrCtrRemoved
1205
	}
1206

1207
	return s.rewriteContainerConfig(ctr, newCfg)
1208
}
1209

1210
// SafeRewriteContainerConfig rewrites a container's configuration in a more
1211
// limited fashion than RewriteContainerConfig. It is marked as safe to use
1212
// under most circumstances, unlike RewriteContainerConfig.
1213
// DO NOT USE TO: Change container dependencies, change pod membership, change
1214
// locks, change container ID.
1215
// TODO: Once BoltDB is removed, this can be combined with RewriteContainerConfig.
1216
func (s *SQLiteState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error {
1217
	if !s.valid {
1218
		return define.ErrDBClosed
1219
	}
1220

1221
	if !ctr.valid {
1222
		return define.ErrCtrRemoved
1223
	}
1224

1225
	if newName != "" && newCfg.Name != newName {
1226
		return fmt.Errorf("new name %s for container %s must match name in given container config: %w", newName, ctr.ID(), define.ErrInvalidArg)
1227
	}
1228
	if newName != "" && oldName == "" {
1229
		return fmt.Errorf("must provide old name for container if a new name is given: %w", define.ErrInvalidArg)
1230
	}
1231

1232
	return s.rewriteContainerConfig(ctr, newCfg)
1233
}
1234

1235
// RewritePodConfig rewrites a pod's configuration.
1236
// WARNING: This function is DANGEROUS. Do not use without reading the full
1237
// comment on this function in state.go.
1238
func (s *SQLiteState) RewritePodConfig(pod *Pod, newCfg *PodConfig) (defErr error) {
1239
	if !s.valid {
1240
		return define.ErrDBClosed
1241
	}
1242

1243
	if !pod.valid {
1244
		return define.ErrPodRemoved
1245
	}
1246

1247
	json, err := json.Marshal(newCfg)
1248
	if err != nil {
1249
		return fmt.Errorf("error marshalling pod %s config JSON: %w", pod.ID(), err)
1250
	}
1251

1252
	tx, err := s.conn.Begin()
1253
	if err != nil {
1254
		return fmt.Errorf("beginning transaction to rewrite pod %s config: %w", pod.ID(), err)
1255
	}
1256
	defer func() {
1257
		if defErr != nil {
1258
			if err := tx.Rollback(); err != nil {
1259
				logrus.Errorf("Rolling back transaction to rewrite pod %s config: %v", pod.ID(), err)
1260
			}
1261
		}
1262
	}()
1263

1264
	results, err := tx.Exec("UPDATE PodConfig SET Name=?, JSON=? WHERE ID=?;", newCfg.Name, json, pod.ID())
1265
	if err != nil {
1266
		return fmt.Errorf("updating pod config table with new configuration for pod %s: %w", pod.ID(), err)
1267
	}
1268
	rows, err := results.RowsAffected()
1269
	if err != nil {
1270
		return fmt.Errorf("retrieving pod %s config rewrite rows affected: %w", pod.ID(), err)
1271
	}
1272
	if rows == 0 {
1273
		pod.valid = false
1274
		return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod)
1275
	}
1276

1277
	if err := tx.Commit(); err != nil {
1278
		return fmt.Errorf("committing transaction to rewrite pod %s config: %w", pod.ID(), err)
1279
	}
1280

1281
	return nil
1282
}
1283

1284
// RewriteVolumeConfig rewrites a volume's configuration.
1285
// WARNING: This function is DANGEROUS. Do not use without reading the full
1286
// comment on this function in state.go.
1287
func (s *SQLiteState) RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) (defErr error) {
1288
	if !s.valid {
1289
		return define.ErrDBClosed
1290
	}
1291

1292
	if !volume.valid {
1293
		return define.ErrVolumeRemoved
1294
	}
1295

1296
	json, err := json.Marshal(newCfg)
1297
	if err != nil {
1298
		return fmt.Errorf("error marshalling volume %s new config JSON: %w", volume.Name(), err)
1299
	}
1300

1301
	tx, err := s.conn.Begin()
1302
	if err != nil {
1303
		return fmt.Errorf("beginning transaction to rewrite volume %s config: %w", volume.Name(), err)
1304
	}
1305
	defer func() {
1306
		if defErr != nil {
1307
			if err := tx.Rollback(); err != nil {
1308
				logrus.Errorf("Rolling back transaction to rewrite volume %s config: %v", volume.Name(), err)
1309
			}
1310
		}
1311
	}()
1312

1313
	results, err := tx.Exec("UPDATE VolumeConfig SET Name=?, JSON=? WHERE ID=?;", newCfg.Name, json, volume.Name())
1314
	if err != nil {
1315
		return fmt.Errorf("updating volume config table with new configuration for volume %s: %w", volume.Name(), err)
1316
	}
1317
	rows, err := results.RowsAffected()
1318
	if err != nil {
1319
		return fmt.Errorf("retrieving volume %s config rewrite rows affected: %w", volume.Name(), err)
1320
	}
1321
	if rows == 0 {
1322
		volume.valid = false
1323
		return fmt.Errorf("no volume with name %q found in DB: %w", volume.Name(), define.ErrNoSuchVolume)
1324
	}
1325

1326
	if err := tx.Commit(); err != nil {
1327
		return fmt.Errorf("committing transaction to rewrite volume %s config: %w", volume.Name(), err)
1328
	}
1329

1330
	return nil
1331
}
1332

1333
// Pod retrieves a pod given its full ID
1334
func (s *SQLiteState) Pod(id string) (*Pod, error) {
1335
	if id == "" {
1336
		return nil, define.ErrEmptyID
1337
	}
1338

1339
	if !s.valid {
1340
		return nil, define.ErrDBClosed
1341
	}
1342

1343
	row := s.conn.QueryRow("SELECT JSON FROM PodConfig WHERE ID=?;", id)
1344
	var rawJSON string
1345
	if err := row.Scan(&rawJSON); err != nil {
1346
		if errors.Is(err, sql.ErrNoRows) {
1347
			return nil, define.ErrNoSuchPod
1348
		}
1349
		return nil, fmt.Errorf("retrieving pod %s config from DB: %w", id, err)
1350
	}
1351

1352
	ctrCfg := new(ContainerConfig)
1353
	if err := json.Unmarshal([]byte(rawJSON), ctrCfg); err != nil {
1354
		return nil, fmt.Errorf("unmarshalling container %s config: %w", id, err)
1355
	}
1356

1357
	return s.createPod(rawJSON)
1358
}
1359

1360
// LookupPod retrieves a pod from a full or unique partial ID, or a name.
1361
func (s *SQLiteState) LookupPod(idOrName string) (*Pod, error) {
1362
	if idOrName == "" {
1363
		return nil, define.ErrEmptyID
1364
	}
1365

1366
	if !s.valid {
1367
		return nil, define.ErrDBClosed
1368
	}
1369

1370
	rows, err := s.conn.Query("SELECT JSON, Name FROM PodConfig WHERE PodConfig.Name=? OR (PodConfig.ID LIKE ?);", idOrName, idOrName+"%")
1371
	if err != nil {
1372
		return nil, fmt.Errorf("looking up pod %q in database: %w", idOrName, err)
1373
	}
1374
	defer rows.Close()
1375

1376
	var (
1377
		rawJSON, name string
1378
		exactName     bool
1379
		resCount      uint
1380
	)
1381
	for rows.Next() {
1382
		if err := rows.Scan(&rawJSON, &name); err != nil {
1383
			return nil, fmt.Errorf("error retrieving pod %q ID from database: %w", idOrName, err)
1384
		}
1385
		if name == idOrName {
1386
			exactName = true
1387
			break
1388
		}
1389
		resCount++
1390
	}
1391
	if err := rows.Err(); err != nil {
1392
		return nil, err
1393
	}
1394
	if !exactName {
1395
		if resCount == 0 {
1396
			return nil, fmt.Errorf("no pod with name or ID %s found: %w", idOrName, define.ErrNoSuchPod)
1397
		} else if resCount > 1 {
1398
			return nil, fmt.Errorf("more than one result for pod %q: %w", idOrName, define.ErrCtrExists)
1399
		}
1400
	}
1401

1402
	return s.createPod(rawJSON)
1403
}
1404

1405
// HasPod checks if a pod with the given ID exists in the state
1406
func (s *SQLiteState) HasPod(id string) (bool, error) {
1407
	if id == "" {
1408
		return false, define.ErrEmptyID
1409
	}
1410

1411
	if !s.valid {
1412
		return false, define.ErrDBClosed
1413
	}
1414

1415
	row := s.conn.QueryRow("SELECT 1 FROM PodConfig WHERE ID=?;", id)
1416

1417
	var check int
1418
	if err := row.Scan(&check); err != nil {
1419
		if errors.Is(err, sql.ErrNoRows) {
1420
			return false, nil
1421
		}
1422
		return false, fmt.Errorf("looking up pod %s in database: %w", id, err)
1423
	} else if check != 1 {
1424
		return false, fmt.Errorf("check digit for pod %s lookup incorrect: %w", id, define.ErrInternal)
1425
	}
1426

1427
	return true, nil
1428
}
1429

1430
// PodHasContainer checks if the given pod has a container with the given ID
1431
func (s *SQLiteState) PodHasContainer(pod *Pod, id string) (bool, error) {
1432
	if id == "" {
1433
		return false, define.ErrEmptyID
1434
	}
1435

1436
	if !s.valid {
1437
		return false, define.ErrDBClosed
1438
	}
1439

1440
	if !pod.valid {
1441
		return false, define.ErrPodRemoved
1442
	}
1443

1444
	var check int
1445
	row := s.conn.QueryRow("SELECT 1 FROM ContainerConfig WHERE ID=? AND PodID=?;", id, pod.ID())
1446
	if err := row.Scan(&check); err != nil {
1447
		if errors.Is(err, sql.ErrNoRows) {
1448
			return false, nil
1449
		}
1450
		return false, fmt.Errorf("checking if pod %s has container %s in database: %w", pod.ID(), id, err)
1451
	} else if check != 1 {
1452
		return false, fmt.Errorf("check digit for pod %s lookup incorrect: %w", id, define.ErrInternal)
1453
	}
1454

1455
	return true, nil
1456
}
1457

1458
// PodContainersByID returns the IDs of all containers present in the given pod
1459
func (s *SQLiteState) PodContainersByID(pod *Pod) ([]string, error) {
1460
	if !s.valid {
1461
		return nil, define.ErrDBClosed
1462
	}
1463

1464
	if !pod.valid {
1465
		return nil, define.ErrPodRemoved
1466
	}
1467

1468
	rows, err := s.conn.Query("SELECT ID FROM ContainerConfig WHERE PodID=?;", pod.ID())
1469
	if err != nil {
1470
		return nil, fmt.Errorf("retrieving container IDs of pod %s from database: %w", pod.ID(), err)
1471
	}
1472
	defer rows.Close()
1473

1474
	var ids []string
1475
	for rows.Next() {
1476
		var id string
1477
		if err := rows.Scan(&id); err != nil {
1478
			return nil, fmt.Errorf("scanning container from database: %w", err)
1479
		}
1480

1481
		ids = append(ids, id)
1482
	}
1483
	if err := rows.Err(); err != nil {
1484
		return nil, err
1485
	}
1486

1487
	return ids, nil
1488
}
1489

1490
// PodContainers returns all the containers present in the given pod
1491
func (s *SQLiteState) PodContainers(pod *Pod) ([]*Container, error) {
1492
	if !s.valid {
1493
		return nil, define.ErrDBClosed
1494
	}
1495

1496
	if !pod.valid {
1497
		return nil, define.ErrPodRemoved
1498
	}
1499

1500
	rows, err := s.conn.Query("SELECT JSON FROM ContainerConfig WHERE PodID=?;", pod.ID())
1501
	if err != nil {
1502
		return nil, fmt.Errorf("retrieving containers of pod %s from database: %w", pod.ID(), err)
1503
	}
1504
	defer rows.Close()
1505

1506
	var ctrs []*Container
1507
	for rows.Next() {
1508
		var rawJSON string
1509
		if err := rows.Scan(&rawJSON); err != nil {
1510
			return nil, fmt.Errorf("scanning container from database: %w", err)
1511
		}
1512

1513
		ctr := new(Container)
1514
		ctr.config = new(ContainerConfig)
1515
		ctr.state = new(ContainerState)
1516
		ctr.runtime = s.runtime
1517

1518
		if err := json.Unmarshal([]byte(rawJSON), ctr.config); err != nil {
1519
			return nil, fmt.Errorf("unmarshalling container config: %w", err)
1520
		}
1521

1522
		ctrs = append(ctrs, ctr)
1523
	}
1524
	if err := rows.Err(); err != nil {
1525
		return nil, err
1526
	}
1527

1528
	for _, ctr := range ctrs {
1529
		if err := finalizeCtrSqlite(ctr); err != nil {
1530
			return nil, err
1531
		}
1532
	}
1533

1534
	return ctrs, nil
1535
}
1536

1537
// AddPod adds the given pod to the state.
1538
func (s *SQLiteState) AddPod(pod *Pod) (defErr error) {
1539
	if !s.valid {
1540
		return define.ErrDBClosed
1541
	}
1542

1543
	if !pod.valid {
1544
		return define.ErrPodRemoved
1545
	}
1546

1547
	infraID := sql.NullString{}
1548
	if pod.state.InfraContainerID != "" {
1549
		if err := infraID.Scan(pod.state.InfraContainerID); err != nil {
1550
			return fmt.Errorf("scanning infra container ID %q: %w", pod.state.InfraContainerID, err)
1551
		}
1552
	}
1553

1554
	configJSON, err := json.Marshal(pod.config)
1555
	if err != nil {
1556
		return fmt.Errorf("marshalling pod config json: %w", err)
1557
	}
1558

1559
	stateJSON, err := json.Marshal(pod.state)
1560
	if err != nil {
1561
		return fmt.Errorf("marshalling pod state json: %w", err)
1562
	}
1563

1564
	tx, err := s.conn.Begin()
1565
	if err != nil {
1566
		return fmt.Errorf("beginning pod create transaction: %w", err)
1567
	}
1568
	defer func() {
1569
		if defErr != nil {
1570
			if err := tx.Rollback(); err != nil {
1571
				logrus.Errorf("Rolling back transaction to create pod: %v", err)
1572
			}
1573
		}
1574
	}()
1575

1576
	// TODO: explore whether there's a more idiomatic way to do error checks for the name.
1577
	// There is a sqlite3.ErrConstraintUnique error but I (vrothberg) couldn't find a way
1578
	// to work with the returned errors yet.
1579
	var check int
1580
	row := tx.QueryRow("SELECT 1 FROM PodConfig WHERE Name=?;", pod.Name())
1581
	if err := row.Scan(&check); err != nil {
1582
		if !errors.Is(err, sql.ErrNoRows) {
1583
			return fmt.Errorf("checking if pod name %s exists in database: %w", pod.Name(), err)
1584
		}
1585
	} else if check != 0 {
1586
		return fmt.Errorf("name %q is in use: %w", pod.Name(), define.ErrPodExists)
1587
	}
1588

1589
	if _, err := tx.Exec("INSERT INTO IDNamespace VALUES (?);", pod.ID()); err != nil {
1590
		return fmt.Errorf("adding pod id to database: %w", err)
1591
	}
1592
	if _, err := tx.Exec("INSERT INTO PodConfig VALUES (?, ?, ?);", pod.ID(), pod.Name(), configJSON); err != nil {
1593
		return fmt.Errorf("adding pod config to database: %w", err)
1594
	}
1595
	if _, err := tx.Exec("INSERT INTO PodState VALUES (?, ?, ?);", pod.ID(), infraID, stateJSON); err != nil {
1596
		return fmt.Errorf("adding pod state to database: %w", err)
1597
	}
1598

1599
	if err := tx.Commit(); err != nil {
1600
		return fmt.Errorf("committing transaction: %w", err)
1601
	}
1602

1603
	return nil
1604
}
1605

1606
// RemovePod removes the given pod from the state.
1607
// Only empty pods can be removed.
1608
func (s *SQLiteState) RemovePod(pod *Pod) (defErr error) {
1609
	if !s.valid {
1610
		return define.ErrDBClosed
1611
	}
1612

1613
	if !pod.valid {
1614
		return define.ErrPodRemoved
1615
	}
1616

1617
	tx, err := s.conn.Begin()
1618
	if err != nil {
1619
		return fmt.Errorf("beginning pod %s removal transaction: %w", pod.ID(), err)
1620
	}
1621
	defer func() {
1622
		if defErr != nil {
1623
			if err := tx.Rollback(); err != nil {
1624
				logrus.Errorf("Rolling back transaction to remove pod %s: %v", pod.ID(), err)
1625
			}
1626
		}
1627
	}()
1628

1629
	var check int
1630
	row := tx.QueryRow("SELECT 1 FROM ContainerConfig WHERE PodID=? AND ID!=?;", pod.ID(), pod.state.InfraContainerID)
1631
	if err := row.Scan(&check); err != nil {
1632
		if !errors.Is(err, sql.ErrNoRows) {
1633
			return fmt.Errorf("checking if pod %s has containers in database: %w", pod.ID(), err)
1634
		}
1635
	} else if check != 0 {
1636
		return fmt.Errorf("pod %s is not empty: %w", pod.ID(), define.ErrCtrExists)
1637
	}
1638

1639
	checkResult := func(result sql.Result) error {
1640
		rows, err := result.RowsAffected()
1641
		if err != nil {
1642
			return fmt.Errorf("retrieving pod %s delete rows affected: %w", pod.ID(), err)
1643
		}
1644
		if rows == 0 {
1645
			pod.valid = false
1646
			return define.ErrNoSuchPod
1647
		}
1648
		return nil
1649
	}
1650

1651
	result, err := tx.Exec("DELETE FROM IDNamespace WHERE ID=?;", pod.ID())
1652
	if err != nil {
1653
		return fmt.Errorf("removing pod %s id from database: %w", pod.ID(), err)
1654
	}
1655
	if err := checkResult(result); err != nil {
1656
		return err
1657
	}
1658

1659
	result, err = tx.Exec("DELETE FROM PodConfig WHERE ID=?;", pod.ID())
1660
	if err != nil {
1661
		return fmt.Errorf("removing pod %s config from database: %w", pod.ID(), err)
1662
	}
1663
	if err := checkResult(result); err != nil {
1664
		return err
1665
	}
1666

1667
	result, err = tx.Exec("DELETE FROM PodState WHERE ID=?;", pod.ID())
1668
	if err != nil {
1669
		return fmt.Errorf("removing pod %s state from database: %w", pod.ID(), err)
1670
	}
1671
	if err := checkResult(result); err != nil {
1672
		return err
1673
	}
1674

1675
	if err := tx.Commit(); err != nil {
1676
		return fmt.Errorf("committing pod %s removal transaction: %w", pod.ID(), err)
1677
	}
1678

1679
	return nil
1680
}
1681

1682
// RemovePodContainers removes all containers in a pod.
1683
func (s *SQLiteState) RemovePodContainers(pod *Pod) (defErr error) {
1684
	if !s.valid {
1685
		return define.ErrDBClosed
1686
	}
1687

1688
	if !pod.valid {
1689
		return define.ErrPodRemoved
1690
	}
1691

1692
	tx, err := s.conn.Begin()
1693
	if err != nil {
1694
		return fmt.Errorf("beginning removal transaction for containers of pod %s: %w", pod.ID(), err)
1695
	}
1696
	defer func() {
1697
		if defErr != nil {
1698
			if err := tx.Rollback(); err != nil {
1699
				logrus.Errorf("Rolling back transaction to remove containers of pod %s: %v", pod.ID(), err)
1700
			}
1701
		}
1702
	}()
1703

1704
	rows, err := tx.Query("SELECT ID FROM ContainerConfig WHERE PodID=?;", pod.ID())
1705
	if err != nil {
1706
		return fmt.Errorf("retrieving container IDs of pod %s from database: %w", pod.ID(), err)
1707
	}
1708
	defer rows.Close()
1709

1710
	for rows.Next() {
1711
		var id string
1712
		if err := rows.Scan(&id); err != nil {
1713
			return fmt.Errorf("scanning container from database: %w", err)
1714
		}
1715

1716
		if err := s.removeContainerWithTx(id, tx); err != nil {
1717
			return err
1718
		}
1719
	}
1720
	if err := rows.Err(); err != nil {
1721
		return err
1722
	}
1723

1724
	if err := tx.Commit(); err != nil {
1725
		return fmt.Errorf("committing pod containers %s removal transaction: %w", pod.ID(), err)
1726
	}
1727

1728
	return nil
1729
}
1730

1731
// AddContainerToPod adds the given container to an existing pod
1732
// The container will be added to the state and the pod
1733
func (s *SQLiteState) AddContainerToPod(pod *Pod, ctr *Container) error {
1734
	if !s.valid {
1735
		return define.ErrDBClosed
1736
	}
1737

1738
	if !pod.valid {
1739
		return define.ErrPodRemoved
1740
	}
1741

1742
	if !ctr.valid {
1743
		return define.ErrCtrRemoved
1744
	}
1745

1746
	if ctr.config.Pod != pod.ID() {
1747
		return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr)
1748
	}
1749

1750
	return s.addContainer(ctr)
1751
}
1752

1753
// RemoveContainerFromPod removes a container from an existing pod
1754
// The container will also be removed from the state
1755
func (s *SQLiteState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
1756
	if !s.valid {
1757
		return define.ErrDBClosed
1758
	}
1759

1760
	if !pod.valid {
1761
		return define.ErrPodRemoved
1762
	}
1763

1764
	if ctr.config.Pod == "" {
1765
		return fmt.Errorf("container %s is not part of a pod, use RemoveContainer instead: %w", ctr.ID(), define.ErrNoSuchPod)
1766
	}
1767

1768
	if ctr.config.Pod != pod.ID() {
1769
		return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrInvalidArg)
1770
	}
1771

1772
	return s.removeContainer(ctr)
1773
}
1774

1775
// UpdatePod updates a pod's state from the database.
1776
func (s *SQLiteState) UpdatePod(pod *Pod) error {
1777
	if !s.valid {
1778
		return define.ErrDBClosed
1779
	}
1780

1781
	if !pod.valid {
1782
		return define.ErrPodRemoved
1783
	}
1784

1785
	row := s.conn.QueryRow("SELECT JSON FROM PodState WHERE ID=?;", pod.ID())
1786

1787
	var rawJSON string
1788
	if err := row.Scan(&rawJSON); err != nil {
1789
		if errors.Is(err, sql.ErrNoRows) {
1790
			// Pod was removed
1791
			pod.valid = false
1792
			return fmt.Errorf("no pod with ID %s found in database: %w", pod.ID(), define.ErrNoSuchPod)
1793
		}
1794
	}
1795

1796
	newState := new(podState)
1797
	if err := json.Unmarshal([]byte(rawJSON), newState); err != nil {
1798
		return fmt.Errorf("unmarshalling pod %s state JSON: %w", pod.ID(), err)
1799
	}
1800

1801
	pod.state = newState
1802

1803
	return nil
1804
}
1805

1806
// SavePod saves a pod's state to the database.
1807
func (s *SQLiteState) SavePod(pod *Pod) (defErr error) {
1808
	if !s.valid {
1809
		return define.ErrDBClosed
1810
	}
1811

1812
	if !pod.valid {
1813
		return define.ErrPodRemoved
1814
	}
1815

1816
	stateJSON, err := json.Marshal(pod.state)
1817
	if err != nil {
1818
		return fmt.Errorf("marshalling pod %s state JSON: %w", pod.ID(), err)
1819
	}
1820

1821
	tx, err := s.conn.Begin()
1822
	if err != nil {
1823
		return fmt.Errorf("beginning pod %s save transaction: %w", pod.ID(), err)
1824
	}
1825
	defer func() {
1826
		if defErr != nil {
1827
			if err := tx.Rollback(); err != nil {
1828
				logrus.Errorf("Rolling back transaction to save pod %s state: %v", pod.ID(), err)
1829
			}
1830
		}
1831
	}()
1832

1833
	result, err := tx.Exec("UPDATE PodState SET JSON=? WHERE ID=?;", stateJSON, pod.ID())
1834
	if err != nil {
1835
		return fmt.Errorf("writing pod %s state: %w", pod.ID(), err)
1836
	}
1837
	rows, err := result.RowsAffected()
1838
	if err != nil {
1839
		return fmt.Errorf("retrieving pod %s save rows affected: %w", pod.ID(), err)
1840
	}
1841
	if rows == 0 {
1842
		pod.valid = false
1843
		return define.ErrNoSuchPod
1844
	}
1845

1846
	if err := tx.Commit(); err != nil {
1847
		return fmt.Errorf("committing pod %s state: %w", pod.ID(), err)
1848
	}
1849

1850
	return nil
1851
}
1852

1853
// AllPods returns all pods present in the state.
1854
func (s *SQLiteState) AllPods() ([]*Pod, error) {
1855
	if !s.valid {
1856
		return nil, define.ErrDBClosed
1857
	}
1858

1859
	pods := []*Pod{}
1860
	rows, err := s.conn.Query("SELECT JSON FROM PodConfig;")
1861
	if err != nil {
1862
		return nil, fmt.Errorf("retrieving all pods from database: %w", err)
1863
	}
1864
	defer rows.Close()
1865

1866
	for rows.Next() {
1867
		var rawJSON string
1868
		if err := rows.Scan(&rawJSON); err != nil {
1869
			return nil, fmt.Errorf("scanning pod from database: %w", err)
1870
		}
1871

1872
		pod, err := s.createPod(rawJSON)
1873
		if err != nil {
1874
			return nil, err
1875
		}
1876

1877
		pods = append(pods, pod)
1878
	}
1879
	if err := rows.Err(); err != nil {
1880
		return nil, err
1881
	}
1882

1883
	return pods, nil
1884
}
1885

1886
// AddVolume adds the given volume to the state. It also adds ctrDepID to
1887
// the sub bucket holding the container dependencies that this volume has
1888
func (s *SQLiteState) AddVolume(volume *Volume) (defErr error) {
1889
	if !s.valid {
1890
		return define.ErrDBClosed
1891
	}
1892

1893
	if !volume.valid {
1894
		return define.ErrVolumeRemoved
1895
	}
1896

1897
	cfgJSON, err := json.Marshal(volume.config)
1898
	if err != nil {
1899
		return fmt.Errorf("marshalling volume %s configuration json: %w", volume.Name(), err)
1900
	}
1901

1902
	volState := volume.state
1903
	if volState == nil {
1904
		volState = new(VolumeState)
1905
	}
1906

1907
	stateJSON, err := json.Marshal(volState)
1908
	if err != nil {
1909
		return fmt.Errorf("marshalling volume %s state json: %w", volume.Name(), err)
1910
	}
1911

1912
	storageID := sql.NullString{}
1913
	if volume.config.StorageID != "" {
1914
		storageID.Valid = true
1915
		storageID.String = volume.config.StorageID
1916
	}
1917

1918
	tx, err := s.conn.Begin()
1919
	if err != nil {
1920
		return fmt.Errorf("beginning volume create transaction: %w", err)
1921
	}
1922
	defer func() {
1923
		if defErr != nil {
1924
			if err := tx.Rollback(); err != nil {
1925
				logrus.Errorf("Rolling back transaction to create volume: %v", err)
1926
			}
1927
		}
1928
	}()
1929

1930
	// TODO: There has to be a better way of doing this
1931
	var check int
1932
	row := tx.QueryRow("SELECT 1 FROM VolumeConfig WHERE Name=?;", volume.Name())
1933
	if err := row.Scan(&check); err != nil {
1934
		if !errors.Is(err, sql.ErrNoRows) {
1935
			return fmt.Errorf("checking if volume name %s exists in database: %w", volume.Name(), err)
1936
		}
1937
	} else if check != 0 {
1938
		return fmt.Errorf("name %q is in use: %w", volume.Name(), define.ErrVolumeExists)
1939
	}
1940

1941
	if _, err := tx.Exec("INSERT INTO VolumeConfig VALUES (?, ?, ?);", volume.Name(), storageID, cfgJSON); err != nil {
1942
		return fmt.Errorf("adding volume %s config to database: %w", volume.Name(), err)
1943
	}
1944

1945
	if _, err := tx.Exec("INSERT INTO VolumeState VALUES (?, ?);", volume.Name(), stateJSON); err != nil {
1946
		return fmt.Errorf("adding volume %s state to database: %w", volume.Name(), err)
1947
	}
1948

1949
	if err := tx.Commit(); err != nil {
1950
		return fmt.Errorf("committing transaction: %w", err)
1951
	}
1952

1953
	return nil
1954
}
1955

1956
// RemoveVolume removes the given volume from the state
1957
func (s *SQLiteState) RemoveVolume(volume *Volume) (defErr error) {
1958
	if !s.valid {
1959
		return define.ErrDBClosed
1960
	}
1961

1962
	tx, err := s.conn.Begin()
1963
	if err != nil {
1964
		return fmt.Errorf("beginning volume %s removal transaction: %w", volume.Name(), err)
1965
	}
1966
	defer func() {
1967
		if defErr != nil {
1968
			if err := tx.Rollback(); err != nil {
1969
				logrus.Errorf("Rolling back transaction to remove volume %s: %v", volume.Name(), err)
1970
			}
1971
		}
1972
	}()
1973

1974
	rows, err := tx.Query("SELECT ContainerID FROM ContainerVolume WHERE VolumeName=?;", volume.Name())
1975
	if err != nil {
1976
		return fmt.Errorf("querying for containers using volume %s: %w", volume.Name(), err)
1977
	}
1978
	defer rows.Close()
1979

1980
	var ctrs []string
1981
	for rows.Next() {
1982
		var ctr string
1983
		if err := rows.Scan(&ctr); err != nil {
1984
			return fmt.Errorf("error scanning row for containers using volume %s: %w", volume.Name(), err)
1985
		}
1986
		ctrs = append(ctrs, ctr)
1987
	}
1988
	if err := rows.Err(); err != nil {
1989
		return err
1990
	}
1991
	if len(ctrs) > 0 {
1992
		return fmt.Errorf("volume %s is in use by containers %s: %w", volume.Name(), strings.Join(ctrs, ","), define.ErrVolumeBeingUsed)
1993
	}
1994

1995
	// TODO TODO TODO:
1996
	// Need to verify that at least 1 row was deleted from VolumeConfig.
1997
	// Otherwise return ErrNoSuchVolume
1998

1999
	if _, err := tx.Exec("DELETE FROM VolumeConfig WHERE Name=?;", volume.Name()); err != nil {
2000
		return fmt.Errorf("removing volume %s config from DB: %w", volume.Name(), err)
2001
	}
2002

2003
	if _, err := tx.Exec("DELETE FROM VolumeState WHERE Name=?;", volume.Name()); err != nil {
2004
		return fmt.Errorf("removing volume %s state from DB: %w", volume.Name(), err)
2005
	}
2006

2007
	if err := tx.Commit(); err != nil {
2008
		return fmt.Errorf("committing transaction to remove volume %s: %w", volume.Name(), err)
2009
	}
2010

2011
	return nil
2012
}
2013

2014
// UpdateVolume updates the volume's state from the database.
2015
func (s *SQLiteState) UpdateVolume(volume *Volume) error {
2016
	if !s.valid {
2017
		return define.ErrDBClosed
2018
	}
2019

2020
	if !volume.valid {
2021
		return define.ErrVolumeRemoved
2022
	}
2023

2024
	row := s.conn.QueryRow("SELECT JSON FROM VolumeState WHERE Name=?;", volume.Name())
2025

2026
	var stateJSON string
2027
	if err := row.Scan(&stateJSON); err != nil {
2028
		if errors.Is(err, sql.ErrNoRows) {
2029
			volume.valid = false
2030
			return define.ErrNoSuchVolume
2031
		}
2032
		return fmt.Errorf("scanning volume %s state JSON: %w", volume.Name(), err)
2033
	}
2034

2035
	newState := new(VolumeState)
2036
	if err := json.Unmarshal([]byte(stateJSON), newState); err != nil {
2037
		return fmt.Errorf("unmarshalling volume %s state: %w", volume.Name(), err)
2038
	}
2039

2040
	volume.state = newState
2041

2042
	return nil
2043
}
2044

2045
// SaveVolume saves the volume's state to the database.
2046
func (s *SQLiteState) SaveVolume(volume *Volume) (defErr error) {
2047
	if !s.valid {
2048
		return define.ErrDBClosed
2049
	}
2050

2051
	if !volume.valid {
2052
		return define.ErrVolumeRemoved
2053
	}
2054

2055
	stateJSON, err := json.Marshal(volume.state)
2056
	if err != nil {
2057
		return fmt.Errorf("marshalling volume %s state JSON: %w", volume.Name(), err)
2058
	}
2059

2060
	tx, err := s.conn.Begin()
2061
	if err != nil {
2062
		return fmt.Errorf("beginning transaction to rewrite volume %s state: %w", volume.Name(), err)
2063
	}
2064
	defer func() {
2065
		if defErr != nil {
2066
			if err := tx.Rollback(); err != nil {
2067
				logrus.Errorf("Rolling back transaction to rewrite volume %s state: %v", volume.Name(), err)
2068
			}
2069
		}
2070
	}()
2071

2072
	results, err := tx.Exec("UPDATE VolumeState SET JSON=? WHERE Name=?;", stateJSON, volume.Name())
2073
	if err != nil {
2074
		return fmt.Errorf("updating volume %s state in DB: %w", volume.Name(), err)
2075
	}
2076
	rows, err := results.RowsAffected()
2077
	if err != nil {
2078
		return fmt.Errorf("retrieving volume %s state rewrite rows affected: %w", volume.Name(), err)
2079
	}
2080
	if rows == 0 {
2081
		volume.valid = false
2082
		return define.ErrNoSuchVolume
2083
	}
2084

2085
	if err := tx.Commit(); err != nil {
2086
		return fmt.Errorf("committing transaction to rewrite volume %s state: %w", volume.Name(), err)
2087
	}
2088

2089
	return nil
2090
}
2091

2092
// AllVolumes returns all volumes present in the state.
2093
func (s *SQLiteState) AllVolumes() ([]*Volume, error) {
2094
	if !s.valid {
2095
		return nil, define.ErrDBClosed
2096
	}
2097

2098
	rows, err := s.conn.Query("SELECT JSON FROM VolumeConfig;")
2099
	if err != nil {
2100
		return nil, fmt.Errorf("querying database for all volumes: %w", err)
2101
	}
2102
	defer rows.Close()
2103

2104
	var volumes []*Volume
2105

2106
	for rows.Next() {
2107
		var configJSON string
2108
		if err := rows.Scan(&configJSON); err != nil {
2109
			return nil, fmt.Errorf("scanning volume config from database: %w", err)
2110
		}
2111
		vol := new(Volume)
2112
		vol.config = new(VolumeConfig)
2113
		vol.state = new(VolumeState)
2114
		vol.runtime = s.runtime
2115

2116
		if err := json.Unmarshal([]byte(configJSON), vol.config); err != nil {
2117
			return nil, fmt.Errorf("unmarshalling volume config: %w", err)
2118
		}
2119

2120
		if err := finalizeVolumeSqlite(vol); err != nil {
2121
			return nil, err
2122
		}
2123

2124
		volumes = append(volumes, vol)
2125
	}
2126
	if err := rows.Err(); err != nil {
2127
		return nil, err
2128
	}
2129

2130
	return volumes, nil
2131
}
2132

2133
// Volume retrieves a volume from full name.
2134
func (s *SQLiteState) Volume(name string) (*Volume, error) {
2135
	if name == "" {
2136
		return nil, define.ErrEmptyID
2137
	}
2138

2139
	if !s.valid {
2140
		return nil, define.ErrDBClosed
2141
	}
2142

2143
	row := s.conn.QueryRow("SELECT JSON FROM VolumeConfig WHERE Name=?;", name)
2144

2145
	var configJSON string
2146

2147
	if err := row.Scan(&configJSON); err != nil {
2148
		if errors.Is(err, sql.ErrNoRows) {
2149
			return nil, define.ErrNoSuchVolume
2150
		}
2151
	}
2152

2153
	vol := new(Volume)
2154
	vol.config = new(VolumeConfig)
2155
	vol.state = new(VolumeState)
2156
	vol.runtime = s.runtime
2157

2158
	if err := json.Unmarshal([]byte(configJSON), vol.config); err != nil {
2159
		return nil, fmt.Errorf("unmarshalling volume %s config JSON: %w", name, err)
2160
	}
2161

2162
	if err := finalizeVolumeSqlite(vol); err != nil {
2163
		return nil, err
2164
	}
2165

2166
	return vol, nil
2167
}
2168

2169
// LookupVolume locates a volume from a unique partial name.
2170
func (s *SQLiteState) LookupVolume(name string) (*Volume, error) {
2171
	if name == "" {
2172
		return nil, define.ErrEmptyID
2173
	}
2174

2175
	if !s.valid {
2176
		return nil, define.ErrDBClosed
2177
	}
2178

2179
	rows, err := s.conn.Query("SELECT Name, JSON FROM VolumeConfig WHERE Name LIKE ? ORDER BY LENGTH(Name) ASC;", name+"%")
2180
	if err != nil {
2181
		return nil, fmt.Errorf("querying database for volume %s: %w", name, err)
2182
	}
2183
	defer rows.Close()
2184

2185
	var foundName, configJSON string
2186
	for rows.Next() {
2187
		if foundName != "" {
2188
			return nil, fmt.Errorf("more than one result for volume name %s: %w", name, define.ErrVolumeExists)
2189
		}
2190
		if err := rows.Scan(&foundName, &configJSON); err != nil {
2191
			return nil, fmt.Errorf("retrieving volume %s config from database: %w", name, err)
2192
		}
2193
		if foundName == name {
2194
			break
2195
		}
2196
	}
2197
	if err := rows.Err(); err != nil {
2198
		return nil, err
2199
	}
2200
	if foundName == "" {
2201
		return nil, fmt.Errorf("no volume with name %q found: %w", name, define.ErrNoSuchVolume)
2202
	}
2203

2204
	vol := new(Volume)
2205
	vol.config = new(VolumeConfig)
2206
	vol.state = new(VolumeState)
2207
	vol.runtime = s.runtime
2208

2209
	if err := json.Unmarshal([]byte(configJSON), vol.config); err != nil {
2210
		return nil, fmt.Errorf("unmarshalling volume %s config JSON: %w", name, err)
2211
	}
2212

2213
	if err := finalizeVolumeSqlite(vol); err != nil {
2214
		return nil, err
2215
	}
2216

2217
	return vol, nil
2218
}
2219

2220
// HasVolume returns true if the given volume exists in the state.
2221
// Otherwise it returns false.
2222
func (s *SQLiteState) HasVolume(name string) (bool, error) {
2223
	if name == "" {
2224
		return false, define.ErrEmptyID
2225
	}
2226

2227
	if !s.valid {
2228
		return false, define.ErrDBClosed
2229
	}
2230

2231
	row := s.conn.QueryRow("SELECT 1 FROM VolumeConfig WHERE Name=?;", name)
2232

2233
	var check int
2234
	if err := row.Scan(&check); err != nil {
2235
		if errors.Is(err, sql.ErrNoRows) {
2236
			return false, nil
2237
		}
2238
		return false, fmt.Errorf("looking up volume %s in database: %w", name, err)
2239
	}
2240
	if check != 1 {
2241
		return false, fmt.Errorf("check digit for volume %s lookup incorrect: %w", name, define.ErrInternal)
2242
	}
2243

2244
	return true, nil
2245
}
2246

2247
// VolumeInUse checks if any container is using the volume.
2248
// It returns a slice of the IDs of the containers using the given
2249
// volume. If the slice is empty, no containers use the given volume.
2250
func (s *SQLiteState) VolumeInUse(volume *Volume) ([]string, error) {
2251
	if !s.valid {
2252
		return nil, define.ErrDBClosed
2253
	}
2254

2255
	if !volume.valid {
2256
		return nil, define.ErrVolumeRemoved
2257
	}
2258

2259
	rows, err := s.conn.Query("SELECT ContainerID FROM ContainerVolume WHERE VolumeName=?;", volume.Name())
2260
	if err != nil {
2261
		return nil, fmt.Errorf("querying database for containers using volume %s: %w", volume.Name(), err)
2262
	}
2263
	defer rows.Close()
2264

2265
	var ctrs []string
2266
	for rows.Next() {
2267
		var ctr string
2268
		if err := rows.Scan(&ctr); err != nil {
2269
			return nil, fmt.Errorf("scanning container ID for container using volume %s: %w", volume.Name(), err)
2270
		}
2271
		ctrs = append(ctrs, ctr)
2272
	}
2273
	if err := rows.Err(); err != nil {
2274
		return nil, err
2275
	}
2276

2277
	return ctrs, nil
2278
}
2279

2280
// ContainerIDIsVolume checks if the given c/storage container ID is used as
2281
// backing storage for a volume.
2282
func (s *SQLiteState) ContainerIDIsVolume(id string) (bool, error) {
2283
	if !s.valid {
2284
		return false, define.ErrDBClosed
2285
	}
2286

2287
	row := s.conn.QueryRow("SELECT 1 FROM VolumeConfig WHERE StorageID=?;", id)
2288
	var checkDigit int
2289
	if err := row.Scan(&checkDigit); err != nil {
2290
		if errors.Is(err, sql.ErrNoRows) {
2291
			return false, nil
2292
		}
2293
		return false, fmt.Errorf("error retrieving volumes using storage ID %s: %w", id, err)
2294
	}
2295
	if checkDigit != 1 {
2296
		return false, fmt.Errorf("check digit for volumes using storage ID %s was incorrect: %w", id, define.ErrInternal)
2297
	}
2298

2299
	return true, nil
2300
}
2301

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

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

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

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