551 lines
19 KiB
Ruby
551 lines
19 KiB
Ruby
#
|
|
# Cookbook Name:: runit
|
|
# Provider:: service
|
|
#
|
|
# Copyright 2011, Joshua Timberman
|
|
# Copyright 2011, Chef Software, Inc.
|
|
#
|
|
# 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 'chef/provider/service'
|
|
require 'chef/provider/link'
|
|
require 'chef/resource/link'
|
|
require 'chef/provider/directory'
|
|
require 'chef/resource/directory'
|
|
require 'chef/provider/template'
|
|
require 'chef/resource/template'
|
|
require 'chef/provider/file'
|
|
require 'chef/resource/file'
|
|
require 'chef/mixin/shell_out'
|
|
require 'chef/mixin/language'
|
|
|
|
class Chef
|
|
class Provider
|
|
class Service
|
|
class Runit < Chef::Provider::Service
|
|
# refactor this whole thing into a Chef11 LWRP
|
|
include Chef::Mixin::ShellOut
|
|
|
|
def initialize(*args)
|
|
super
|
|
new_resource.supports[:status] = true
|
|
end
|
|
|
|
def load_current_resource
|
|
@current_resource = Chef::Resource::RunitService.new(new_resource.name)
|
|
current_resource.service_name(new_resource.service_name)
|
|
|
|
Chef::Log.debug("Checking status of service #{new_resource.service_name}")
|
|
|
|
# verify Runit was installed properly
|
|
unless ::File.exist?(new_resource.sv_bin) && ::File.executable?(new_resource.sv_bin)
|
|
no_runit_message = "Could not locate main runit sv_bin at \"#{new_resource.sv_bin}\". "
|
|
no_runit_message << "Did you remember to install runit before declaring a \"runit_service\" resource? "
|
|
no_runit_message << "\n\nTry adding the following to the top of your recipe:\n\ninclude_recipe \"runit\""
|
|
fail no_runit_message
|
|
end
|
|
|
|
current_resource.running(running?)
|
|
current_resource.enabled(enabled?)
|
|
current_resource.env(get_current_env)
|
|
current_resource
|
|
end
|
|
|
|
#
|
|
# Chef::Provider::Service overrides
|
|
#
|
|
|
|
def action_create
|
|
configure_service # Do this every run, even if service is already enabled and running
|
|
Chef::Log.info("#{new_resource} configured")
|
|
end
|
|
|
|
def action_enable
|
|
configure_service # Do this every run, even if service is already enabled and running
|
|
Chef::Log.info("#{new_resource} configured")
|
|
if current_resource.enabled
|
|
Chef::Log.debug("#{new_resource} already enabled - nothing to do")
|
|
else
|
|
enable_service
|
|
Chef::Log.info("#{new_resource} enabled")
|
|
end
|
|
load_new_resource_state
|
|
new_resource.enabled(true)
|
|
restart_service if new_resource.restart_on_update && run_script.updated_by_last_action?
|
|
restart_log_service if new_resource.restart_on_update && log_run_script.updated_by_last_action?
|
|
restart_log_service if new_resource.restart_on_update && log_config_file.updated_by_last_action?
|
|
end
|
|
|
|
def configure_service
|
|
if new_resource.sv_templates
|
|
Chef::Log.debug("Creating sv_dir for #{new_resource.service_name}")
|
|
do_action(sv_dir, :create)
|
|
Chef::Log.debug("Creating run_script for #{new_resource.service_name}")
|
|
do_action(run_script, :create)
|
|
|
|
if new_resource.log
|
|
Chef::Log.debug("Setting up svlog for #{new_resource.service_name}")
|
|
do_action(log_dir, :create)
|
|
do_action(log_main_dir, :create)
|
|
do_action(default_log_dir, :create) if new_resource.default_logger
|
|
do_action(log_run_script, :create)
|
|
do_action(log_config_file, :create)
|
|
else
|
|
Chef::Log.debug("log not specified for #{new_resource.service_name}, continuing")
|
|
end
|
|
|
|
unless new_resource.env.empty?
|
|
Chef::Log.debug("Setting up environment files for #{new_resource.service_name}")
|
|
do_action(env_dir, :create)
|
|
env_files.each do |file|
|
|
file.action.each { |action| do_action(file, action) }
|
|
end
|
|
else
|
|
Chef::Log.debug("Environment not specified for #{new_resource.service_name}, continuing")
|
|
end
|
|
|
|
if new_resource.check
|
|
Chef::Log.debug("Creating check script for #{new_resource.service_name}")
|
|
do_action(check_script, :create)
|
|
else
|
|
Chef::Log.debug("Check script not specified for #{new_resource.service_name}, continuing")
|
|
end
|
|
|
|
if new_resource.finish
|
|
Chef::Log.debug("Creating finish script for #{new_resource.service_name}")
|
|
do_action(finish_script, :create)
|
|
else
|
|
Chef::Log.debug("Finish script not specified for #{new_resource.service_name}, continuing")
|
|
end
|
|
|
|
unless new_resource.control.empty?
|
|
Chef::Log.debug("Creating control signal scripts for #{new_resource.service_name}")
|
|
do_action(control_dir, :create)
|
|
control_signal_files.each { |file| do_action(file, :create) }
|
|
else
|
|
Chef::Log.debug("Control signals not specified for #{new_resource.service_name}, continuing")
|
|
end
|
|
end
|
|
|
|
Chef::Log.debug("Creating lsb_init compatible interface #{new_resource.service_name}")
|
|
do_action(lsb_init, :create)
|
|
end
|
|
|
|
def enable_service
|
|
Chef::Log.debug("Creating symlink in service_dir for #{new_resource.service_name}")
|
|
do_action(service_link, :create)
|
|
|
|
unless inside_docker?
|
|
Chef::Log.debug("waiting until named pipe #{service_dir_name}/supervise/ok exists.")
|
|
until ::FileTest.pipe?("#{service_dir_name}/supervise/ok")
|
|
sleep 1
|
|
Chef::Log.debug('.')
|
|
end
|
|
|
|
if new_resource.log
|
|
Chef::Log.debug("waiting until named pipe #{service_dir_name}/log/supervise/ok exists.")
|
|
until ::FileTest.pipe?("#{service_dir_name}/log/supervise/ok")
|
|
sleep 1
|
|
Chef::Log.debug('.')
|
|
end
|
|
end
|
|
else
|
|
Chef::Log.debug("skipping */supervise/ok check inside docker")
|
|
end
|
|
end
|
|
|
|
def disable_service
|
|
shell_out("#{new_resource.sv_bin} #{sv_args}down #{service_dir_name}")
|
|
Chef::Log.debug("#{new_resource} down")
|
|
FileUtils.rm(service_dir_name)
|
|
Chef::Log.debug("#{new_resource} service symlink removed")
|
|
end
|
|
|
|
def start_service
|
|
shell_out!("#{new_resource.sv_bin} #{sv_args}start #{service_dir_name}")
|
|
end
|
|
|
|
def stop_service
|
|
shell_out!("#{new_resource.sv_bin} #{sv_args}stop #{service_dir_name}")
|
|
end
|
|
|
|
def restart_service
|
|
shell_out!("#{new_resource.sv_bin} #{sv_args}restart #{service_dir_name}")
|
|
end
|
|
|
|
def restart_log_service
|
|
shell_out!("#{new_resource.sv_bin} #{sv_args}restart #{service_dir_name}/log")
|
|
end
|
|
|
|
def reload_service
|
|
shell_out!("#{new_resource.sv_bin} #{sv_args}force-reload #{service_dir_name}")
|
|
end
|
|
|
|
def reload_log_service
|
|
shell_out!("#{new_resource.sv_bin} #{sv_args}force-reload #{service_dir_name}/log")
|
|
end
|
|
|
|
#
|
|
# Addtional Runit-only actions
|
|
#
|
|
|
|
# only take action if the service is running
|
|
[:down, :hup, :int, :term, :kill, :quit].each do |signal|
|
|
define_method "action_#{signal}".to_sym do
|
|
if current_resource.running
|
|
runit_send_signal(signal)
|
|
else
|
|
Chef::Log.debug("#{new_resource} not running - nothing to do")
|
|
end
|
|
end
|
|
end
|
|
|
|
# only take action if service is *not* running
|
|
[:up, :once, :cont].each do |signal|
|
|
define_method "action_#{signal}".to_sym do
|
|
if current_resource.running
|
|
Chef::Log.debug("#{new_resource} already running - nothing to do")
|
|
else
|
|
runit_send_signal(signal)
|
|
end
|
|
end
|
|
end
|
|
|
|
def action_usr1
|
|
runit_send_signal(1, :usr1)
|
|
end
|
|
|
|
def action_usr2
|
|
runit_send_signal(2, :usr2)
|
|
end
|
|
|
|
private
|
|
|
|
def runit_send_signal(signal, friendly_name = nil)
|
|
friendly_name ||= signal
|
|
converge_by("send #{friendly_name} to #{new_resource}") do
|
|
shell_out!("#{new_resource.sv_bin} #{sv_args}#{signal} #{service_dir_name}")
|
|
Chef::Log.info("#{new_resource} sent #{friendly_name}")
|
|
end
|
|
end
|
|
|
|
def running?
|
|
cmd = shell_out("#{new_resource.sv_bin} #{sv_args}status #{service_dir_name}")
|
|
(cmd.stdout =~ /^run:/ && cmd.exitstatus == 0)
|
|
end
|
|
|
|
def log_running?
|
|
cmd = shell_out("#{new_resource.sv_bin} #{sv_args}status #{service_dir_name}/log")
|
|
(cmd.stdout =~ /^run:/ && cmd.exitstatus == 0)
|
|
end
|
|
|
|
def enabled?
|
|
::File.exists?(::File.join(service_dir_name, 'run'))
|
|
end
|
|
|
|
def log_service_name
|
|
::File.join(new_resource.service_name, 'log')
|
|
end
|
|
|
|
def sv_dir_name
|
|
::File.join(new_resource.sv_dir, new_resource.service_name)
|
|
end
|
|
|
|
def sv_args
|
|
sv_args = ''
|
|
sv_args += "-w '#{new_resource.sv_timeout}' " unless new_resource.sv_timeout.nil?
|
|
sv_args += '-v ' if new_resource.sv_verbose
|
|
sv_args
|
|
end
|
|
|
|
def service_dir_name
|
|
::File.join(new_resource.service_dir, new_resource.service_name)
|
|
end
|
|
|
|
def log_dir_name
|
|
::File.join(new_resource.service_dir, new_resource.service_name, log)
|
|
end
|
|
|
|
def template_cookbook
|
|
new_resource.cookbook.nil? ? new_resource.cookbook_name.to_s : new_resource.cookbook
|
|
end
|
|
|
|
def default_logger_content
|
|
"#!/bin/sh
|
|
exec svlogd -tt /var/log/#{new_resource.service_name}"
|
|
end
|
|
|
|
#
|
|
# Helper Resources
|
|
#
|
|
def do_action(resource, action)
|
|
resource.run_action(action)
|
|
new_resource.updated_by_last_action(true) if resource.updated_by_last_action?
|
|
end
|
|
|
|
def sv_dir
|
|
@sv_dir ||=
|
|
begin
|
|
d = Chef::Resource::Directory.new(sv_dir_name, run_context)
|
|
d.recursive(true)
|
|
d.owner(new_resource.owner)
|
|
d.group(new_resource.group)
|
|
d.mode(00755)
|
|
d
|
|
end
|
|
end
|
|
|
|
def run_script
|
|
@run_script ||=
|
|
begin
|
|
t = Chef::Resource::Template.new(::File.join(sv_dir_name, 'run'), run_context)
|
|
t.owner(new_resource.owner)
|
|
t.group(new_resource.group)
|
|
t.source("sv-#{new_resource.run_template_name}-run.erb")
|
|
t.cookbook(template_cookbook)
|
|
t.mode(00755)
|
|
t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?)
|
|
t
|
|
end
|
|
end
|
|
|
|
def log_dir
|
|
@log_dir ||=
|
|
begin
|
|
d = Chef::Resource::Directory.new(::File.join(sv_dir_name, 'log'), run_context)
|
|
d.recursive(true)
|
|
d.owner(new_resource.owner)
|
|
d.group(new_resource.group)
|
|
d.mode(00755)
|
|
d
|
|
end
|
|
end
|
|
|
|
def log_main_dir
|
|
@log_main_dir ||=
|
|
begin
|
|
d = Chef::Resource::Directory.new(::File.join(sv_dir_name, 'log', 'main'), run_context)
|
|
d.recursive(true)
|
|
d.owner(new_resource.owner)
|
|
d.group(new_resource.group)
|
|
d.mode(00755)
|
|
d
|
|
end
|
|
end
|
|
|
|
def default_log_dir
|
|
@default_log_dir ||=
|
|
begin
|
|
d = Chef::Resource::Directory.new(::File.join("/var/log/#{new_resource.service_name}"), run_context)
|
|
d.recursive(true)
|
|
d.owner(new_resource.owner)
|
|
d.group(new_resource.group)
|
|
d.mode(00755)
|
|
d
|
|
end
|
|
end
|
|
|
|
def log_run_script
|
|
@log_run_script ||=
|
|
begin
|
|
if new_resource.default_logger
|
|
f = Chef::Resource::File.new(
|
|
::File.join(sv_dir_name, 'log', 'run'),
|
|
run_context
|
|
)
|
|
f.content(default_logger_content)
|
|
f.owner(new_resource.owner)
|
|
f.group(new_resource.group)
|
|
f.mode(00755)
|
|
f
|
|
else
|
|
t = Chef::Resource::Template.new(
|
|
::File.join(sv_dir_name, 'log', 'run'),
|
|
run_context
|
|
)
|
|
t.owner(new_resource.owner)
|
|
t.group(new_resource.group)
|
|
t.mode(00755)
|
|
t.source("sv-#{new_resource.log_template_name}-log-run.erb")
|
|
t.cookbook(template_cookbook)
|
|
t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?)
|
|
t
|
|
end
|
|
end
|
|
end
|
|
|
|
def log_config_file
|
|
@log_config_file ||=
|
|
begin
|
|
t = Chef::Resource::Template.new(::File.join(sv_dir_name, 'log', 'config'), run_context)
|
|
t.owner(new_resource.owner)
|
|
t.group(new_resource.group)
|
|
t.mode(00644)
|
|
t.cookbook('runit')
|
|
t.source('log-config.erb')
|
|
t.variables(
|
|
:size => new_resource.log_size,
|
|
:num => new_resource.log_num,
|
|
:min => new_resource.log_min,
|
|
:timeout => new_resource.log_timeout,
|
|
:processor => new_resource.log_processor,
|
|
:socket => new_resource.log_socket,
|
|
:prefix => new_resource.log_prefix,
|
|
:append => new_resource.log_config_append
|
|
)
|
|
t
|
|
end
|
|
end
|
|
|
|
def env_dir
|
|
@env_dir ||=
|
|
begin
|
|
d = Chef::Resource::Directory.new(::File.join(sv_dir_name, 'env'), run_context)
|
|
d.owner(new_resource.owner)
|
|
d.group(new_resource.group)
|
|
d.mode(00755)
|
|
d
|
|
end
|
|
end
|
|
|
|
def env_files
|
|
@env_files ||=
|
|
begin
|
|
create_files = new_resource.env.map do |var, value|
|
|
f = Chef::Resource::File.new(::File.join(sv_dir_name, 'env', var), run_context)
|
|
f.owner(new_resource.owner)
|
|
f.group(new_resource.group)
|
|
f.content(value)
|
|
f.action(:create)
|
|
f
|
|
end
|
|
extra_env = current_resource.env.reject { |k,_| new_resource.env.key?(k) }
|
|
delete_files = extra_env.map do |k,_|
|
|
f = Chef::Resource::File.new(::File.join(sv_dir_name, 'env', k), run_context)
|
|
f.action(:delete)
|
|
f
|
|
end
|
|
create_files + delete_files
|
|
end
|
|
end
|
|
|
|
def get_current_env
|
|
env_dir = ::File.join(sv_dir_name, 'env')
|
|
return {} unless ::File.directory? env_dir
|
|
files = ::Dir.glob(::File.join(env_dir,'*'))
|
|
env = files.reduce({}) do |c,o|
|
|
contents = ::IO.read(o).rstrip
|
|
c.merge!(::File.basename(o) => contents)
|
|
end
|
|
env
|
|
end
|
|
|
|
def check_script
|
|
@check_script ||=
|
|
begin
|
|
t = Chef::Resource::Template.new(::File.join(sv_dir_name, 'check'), run_context)
|
|
t.owner(new_resource.owner)
|
|
t.group(new_resource.group)
|
|
t.source("sv-#{new_resource.check_script_template_name}-check.erb")
|
|
t.cookbook(template_cookbook)
|
|
t.mode(00755)
|
|
t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?)
|
|
t
|
|
end
|
|
end
|
|
|
|
def finish_script
|
|
@finish_script ||=
|
|
begin
|
|
t = Chef::Resource::Template.new(::File.join(sv_dir_name, 'finish'), run_context)
|
|
t.owner(new_resource.owner)
|
|
t.group(new_resource.group)
|
|
t.mode(00755)
|
|
t.source("sv-#{new_resource.finish_script_template_name}-finish.erb")
|
|
t.cookbook(template_cookbook)
|
|
t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?)
|
|
t
|
|
end
|
|
end
|
|
|
|
def control_dir
|
|
@control_dir ||=
|
|
begin
|
|
d = Chef::Resource::Directory.new(::File.join(sv_dir_name, 'control'), run_context)
|
|
d.owner(new_resource.owner)
|
|
d.group(new_resource.group)
|
|
d.mode(00755)
|
|
d
|
|
end
|
|
end
|
|
|
|
def control_signal_files
|
|
@control_signal_files ||=
|
|
begin
|
|
new_resource.control.map do |signal|
|
|
t = Chef::Resource::Template.new(
|
|
::File.join(sv_dir_name, 'control', signal),
|
|
run_context
|
|
)
|
|
t.owner(new_resource.owner)
|
|
t.group(new_resource.group)
|
|
t.mode(00755)
|
|
t.source("sv-#{new_resource.control_template_names[signal]}-#{signal}.erb")
|
|
t.cookbook(template_cookbook)
|
|
t.variables(:options => new_resource.options) if new_resource.options.respond_to?(:has_key?)
|
|
t
|
|
end
|
|
end
|
|
end
|
|
|
|
def lsb_init
|
|
@lsb_init ||=
|
|
begin
|
|
initfile = ::File.join(new_resource.lsb_init_dir, new_resource.service_name)
|
|
if node['platform'] == 'debian'
|
|
::File.unlink(initfile) if ::File.symlink?(initfile)
|
|
t = Chef::Resource::Template.new(initfile, run_context)
|
|
t.owner('root')
|
|
t.group('root')
|
|
t.mode(00755)
|
|
t.cookbook('runit')
|
|
t.source('init.d.erb')
|
|
t.variables(:name => new_resource.service_name)
|
|
t
|
|
else
|
|
l = Chef::Resource::Link.new(initfile, run_context)
|
|
l.to(new_resource.sv_bin)
|
|
l
|
|
end
|
|
end
|
|
end
|
|
|
|
def service_link
|
|
@service_link ||=
|
|
begin
|
|
l = Chef::Resource::Link.new(::File.join(service_dir_name), run_context)
|
|
l.to(sv_dir_name)
|
|
l
|
|
end
|
|
end
|
|
|
|
def inside_docker?
|
|
results = `cat /proc/1/cgroup`.strip.split("\n")
|
|
results.any?{|val| /docker/ =~ val}
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|