Skip to content

Commit c49b801

Browse files
authored
Merge pull request #758 from aaannz/db-online-backup
Database online backup
2 parents 7e3fd91 + 0ee843a commit c49b801

25 files changed

Lines changed: 1573 additions & 32 deletions

mgradm/cmd/backup/backup.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: 2025 SUSE LLC
1+
// SPDX-FileCopyrightText: 2026 SUSE LLC
22
//
33
// SPDX-License-Identifier: Apache-2.0
44

@@ -12,6 +12,7 @@ import (
1212
"github.com/spf13/cobra"
1313

1414
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/backup/create"
15+
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/backup/db"
1516
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/backup/restore"
1617
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/backup/shared"
1718
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
@@ -79,6 +80,7 @@ func NewCommand(globalFlags *types.GlobalFlags) *cobra.Command {
7980
}
8081
backupCmd.AddCommand(newCreateCmd(globalFlags, doBackup))
8182
backupCmd.AddCommand(newRestoreCmd(globalFlags, doRestore))
83+
backupCmd.AddCommand(db.NewDBCmd(globalFlags))
8284
return backupCmd
8385
}
8486

mgradm/cmd/backup/db/cmd.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// SPDX-FileCopyrightText: 2026 SUSE LLC
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package db
6+
7+
import (
8+
"github.com/spf13/cobra"
9+
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
10+
"github.com/uyuni-project/uyuni-tools/shared/types"
11+
"github.com/uyuni-project/uyuni-tools/shared/utils"
12+
)
13+
14+
// CLI definitions
15+
16+
func NewDBCmd(globalFlags *types.GlobalFlags) *cobra.Command {
17+
dbCmd := &cobra.Command{
18+
Use: "db",
19+
Short: L("Database backup management"),
20+
Long: L("Tools for online database backup management"),
21+
}
22+
dbCmd.AddCommand(newDBEnableCmd(globalFlags, doDBEnable))
23+
dbCmd.AddCommand(newDBRebaseCmd(globalFlags, doDBRebase))
24+
dbCmd.AddCommand(newDBDisableCmd(globalFlags, doDBDisable))
25+
dbCmd.AddCommand(newDBStatusCmd(globalFlags, doDBStatus))
26+
dbCmd.AddCommand(newDBRestoreCmd(globalFlags, doDBRestore))
27+
return dbCmd
28+
}
29+
30+
func newDBEnableCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[Flagpole]) *cobra.Command {
31+
var flags Flagpole
32+
cmd := &cobra.Command{
33+
Use: "enable",
34+
Short: L("Enable continuous archiving backup"),
35+
RunE: func(cmd *cobra.Command, args []string) error {
36+
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
37+
},
38+
}
39+
cmd.Flags().Bool("force", false, L("Reconfigure already configured backup"))
40+
41+
return cmd
42+
}
43+
44+
func newDBRebaseCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[Flagpole]) *cobra.Command {
45+
var flags Flagpole
46+
cmd := &cobra.Command{
47+
Use: "rebase",
48+
Short: L("Rebase continuous archiving backup"),
49+
RunE: func(cmd *cobra.Command, args []string) error {
50+
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
51+
},
52+
}
53+
54+
return cmd
55+
}
56+
57+
func newDBDisableCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[Flagpole]) *cobra.Command {
58+
var flags Flagpole
59+
cmd := &cobra.Command{
60+
Use: "disable",
61+
Short: L("Disable continuous archiving backup"),
62+
RunE: func(cmd *cobra.Command, args []string) error {
63+
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
64+
},
65+
}
66+
cmd.Flags().Bool("force", false, L("Don't ask for confirmation when purging volume"))
67+
cmd.Flags().Bool("purge-volume", false, L("Also remove the volume"))
68+
69+
return cmd
70+
}
71+
72+
func newDBStatusCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[Flagpole]) *cobra.Command {
73+
var flags Flagpole
74+
cmd := &cobra.Command{
75+
Use: "status",
76+
Short: L("Check WAL based database backup status"),
77+
RunE: func(cmd *cobra.Command, args []string) error {
78+
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
79+
},
80+
}
81+
return cmd
82+
}
83+
84+
func newDBRestoreCmd(globalFlags *types.GlobalFlags, run utils.CommandFunc[Flagpole]) *cobra.Command {
85+
var flags Flagpole
86+
cmd := &cobra.Command{
87+
Use: "restore",
88+
Short: L("Restore database backup"),
89+
RunE: func(cmd *cobra.Command, args []string) error {
90+
return utils.CommandHelper(globalFlags, cmd, args, &flags, nil, run)
91+
},
92+
}
93+
cmd.Flags().Bool("force", false, L("Don't ask for confirmation"))
94+
return cmd
95+
}
96+
97+
// Actual actions
98+
99+
func doDBEnable(
100+
_ *types.GlobalFlags,
101+
flags *Flagpole,
102+
_ *cobra.Command,
103+
_ []string,
104+
) error {
105+
return Enable(flags.Force)
106+
}
107+
108+
func doDBRebase(
109+
_ *types.GlobalFlags,
110+
_ *Flagpole,
111+
_ *cobra.Command,
112+
_ []string,
113+
) error {
114+
return Rebase()
115+
}
116+
117+
func doDBDisable(
118+
_ *types.GlobalFlags,
119+
flags *Flagpole,
120+
_ *cobra.Command,
121+
_ []string,
122+
) error {
123+
return Disable(flags)
124+
}
125+
126+
func doDBStatus(
127+
_ *types.GlobalFlags,
128+
_ *Flagpole,
129+
_ *cobra.Command,
130+
_ []string,
131+
) error {
132+
return Status()
133+
}
134+
135+
func doDBRestore(
136+
_ *types.GlobalFlags,
137+
flags *Flagpole,
138+
_ *cobra.Command,
139+
_ []string,
140+
) error {
141+
return Restore(flags.Force)
142+
}

