12
"github.com/containers/common/libnetwork/types"
13
"github.com/containers/podman/v5/libpod/define"
14
"github.com/sirupsen/logrus"
16
// SQLite backend for database/sql
17
_ "github.com/mattn/go-sqlite3"
20
func initSQLiteDB(conn *sql.DB) (defErr error) {
21
// Start with a transaction to avoid "database locked" errors.
22
// See https://github.com/mattn/go-sqlite3/issues/274#issuecomment-1429054597
23
tx, err := conn.Begin()
25
return fmt.Errorf("beginning transaction: %w", err)
29
if err := tx.Rollback(); err != nil {
30
logrus.Errorf("Rolling back transaction to create tables: %v", err)
35
sameSchema, err := migrateSchemaIfNecessary(tx)
40
if err := createSQLiteTables(tx); err != nil {
44
if err := tx.Commit(); err != nil {
45
return fmt.Errorf("committing transaction: %w", err)
50
func migrateSchemaIfNecessary(tx *sql.Tx) (bool, error) {
51
// First, check if the DBConfig table exists
52
checkRow := tx.QueryRow("SELECT 1 FROM sqlite_master WHERE type='table' AND name='DBConfig';")
54
if err := checkRow.Scan(&check); err != nil {
55
if errors.Is(err, sql.ErrNoRows) {
58
return false, fmt.Errorf("checking if DB config table exists: %w", err)
61
// Table does not exist, fresh database, no need to migrate.
65
row := tx.QueryRow("SELECT SchemaVersion FROM DBConfig;")
67
if err := row.Scan(&schemaVer); err != nil {
68
if errors.Is(err, sql.ErrNoRows) {
69
// Brand-new, unpopulated DB.
70
// Schema was just created, so it has to be the latest.
73
return false, fmt.Errorf("scanning schema version from DB config: %w", err)
76
// If the schema version 0 or less, it's invalid
78
return false, fmt.Errorf("database schema version %d is invalid: %w", schemaVer, define.ErrInternal)
81
// Same schema -> nothing do to.
82
if schemaVer == schemaVersion {
86
// If the DB is a later schema than we support, we have to error
87
if schemaVer > schemaVersion {
88
return false, fmt.Errorf("database has schema version %d while this libpod version only supports version %d: %w",
89
schemaVer, schemaVersion, define.ErrInternal)
92
// Perform schema migration here, one version at a time.
97
// Initialize all required tables for the SQLite state
98
func createSQLiteTables(tx *sql.Tx) error {
99
// Technically we could split the "CREATE TABLE IF NOT EXISTS" and ");"
100
// bits off each command and add them in the for loop where we actually
101
// run the SQL, but that seems unnecessary.
103
CREATE TABLE IF NOT EXISTS DBConfig(
104
ID INTEGER PRIMARY KEY NOT NULL,
105
SchemaVersion INTEGER NOT NULL,
107
StaticDir TEXT NOT NULL,
108
TmpDir TEXT NOT NULL,
109
GraphRoot TEXT NOT NULL,
110
RunRoot TEXT NOT NULL,
111
GraphDriver TEXT NOT NULL,
112
VolumeDir TEXT NOT NULL,
116
const idNamespace = `
117
CREATE TABLE IF NOT EXISTS IDNamespace(
118
ID TEXT PRIMARY KEY NOT NULL
121
const containerConfig = `
122
CREATE TABLE IF NOT EXISTS ContainerConfig(
123
ID TEXT PRIMARY KEY NOT NULL,
124
Name TEXT UNIQUE NOT NULL,
127
FOREIGN KEY (ID) REFERENCES IDNamespace(ID) DEFERRABLE INITIALLY DEFERRED,
128
FOREIGN KEY (ID) REFERENCES ContainerState(ID) DEFERRABLE INITIALLY DEFERRED,
129
FOREIGN KEY (PodID) REFERENCES PodConfig(ID)
132
const containerState = `
133
CREATE TABLE IF NOT EXISTS ContainerState(
134
ID TEXT PRIMARY KEY NOT NULL,
135
State INTEGER NOT NULL,
138
FOREIGN KEY (ID) REFERENCES ContainerConfig(ID) DEFERRABLE INITIALLY DEFERRED,
139
CHECK (ExitCode BETWEEN -1 AND 255)
142
const containerExecSession = `
143
CREATE TABLE IF NOT EXISTS ContainerExecSession(
144
ID TEXT PRIMARY KEY NOT NULL,
145
ContainerID TEXT NOT NULL,
146
FOREIGN KEY (ContainerID) REFERENCES ContainerConfig(ID)
149
const containerDependency = `
150
CREATE TABLE IF NOT EXISTS ContainerDependency(
152
DependencyID TEXT NOT NULL,
153
PRIMARY KEY (ID, DependencyID),
154
FOREIGN KEY (ID) REFERENCES ContainerConfig(ID) DEFERRABLE INITIALLY DEFERRED,
155
FOREIGN KEY (DependencyID) REFERENCES ContainerConfig(ID),
156
CHECK (ID <> DependencyID)
159
const containerVolume = `
160
CREATE TABLE IF NOT EXISTS ContainerVolume(
161
ContainerID TEXT NOT NULL,
162
VolumeName TEXT NOT NULL,
163
PRIMARY KEY (ContainerID, VolumeName),
164
FOREIGN KEY (ContainerID) REFERENCES ContainerConfig(ID) DEFERRABLE INITIALLY DEFERRED,
165
FOREIGN KEY (VolumeName) REFERENCES VolumeConfig(Name)
168
const containerExitCode = `
169
CREATE TABLE IF NOT EXISTS ContainerExitCode(
170
ID TEXT PRIMARY KEY NOT NULL,
171
Timestamp INTEGER NOT NULL,
172
ExitCode INTEGER NOT NULL,
173
CHECK (ExitCode BETWEEN -1 AND 255)
177
CREATE TABLE IF NOT EXISTS PodConfig(
178
ID TEXT PRIMARY KEY NOT NULL,
179
Name TEXT UNIQUE NOT NULL,
181
FOREIGN KEY (ID) REFERENCES IDNamespace(ID) DEFERRABLE INITIALLY DEFERRED,
182
FOREIGN KEY (ID) REFERENCES PodState(ID) DEFERRABLE INITIALLY DEFERRED
186
CREATE TABLE IF NOT EXISTS PodState(
187
ID TEXT PRIMARY KEY NOT NULL,
188
InfraContainerID TEXT,
190
FOREIGN KEY (ID) REFERENCES PodConfig(ID) DEFERRABLE INITIALLY DEFERRED,
191
FOREIGN KEY (InfraContainerID) REFERENCES ContainerConfig(ID) DEFERRABLE INITIALLY DEFERRED
194
const volumeConfig = `
195
CREATE TABLE IF NOT EXISTS VolumeConfig(
196
Name TEXT PRIMARY KEY NOT NULL,
199
FOREIGN KEY (Name) REFERENCES VolumeState(Name) DEFERRABLE INITIALLY DEFERRED
202
const volumeState = `
203
CREATE TABLE IF NOT EXISTS VolumeState(
204
Name TEXT PRIMARY KEY NOT NULL,
206
FOREIGN KEY (Name) REFERENCES VolumeConfig(Name) DEFERRABLE INITIALLY DEFERRED
209
tables := map[string]string{
210
"DBConfig": dbConfig,
211
"IDNamespace": idNamespace,
212
"ContainerConfig": containerConfig,
213
"ContainerState": containerState,
214
"ContainerExecSession": containerExecSession,
215
"ContainerDependency": containerDependency,
216
"ContainerVolume": containerVolume,
217
"ContainerExitCode": containerExitCode,
218
"PodConfig": podConfig,
219
"PodState": podState,
220
"VolumeConfig": volumeConfig,
221
"VolumeState": volumeState,
224
for tblName, cmd := range tables {
225
if _, err := tx.Exec(cmd); err != nil {
226
return fmt.Errorf("creating table %s: %w", tblName, err)
232
// Get the config of a container with the given ID from the database
233
func (s *SQLiteState) getCtrConfig(id string) (*ContainerConfig, error) {
234
row := s.conn.QueryRow("SELECT JSON FROM ContainerConfig WHERE ID=?;", id)
237
if err := row.Scan(&rawJSON); err != nil {
238
if errors.Is(err, sql.ErrNoRows) {
239
return nil, define.ErrNoSuchCtr
241
return nil, fmt.Errorf("retrieving container %s config from DB: %w", id, err)
244
ctrCfg := new(ContainerConfig)
246
if err := json.Unmarshal([]byte(rawJSON), ctrCfg); err != nil {
247
return nil, fmt.Errorf("unmarshalling container %s config: %w", id, err)
253
// Finalize a container that was pulled out of the database.
254
func finalizeCtrSqlite(ctr *Container) error {
256
lock, err := ctr.runtime.lockManager.RetrieveLock(ctr.config.LockID)
258
return fmt.Errorf("retrieving lock for container %s: %w", ctr.ID(), err)
262
// Get the OCI runtime
263
if ctr.config.OCIRuntime == "" {
264
ctr.ociRuntime = ctr.runtime.defaultOCIRuntime
266
// Handle legacy containers which might use a literal path for
267
// their OCI runtime name.
268
runtimeName := ctr.config.OCIRuntime
269
ociRuntime, ok := ctr.runtime.ociRuntimes[runtimeName]
273
// If the path starts with a / and exists, make a new
274
// OCI runtime for it using the full path.
275
if strings.HasPrefix(runtimeName, "/") {
276
if stat, err := os.Stat(runtimeName); err == nil && !stat.IsDir() {
277
newOCIRuntime, err := newConmonOCIRuntime(runtimeName, []string{runtimeName}, ctr.runtime.conmonPath, ctr.runtime.runtimeFlags, ctr.runtime.config)
279
// TODO: There is a potential risk of concurrent map modification here.
280
// This is an unlikely case, though.
281
ociRuntime = newOCIRuntime
282
ctr.runtime.ociRuntimes[runtimeName] = ociRuntime
289
// Use a MissingRuntime implementation
290
ociRuntime = getMissingRuntime(runtimeName, ctr.runtime)
293
ctr.ociRuntime = ociRuntime
301
// Finalize a pod that was pulled out of the database.
302
func (s *SQLiteState) createPod(rawJSON string) (*Pod, error) {
303
config := new(PodConfig)
304
if err := json.Unmarshal([]byte(rawJSON), config); err != nil {
305
return nil, fmt.Errorf("unmarshalling pod config: %w", err)
307
lock, err := s.runtime.lockManager.RetrieveLock(config.LockID)
309
return nil, fmt.Errorf("retrieving lock for pod %s: %w", config.ID, err)
314
pod.state = new(podState)
316
pod.runtime = s.runtime
322
// Finalize a volume that was pulled out of the database
323
func finalizeVolumeSqlite(vol *Volume) error {
325
lock, err := vol.runtime.lockManager.RetrieveLock(vol.config.LockID)
327
return fmt.Errorf("retrieving lock for volume %s: %w", vol.Name(), err)
331
// Retrieve volume driver
332
if vol.UsesVolumeDriver() {
333
plugin, err := vol.runtime.getVolumePlugin(vol.config)
335
// We want to fail gracefully here, to ensure that we
336
// can still remove volumes even if their plugin is
337
// missing. Otherwise, we end up with volumes that
338
// cannot even be retrieved from the database and will
339
// cause things like `volume ls` to fail.
340
logrus.Errorf("Volume %s uses volume plugin %s, but it cannot be accessed - some functionality may not be available: %v", vol.Name(), vol.config.Driver, err)
351
func (s *SQLiteState) rewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) (defErr error) {
352
json, err := json.Marshal(newCfg)
354
return fmt.Errorf("error marshalling container %s new config JSON: %w", ctr.ID(), err)
357
tx, err := s.conn.Begin()
359
return fmt.Errorf("beginning transaction to rewrite container %s config: %w", ctr.ID(), err)
363
if err := tx.Rollback(); err != nil {
364
logrus.Errorf("Rolling back transaction to rewrite container %s config: %v", ctr.ID(), err)
369
results, err := tx.Exec("UPDATE ContainerConfig SET Name=?, JSON=? WHERE ID=?;", newCfg.Name, json, ctr.ID())
371
return fmt.Errorf("updating container config table with new configuration for container %s: %w", ctr.ID(), err)
373
rows, err := results.RowsAffected()
375
return fmt.Errorf("retrieving container %s config rewrite rows affected: %w", ctr.ID(), err)
379
return define.ErrNoSuchCtr
382
if err := tx.Commit(); err != nil {
383
return fmt.Errorf("committing transaction to rewrite container %s config: %w", ctr.ID(), err)
389
func (s *SQLiteState) addContainer(ctr *Container) (defErr error) {
390
configJSON, err := json.Marshal(ctr.config)
392
return fmt.Errorf("marshalling container config json: %w", err)
395
stateJSON, err := json.Marshal(ctr.state)
397
return fmt.Errorf("marshalling container state json: %w", err)
399
deps := ctr.Dependencies()
401
podID := sql.NullString{}
402
if ctr.config.Pod != "" {
404
podID.String = ctr.config.Pod
407
tx, err := s.conn.Begin()
409
return fmt.Errorf("beginning container create transaction: %w", err)
413
if err := tx.Rollback(); err != nil {
414
logrus.Errorf("Rolling back transaction to create container: %v", err)
419
// TODO: There has to be a better way of doing this
421
row := tx.QueryRow("SELECT 1 FROM ContainerConfig WHERE Name=?;", ctr.Name())
422
if err := row.Scan(&check); err != nil {
423
if !errors.Is(err, sql.ErrNoRows) {
424
return fmt.Errorf("checking if container name %s exists in database: %w", ctr.Name(), err)
426
} else if check != 0 {
427
return fmt.Errorf("name %q is in use: %w", ctr.Name(), define.ErrCtrExists)
430
if _, err := tx.Exec("INSERT INTO IDNamespace VALUES (?);", ctr.ID()); err != nil {
431
return fmt.Errorf("adding container id to database: %w", err)
433
if _, err := tx.Exec("INSERT INTO ContainerConfig VALUES (?, ?, ?, ?);", ctr.ID(), ctr.Name(), podID, configJSON); err != nil {
434
return fmt.Errorf("adding container config to database: %w", err)
436
if _, err := tx.Exec("INSERT INTO ContainerState VALUES (?, ?, ?, ?);", ctr.ID(), int(ctr.state.State), ctr.state.ExitCode, stateJSON); err != nil {
437
return fmt.Errorf("adding container state to database: %w", err)
439
for _, dep := range deps {
440
// Check if the dependency is in the same pod
441
var depPod sql.NullString
442
row := tx.QueryRow("SELECT PodID FROM ContainerConfig WHERE ID=?;", dep)
443
if err := row.Scan(&depPod); err != nil {
444
if errors.Is(err, sql.ErrNoRows) {
445
return fmt.Errorf("container dependency %s does not exist in database: %w", dep, define.ErrNoSuchCtr)
449
case ctr.config.Pod == "" && depPod.Valid:
450
return fmt.Errorf("container dependency %s is part of a pod, but container is not: %w", dep, define.ErrInvalidArg)
451
case ctr.config.Pod != "" && !depPod.Valid:
452
return fmt.Errorf("container dependency %s is not part of pod, but this container belongs to pod %s: %w", dep, ctr.config.Pod, define.ErrInvalidArg)
453
case ctr.config.Pod != "" && depPod.String != ctr.config.Pod:
454
return fmt.Errorf("container dependency %s is part of pod %s but container is part of pod %s, pods must match: %w", dep, depPod.String, ctr.config.Pod, define.ErrInvalidArg)
457
if _, err := tx.Exec("INSERT INTO ContainerDependency VALUES (?, ?);", ctr.ID(), dep); err != nil {
458
return fmt.Errorf("adding container dependency %s to database: %w", dep, err)
461
volMap := make(map[string]bool)
462
for _, vol := range ctr.config.NamedVolumes {
463
if _, ok := volMap[vol.Name]; !ok {
464
if _, err := tx.Exec("INSERT INTO ContainerVolume VALUES (?, ?);", ctr.ID(), vol.Name); err != nil {
465
return fmt.Errorf("adding container volume %s to database: %w", vol.Name, err)
467
volMap[vol.Name] = true
471
if err := tx.Commit(); err != nil {
472
return fmt.Errorf("committing transaction: %w", err)
478
// removeContainer remove the specified container from the database.
479
func (s *SQLiteState) removeContainer(ctr *Container) (defErr error) {
480
tx, err := s.conn.Begin()
482
return fmt.Errorf("beginning container %s removal transaction: %w", ctr.ID(), err)
487
if err := tx.Rollback(); err != nil {
488
logrus.Errorf("Rolling back transaction to remove container %s: %v", ctr.ID(), err)
493
if err := s.removeContainerWithTx(ctr.ID(), tx); err != nil {
497
if err := tx.Commit(); err != nil {
498
return fmt.Errorf("committing container %s removal transaction: %w", ctr.ID(), err)
504
// removeContainerWithTx removes the container with the specified transaction.
505
// Callers are responsible for committing.
506
func (s *SQLiteState) removeContainerWithTx(id string, tx *sql.Tx) error {
508
// Need to verify that at least 1 row was deleted from ContainerConfig.
509
// Otherwise return ErrNoSuchCtr.
510
if _, err := tx.Exec("DELETE FROM IDNamespace WHERE ID=?;", id); err != nil {
511
return fmt.Errorf("removing container %s id from database: %w", id, err)
513
if _, err := tx.Exec("DELETE FROM ContainerConfig WHERE ID=?;", id); err != nil {
514
return fmt.Errorf("removing container %s config from database: %w", id, err)
516
if _, err := tx.Exec("DELETE FROM ContainerState WHERE ID=?;", id); err != nil {
517
return fmt.Errorf("removing container %s state from database: %w", id, err)
519
if _, err := tx.Exec("DELETE FROM ContainerDependency WHERE ID=?;", id); err != nil {
520
return fmt.Errorf("removing container %s dependencies from database: %w", id, err)
522
if _, err := tx.Exec("DELETE FROM ContainerVolume WHERE ContainerID=?;", id); err != nil {
523
return fmt.Errorf("removing container %s volumes from database: %w", id, err)
525
if _, err := tx.Exec("DELETE FROM ContainerExecSession WHERE ContainerID=?;", id); err != nil {
526
return fmt.Errorf("removing container %s exec sessions from database: %w", id, err)
531
// networkModify allows you to modify or add a new network, to add a new network use the new bool
532
func (s *SQLiteState) networkModify(ctr *Container, network string, opts types.PerNetworkOptions, new, disconnect bool) error {
534
return define.ErrDBClosed
538
return define.ErrCtrRemoved
542
return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg)
545
if new && disconnect {
546
return fmt.Errorf("new and disconnect are mutually exclusive: %w", define.ErrInvalidArg)
549
// Grab a fresh copy of the config, in case anything changed
550
newCfg, err := s.getCtrConfig(ctr.ID())
551
if err != nil && errors.Is(err, define.ErrNoSuchCtr) {
553
return define.ErrNoSuchCtr
556
_, ok := newCfg.Networks[network]
558
return fmt.Errorf("container %s is already connected to network %s: %w", ctr.ID(), network, define.ErrNetworkConnected)
560
if !ok && (!new || disconnect) {
561
return fmt.Errorf("container %s is not connected to network %s: %w", ctr.ID(), network, define.ErrNoSuchNetwork)
565
if newCfg.Networks == nil {
566
newCfg.Networks = make(map[string]types.PerNetworkOptions)
568
newCfg.Networks[network] = opts
570
delete(newCfg.Networks, network)
573
if err := s.rewriteContainerConfig(ctr, newCfg); err != nil {