]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - cmd/safcm/sync_sync_test.go
4b614d19b30168ba4bd20bb43c75b2b540379316
[safcm/safcm.git] / cmd / safcm / sync_sync_test.go
1 // Copyright (C) 2021-2024  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) //nolint:errcheck
37
38         tests := []struct {
39                 name      string
40                 project   string
41                 host      string
42                 detected  []string
43                 exp       safcm.MsgSyncReq
44                 expEvents []string
45                 expErr    error
46         }{
47
48                 // NOTE: Also update MsgSyncReq in safcm-remote test cases
49                 // changing the MsgSyncReq struct!
50
51                 {
52                         "project: host1",
53                         "project",
54                         "host1.example.org",
55                         nil,
56                         safcm.MsgSyncReq{
57                                 Groups: []string{
58                                         "all",
59                                         "group",
60                                         "group3",
61                                         "remove",
62                                         "host1.example.org",
63                                 },
64                                 Files: map[string]*safcm.File{
65                                         "/": {
66                                                 OrigGroup: "group",
67                                                 Path:      "/",
68                                                 Mode:      fs.ModeDir | 0755 | fs.ModeSetgid,
69                                                 Uid:       -1,
70                                                 Gid:       -1,
71                                                 TriggerCommands: []string{
72                                                         "touch /.update",
73                                                 },
74                                         },
75                                         "/etc": {
76                                                 OrigGroup: "group",
77                                                 Path:      "/etc",
78                                                 Mode:      fs.ModeDir | 0755,
79                                                 Uid:       -1,
80                                                 Gid:       -1,
81                                         },
82                                         "/etc/.hidden": {
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": {
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\n\nall\n\n\nhost1.example.org\n\n\n\n"),
97                                         },
98                                         "/etc/rc.local": {
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": {
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": {
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: []*safcm.Command{
139                                         {
140                                                 OrigGroup: "group",
141                                                 Cmd:       "echo command one",
142                                         },
143                                         {
144                                                 OrigGroup: "group",
145                                                 Cmd:       "echo -n command two",
146                                         },
147                                 },
148                         },
149                         []string{
150                                 "3 false host groups: all group group3 host1.example.org remove",
151                                 "3 false host group priorities (descending): host1.example.org",
152                         },
153                         nil,
154                 },
155
156                 {
157                         "conflict: file",
158                         "project-conflict-file",
159                         "host1.example.org",
160                         nil,
161                         safcm.MsgSyncReq{},
162                         []string{
163                                 "3 false host groups: all dns host1.example.org",
164                                 "3 false host group priorities (descending): host1.example.org",
165                         },
166                         fmt.Errorf("groups dns and all both provide \"/etc/resolv.conf\"\nUse 'group_priority' in config.yaml to declare preference"),
167                 },
168                 {
169                         "conflict: file from detected group",
170                         "project-conflict-file",
171                         "host2.example.org",
172                         []string{
173                                 "detected_other",
174                         },
175                         safcm.MsgSyncReq{},
176                         []string{
177                                 "3 false host groups: all detected_other host2.example.org other",
178                                 "3 false host group priorities (descending): host2.example.org",
179                         },
180                         fmt.Errorf("groups other and all both provide \"/etc/resolv.conf\"\nUse 'group_priority' in config.yaml to declare preference"),
181                 },
182
183                 {
184                         "conflict: dir",
185                         "project-conflict-dir",
186                         "host1.example.org",
187                         nil,
188                         safcm.MsgSyncReq{},
189                         []string{
190                                 "3 false host groups: all dns host1.example.org",
191                                 "3 false host group priorities (descending): host1.example.org",
192                         },
193                         fmt.Errorf("groups dns and all both provide \"/etc\"\nUse 'group_priority' in config.yaml to declare preference"),
194                 },
195                 {
196                         "conflict: dir from detected group",
197                         "project-conflict-dir",
198                         "host2.example.org",
199                         []string{
200                                 "detected_other",
201                         },
202                         safcm.MsgSyncReq{},
203                         []string{
204                                 "3 false host groups: all detected_other host2.example.org other",
205                                 "3 false host group priorities (descending): host2.example.org",
206                         },
207                         fmt.Errorf("groups other and all both provide \"/etc\"\nUse 'group_priority' in config.yaml to declare preference"),
208                 },
209
210                 {
211                         "group: cycle",
212                         "project-group-cycle",
213                         "host1.example.org",
214                         nil,
215                         safcm.MsgSyncReq{},
216                         nil,
217                         fmt.Errorf("groups.yaml: cycle while expanding group \"group-b\""),
218                 },
219
220                 {
221                         "group_priority",
222                         "project-group_priority",
223                         "host1.example.org",
224                         nil,
225                         safcm.MsgSyncReq{
226                                 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
227                                 Files: map[string]*safcm.File{
228                                         "/": {
229                                                 OrigGroup: "host1.example.org",
230                                                 Path:      "/",
231                                                 Mode:      fs.ModeDir | 0755,
232                                                 Uid:       -1,
233                                                 Gid:       -1,
234                                         },
235                                         "/etc": {
236                                                 OrigGroup: "host1.example.org",
237                                                 Path:      "/etc",
238                                                 Mode:      fs.ModeDir | 0755,
239                                                 Uid:       -1,
240                                                 Gid:       -1,
241                                         },
242                                         "/etc/dir-to-file": {
243                                                 OrigGroup: "group-a",
244                                                 Path:      "/etc/dir-to-file",
245                                                 Mode:      0644,
246                                                 Uid:       -1,
247                                                 Gid:       -1,
248                                                 Data:      []byte("dir-to-file: from group-a\n"),
249                                         },
250                                         "/etc/dir-to-filex": {
251                                                 OrigGroup: "group-b",
252                                                 Path:      "/etc/dir-to-filex",
253                                                 Mode:      0644,
254                                                 Uid:       -1,
255                                                 Gid:       -1,
256                                                 Data:      []byte("dir-to-filex\n"),
257                                         },
258                                         "/etc/dir-to-link": {
259                                                 OrigGroup: "group-a",
260                                                 Path:      "/etc/dir-to-link",
261                                                 Mode:      fs.ModeSymlink | 0777,
262                                                 Uid:       -1,
263                                                 Gid:       -1,
264                                                 Data:      []byte("target"),
265                                         },
266                                         "/etc/dir-to-linkx": {
267                                                 OrigGroup: "group-b",
268                                                 Path:      "/etc/dir-to-linkx",
269                                                 Mode:      0644,
270                                                 Uid:       -1,
271                                                 Gid:       -1,
272                                                 Data:      []byte("dir-to-linkx\n"),
273                                         },
274                                         "/etc/file-to-dir": {
275                                                 OrigGroup: "group-a",
276                                                 Path:      "/etc/file-to-dir",
277                                                 Mode:      fs.ModeDir | 0755,
278                                                 Uid:       -1,
279                                                 Gid:       -1,
280                                         },
281                                         "/etc/file-to-dir/file": {
282                                                 OrigGroup: "group-a",
283                                                 Path:      "/etc/file-to-dir/file",
284                                                 Mode:      0644,
285                                                 Uid:       -1,
286                                                 Gid:       -1,
287                                                 Data:      []byte("file: from group-a\n"),
288                                         },
289                                         "/etc/file-to-dir/dir": {
290                                                 OrigGroup: "group-a",
291                                                 Path:      "/etc/file-to-dir/dir",
292                                                 Mode:      fs.ModeDir | 0755,
293                                                 Uid:       -1,
294                                                 Gid:       -1,
295                                         },
296                                         "/etc/file-to-dir/dir/file2": {
297                                                 OrigGroup: "group-a",
298                                                 Path:      "/etc/file-to-dir/dir/file2",
299                                                 Mode:      0644,
300                                                 Uid:       -1,
301                                                 Gid:       -1,
302                                                 Data:      []byte("file2: from group-a\n"),
303                                         },
304                                         "/etc/motd": {
305                                                 OrigGroup: "host1.example.org",
306                                                 Path:      "/etc/motd",
307                                                 Mode:      0644,
308                                                 Uid:       -1,
309                                                 Gid:       -1,
310                                                 Data:      []byte("motd: from host1\n"),
311                                         },
312                                 },
313                         },
314                         []string{
315                                 "3 false host groups: all group-a group-b host1.example.org",
316                                 "3 false host group priorities (descending): host1.example.org group-a group-b all",
317                                 `4 false files: "/etc": group group-a overwrites triggers from group group-b`,
318                                 `4 false files: "/etc": group host1.example.org overwrites triggers from group group-a`,
319                         },
320                         nil,
321                 },
322
323                 {
324                         "group_priority (single group)",
325                         "project-group_priority-single",
326                         "host1.example.org",
327                         nil,
328                         safcm.MsgSyncReq{
329                                 Groups: []string{"all", "group-b", "group-a", "host1.example.org"},
330                                 Files: map[string]*safcm.File{
331                                         "/": {
332                                                 OrigGroup: "group-a",
333                                                 Path:      "/",
334                                                 Mode:      fs.ModeDir | 0755,
335                                                 Uid:       -1,
336                                                 Gid:       -1,
337                                         },
338                                         "/file.txt": {
339                                                 OrigGroup: "group-a",
340                                                 Path:      "/file.txt",
341                                                 Mode:      0644,
342                                                 Uid:       -1,
343                                                 Gid:       -1,
344                                                 Data:      []byte("file.txt: from group-a\n"),
345                                         },
346                                 },
347                         },
348                         []string{
349                                 "3 false host groups: all group-a group-b host1.example.org",
350                                 "3 false host group priorities (descending): host1.example.org group-a",
351                         },
352                         nil,
353                 },
354         }
355
356         for _, tc := range tests {
357                 t.Run(tc.name, func(t *testing.T) {
358                         err = os.Chdir(filepath.Join(cwd,
359                                 "testdata", tc.project))
360                         if err != nil {
361                                 t.Fatal(err)
362                         }
363
364                         // `safcm fixperms` in case user has strict umask
365                         log.SetOutput(io.Discard)
366                         err := MainFixperms()
367                         if err != nil {
368                                 t.Fatal(err)
369                         }
370                         log.SetOutput(os.Stderr)
371
372                         cfg, allHosts, allGroups, err := LoadBaseFiles()
373                         if err != nil {
374                                 t.Fatal(err)
375                         }
376
377                         var events []string
378                         s := &Sync{
379                                 host:      allHosts.Map[tc.host],
380                                 config:    cfg,
381                                 allHosts:  allHosts,
382                                 allGroups: allGroups,
383                                 logFunc: func(level safcm.LogLevel, escaped bool, msg string) {
384                                         events = append(events,
385                                                 fmt.Sprintf("%d %v %s", level, escaped, msg))
386                                 },
387                         }
388
389                         res, err := s.hostSyncReq(tc.detected)
390                         testutil.AssertEqual(t, "res", res, tc.exp)
391                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
392                         testutil.AssertEqual(t, "events",
393                                 events, tc.expEvents)
394                 })
395         }
396 }