engine: Fix up some send/recv corner cases
Initially I wasn't 100% clear or decided on the send/recv semantics. After some experimenting, I think this is much closer to what we want. Nothing should break or regress here, this only enables more possibilities.
This commit is contained in:
@@ -38,6 +38,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/user"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -161,10 +162,28 @@ type ExecRes struct {
|
||||
// used for any command being run.
|
||||
Group string `lang:"group" yaml:"group"`
|
||||
|
||||
// SendOutput is a value which can be sent for the Send/Recv Output
|
||||
// field if no value is available in the cache. This is used in very
|
||||
// specialized scenarios (particularly prototyping and unclean
|
||||
// environments) and should not be used routinely. It should be used
|
||||
// only in situations where we didn't produce our own sending values,
|
||||
// and there are none in the cache, and instead are relying on a runtime
|
||||
// mechanism to help us out. This can commonly occur if you wish to make
|
||||
// incremental progress when locally testing some code using Send/Recv,
|
||||
// but you are combining it with --tmp-prefix for other reasons.
|
||||
SendOutput *string `lang:"send_output" yaml:"send_output"`
|
||||
|
||||
// SendStdout is like SendOutput but for stdout alone. See those docs.
|
||||
SendStdout *string `lang:"send_stdout" yaml:"send_stdout"`
|
||||
|
||||
// SendStderr is like SendOutput but for stderr alone. See those docs.
|
||||
SendStderr *string `lang:"send_stderr" yaml:"send_stderr"`
|
||||
|
||||
output *string // all cmd output, read only, do not set!
|
||||
stdout *string // the cmd stdout, read only, do not set!
|
||||
stderr *string // the cmd stderr, read only, do not set!
|
||||
|
||||
dir string // the path to local storage
|
||||
interruptChan chan struct{}
|
||||
wg *sync.WaitGroup
|
||||
}
|
||||
@@ -225,6 +244,12 @@ func (obj *ExecRes) Validate() error {
|
||||
func (obj *ExecRes) Init(init *engine.Init) error {
|
||||
obj.init = init // save for later
|
||||
|
||||
dir, err := obj.init.VarDir("")
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not get VarDir in Init()")
|
||||
}
|
||||
obj.dir = dir
|
||||
|
||||
obj.interruptChan = make(chan struct{})
|
||||
obj.wg = &sync.WaitGroup{}
|
||||
|
||||
@@ -364,6 +389,10 @@ func (obj *ExecRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
// check and this will run. It is still guarded by the IfCmd, but it can
|
||||
// have a chance to execute, and all without the check of obj.Refresh()!
|
||||
|
||||
if err := obj.checkApplyReadCache(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if obj.IfCmd != "" { // if there is no onlyif check, we should just run
|
||||
var cmdName string
|
||||
var cmdArgs []string
|
||||
@@ -423,6 +452,13 @@ func (obj *ExecRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
obj.init.Logf("ifcmd out:")
|
||||
obj.init.Logf("%s", s)
|
||||
}
|
||||
//if err := obj.checkApplyWriteCache(); err != nil {
|
||||
// return false, err
|
||||
//}
|
||||
obj.safety()
|
||||
if err := obj.send(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil // don't run
|
||||
}
|
||||
if s := out.String(); s == "" {
|
||||
@@ -436,12 +472,26 @@ func (obj *ExecRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
if obj.Creates != "" { // gate the extra syscall
|
||||
if _, err := os.Stat(obj.Creates); err == nil {
|
||||
obj.init.Logf("creates file exists, skipping cmd")
|
||||
//if err := obj.checkApplyWriteCache(); err != nil {
|
||||
// return false, err
|
||||
//}
|
||||
obj.safety()
|
||||
if err := obj.send(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil // don't run
|
||||
}
|
||||
}
|
||||
|
||||
// state is not okay, no work done, exit, but without error
|
||||
if !apply {
|
||||
//if err := obj.checkApplyWriteCache(); err != nil {
|
||||
// return false, err
|
||||
//}
|
||||
//obj.safety()
|
||||
if err := obj.send(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -654,11 +704,10 @@ func (obj *ExecRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := obj.init.Send(&ExecSends{
|
||||
Output: obj.output,
|
||||
Stdout: obj.stdout,
|
||||
Stderr: obj.stderr,
|
||||
}); err != nil {
|
||||
if err := obj.checkApplyWriteCache(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := obj.send(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -670,6 +719,77 @@ func (obj *ExecRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
||||
return false, nil // success
|
||||
}
|
||||
|
||||
// send is a helper to avoid duplication of the same send operation.
|
||||
func (obj *ExecRes) send() error {
|
||||
return obj.init.Send(&ExecSends{
|
||||
Output: obj.output,
|
||||
Stdout: obj.stdout,
|
||||
Stderr: obj.stderr,
|
||||
})
|
||||
}
|
||||
|
||||
// safety is a helper function that populates the cached "send" values if they
|
||||
// are empty. It must only be called right before actually sending any values,
|
||||
// and right before CheckApply returns. It should be used only in situations
|
||||
// where we didn't produce our own sending values, and there are none in the
|
||||
// cache, and instead are relying on a runtime mechanism to help us out. This
|
||||
// mechanism is useful as a backstop for when we're running in unclean
|
||||
// scenarios.
|
||||
func (obj *ExecRes) safety() {
|
||||
if x := obj.SendOutput; x != nil && obj.output == nil {
|
||||
s := *x // copy
|
||||
obj.output = &s
|
||||
}
|
||||
if x := obj.SendStdout; x != nil && obj.stdout == nil {
|
||||
s := *x // copy
|
||||
obj.stdout = &s
|
||||
}
|
||||
if x := obj.SendStderr; x != nil && obj.stderr == nil {
|
||||
s := *x // copy
|
||||
obj.stderr = &s
|
||||
}
|
||||
}
|
||||
|
||||
// checkApplyReadCache is a helper to do all our reading from the cache.
|
||||
func (obj *ExecRes) checkApplyReadCache() error {
|
||||
output, err := engineUtil.ReadData(path.Join(obj.dir, "output"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.output = output
|
||||
|
||||
stdout, err := engineUtil.ReadData(path.Join(obj.dir, "stdout"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.stdout = stdout
|
||||
|
||||
stderr, err := engineUtil.ReadData(path.Join(obj.dir, "stderr"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
obj.stderr = stderr
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkApplyWriteCache is a helper to do all our writing into the cache.
|
||||
func (obj *ExecRes) checkApplyWriteCache() error {
|
||||
if _, err := engineUtil.WriteData(path.Join(obj.dir, "output"), obj.output); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := engineUtil.WriteData(path.Join(obj.dir, "stdout"), obj.stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := engineUtil.WriteData(path.Join(obj.dir, "stderr"), obj.stderr); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Cmp compares two resources and returns an error if they are not equivalent.
|
||||
func (obj *ExecRes) Cmp(r engine.Res) error {
|
||||
// we can only compare ExecRes to others of the same resource kind
|
||||
@@ -740,6 +860,16 @@ func (obj *ExecRes) Cmp(r engine.Res) error {
|
||||
return fmt.Errorf("the Group differs")
|
||||
}
|
||||
|
||||
if err := engineUtil.StrPtrCmp(obj.SendOutput, res.SendOutput); err != nil {
|
||||
return errwrap.Wrapf(err, "the SendOutput differs")
|
||||
}
|
||||
if err := engineUtil.StrPtrCmp(obj.SendStdout, res.SendStdout); err != nil {
|
||||
return errwrap.Wrapf(err, "the SendStdout differs")
|
||||
}
|
||||
if err := engineUtil.StrPtrCmp(obj.SendStderr, res.SendStderr); err != nil {
|
||||
return errwrap.Wrapf(err, "the SendStderr differs")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user