Merge pull request #30381 from quantonganh/launchctl-yosemite

Launchctl yosemite
This commit is contained in:
Colton Myers 2016-01-25 16:08:25 -07:00
commit 818ff2af78
2 changed files with 108 additions and 13 deletions

View file

@ -5,27 +5,39 @@ Module for the management of MacOS systems that use launchd/launchctl
:depends: - plistlib Python module
'''
from __future__ import absolute_import
from distutils.version import LooseVersion
# Import python libs
import logging
import os
import plistlib
import re
# Import salt libs
import salt.utils
import salt.utils.decorators as decorators
import salt.ext.six as six
# Set up logging
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'service'
BEFORE_YOSEMITE = True
def __virtual__():
'''
Only work on MacOS
'''
if __grains__['os'] == 'MacOS':
if LooseVersion(__grains__['osmajorrelease']) >= '10.10':
global BEFORE_YOSEMITE
BEFORE_YOSEMITE = False
return __virtualname__
return (False, 'launchctl execution module cannot be loaded: only available on MacOS.')
return (False, 'launchctl execution module cannot be loaded: '
'only available on MacOS.')
def _launchd_paths():
@ -57,19 +69,23 @@ def _available_services():
continue
try:
# This assumes most of the plist files will be already in XML format
# This assumes most of the plist files
# will be already in XML format
with salt.utils.fopen(file_path):
plist = plistlib.readPlist(true_path)
except Exception:
# If plistlib is unable to read the file we'll need to use
# the system provided plutil program to do the conversion
cmd = '/usr/bin/plutil -convert xml1 -o - -- "{0}"'.format(true_path)
plist_xml = __salt__['cmd.run_all'](cmd, python_shell=False)['stdout']
cmd = '/usr/bin/plutil -convert xml1 -o - -- "{0}"'.format(
true_path)
plist_xml = __salt__['cmd.run_all'](
cmd, python_shell=False)['stdout']
if six.PY2:
plist = plistlib.readPlistFromString(plist_xml)
else:
plist = plistlib.readPlistFromBytes(salt.utils.to_bytes(plist_xml))
plist = plistlib.readPlistFromBytes(
salt.utils.to_bytes(plist_xml))
available_services[plist.Label.lower()] = {
'filename': filename,
@ -129,17 +145,22 @@ def get_all():
def _get_launchctl_data(job_label, runas=None):
cmd = 'launchctl list -x {0}'.format(job_label)
if BEFORE_YOSEMITE:
cmd = 'launchctl list -x {0}'.format(job_label)
else:
cmd = 'launchctl list {0}'.format(job_label)
launchctl_xml = __salt__['cmd.run_all'](cmd, python_shell=False, runas=runas)
launchctl_data = __salt__['cmd.run_all'](cmd,
python_shell=False,
runas=runas)
if launchctl_xml['stderr'] == 'launchctl list returned unknown response':
if launchctl_data['stderr']:
# The service is not loaded, further, it might not even exist
# in either case we didn't get XML to parse, so return an empty
# dict
return dict()
return None
return dict(plistlib.readPlistFromString(launchctl_xml['stdout']))
return launchctl_data['stdout']
def available(job_label):
@ -185,7 +206,14 @@ def status(job_label, runas=None):
lookup_name = service['plist']['Label'] if service else job_label
launchctl_data = _get_launchctl_data(lookup_name, runas=runas)
return 'PID' in launchctl_data
if launchctl_data:
if BEFORE_YOSEMITE:
return 'PID' in dict(plistlib.readPlistFromString(launchctl_data))
else:
pattern = '"PID" = [0-9]+;'
return True if re.search(pattern, launchctl_data) else False
else:
return False
def stop(job_label, runas=None):
@ -202,7 +230,8 @@ def stop(job_label, runas=None):
'''
service = _service_by_name(job_label)
if service:
cmd = 'launchctl unload -w {0}'.format(service['file_path'], runas=runas)
cmd = 'launchctl unload -w {0}'.format(service['file_path'],
runas=runas)
return not __salt__['cmd.retcode'](cmd, runas=runas, python_shell=False)
return False
@ -240,3 +269,47 @@ def restart(job_label, runas=None):
'''
stop(job_label, runas=runas)
return start(job_label, runas=runas)
def enabled(job_label, runas=None):
'''
Return True if the named service is enabled, false otherwise
CLI Example:
.. code-block:: bash
salt '*' service.enabled <service label>
'''
overrides_data = dict(plistlib.readPlist(
'/var/db/launchd.db/com.apple.launchd/overrides.plist'
))
if overrides_data.get(job_label, False):
if overrides_data[job_label]['Disabled']:
return False
else:
return True
else:
return False
def disabled(job_label, runas=None):
'''
Return True if the named service is disabled, false otherwise
CLI Example:
.. code-block:: bash
salt '*' service.disabled <service label>
'''
overrides_data = dict(plistlib.readPlist(
'/var/db/launchd.db/com.apple.launchd/overrides.plist'
))
if overrides_data.get(job_label, False):
if overrides_data[job_label]['Disabled']:
return True
else:
return False
else:
return True

View file

@ -58,12 +58,34 @@ class LaunchctlTestCase(TestCase):
'''
Test for Return the status for a service
'''
launchctl_data = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>salt-minion</string>
<key>LastExitStatus</key>
<integer>0</integer>
<key>LimitLoadToSessionType</key>
<string>System</string>
<key>OnDemand</key>
<false/>
<key>PID</key>
<integer>71</integer>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/salt-minion</string>
</array>
<key>TimeOut</key>
<integer>30</integer>
</dict>
</plist>'''
with patch.object(launchctl,
'_service_by_name',
return_value={'plist':
{'Label': 'A'}}):
with patch.object(launchctl, '_get_launchctl_data',
return_value={'PID': 'B'}):
return_value=launchctl_data):
self.assertTrue(launchctl.status('job_label'))
def test_stop(self):