engine: resources: Add a nif command and make it clearer

Let's have the opposite, and have an example so you don't forget which
is which.
This commit is contained in:
James Shubin
2025-10-02 20:48:36 -04:00
parent 5f4ae05340
commit 6c6c9df75e
2 changed files with 140 additions and 3 deletions

View File

@@ -127,9 +127,9 @@ type ExecRes struct {
WatchShell string `lang:"watchshell" yaml:"watchshell"` WatchShell string `lang:"watchshell" yaml:"watchshell"`
// IfCmd is the command that runs to guard against running the Cmd. If // IfCmd is the command that runs to guard against running the Cmd. If
// this command succeeds, then Cmd *will* be run. If this command // this command succeeds, then Cmd *will not* be blocked from running.
// returns a non-zero result, then the Cmd will not be run. Any error // If this command returns a non-zero result, then the Cmd will not be
// scenario or timeout will cause the resource to error. // run. Any error scenario or timeout will cause the resource to error.
IfCmd string `lang:"ifcmd" yaml:"ifcmd"` IfCmd string `lang:"ifcmd" yaml:"ifcmd"`
// IfCwd is the Cwd for the IfCmd. See the docs for Cwd. // IfCwd is the Cwd for the IfCmd. See the docs for Cwd.
@@ -145,6 +145,19 @@ type ExecRes struct {
// does!) // does!)
IfEquals *string `lang:"ifequals" yaml:"ifequals"` IfEquals *string `lang:"ifequals" yaml:"ifequals"`
// NIfCmd is the command that runs to guard against running the Cmd. If
// this command succeeds, then Cmd *will* be blocked from running. If
// this command returns a non-zero result, then the Cmd will be allowed
// to run if not blocked by anything else. This is the opposite of the
// IfCmd.
NIfCmd string `lang:"nifcmd" yaml:"nifcmd"`
// NIfCwd is the Cwd for the NIfCmd. See the docs for Cwd.
NIfCwd string `lang:"nifcwd" yaml:"nifcwd"`
// NIfShell is the Shell for the NIfCmd. See the docs for Shell.
NIfShell string `lang:"nifshell" yaml:"nifshell"`
// Creates is the absolute file path to check for before running the // Creates is the absolute file path to check for before running the
// main cmd. If this path exists, then the cmd will not run. More // main cmd. If this path exists, then the cmd will not run. More
// precisely we attempt to `stat` the file, so it must succeed for a // precisely we attempt to `stat` the file, so it must succeed for a
@@ -535,6 +548,93 @@ func (obj *ExecRes) CheckApply(ctx context.Context, apply bool) (bool, error) {
} }
} }
if obj.NIfCmd != "" { // opposite of the ifcmd check
var cmdName string
var cmdArgs []string
if obj.NIfShell == "" {
// call without a shell
// FIXME: are there still whitespace splitting issues?
split := strings.Fields(obj.NIfCmd)
cmdName = split[0]
//d, _ := os.Getwd() // TODO: how does this ever error ?
//cmdName = path.Join(d, cmdName)
cmdArgs = split[1:]
} else {
cmdName = obj.NIfShell // usually bash, or sh
cmdArgs = []string{"-c", obj.NIfCmd}
}
cmd := exec.Command(cmdName, cmdArgs...)
cmd.Dir = obj.NIfCwd // 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
err = cmd.Run()
if err == nil {
obj.init.Logf("nifcmd: %s", strings.Join(cmd.Args, " "))
obj.init.Logf("nifcmd exited with: %d, skipping cmd", 0)
s := out.String()
if s == "" {
obj.init.Logf("nifcmd out empty!")
} else {
obj.init.Logf("nifcmd 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
}
exitErr, ok := err.(*exec.ExitError) // embeds an os.ProcessState
if !ok {
// command failed in some bad way
return false, errwrap.Wrapf(err, "nifcmd 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 nifcmd")
}
exitStatus := wStatus.ExitStatus()
if exitStatus == 0 {
// i'm not sure if this could happen
return false, errwrap.Wrapf(err, "unexpected nifcmd exit status of zero")
}
obj.init.Logf("nifcmd: %s", strings.Join(cmd.Args, " "))
obj.init.Logf("nifcmd exited with: %d, not skipping cmd", exitStatus)
if s := out.String(); s == "" {
obj.init.Logf("nifcmd out empty!")
} else {
obj.init.Logf("nifcmd out:")
obj.init.Logf("%s", s)
}
//if obj.NIfEquals != nil && *obj.NIfEquals == s {
// obj.init.Logf("nifequals matched")
// return true, nil // don't run
//}
}
if obj.Creates != "" { // gate the extra syscall if obj.Creates != "" { // gate the extra syscall
if _, err := os.Stat(obj.Creates); err == nil { if _, err := os.Stat(obj.Creates); err == nil {
obj.init.Logf("creates file exists, skipping cmd") obj.init.Logf("creates file exists, skipping cmd")
@@ -910,6 +1010,16 @@ func (obj *ExecRes) Cmp(r engine.Res) error {
return errwrap.Wrapf(err, "the IfEquals differs") return errwrap.Wrapf(err, "the IfEquals differs")
} }
if obj.NIfCmd != res.NIfCmd {
return fmt.Errorf("the NIfCmd differs")
}
if obj.NIfCwd != res.NIfCwd {
return fmt.Errorf("the NIfCwd differs")
}
if obj.NIfShell != res.NIfShell {
return fmt.Errorf("the NIfShell differs")
}
if obj.Creates != res.Creates { if obj.Creates != res.Creates {
return fmt.Errorf("the Creates differs") return fmt.Errorf("the Creates differs")
} }
@@ -956,6 +1066,7 @@ type ExecUID struct {
Cmd string Cmd string
WatchCmd string WatchCmd string
IfCmd string IfCmd string
NIfCmd string
DoneCmd string DoneCmd string
// TODO: add more elements here // TODO: add more elements here
} }
@@ -1046,6 +1157,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,
NIfCmd: obj.NIfCmd,
DoneCmd: obj.DoneCmd, DoneCmd: obj.DoneCmd,
// TODO: add more params here // TODO: add more params here
} }
@@ -1140,6 +1252,11 @@ 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.NIfShell != "" {
paths = append(paths, obj.NIfShell)
} else if sp := strings.Fields(obj.NIfCmd); len(sp) > 0 {
paths = append(paths, sp[0])
}
if obj.DoneShell != "" { if obj.DoneShell != "" {
paths = append(paths, obj.DoneShell) paths = append(paths, obj.DoneShell)
} else if sp := strings.Fields(obj.DoneCmd); len(sp) > 0 { } else if sp := strings.Fields(obj.DoneCmd); len(sp) > 0 {

20
examples/lang/exec-if.mcl Normal file
View File

@@ -0,0 +1,20 @@
exec "i will run 1" {
cmd => "/usr/bin/echo i WILL run",
ifcmd => "/usr/bin/true",
}
exec "i will not run 2" {
cmd => "/usr/bin/echo i will NOT run",
ifcmd => "/usr/bin/false",
}
# nifcmd exited with: 0, skipping cmd
exec "i will not run 1" {
cmd => "/usr/bin/echo i will NOT run",
nifcmd => "/usr/bin/true",
}
exec "i will run 2" {
cmd => "/usr/bin/echo i WILL run",
nifcmd => "/usr/bin/false",
}