Merge pull request #47256 from weswhet/add-mac_service-update

add macOS mac_service update.
This commit is contained in:
Nicole Thomas 2018-05-22 11:03:20 -04:00 committed by GitHub
commit de84f6fbd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 149 additions and 22 deletions

View File

@ -2,6 +2,24 @@
'''
The service module for macOS
.. versionadded:: 2016.3.0
This module has support for services in the following locations.
.. code-block:: bash
/System/Library/LaunchDaemons/
/System/Library/LaunchAgents/
/Library/LaunchDaemons/
/Library/LaunchAgents/
# As of version "Fluorine" support for user-specific services were added.
/Users/foo/Library/LaunchAgents/
.. note::
As of version "Fluorine", if a service is located in a ``LaunchAgent`` path
and a ``runas`` user is NOT specified the current console user will be used
to properly interact with the service.
'''
from __future__ import absolute_import, unicode_literals, print_function
@ -62,7 +80,7 @@ def _get_service(name):
:return: The service information for the service, otherwise an Error
:rtype: dict
'''
services = salt.utils.mac_utils.available_services()
services = __utils__['mac_utils.available_services']()
name = name.lower()
if name in services:
@ -113,6 +131,68 @@ def _always_running_service(name):
return False
def _get_domain_target(name, service_target=False):
'''
Returns the domain/service target and path for a service. This is used to
determine whether or not a service should be loaded in a user space or
system space.
:param str name: Service label, file name, or full path
:param bool service_target: Whether to return a full
service target. This is needed for the enable and disable
subcommands of /bin/launchctl. Defaults to False
:return: Tuple of the domain/service target and the path to the service.
:rtype: tuple
.. versionadded:: Fluorine
'''
# Get service information
service = _get_service(name)
# get the path to the service
path = service['file_path']
# most of the time we'll be at the system level.
domain_target = 'system'
# check if a LaunchAgent as we should treat these differently.
if 'LaunchAgents' in path:
# Get the console user so we can service in the correct session
uid = __utils__['mac_utils.console_user']()
domain_target = 'gui/{}'.format(uid)
# check to see if we need to make it a full service target.
if service_target is True:
domain_target = '{}/{}'.format(domain_target, service['plist']['Label'])
return (domain_target, path)
def _launch_agent(name):
'''
Checks to see if the provided service is a LaunchAgent
:param str name: Service label, file name, or full path
:return: True if a LaunchAgent, False if not.
:rtype: bool
.. versionadded:: Fluorine
'''
# Get the path to the service.
path = _get_service(name)['file_path']
if 'LaunchAgents' not in path:
return False
return True
def show(name):
'''
Show properties of a launchctl service
@ -158,7 +238,7 @@ def launchctl(sub_cmd, *args, **kwargs):
salt '*' service.launchctl debug org.cups.cupsd
'''
return salt.utils.mac_utils.launchctl(sub_cmd, *args, **kwargs)
return __utils__['mac_utils.launchctl'](sub_cmd, *args, **kwargs)
def list_(name=None, runas=None):
@ -185,6 +265,11 @@ def list_(name=None, runas=None):
service = _get_service(name)
label = service['plist']['Label']
# we can assume if we are trying to list a LaunchAgent we need
# to run as a user, if not provided, we'll use the console user.
if not runas and _launch_agent(name):
runas = __utils__['mac_utils.console_user'](username=True)
# Collect information on service: will raise an error if it fails
return launchctl('list',
label,
@ -216,12 +301,11 @@ def enable(name, runas=None):
salt '*' service.enable org.cups.cupsd
'''
# Get service information and label
service = _get_service(name)
label = service['plist']['Label']
# Get the domain target. enable requires a full <service-target>
service_target = _get_domain_target(name, service_target=True)[0]
# Enable the service: will raise an error if it fails
return launchctl('enable', 'system/{0}'.format(label), runas=runas)
return launchctl('enable', service_target, runas=runas)
def disable(name, runas=None):
@ -242,12 +326,11 @@ def disable(name, runas=None):
salt '*' service.disable org.cups.cupsd
'''
# Get service information and label
service = _get_service(name)
label = service['plist']['Label']
# Get the service target. enable requires a full <service-target>
service_target = _get_domain_target(name, service_target=True)[0]
# disable the service: will raise an error if it fails
return launchctl('disable', 'system/{0}'.format(label), runas=runas)
return launchctl('disable', service_target, runas=runas)
def start(name, runas=None):
@ -271,12 +354,11 @@ def start(name, runas=None):
salt '*' service.start org.cups.cupsd
'''
# Get service information and file path
service = _get_service(name)
path = service['file_path']
# Get the domain target.
domain_target, path = _get_domain_target(name)
# Load the service: will raise an error if it fails
return launchctl('load', path, runas=runas)
# Load (bootstrap) the service: will raise an error if it fails
return launchctl('bootstrap', domain_target, path, runas=runas)
def stop(name, runas=None):
@ -301,12 +383,11 @@ def stop(name, runas=None):
salt '*' service.stop org.cups.cupsd
'''
# Get service information and file path
service = _get_service(name)
path = service['file_path']
# Get the domain target.
domain_target, path = _get_domain_target(name)
# Disable the Launch Daemon: will raise an error if it fails
return launchctl('unload', path, runas=runas)
# Stop (bootout) the service: will raise an error if it fails
return launchctl('bootout', domain_target, path, runas=runas)
def restart(name, runas=None):
@ -368,6 +449,9 @@ def status(name, sig=None, runas=None):
if not _always_running_service(name) and enabled(name):
return 'loaded'
if not runas and _launch_agent(name):
runas = __utils__['mac_utils.console_user'](username=True)
output = list_(runas=runas)
# Used a string here instead of a list because that's what the linux version
@ -493,7 +577,7 @@ def get_all(runas=None):
enabled = get_enabled(runas=runas)
# Get list of all services
available = list(salt.utils.mac_utils.available_services().keys())
available = list(__utils__['mac_utils.available_services']().keys())
# Return composite list
return sorted(set(enabled + available))
@ -514,7 +598,6 @@ def get_enabled(runas=None):
.. code-block:: bash
salt '*' service.get_enabled
salt '*' service.get_enabled running=True
'''
# Collect list of enabled services
stdout = list_(runas=runas)

