Skip to content

Latest commit

 

History

History
225 lines (192 loc) · 8.04 KB

pystemd.run.md

File metadata and controls

225 lines (192 loc) · 8.04 KB

pystemd.run

pystemd.run imitates systemd-run, but with a pythonic feel to it.

The key word here is inspired, pystemd.run can act and look like systemd-run in many scenarios, but unlike pystemd, it's not a wrapper around the systemd-run C calls, it's a reimplementation of the same ideas but in Python. With that said, it is heavily inspired by the systemd-run source code, but it diverges from it when needed to be more pythonic, or when systemd-run relies on systemd internals that are not exposed as an API in libsystemd.

One extra thing to notice is that pystemd.run is an API, not a command line tool, so we will not assume that this is the only thing your program calls, but we will assume that this just another building block for your program.

Options:

  • cmd: Array with the command to execute (absolute path only)
  • stop_cmd: Array with the command to execute on stop (absolute path only)
  • stop_post_cmd: Array with the command to execute after stop (absolute path only)
  • start_pre_cmd: Array with the command to execute on pre start (absolute path only)
  • start_post_cmd: Array with the command to execute on on post start (absolute path only)
  • address: A custom dbus socket address
  • service_type: Set the unit type, e.g. notify, oneshot. If you dont give a value, the unit type will be whatever systemd thinks is the default.
  • name: Name of the unit. If not provided, it will be autogenerated.
  • user: Username to execute the command, defaults to current user.
  • user_mode: Equivalent to running systemd-run --user. Defaults to True if current user id not root (uid = 0).
  • nice: Nice level to run the command.
  • runtime_max_sec: Set seconds before sending a sigterm to the process, if the service does not die nicely, it will send a sigkill.
  • env: A dict with environment variables.
  • extra: If you know what you are doing, you can pass extra configuration settings to the start_transient_unit method. machine: Machine name to execute the command, by default we connect to the host's dbus.
  • wait: Wait for command completion before returning control, defaults to False.
  • remain_after_exit: If True, the transient unit will remain after cmd has finished, also if true, this methods will return pystemd.systemd1.Unit object. defaults to False and this method returns None and the unit will be gone as soon as is done.
  • collect: Unload unit after it ran, even when failed.
  • raise_on_fail: Will raise a PystemdRunError is cmd exit with non 0 status code, it won't take affect unless you set wait=True, defaults to False.
  • pty: Set this variable to True if you want a pty to be created. if you pass a machine, the pty will be created in the machine. Setting this value will ignore whatever you set in pty_master and pty_path.
  • pty_master: It has only meaning if you pass a pty_path also, this file descriptor will be used to forward redirection to stdin and stdout if no stdin or stdout is present, then this value does nothing.
  • pty_path: Setting this value will pass this pty_path to the created process and will connect the process stdin, stdout and stderr to this pty. by itself it only ensure that your process has a real pty that can have ioctl operation over it. if you also pass a pty_master, stdin and stdout the pty forwars is handle for you.
  • stdin: Specify a file descriptor for stdin. By default this is None and your unit will not have a stdin. If you set pty = True, or set a pty_master then that pty will be read and forwarded to this file descriptor.
  • stdout: Specify a file descriptor for stdout. By default this is None and your unit will not have a stdout. If you set pty = True, or set a pty_master then that pty will be read and forwarded to this file descriptor.
  • stderr: Specify a file descriptor for stderr. By default this is None and your unit will not have a stderr.
  • slice_: the slice under you want to run the unit.

Examples

Usage examples (all run as root):