mgradm/cmd/backup/db/cmd_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// SPDX-FileCopyrightText: 2026 SUSE LLC
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package db
6+
7+
import (
8+
"testing"
9+
10+
"github.com/spf13/cobra"
11+
"github.com/uyuni-project/uyuni-tools/shared/testutils"
12+
"github.com/uyuni-project/uyuni-tools/shared/types"
13+
)
14+
15+
func TestEnableParamsParsing(t *testing.T) {
16+
args := []string{"--force"}
17+
18+
// Test function asserting that the args are properly parsed
19+
tester := func(_ *types.GlobalFlags, flags *Flagpole,
20+
_ *cobra.Command, _ []string,
21+
) error {
22+
testutils.AssertTrue(t, "Force flag not true", flags.Force)
23+
return nil
24+
}
25+
26+
globalFlags := types.GlobalFlags{}
27+
cmd := newDBEnableCmd(&globalFlags, tester)
28+
29+
testutils.AssertHasAllFlags(t, cmd, args)
30+
31+
cmd.SetArgs(args)
32+
if err := cmd.Execute(); err != nil {
33+
t.Errorf("command failed with error: %s", err)
34+
}
35+
}
36+
37+
func TestRebaseParamsParsing(t *testing.T) {
38+
args := []string{}
39+
40+
// Test function asserting that the args are properly parsed
41+
tester := func(_ *types.GlobalFlags, _ *Flagpole,
42+
_ *cobra.Command, _ []string,
43+
) error {
44+
return nil
45+
}
46+
47+
globalFlags := types.GlobalFlags{}
48+
cmd := newDBRebaseCmd(&globalFlags, tester)
49+
50+
cmd.SetArgs(args)
51+
if err := cmd.Execute(); err != nil {
52+
t.Errorf("command failed with error: %s", err)
53+
}
54+
}
55+
56+
func TestDisableParamsParsing(t *testing.T) {
57+
args := []string{"--force", "--purge-volume"}
58+
59+
// Test function asserting that the args are properly parsed
60+
tester := func(_ *types.GlobalFlags, flags *Flagpole,
61+
_ *cobra.Command, _ []string,
62+
) error {
63+
testutils.AssertTrue(t, "Force flag not true", flags.Force)
64+
testutils.AssertTrue(t, "PurgeVolume flag not true", flags.Purge.Volume)
65+
return nil
66+
}
67+
68+
globalFlags := types.GlobalFlags{}
69+
cmd := newDBDisableCmd(&globalFlags, tester)
70+
71+
testutils.AssertHasAllFlags(t, cmd, args)
72+
73+
cmd.SetArgs(args)
74+
if err := cmd.Execute(); err != nil {
75+
t.Errorf("command failed with error: %s", err)
76+
}
77+
}
78+
79+
func TestStatusParamsParsing(t *testing.T) {
80+
args := []string{}
81+
82+
// Test function asserting that the args are properly parsed
83+
tester := func(_ *types.GlobalFlags, _ *Flagpole,
84+
_ *cobra.Command, _ []string,
85+
) error {
86+
return nil
87+
}
88+
89+
globalFlags := types.GlobalFlags{}
90+
cmd := newDBStatusCmd(&globalFlags, tester)
91+
92+
cmd.SetArgs(args)
93+
if err := cmd.Execute(); err != nil {
94+
t.Errorf("command failed with error: %s", err)
95+
}
96+
}
97+
98+
func TestRestoreParamsParsing(t *testing.T) {
99+
args := []string{"--force"}
100+
101+
// Test function asserting that the args are properly parsed
102+
tester := func(_ *types.GlobalFlags, flags *Flagpole,
103+
_ *cobra.Command, _ []string,
104+
) error {
105+
testutils.AssertTrue(t, "Force flag not true", flags.Force)
106+
return nil
107+
}
108+
109+
globalFlags := types.GlobalFlags{}
110+
cmd := newDBRestoreCmd(&globalFlags, tester)
111+
112+
testutils.AssertHasAllFlags(t, cmd, args)
113+
114+
cmd.SetArgs(args)
115+
if err := cmd.Execute(); err != nil {
116+
t.Errorf("command failed with error: %s", err)
117+
}
118+
}

