1"""
2Provide the service module for system supervisord or supervisord in a
3virtualenv
4"""
5
6
7import configparser
8import os
9
10import salt.utils.stringutils
11from salt.exceptions import CommandExecutionError, CommandNotFoundError
12
13
14def __virtual__():
15    # We can't decide at load time whether supervisorctl is present. The
16    # function _get_supervisorctl_bin does a much more thorough job and can
17    # only be accurate at call time.
18    return True
19
20
21def _get_supervisorctl_bin(bin_env):
22    """
23    Return supervisorctl command to call, either from a virtualenv, an argument
24    passed in, or from the global modules options
25    """
26    cmd = "supervisorctl"
27    if not bin_env:
28        which_result = __salt__["cmd.which_bin"]([cmd])
29        if which_result is None:
30            raise CommandNotFoundError("Could not find a `{}` binary".format(cmd))
31        return which_result
32
33    # try to get binary from env
34    if os.path.isdir(bin_env):
35        cmd_bin = os.path.join(bin_env, "bin", cmd)
36        if os.path.isfile(cmd_bin):
37            return cmd_bin
38        raise CommandNotFoundError("Could not find a `{}` binary".format(cmd))
39
40    return bin_env
41
42
43def _ctl_cmd(cmd, name, conf_file, bin_env):
44    """
45    Return the command list to use
46    """
47    ret = [_get_supervisorctl_bin(bin_env)]
48    if conf_file is not None:
49        ret += ["-c", conf_file]
50    ret.append(cmd)
51    if name:
52        ret.append(name)
53    return ret
54
55
56def _get_return(ret):
57    retmsg = ret["stdout"]
58    if ret["retcode"] != 0:
59        # This is a non 0 exit code
60        if "ERROR" not in retmsg:
61            retmsg = "ERROR: {}".format(retmsg)
62    return retmsg
63
64
65def start(name="all", user=None, conf_file=None, bin_env=None):
66    """
67    Start the named service.
68    Process group names should not include a trailing asterisk.
69
70    user
71        user to run supervisorctl as
72    conf_file
73        path to supervisord config file
74    bin_env
75        path to supervisorctl bin or path to virtualenv with supervisor
76        installed
77
78    CLI Example:
79
80    .. code-block:: bash
81
82        salt '*' supervisord.start <service>
83        salt '*' supervisord.start <group>:
84    """
85    if name.endswith(":*"):
86        name = name[:-1]
87    ret = __salt__["cmd.run_all"](
88        _ctl_cmd("start", name, conf_file, bin_env),
89        runas=user,
90        python_shell=False,
91    )
92    return _get_return(ret)
93
94
95def restart(name="all", user=None, conf_file=None, bin_env=None):
96    """
97    Restart the named service.
98    Process group names should not include a trailing asterisk.
99
100    user
101        user to run supervisorctl as
102    conf_file
103        path to supervisord config file
104    bin_env
105        path to supervisorctl bin or path to virtualenv with supervisor
106        installed
107
108    CLI Example:
109
110    .. code-block:: bash
111
112        salt '*' supervisord.restart <service>
113        salt '*' supervisord.restart <group>:
114    """
115    if name.endswith(":*"):
116        name = name[:-1]
117    ret = __salt__["cmd.run_all"](
118        _ctl_cmd("restart", name, conf_file, bin_env),
119        runas=user,
120        python_shell=False,
121    )
122    return _get_return(ret)
123
124
125def stop(name="all", user=None, conf_file=None, bin_env=None):
126    """
127    Stop the named service.
128    Process group names should not include a trailing asterisk.
129
130    user
131        user to run supervisorctl as
132    conf_file
133        path to supervisord config file
134    bin_env
135        path to supervisorctl bin or path to virtualenv with supervisor
136        installed
137
138    CLI Example:
139
140    .. code-block:: bash
141
142        salt '*' supervisord.stop <service>
143        salt '*' supervisord.stop <group>:
144    """
145    if name.endswith(":*"):
146        name = name[:-1]
147    ret = __salt__["cmd.run_all"](
148        _ctl_cmd("stop", name, conf_file, bin_env),
149        runas=user,
150        python_shell=False,
151    )
152    return _get_return(ret)
153
154
155def add(name, user=None, conf_file=None, bin_env=None):
156    """
157    Activates any updates in config for process/group.
158
159    user
160        user to run supervisorctl as
161    conf_file
162        path to supervisord config file
163    bin_env
164        path to supervisorctl bin or path to virtualenv with supervisor
165        installed
166
167    CLI Example:
168
169    .. code-block:: bash
170
171        salt '*' supervisord.add <name>
172    """
173    if name.endswith(":"):
174        name = name[:-1]
175    elif name.endswith(":*"):
176        name = name[:-2]
177    ret = __salt__["cmd.run_all"](
178        _ctl_cmd("add", name, conf_file, bin_env),
179        runas=user,
180        python_shell=False,
181    )
182    return _get_return(ret)
183
184
185def remove(name, user=None, conf_file=None, bin_env=None):
186    """
187    Removes process/group from active config
188
189    user
190        user to run supervisorctl as
191    conf_file
192        path to supervisord config file
193    bin_env
194        path to supervisorctl bin or path to virtualenv with supervisor
195        installed
196
197    CLI Example:
198
199    .. code-block:: bash
200
201        salt '*' supervisord.remove <name>
202    """
203    if name.endswith(":"):
204        name = name[:-1]
205    elif name.endswith(":*"):
206        name = name[:-2]
207    ret = __salt__["cmd.run_all"](
208        _ctl_cmd("remove", name, conf_file, bin_env),
209        runas=user,
210        python_shell=False,
211    )
212    return _get_return(ret)
213
214
215def reread(user=None, conf_file=None, bin_env=None):
216    """
217    Reload the daemon's configuration files
218
219    user
220        user to run supervisorctl as
221    conf_file
222        path to supervisord config file
223    bin_env
224        path to supervisorctl bin or path to virtualenv with supervisor
225        installed
226
227    CLI Example:
228
229    .. code-block:: bash
230
231        salt '*' supervisord.reread
232    """
233    ret = __salt__["cmd.run_all"](
234        _ctl_cmd("reread", None, conf_file, bin_env),
235        runas=user,
236        python_shell=False,
237    )
238    return _get_return(ret)
239
240
241def update(user=None, conf_file=None, bin_env=None, name=None):
242    """
243    Reload config and add/remove/update as necessary
244
245    user
246        user to run supervisorctl as
247    conf_file
248        path to supervisord config file
249    bin_env
250        path to supervisorctl bin or path to virtualenv with supervisor
251        installed
252    name
253        name of the process group to update. if none then update any
254        process group that has changes
255
256    CLI Example:
257
258    .. code-block:: bash
259
260        salt '*' supervisord.update
261    """
262
263    if isinstance(name, str):
264        if name.endswith(":"):
265            name = name[:-1]
266        elif name.endswith(":*"):
267            name = name[:-2]
268
269    ret = __salt__["cmd.run_all"](
270        _ctl_cmd("update", name, conf_file, bin_env),
271        runas=user,
272        python_shell=False,
273    )
274    return _get_return(ret)
275
276
277def status(name=None, user=None, conf_file=None, bin_env=None):
278    """
279    List programs and its state
280
281    user
282        user to run supervisorctl as
283    conf_file
284        path to supervisord config file
285    bin_env
286        path to supervisorctl bin or path to virtualenv with supervisor
287        installed
288
289    CLI Example:
290
291    .. code-block:: bash
292
293        salt '*' supervisord.status
294    """
295    all_process = {}
296    for line in status_raw(name, user, conf_file, bin_env).splitlines():
297        if len(line.split()) > 2:
298            process, state, reason = line.split(None, 2)
299        else:
300            process, state, reason = line.split() + [""]
301        all_process[process] = {"state": state, "reason": reason}
302    return all_process
303
304
305def status_raw(name=None, user=None, conf_file=None, bin_env=None):
306    """
307    Display the raw output of status
308
309    user
310        user to run supervisorctl as
311    conf_file
312        path to supervisord config file
313    bin_env
314        path to supervisorctl bin or path to virtualenv with supervisor
315        installed
316
317    CLI Example:
318
319    .. code-block:: bash
320
321        salt '*' supervisord.status_raw
322    """
323    ret = __salt__["cmd.run_all"](
324        _ctl_cmd("status", name, conf_file, bin_env),
325        runas=user,
326        python_shell=False,
327    )
328    return _get_return(ret)
329
330
331def custom(command, user=None, conf_file=None, bin_env=None):
332    """
333    Run any custom supervisord command
334
335    user
336        user to run supervisorctl as
337    conf_file
338        path to supervisord config file
339    bin_env
340        path to supervisorctl bin or path to virtualenv with supervisor
341        installed
342
343    CLI Example:
344
345    .. code-block:: bash
346
347        salt '*' supervisord.custom "mstop '*gunicorn*'"
348    """
349    ret = __salt__["cmd.run_all"](
350        _ctl_cmd(command, None, conf_file, bin_env),
351        runas=user,
352        python_shell=False,
353    )
354    return _get_return(ret)
355
356
357# TODO: try to find a way to use the supervisor python module to read the
358# config information
359def _read_config(conf_file=None):
360    """
361    Reads the config file using configparser
362    """
363    if conf_file is None:
364        paths = ("/etc/supervisor/supervisord.conf", "/etc/supervisord.conf")
365        for path in paths:
366            if os.path.exists(path):
367                conf_file = path
368                break
369    if conf_file is None:
370        raise CommandExecutionError("No suitable config file found")
371    config = configparser.ConfigParser()
372    try:
373        config.read(conf_file)
374    except OSError as exc:
375        raise CommandExecutionError("Unable to read from {}: {}".format(conf_file, exc))
376    return config
377
378
379def options(name, conf_file=None):
380    """
381    .. versionadded:: 2014.1.0
382
383    Read the config file and return the config options for a given process
384
385    name
386        Name of the configured process
387    conf_file
388        path to supervisord config file
389
390    CLI Example:
391
392    .. code-block:: bash
393
394        salt '*' supervisord.options foo
395    """
396    config = _read_config(conf_file)
397    section_name = "program:{}".format(name)
398    if section_name not in config.sections():
399        raise CommandExecutionError("Process '{}' not found".format(name))
400    ret = {}
401    for key, val in config.items(section_name):
402        val = salt.utils.stringutils.to_num(val.split(";")[0].strip())
403        # pylint: disable=maybe-no-member
404        if isinstance(val, str):
405            if val.lower() == "true":
406                val = True
407            elif val.lower() == "false":
408                val = False
409        # pylint: enable=maybe-no-member
410        ret[key] = val
411    return ret
412
413
414def status_bool(name, expected_state=None, user=None, conf_file=None, bin_env=None):
415    """
416    Check for status of a specific supervisord process and return boolean result.
417
418    name
419        name of the process to check
420
421    expected_state
422        search for a specific process state. If set to ``None`` - any process state will match.
423
424    user
425        user to run supervisorctl as
426
427    conf_file
428        path to supervisord config file
429
430    bin_env
431        path to supervisorctl bin or path to virtualenv with supervisor
432        installed
433
434    CLI Example:
435
436    .. code-block:: bash
437
438        salt '*' supervisord.status_bool nginx expected_state='RUNNING'
439    """
440
441    cmd = "status {}".format(name)
442    for line in custom(cmd, user, conf_file, bin_env).splitlines():
443        if len(line.split()) > 2:
444            process, state, reason = line.split(None, 2)
445        else:
446            process, state, reason = line.split() + [""]
447
448    if reason == "(no such process)" or process != name:
449        return False
450
451    if expected_state is None or state == expected_state:
452        return True
453    else:
454        return False
455