~canonical-ci-engineering/ubuntu-ci-services-itself/ansible

« back to all changes in this revision

Viewing changes to library/service

  • Committer: Package Import Robot
  • Author(s): Michael Vogt, Harlan Lieberman-Berg, Michael Vogt
  • Date: 2013-11-01 09:40:59 UTC
  • mfrom: (1.1.2)
  • Revision ID: package-import@ubuntu.com-20131101094059-6w580ocxzqyqzuu3
Tags: 1.3.4+dfsg-1
[ Harlan Lieberman-Berg ]
* New upstream release (Closes: #717777).
  Fixes CVE-2013-2233 (Closes: #714822).
  Fixes CVE-2013-4259 (Closes: #721766).
* Drop fix-ansible-cfg patch.
* Change docsite generation to not expect docs as part of a wordpress install.
* Add trivial patch to fix lintian error with rpm-key script.
* Add patch header information to fix-html-makefile.

[ Michael Vogt ]
* add myself to uploader
* build/ship the module manpages for ansible in the ansible package

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python
2
 
# -*- coding: utf-8 -*-
3
 
 
4
 
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
5
 
#
6
 
# This file is part of Ansible
7
 
#
8
 
# Ansible is free software: you can redistribute it and/or modify
9
 
# it under the terms of the GNU General Public License as published by
10
 
# the Free Software Foundation, either version 3 of the License, or
11
 
# (at your option) any later version.
12
 
#
13
 
# Ansible is distributed in the hope that it will be useful,
14
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 
# GNU General Public License for more details.
17
 
#
18
 
# You should have received a copy of the GNU General Public License
19
 
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
20
 
 
21
 
DOCUMENTATION = '''
22
 
---
23
 
module: service
24
 
author: Michael DeHaan
25
 
version_added: "0.1"
26
 
short_description:  Manage services.
27
 
description:
28
 
    - Controls services on remote hosts.
29
 
options:
30
 
    name:
31
 
        required: true
32
 
        description:
33
 
        - Name of the service.
34
 
    state:
35
 
        required: false
36
 
        choices: [ started, stopped, restarted, reloaded ]
37
 
        description:
38
 
          - C(started)/C(stopped) are idempotent actions that will not run
39
 
            commands unless necessary.  C(restarted) will always bounce the
40
 
            service.  C(reloaded) will always reload.
41
 
    pattern:
42
 
        required: false
43
 
        version_added: "0.7"
44
 
        description:
45
 
        - If the service does not respond to the status command, name a
46
 
          substring to look for as would be found in the output of the I(ps)
47
 
          command as a stand-in for a status result.  If the string is found,
48
 
          the service will be assumed to be running.
49
 
    enabled:
50
 
        required: false
51
 
        choices: [ "yes", "no" ]
52
 
        description:
53
 
        - Whether the service should start on boot.
54
 
    arguments:
55
 
        description:
56
 
        - Additional arguments provided on the command line
57
 
        aliases: [ 'args' ]
58
 
examples:
59
 
    - description: Example action to start service httpd, if not running
60
 
      code: "service: name=httpd state=started"
61
 
    - description: Example action to stop service httpd, if running
62
 
      code: "service: name=httpd state=stopped"
63
 
    - description: Example action to restart service httpd, in all cases
64
 
      code: "service: name=httpd state=restarted"
65
 
    - description: Example action to reload service httpd, in all cases
66
 
      code: "service: name=httpd state=reloaded"
67
 
    - description: Example action to start service foo, based on running process /usr/bin/foo
68
 
      code: "service: name=foo pattern=/usr/bin/foo state=started"
69
 
    - description: Example action to restart network service for interface eth0
70
 
      code: "service: name=network state=restarted args=eth0"
71
 
'''
72
 
 
73
 
import platform
74
 
import os
75
 
import tempfile
76
 
import shlex
77
 
import select
78
 
 
79
 
class Service(object):
80
 
    """
81
 
    This is the generic Service manipulation class that is subclassed
82
 
    based on platform.
83
 
 
84
 
    A subclass should override the following action methods:-
85
 
      - get_service_tools
86
 
      - service_enable
87
 
      - get_service_status
88
 
      - service_control
89
 
 
90
 
    All subclasses MUST define platform and distribution (which may be None).
91
 
    """
92
 
 
93
 
    platform = 'Generic'
94
 
    distribution = None
95
 
 
96
 
    def __new__(cls, *args, **kwargs):
97
 
        return load_platform_subclass(Service, args, kwargs)
98
 
 
99
 
    def __init__(self, module):
100
 
        self.module         = module
101
 
        self.name           = module.params['name']
102
 
        self.state          = module.params['state']
103
 
        self.pattern        = module.params['pattern']
104
 
        self.enable         = module.params['enabled']
105
 
        self.changed        = False
106
 
        self.running        = None
107
 
        self.action         = None
108
 
        self.svc_cmd        = None
109
 
        self.svc_initscript = None
110
 
        self.svc_initctl    = None
111
 
        self.enable_cmd     = None
112
 
        self.arguments      = module.params.get('arguments', '')
113
 
        self.rcconf_file    = None
114
 
        self.rcconf_key     = None
115
 
        self.rcconf_value   = None
116
 
 
117
 
        # select whether we dump additional debug info through syslog
118
 
        self.syslogging = False
119
 
 
120
 
    # ===========================================
121
 
    # Platform specific methods (must be replaced by subclass).
122
 
 
123
 
    def get_service_tools(self):
124
 
        self.module.fail_json(msg="get_service_tools not implemented on target platform")
125
 
 
126
 
    def service_enable(self):
127
 
        self.module.fail_json(msg="service_enable not implemented on target platform")
128
 
 
129
 
    def get_service_status(self):
130
 
        self.module.fail_json(msg="get_service_status not implemented on target platform")
131
 
 
132
 
    def service_control(self):
133
 
        self.module.fail_json(msg="service_control not implemented on target platform")
134
 
 
135
 
    # ===========================================
136
 
    # Generic methods that should be used on all platforms.
137
 
 
138
 
    def execute_command(self, cmd, daemonize=False):
139
 
        if self.syslogging:
140
 
            syslog.openlog('ansible-%s' % os.path.basename(__file__))
141
 
            syslog.syslog(syslog.LOG_NOTICE, 'Command %s, daemonize %r' % (cmd, daemonize))
142
 
 
143
 
        # Most things don't need to be daemonized
144
 
        if not daemonize:
145
 
            return self.module.run_command(cmd)
146
 
 
147
 
        # This is complex because daemonization is hard for people.
148
 
        # What we do is daemonize a part of this module, the daemon runs the
149
 
        # command, picks up the return code and output, and returns it to the
150
 
        # main process.
151
 
        pipe = os.pipe()
152
 
        pid = os.fork()
153
 
        if pid == 0:
154
 
            os.close(pipe[0])
155
 
            # Set stdin/stdout/stderr to /dev/null
156
 
            fd = os.open(os.devnull, os.O_RDWR)
157
 
            if fd != 0:
158
 
                os.dup2(fd, 0)
159
 
            if fd != 1:
160
 
                os.dup2(fd, 1)
161
 
            if fd != 2:
162
 
                os.dup2(fd, 2)
163
 
            if fd not in (0, 1, 2):
164
 
                os.close(fd)
165
 
 
166
 
            # Make us a daemon. Yes, that's all it takes.
167
 
            pid = os.fork()
168
 
            if pid > 0:
169
 
                os._exit(0)
170
 
            os.setsid()
171
 
            os.chdir("/")
172
 
            pid = os.fork()
173
 
            if pid > 0:
174
 
                os._exit(0)
175
 
 
176
 
            # Start the command
177
 
            p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=lambda: os.close(pipe[1]))
178
 
            stdout = ""
179
 
            stderr = ""
180
 
            fds = [p.stdout, p.stderr]
181
 
            # Wait for all output, or until the main process is dead and its output is done.
182
 
            while fds:
183
 
                rfd, wfd, efd = select.select(fds, [], fds, 1)
184
 
                if not (rfd + wfd + efd) and p.poll() is not None:
185
 
                    break
186
 
                if p.stdout in rfd:
187
 
                    dat = os.read(p.stdout.fileno(), 4096)
188
 
                    if not dat:
189
 
                        fds.remove(p.stdout)
190
 
                    stdout += dat
191
 
                if p.stderr in rfd:
192
 
                    dat = os.read(p.stderr.fileno(), 4096)
193
 
                    if not dat:
194
 
                        fds.remove(p.stderr)
195
 
                    stderr += dat
196
 
            p.wait()
197
 
            # Return a JSON blob to parent
198
 
            os.write(pipe[1], json.dumps([p.returncode, stdout, stderr]))
199
 
            os.close(pipe[1])
200
 
            os._exit(0)
201
 
        elif pid == -1:
202
 
            self.module.fail_json(msg="unable to fork")
203
 
        else:
204
 
            os.close(pipe[1])
205
 
            os.waitpid(pid, 0)
206
 
            # Wait for data from daemon process and process it.
207
 
            data = ""
208
 
            while True:
209
 
                rfd, wfd, efd = select.select([pipe[0]], [], [pipe[0]])
210
 
                if pipe[0] in rfd:
211
 
                    dat = os.read(pipe[0], 4096)
212
 
                    if not dat:
213
 
                        break
214
 
                    data += dat
215
 
            return json.loads(data)
216
 
 
217
 
    def check_ps(self):
218
 
        # Set ps flags
219
 
        if platform.system() == 'SunOS':
220
 
            psflags = '-ef'
221
 
        else:
222
 
            psflags = 'auxww'
223
 
 
224
 
        # Find ps binary
225
 
        psbin = self.module.get_bin_path('ps', True)
226
 
 
227
 
        (rc, psout, pserr) = self.execute_command('%s %s' % (psbin, psflags))
228
 
        # If rc is 0, set running as appropriate
229
 
        if rc == 0:
230
 
            self.running = False
231
 
            lines = psout.split("\n")
232
 
            for line in lines:
233
 
                if self.pattern in line and not "pattern=" in line:
234
 
                    # so as to not confuse ./hacking/test-module
235
 
                    self.running = True
236
 
                    break
237
 
 
238
 
    def check_service_changed(self):
239
 
        if self.state and self.running is None:
240
 
            self.module.fail_json(msg="failed determining service state, possible typo of service name?")
241
 
        # Find out if state has changed
242
 
        if not self.running and self.state in ["started", "running"]:
243
 
            self.changed = True
244
 
        elif self.running and self.state in ["stopped","reloaded"]:
245
 
            self.changed = True
246
 
        elif self.state == "restarted":
247
 
            self.changed = True
248
 
        if self.module.check_mode and self.changed:
249
 
            self.module.exit_json(changed=True, msg='service state changed')
250
 
 
251
 
    def modify_service_state(self):
252
 
 
253
 
        # Only do something if state will change
254
 
        if self.changed:
255
 
            # Control service
256
 
            if self.state in ['started', 'running']:
257
 
                self.action = "start"
258
 
            elif self.state == 'stopped':
259
 
                self.action = "stop"
260
 
            elif self.state == 'reloaded':
261
 
                self.action = "reload"
262
 
            elif self.state == 'restarted':
263
 
                self.action = "restart"
264
 
 
265
 
            if self.module.check_mode:
266
 
                self.module.exit_json(changed=True, msg='changing service state')
267
 
 
268
 
            return self.service_control()
269
 
 
270
 
        else:
271
 
            # If nothing needs to change just say all is well
272
 
            rc = 0
273
 
            err = ''
274
 
            out = ''
275
 
            return rc, out, err
276
 
 
277
 
    def service_enable_rcconf(self):
278
 
        if self.rcconf_file is None or self.rcconf_key is None or self.rcconf_value is None:
279
 
            self.module.fail_json(msg="service_enable_rcconf() requires rcconf_file, rcconf_key and rcconf_value")
280
 
 
281
 
        changed = None
282
 
        entry = '%s="%s"\n' % (self.rcconf_key, self.rcconf_value)
283
 
        RCFILE = open(self.rcconf_file, "r")
284
 
        new_rc_conf = []
285
 
 
286
 
        # Build a list containing the possibly modified file.
287
 
        for rcline in RCFILE:
288
 
            # Parse line removing whitespaces, quotes, etc.
289
 
            rcarray = shlex.split(rcline, comments=True)
290
 
            if len(rcarray) >= 1 and '=' in rcarray[0]:
291
 
                (key, value) = rcarray[0].split("=", 1)
292
 
                if key == self.rcconf_key:
293
 
                    if value == self.rcconf_value:
294
 
                        # Since the proper entry already exists we can stop iterating.
295
 
                        changed = False
296
 
                        break
297
 
                    else:
298
 
                        # We found the key but the value is wrong, replace with new entry.
299
 
                        rcline = entry
300
 
                        changed = True
301
 
 
302
 
            # Add line to the list.
303
 
            new_rc_conf.append(rcline)
304
 
 
305
 
        # We are done with reading the current rc.conf, close it.
306
 
        RCFILE.close()
307
 
 
308
 
        # If we did not see any trace of our entry we need to add it.
309
 
        if changed is None:
310
 
            new_rc_conf.append(entry)
311
 
            changed = True
312
 
 
313
 
        if changed is True:
314
 
 
315
 
            if self.module.check_mode:
316
 
                self.module.exit_json(changed=True, msg="changing service enablement")
317
 
 
318
 
            # Create a temporary file next to the current rc.conf (so we stay on the same filesystem).
319
 
            # This way the replacement operation is atomic.
320
 
            rcconf_dir = os.path.dirname(self.rcconf_file)
321
 
            rcconf_base = os.path.basename(self.rcconf_file)
322
 
            (TMP_RCCONF, tmp_rcconf_file) = tempfile.mkstemp(dir=rcconf_dir, prefix="%s-" % rcconf_base)
323
 
 
324
 
            # Write out the contents of the list into our temporary file.
325
 
            for rcline in new_rc_conf:
326
 
                os.write(TMP_RCCONF, rcline)
327
 
 
328
 
            # Close temporary file.
329
 
            os.close(TMP_RCCONF)
330
 
 
331
 
            # Replace previous rc.conf.
332
 
            self.module.atomic_replace(tmp_rcconf_file, self.rcconf_file)
333
 
 
334
 
# ===========================================
335
 
# Subclass: Linux
336
 
 
337
 
class LinuxService(Service):
338
 
    """
339
 
    This is the Linux Service manipulation class - it is currently supporting
340
 
    a mixture of binaries and init scripts for controlling services started at
341
 
    boot, as well as for controlling the current state.
342
 
    """
343
 
 
344
 
    platform = 'Linux'
345
 
    distribution = None
346
 
 
347
 
    def get_service_tools(self):
348
 
 
349
 
        paths = [ '/sbin', '/usr/sbin', '/bin', '/usr/bin' ]
350
 
        binaries = [ 'service', 'chkconfig', 'update-rc.d', 'initctl', 'systemctl', 'start', 'stop', 'restart' ]
351
 
        initpaths = [ '/etc/init.d' ]
352
 
        location = dict()
353
 
 
354
 
        for binary in binaries:
355
 
            location[binary] = None
356
 
        for binary in binaries:
357
 
            location[binary] = self.module.get_bin_path(binary)
358
 
 
359
 
        # Locate a tool for enable options
360
 
        if location.get('chkconfig', None) and os.path.exists("/etc/init.d/%s" % self.name):
361
 
            # we are using a standard SysV service
362
 
            self.enable_cmd = location['chkconfig']
363
 
        elif location.get('update-rc.d', None) and os.path.exists("/etc/init/%s.conf" % self.name):
364
 
            # service is managed by upstart
365
 
            self.enable_cmd = location['update-rc.d']
366
 
        elif location.get('update-rc.d', None) and os.path.exists("/etc/init.d/%s" % self.name):
367
 
            # service is managed by with SysV init scripts, but with update-rc.d
368
 
            self.enable_cmd = location['update-rc.d']
369
 
        elif location.get('systemctl', None):
370
 
 
371
 
            # verify service is managed by systemd
372
 
            rc, out, err = self.execute_command("%s list-unit-files" % (location['systemctl']))
373
 
 
374
 
            # adjust the service name to account for template service unit files
375
 
            index = self.name.find('@')
376
 
            if index == -1:
377
 
                name = self.name
378
 
            else:
379
 
                name = self.name[:index+1]
380
 
 
381
 
            look_for = "%s.service" % name
382
 
            for line in out.splitlines():
383
 
               if line.startswith(look_for):
384
 
                   self.enable_cmd = location['systemctl']
385
 
                   break
386
 
 
387
 
        # Locate a tool for runtime service management (start, stop etc.)
388
 
        self.svc_cmd = ''
389
 
        if location.get('service', None) and os.path.exists("/etc/init.d/%s" % self.name):
390
 
            # SysV init script
391
 
            self.svc_cmd = location['service']
392
 
        elif location.get('start', None) and os.path.exists("/etc/init/%s.conf" % self.name):
393
 
            # upstart -- rather than being managed by one command, start/stop/restart are actual commands
394
 
            self.svc_cmd = ''
395
 
        else:
396
 
            # still a SysV init script, but /sbin/service isn't installed
397
 
            for initdir in initpaths:
398
 
                initscript = "%s/%s" % (initdir,self.name)
399
 
                if os.path.isfile(initscript):
400
 
                    self.svc_initscript = initscript
401
 
 
402
 
        # couldn't find anything yet, assume systemd
403
 
        if self.svc_initscript is None:
404
 
            if location.get('systemctl'):
405
 
                self.svc_cmd = location['systemctl']
406
 
 
407
 
        if self.svc_cmd is None and not self.svc_initscript:
408
 
            self.module.fail_json(msg='cannot find \'service\' binary or init script for service, aborting')
409
 
 
410
 
        if location.get('initctl', None):
411
 
            self.svc_initctl = location['initctl']
412
 
 
413
 
    def get_service_status(self):
414
 
        self.action = "status"
415
 
        rc, status_stdout, status_stderr = self.service_control()
416
 
 
417
 
        # if we have decided the service is managed by upstart, we check for some additional output...
418
 
        if self.svc_initctl and self.running is None:
419
 
            # check the job status by upstart response
420
 
            initctl_rc, initctl_status_stdout, initctl_status_stderr = self.execute_command("%s status %s" % (self.svc_initctl, self.name))
421
 
            if initctl_status_stdout.find("stop/waiting") != -1:
422
 
                self.running = False
423
 
            elif initctl_status_stdout.find("start/running") != -1:
424
 
                self.running = True
425
 
 
426
 
        # if the job status is still not known check it by response code
427
 
        # For reference, see:
428
 
        # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
429
 
        if self.running is None:
430
 
            if rc in [1, 2, 3, 4, 69]:
431
 
                self.running = False
432
 
            elif rc == 0:
433
 
                self.running = True
434
 
 
435
 
        # if the job status is still not known check it by status output keywords
436
 
        if self.running is None:
437
 
            # first tranform the status output that could irritate keyword matching
438
 
            cleanout = status_stdout.lower().replace(self.name.lower(), '')
439
 
            if "stop" in cleanout:
440
 
                self.running = False
441
 
            elif "run" in cleanout and "not" in cleanout:
442
 
                self.running = False
443
 
            elif "run" in cleanout and "not" not in cleanout:
444
 
                self.running = True
445
 
            elif "start" in cleanout and "not" not in cleanout:
446
 
                self.running = True
447
 
            elif 'could not access pid file' in cleanout:
448
 
                self.running = False
449
 
            elif 'is dead and pid file exists' in cleanout:
450
 
                self.running = False
451
 
            elif 'dead but subsys locked' in cleanout:
452
 
                self.running = False
453
 
            elif 'dead but pid file exists' in cleanout:
454
 
                self.running = False
455
 
 
456
 
        # if the job status is still not known check it by special conditions
457
 
        if self.running is None:
458
 
            if self.name == 'iptables' and status_stdout.find("ACCEPT") != -1:
459
 
                # iptables status command output is lame
460
 
                # TODO: lookup if we can use a return code for this instead?
461
 
                self.running = True
462
 
 
463
 
        return self.running
464
 
 
465
 
 
466
 
    def service_enable(self):
467
 
 
468
 
        if self.enable_cmd is None:
469
 
            self.module.fail_json(msg='service name not recognized')
470
 
 
471
 
        # FIXME: we use chkconfig or systemctl
472
 
        # to decide whether to run the command here but need something
473
 
        # similar for upstart
474
 
 
475
 
        if self.enable_cmd.endswith("chkconfig"):
476
 
            (rc, out, err) = self.execute_command("%s --list %s" % (self.enable_cmd, self.name))
477
 
            if 'chkconfig --add %s' % self.name in err:
478
 
                self.execute_command("%s --add %s" % (self.enable_cmd, self.name))
479
 
                (rc, out, err) = self.execute_command("%s --list %s" % (self.enable_cmd, self.name))
480
 
            if not self.name in out:
481
 
                self.module.fail_json(msg="unknown service name")
482
 
            state = out.split()[-1]
483
 
            if self.enable and ( "3:on" in out and "5:on" in out ):
484
 
                return
485
 
            elif not self.enable and ( "3:off" in out and "5:off" in out ):
486
 
                return
487
 
 
488
 
        if self.enable_cmd.endswith("systemctl"):
489
 
            (rc, out, err) = self.execute_command("%s show %s.service" % (self.enable_cmd, self.name))
490
 
 
491
 
            d = dict(line.split('=', 1) for line in out.splitlines())
492
 
            if "UnitFileState" in d:
493
 
                if self.enable and d["UnitFileState"] == "enabled":
494
 
                    return
495
 
                elif not self.enable and d["UnitFileState"] == "disabled":
496
 
                    return
497
 
            elif not self.enable:
498
 
                return
499
 
 
500
 
        # we change argument depending on real binary used
501
 
        # update-rc.d wants enable/disable while
502
 
        # chkconfig wants on/off
503
 
        # also, systemctl needs the argument order reversed
504
 
        if self.enable:
505
 
            on_off = "on"
506
 
            enable_disable = "enable"
507
 
        else:
508
 
            on_off = "off"
509
 
            enable_disable = "disable"
510
 
 
511
 
        if self.enable_cmd.endswith("update-rc.d"):
512
 
            args = (self.enable_cmd, self.name, enable_disable)
513
 
        elif self.enable_cmd.endswith("systemctl"):
514
 
            args = (self.enable_cmd, enable_disable, self.name + ".service")
515
 
        else:
516
 
            args = (self.enable_cmd, self.name, on_off)
517
 
 
518
 
        self.changed = True
519
 
 
520
 
        if self.module.check_mode and self.changed:
521
 
            self.module.exit_json(changed=True)
522
 
 
523
 
        return self.execute_command("%s %s %s" % args)
524
 
 
525
 
 
526
 
    def service_control(self):
527
 
 
528
 
        # Decide what command to run
529
 
        svc_cmd = ''
530
 
        arguments = self.arguments
531
 
        if self.svc_cmd:
532
 
            if not self.svc_cmd.endswith("systemctl"):
533
 
                # SysV take the form <cmd> <name> <action>
534
 
                svc_cmd = "%s %s" % (self.svc_cmd, self.name)
535
 
            else:
536
 
                # systemd commands take the form <cmd> <action> <name>
537
 
                svc_cmd = self.svc_cmd
538
 
                arguments = "%s %s" % (self.name, arguments)
539
 
        elif self.svc_initscript:
540
 
            # upstart
541
 
            svc_cmd = "%s" % self.svc_initscript
542
 
 
543
 
        if self.action is not "restart":
544
 
            if svc_cmd != '':
545
 
                # upstart or systemd
546
 
                rc_state, stdout, stderr = self.execute_command("%s %s %s" % (svc_cmd, self.action, arguments), daemonize=True)
547
 
            else:
548
 
                # SysV
549
 
                rc_state, stdout, stderr = self.execute_command("%s %s %s" % (self.action, self.name, arguments), daemonize=True)
550
 
        else:
551
 
            # not all services support restart. Do it the hard way.
552
 
            if svc_cmd != '':
553
 
                # upstart or systemd
554
 
                rc1, stdout1, stderr1 = self.execute_command("%s %s %s" % (svc_cmd, 'stop', arguments), daemonize=True)
555
 
            else:
556
 
                # SysV
557
 
                rc1, stdout1, stderr1 = self.execute_command("%s %s %s" % ('stop', self.name, arguments), daemonize=True)
558
 
 
559
 
            if svc_cmd != '':
560
 
                # upstart or systemd
561
 
                rc2, stdout2, stderr2 = self.execute_command("%s %s %s" % (svc_cmd, 'start', arguments), daemonize=True)
562
 
            else:
563
 
                # SysV
564
 
                rc2, stdout2, stderr2 = self.execute_command("%s %s %s" % ('start', self.name, arguments), daemonize=True)
565
 
 
566
 
            # merge return information
567
 
            if rc1 != 0 and rc2 == 0:
568
 
                rc_state = rc2
569
 
                stdout = stdout2
570
 
                stderr = stderr2
571
 
            else:
572
 
                rc_state = rc1 + rc2
573
 
                stdout = stdout1 + stdout2
574
 
                stderr = stderr1 + stderr2
575
 
 
576
 
        return(rc_state, stdout, stderr)
577
 
 
578
 
# ===========================================
579
 
# Subclass: FreeBSD
580
 
 
581
 
class FreeBsdService(Service):
582
 
    """
583
 
    This is the FreeBSD Service manipulation class - it uses the /etc/rc.conf
584
 
    file for controlling services started at boot and the 'service' binary to
585
 
    check status and perform direct service manipulation.
586
 
    """
587
 
 
588
 
    platform = 'FreeBSD'
589
 
    distribution = None
590
 
 
591
 
    def get_service_tools(self):
592
 
        self.svc_cmd = self.module.get_bin_path('service', True)
593
 
 
594
 
        if not self.svc_cmd:
595
 
            self.module.fail_json(msg='unable to find service binary')
596
 
 
597
 
    def get_service_status(self):
598
 
        rc, stdout, stderr = self.execute_command("%s %s %s" % (self.svc_cmd, self.name, 'onestatus'))
599
 
        if rc == 1:
600
 
            self.running = False
601
 
        elif rc == 0:
602
 
            self.running = True
603
 
 
604
 
    def service_enable(self):
605
 
        if self.enable:
606
 
            self.rcconf_value = "YES"
607
 
        else:
608
 
            self.rcconf_value = "NO"
609
 
 
610
 
        rcfiles = [ '/etc/rc.conf','/usr/local/etc/rc.conf' ]
611
 
        for rcfile in rcfiles:
612
 
            if os.path.isfile(rcfile):
613
 
                self.rcconf_file = rcfile
614
 
 
615
 
        self.rcconf_key = "%s_enable" % self.name
616
 
 
617
 
        return self.service_enable_rcconf()
618
 
 
619
 
    def service_control(self):
620
 
 
621
 
        if self.action is "start":
622
 
            self.action = "onestart"
623
 
        if self.action is "stop":
624
 
            self.action = "onestop"
625
 
        if self.action is "reload":
626
 
            self.action = "onereload"
627
 
 
628
 
        return self.execute_command("%s %s %s" % (self.svc_cmd, self.name, self.action))
629
 
 
630
 
# ===========================================
631
 
# Subclass: OpenBSD
632
 
 
633
 
class OpenBsdService(Service):
634
 
    """
635
 
    This is the OpenBSD Service manipulation class - it uses /etc/rc.d for
636
 
    service control. Enabling a service is currently not supported because the
637
 
    <service>_flags variable is not boolean, you should supply a rc.conf.local
638
 
    file in some other way.
639
 
    """
640
 
 
641
 
    platform = 'OpenBSD'
642
 
    distribution = None
643
 
 
644
 
    def get_service_tools(self):
645
 
        rcdir = '/etc/rc.d'
646
 
 
647
 
        rc_script = "%s/%s" % (rcdir, self.name)
648
 
        if os.path.isfile(rc_script):
649
 
            self.svc_cmd = rc_script
650
 
 
651
 
        if not self.svc_cmd:
652
 
            self.module.fail_json(msg='unable to find rc.d script')
653
 
 
654
 
    def get_service_status(self):
655
 
        rc, stdout, stderr = self.execute_command("%s %s" % (self.svc_cmd, 'check'))
656
 
        if rc == 1:
657
 
            self.running = False
658
 
        elif rc == 0:
659
 
            self.running = True
660
 
 
661
 
    def service_control(self):
662
 
        return self.execute_command("%s %s" % (self.svc_cmd, self.action))
663
 
 
664
 
# ===========================================
665
 
# Subclass: NetBSD
666
 
 
667
 
class NetBsdService(Service):
668
 
    """
669
 
    This is the NetBSD Service manipulation class - it uses the /etc/rc.conf
670
 
    file for controlling services started at boot, check status and perform
671
 
    direct service manipulation. Init scripts in /etc/rcd are used for
672
 
    controlling services (start/stop) as well as for controlling the current
673
 
    state.
674
 
    """
675
 
 
676
 
    platform = 'NetBSD'
677
 
    distribution = None
678
 
 
679
 
    def get_service_tools(self):
680
 
        initpaths = [ '/etc/rc.d' ]             # better: $rc_directories - how to get in here? Run: sh -c '. /etc/rc.conf ; echo $rc_directories'
681
 
 
682
 
        for initdir in initpaths:
683
 
            initscript = "%s/%s" % (initdir,self.name)
684
 
            if os.path.isfile(initscript):
685
 
                self.svc_initscript = initscript
686
 
 
687
 
        if not self.svc_initscript:
688
 
            self.module.fail_json(msg='unable to find rc.d script')
689
 
 
690
 
    def service_enable(self):
691
 
        if self.enable:
692
 
            self.rcconf_value = "YES"
693
 
        else:
694
 
            self.rcconf_value = "NO"
695
 
 
696
 
        rcfiles = [ '/etc/rc.conf' ]            # Overkill?
697
 
        for rcfile in rcfiles:
698
 
            if os.path.isfile(rcfile):
699
 
                self.rcconf_file = rcfile
700
 
 
701
 
        self.rcconf_key = "%s" % self.name
702
 
 
703
 
        return self.service_enable_rcconf()
704
 
 
705
 
    def get_service_status(self):
706
 
        self.svc_cmd = "%s" % self.svc_initscript
707
 
        rc, stdout, stderr = self.execute_command("%s %s" % (self.svc_cmd, 'onestatus'))
708
 
        if rc == 1:
709
 
            self.running = False
710
 
        elif rc == 0:
711
 
            self.running = True
712
 
 
713
 
    def service_control(self):
714
 
        if self.action is "start":
715
 
            self.action = "onestart"
716
 
        if self.action is "stop":
717
 
            self.action = "onestop"
718
 
 
719
 
        self.svc_cmd = "%s" % self.svc_initscript
720
 
        return self.execute_command("%s %s" % (self.svc_cmd, self.action), daemonize=True)
721
 
 
722
 
# ===========================================
723
 
# Subclass: SunOS
724
 
class SunOSService(Service):
725
 
    """
726
 
    This is the SunOS Service manipulation class - it uses the svcadm
727
 
    command for controlling services, and svcs command for checking status.
728
 
    It also tries to be smart about taking the service out of maintenance
729
 
    state if necessary.
730
 
    """
731
 
    platform = 'SunOS'
732
 
    distribution = None
733
 
 
734
 
    def get_service_tools(self):
735
 
        self.svcs_cmd = self.module.get_bin_path('svcs', True)
736
 
 
737
 
        if not self.svcs_cmd:
738
 
            self.module.fail_json(msg='unable to find svcs binary')
739
 
 
740
 
        self.svcadm_cmd = self.module.get_bin_path('svcadm', True)
741
 
    
742
 
        if not self.svcadm_cmd:
743
 
            self.module.fail_json(msg='unable to find svcadm binary')
744
 
 
745
 
    def get_service_status(self):
746
 
        status = self.get_sunos_svcs_status()
747
 
        # Only 'online' is considered properly running. Everything else is off
748
 
        # or has some sort of problem.
749
 
        if status == 'online':
750
 
            self.running = True
751
 
        else:
752
 
            self.running = False
753
 
 
754
 
    def get_sunos_svcs_status(self):
755
 
        rc, stdout, stderr = self.execute_command("%s %s" % (self.svcs_cmd, self.name))
756
 
        if rc == 1:
757
 
            if stderr:
758
 
                self.module.fail_json(msg=stderr)
759
 
            else:
760
 
                self.module.fail_json(msg=stdout)
761
 
 
762
 
        lines = stdout.rstrip("\n").split("\n")
763
 
        status = lines[-1].split(" ")[0]
764
 
        # status is one of: online, offline, degraded, disabled, maintenance, uninitialized
765
 
        # see man svcs(1)
766
 
        return status
767
 
 
768
 
    def service_enable(self):
769
 
        # Get current service enablement status
770
 
        rc, stdout, stderr = self.execute_command("%s -l %s" % (self.svcs_cmd, self.name))
771
 
 
772
 
        if rc != 0:
773
 
            if stderr:
774
 
                self.module.fail_json(msg=stderr)
775
 
            else:
776
 
                self.module.fail_json(msg=stdout)
777
 
 
778
 
        enabled = False
779
 
        temporary = False
780
 
 
781
 
        # look for enabled line, which could be one of:
782
 
        #    enabled   true (temporary)
783
 
        #    enabled   false (temporary)
784
 
        #    enabled   true
785
 
        #    enabled   false
786
 
        for line in stdout.split("\n"):
787
 
            if line.find("enabled") == 0:
788
 
                if line.find("true") != -1:
789
 
                    enabled = True
790
 
                if line.find("temporary") != -1:
791
 
                    temporary = True
792
 
                
793
 
        startup_enabled = (enabled and not temporary) or (not enabled and temporary)
794
 
        
795
 
        if self.enable and startup_enabled:
796
 
            return
797
 
        elif (not self.enable) and (not startup_enabled):
798
 
            return
799
 
 
800
 
        # Mark service as started or stopped (this will have the side effect of
801
 
        # actually stopping or starting the service)
802
 
        if self.enable:
803
 
            subcmd = "enable -rs"
804
 
        else:
805
 
            subcmd = "disable -s"
806
 
 
807
 
        rc, stdout, stderr = self.execute_command("%s %s %s" % (self.svcadm_cmd, subcmd, self.name))
808
 
 
809
 
        if rc != 0:
810
 
            if stderr:
811
 
                self.module.fail_json(msg=stderr)
812
 
            else:
813
 
                self.module.fail_json(msg=stdout)
814
 
 
815
 
        self.changed = True
816
 
        
817
 
            
818
 
    def service_control(self):
819
 
        status = self.get_sunos_svcs_status()
820
 
 
821
 
        # if starting or reloading, clear maintenace states
822
 
        if self.action in ['start', 'reload', 'restart'] and status in ['maintenance', 'degraded']:
823
 
            rc, stdout, stderr = self.execute_command("%s clear %s" % (self.svcadm_cmd, self.name))
824
 
            if rc != 0:
825
 
                return rc, stdout, stderr
826
 
            status = self.get_sunos_svcs_status()
827
 
 
828
 
        if status in ['maintenance', 'degraded']:
829
 
            self.module.fail_json(msg="Failed to bring service out of %s status." % status)
830
 
 
831
 
        if self.action == 'start':
832
 
            subcmd = "enable -rst"
833
 
        elif self.action == 'stop':
834
 
            subcmd = "disable -st"
835
 
        elif self.action == 'reload':
836
 
            subcmd = "refresh"
837
 
        elif self.action == 'restart' and status == 'online':
838
 
            subcmd = "restart"
839
 
        elif self.action == 'restart' and status != 'online':
840
 
            subcmd = "enable -rst"
841
 
            
842
 
        return self.execute_command("%s %s %s" % (self.svcadm_cmd, subcmd, self.name))
843
 
 
844
 
 
845
 
# ===========================================
846
 
# Main control flow
847
 
 
848
 
def main():
849
 
    module = AnsibleModule(
850
 
        argument_spec = dict(
851
 
            name = dict(required=True),
852
 
            state = dict(choices=['running', 'started', 'stopped', 'restarted', 'reloaded']),
853
 
            pattern = dict(required=False, default=None),
854
 
            enabled = dict(choices=BOOLEANS, type='bool'),
855
 
            arguments = dict(aliases=['args'], default=''),
856
 
        ),
857
 
        supports_check_mode=True
858
 
    )
859
 
 
860
 
    service = Service(module)
861
 
 
862
 
    if service.syslogging:
863
 
        syslog.openlog('ansible-%s' % os.path.basename(__file__))
864
 
        syslog.syslog(syslog.LOG_NOTICE, 'Service instantiated - platform %s' % service.platform)
865
 
        if service.distribution:
866
 
            syslog.syslog(syslog.LOG_NOTICE, 'Service instantiated - distribution %s' % service.distribution)
867
 
 
868
 
    rc = 0
869
 
    out = ''
870
 
    err = ''
871
 
    result = {}
872
 
    result['name'] = service.name
873
 
    result['state'] = service.state
874
 
 
875
 
    # Find service management tools
876
 
    service.get_service_tools()
877
 
 
878
 
    # Enable/disable service startup at boot if requested
879
 
    if service.module.params['enabled'] is not None:
880
 
        # FIXME: ideally this should detect if we need to toggle the enablement state, though
881
 
        # it's unlikely the changed handler would need to fire in this case so it's a minor thing.
882
 
        service.service_enable()
883
 
 
884
 
    # Collect service status
885
 
    if service.pattern:
886
 
        service.check_ps()
887
 
    service.get_service_status()
888
 
 
889
 
    # Calculate if request will change service state
890
 
    service.check_service_changed()
891
 
 
892
 
    # Modify service state if necessary
893
 
    (rc, out, err) = service.modify_service_state()
894
 
 
895
 
    if rc != 0:
896
 
        if err:
897
 
            module.fail_json(msg=err)
898
 
        else:
899
 
            module.fail_json(msg=out)
900
 
 
901
 
    result['changed'] = service.changed
902
 
    if service.module.params['enabled'] is not None:
903
 
        result['enabled'] = service.module.params['enabled']
904
 
 
905
 
    if not service.module.params['state']:
906
 
        status = service.get_service_status()
907
 
        if status is None:
908
 
            result['state'] = 'absent'
909
 
        elif status is False:
910
 
            result['state'] = 'started'
911
 
        else:
912
 
            result['state'] = 'stopped'
913
 
    else:
914
 
        # as we may have just bounced the service the service command may not
915
 
        # report accurate state at this moment so just show what we ran
916
 
        if service.module.params['state'] in ['started','restarted','running']:
917
 
            result['state'] = 'started'
918
 
        else:
919
 
            result['state'] = 'stopped'
920
 
 
921
 
    module.exit_json(**result)
922
 
 
923
 
# this is magic, see lib/ansible/module_common.py
924
 
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
925
 
 
926
 
main()