This repository has been archived by the owner on Jan 11, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
command.go
134 lines (108 loc) · 2.65 KB
/
command.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package fifo
import (
"context"
"github.com/pkg/errors"
"golang.org/x/sync/errgroup"
"os/exec"
"syscall"
)
func NewCommand(t *Task) (*Command, error) {
return &Command{
t: t,
}, nil
}
type Command struct {
t *Task
}
func destroyWhenError(mu *MultiError, targets ...WriteDestroyCloser) {
if mu != nil && len(mu.err) > 0 {
for _, tg := range targets {
mu = Catch(mu, tg.Destroy())
}
}
}
// wait for a given command to finish and collect its exit code.
func wait(p *exec.Cmd) (int, error) {
err := p.Wait()
if err == nil {
return 0, nil
}
ex, ok := err.(*exec.ExitError)
if !ok {
return 1, err
}
return ex.ExitCode(), nil
}
func (c *Command) Start(ctx context.Context) (code int, mu *MultiError) {
mu = new(MultiError)
gen := &TemplateGenerator{
Provider: c.t,
SourceTags: c.t.Sources,
TargetTags: c.t.Targets,
}
args, err := gen.Replace(c.t.Call.Args)
if err != nil {
mu.Append(err)
return
}
defer mu.CatchMulti(gen.Sources.Teardown)
// Destroy any created targets on failure
defer func() {
if len(gen.Targets) > 0 && !c.t.Preserve {
for _, tg := range gen.Targets {
destroyWhenError(mu, tg.Stream)
}
}
}()
defer mu.CatchMulti(gen.Targets.Teardown)
stdin, err := c.t.SetupInput()
if err != nil {
mu.Append(errors.Wrap(err, "unable to setup input"))
return
}
defer mu.Catch(stdin.Close)
// Setup output for stdout and stderr
stdout, stderr, err := c.t.SetupOutput()
if err != nil {
mu.Append(errors.Wrap(err, "unable to setup output"))
return
}
// Destroy stdout and stderr on error
defer destroyWhenError(mu, stdout, stderr)
// Close stdout and stderr when done
defer mu.Catch(stdout.Close, stderr.Close)
p := exec.CommandContext(ctx, c.t.Call.Executable, args...)
p.Stdin = stdin
p.Stdout = stdout
p.Stderr = stderr
p.Env = c.t.Call.Environment
if c.t.Call.WorkingDirectory != "" {
p.Dir = c.t.Call.WorkingDirectory
}
g, ctx := errgroup.WithContext(ctx)
if len(gen.Targets) > 0 {
// the named pipe for receiving data from the command needs to be setup before the command starts
g.Go(func() error {
return gen.Targets.Copy(ctx)
})
}
if mu.Catch(p.Start) {
return
}
if len(gen.Sources) > 0 {
// named pipe for writing data to the command needs to be setup after the command starts
g.Go(func() error {
return gen.Sources.Copy(ctx)
})
}
// wait from the copy groups to complete
mu.Append(g.Wait())
// If we had errors processing IO then signal the process to prematurely SIGTERM
if len(mu.Errors()) > 0 {
mu.Append(p.Process.Signal(syscall.SIGTERM))
}
// wait for the command to complete and capture the error code
code, err = wait(p)
mu.Append(err)
return
}