1 // SPDX-License-Identifier: GPL-3.0-or-later
2 // Copyright (C) 2021-2024 Simon Ruderich
17 ft "ruderich.org/simon/safcm/remote/sync/filetest"
18 "ruderich.org/simon/safcm/testutil"
21 func TestSyncSshEndToEnd(t *testing.T) {
22 cwd, err := os.Getwd()
26 defer os.Chdir(cwd) //nolint:errcheck
29 // Needs different options in sshd_config
30 if runtime.GOOS == "openbsd" {
34 sshDir := cwd + "/testdata/ssh"
35 sshCmd := exec.Command("/usr/sbin/sshd",
36 "-D", // stay in foreground
37 "-e", // write messages to stderr instead of syslog
38 "-f", sshDir+"/sshd/sshd_config"+suffix,
39 "-h", sshDir+"/sshd/ssh_host_key",
40 "-o", "AuthorizedKeysFile="+sshDir+"/ssh/authorized_keys",
42 sshCmd.Stderr = os.Stderr
47 defer sshCmd.Process.Kill() //nolint:errcheck
49 // Wait until SSH server is ready (up to 30 seconds)
50 for i := 0; i < 30; i++ {
51 conn, err := net.Dial("tcp", "127.0.0.1:29327")
56 time.Sleep(time.Second)
59 err = os.Chdir(sshDir + "/project")
64 ft.CreateDirectoryExists("no-changes.example.org", 0755)
65 ft.CreateDirectoryExists("no-changes.example.org/files", 0755)
66 ft.CreateDirectoryExists("no-changes.example.org/files/etc", 0755)
67 ft.CreateDirectoryExists("no-changes.example.org/files/tmp", 0755)
69 noChangePermissions := `
74 if runtime.GOOS == "openbsd" || runtime.GOOS == "freebsd" {
75 noChangePermissions = `
81 ft.CreateFile("no-changes.example.org/permissions.yaml",
82 noChangePermissions, 0644)
84 skipUnlessCiRun := len(os.Getenv("SAFCM_CI_RUN")) == 0
99 []string{"no-settings.example.org"},
100 `<LOG>[info] [no-settings.example.org] remote helper upload in progress
101 <LOG>[info] [no-settings.example.org] no changes
106 "no settings (no helper upload)",
109 []string{"no-settings.example.org"},
110 `<LOG>[info] [no-settings.example.org] no changes
115 "no settings (error)",
118 []string{"-log", "error", "no-settings.example.org"},
123 "no settings (verbose)",
126 []string{"-log", "verbose", "no-settings.example.org"},
127 `<LOG>[info] [no-settings.example.org] remote helper upload in progress
128 <LOG>[verbose] [no-settings.example.org] host groups: all <DET> <DET> no-settings.example.org
129 <LOG>[verbose] [no-settings.example.org] host group priorities (descending): no-settings.example.org
130 <LOG>[info] [no-settings.example.org] no changes
135 "no settings (debug2)",
138 []string{"-log", "debug2", "no-settings.example.org"},
139 `<LOG>[info] [no-settings.example.org] remote helper upload in progress
140 <LOG>[verbose] [no-settings.example.org] host groups: all <DET> <DET> no-settings.example.org
141 <LOG>[verbose] [no-settings.example.org] host group priorities (descending): no-settings.example.org
142 <LOG>[info] [no-settings.example.org] no changes
147 // NOTE: We use -n on regular runs to prevent changing
148 // anything important on the host when running as root!
151 "no changes (dry-run)",
154 []string{"-n", "no-changes.example.org"},
155 `<LOG>[info] [no-changes.example.org] remote helper upload in progress
156 <LOG>[info] [no-changes.example.org] no changes
161 "no changes (dry-run, debug2)",
164 []string{"-n", "-log", "debug2", "no-changes.example.org"},
165 `<LOG>[info] [no-changes.example.org] remote helper upload in progress
166 <LOG>[verbose] [no-changes.example.org] host groups: all <DET> <DET> no-changes.example.org
167 <LOG>[verbose] [no-changes.example.org] host group priorities (descending): no-changes.example.org
168 <LOG>[debug] [no-changes.example.org] files: "/" (no-changes.example.org): unchanged
169 <LOG>[debug] [no-changes.example.org] files: "/etc" (no-changes.example.org): unchanged
170 <LOG>[debug] [no-changes.example.org] files: "/tmp" (no-changes.example.org): unchanged
171 <LOG>[info] [no-changes.example.org] no changes
179 []string{"no-changes.example.org"},
180 `<LOG>[info] [no-changes.example.org] remote helper upload in progress
181 <LOG>[info] [no-changes.example.org] no changes
186 "no changes (debug2)",
189 []string{"-log", "debug2", "no-changes.example.org"},
190 `<LOG>[info] [no-changes.example.org] remote helper upload in progress
191 <LOG>[verbose] [no-changes.example.org] host groups: all <DET> <DET> no-changes.example.org
192 <LOG>[verbose] [no-changes.example.org] host group priorities (descending): no-changes.example.org
193 <LOG>[debug] [no-changes.example.org] files: "/" (no-changes.example.org): unchanged
194 <LOG>[debug] [no-changes.example.org] files: "/etc" (no-changes.example.org): unchanged
195 <LOG>[debug] [no-changes.example.org] files: "/tmp" (no-changes.example.org): unchanged
196 <LOG>[info] [no-changes.example.org] no changes
202 "no effect commands (dry-run)",
205 []string{"-n", "no-effect-commands.example.org"},
206 `<LOG>[info] [no-effect-commands.example.org] remote helper upload in progress
207 <LOG>[info] [no-effect-commands.example.org]
208 will execute 2 command(s): (dry-run)
209 "echo this is a command"
215 "no effect commands (dry-run)",
218 []string{"-n", "-log", "debug2", "no-effect-commands.example.org"},
219 `<LOG>[info] [no-effect-commands.example.org] remote helper upload in progress
220 <LOG>[verbose] [no-effect-commands.example.org] host groups: all <DET> <DET> no-effect-commands.example.org
221 <LOG>[verbose] [no-effect-commands.example.org] host group priorities (descending): no-effect-commands.example.org
222 <LOG>[info] [no-effect-commands.example.org]
223 will execute 2 command(s): (dry-run)
224 "echo this is a command"
230 "no effect commands",
233 []string{"no-effect-commands.example.org"},
234 `<LOG>[info] [no-effect-commands.example.org] remote helper upload in progress
235 <LOG>[info] [no-effect-commands.example.org]
236 executed 2 command(s):
237 "echo this is a command":
244 "no effect commands (debug2)",
247 []string{"-log", "debug2", "no-effect-commands.example.org"},
248 `<LOG>[info] [no-effect-commands.example.org] remote helper upload in progress
249 <LOG>[verbose] [no-effect-commands.example.org] host groups: all <DET> <DET> no-effect-commands.example.org
250 <LOG>[verbose] [no-effect-commands.example.org] host group priorities (descending): no-effect-commands.example.org
251 <LOG>[verbose] [no-effect-commands.example.org] commands: running "/bin/sh" "-c" "echo this is a command" (no-effect-commands.example.org)
252 <LOG>[debug2] [no-effect-commands.example.org] commands: command output:
254 <LOG>[verbose] [no-effect-commands.example.org] commands: running "/bin/sh" "-c" "true" (no-effect-commands.example.org)
255 <LOG>[info] [no-effect-commands.example.org]
256 executed 2 command(s):
257 "echo this is a command":
265 "no effect commands failing (dry-run)",
268 []string{"-n", "no-effect-commands-failing.example.org"},
269 `<LOG>[info] [no-effect-commands-failing.example.org] remote helper upload in progress
270 <LOG>[info] [no-effect-commands-failing.example.org]
271 will execute 2 command(s): (dry-run)
272 "echo this is a command"
273 "echo failing; false"
278 "no effect commands failing (dry-run)",
281 []string{"-n", "-log", "debug2", "no-effect-commands-failing.example.org"},
282 `<LOG>[info] [no-effect-commands-failing.example.org] remote helper upload in progress
283 <LOG>[verbose] [no-effect-commands-failing.example.org] host groups: all <DET> <DET> no-effect-commands-failing.example.org
284 <LOG>[verbose] [no-effect-commands-failing.example.org] host group priorities (descending): no-effect-commands-failing.example.org
285 <LOG>[info] [no-effect-commands-failing.example.org]
286 will execute 2 command(s): (dry-run)
287 "echo this is a command"
288 "echo failing; false"
293 "no effect commands failing",
296 []string{"no-effect-commands-failing.example.org"},
297 `<LOG>[info] [no-effect-commands-failing.example.org] remote helper upload in progress
298 <LOG>[info] [no-effect-commands-failing.example.org]
299 executed 2 command(s):
300 "echo this is a command":
302 "echo failing; false", failed: "exit status 1":
304 <LOG>[error] [no-effect-commands-failing.example.org] commands: "echo failing; false" failed: exit status 1
306 fmt.Errorf("exit status 1"),
309 "no effect commands failing (debug2)",
312 []string{"-log", "debug2", "no-effect-commands-failing.example.org"},
313 `<LOG>[info] [no-effect-commands-failing.example.org] remote helper upload in progress
314 <LOG>[verbose] [no-effect-commands-failing.example.org] host groups: all <DET> <DET> no-effect-commands-failing.example.org
315 <LOG>[verbose] [no-effect-commands-failing.example.org] host group priorities (descending): no-effect-commands-failing.example.org
316 <LOG>[verbose] [no-effect-commands-failing.example.org] commands: running "/bin/sh" "-c" "echo this is a command" (no-effect-commands-failing.example.org)
317 <LOG>[debug2] [no-effect-commands-failing.example.org] commands: command output:
319 <LOG>[verbose] [no-effect-commands-failing.example.org] commands: running "/bin/sh" "-c" "echo failing; false" (no-effect-commands-failing.example.org)
320 <LOG>[debug2] [no-effect-commands-failing.example.org] commands: command output:
322 <LOG>[info] [no-effect-commands-failing.example.org]
323 executed 2 command(s):
324 "echo this is a command":
326 "echo failing; false", failed: "exit status 1":
328 <LOG>[error] [no-effect-commands-failing.example.org] commands: "echo failing; false" failed: exit status 1
330 fmt.Errorf("exit status 1"),
334 remotePath := fmt.Sprintf("/tmp/safcm-remote-%d", os.Getuid())
336 logRegexp := regexp.MustCompile(`^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `)
337 detectedRegexp := regexp.MustCompile(`detected_\S+`)
339 t.Run("error before connection is established", func(t *testing.T) {
340 // Fake $PATH so safcm cannot find the `ssh` binary.
341 path := os.Getenv("PATH")
342 os.Setenv("PATH", "")
343 defer os.Setenv("PATH", path)
345 cmd := exec.Command("../../../../../safcm",
346 "sync", "-n", "no-settings.example.org")
347 _, err := cmd.CombinedOutput()
349 t.Errorf("err = nil")
353 for _, tc := range tests {
354 t.Run(tc.name, func(t *testing.T) {
356 os.Remove(remotePath)
359 args := append([]string{"sync",
360 "-sshconfig", sshDir + "/ssh/ssh_config",
362 cmd := exec.Command("../../../../../safcm", args...)
363 out, err := cmd.CombinedOutput()
366 for _, x := range strings.Split(string(out), "\n") {
367 // Strip parts which change on each run (LOG)
368 // or depending on the system (DET)
369 x = logRegexp.ReplaceAllString(x, "<LOG>")
370 x = detectedRegexp.ReplaceAllString(x, "<DET>")
373 res := strings.Join(tmp, "\n")
375 testutil.AssertEqual(t, "res", res, tc.exp)
376 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
380 os.Remove(remotePath)