]> ruderich.org/simon Gitweb - safcm/safcm.git/blob - remote/sync/commands_test.go
remote: support creating files with missing parents in dry-run
[safcm/safcm.git] / remote / sync / commands_test.go
1 // Copyright (C) 2021-2023  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 sync
17
18 import (
19         "fmt"
20         "io/fs"
21         "os"
22         "os/exec"
23         "testing"
24
25         "ruderich.org/simon/safcm"
26         "ruderich.org/simon/safcm/testutil"
27 )
28
29 func TestSyncCommands(t *testing.T) {
30         exe, err := os.Executable()
31         if err != nil {
32                 t.Fatal(err)
33         }
34         env := append(os.Environ(),
35                 "SAFCM_HELPER="+exe,
36                 "SAFCM_GROUPS=all group1 group2 host.example.org",
37                 "SAFCM_GROUP_all=all",
38                 "SAFCM_GROUP_group1=group1",
39                 "SAFCM_GROUP_group2=group2",
40                 "SAFCM_GROUP_host.example.org=host.example.org",
41         )
42
43         tests := []struct {
44                 name     string
45                 req      safcm.MsgSyncReq
46                 triggers []string
47                 stdout   [][]byte
48                 stderr   [][]byte
49                 errors   []error
50                 expCmds  []*exec.Cmd
51                 expDbg   []string
52                 expResp  safcm.MsgSyncResp
53                 expErr   error
54         }{
55
56                 // NOTE: Also update MsgSyncResp in safcm test cases when
57                 // changing the MsgSyncResp struct!
58
59                 {
60                         "successful command",
61                         safcm.MsgSyncReq{
62                                 Groups: []string{
63                                         "all",
64                                         "group1",
65                                         "group2",
66                                         "host.example.org",
67                                 },
68                                 Commands: []*safcm.Command{
69                                         {
70                                                 OrigGroup: "group",
71                                                 Cmd:       "echo; env | grep SAFCM_",
72                                         },
73                                 },
74                         },
75                         nil,
76                         [][]byte{[]byte("fake stdout/stderr")},
77                         [][]byte{nil},
78                         []error{nil},
79                         []*exec.Cmd{{
80                                 Path: "/bin/sh",
81                                 Args: []string{
82                                         "/bin/sh", "-c",
83                                         "echo; env | grep SAFCM_",
84                                 },
85                                 Env: env,
86                         }},
87                         []string{
88                                 `3: commands: running "/bin/sh" "-c" "echo; env | grep SAFCM_" (group)`,
89                                 "5: commands: command output:\nfake stdout/stderr",
90                         },
91                         safcm.MsgSyncResp{
92                                 CommandChanges: []safcm.CommandChange{
93                                         {
94                                                 Command: "echo; env | grep SAFCM_",
95                                                 Output:  "fake stdout/stderr",
96                                         },
97                                 },
98                         },
99                         nil,
100                 },
101                 {
102                         "successful command (dry-run)",
103                         safcm.MsgSyncReq{
104                                 DryRun: true,
105                                 Groups: []string{
106                                         "all",
107                                         "group1",
108                                         "group2",
109                                         "host.example.org",
110                                 },
111                                 Commands: []*safcm.Command{
112                                         {
113                                                 OrigGroup: "group",
114                                                 Cmd:       "echo; env | grep SAFCM_",
115                                         },
116                                 },
117                         },
118                         nil,
119                         nil,
120                         nil,
121                         nil,
122                         nil,
123                         nil,
124                         safcm.MsgSyncResp{
125                                 CommandChanges: []safcm.CommandChange{
126                                         {
127                                                 Command: "echo; env | grep SAFCM_",
128                                         },
129                                 },
130                         },
131                         nil,
132                 },
133
134                 {
135                         "failed command",
136                         safcm.MsgSyncReq{
137                                 Groups: []string{
138                                         "all",
139                                         "group1",
140                                         "group2",
141                                         "host.example.org",
142                                 },
143                                 Commands: []*safcm.Command{
144                                         {
145                                                 OrigGroup: "group",
146                                                 Cmd:       "echo hi; false",
147                                         },
148                                 },
149                         },
150                         nil,
151                         [][]byte{[]byte("fake stdout/stderr")},
152                         [][]byte{nil},
153                         []error{fmt.Errorf("fake error")},
154                         []*exec.Cmd{{
155                                 Path: "/bin/sh",
156                                 Args: []string{
157                                         "/bin/sh", "-c",
158                                         "echo hi; false",
159                                 },
160                                 Env: env,
161                         }},
162                         []string{
163                                 `3: commands: running "/bin/sh" "-c" "echo hi; false" (group)`,
164                                 "5: commands: command output:\nfake stdout/stderr",
165                         },
166                         safcm.MsgSyncResp{
167                                 CommandChanges: []safcm.CommandChange{
168                                         {
169                                                 Command: "echo hi; false",
170                                                 Output:  "fake stdout/stderr",
171                                                 Error:   "fake error",
172                                         },
173                                 },
174                         },
175                         fmt.Errorf("\"echo hi; false\" failed: fake error"),
176                 },
177                 {
178                         "failed command (dry-run)",
179                         safcm.MsgSyncReq{
180                                 DryRun: true,
181                                 Groups: []string{
182                                         "all",
183                                         "group1",
184                                         "group2",
185                                         "host.example.org",
186                                 },
187                                 Commands: []*safcm.Command{
188                                         {
189                                                 OrigGroup: "group",
190                                                 Cmd:       "echo hi; false",
191                                         },
192                                 },
193                         },
194                         nil,
195                         nil,
196                         nil,
197                         nil,
198                         nil,
199                         nil,
200                         safcm.MsgSyncResp{
201                                 CommandChanges: []safcm.CommandChange{
202                                         {
203                                                 Command: "echo hi; false",
204                                         },
205                                 },
206                         },
207                         nil,
208                 },
209
210                 {
211                         "multiple commands, abort on first failed",
212                         safcm.MsgSyncReq{
213                                 Groups: []string{
214                                         "all",
215                                         "group1",
216                                         "group2",
217                                         "host.example.org",
218                                 },
219                                 Commands: []*safcm.Command{
220                                         {
221                                                 OrigGroup: "group1",
222                                                 Cmd:       "echo first",
223                                         }, {
224                                                 OrigGroup: "group2",
225                                                 Cmd:       "echo second",
226                                         }, {
227                                                 OrigGroup: "group3",
228                                                 Cmd:       "false",
229                                         }, {
230                                                 OrigGroup: "group4",
231                                                 Cmd:       "echo third",
232                                         },
233                                 },
234                         },
235                         nil,
236                         [][]byte{
237                                 []byte("fake stdout/stderr first"),
238                                 []byte("fake stdout/stderr second"),
239                                 nil,
240                         },
241                         [][]byte{
242                                 nil,
243                                 nil,
244                                 nil,
245                         },
246                         []error{
247                                 nil,
248                                 nil,
249                                 fmt.Errorf("fake error"),
250                         },
251                         []*exec.Cmd{{
252                                 Path: "/bin/sh",
253                                 Args: []string{
254                                         "/bin/sh", "-c",
255                                         "echo first",
256                                 },
257                                 Env: env,
258                         }, {
259                                 Path: "/bin/sh",
260                                 Args: []string{
261                                         "/bin/sh", "-c",
262                                         "echo second",
263                                 },
264                                 Env: env,
265                         }, {
266                                 Path: "/bin/sh",
267                                 Args: []string{
268                                         "/bin/sh", "-c",
269                                         "false",
270                                 },
271                                 Env: env,
272                         }},
273                         []string{
274                                 `3: commands: running "/bin/sh" "-c" "echo first" (group1)`,
275                                 "5: commands: command output:\nfake stdout/stderr first",
276                                 `3: commands: running "/bin/sh" "-c" "echo second" (group2)`,
277                                 "5: commands: command output:\nfake stdout/stderr second",
278                                 `3: commands: running "/bin/sh" "-c" "false" (group3)`,
279                         },
280                         safcm.MsgSyncResp{
281                                 CommandChanges: []safcm.CommandChange{
282                                         {
283                                                 Command: "echo first",
284                                                 Output:  "fake stdout/stderr first",
285                                         },
286                                         {
287                                                 Command: "echo second",
288                                                 Output:  "fake stdout/stderr second",
289                                         },
290                                         {
291                                                 Command: "false",
292                                                 Output:  "",
293                                                 Error:   "fake error",
294                                         },
295                                 },
296                         },
297                         fmt.Errorf("\"false\" failed: fake error"),
298                 },
299
300                 {
301                         "triggers",
302                         safcm.MsgSyncReq{
303                                 Groups: []string{
304                                         "all",
305                                         "group1",
306                                         "group2",
307                                         "host.example.org",
308                                 },
309                                 Files: map[string]*safcm.File{
310                                         ".": {
311                                                 OrigGroup: "group",
312                                                 Path:      ".",
313                                                 Mode:      fs.ModeDir | 0700,
314                                                 Uid:       -1,
315                                                 Gid:       -1,
316                                                 TriggerCommands: []string{
317                                                         "echo trigger .",
318                                                 },
319                                         },
320                                         "dir": {
321                                                 OrigGroup: "group",
322                                                 Path:      "dir",
323                                                 Mode:      fs.ModeDir | 0755,
324                                                 Uid:       -1,
325                                                 Gid:       -1,
326                                                 TriggerCommands: []string{
327                                                         "echo trigger dir",
328                                                 },
329                                         },
330                                         "dir/file": {
331                                                 OrigGroup: "group",
332                                                 Path:      "dir/file",
333                                                 Mode:      0644,
334                                                 Uid:       -1,
335                                                 Gid:       -1,
336                                                 Data:      []byte("content\n"),
337                                                 TriggerCommands: []string{
338                                                         "echo trigger dir/file",
339                                                 },
340                                         },
341                                 },
342                                 Commands: []*safcm.Command{
343                                         {
344                                                 OrigGroup: "group",
345                                                 Cmd:       "echo; env | grep SAFCM_",
346                                         },
347                                 },
348                         },
349                         []string{
350                                 ".",
351                                 "dir",
352                         },
353                         [][]byte{
354                                 []byte("fake stdout/stderr ."),
355                                 []byte("fake stdout/stderr dir"),
356                                 []byte("fake stdout/stderr"),
357                         },
358                         [][]byte{
359                                 nil,
360                                 nil,
361                                 nil,
362                         },
363                         []error{
364                                 nil,
365                                 nil,
366                                 nil,
367                         },
368                         []*exec.Cmd{{
369                                 Path: "/bin/sh",
370                                 Args: []string{
371                                         "/bin/sh", "-c",
372                                         "echo trigger .",
373                                 },
374                                 Env: env,
375                         }, {
376                                 Path: "/bin/sh",
377                                 Args: []string{
378                                         "/bin/sh", "-c",
379                                         "echo trigger dir",
380                                 },
381                                 Env: env,
382                         }, {
383                                 Path: "/bin/sh",
384                                 Args: []string{
385                                         "/bin/sh", "-c",
386                                         "echo; env | grep SAFCM_",
387                                 },
388                                 Env: env,
389                         }},
390                         []string{
391                                 `3: commands: running "/bin/sh" "-c" "echo trigger ." (".")`,
392                                 "5: commands: command output:\nfake stdout/stderr .",
393                                 `3: commands: running "/bin/sh" "-c" "echo trigger dir" ("dir")`,
394                                 "5: commands: command output:\nfake stdout/stderr dir",
395                                 `3: commands: running "/bin/sh" "-c" "echo; env | grep SAFCM_" (group)`,
396                                 "5: commands: command output:\nfake stdout/stderr",
397                         },
398                         safcm.MsgSyncResp{
399                                 CommandChanges: []safcm.CommandChange{
400                                         {
401                                                 Command: "echo trigger .",
402                                                 Trigger: ".",
403                                                 Output:  "fake stdout/stderr .",
404                                         },
405                                         {
406                                                 Command: "echo trigger dir",
407                                                 Trigger: "dir",
408                                                 Output:  "fake stdout/stderr dir",
409                                         },
410                                         {
411                                                 Command: "echo; env | grep SAFCM_",
412                                                 Output:  "fake stdout/stderr",
413                                         },
414                                 },
415                         },
416                         nil,
417                 },
418
419                 {
420                         "failed trigger",
421                         safcm.MsgSyncReq{
422                                 Groups: []string{
423                                         "all",
424                                         "group1",
425                                         "group2",
426                                         "host.example.org",
427                                 },
428                                 Files: map[string]*safcm.File{
429                                         ".": {
430                                                 OrigGroup: "group",
431                                                 Path:      ".",
432                                                 Mode:      fs.ModeDir | 0700,
433                                                 Uid:       -1,
434                                                 Gid:       -1,
435                                                 TriggerCommands: []string{
436                                                         "echo trigger .",
437                                                 },
438                                         },
439                                         "dir": {
440                                                 OrigGroup: "group",
441                                                 Path:      "dir",
442                                                 Mode:      fs.ModeDir | 0755,
443                                                 Uid:       -1,
444                                                 Gid:       -1,
445                                                 TriggerCommands: []string{
446                                                         "false",
447                                                 },
448                                         },
449                                         "dir/file": {
450                                                 OrigGroup: "group",
451                                                 Path:      "dir/file",
452                                                 Mode:      0644,
453                                                 Uid:       -1,
454                                                 Gid:       -1,
455                                                 Data:      []byte("content\n"),
456                                                 TriggerCommands: []string{
457                                                         "echo trigger dir/file",
458                                                 },
459                                         },
460                                 },
461                                 Commands: []*safcm.Command{
462                                         {
463                                                 OrigGroup: "group",
464                                                 Cmd:       "echo; env | grep SAFCM_",
465                                         },
466                                 },
467                         },
468                         []string{
469                                 ".",
470                                 "dir",
471                         },
472                         [][]byte{
473                                 []byte("fake stdout/stderr ."),
474                                 []byte("fake stdout/stderr dir"),
475                         },
476                         [][]byte{
477                                 nil,
478                                 nil,
479                         },
480                         []error{
481                                 nil,
482                                 fmt.Errorf("fake error"),
483                         },
484                         []*exec.Cmd{{
485                                 Path: "/bin/sh",
486                                 Args: []string{
487                                         "/bin/sh", "-c",
488                                         "echo trigger .",
489                                 },
490                                 Env: env,
491                         }, {
492                                 Path: "/bin/sh",
493                                 Args: []string{
494                                         "/bin/sh", "-c",
495                                         "false",
496                                 },
497                                 Env: env,
498                         }},
499                         []string{
500                                 `3: commands: running "/bin/sh" "-c" "echo trigger ." (".")`,
501                                 "5: commands: command output:\nfake stdout/stderr .",
502                                 `3: commands: running "/bin/sh" "-c" "false" ("dir")`,
503                                 "5: commands: command output:\nfake stdout/stderr dir",
504                         },
505                         safcm.MsgSyncResp{
506                                 CommandChanges: []safcm.CommandChange{
507                                         {
508                                                 Command: "echo trigger .",
509                                                 Trigger: ".",
510                                                 Output:  "fake stdout/stderr .",
511                                         },
512                                         {
513                                                 Command: "false",
514                                                 Trigger: "dir",
515                                                 Output:  "fake stdout/stderr dir",
516                                                 Error:   "fake error",
517                                         },
518                                 },
519                         },
520                         fmt.Errorf("\"false\" failed: fake error"),
521                 },
522         }
523
524         for _, tc := range tests {
525                 t.Run(tc.name, func(t *testing.T) {
526                         s, res := prepareSync(tc.req, &testRunner{
527                                 t:         t,
528                                 expCmds:   tc.expCmds,
529                                 resStdout: tc.stdout,
530                                 resStderr: tc.stderr,
531                                 resError:  tc.errors,
532                         })
533                         s.triggers = tc.triggers
534
535                         err := s.syncCommands()
536                         testutil.AssertErrorEqual(t, "err", err, tc.expErr)
537                         dbg := res.Wait()
538
539                         testutil.AssertEqual(t, "resp", s.resp, tc.expResp)
540                         testutil.AssertEqual(t, "dbg", dbg, tc.expDbg)
541                 })
542         }
543 }