]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - cmd/safcm/sync_sync_test.go
Add README.adoc
[safcm/safcm.git] / cmd / safcm / sync_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
17
18 import (
19         "fmt"
20         "io"
21         "io/fs"
22         "log"
23         "os"
24         "path/filepath"
25         "testing"
26
27         "ruderich.org/simon/safcm"
28         "ruderich.org/simon/safcm/testutil"
29 )
30
31 func TestHostSyncReq(t *testing.T) {
32         cwd, err := os.Getwd()
33         if err != nil {
34                 t.Fatal(err)
35         }
36         defer os.Chdir(cwd)
37
38         tests := []struct {
39                 name      string
40                 project   string
41                 host      string
42                 detected  []string
43                 level     safcm.LogLevel
44                 exp       safcm.MsgSyncReq
45                 expEvents []string
46                 expErr    error
47         }{
48
49                 // NOTE: Also update MsgSyncReq in safcm-remote test cases
50                 // when changing anything here!
51
52                 {
53                         "project: host1",
54                         "project",
55                         "host1.example.org",
56                         nil,
57                         safcm.LogDebug3,
58                         safcm.MsgSyncReq{
59                                 Groups: []string{
60                                         "all",
61                                         "group",
62                                         "remove",
63                                         "host1.example.org",
64                                 },
65                                 Files: map[string]*safcm.File{
66                                         "/": &safcm.File{Path: "/",
67                                                 OrigGroup: "group",
68                                                 Mode:      fs.ModeDir | 0755 | fs.ModeSetgid,
69                                                 Uid:       -1,
70                                                 Gid:       -1,
71                                                 TriggerCommands: []string{
72                                                         "touch /.update",
73                                                 },
74                                         },
75                                         "/etc": &safcm.File{
76                                                 OrigGroup: "group",
77                                                 Path:      "/etc",
78                                                 Mode:      fs.ModeDir | 0755,
79                                                 Uid:       -1,
80                                                 Gid:       -1,
81                                         },
82                                         "/etc/.hidden": &safcm.File{
83                                                 OrigGroup: "group",
84                                                 Path:      "/etc/.hidden",
85                                                 Mode:      0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
86                                                 Uid:       -1,
87                                                 Gid:       -1,
88                                                 Data:      []byte("..."),
89                                         },
90                                         "/etc/motd": &safcm.File{
91                                                 OrigGroup: "group",
92                                                 Path:      "/etc/motd",
93                                                 Mode:      0644,
94                                                 Uid:       -1,
95                                                 Gid:       -1,
96                                                 Data:      []byte("Welcome to Host ONE\n\n\n\n"),
97                                         },
98                                         "/etc/rc.local": &safcm.File{
99                                                 OrigGroup: "group",
100                                                 Path:      "/etc/rc.local",
101                                                 Mode:      0700,
102                                                 Uid:       -1,
103                                                 Gid:       -1,
104                                                 Data:      []byte("#!/bin/sh\n"),
105                                                 TriggerCommands: []string{
106                                                         "/etc/rc.local",
107                                                 },
108                                         },
109                                         "/etc/resolv.conf": &safcm.File{
110                                                 OrigGroup: "group",
111                                                 Path:      "/etc/resolv.conf",
112                                                 Mode:      0641,
113                                                 User:      "user",
114                                                 Uid:       -1,
115                                                 Group:     "group",
116                                                 Gid:       -1,
117                                                 Data:      []byte("nameserver ::1\n"),
118                                                 TriggerCommands: []string{
119                                                         "echo resolv.conf updated",
120                                                 },
121                                         },
122                                         "/etc/test": &safcm.File{
123                                                 OrigGroup: "group",
124                                                 Path:      "/etc/test",
125                                                 Mode:      os.ModeSymlink | 0777,
126                                                 Uid:       -1,
127                                                 Gid:       -1,
128                                                 Data:      []byte("doesnt-exist"),
129                                         },
130                                 },
131                                 Packages: []string{
132                                         "unbound",
133                                         "unbound-anchor",
134                                 },
135                                 Services: []string{
136                                         "unbound",
137                                 },
138                                 Commands: []string{
139                                         "echo command one",
140                                         "echo -n command two",
141                                 },
142                         },
143                         []string{
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",
146                         },
147                         nil,
148                 },
149
150                 {
151                         "project: host1 (log level info)",
152                         "project",
153                         "host1.example.org",
154                         nil,
155                         safcm.LogInfo,
156                         safcm.MsgSyncReq{
157                                 Groups: []string{
158                                         "all",
159                                         "group",
160                                         "remove",
161                                         "host1.example.org",
162                                 },
163                                 Files: map[string]*safcm.File{
164                                         "/": &safcm.File{Path: "/",
165                                                 OrigGroup: "group",
166                                                 Mode:      fs.ModeDir | 0755 | fs.ModeSetgid,
167                                                 Uid:       -1,
168                                                 Gid:       -1,
169                                                 TriggerCommands: []string{
170                                                         "touch /.update",
171                                                 },
172                                         },
173                                         "/etc": &safcm.File{
174                                                 OrigGroup: "group",
175                                                 Path:      "/etc",
176                                                 Mode:      fs.ModeDir | 0755,
177                                                 Uid:       -1,
178                                                 Gid:       -1,
179                                         },
180                                         "/etc/.hidden": &safcm.File{
181                                                 OrigGroup: "group",
182                                                 Path:      "/etc/.hidden",
183                                                 Mode:      0100 | fs.ModeSetuid | fs.ModeSetgid | fs.ModeSticky,
184                                                 Uid:       -1,
185                                                 Gid:       -1,
186                                                 Data:      []byte("..."),
187                                         },
188                                         "/etc/motd": &safcm.File{
189                                                 OrigGroup: "group",
190                                                 Path:      "/etc/motd",
191                                                 Mode:      0644,
192                                                 Uid:       -1,
193                                                 Gid:       -1,
194                                                 Data:      []byte("Welcome to Host ONE\n\n\n\n"),
195                                         },
196                                         "/etc/rc.local": &safcm.File{
197                                                 OrigGroup: "group",
198                                                 Path:      "/etc/rc.local",
199                                                 Mode:      0700,
200                                                 Uid:       -1,
201                                                 Gid:       -1,
202                                                 Data:      []byte("#!/bin/sh\n"),
203                                                 TriggerCommands: []string{
204                                                         "/etc/rc.local",
205                                                 },
206                                         },
207                                         "/etc/resolv.conf": &safcm.File{
208                                                 OrigGroup: "group",
209                                                 Path:      "/etc/resolv.conf",
210                                                 Mode:      0641,
211                                                 User:      "user",
212                                                 Uid:       -1,
213                                                 Group:     "group",
214                                                 Gid:       -1,
215                                                 Data:      []byte("nameserver ::1\n"),
216                                                 TriggerCommands: []string{
217                                                         "echo resolv.conf updated",
218                                                 },
219                                         },
220                                         "/etc/test": &safcm.File{
221                                                 OrigGroup: "group",
222                                                 Path:      "/etc/test",
223                                                 Mode:      os.ModeSymlink | 0777,
224                                                 Uid:       -1,
225                                                 Gid:       -1,
226                                                 Data:      []byte("doesnt-exist"),
227                                         },
228                                 },
229                                 Packages: []string{
230                                         "unbound",
231                                         "unbound-anchor",
232                                 },
233                                 Services: []string{
234                                         "unbound",
235                                 },
236                                 Commands: []string{
237                                         "echo command one",
238                                         "echo -n command two",
239                                 },
240                         },
241                         nil,
242                         nil,
243                 },
244
245                 {
246                         "conflict: file",
247                         "project-conflict-file",
248                         "host1.example.org",
249                         nil,
250                         safcm.LogDebug3,
251                         safcm.MsgSyncReq{},
252                         []string{
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",
255                         },
256                         fmt.Errorf("groups dns and all both provide file \"/etc/resolv.conf\"\nUse 'group_order' in config.yaml to declare preference"),
257                 },
258                 {
259                         "conflict: file from detected group",
260                         "project-conflict-file",
261                         "host2.example.org",
262                         []string{
263                                 "detected_other",
264                         },
265                         safcm.LogDebug3,
266                         safcm.MsgSyncReq{},
267                         []string{
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",
270                         },
271                         fmt.Errorf("groups other and all both provide file \"/etc/resolv.conf\"\nUse 'group_order' in config.yaml to declare preference"),
272                 },
273
274                 {
275                         "conflict: dir",
276                         "project-conflict-dir",
277                         "host1.example.org",
278                         nil,
279                         safcm.LogDebug3,
280                         safcm.MsgSyncReq{},
281                         []string{
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",
284                         },
285                         fmt.Errorf("groups dns and all both provide file \"/etc\"\nUse 'group_order' in config.yaml to declare preference"),
286                 },
287                 {
288                         "conflict: dir from detected group",
289                         "project-conflict-dir",
290                         "host2.example.org",
291                         []string{
292                                 "detected_other",
293                         },
294                         safcm.LogDebug3,
295                         safcm.MsgSyncReq{},
296                         []string{
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",
299                         },
300                         fmt.Errorf("groups other and all both provide file \"/etc\"\nUse 'group_order' in config.yaml to declare preference"),
301                 },
302
303                 {
304                         "group: cycle",
305                         "project-group-cycle",
306                         "host1.example.org",
307                         nil,
308                         safcm.LogDebug3,
309                         safcm.MsgSyncReq{},
310                         nil,
311                         fmt.Errorf("groups.yaml: cycle while expanding group \"group-b\""),
312                 },
313
314                 {
315                         "group_order",
316                         "project-group_order",
317                         "host1.example.org",
318                         nil,
319                         safcm.LogDebug3,
320                         safcm.MsgSyncReq{
321                                 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
322                                 Files: map[string]*safcm.File{
323                                         "/": {
324                                                 Path:      "/",
325                                                 Mode:      fs.ModeDir | 0755,
326                                                 Uid:       -1,
327                                                 Gid:       -1,
328                                                 OrigGroup: "host1.example.org",
329                                         },
330                                         "/etc": {
331                                                 Path:      "/etc",
332                                                 Mode:      fs.ModeDir | 0755,
333                                                 Uid:       -1,
334                                                 Gid:       -1,
335                                                 OrigGroup: "host1.example.org",
336                                         },
337                                         "/etc/dir-to-file": {
338                                                 Path:      "/etc/dir-to-file",
339                                                 Mode:      0644,
340                                                 Uid:       -1,
341                                                 Gid:       -1,
342                                                 Data:      []byte("dir-to-file: from group-a\n"),
343                                                 OrigGroup: "group-a",
344                                         },
345                                         "/etc/dir-to-filex": {
346                                                 OrigGroup: "group-b",
347                                                 Path:      "/etc/dir-to-filex",
348                                                 Mode:      0644,
349                                                 Uid:       -1,
350                                                 Gid:       -1,
351                                                 Data:      []byte("dir-to-filex\n"),
352                                         },
353                                         "/etc/dir-to-link": {
354                                                 Path:      "/etc/dir-to-link",
355                                                 Mode:      fs.ModeSymlink | 0777,
356                                                 Uid:       -1,
357                                                 Gid:       -1,
358                                                 Data:      []byte("target"),
359                                                 OrigGroup: "group-a",
360                                         },
361                                         "/etc/dir-to-linkx": {
362                                                 OrigGroup: "group-b",
363                                                 Path:      "/etc/dir-to-linkx",
364                                                 Mode:      0644,
365                                                 Uid:       -1,
366                                                 Gid:       -1,
367                                                 Data:      []byte("dir-to-linkx\n"),
368                                         },
369                                         "/etc/file-to-dir": {
370                                                 Path:      "/etc/file-to-dir",
371                                                 Mode:      fs.ModeDir | 0755,
372                                                 Uid:       -1,
373                                                 Gid:       -1,
374                                                 OrigGroup: "group-a",
375                                         },
376                                         "/etc/file-to-dir/file": {
377                                                 Path:      "/etc/file-to-dir/file",
378                                                 Mode:      0644,
379                                                 Uid:       -1,
380                                                 Gid:       -1,
381                                                 Data:      []byte("file: from group-a\n"),
382                                                 OrigGroup: "group-a",
383                                         },
384                                         "/etc/file-to-dir/dir": {
385                                                 Path:      "/etc/file-to-dir/dir",
386                                                 Mode:      fs.ModeDir | 0755,
387                                                 Uid:       -1,
388                                                 Gid:       -1,
389                                                 OrigGroup: "group-a",
390                                         },
391                                         "/etc/file-to-dir/dir/file2": {
392                                                 Path:      "/etc/file-to-dir/dir/file2",
393                                                 Mode:      0644,
394                                                 Uid:       -1,
395                                                 Gid:       -1,
396                                                 Data:      []byte("file2: from group-a\n"),
397                                                 OrigGroup: "group-a",
398                                         },
399                                         "/etc/motd": {
400                                                 Path:      "/etc/motd",
401                                                 Mode:      0644,
402                                                 Uid:       -1,
403                                                 Gid:       -1,
404                                                 Data:      []byte("motd: from host1\n"),
405                                                 OrigGroup: "host1.example.org",
406                                         },
407                                 },
408                         },
409                         []string{
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`,
414                         },
415                         nil,
416                 },
417         }
418
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))
423                         if err != nil {
424                                 t.Fatal(err)
425                         }
426
427                         // `safcm fixperms` in case user has strict umask
428                         log.SetOutput(io.Discard)
429                         err := MainFixperms()
430                         if err != nil {
431                                 t.Fatal(err)
432                         }
433                         log.SetOutput(os.Stderr)
434
435                         cfg, allHosts, allGroups, err := LoadBaseFiles()
436                         if err != nil {
437                                 t.Fatal(err)
438                         }
439                         cfg.LogLevel = tc.level
440
441                         var events []string
442                         ch := make(chan Event)
443                         done := make(chan struct{})
444                         go func() {
445                                 for {
446                                         x, ok := <-ch
447                                         if !ok {
448                                                 break
449                                         }
450                                         if x.ConnEvent.Type != 0 {
451                                                 panic("unexpected ConnEvent")
452                                         }
453                                         events = append(events,
454                                                 fmt.Sprintf("%s: %v %d %s",
455                                                         x.Host.Name,
456                                                         x.Error, x.Log.Level,
457                                                         x.Log.Text))
458                                 }
459                                 done <- struct{}{}
460                         }()
461
462                         s := &Sync{
463                                 host:      allHosts.Map[tc.host],
464                                 config:    cfg,
465                                 allHosts:  allHosts,
466                                 allGroups: allGroups,
467                                 events:    ch,
468                         }
469
470                         res, err := s.hostSyncReq(tc.detected)
471
472                         testutil.AssertEqual(t, "res", res, tc.exp)
473                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
474
475                         close(ch)
476                         <-done
477                         testutil.AssertEqual(t, "events",
478                                 events, tc.expEvents)
479                 })
480         }
481 }