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