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/>.
28 "github.com/google/go-cmp/cmp"
30 "ruderich.org/simon/safcm"
33 func TestHostSyncReq(t *testing.T) {
34 cwd, err := os.Getwd()
51 // NOTE: Also update MsgSyncReq in safcm-remote test cases
52 // when changing anything here!
67 Files: map[string]*safcm.File{
68 "/": &safcm.File{Path: "/",
70 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
73 TriggerCommands: []string{
80 Mode: fs.ModeDir | 0755,
84 "/etc/.hidden": &safcm.File{
87 Mode: 0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
92 "/etc/motd": &safcm.File{
98 Data: []byte("Welcome to Host ONE\n\n\n\n"),
100 "/etc/rc.local": &safcm.File{
102 Path: "/etc/rc.local",
106 Data: []byte("#!/bin/sh\n"),
107 TriggerCommands: []string{
111 "/etc/resolv.conf": &safcm.File{
113 Path: "/etc/resolv.conf",
119 Data: []byte("nameserver ::1\n"),
120 TriggerCommands: []string{
121 "echo resolv.conf updated",
124 "/etc/test": &safcm.File{
127 Mode: os.ModeSymlink | 0777,
130 Data: []byte("doesnt-exist"),
142 "echo -n command two",
146 "host1.example.org: <nil> 3 host groups: all group host1.example.org remove",
147 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
153 "project: host1 (log level info)",
165 Files: map[string]*safcm.File{
166 "/": &safcm.File{Path: "/",
168 Mode: fs.ModeDir | 0755 | fs.ModeSetgid,
171 TriggerCommands: []string{
178 Mode: fs.ModeDir | 0755,
182 "/etc/.hidden": &safcm.File{
184 Path: "/etc/.hidden",
185 Mode: 0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
190 "/etc/motd": &safcm.File{
196 Data: []byte("Welcome to Host ONE\n\n\n\n"),
198 "/etc/rc.local": &safcm.File{
200 Path: "/etc/rc.local",
204 Data: []byte("#!/bin/sh\n"),
205 TriggerCommands: []string{
209 "/etc/resolv.conf": &safcm.File{
211 Path: "/etc/resolv.conf",
217 Data: []byte("nameserver ::1\n"),
218 TriggerCommands: []string{
219 "echo resolv.conf updated",
222 "/etc/test": &safcm.File{
225 Mode: os.ModeSymlink | 0777,
228 Data: []byte("doesnt-exist"),
240 "echo -n command two",
249 "project-conflict-file",
255 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
256 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
258 fmt.Errorf("groups dns and all both provide file \"/etc/resolv.conf\"\nUse 'group_order' in config.yaml to declare preference"),
261 "conflict: file from detected group",
262 "project-conflict-file",
270 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
271 "host2.example.org: <nil> 3 host group priorities (desc. order): host2.example.org",
273 fmt.Errorf("groups other and all both provide file \"/etc/resolv.conf\"\nUse 'group_order' in config.yaml to declare preference"),
278 "project-conflict-dir",
284 "host1.example.org: <nil> 3 host groups: all dns host1.example.org",
285 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org",
287 fmt.Errorf("groups dns and all both provide file \"/etc\"\nUse 'group_order' in config.yaml to declare preference"),
290 "conflict: dir from detected group",
291 "project-conflict-dir",
299 "host2.example.org: <nil> 3 host groups: all detected_other host2.example.org other",
300 "host2.example.org: <nil> 3 host group priorities (desc. order): host2.example.org",
302 fmt.Errorf("groups other and all both provide file \"/etc\"\nUse 'group_order' in config.yaml to declare preference"),
307 "project-group-cycle",
313 fmt.Errorf("groups.yaml: cycle while expanding group \"group-b\""),
318 "project-group_order",
323 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
324 Files: map[string]*safcm.File{
327 Mode: fs.ModeDir | 0755,
330 OrigGroup: "host1.example.org",
334 Mode: fs.ModeDir | 0755,
337 OrigGroup: "host1.example.org",
339 "/etc/dir-to-file": {
340 Path: "/etc/dir-to-file",
344 Data: []byte("dir-to-file: from group-a\n"),
345 OrigGroup: "group-a",
347 "/etc/dir-to-filex": {
348 OrigGroup: "group-b",
349 Path: "/etc/dir-to-filex",
353 Data: []byte("dir-to-filex\n"),
355 "/etc/dir-to-link": {
356 Path: "/etc/dir-to-link",
357 Mode: fs.ModeSymlink | 0777,
360 Data: []byte("target"),
361 OrigGroup: "group-a",
363 "/etc/dir-to-linkx": {
364 OrigGroup: "group-b",
365 Path: "/etc/dir-to-linkx",
369 Data: []byte("dir-to-linkx\n"),
371 "/etc/file-to-dir": {
372 Path: "/etc/file-to-dir",
373 Mode: fs.ModeDir | 0755,
376 OrigGroup: "group-a",
378 "/etc/file-to-dir/file": {
379 Path: "/etc/file-to-dir/file",
383 Data: []byte("file: from group-a\n"),
384 OrigGroup: "group-a",
386 "/etc/file-to-dir/dir": {
387 Path: "/etc/file-to-dir/dir",
388 Mode: fs.ModeDir | 0755,
391 OrigGroup: "group-a",
393 "/etc/file-to-dir/dir/file2": {
394 Path: "/etc/file-to-dir/dir/file2",
398 Data: []byte("file2: from group-a\n"),
399 OrigGroup: "group-a",
406 Data: []byte("motd: from host1\n"),
407 OrigGroup: "host1.example.org",
412 "host1.example.org: <nil> 3 host groups: all group-a group-b host1.example.org",
413 "host1.example.org: <nil> 3 host group priorities (desc. order): host1.example.org group-a group-b all",
414 `host1.example.org: <nil> 4 files: "/etc": group group-a overwrites triggers from group group-b`,
415 `host1.example.org: <nil> 4 files: "/etc": group host1.example.org overwrites triggers from group group-a`,
421 for _, tc := range tests {
422 t.Run(tc.name, func(t *testing.T) {
423 err = os.Chdir(filepath.Join(cwd, "testdata", tc.project))
428 // `safcm fixperms` in case user has strict umask
429 log.SetOutput(io.Discard)
430 err := MainFixperms()
434 log.SetOutput(os.Stderr)
436 cfg, allHosts, allGroups, err := LoadBaseFiles()
440 cfg.LogLevel = tc.level
443 ch := make(chan Event)
444 done := make(chan struct{})
451 if x.ConnEvent.Type != 0 {
452 panic("unexpected ConnEvent")
454 events = append(events,
455 fmt.Sprintf("%s: %v %d %s",
457 x.Error, x.Log.Level,
464 host: allHosts.Map[tc.host],
467 allGroups: allGroups,
471 res, err := s.hostSyncReq(tc.detected)
472 if !reflect.DeepEqual(tc.exp, res) {
474 cmp.Diff(tc.exp, res))
476 // Ugly but the simplest way to compare errors (including nil)
477 if fmt.Sprintf("%s", err) != fmt.Sprintf("%s", tc.expErr) {
478 t.Errorf("err = %#v, want %#v",
484 if !reflect.DeepEqual(tc.expEvents, events) {
485 t.Errorf("events: %s",
486 cmp.Diff(tc.expEvents, events))