File Manager
Viewing File: upstart.rb
#
# Author:: Bryan McLellan <btm@loftninjas.org>
# Copyright:: Copyright 2010-2016, Bryan McLellan
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require_relative "../../resource/service"
require_relative "simple"
require_relative "../../util/file_edit"
class Chef
class Provider
class Service
class Upstart < Chef::Provider::Service::Simple
# to maintain a local state of service across restart's internal calls
attr_accessor :upstart_service_running
provides :service, platform_family: "debian", override: true do
upstart?
end
UPSTART_STATE_FORMAT = %r{\S+ \(?(start|stop)?\)? ?[/ ](\w+)}.freeze
# Returns true if the configs for the service name has upstart variable
def self.supports?(resource, action)
service_script_exist?(:upstart, resource.service_name)
end
# Upstart does more than start or stop a service, creating multiple 'states' [1] that a service can be in.
# In chef, when we ask a service to start, we expect it to have started before performing the next step
# since we have top down dependencies. Which is to say we may follow with a resource next that requires
# that service to be running. According to [2] we can trust that sending a 'goal' such as start will not
# return until that 'goal' is reached, or some error has occurred.
#
# [1] http://upstart.ubuntu.com/wiki/JobStates
# [2] http://www.netsplit.com/2008/04/27/upstart-05-events/
def initialize(new_resource, run_context)
# TODO: re-evaluate if this is needed after integrating cookbook fix
raise ArgumentError, "run_context cannot be nil" unless run_context
super
run_context.node
# dup so we can mutate @job
@job = @new_resource.service_name.dup
if @new_resource.parameters
@new_resource.parameters.each do |key, value|
@job << " #{key}=#{value}"
end
end
platform, version = Chef::Platform.find_platform_and_version(run_context.node)
if platform == "ubuntu" && (8.04..9.04).cover?(version.to_f)
@upstart_job_dir = "/etc/event.d"
@upstart_conf_suffix = ""
else
@upstart_job_dir = "/etc/init"
@upstart_conf_suffix = ".conf"
end
@command_success = true # new_resource.status_command= false, means upstart used
@config_file_found = true
@upstart_command_success = true
end
def define_resource_requirements
# Do not call super, only call shared requirements
shared_resource_requirements
requirements.assert(:all_actions) do |a|
unless @command_success
whyrun_msg = if @new_resource.status_command
"Provided status command #{@new_resource.status_command} failed."
else
"Could not determine upstart state for service"
end
end
a.assertion { @command_success }
# no failure here, just document the assumptions made.
a.whyrun "#{whyrun_msg} Assuming service installed and not running."
end
requirements.assert(:all_actions) do |a|
a.assertion { @config_file_found }
# no failure here, just document the assumptions made.
a.whyrun "Could not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}. Assuming service is disabled."
end
end
def load_current_resource
@current_resource = Chef::Resource::Service.new(@new_resource.name)
@current_resource.service_name(@new_resource.service_name)
# Get running/stopped state
# We do not support searching for a service via ps when using upstart since status is a native
# upstart function. We will however support status_command in case someone wants to do something special.
if @new_resource.status_command
logger.trace("#{@new_resource} you have specified a status command, running..")
begin
if shell_out!(@new_resource.status_command).exitstatus == 0
@upstart_service_running = true
end
rescue
@command_success = false
@upstart_service_running = false
nil
end
else
begin
if upstart_goal_state == "start"
@upstart_service_running = true
else
@upstart_service_running = false
end
rescue Chef::Exceptions::Exec
@command_success = false
@upstart_service_running = false
nil
end
end
# Get enabled/disabled state by reading job configuration file
if ::File.exists?("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
logger.trace("#{@new_resource} found #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
::File.open("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}", "r") do |file|
while line = file.gets
case line
when /^start on/
logger.trace("#{@new_resource} enabled: #{line.chomp}")
@current_resource.enabled true
break
when /^#start on/
logger.trace("#{@new_resource} disabled: #{line.chomp}")
@current_resource.enabled false
break
end
end
end
else
@config_file_found = false
logger.trace("#{@new_resource} did not find #{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
@current_resource.enabled false
end
@current_resource.running @upstart_service_running
@current_resource
end
def start_service
# Calling start on a service that is already started will return 1
# Our 'goal' when we call start is to ensure the service is started
if @upstart_service_running
logger.trace("#{@new_resource} already running, not starting")
else
if @new_resource.start_command
super
else
shell_out!("/sbin/start #{@job}", default_env: false)
end
end
@upstart_service_running = true
end
def stop_service
# Calling stop on a service that is already stopped will return 1
# Our 'goal' when we call stop is to ensure the service is stopped
unless @upstart_service_running
logger.trace("#{@new_resource} not running, not stopping")
else
if @new_resource.stop_command
super
else
shell_out!("/sbin/stop #{@job}", default_env: false)
end
end
@upstart_service_running = false
end
def restart_service
if @new_resource.restart_command
super
# Upstart always provides restart functionality so we don't need to mimic it with stop/sleep/start.
# Older versions of upstart would fail on restart if the service was currently stopped, check for that. LP:430883
# But for safe working of latest upstart job config being loaded, 'restart' can't be used as per link
# http://upstart.ubuntu.com/cookbook/#restart (it doesn't uses latest jon config from disk but retains old)
else
if @upstart_service_running
stop_service
sleep 1
start_service
else
start_service
end
end
@upstart_service_running = true
end
def reload_service
if @new_resource.reload_command
super
else
# upstart >= 0.6.3-4 supports reload (HUP)
shell_out!("/sbin/reload #{@job}", default_env: false)
end
@upstart_service_running = true
end
# https://bugs.launchpad.net/upstart/+bug/94065
def enable_service
logger.trace("#{@new_resource} upstart lacks inherent support for enabling services, editing job config file")
conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
conf.search_file_replace(/^#start on/, "start on")
conf.write_file
end
def disable_service
logger.trace("#{@new_resource} upstart lacks inherent support for disabling services, editing job config file")
conf = Chef::Util::FileEdit.new("#{@upstart_job_dir}/#{@new_resource.service_name}#{@upstart_conf_suffix}")
conf.search_file_replace(/^start on/, "#start on")
conf.write_file
end
def upstart_goal_state
command = "/sbin/status #{@job}"
so = shell_out(command)
so.stdout.each_line do |line|
# service goal/state
# OR
# service (instance) goal/state
# OR
# service (goal) state
line =~ UPSTART_STATE_FORMAT
data = Regexp.last_match
return data[1]
end
end
end
end
end
end