class Chef::Provider::Service::Macosx

Constants

PLIST_DIRS

Public Class Methods

gather_plist_dirs() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 35
def self.gather_plist_dirs
  locations = %w{/Library/LaunchAgents
                 /Library/LaunchDaemons
                 /System/Library/LaunchAgents
                 /System/Library/LaunchDaemons }
  Chef::Util::PathHelper.home("Library", "LaunchAgents") { |p| locations << p }
  locations
end

Public Instance Methods

define_resource_requirements() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 73
def define_resource_requirements
  requirements.assert(:reload) do |a|
    a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support :reload"
  end

  requirements.assert(:all_actions) do |a|
    a.assertion { @plist_size < 2 }
    a.failure_message Chef::Exceptions::Service, "Several plist files match service name. Please use full service name."
  end

  requirements.assert(:all_actions) do |a|
    a.assertion { ::File.exist?(@plist.to_s) }
    a.failure_message Chef::Exceptions::Service,
      "Could not find plist for #{@new_resource}"
  end

  requirements.assert(:enable, :disable) do |a|
    a.assertion { !@service_label.to_s.empty? }
    a.failure_message Chef::Exceptions::Service,
      "Could not find service's label in plist file '#{@plist}'!"
  end

  requirements.assert(:all_actions) do |a|
    a.assertion { @plist_size > 0 }
    # No failure here in original code - so we also will not
    # fail. Instead warn that the service is potentially missing
    a.whyrun "Assuming that the service would have been previously installed and is currently disabled." do
      @current_resource.enabled(false)
      @current_resource.running(false)
    end
  end
end
disable_service() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 162
def disable_service
  unless @current_resource.enabled
    logger.debug("#{@new_resource} not enabled, not disabling")
  else
    unload_service
  end
end
enable_service() click to toggle source

On macOS, enabling a service has the side-effect of starting it, and disabling a service has the side-effect of stopping it.

This makes some sense on macOS since launchctl is an “init”-style supervisor that will restart daemons that are crashing, etc.

FIXME: Does this make any sense at all? The difference between enabled and running as state would seem to only be useful for completely broken services (enabled, not restarting, but not running => totally broken?).

It seems like otherwise :enable is equivalent to :start, and :disable is equivalent to :stop? But just with strangely different behavior in the face of a broken service?

# File lib/chef/provider/service/macosx.rb, line 154
def enable_service
  if @current_resource.enabled
    logger.debug("#{@new_resource} already enabled, not enabling")
  else
    load_service
  end
end
load_current_resource() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 46
def load_current_resource
  @current_resource = Chef::Resource::MacosxService.new(@new_resource.name)
  @current_resource.service_name(@new_resource.service_name)
  @plist_size = 0
  @plist = @new_resource.plist || find_service_plist
  @service_label = find_service_label
  # LaunchAgents should be loaded as the console user.
  @console_user = @plist ? @plist.include?("LaunchAgents") : false
  @session_type = @new_resource.session_type

  if @console_user
    @console_user = Etc.getpwuid(::File.stat("/dev/console").uid).name
    logger.trace("#{new_resource} console_user: '#{@console_user}'")

    @base_user_cmd = "su -l #{@console_user} -c"
    logger.trace("#{new_resource} base_user_cmd: '#{@base_user_cmd}'")

    # Default LaunchAgent session should be Aqua
    @session_type = "Aqua" if @session_type.nil?
  end

  logger.trace("#{new_resource} Plist: '#{@plist}' service_label: '#{@service_label}'")
  set_service_status

  @current_resource
end
load_service() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 170
def load_service
  session = @session_type ? "-S #{@session_type} " : ""
  cmd = "/bin/launchctl load -w " + session + @plist
  shell_out_as_user(cmd)
end
restart_service() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 130
def restart_service
  if @new_resource.restart_command
    super
  else
    unload_service
    sleep 1
    load_service
  end
end
set_service_status() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 190
def set_service_status
  return if @plist.nil? || @service_label.to_s.empty?

  cmd = "/bin/launchctl list #{@service_label}"
  res = shell_out_as_user(cmd)

  if res.exitstatus == 0
    @current_resource.enabled(true)
  else
    @current_resource.enabled(false)
  end

  if @current_resource.enabled
    res.stdout.each_line do |line|
      case line.downcase
      when /\s+\"pid\"\s+=\s+(\d+).*/
        pid = $1
        @current_resource.running(pid.to_i != 0)
        logger.trace("Current PID for #{@service_label} is #{pid}")
      end
    end
  else
    @current_resource.running(false)
  end
end
shell_out_as_user(cmd) click to toggle source
# File lib/chef/provider/service/macosx.rb, line 181
def shell_out_as_user(cmd)
  if @console_user
    shell_out("#{@base_user_cmd} '#{cmd}'", default_env: false)
  else
    shell_out(cmd, default_env: false)

  end
end
start_service() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 106
def start_service
  if @current_resource.running
    logger.debug("#{@new_resource} already running, not starting")
  else
    if @new_resource.start_command
      super
    else
      load_service
    end
  end
end
stop_service() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 118
def stop_service
  unless @current_resource.running
    logger.debug("#{@new_resource} not running, not stopping")
  else
    if @new_resource.stop_command
      super
    else
      unload_service
    end
  end
end
unload_service() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 176
def unload_service
  cmd = "/bin/launchctl unload -w " + @plist
  shell_out_as_user(cmd)
end

Private Instance Methods

find_service_label() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 218
def find_service_label
  # CHEF-5223 "you can't glob for a file that hasn't been converged
  # onto the node yet."
  return nil if @plist.nil?

  # Plist must exist by this point
  raise Chef::Exceptions::FileNotFound, "Cannot find #{@plist}!" unless ::File.exist?(@plist)

  # Most services have the same internal label as the name of the
  # plist file. However, there is no rule saying that *has* to be
  # the case, and some core services (notably, ssh) do not follow
  # this rule.

  # plist files can come in XML or Binary formats. this command
  # will make sure we get XML every time.
  plist_xml = shell_out!(
    "plutil -convert xml1 -o - #{@plist}",
    default_env: false
  ).stdout

  plist_doc = REXML::Document.new(plist_xml)
  plist_doc.elements[
    "/plist/dict/key[text()='Label']/following::string[1]/text()"]
end
find_service_plist() click to toggle source
# File lib/chef/provider/service/macosx.rb, line 243
def find_service_plist
  plists = PLIST_DIRS.inject([]) do |results, dir|
    edir = ::File.expand_path(dir)
    entries = Dir.glob(
      "#{edir}/*#{Chef::Util::PathHelper.escape_glob_dir(@current_resource.service_name)}*.plist"
    )
    entries.any? ? results << entries : results
  end
  plists.flatten!
  @plist_size = plists.size
  plists.first
end