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!
66 Files: map[string]*safcm.File{
67 "/": &safcm.File{Path: "/",
69 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
72 TriggerCommands: []string{
79 Mode: fs.ModeDir | 0755,
83 "/etc/.hidden": &safcm.File{
86 Mode: 0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
91 "/etc/motd": &safcm.File{
97 Data: []byte("Welcome to Host ONE\n\n\n\n\n\nall\n\n\nhost1.example.org\n\n\n\n"),
99 "/etc/rc.local": &safcm.File{
101 Path: "/etc/rc.local",
105 Data: []byte("#!/bin/sh\n"),
106 TriggerCommands: []string{
110 "/etc/resolv.conf": &safcm.File{
112 Path: "/etc/resolv.conf",
118 Data: []byte("nameserver ::1\n"),
119 TriggerCommands: []string{
120 "echo resolv.conf updated",
123 "/etc/test": &safcm.File{
126 Mode: os.ModeSymlink | 0777,
129 Data: []byte("doesnt-exist"),
139 Commands: []*safcm.Command{
142 Cmd: "echo command one",
146 Cmd: "echo -n command two",
151 "host1.example.org: <nil> 3 host groups: all group group3 host1.example.org remove",
152 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org",
158 "project: host1 (log level info)",
171 Files: map[string]*safcm.File{
172 "/": &safcm.File{Path: "/",
174 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
177 TriggerCommands: []string{
184 Mode: fs.ModeDir | 0755,
188 "/etc/.hidden": &safcm.File{
190 Path: "/etc/.hidden",
191 Mode: 0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
196 "/etc/motd": &safcm.File{
202 Data: []byte("Welcome to Host ONE\n\n\n\n\n\nall\n\n\nhost1.example.org\n\n\n\n"),
204 "/etc/rc.local": &safcm.File{
206 Path: "/etc/rc.local",
210 Data: []byte("#!/bin/sh\n"),
211 TriggerCommands: []string{
215 "/etc/resolv.conf": &safcm.File{
217 Path: "/etc/resolv.conf",
223 Data: []byte("nameserver ::1\n"),
224 TriggerCommands: []string{
225 "echo resolv.conf updated",
228 "/etc/test": &safcm.File{
231 Mode: os.ModeSymlink | 0777,
234 Data: []byte("doesnt-exist"),
244 Commands: []*safcm.Command{
247 Cmd: "echo command one",
251 Cmd: "echo -n command two",
261 "project-conflict-file",
267 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
268 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org",
270 fmt.Errorf("groups dns and all both provide file \"/etc/resolv.conf\"\nUse 'group_priority' in config.yaml to declare preference"),
273 "conflict: file from detected group",
274 "project-conflict-file",
282 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
283 "host2.example.org: <nil> 3 host group priorities (descending): host2.example.org",
285 fmt.Errorf("groups other and all both provide file \"/etc/resolv.conf\"\nUse 'group_priority' in config.yaml to declare preference"),
290 "project-conflict-dir",
296 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
297 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org",
299 fmt.Errorf("groups dns and all both provide file \"/etc\"\nUse 'group_priority' in config.yaml to declare preference"),
302 "conflict: dir from detected group",
303 "project-conflict-dir",
311 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
312 "host2.example.org: <nil> 3 host group priorities (descending): host2.example.org",
314 fmt.Errorf("groups other and all both provide file \"/etc\"\nUse 'group_priority' in config.yaml to declare preference"),
319 "project-group-cycle",
325 fmt.Errorf("groups.yaml: cycle while expanding group \"group-b\""),
330 "project-group_priority",
335 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
336 Files: map[string]*safcm.File{
339 Mode: fs.ModeDir | 0755,
342 OrigGroup: "host1.example.org",
346 Mode: fs.ModeDir | 0755,
349 OrigGroup: "host1.example.org",
351 "/etc/dir-to-file": {
352 Path: "/etc/dir-to-file",
356 Data: []byte("dir-to-file: from group-a\n"),
357 OrigGroup: "group-a",
359 "/etc/dir-to-filex": {
360 OrigGroup: "group-b",
361 Path: "/etc/dir-to-filex",
365 Data: []byte("dir-to-filex\n"),
367 "/etc/dir-to-link": {
368 Path: "/etc/dir-to-link",
369 Mode: fs.ModeSymlink | 0777,
372 Data: []byte("target"),
373 OrigGroup: "group-a",
375 "/etc/dir-to-linkx": {
376 OrigGroup: "group-b",
377 Path: "/etc/dir-to-linkx",
381 Data: []byte("dir-to-linkx\n"),
383 "/etc/file-to-dir": {
384 Path: "/etc/file-to-dir",
385 Mode: fs.ModeDir | 0755,
388 OrigGroup: "group-a",
390 "/etc/file-to-dir/file": {
391 Path: "/etc/file-to-dir/file",
395 Data: []byte("file: from group-a\n"),
396 OrigGroup: "group-a",
398 "/etc/file-to-dir/dir": {
399 Path: "/etc/file-to-dir/dir",
400 Mode: fs.ModeDir | 0755,
403 OrigGroup: "group-a",
405 "/etc/file-to-dir/dir/file2": {
406 Path: "/etc/file-to-dir/dir/file2",
410 Data: []byte("file2: from group-a\n"),
411 OrigGroup: "group-a",
418 Data: []byte("motd: from host1\n"),
419 OrigGroup: "host1.example.org",
424 "host1.example.org: <nil> 3 host groups: all group-a group-b host1.example.org",
425 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org group-a group-b all",
426 `host1.example.org: <nil> 4 files: "/etc": group group-a overwrites triggers from group group-b`,
427 `host1.example.org: <nil> 4 files: "/etc": group host1.example.org overwrites triggers from group group-a`,
433 "group_priority (single group)",
434 "project-group_priority-single",
439 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
440 Files: map[string]*safcm.File{
443 Mode: fs.ModeDir | 0755,
446 OrigGroup: "group-a",
453 Data: []byte("file.txt: from group-a\n"),
454 OrigGroup: "group-a",
459 "host1.example.org: <nil> 3 host groups: all group-a group-b host1.example.org",
460 "host1.example.org: <nil> 3 host group priorities (descending): host1.example.org group-a",
466 for _, tc := range tests {
467 t.Run(tc.name, func(t *testing.T) {
468 err = os.Chdir(filepath.Join(cwd,
469 "testdata", tc.project))
474 // `safcm fixperms` in case user has strict umask
475 log.SetOutput(io.Discard)
476 err := MainFixperms()
480 log.SetOutput(os.Stderr)
482 cfg, allHosts, allGroups, err := LoadBaseFiles()
486 cfg.LogLevel = tc.level
489 ch := make(chan Event)
490 done := make(chan struct{})
497 if x.ConnEvent.Type != 0 {
498 panic("unexpected ConnEvent")
500 events = append(events,
501 fmt.Sprintf("%s: %v %d %s",
503 x.Error, x.Log.Level,
510 host: allHosts.Map[tc.host],
513 allGroups: allGroups,
517 res, err := s.hostSyncReq(tc.detected)
519 testutil.AssertEqual(t, "res", res, tc.exp)
520 testutil.AssertErrorEqual(t, "err", err, tc.expErr)
524 testutil.AssertEqual(t, "events",
525 events, tc.expEvents)