1 // Copyright (C) 2021 Simon Ruderich
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.
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.
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/>.
27 "ruderich.org/simon/safcm"
28 "ruderich.org/simon/safcm/testutil"
31 func TestHostSyncReq(t *testing.T) {
32 cwd, err := os.Getwd()
49 // NOTE: Also update MsgSyncReq in safcm-remote test cases
50 // when changing anything here!
65 Files: map[string]*safcm.File{
66 "/": &safcm.File{Path: "/",
68 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
71 TriggerCommands: []string{
78 Mode: fs.ModeDir | 0755,
82 "/etc/.hidden": &safcm.File{
85 Mode: 0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
90 "/etc/motd": &safcm.File{
96 Data: []byte("Welcome to Host ONE\n\n\n\n\n\nall\n\n\nhost1.example.org\n\n\n\n"),
98 "/etc/rc.local": &safcm.File{
100 Path: "/etc/rc.local",
104 Data: []byte("#!/bin/sh\n"),
105 TriggerCommands: []string{
109 "/etc/resolv.conf": &safcm.File{
111 Path: "/etc/resolv.conf",
117 Data: []byte("nameserver ::1\n"),
118 TriggerCommands: []string{
119 "echo resolv.conf updated",
122 "/etc/test": &safcm.File{
125 Mode: os.ModeSymlink | 0777,
128 Data: []byte("doesnt-exist"),
140 "echo -n command two",
144 "host1.example.org: <nil> 3 host groups: all group host1.example.org remove",
145 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
151 "project: host1 (log level info)",
163 Files: map[string]*safcm.File{
164 "/": &safcm.File{Path: "/",
166 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
169 TriggerCommands: []string{
176 Mode: fs.ModeDir | 0755,
180 "/etc/.hidden": &safcm.File{
182 Path: "/etc/.hidden",
183 Mode: 0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
188 "/etc/motd": &safcm.File{
194 Data: []byte("Welcome to Host ONE\n\n\n\n\n\nall\n\n\nhost1.example.org\n\n\n\n"),
196 "/etc/rc.local": &safcm.File{
198 Path: "/etc/rc.local",
202 Data: []byte("#!/bin/sh\n"),
203 TriggerCommands: []string{
207 "/etc/resolv.conf": &safcm.File{
209 Path: "/etc/resolv.conf",
215 Data: []byte("nameserver ::1\n"),
216 TriggerCommands: []string{
217 "echo resolv.conf updated",
220 "/etc/test": &safcm.File{
223 Mode: os.ModeSymlink | 0777,
226 Data: []byte("doesnt-exist"),
238 "echo -n command two",
247 "project-conflict-file",
253 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
254 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
256 fmt.Errorf("groups dns and all both provide file \"/etc/resolv.conf\"\nUse 'group_order' in config.yaml to declare preference"),
259 "conflict: file from detected group",
260 "project-conflict-file",
268 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
269 "host2.example.org: <nil> 3 host group priorities (desc. order): host2.example.org",
271 fmt.Errorf("groups other and all both provide file \"/etc/resolv.conf\"\nUse 'group_order' in config.yaml to declare preference"),
276 "project-conflict-dir",
282 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
283 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
285 fmt.Errorf("groups dns and all both provide file \"/etc\"\nUse 'group_order' in config.yaml to declare preference"),
288 "conflict: dir from detected group",
289 "project-conflict-dir",
297 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
298 "host2.example.org: <nil> 3 host group priorities (desc. order): host2.example.org",
300 fmt.Errorf("groups other and all both provide file \"/etc\"\nUse 'group_order' in config.yaml to declare preference"),
305 "project-group-cycle",
311 fmt.Errorf("groups.yaml: cycle while expanding group \"group-b\""),
316 "project-group_order",
321 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
322 Files: map[string]*safcm.File{
325 Mode: fs.ModeDir | 0755,
328 OrigGroup: "host1.example.org",
332 Mode: fs.ModeDir | 0755,
335 OrigGroup: "host1.example.org",
337 "/etc/dir-to-file": {
338 Path: "/etc/dir-to-file",
342 Data: []byte("dir-to-file: from group-a\n"),
343 OrigGroup: "group-a",
345 "/etc/dir-to-filex": {
346 OrigGroup: "group-b",
347 Path: "/etc/dir-to-filex",
351 Data: []byte("dir-to-filex\n"),
353 "/etc/dir-to-link": {
354 Path: "/etc/dir-to-link",
355 Mode: fs.ModeSymlink | 0777,
358 Data: []byte("target"),
359 OrigGroup: "group-a",
361 "/etc/dir-to-linkx": {
362 OrigGroup: "group-b",
363 Path: "/etc/dir-to-linkx",
367 Data: []byte("dir-to-linkx\n"),
369 "/etc/file-to-dir": {
370 Path: "/etc/file-to-dir",
371 Mode: fs.ModeDir | 0755,
374 OrigGroup: "group-a",
376 "/etc/file-to-dir/file": {
377 Path: "/etc/file-to-dir/file",
381 Data: []byte("file: from group-a\n"),
382 OrigGroup: "group-a",
384 "/etc/file-to-dir/dir": {
385 Path: "/etc/file-to-dir/dir",
386 Mode: fs.ModeDir | 0755,
389 OrigGroup: "group-a",
391 "/etc/file-to-dir/dir/file2": {
392 Path: "/etc/file-to-dir/dir/file2",
396 Data: []byte("file2: from group-a\n"),
397 OrigGroup: "group-a",
404 Data: []byte("motd: from host1\n"),
405 OrigGroup: "host1.example.org",
410 "host1.example.org: <nil> 3 host groups: all group-a group-b host1.example.org",
411 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org group-a group-b all",
412 `host1.example.org: <nil> 4 files: "/etc": group group-a overwrites triggers from group group-b`,
413 `host1.example.org: <nil> 4 files: "/etc": group host1.example.org overwrites triggers from group group-a`,
419 for _, tc := range tests {
420 t.Run(tc.name, func(t *testing.T) {
421 err = os.Chdir(filepath.Join(cwd,
422 "testdata", tc.project))
427 // `safcm fixperms` in case user has strict umask
428 log.SetOutput(io.Discard)
429 err := MainFixperms()
433 log.SetOutput(os.Stderr)
435 cfg, allHosts, allGroups, err := LoadBaseFiles()
439 cfg.LogLevel = tc.level
442 ch := make(chan Event)
443 done := make(chan struct{})
450 if x.ConnEvent.Type != 0 {
451 panic("unexpected ConnEvent")
453 events = append(events,
454 fmt.Sprintf("%s: %v %d %s",
456 x.Error, x.Log.Level,
463 host: allHosts.Map[tc.host],
466 allGroups: allGroups,
470 res, err := s.hostSyncReq(tc.detected)
472 testutil.AssertEqual(t, "res", res, tc.exp)
473 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
477 testutil.AssertEqual(t, "events",
478 events, tc.expEvents)