exec.go

v0.3.0
Doc Versions Source
1
package steps
2
3
import (
4
	"strings"
5
6
	"go.bigb.es/auxilia/culpa"
7
	"go.bigb.es/auxilia/legatus"
8
)
9
10
// Exec runs a command with idempotency guards. Check evaluates the guards
11
// (PC4); Apply runs the command. Guards (highest precedence first):
12
//   - Creates: skip when the path already exists.
13
//   - Unless:  skip when the shell command exits 0.
14
//   - OnlyIf:  skip when the shell command exits non-zero.
15
//
16
// With no guard, the command always runs.
17
type Exec struct {
18
	Cmd     string
19
	Args    []string
20
	Creates string
21
	Unless  string
22
	OnlyIf  string
23
	Sudo    bool
24
}
25
26
// Name returns the step name.
27
func (e Exec) Name() string { return "exec:" + e.Cmd }
28
29
// Check evaluates the guards and reports whether the command should run.
30
func (e Exec) Check(fc *legatus.FlowCtx) (bool, error) {
31
	if e.Creates != "" {
32
		res, err := fc.Conn().Exec(fc.Ctx(), legatus.Cmd{Path: "test", Args: []string{"-e", e.Creates}, Sudo: e.Sudo})
33
		if err != nil {
34
			return false, culpa.WithCode(culpa.Wrapf(err, "creates guard %s", e.Creates), "EXEC_GUARD")
35
		}
36
		if res.ExitCode == 0 {
37
			return false, nil // target exists ⇒ nothing to do.
38
		}
39
	}
40
41
	if e.Unless != "" {
42
		code, err := runGuard(fc.Ctx(), fc.Conn(), e.Unless, e.Sudo)
43
		if err != nil {
44
			return false, err
45
		}
46
		if code == 0 {
47
			return false, nil // unless succeeded ⇒ skip.
48
		}
49
	}
50
51
	if e.OnlyIf != "" {
52
		code, err := runGuard(fc.Ctx(), fc.Conn(), e.OnlyIf, e.Sudo)
53
		if err != nil {
54
			return false, err
55
		}
56
		if code != 0 {
57
			return false, nil // onlyif failed ⇒ skip.
58
		}
59
	}
60
61
	return true, nil
62
}
63
64
// Apply runs the command and reports it changed state.
65
func (e Exec) Apply(fc *legatus.FlowCtx) (legatus.Outcome, error) {
66
	res, err := fc.Conn().Exec(fc.Ctx(), legatus.Cmd{Path: e.Cmd, Args: e.Args, Sudo: e.Sudo})
67
	if err != nil {
68
		return legatus.Outcome{}, culpa.WithCode(culpa.Wrapf(err, "exec %s", e.Cmd), "EXEC_RUN")
69
	}
70
	if res.ExitCode != 0 {
71
		return legatus.Outcome{}, culpa.WithCode(
72
			culpa.Errorf("exec %s: exit %d: %s", e.Cmd, res.ExitCode, strings.TrimSpace(string(res.Combined()))),
73
			"EXEC_RUN")
74
	}
75
	return legatus.Outcome{Changed: true, Message: "ran " + e.Cmd, Data: res}, nil
76
}
77

Source Files