Skip to content

Commit c1edbf7

Browse files
committed
Fix backup handling of images and systemd files.
This commit fixes several issues with the backup: - podman inspect needs the full image path, not just the basename of it. In order to get the image that is currently used we read it from the systemd service config files. - computing the list of systemd files ourselves is error prone and failed with instanciated services. Using `systemctl show -p` with the DropInPaths and FragmentPath is much safer.
1 parent eb2e2da commit c1edbf7

7 files changed

Lines changed: 73 additions & 21 deletions

File tree

mgradm/cmd/backup/create/create.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,13 @@ func gatherContainerImagesToBackup(skipImages bool) []string {
161161

162162
if !skipImages {
163163
for _, service := range utils.UyuniServices {
164-
if present, err := podman.IsImagePresent(service.Image.Name); err == nil && len(present) > 0 {
165-
images = append(images, service.Image.Name)
164+
serviceName, skip := findService(service.Name)
165+
if skip {
166+
continue
167+
}
168+
image := podman.GetServiceImage(serviceName)
169+
if image != "" {
170+
images = append(images, image)
166171
}
167172
}
168173
}

mgradm/cmd/backup/create/systemd_utils.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"io"
1111
"os"
1212
"path"
13+
"strings"
1314

1415
"github.com/rs/zerolog/log"
1516
backup "github.com/uyuni-project/uyuni-tools/mgradm/cmd/backup/shared"
@@ -76,20 +77,19 @@ func gatherSystemdItems() []string {
7677
continue
7778
}
7879

79-
result = append(result, podman.GetServicePath(serviceName))
80-
// For single mandatory replica following returns 0 so loop is skipped
81-
for i := 0; i < systemd.CurrentReplicaCount(service.Name); i++ {
82-
result = append(result, podman.GetServicePath(fmt.Sprintf("%s%d", serviceName, i)))
83-
}
84-
serviceConfDir := podman.GetServiceConfFolder(serviceName)
85-
serviceFiles, err := os.ReadDir(serviceConfDir)
80+
servicePath, err := systemd.GetServiceProperty(serviceName, podman.FragmentPath)
8681
if err != nil {
87-
log.Debug().Msgf("Service configuration directory %s not found, skipping", serviceConfDir)
88-
continue
82+
log.Error().Err(err).Msgf(L("failed to get the path to the %s service file"), serviceName)
83+
} else {
84+
result = append(result, servicePath)
8985
}
90-
result = append(result, serviceConfDir)
91-
for _, entry := range serviceFiles {
92-
result = append(result, path.Join(serviceConfDir, entry.Name()))
86+
87+
// Get the drop in files
88+
dropIns, err := systemd.GetServiceProperty(serviceName, podman.DropInPaths)
89+
if err != nil {
90+
log.Error().Err(err).Msgf(L("failed to get the path to the %s service configuration files"), serviceName)
91+
} else {
92+
result = append(result, strings.Split(dropIns, " ")...)
9393
}
9494
}
9595
return result

shared/podman/images.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net/url"
1313
"os"
1414
"path"
15+
"path/filepath"
1516
"regexp"
1617
"strings"
1718

@@ -354,18 +355,21 @@ func DeleteImage(name string, dryRun bool) error {
354355
return nil
355356
}
356357

358+
var newRunner = utils.NewRunner
359+
357360
// ExportImage saves a podman image based on its name to a specified directory.
358361
// outputDir option expects already existing directory.
359362
// If dryRun is set to true, nothing will be done, only messages logged to explain what would happen.
360363
func ExportImage(name string, outputDir string, dryRun bool) error {
361364
exists := imageExists(name)
362365
if exists {
363-
saveCommand := []string{"podman", "image", "save", "--quiet", "-o", path.Join(outputDir, name+".tar"), name}
366+
baseName, _, _ := strings.Cut(filepath.Base(name), ":")
367+
saveCommand := []string{"podman", "image", "save", "--quiet", "-o", path.Join(outputDir, baseName+".tar"), name}
364368
if dryRun {
365369
log.Info().Msgf(L("Would run %s"), strings.Join(saveCommand, " "))
366370
} else {
367371
log.Info().Msgf(L("Run %s"), strings.Join(saveCommand, " "))
368-
err := utils.RunCmd(saveCommand[0], saveCommand[1:]...)
372+
_, err := newRunner(saveCommand[0], saveCommand[1:]...).Exec()
369373
if err != nil {
370374
return utils.Errorf(err, L("Failed to export image %s"), name)
371375
}
@@ -385,7 +389,7 @@ func RestoreImage(imageFile string, dryRun bool) error {
385389
log.Info().Msgf(L("Would run %s"), strings.Join(restoreCommand, " "))
386390
} else {
387391
log.Info().Msgf(L("Run %s"), strings.Join(restoreCommand, " "))
388-
err := utils.RunCmd(restoreCommand[0], restoreCommand[1:]...)
392+
_, err := newRunner(restoreCommand[0], restoreCommand[1:]...).Exec()
389393
if err != nil {
390394
return utils.Errorf(err, L("Failed to restore image %s"), imageFile)
391395
}

shared/podman/interfaces.go

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

55
package podman
66

7+
const (
8+
// FragmentPath is a systemd property containing the path of the service file.
9+
FragmentPath = "FragmentPath"
10+
11+
// DropInPaths is a systemd property containing the paths of the service configuration file separated by a space.
12+
DropInPaths = "DropInPaths"
13+
)
14+
715
// Systemd is an interface providing systemd operations.
816
type Systemd interface {
917

@@ -65,4 +73,7 @@ type Systemd interface {
6573

6674
// GetServicesFromSystemdFiles return the uyuni enabled services as string list.
6775
GetServicesFromSystemdFiles(systemdFileList string) []string
76+
77+
// GetServiceProperty returns the value of a systemd service property.
78+
GetServiceProperty(service string, property string) (string, error)
6879
}

shared/podman/systemd.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,18 @@ func GetServicePath(name string) string {
7373
return path.Join(servicesPath, name+".service")
7474
}
7575

76+
func (s SystemdImpl) GetServiceProperty(service string, property string) (string, error) {
77+
serviceName := service
78+
if strings.HasSuffix(service, "@") {
79+
serviceName = service + "0"
80+
}
81+
out, err := newRunner("systemctl", "show", "-p", property, serviceName).Exec()
82+
if err != nil {
83+
return "", utils.Errorf(err, L("Failed to get the %[1]s property from %[2]s service"), property, service)
84+
}
85+
return strings.TrimPrefix(strings.TrimSpace(string(out)), property+"="), nil
86+
}
87+
7688
// GetServiceConfFolder return the conf folder for systemd services.
7789
func GetServiceConfFolder(name string) string {
7890
return path.Join(servicesPath, name+".service.d")

shared/podman/systemd_test.go

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

55
package podman
66

77
import (
8+
"errors"
89
"os"
910
"path"
11+
"strings"
1012
"testing"
1113

1214
"github.com/uyuni-project/uyuni-tools/shared/testutils"
@@ -91,3 +93,21 @@ Environment="PODMAN_EXTRA_ARGS="
9193
actual = testutils.ReadFile(t, path.Join(serviceConfDir, "custom.conf"))
9294
testutils.AssertEquals(t, "invalid custom.conf file", customFile, actual)
9395
}
96+
97+
func TestGetServiceProperty(t *testing.T) {
98+
newRunner = testutils.FakeRunnerGenerator("TestProperty=foo bar\n", nil)
99+
tested := SystemdImpl{}
100+
actual, err := tested.GetServiceProperty("myservice", "TestProperty")
101+
testutils.AssertTrue(t, "No error expected", err == nil)
102+
testutils.AssertEquals(t, "Wrong expected property", "foo bar", actual)
103+
}
104+
105+
func TestGetServicePropertyError(t *testing.T) {
106+
newRunner = testutils.FakeRunnerGenerator("", errors.New("Test error"))
107+
tested := SystemdImpl{}
108+
actual, err := tested.GetServiceProperty("myservice", "TestProperty")
109+
testutils.AssertTrue(t, "Error message missing the root error message", strings.Contains(err.Error(), "Test error"))
110+
testutils.AssertTrue(t, "Unexpected error description",
111+
strings.Contains(err.Error(), "Failed to get the TestProperty property from myservice service"))
112+
testutils.AssertEquals(t, "Wrong expected property", "", actual)
113+
}

shared/podman/utils.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,10 @@ func GetServiceImage(service string) string {
146146
return ""
147147
}
148148

149-
imageFinder := regexp.MustCompile(`UYUNI_IMAGE=(.*)`)
149+
imageFinder := regexp.MustCompile(`UYUNI.*_IMAGE=(.*)`)
150150
matches := imageFinder.FindStringSubmatch(string(out))
151151
if len(matches) < 2 {
152-
log.Warn().Msgf(L("no UYUNI_IMAGE defined in %s systemd service"), service)
152+
log.Warn().Msgf(L("no UYUNI.*_IMAGE defined in %s systemd service"), service)
153153
return ""
154154
}
155155
return matches[1]

0 commit comments

Comments
 (0)