View File

@ -11,6 +11,7 @@ import subprocess
import os
import plistlib
import time
import pwd
# Import Salt Libs
import salt.modules.cmdmod
@ -294,6 +295,15 @@ def _available_services():
'/System/Library/LaunchAgents',
'/System/Library/LaunchDaemons',
]
try:
for user in os.listdir('/Users/'):
agent_path = '/Users/{}/Library/LaunchAgents/'.format(user)
if os.path.isdir(agent_path):
launchd_paths.append(agent_path)
except OSError:
pass
_available_services = dict()
for launch_dir in launchd_paths:
for root, dirs, files in salt.utils.path.os_walk(launch_dir):
@ -359,3 +369,37 @@ def available_services():
salt.utils.mac_service.available_services()
'''
return _available_services()
def console_user(username=False):
'''
Gets the UID or Username of the current console user.
:return: The uid or username of the console user.
:param bool username: Whether to return the username of the console
user instead of the UID. Defaults to False
:rtype: Interger of the UID, or a string of the username.
Raises:
CommandExecutionError: If we fail to get the UID.
CLI Example:
.. code-block:: bash
import salt.utils.mac_service
salt.utils.mac_service.console_user()
'''
try:
# returns the 'st_uid' stat from the /dev/console file.
uid = os.stat('/dev/console')[4]
except (OSError, IndexError):
# we should never get here but raise an error if so
raise CommandExecutionError('Failed to get a UID for the console user.')
if username:
return pwd.getpwuid(uid)[0]
return uid