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