engine: resources: Add a DoneCmd feature for exec resource
This makes a common pattern of `$cmd && echo done > /tmp/donefile` much clearer and easier.
This commit is contained in:
@@ -106,6 +106,18 @@ type ExecRes struct {
|
|||||||
// IfShell is the Shell for the IfCmd. See the docs for Shell.
|
// IfShell is the Shell for the IfCmd. See the docs for Shell.
|
||||||
IfShell string `lang:"ifshell" yaml:"ifshell"`
|
IfShell string `lang:"ifshell" yaml:"ifshell"`
|
||||||
|
|
||||||
|
// DoneCmd is the command that runs after the regular Cmd runs
|
||||||
|
// successfully. This is a useful pattern to avoid the shelling out to
|
||||||
|
// bash simply to do `$cmd && echo done > /tmp/donefile`. If this
|
||||||
|
// command errors, it behaves as if the normal Cmd had errored.
|
||||||
|
DoneCmd string `lang:"donecmd" yaml:"donecmd"`
|
||||||
|
|
||||||
|
// DoneCwd is the Cwd for the DoneCmd. See the docs for Cwd.
|
||||||
|
DoneCwd string `lang:"donecwd" yaml:"donecwd"`
|
||||||
|
|
||||||
|
// DoneShell is the Shell for the DoneCmd. See the docs for Shell.
|
||||||
|
DoneShell string `lang:"doneshell" yaml:"doneshell"`
|
||||||
|
|
||||||
// User is the (optional) user to use to execute the command. It is used
|
// User is the (optional) user to use to execute the command. It is used
|
||||||
// for any command being run.
|
// for any command being run.
|
||||||
User string `lang:"user" yaml:"user"`
|
User string `lang:"user" yaml:"user"`
|
||||||
@@ -506,6 +518,72 @@ func (obj *ExecRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
|
|||||||
obj.init.Logf("%s", s)
|
obj.init.Logf("%s", s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if obj.DoneCmd != "" {
|
||||||
|
var cmdName string
|
||||||
|
var cmdArgs []string
|
||||||
|
if obj.DoneShell == "" {
|
||||||
|
// call without a shell
|
||||||
|
// FIXME: are there still whitespace splitting issues?
|
||||||
|
split := strings.Fields(obj.DoneCmd)
|
||||||
|
cmdName = split[0]
|
||||||
|
//d, _ := os.Getwd() // TODO: how does this ever error ?
|
||||||
|
//cmdName = path.Join(d, cmdName)
|
||||||
|
cmdArgs = split[1:]
|
||||||
|
} else {
|
||||||
|
cmdName = obj.DoneShell // usually bash, or sh
|
||||||
|
cmdArgs = []string{"-c", obj.DoneCmd}
|
||||||
|
}
|
||||||
|
cmd := exec.Command(cmdName, cmdArgs...)
|
||||||
|
cmd.Dir = obj.DoneCwd // run program in pwd if ""
|
||||||
|
// ignore signals sent to parent process (we're in our own group)
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Setpgid: true,
|
||||||
|
Pgid: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have an user and group, use them
|
||||||
|
var err error
|
||||||
|
if cmd.SysProcAttr.Credential, err = obj.getCredential(); err != nil {
|
||||||
|
return false, errwrap.Wrapf(err, "error while setting credential")
|
||||||
|
}
|
||||||
|
|
||||||
|
var out splitWriter
|
||||||
|
out.Init()
|
||||||
|
cmd.Stdout = out.Stdout
|
||||||
|
cmd.Stderr = out.Stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
exitErr, ok := err.(*exec.ExitError) // embeds an os.ProcessState
|
||||||
|
if !ok {
|
||||||
|
// command failed in some bad way
|
||||||
|
return false, errwrap.Wrapf(err, "donecmd failed in some bad way")
|
||||||
|
}
|
||||||
|
pStateSys := exitErr.Sys() // (*os.ProcessState) Sys
|
||||||
|
wStatus, ok := pStateSys.(syscall.WaitStatus)
|
||||||
|
if !ok {
|
||||||
|
return false, errwrap.Wrapf(err, "could not get exit status of donecmd")
|
||||||
|
}
|
||||||
|
exitStatus := wStatus.ExitStatus()
|
||||||
|
if exitStatus == 0 {
|
||||||
|
// i'm not sure if this could happen
|
||||||
|
return false, errwrap.Wrapf(err, "unexpected donecmd exit status of zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
if s := out.String(); s == "" {
|
||||||
|
obj.init.Logf("donecmd exit status %d", exitStatus)
|
||||||
|
} else {
|
||||||
|
obj.init.Logf("donecmd error: %s", s)
|
||||||
|
}
|
||||||
|
return false, errwrap.Wrapf(err, "cmd error") // exit status will be in the error
|
||||||
|
}
|
||||||
|
if s := out.String(); s == "" {
|
||||||
|
obj.init.Logf("donecmd out empty!")
|
||||||
|
} else {
|
||||||
|
obj.init.Logf("donecmd out:")
|
||||||
|
obj.init.Logf("%s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := obj.init.Send(&ExecSends{
|
if err := obj.init.Send(&ExecSends{
|
||||||
Output: obj.output,
|
Output: obj.output,
|
||||||
Stdout: obj.stdout,
|
Stdout: obj.stdout,
|
||||||
@@ -571,6 +649,16 @@ func (obj *ExecRes) Cmp(r engine.Res) error {
|
|||||||
return fmt.Errorf("the IfShell differs")
|
return fmt.Errorf("the IfShell differs")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if obj.DoneCmd != res.DoneCmd {
|
||||||
|
return fmt.Errorf("the DoneCmd differs")
|
||||||
|
}
|
||||||
|
if obj.DoneCwd != res.DoneCwd {
|
||||||
|
return fmt.Errorf("the DoneCwd differs")
|
||||||
|
}
|
||||||
|
if obj.DoneShell != res.DoneShell {
|
||||||
|
return fmt.Errorf("the DoneShell differs")
|
||||||
|
}
|
||||||
|
|
||||||
if obj.User != res.User {
|
if obj.User != res.User {
|
||||||
return fmt.Errorf("the User differs")
|
return fmt.Errorf("the User differs")
|
||||||
}
|
}
|
||||||
@@ -593,6 +681,7 @@ type ExecUID struct {
|
|||||||
Cmd string
|
Cmd string
|
||||||
WatchCmd string
|
WatchCmd string
|
||||||
IfCmd string
|
IfCmd string
|
||||||
|
DoneCmd string
|
||||||
// TODO: add more elements here
|
// TODO: add more elements here
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,6 +771,7 @@ func (obj *ExecRes) UIDs() []engine.ResUID {
|
|||||||
Cmd: obj.getCmd(),
|
Cmd: obj.getCmd(),
|
||||||
WatchCmd: obj.WatchCmd,
|
WatchCmd: obj.WatchCmd,
|
||||||
IfCmd: obj.IfCmd,
|
IfCmd: obj.IfCmd,
|
||||||
|
DoneCmd: obj.DoneCmd,
|
||||||
// TODO: add more params here
|
// TODO: add more params here
|
||||||
}
|
}
|
||||||
return []engine.ResUID{x}
|
return []engine.ResUID{x}
|
||||||
@@ -775,6 +865,10 @@ func (obj *ExecRes) cmdFiles() []string {
|
|||||||
} else if sp := strings.Fields(obj.IfCmd); len(sp) > 0 {
|
} else if sp := strings.Fields(obj.IfCmd); len(sp) > 0 {
|
||||||
paths = append(paths, sp[0])
|
paths = append(paths, sp[0])
|
||||||
}
|
}
|
||||||
|
if obj.DoneShell != "" {
|
||||||
|
paths = append(paths, obj.DoneShell)
|
||||||
|
} else if sp := strings.Fields(obj.DoneCmd); len(sp) > 0 {
|
||||||
|
paths = append(paths, sp[0])
|
||||||
}
|
}
|
||||||
return paths
|
return paths
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user