immortal/immortal

Lack of orthogonality: cmd vs require_cmd

tmalaher-telus opened this issue · 2 comments

The cmd configuration value is parsed using strings.Fields(), and executed with: exec.Command(p.command[0], p.command[1:]...)

The require_cmd configuration value is not parsed and is executed with exec.Command(shell, "-c", cfg.RequireCmd).Run()

This means that you cannot use a command in cmd that does any sort of globbing, pipes, quoting, escaping or uses environment variables, and you cannot pass an argument to the command that contains whitespace.

I suppose the issue is that using "sh -c" creates an extra (shell) child process layer, which maybe you want to avoid.

So, for example, I could not run this:

cmd: curl -S -H"X-Custom:Value with spaces" http://example.com/RestService

In other similar tools I've done something like this:

cmd_exec: /bin/curl
cmd_args:
  - -S
  - -HX-Custom:Value with spaces
  - http://example.com/RestService

This still does not allow for globbing, env var expansion or pipes.

Perhaps there should be an alternate way to specify a full "command line" where quoting, interpolation and globbing can work:

cmd_line: echo "Searching for new foo files";date;ls -l $HOME/subdir/*/foo*
nbari commented

Hey, fancy title 😎

cmd is the app/binary/command you would like to demonize, but you still can use /bin/sh for example:

immortal -l /tmp/sleep.log /bin/sh -c "sleep 5 && date"

Output of immortalctl:

$ immortalctl
  PID     Up   Down   Name    CMD
61721   3.2s          61717   /bin/sh -c sleep 5 && date

What is the error you get from your example? try:

cmd: curl -S -H"X-Custom:Value with spaces" http://example.com/RestService
log:
    file: /tmp/test.log

or create a script:

#!/bin/sh
exec 2>&1
curl -S -H"X-Custom:Value with spaces" http://example.com/RestService

Then try:

cmd: /path/to/script
log:
    file: /tmp/test.log

try testing running it like this: immortal -c /path/to/test.yml

require_cmd is used as a condition to start or not your app based on the exit status code, therefore here the use of sh -c so that you could pipe, etc. (https://immortal.run/post/run.yml/)

now if you would not like to "daemonize" but to retry a command X number to times you could try something like:

cmd: rsync -aHAXxv --numeric-ids --delete -P <src> user@host:/<dest>
log:
    file: /tmp/rsync.log
wait: 60
retries: 3

more info here: https://immortal.run/post/retries/

I don't know your use case, but if just need to trigger a command (not an application like a web server) maybe this work better:

nohup command arguments &

A supervisor is something that you usually is not used for triggering things only once

You've pointed out another non-orthogonality:

immortal ...options... command line

behaves differently from:

immortal -c command.yml

So your example: immortal -l /tmp/sleep.log /bin/sh -c "sleep 5 && date" works fine.

But immortal -c command.yml containing: cmd: sleep 5 && date fails with:

sleep: invalid time interval ‘&&’
sleep: invalid time interval ‘date’
Try 'sleep --help' for more information.

Whereas cmd: sh -c "sleep 5 && date" fails with:

5: -c: line 0: unexpected EOF while looking for matching `"'
5: -c: line 1: syntax error: unexpected end of file

This is because what is being passed to exec.Command is:

["sleep", "5" ,"&&", "date"] or ["sh", "-c", "\"sleep", "5", "&&", "date\""]

And since no shell characters are being interpreted, the "sleep" or "sh" commands rightfully complain about the invalid arguments.

So what this comes down to is that with the YAML file option, you can only pass in simple command lines, or better yet, just write a script and call that.

And yes, I'm aware that using "immortal" for a one-shot is weird. I was just trying to create an example.