mgradm/cmd/backup/db/disable.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// SPDX-FileCopyrightText: 2026 SUSE LLC
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package db
6+
7+
import (
8+
"github.com/rs/zerolog/log"
9+
10+
"github.com/uyuni-project/uyuni-tools/shared"
11+
. "github.com/uyuni-project/uyuni-tools/shared/l10n"
12+
"github.com/uyuni-project/uyuni-tools/shared/podman"
13+
"github.com/uyuni-project/uyuni-tools/shared/utils"
14+
)
15+
16+
func Disable(flags *Flagpole) error {
17+
log.Info().Msg(L("Disabling DB backup"))
18+
19+
wasRunning := false
20+
cnx := shared.NewConnection("podman", podman.DBContainerName, "")
21+
if _, err := cnx.GetPodName(); err == nil {
22+
wasRunning = true
23+
if !flags.Force {
24+
res, err := utils.YesNo(L("Database service needs to be restarted to reload backup configurations, continue"))
25+
if err != nil || !res {
26+
log.Info().Msg(L("Backup reconfiguration aborted"))
27+
return nil
28+
}
29+
}
30+
if err := systemd.StopService(podman.DBService); err != nil {
31+
return err
32+
}
33+
}
34+
35+
// Modify postgresql.conf and set archive_mode to off
36+
updates := map[string]string{
37+
"archive_mode": "off",
38+
}
39+
if err := UpdatePostgresConfig(updates); err != nil {
40+
return err
41+
}
42+
43+
if flags.Purge.Volume {
44+
if !flags.Force {
45+
res, err := utils.YesNo(L("Backup volume will be removed, continue?"))
46+
if err != nil || !res {
47+
log.Info().Msg(L("Aborting volume purge"))
48+
return nil
49+
}
50+
}
51+
if err := podman.DeleteVolume(utils.VarPgsqlBackupVolumeMount.Name, false); err != nil {
52+
return err
53+
}
54+
log.Info().Msgf(L("Backup volume '%s' removed"), utils.VarPgsqlBackupVolumeMount.Name)
55+
}
56+
57+
if wasRunning {
58+
log.Info().Msg(L("Starting database…"))
59+
if err := systemd.StartService(podman.DBService); err != nil {
60+
return err
61+
}
62+
}
63+
return Status()
64+
}

0 commit comments

Comments
 (0)