]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - cmd/safcm/main_sync_test.go
tests: add end-to-end test with configuration without any changes
[safcm/safcm.git] / cmd / safcm / main_sync_test.go
1 // Copyright (C) 2021  Simon Ruderich
2 //
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
15
16 package main_test
17
18 import (
19         "fmt"
20         "net"
21         "os"
22         "os/exec"
23         "regexp"
24         "runtime"
25         "strings"
26         "testing"
27         "time"
28
29         ft "ruderich.org/simon/safcm/cmd/safcm-remote/sync/filetest"
30         "ruderich.org/simon/safcm/testutil"
31 )
32
33 func TestSyncSshEndToEnd(t *testing.T) {
34         cwd, err := os.Getwd()
35         if err != nil {
36                 t.Fatal(err)
37         }
38         defer os.Chdir(cwd)
39
40         var suffix string
41         // Needs different options in sshd_config
42         if runtime.GOOS == "openbsd" {
43                 suffix = ".openbsd"
44         }
45
46         sshDir := cwd + "/testdata/ssh"
47         sshCmd := exec.Command("/usr/sbin/sshd",
48                 "-D", // stay in foreground
49                 "-e", // write messages to stderr instead of syslog
50                 "-f", sshDir+"/sshd/sshd_config"+suffix,
51                 "-h", sshDir+"/sshd/ssh_host_key",
52                 "-o", "AuthorizedKeysFile="+sshDir+"/ssh/authorized_keys",
53         )
54         sshCmd.Stderr = os.Stderr
55         err = sshCmd.Start()
56         if err != nil {
57                 t.Fatal(err)
58         }
59         defer sshCmd.Process.Kill()
60
61         // Wait until SSH server is ready (up to 30 seconds)
62         for i := 0; i < 30; i++ {
63                 conn, err := net.Dial("tcp", "127.0.0.1:29327")
64                 if err == nil {
65                         conn.Close()
66                         break
67                 }
68                 time.Sleep(time.Second)
69         }
70
71         err = os.Chdir(sshDir + "/project")
72         if err != nil {
73                 t.Fatal(err)
74         }
75
76         ft.CreateDirectoryExists("no-changes.example.org", 0755)
77         ft.CreateDirectoryExists("no-changes.example.org/files", 0755)
78         ft.CreateDirectoryExists("no-changes.example.org/files/etc", 0755)
79         ft.CreateDirectoryExists("no-changes.example.org/files/tmp", 0755)
80
81         noChangePermissions := `
82 /: 0755 root root
83 /etc: 0755 root root
84 /tmp: 1777 root root
85 `
86         if runtime.GOOS == "openbsd" || runtime.GOOS == "freebsd" {
87                 noChangePermissions = `
88 /: 0755 root wheel
89 /etc: 0755 root wheel
90 /tmp: 1777 root wheel
91 `
92         }
93         ft.CreateFile("no-changes.example.org/permissions.yaml",
94                 noChangePermissions, 0644)
95
96         skipUnlessCiRun := len(os.Getenv("SAFCM_CI_RUN")) == 0
97
98         tests := []struct {
99                 name   string
100                 skip   bool
101                 remove bool
102                 args   []string
103                 exp    string
104                 expErr error
105         }{
106
107                 {
108                         "no settings",
109                         false,
110                         true,
111                         []string{"no-settings.example.org"},
112                         `<LOG>[info]    [no-settings.example.org] remote helper upload in progress
113 <LOG>[info]    [no-settings.example.org] no changes
114 `,
115                         nil,
116                 },
117                 {
118                         "no settings (no helper upload)",
119                         false,
120                         false,
121                         []string{"no-settings.example.org"},
122                         `<LOG>[info]    [no-settings.example.org] no changes
123 `,
124                         nil,
125                 },
126                 {
127                         "no settings (error)",
128                         false,
129                         true,
130                         []string{"-log", "error", "no-settings.example.org"},
131                         ``,
132                         nil,
133                 },
134                 {
135                         "no settings (verbose)",
136                         false,
137                         true,
138                         []string{"-log", "verbose", "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
143 `,
144                         nil,
145                 },
146                 {
147                         "no settings (debug2)",
148                         false,
149                         true,
150                         []string{"-log", "debug2", "no-settings.example.org"},
151                         `<LOG>[info]    [no-settings.example.org] remote helper upload in progress
152 <LOG>[verbose] [no-settings.example.org] host groups: all <DET> <DET> no-settings.example.org
153 <LOG>[verbose] [no-settings.example.org] host group priorities (descending): no-settings.example.org
154 <LOG>[info]    [no-settings.example.org] no changes
155 `,
156                         nil,
157                 },
158
159                 // NOTE: We use -n on regular runs to prevent changing
160                 // anything important on the host when running as root!
161
162                 {
163                         "no changes (dry-run)",
164                         false,
165                         true,
166                         []string{"-n", "no-changes.example.org"},
167                         `<LOG>[info]    [no-changes.example.org] remote helper upload in progress
168 <LOG>[info]    [no-changes.example.org] no changes
169 `,
170                         nil,
171                 },
172                 {
173                         "no changes (dry-run, debug2)",
174                         false,
175                         true,
176                         []string{"-n", "-log", "debug2", "no-changes.example.org"},
177                         `<LOG>[info]    [no-changes.example.org] remote helper upload in progress
178 <LOG>[verbose] [no-changes.example.org] host groups: all <DET> <DET> no-changes.example.org
179 <LOG>[verbose] [no-changes.example.org] host group priorities (descending): no-changes.example.org
180 <LOG>[debug]   [no-changes.example.org] sync remote: files: "/" (no-changes.example.org): unchanged
181 <LOG>[debug]   [no-changes.example.org] sync remote: files: "/etc" (no-changes.example.org): unchanged
182 <LOG>[debug]   [no-changes.example.org] sync remote: files: "/tmp" (no-changes.example.org): unchanged
183 <LOG>[info]    [no-changes.example.org] no changes
184 `,
185                         nil,
186                 },
187
188                 {
189                         "no changes",
190                         skipUnlessCiRun,
191                         true,
192                         []string{"no-changes.example.org"},
193                         `<LOG>[info]    [no-changes.example.org] remote helper upload in progress
194 <LOG>[info]    [no-changes.example.org] no changes
195 `,
196                         nil,
197                 },
198                 {
199                         "no changes (debug2)",
200                         skipUnlessCiRun,
201                         true,
202                         []string{"-log", "debug2", "no-changes.example.org"},
203                         `<LOG>[info]    [no-changes.example.org] remote helper upload in progress
204 <LOG>[verbose] [no-changes.example.org] host groups: all <DET> <DET> no-changes.example.org
205 <LOG>[verbose] [no-changes.example.org] host group priorities (descending): no-changes.example.org
206 <LOG>[debug]   [no-changes.example.org] sync remote: files: "/" (no-changes.example.org): unchanged
207 <LOG>[debug]   [no-changes.example.org] sync remote: files: "/etc" (no-changes.example.org): unchanged
208 <LOG>[debug]   [no-changes.example.org] sync remote: files: "/tmp" (no-changes.example.org): unchanged
209 <LOG>[info]    [no-changes.example.org] no changes
210 `,
211                         nil,
212                 },
213         }
214
215         remotePath := fmt.Sprintf("/tmp/safcm-remote-%d", os.Getuid())
216
217         logRegexp := regexp.MustCompile(`^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} `)
218         detectedRegexp := regexp.MustCompile(`detected_\S+`)
219
220         for _, tc := range tests {
221                 t.Run(tc.name, func(t *testing.T) {
222                         if tc.remove {
223                                 os.Remove(remotePath)
224                         }
225
226                         args := append([]string{"sync",
227                                 "-sshconfig", sshDir + "/ssh/ssh_config",
228                         }, tc.args...)
229                         cmd := exec.Command("../../../../../safcm", args...)
230                         out, err := cmd.CombinedOutput()
231
232                         var tmp []string
233                         for _, x := range strings.Split(string(out), "\n") {
234                                 // Strip parts which change on each run (LOG)
235                                 // or depending on the system (DET)
236                                 x = logRegexp.ReplaceAllString(x, "<LOG>")
237                                 x = detectedRegexp.ReplaceAllString(x, "<DET>")
238                                 tmp = append(tmp, x)
239                         }
240                         res := strings.Join(tmp, "\n")
241
242                         testutil.AssertEqual(t, "res", res, tc.exp)
243                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
244                 })
245         }
246
247         os.Remove(remotePath)
248 }