1.- executes a ``/bin/sleep 42in the background and returns apystemd.systemd1.Unit` object.

>>> import pystemd.run
>>> pystemd.run([b'/bin/sleep', b'42'])
<pystemd.systemd1.unit.Unit at 0x7f8c460695c0>

pystemd can also take raw strings, but there is no path expansion so pystemd.run(b'/bin/sleep 42') is the same as pystemd.run([b'/bin/sleep', b'42']), but not pystemd.run(b'sleep 42')

>>> import pystemd.run
>>> pystemd.run(b'/bin/sleep 42')
<pystemd.systemd1.unit.Unit at 0x7f8c460695c0>

2.- executes /bin/sleep 42 and keeps the unit around after the process is done

>>> import pystemd.run
>>> unit = pystemd.run([b'/bin/sleep', b'42'], remain_after_exit=True)
>>> unit
<pystemd.systemd1.unit.Unit at 0x7f8c460695c0>
>>> unit.Service.MainPID
66244
>>> # ... waiting 42 seconds later
>>> unit.Service.MainPID
0

3.- executing /bin/sleep 42 as other user

>>> import pystemd.run
>>> unit = pystemd.run([b'/bin/sleep', b'42'], remain_after_exit=True,
    user=b'aleivag'
)

4.- Multiple things: Executing /bin/env, on a machine named miniarch, watching the output of the command.

>>> import pystemd.run, sys
>>> pystemd.run(
    [b'/bin/env'],
    machine=b'miniarch',
    wait=True,
    nice=3,
    stdout=sys.stdout.fileno(),
    env={b'SUPER_SECRET_PASSW': b'1234'}
)
LANG=en_US.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
INVOCATION_ID=d7abcffb71ba419cbac448baa00cb495
SUPER_SECRET_PASSW=1234

5.- Executing bash with a proper pty/tty, we specify stdin and stdout to do automatic redirection

>>> import pystemd.run, sys
>>> pystemd.run([b'/bin/bash'], wait=True,
    stdout=sys.stdout,  stdin=sys.stdin, pty=True)

[root@localhost /]# ec<tab>
echo  ecpg
[root@localhost /]# echo "hi all"
hi all
[root@localhost /]# sleep 30
^C
[root@localhost /]# sleep 60
^Z
[1]+  Stopped                 sleep 60
[root@localhost /]# jobs
[1]+  Stopped                 sleep 60
[root@localhost /]# exit
exit
>>>

6.- Write unit stdout and stderr to a file

>>> import pystemd.run
>>> with open("/tmp/pystemd_output", "w") as stdout:
>>>   fd = stdout.fileno()
>>>   unit = pystemd.run([b'/bin/echo', b'omg!'], stdout=fd, stderr=fd)

Note: if using CPython, don't do open().fileno(). open returns a File Object that after .fileno is called has 0 references pointing at it, so the runtime frees the object, closing the file descriptor in the process.

7.- adds ExecStop to pystend.run, this code execute a unit (and stop it right away), and then exec stop waits for the unit main process to finish before exiting.

>>> import pystemd.run
>>> pystemd.run(
    "/bin/sleep 5",
    stop_cmd=[
        "/bin/bash",
        "-c",
        """
if [ -n "${MAINPID}" ]; then
    # send a signal to main process to ask it to stop, and then
    # lets wait for it to actually stop.
    /bin/tail -f  --pid ${MAINPID}
    echo we are done with $MAINPID
else
    echo process not running
fi
         """
    ],
    stdout=1).Unit.Stop(b"replace")
b'/org/freedesktop/systemd1/job/2568298'

# 5 seconds later

we are done with 1015398

Extra notes:

1.- Please note that to use pystemd.run, you need to import pystemd.run, importing pystemd and then calling pystemd.run will not work.

2.- pystemd.run (which is the same as pystemd) accepts Unicode strings and Path object as arguments. They don't have to be bytes, and will be converted internally to byte strings for you. With that said, you should try to pass byte strings when possible to avoid silly encode/decode errors, but also because pystemd in general will always return byte strings instead of unicode strings.

3.- stuff systemd-run does that pystemd.run does not (yet) does, but its on the road-map: a) Run on a remote host. b) Run something different than a transient service, e.g. a timer.

7.- Run a unit in a cgroup We can create a new cgroup with:

sudo mkdir /sys/fs/cgroup/my_amazing.slice

And run the unit with:

>>> import pystemd.run
>>> pystemd.run([b'/bin/yes'], slice_="my_amazing.slice")