mirror of
https://github.com/containers/podman.git
synced 2026-07-10 07:15:05 -04:00
Add migration code for BoltDB to SQLite
This is gated behind a new option in `podman system migrate`,
`--migrate-db`, or by a system restart being performed.
BoltDB support was removed in Podman 6, so we are certain that,
when we start Podman, a SQLite state is in use. However, if we
also detect a valid BoltDB state, we will attempt a migration.
Migration is performed by retrieving all volumes, pods, and
containers (in that order, to ensure there are no dependency
conflicts) from the Bolt database, when adding them to the SQLite
database. If there is a conflict - IE, a container exists in both
SQLite and Bolt - we skip migration for that object. The old DB
is then renamed so we do not try to migrate it again.
Our ability to test complex migration scenarios is limited, but
this should handle simple migrations easily.
This is a heavily adapted version of #27660 rebuilt to work with
Podman 6.0. Substantial changes were required to throw errors
when a BoltDB database is detected and no migration is being
performed. Firstly, for automatic on-reboot migrations, we need
to have a deferred error returned by getDBState (very early in
runtime initialization) that is only acted on much later (once we
know for certain a state refresh is/is not being performed).
The `system migrate --migrate-db` command was much more
problematic. Conceptually, it's not terrible - add a flag to the
runtime to suppress errors, set that flag only when calling the
`system migrate` command with `--migrate-db` - but it unveiled a
serious problem with how we do runtime init (special flags to the
runtime were being ignored because the image runtime set the
Libpod runtime first and had none of the proper handling) which
took a genuinely annoying amount of time to identify and fix.
This cannot be tested automatically, as the ability to create Bolt
databases has been entirely removed with Podman 6.
This also includes 9b810aed3a from
the v5.8 branch by Luap99, which I have had to squash into this
commit to satisfy the build-each-commit check. It was just a
simplification of the SQLite path check.
Signed-off-by: Matt Heon <matthew.heon@pm.me>
This commit is contained in:
@@ -2,6 +2,7 @@ package registry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -72,6 +73,16 @@ func NewContainerEngine(cmd *cobra.Command, _ []string) (entities.ContainerEngin
|
||||
logrus.Debugf("Performing system renumber, runtime validation checks will be relaxed")
|
||||
podmanOptions.IsRenumber = true
|
||||
}
|
||||
if cmd.Name() == "migrate" && cmd.Parent().Name() == "system" {
|
||||
isMigrateDB, err := cmd.Flags().GetBool("migrate-db")
|
||||
if err != nil {
|
||||
return nil, errors.New("system migrate command missing flag migrate-db")
|
||||
}
|
||||
if isMigrateDB {
|
||||
podmanOptions.IsMigrateDB = true
|
||||
logrus.Debugf("Performing database migration, BoltDB checks will be relaxed")
|
||||
}
|
||||
}
|
||||
engine, err := infra.NewContainerEngine(&podmanOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -382,7 +382,10 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
|
||||
// Prep the engines
|
||||
if _, err := registry.NewImageEngine(cmd, args); err != nil {
|
||||
// Container engine MUST be first.
|
||||
// We have special handling in there for passing flags to the runtime, that is ignored by the image engine.
|
||||
// Which creates a container engine, because there isn't an entrypoint to Libimage that isn't Libpod.
|
||||
if _, err := registry.NewContainerEngine(cmd, args); err != nil {
|
||||
// Note: this is gross, but it is the hand we are dealt
|
||||
if registry.IsRemote() && errors.As(err, &bindings.ConnectError{}) && cmd.Parent() == cmd.Root() {
|
||||
switch cmd.Name() {
|
||||
@@ -404,7 +407,7 @@ func persistentPreRunE(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
if _, err := registry.NewContainerEngine(cmd, args); err != nil {
|
||||
if _, err := registry.NewImageEngine(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ func init() {
|
||||
newRuntimeFlagName := "new-runtime"
|
||||
flags.StringVar(&migrateOptions.NewRuntime, newRuntimeFlagName, "", "Specify a new runtime for all containers")
|
||||
_ = migrateCommand.RegisterFlagCompletionFunc(newRuntimeFlagName, completion.AutocompleteNone)
|
||||
|
||||
flags.BoolVar(&migrateOptions.MigrateDB, "migrate-db", false, "Migrate database from BoltDB to SQLite")
|
||||
}
|
||||
|
||||
func migrate(_ *cobra.Command, _ []string) error {
|
||||
|
||||
@@ -26,6 +26,17 @@ newly configured mappings.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
#### **--migrate-db**
|
||||
|
||||
Migrate from the legacy BoltDB database to SQLite.
|
||||
Support for BoltDB has been removed in Podman 6.0, and existing BoltDB databases must be migrated to continue using the containers, pods, and volumes stored in them.
|
||||
This is also done automatically on system reboot.
|
||||
Migrating as part of a reboot is generally preferred as there is less potential for race conditions caused by other Podman processes running at the same time.
|
||||
If a migration is necessary, Podman will fail to run with a descriptive error indicating this command must be used or the system must be rebooted.
|
||||
To ensure complete migration, all other Podman commands should be shut down before database migration.
|
||||
In particular, systemd-activated services like **podman system service** and Quadlets should be manually stopped prior to migration.
|
||||
The legacy database will not be removed, so no data loss should occur even on failure.
|
||||
|
||||
#### **--new-runtime**=*runtime*
|
||||
|
||||
Set a new OCI runtime for all containers.
|
||||
|
||||
@@ -289,6 +289,67 @@ func startNode(ctx context.Context, node *containerNode, setError bool, ctrError
|
||||
}
|
||||
}
|
||||
|
||||
// Migrates all nodes from BoltDB to SQLite
|
||||
func migrateNodeDatabase(node *containerNode, setError bool, ctrErrors map[string]error, ctrsVisited map[string]bool, sqliteState State) {
|
||||
// First, check if we have already visited the node
|
||||
if ctrsVisited[node.id] {
|
||||
return
|
||||
}
|
||||
|
||||
// If setError is true, a dependency of us failed
|
||||
// Mark us as failed and recurse
|
||||
if setError {
|
||||
// Mark us as visited, and set an error
|
||||
ctrsVisited[node.id] = true
|
||||
ctrErrors[node.id] = fmt.Errorf("a dependency of container %s failed to migrate: %w", node.id, define.ErrCtrStateInvalid)
|
||||
|
||||
// Hit anyone who depends on us, and set errors on them too
|
||||
for _, successor := range node.dependedOn {
|
||||
migrateNodeDatabase(successor, true, ctrErrors, ctrsVisited, sqliteState)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Have all our dependencies started?
|
||||
// If not, don't visit the node yet
|
||||
depsVisited := true
|
||||
for _, dep := range node.dependsOn {
|
||||
depsVisited = depsVisited && ctrsVisited[dep.id]
|
||||
}
|
||||
if !depsVisited {
|
||||
// Don't visit us yet, all dependencies are not up
|
||||
// We'll hit the dependencies eventually, and when we do it will
|
||||
// recurse here
|
||||
return
|
||||
}
|
||||
|
||||
// Going to try to migrate the container, mark us as visited
|
||||
ctrsVisited[node.id] = true
|
||||
|
||||
ctrErrored := false
|
||||
|
||||
// Add and save the container
|
||||
if err := sqliteState.AddContainer(node.container); err != nil {
|
||||
if errors.Is(err, define.ErrCtrExists) {
|
||||
logrus.Warnf("Container with name %s already exists in the SQLite database; refusing to migrate from BoltDB", node.container.Name())
|
||||
} else {
|
||||
ctrErrored = true
|
||||
ctrErrors[node.id] = err
|
||||
}
|
||||
} else {
|
||||
if err := sqliteState.SaveContainer(node.container); err != nil {
|
||||
ctrErrored = true
|
||||
ctrErrors[node.id] = err
|
||||
}
|
||||
}
|
||||
|
||||
// Recurse to anyone who depends on us and start them
|
||||
for _, successor := range node.dependedOn {
|
||||
migrateNodeDatabase(successor, ctrErrored, ctrErrors, ctrsVisited, sqliteState)
|
||||
}
|
||||
}
|
||||
|
||||
// Contains all details required for traversing the container graph.
|
||||
type nodeTraversal struct {
|
||||
// Optional. but *MUST* be locked.
|
||||
|
||||
@@ -350,26 +350,6 @@ func WithNetworkConfigDir(dir string) RuntimeOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithNamespace sets the namespace for libpod.
|
||||
// Namespaces are used to create scopes to separate containers and pods
|
||||
// in the state.
|
||||
// When namespace is set, libpod will only view containers and pods in
|
||||
// the same namespace. All containers and pods created will default to
|
||||
// the namespace set here.
|
||||
// A namespace of "", the empty string, is equivalent to no namespace,
|
||||
// and all containers and pods will be visible.
|
||||
func WithNamespace(ns string) RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return define.ErrRuntimeFinalized
|
||||
}
|
||||
|
||||
rt.config.Engine.Namespace = ns
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithVolumePath sets the path under which all named volumes
|
||||
// should be created.
|
||||
// The path changes based on whether the user is running as root or not.
|
||||
@@ -446,6 +426,9 @@ func WithEnableSDNotify() RuntimeOption {
|
||||
// WithSyslog sets a runtime option so we know that we have to log to the syslog as well
|
||||
func WithSyslog() RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return define.ErrRuntimeFinalized
|
||||
}
|
||||
rt.syslog = true
|
||||
return nil
|
||||
}
|
||||
@@ -462,6 +445,20 @@ func WithRuntimeFlags(runtimeFlags []string) RuntimeOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithNoBoltError suppresses errors when a Bolt database exists, which would
|
||||
// otherwise prevent creation of a runtime. Useful when migrating the BoltDB
|
||||
// database to SQLite, which is otherwise impossible as we'll error before
|
||||
// getting to the migration code.
|
||||
func WithNoBoltError() RuntimeOption {
|
||||
return func(rt *Runtime) error {
|
||||
if rt.valid {
|
||||
return define.ErrRuntimeFinalized
|
||||
}
|
||||
rt.noBoltError = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Container Creation Options
|
||||
|
||||
// WithMaxLogSize sets the maximum size of container logs.
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
@@ -107,6 +108,10 @@ type Runtime struct {
|
||||
// errors related to lock initialization so a renumber can be performed
|
||||
// if something has gone wrong.
|
||||
doRenumber bool
|
||||
// Do not error on a BoltDB database existing. Useful for commanding
|
||||
// a database migration, as we can't actually get to the migration code
|
||||
// with an existing Bolt database otherwise.
|
||||
noBoltError bool
|
||||
|
||||
// valid indicates whether the runtime is ready to use.
|
||||
// valid is set to true when a runtime is returned from GetRuntime(),
|
||||
@@ -278,6 +283,8 @@ func getLockManager(runtime *Runtime) (lock.Manager, error) {
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
var errBoltExists = errors.New("legacy database exists")
|
||||
|
||||
func getDBState(runtime *Runtime) (State, error) {
|
||||
// TODO - if we further break out the state implementation into
|
||||
// libpod/state, the config could take care of the code below. It
|
||||
@@ -290,8 +297,24 @@ func getDBState(runtime *Runtime) (State, error) {
|
||||
|
||||
switch backend {
|
||||
case config.DBBackendBoltDB:
|
||||
return nil, fmt.Errorf("the BoltDB database backend was removed in Podman 6.0")
|
||||
return nil, fmt.Errorf("the BoltDB database backend was removed in Podman 6.0 - please comment out the `database_backend` line in containers.conf and migrate to SQLite by rebooting the system or using the `podman system migrate --migrate-db` command")
|
||||
case config.DBBackendDefault:
|
||||
if !runtime.noBoltError {
|
||||
boltDBPath := getBoltDBPath(runtime)
|
||||
if err := fileutils.Exists(boltDBPath); err == nil {
|
||||
// We need to return a valid state.
|
||||
// The error below can be discarded if we are performing a state refresh - in which case we migrate the BoltDB database.
|
||||
// Problem: We cannot know if a refresh is happening until almost the end of runtime init.
|
||||
// And we can't really change that - it has to be after the database is set up.
|
||||
// (We need paths from the state to know where to check for the alive file).
|
||||
// Solution is to return a valid state, and defer handling the errBoltExists error until we are sure we are/are not refreshing.
|
||||
sqliteState, err := NewSqliteState(runtime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sqliteState, fmt.Errorf("a BoltDB database exists but is no longer being used. BoltDB support was removed in the Podman 6.0 release. The legacy database can be migrated to SQLite by rebooting and running Podman again or using the `podman system migrate --migrate-db` command. IMPORTANT: Only use the `system migrate` command when you can ensure there are no other Podman processes running. If you are not absolutely sure of this, it is strongly recommended that you reboot. If you do not care about the contents of the legacy database, you can rename or remove the legacy database at %q to suppress this error: %w", boltDBPath, errBoltExists)
|
||||
}
|
||||
}
|
||||
fallthrough
|
||||
case config.DBBackendSQLite:
|
||||
return NewSqliteState(runtime)
|
||||
@@ -341,10 +364,44 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
|
||||
return fmt.Errorf("creating runtime volume path directory: %w", err)
|
||||
}
|
||||
|
||||
// Check if a SQLite DB exists prior to getting a DB.
|
||||
// If it does not, and we have a Bolt database, we may need to remove the SQLite DB
|
||||
// that is created below if we are not performing a database migration.
|
||||
// This ensures that reverting to Podman 5.8.2 will not default to SQLite and break things.
|
||||
sqliteDBExists := checkSQLiteDBExists(runtime)
|
||||
|
||||
// Set up the state.
|
||||
boltExists := false
|
||||
var boltError error
|
||||
runtime.state, err = getDBState(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
if errors.Is(err, errBoltExists) {
|
||||
boltExists = true
|
||||
boltError = err
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// We did not have a SQLite DB before Podman ran.
|
||||
// Remove the one that was created (if one was created) so we don't break reverting to 5.8
|
||||
removeSQLite := false
|
||||
if !sqliteDBExists && boltExists {
|
||||
removeSQLite = true
|
||||
defer func() {
|
||||
if removeSQLite {
|
||||
// Do a final check that the BoltDB state still exists.
|
||||
// If it doesn't, someone else probably performed a migration to SQLite before we got here.
|
||||
// In that case, it'd be bad to remove the now in use database.
|
||||
if err := fileutils.Exists(getBoltDBPath(runtime)); err != nil {
|
||||
return
|
||||
}
|
||||
sqlitePath := sqliteStatePath(runtime)
|
||||
if err := os.Remove(sqlitePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
logrus.Errorf("Error removing SQLite DB %s created during Podman init: %v", sqlitePath, err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Grab config from the database so we can reset some defaults
|
||||
@@ -572,11 +629,21 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (retErr error) {
|
||||
// As such, it's not really a performance concern
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
doRefresh = true
|
||||
removeSQLite = false
|
||||
} else {
|
||||
return fmt.Errorf("reading runtime status file %s: %w", runtimeAliveFile, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !doRefresh && boltExists {
|
||||
// If the BoltDB database still exists, throw an error.
|
||||
// If it doesn't: assume that migration happened in the background.
|
||||
// In that case, safe to proceed.
|
||||
if err := fileutils.Exists(getBoltDBPath(runtime)); err == nil || !errors.Is(err, fs.ErrNotExist) {
|
||||
return boltError
|
||||
}
|
||||
}
|
||||
|
||||
runtime.lockManager, err = getLockManager(runtime)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -792,6 +859,14 @@ func (r *Runtime) Shutdown(force bool) error {
|
||||
func (r *Runtime) refresh(ctx context.Context, alivePath string) error {
|
||||
logrus.Debugf("Podman detected system restart - performing state refresh")
|
||||
|
||||
// Only error that can be returned is no BoltDB present.
|
||||
// In that case, no need to do anything.
|
||||
if err := r.checkCanMigrate(); err == nil {
|
||||
if err := r.migrateDB(); err != nil {
|
||||
logrus.Errorf("Automatic migration from BoltDB to SQLite failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Clear state of database if not running in container
|
||||
if !graphRootMounted() {
|
||||
// First clear the state in the database
|
||||
|
||||
@@ -3,19 +3,22 @@
|
||||
package libpod
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.podman.io/podman/v6/libpod/define"
|
||||
"go.podman.io/podman/v6/pkg/namespaces"
|
||||
"go.podman.io/storage/pkg/fileutils"
|
||||
)
|
||||
|
||||
// Migrate stops the rootless pause process and performs any necessary database
|
||||
// migrations that are required. It can also migrate all containers to a new OCI
|
||||
// runtime, if requested.
|
||||
func (r *Runtime) Migrate(newRuntime string) error {
|
||||
func (r *Runtime) Migrate(newRuntime string, migrateDB bool) error {
|
||||
// Acquire the alive lock and hold it.
|
||||
// Ensures that we don't let other Podman commands run while we are
|
||||
// rewriting things in the DB.
|
||||
@@ -97,5 +100,138 @@ func (r *Runtime) Migrate(newRuntime string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if migrateDB {
|
||||
if err := r.checkCanMigrate(); err != nil {
|
||||
switch {
|
||||
case errors.Is(err, errCannotMigrateNoBolt):
|
||||
fmt.Printf("No migration is necessary: %v\n", err)
|
||||
return r.stopPauseProcess()
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.migrateDB(); err != nil {
|
||||
return fmt.Errorf("migrating database from BoltDB to SQLite: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return r.stopPauseProcess()
|
||||
}
|
||||
|
||||
var errCannotMigrateNoBolt = errors.New("no BoltDB database to migrate")
|
||||
|
||||
func (r *Runtime) checkCanMigrate() error {
|
||||
boltPath := getBoltDBPath(r)
|
||||
if err := fileutils.Exists(boltPath); err != nil {
|
||||
return errCannotMigrateNoBolt
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Runtime) migrateDB() error {
|
||||
boltPath := getBoltDBPath(r)
|
||||
// Get us a Bolt database
|
||||
oldState, err := NewBoltState(boltPath, r)
|
||||
if err != nil {
|
||||
return fmt.Errorf("opening legacy Bolt database at %s: %w", boltPath, err)
|
||||
}
|
||||
|
||||
// Migrate volumes, then pods, then containers.
|
||||
// Containers must be last as the pods they are part of and volumes they use must already exist.
|
||||
allVolumes, err := oldState.AllVolumes()
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving volumes from boltdb: %w", err)
|
||||
}
|
||||
for _, vol := range allVolumes {
|
||||
if err := r.state.AddVolume(vol); err != nil {
|
||||
if errors.Is(err, define.ErrVolumeExists) {
|
||||
logrus.Warnf("Volume with name %s already exists in the SQLite database; refusing to migrate from BoltDB", vol.Name())
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := oldState.UpdateVolume(vol); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.state.SaveVolume(vol); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
allPods, err := oldState.AllPods()
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving pods from boltdb: %w", err)
|
||||
}
|
||||
for _, pod := range allPods {
|
||||
if err := r.state.AddPod(pod); err != nil {
|
||||
if errors.Is(err, define.ErrPodExists) {
|
||||
logrus.Warnf("Pod with name %s already exists in the SQLite database; refusing to migrate from BoltDB", pod.Name())
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
if err := oldState.UpdatePod(pod); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.state.SavePod(pod); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Containers must be done as a graph due to dependencies.
|
||||
// The state will error if we add a container before its dependencies.
|
||||
allCtrs, err := oldState.AllContainers(true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("retrieving containers from boltdb: %w", err)
|
||||
}
|
||||
|
||||
// BoltDB doesn't actually populate container networks on initial pull
|
||||
// from the database, that needs to be done separately.
|
||||
for _, ctr := range allCtrs {
|
||||
ctrNetworks, err := oldState.GetNetworks(ctr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctr.config.Networks = convertLegacyNetworks(ctrNetworks)
|
||||
}
|
||||
|
||||
graph, err := BuildContainerGraph(allCtrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctrErrors := make(map[string]error)
|
||||
ctrsVisited := make(map[string]bool)
|
||||
|
||||
for _, node := range graph.noDepNodes {
|
||||
migrateNodeDatabase(node, false, ctrErrors, ctrsVisited, r.state)
|
||||
}
|
||||
if len(ctrErrors) > 0 {
|
||||
newErrors := make([]error, 0, len(ctrErrors))
|
||||
for id, err := range ctrErrors {
|
||||
newErrors = append(newErrors, fmt.Errorf("migrating container %s: %w", id, err))
|
||||
}
|
||||
return errors.Join(newErrors...)
|
||||
}
|
||||
|
||||
oldState.Close()
|
||||
|
||||
// Move the Bolt database so it is not reused, but preserve so data is not lost.
|
||||
newBoltDBPath := fmt.Sprintf("%s-old", boltPath)
|
||||
if err := os.Rename(boltPath, newBoltDBPath); err != nil {
|
||||
return fmt.Errorf("renaming old database %s to %s: %w", boltPath, newBoltDBPath, err)
|
||||
}
|
||||
fmt.Printf("Old database has been renamed to %s and will no longer be used\n", newBoltDBPath)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getBoltDBPath(runtime *Runtime) string {
|
||||
baseDir := runtime.config.Engine.StaticDir
|
||||
if runtime.storageConfig.TransientStore {
|
||||
baseDir = runtime.config.Engine.TmpDir
|
||||
}
|
||||
return filepath.Join(baseDir, "bolt_state.db")
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@ type SQLiteState struct {
|
||||
}
|
||||
|
||||
const (
|
||||
// Name of the actual database file
|
||||
sqliteDbFilename = "db.sql"
|
||||
// Deal with timezone automatically.
|
||||
sqliteOptionLocation = "_loc=auto"
|
||||
// Force an fsync after each transaction (https://www.sqlite.org/pragma.html#pragma_synchronous).
|
||||
@@ -44,7 +46,7 @@ const (
|
||||
sqliteOptionCaseSensitiveLike = "&_cslike=TRUE"
|
||||
|
||||
// Assembled sqlite options used when opening the database.
|
||||
sqliteOptions = "db.sql?" +
|
||||
sqliteOptions = "?" +
|
||||
sqliteOptionLocation +
|
||||
sqliteOptionSynchronous +
|
||||
sqliteOptionForeignKeys +
|
||||
@@ -57,17 +59,12 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) {
|
||||
logrus.Info("Using sqlite as database backend")
|
||||
state := new(SQLiteState)
|
||||
|
||||
basePath := runtime.storageConfig.GraphRoot
|
||||
if runtime.storageConfig.TransientStore {
|
||||
basePath = runtime.storageConfig.RunRoot
|
||||
} else if !runtime.storageSet.StaticDirSet {
|
||||
basePath = runtime.config.Engine.StaticDir
|
||||
}
|
||||
dbPath := sqliteStatePath(runtime)
|
||||
|
||||
// c/storage is set up *after* the DB - so even though we use the c/s
|
||||
// root (or, for transient, runroot) dir, we need to make the dir
|
||||
// ourselves.
|
||||
if err := os.MkdirAll(basePath, 0o700); err != nil {
|
||||
if err := os.MkdirAll(filepath.Dir(dbPath), 0o700); err != nil {
|
||||
return nil, fmt.Errorf("creating root directory: %w", err)
|
||||
}
|
||||
|
||||
@@ -82,7 +79,7 @@ func NewSqliteState(runtime *Runtime) (_ State, defErr error) {
|
||||
}
|
||||
sqliteOptionBusyTimeout := "&_busy_timeout=" + busyTimeout
|
||||
|
||||
conn, err := sql.Open("sqlite3", filepath.Join(basePath, sqliteOptions+sqliteOptionBusyTimeout))
|
||||
conn, err := sql.Open("sqlite3", dbPath+sqliteOptions+sqliteOptionBusyTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("initializing sqlite database: %w", err)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -14,11 +15,30 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"go.podman.io/common/libnetwork/types"
|
||||
"go.podman.io/podman/v6/libpod/define"
|
||||
"go.podman.io/storage/pkg/fileutils"
|
||||
|
||||
// SQLite backend for database/sql
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// sqliteStatePath returns the path to the sqlite file.
|
||||
func sqliteStatePath(runtime *Runtime) string {
|
||||
basePath := runtime.storageConfig.GraphRoot
|
||||
if runtime.storageConfig.TransientStore {
|
||||
basePath = runtime.storageConfig.RunRoot
|
||||
} else if !runtime.storageSet.StaticDirSet {
|
||||
basePath = runtime.config.Engine.StaticDir
|
||||
}
|
||||
return filepath.Join(basePath, sqliteDbFilename)
|
||||
}
|
||||
|
||||
func checkSQLiteDBExists(runtime *Runtime) bool {
|
||||
if err := fileutils.Exists(sqliteStatePath(runtime)); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func initSQLiteDB(conn *sql.DB) (defErr error) {
|
||||
// Start with a transaction to avoid "database locked" errors.
|
||||
// See https://github.com/mattn/go-sqlite3/issues/274#issuecomment-1429054597
|
||||
|
||||
@@ -43,6 +43,7 @@ type PodmanConfig struct {
|
||||
TLSDetailsFile string // Path to a containers-tls-details.yaml(5) file
|
||||
IsRenumber bool // Is this a system renumber command? If so, a number of checks will be relaxed
|
||||
IsReset bool // Is this a system reset command? If so, a number of checks will be skipped/omitted
|
||||
IsMigrateDB bool // Is this a system migrate --migrate-db command? If so, certain BoltDB related errors will be suppressed
|
||||
MaxWorks int // maximum number of parallel threads
|
||||
MemoryProfile string // Hidden: Should memory profile be taken
|
||||
RegistriesConf string // allows for specifying a custom registries.conf
|
||||
|
||||
@@ -63,6 +63,7 @@ type SystemPruneReport struct {
|
||||
// cli to migrate runtimes of containers
|
||||
type SystemMigrateOptions struct {
|
||||
NewRuntime string
|
||||
MigrateDB bool
|
||||
}
|
||||
|
||||
// SystemDfOptions describes the options for getting df information
|
||||
|
||||
@@ -309,7 +309,7 @@ func (ic *ContainerEngine) Renumber(_ context.Context) error {
|
||||
}
|
||||
|
||||
func (ic *ContainerEngine) Migrate(_ context.Context, options entities.SystemMigrateOptions) error {
|
||||
return ic.Libpod.Migrate(options.NewRuntime)
|
||||
return ic.Libpod.Migrate(options.NewRuntime, options.MigrateDB)
|
||||
}
|
||||
|
||||
func unshareEnv(graphroot, runroot string) []string {
|
||||
|
||||
@@ -32,20 +32,22 @@ var (
|
||||
)
|
||||
|
||||
type engineOpts struct {
|
||||
withFDS bool
|
||||
reset bool
|
||||
renumber bool
|
||||
config *entities.PodmanConfig
|
||||
withFDS bool
|
||||
reset bool
|
||||
renumber bool
|
||||
noBoltError bool
|
||||
config *entities.PodmanConfig
|
||||
}
|
||||
|
||||
// GetRuntime generates a new libpod runtime configured by command line options
|
||||
func GetRuntime(ctx context.Context, flags *flag.FlagSet, cfg *entities.PodmanConfig) (*libpod.Runtime, error) {
|
||||
runtimeSync.Do(func() {
|
||||
runtimeLib, runtimeErr = getRuntime(ctx, flags, &engineOpts{
|
||||
withFDS: true,
|
||||
reset: cfg.IsReset,
|
||||
renumber: cfg.IsRenumber,
|
||||
config: cfg,
|
||||
withFDS: true,
|
||||
reset: cfg.IsReset,
|
||||
renumber: cfg.IsRenumber,
|
||||
noBoltError: cfg.IsMigrateDB,
|
||||
config: cfg,
|
||||
})
|
||||
})
|
||||
return runtimeLib, runtimeErr
|
||||
@@ -132,6 +134,9 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
|
||||
if opts.renumber {
|
||||
options = append(options, libpod.WithRenumber())
|
||||
}
|
||||
if opts.noBoltError {
|
||||
options = append(options, libpod.WithNoBoltError())
|
||||
}
|
||||
|
||||
if len(cfg.RuntimeFlags) > 0 {
|
||||
runtimeFlags := []string{}
|
||||
@@ -149,10 +154,6 @@ func getRuntime(ctx context.Context, fs *flag.FlagSet, opts *engineOpts) (*libpo
|
||||
// TODO CLI flags for image config?
|
||||
// TODO CLI flag for signature policy?
|
||||
|
||||
if len(cfg.ContainersConf.Engine.Namespace) > 0 {
|
||||
options = append(options, libpod.WithNamespace(cfg.ContainersConf.Engine.Namespace))
|
||||
}
|
||||
|
||||
if fs.Changed("runtime") {
|
||||
options = append(options, libpod.WithOCIRuntime(cfg.RuntimePath))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user