Skip to content

Commit 63092e2

Browse files
authored
Merge branch 'main' into fix-418-interactive-sql-logging
2 parents 62d10b4 + b6ad445 commit 63092e2

22 files changed

Lines changed: 1280 additions & 64 deletions

File tree

.tito/packages/uyuni-tools

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5.2.13-0 ./
1+
5.2.16-0 ./

mgradm/cmd/backup/create/systemd_utils.go

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

55
package create
66

77
import (
88
"archive/tar"
9+
"encoding/csv"
910
"fmt"
1011
"io"
1112
"os"
@@ -91,9 +92,22 @@ func gatherSystemdItems() []string {
9192
if err != nil {
9293
log.Debug().Err(err).Msgf("failed to get the path to the %s service configuration files", serviceName)
9394
} else {
94-
dropIns := strings.Split(dropIns, " ")
95-
result = append(result, filepath.Dir(dropIns[0]))
96-
result = append(result, dropIns[:]...)
95+
r := csv.NewReader(strings.NewReader(dropIns))
96+
r.Comma = ' '
97+
dropIns, err := r.Read()
98+
if err != nil {
99+
log.Debug().Err(err).Msgf("failed to parse the drop-in paths for %s service", serviceName)
100+
}
101+
if len(dropIns) > 0 {
102+
result = append(result, filepath.Dir(dropIns[0]))
103+
result = append(result, dropIns[:]...)
104+
}
105+
}
106+
107+
// Get the environment file
108+
envFile := path.Join(servicePath+".d", podman.ServerEnvironmentFile)
109+
if _, err := os.Stat(envFile); err == nil {
110+
result = append(result, envFile)
97111
}
98112
}
99113
return result
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// SPDX-FileCopyrightText: 2026 SUSE LLC
2+
//
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
package create
6+
7+
import (
8+
"archive/tar"
9+
"io"
10+
"os"
11+
"path/filepath"
12+
"testing"
13+
14+
"github.com/uyuni-project/uyuni-tools/shared/podman"
15+
"github.com/uyuni-project/uyuni-tools/shared/testutils"
16+
"github.com/uyuni-project/uyuni-tools/shared/types"
17+
"github.com/uyuni-project/uyuni-tools/shared/utils"
18+
)
19+
20+
func TestFindService(t *testing.T) {
21+
oldSystemd := systemd
22+
defer func() { systemd = oldSystemd }()
23+
24+
tests := []struct {
25+
name string
26+
serviceName string
27+
hasServiceMock func(string) bool
28+
expectedService string
29+
expectedSkip bool
30+
}{
31+
{
32+
name: "Service directly exists",
33+
serviceName: "uyuni-server",
34+
hasServiceMock: func(name string) bool {
35+
return name == "uyuni-server"
36+
},
37+
expectedService: "uyuni-server",
38+
expectedSkip: false,
39+
},
40+
{
41+
name: "Service exists as template service",
42+
serviceName: "uyuni-server",
43+
hasServiceMock: func(name string) bool {
44+
return name == "uyuni-server@"
45+
},
46+
expectedService: "uyuni-server@",
47+
expectedSkip: false,
48+
},
49+
{
50+
name: "Service does not exist",
51+
serviceName: "uyuni-server",
52+
hasServiceMock: func(_ string) bool {
53+
return false
54+
},
55+
expectedService: "uyuni-server@",
56+
expectedSkip: true,
57+
},
58+
}
59+
60+
for _, tt := range tests {
61+
t.Run(tt.name, func(t *testing.T) {
62+
installedServices := []string{}
63+
if tt.hasServiceMock(tt.serviceName) {
64+
installedServices = append(installedServices, tt.serviceName)
65+
}
66+
if tt.hasServiceMock(tt.serviceName + "@") {
67+
installedServices = append(installedServices, tt.serviceName+"@")
68+
}
69+
70+
systemd = podman.NewSystemdWithDriver(&testutils.FakeSystemdDriver{Installed: installedServices})
71+
service, skip := findService(tt.serviceName)
72+
testutils.AssertEquals(t, "service name mismatch", tt.expectedService, service)
73+
testutils.AssertEquals(t, "skip mismatch", tt.expectedSkip, skip)
74+
})
75+
}
76+
}
77+
78+
func TestGatherSystemdItems(t *testing.T) {
79+
oldSystemd := systemd
80+
oldUyuniServices := utils.UyuniServices
81+
defer func() {
82+
systemd = oldSystemd
83+
utils.UyuniServices = oldUyuniServices
84+
}()
85+
86+
// We'll use a single custom service for testing
87+
utils.UyuniServices = []types.UyuniService{
88+
{Name: "test-service"},
89+
}
90+
91+
tempDir := t.TempDir()
92+
93+
// Service is skipped (does not exist)
94+
systemd = podman.NewSystemdWithDriver(&testutils.FakeSystemdDriver{})
95+
items := gatherSystemdItems()
96+
testutils.AssertEquals(t, "items length should be 0", 0, len(items))
97+
98+
// Service exists, but FragmentPath fails
99+
systemd = podman.NewSystemdWithDriver(&testutils.FakeSystemdDriver{
100+
Installed: []string{"test-service"},
101+
})
102+
items = gatherSystemdItems()
103+
testutils.AssertEquals(t, "items length should be 0 on FragmentPath error", 0, len(items))
104+
105+
// Service exists, FragmentPath succeeds, but DropInPaths fails, and no env file
106+
servicePath := filepath.Join(tempDir, "test-service.service")
107+
systemd = podman.NewSystemdWithDriver(&testutils.FakeSystemdDriver{
108+
Installed: []string{"test-service"},
109+
ServiceProperties: map[string]map[string]string{
110+
"test-service": {
111+
podman.FragmentPath: servicePath,
112+
},
113+
},
114+
})
115+
items = gatherSystemdItems()
116+
expectedItems := []string{servicePath}
117+
testutils.AssertEquals(t, "items should only contain service path", expectedItems, items)
118+
119+
// Service exists, FragmentPath succeeds, DropInPaths is present but empty, and no env file
120+
systemd = podman.NewSystemdWithDriver(&testutils.FakeSystemdDriver{
121+
Installed: []string{"test-service"},
122+
ServiceProperties: map[string]map[string]string{
123+
"test-service": {
124+
podman.FragmentPath: servicePath,
125+
podman.DropInPaths: "",
126+
},
127+
},
128+
})
129+
items = gatherSystemdItems()
130+
expectedItems = []string{servicePath}
131+
testutils.AssertEquals(t, "items should only contain service path when DropInPaths is empty", expectedItems, items)
132+
133+
// Service exists, FragmentPath succeeds, DropInPaths succeeds, and env file exists
134+
serviceDir := servicePath + ".d"
135+
err := os.MkdirAll(serviceDir, 0755)
136+
if err != nil {
137+
t.Fatal(err)
138+
}
139+
140+
envFile := filepath.Join(serviceDir, podman.ServerEnvironmentFile)
141+
err = os.WriteFile(envFile, []byte("some env"), 0644)
142+
if err != nil {
143+
t.Fatal(err)
144+
}
145+
146+
dropIn1 := filepath.Join(serviceDir, "10-test.conf")
147+
dropIn2 := filepath.Join(serviceDir, "20-test.conf")
148+
// Test with a drop-in file that has spaces in its name. Systemd adds quotes around such paths
149+
dropIn3 := filepath.Join(serviceDir, "30 with space test.conf")
150+
dropInPaths := dropIn1 + " " + dropIn2 + " " + "\"" + dropIn3 + "\""
151+
152+
systemd = podman.NewSystemdWithDriver(&testutils.FakeSystemdDriver{
153+
Installed: []string{"test-service"},
154+
ServiceProperties: map[string]map[string]string{
155+
"test-service": {
156+
podman.FragmentPath: servicePath,
157+
podman.DropInPaths: dropInPaths,
158+
},
159+
},
160+
})
161+
162+
items = gatherSystemdItems()
163+
expectedItems = []string{
164+
servicePath,
165+
serviceDir,
166+
dropIn1,
167+
dropIn2,
168+
dropIn3,
169+
envFile,
170+
}
171+
testutils.AssertEquals(t, "items mismatch", expectedItems, items)
172+
}
173+
174+
func TestExportSystemdConfiguration(t *testing.T) {
175+
oldSystemd := systemd
176+
oldUyuniServices := utils.UyuniServices
177+
defer func() {
178+
systemd = oldSystemd
179+
utils.UyuniServices = oldUyuniServices
180+
}()
181+
182+
tempDir := t.TempDir()
183+
184+
serviceFile := filepath.Join(tempDir, "fake-service.service")
185+
serviceContent := "[Service]\nExecStart=/usr/bin/fake"
186+
err := os.WriteFile(serviceFile, []byte(serviceContent), 0644)
187+
if err != nil {
188+
t.Fatal(err)
189+
}
190+
191+
serviceDir := serviceFile + ".d"
192+
err = os.MkdirAll(serviceDir, 0755)
193+
if err != nil {
194+
t.Fatal(err)
195+
}
196+
197+
dropInFile := filepath.Join(serviceDir, "custom.conf")
198+
dropInContent := "[Service]\nEnvironment=FOO=bar"
199+
err = os.WriteFile(dropInFile, []byte(dropInContent), 0644)
200+
if err != nil {
201+
t.Fatal(err)
202+
}
203+
204+
dropInWhiteSpaceFile := filepath.Join(serviceDir, "with space test.conf")
205+
dropInWhiteSpaceContent := "[Service]\nEnvironment=BAR=foo"
206+
err = os.WriteFile(dropInWhiteSpaceFile, []byte(dropInWhiteSpaceContent), 0644)
207+
if err != nil {
208+
t.Fatal(err)
209+
}
210+
211+
dropInPaths := dropInFile + " " + "\"" + dropInWhiteSpaceFile + "\""
212+
213+
utils.UyuniServices = []types.UyuniService{
214+
{Name: "fake-service"},
215+
}
216+
217+
envFileContent := "SOME_ENV=1"
218+
envFile := filepath.Join(serviceDir, podman.ServerEnvironmentFile)
219+
err = os.WriteFile(envFile, []byte(envFileContent), 0644)
220+
if err != nil {
221+
t.Fatal(err)
222+
}
223+
224+
systemd = podman.NewSystemdWithDriver(&testutils.FakeSystemdDriver{
225+
Installed: []string{"fake-service"},
226+
ServiceProperties: map[string]map[string]string{
227+
"fake-service": {
228+
podman.FragmentPath: serviceFile,
229+
podman.DropInPaths: dropInPaths,
230+
},
231+
},
232+
})
233+
234+
outputDir := t.TempDir()
235+
236+
// dryRun = true
237+
err = exportSystemdConfiguration(outputDir, true)
238+
testutils.AssertNoError(t, "exportSystemdConfiguration with dryRun=true failed", err)
239+
240+
backupFilePath := filepath.Join(outputDir, "systemdBackup.tar")
241+
if _, err := os.Stat(backupFilePath); err == nil {
242+
t.Error("backup file should not have been created on dryRun=true")
243+
}
244+
245+
// dryRun = false
246+
err = exportSystemdConfiguration(outputDir, false)
247+
testutils.AssertNoError(t, "exportSystemdConfiguration with dryRun=false failed", err)
248+
249+
if _, err := os.Stat(backupFilePath); os.IsNotExist(err) {
250+
t.Fatal("backup file was not created on dryRun=false")
251+
}
252+
253+
tarFile, err := os.Open(backupFilePath)
254+
if err != nil {
255+
t.Fatal(err)
256+
}
257+
defer tarFile.Close()
258+
259+
tr := tar.NewReader(tarFile)
260+
foundFiles := make(map[string]string)
261+
262+
for {
263+
header, err := tr.Next()
264+
if err == io.EOF {
265+
break
266+
}
267+
if err != nil {
268+
t.Fatal(err)
269+
}
270+
271+
if header.FileInfo().IsDir() {
272+
foundFiles[header.Name] = "dir"
273+
} else {
274+
content, err := io.ReadAll(tr)
275+
if err != nil {
276+
t.Fatal(err)
277+
}
278+
foundFiles[header.Name] = string(content)
279+
}
280+
}
281+
282+
expectedServiceDirName := serviceDir + "/"
283+
testutils.AssertEquals(t, "service file content", serviceContent, foundFiles[serviceFile])
284+
testutils.AssertEquals(t, "service dir entry type", "dir", foundFiles[expectedServiceDirName])
285+
testutils.AssertEquals(t, "drop-in file content", dropInContent, foundFiles[dropInFile])
286+
testutils.AssertEquals(t, "drop-in white space content", dropInWhiteSpaceContent, foundFiles[dropInWhiteSpaceFile])
287+
testutils.AssertEquals(t, "env file content", envFileContent, foundFiles[envFile])
288+
}

mgradm/cmd/cmd.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/restart"
2121
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/scale"
2222
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/server"
23+
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/ssl"
2324
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/start"
2425
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/status"
2526
"github.com/uyuni-project/uyuni-tools/mgradm/cmd/stop"
@@ -93,6 +94,7 @@ func NewUyuniadmCommand() (*cobra.Command, error) {
9394
rootCmd.AddCommand(gpg.NewCommand(globalFlags))
9495
rootCmd.AddCommand(backup.NewCommand(globalFlags))
9596
rootCmd.AddCommand(server.NewCommand(globalFlags))
97+
rootCmd.AddCommand(ssl.NewCommand(globalFlags))
9698

9799
rootCmd.AddCommand(utils.GetConfigHelpCommand())
98100

0 commit comments

Comments
 (0)