summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorScott Bonds <scott@ggr.com>2014-10-11 10:17:07 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2015-01-25 10:43:19 -0800
commit6b732127edbf1b63ae9748a80cc869ad4230c9b2 (patch)
tree95ee761e3527b23b48f232d8c6a3202261d8c7c6 /lib
parent27b0b69b3cd2d2ec84a7ff37ac46453ae942f938 (diff)
downloadchef-6b732127edbf1b63ae9748a80cc869ad4230c9b2.tar.gz
add service provider for OpenBSD
Diffstat (limited to 'lib')
-rw-r--r--lib/chef/platform/provider_mapping.rb3
-rw-r--r--lib/chef/provider/service/openbsd.rb466
-rw-r--r--lib/chef/providers.rb1
-rw-r--r--lib/chef/resource/service.rb11
4 files changed, 480 insertions, 1 deletions
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb
index 271e72f761..3c7ecf038c 100644
--- a/lib/chef/platform/provider_mapping.rb
+++ b/lib/chef/platform/provider_mapping.rb
@@ -366,7 +366,8 @@ class Chef
:openbsd => {
:default => {
:group => Chef::Provider::Group::Usermod,
- :package => Chef::Provider::Package::Openbsd
+ :package => Chef::Provider::Package::Openbsd,
+ :service => Chef::Provider::Service::Openbsd
}
},
:hpux => {
diff --git a/lib/chef/provider/service/openbsd.rb b/lib/chef/provider/service/openbsd.rb
new file mode 100644
index 0000000000..a587525a7d
--- /dev/null
+++ b/lib/chef/provider/service/openbsd.rb
@@ -0,0 +1,466 @@
+#
+# Author:: Scott Bonds (<scott@ggr.com>)
+# Copyright:: Copyright (c) 2014 Scott Bonds
+# 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 'chef/mixin/command'
+require 'chef/mixin/shell_out'
+require 'chef/provider/service/init'
+require 'chef/resource/service'
+
+class Chef
+ class Provider
+ class Service
+ class Openbsd < Chef::Provider::Service::Init
+
+ include Chef::Mixin::ShellOut
+
+ def initialize(new_resource, run_context)
+ super
+ @init_command = nil
+ if ::File.exist?("/etc/rc.d/#{new_resource.service_name}")
+ @init_command = "/etc/rc.d/#{new_resource.service_name}"
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+ @rcd_script_found = true
+
+ # Determine if we're talking about /etc/rc.d or /usr/local/etc/rc.d
+ if ::File.exists?("/etc/rc.d/#{current_resource.service_name}")
+ @init_command = "/etc/rc.d/#{current_resource.service_name}"
+ elsif ::File.exists?("/usr/local/etc/rc.d/#{current_resource.service_name}")
+ @init_command = "/usr/local/etc/rc.d/#{current_resource.service_name}"
+ else
+ @rcd_script_found = false
+ return
+ end
+ Chef::Log.debug("#{@current_resource} found at #{@init_command}")
+ determine_current_status!
+ determine_enabled_status!
+
+ @current_resource
+ end
+
+ def define_resource_requirements
+ shared_resource_requirements
+ requirements.assert(:start, :enable, :reload, :restart) do |a|
+ a.assertion { @rcd_script_found }
+ a.failure_message Chef::Exceptions::Service, "#{@new_resource}: unable to locate the rc.d script"
+ end
+
+ requirements.assert(:all_actions) do |a|
+ a.assertion { @enabled_state_found }
+ # for consistency with original behavior, this will not fail in non-whyrun mode;
+ # rather it will silently set enabled state=>false
+ a.whyrun "Unable to determine enabled/disabled state, assuming this will be correct for an actual run. Assuming disabled."
+ end
+
+ requirements.assert(:start, :enable, :reload, :restart) do |a|
+ a.assertion { @rcd_script_found && builtin_service_enable_variable_name != nil }
+ a.failure_message Chef::Exceptions::Service, "Could not find the service name in #{@init_command} and rcvar"
+ # No recovery in whyrun mode - the init file is present but not correct.
+ end
+ end
+
+ def start_service
+ if @new_resource.start_command
+ super
+ else
+ shell_out_with_systems_locale!("#{@init_command} start")
+ end
+ end
+
+ def stop_service
+ if @new_resource.stop_command
+ super
+ else
+ shell_out_with_systems_locale!("#{@init_command} stop")
+ end
+ end
+
+ def restart_service
+ if @new_resource.restart_command
+ super
+ elsif @new_resource.supports[:restart]
+ shell_out_with_systems_locale!("#{@init_command} restart")
+ else
+ stop_service
+ sleep 1
+ start_service
+ end
+ end
+
+ def enable_service()
+ set_service_enable(true)
+ end
+
+ def disable_service()
+ set_service_enable(false)
+ end
+
+ protected
+
+ def determine_current_status!
+ if !@new_resource.status_command && @new_resource.supports[:status]
+ Chef::Log.debug("#{@new_resource} supports status, running")
+ begin
+ if shell_out("#{default_init_command} check").exitstatus == 0
+ @current_resource.running true
+ Chef::Log.debug("#{@new_resource} is running")
+ end
+ # ShellOut sometimes throws different types of Exceptions than ShellCommandFailed.
+ # Temporarily catching different types of exceptions here until we get Shellout fixed.
+ # TODO: Remove the line before one we get the ShellOut fix.
+ rescue Mixlib::ShellOut::ShellCommandFailed, SystemCallError
+ @status_load_success = false
+ @current_resource.running false
+ nil
+ end
+ else
+ super
+ end
+ end
+
+ private
+
+ # The variable name used in /etc/rc.conf.local for enabling this service
+ def builtin_service_enable_variable_name
+ if @rcd_script_found
+ ::File.open(@init_command) do |rcscript|
+ rcscript.each_line do |line|
+ if line =~ /^# \$OpenBSD: (\w+)[(.rc),]?/
+ return $1 + "_flags"
+ end
+ end
+ end
+ end
+ # Fallback allows us to keep running in whyrun mode when
+ # the script does not exist.
+ @new_resource.service_name
+ end
+
+ def set_service_enable(enable)
+ var_name = builtin_service_enable_variable_name
+ if is_builtin
+ # a builtin service is enabled when either <service>_flags is
+ # set equal to something other than 'NO', can be blank or it can be
+ # a set of options that should be passed to the startup script
+ old_value = new_value = nil
+ if enable
+ if files['/etc/rc.conf.local'] && var_name
+ files['/etc/rc.conf.local'].split("\n").each do |line|
+ if line =~ /^#{Regexp.escape(var_name)}=(.*)/
+ old_value = $1
+ break
+ end
+ end
+ end
+ if files['/etc/rc.conf'] && var_name
+ files['/etc/rc.conf'].split("\n").each do |line|
+ if line =~ /^#{Regexp.escape(var_name)}=(.*)/
+ old_value = $1
+ break
+ end
+ end
+ end
+ if old_value && old_value =~ /"?[Nn][Oo]"?/
+ new_value = ''
+ else
+ new_value = old_value
+ Chef::Log.debug("service is already enabled and has parameters, skipping")
+ end
+ end
+ #Chef::Log.debug("SCOTT enable me-1!")
+ # This is run immediately so the service can be started at any time
+ # after the :enable action, during this Chef run.
+ contents = configurate(
+ setting: builtin_service_enable_variable_name,
+ value: new_value,
+ remove: !enable
+ )
+ else
+ #Chef::Log.debug("SCOTT enable me-2!")
+ #Chef::Log.debug("SCOTT service_name is #{@new_resource.service_name}")
+ service_name = @new_resource.service_name.clone
+ service_after = @new_resource.after ? @new_resource.after.clone : nil
+ contents = configurate(
+ setting: 'pkg_scripts',
+ format: :space_delimited_list,
+ value: service_name,
+ after: service_after,
+ remove: !enable
+ )
+ end
+ end
+
+ def is_builtin
+ result = false
+ var_name = builtin_service_enable_variable_name
+ if files['/etc/rc.conf'] && var_name
+ files['/etc/rc.conf'].split("\n").each do |line|
+ case line
+ when /^#{Regexp.escape(var_name)}=(.*)/
+ result = true
+ end
+ end
+ end
+ #Chef::Log.debug("SCOTT: builtin? #{result}")
+ result
+ end
+
+ def determine_enabled_status!
+ result = false # Default to disabled if the service doesn't currently exist at all
+ if is_builtin
+ var_name = builtin_service_enable_variable_name
+ if files['/etc/rc.conf.local'] && var_name
+ files['/etc/rc.conf.local'].split("\n").each do |line|
+ case line
+ when /^#{Regexp.escape(var_name)}=(.*)/
+ @enabled_state_found = true
+ if $1 =~ /"?[Nn][Oo]"?/
+ result = false
+ else
+ result = true
+ end
+ break
+ end
+ end
+ end
+ if files['/etc/rc.conf'] && var_name && !@enabled_state_found
+ files['/etc/rc.conf'].split("\n").each do |line|
+ case line
+ when /^#{Regexp.escape(var_name)}=(.*)/
+ @enabled_state_found = true
+ if $1 =~ /"?[Nn][Oo]"?/
+ result = false
+ else
+ result = true
+ end
+ break
+ end
+ end
+ end
+ else
+ var_name = @new_resource.service_name
+ if files['/etc/rc.conf'] && var_name
+ files['/etc/rc.conf'].split("\n").each do |line|
+ if line =~ /pkg_scripts="(.*)"/
+ @enabled_state_found = true
+ if $1.include?(var_name)
+ result = true
+ end
+ break
+ end
+ end
+ end
+ end
+ #Chef::Log.debug("SCOTT: enabled? #{result}")
+
+ current_resource.enabled result
+ end
+
+ # this is used for sorting lists of dependencies in configurate
+ class TsortableHash < Hash
+ include TSort
+ alias tsort_each_node each_key
+ def tsort_each_child(node, &block)
+ fetch(node).each(&block)
+ end
+ end
+
+ def afters
+ @@afters ||= {}
+ end
+
+ def afters=(new_value)
+ @@afters = new_value
+ end
+
+ def files
+ return @@files if defined? @@files
+ @@files = {}
+ ['/etc/rc.conf', '/etc/rc.conf.local'].each do |file|
+ if ::File.exists? file
+ @@files[file] = ::File.read(file)
+ end
+ end
+ @@files
+ end
+
+ def files=(new_value)
+ @@files = new_value
+ end
+
+ def configurate(options)
+ file = '/etc/rc.conf.local'
+
+ Chef::Log.debug("Configurate: options[:setting] #{options[:setting]}")
+ Chef::Log.debug("Configurate: options[:value] #{options[:value]}")
+ Chef::Log.debug("Configurate: options[:after] #{options[:after]}")
+ Chef::Log.debug("Configurate: options[:remove] #{options[:remove]}")
+ Chef::Log.debug("Configurate: options[:prefix] #{options[:prefix]}")
+
+ options[:comment] ||= '#'
+ options[:equals] ||= '='
+
+ # Store which values need to come 'after' which other values for a given
+ # file + setting. For example, on OpenBSD the '/etc/rc.conf.local' file
+ # has a setting called 'pkg_scripts' which specifies the order that
+ # (addon) services should start up. In some cases, you need a certain
+ # service to start only *after* some service it depends on has started.
+ afters["#{file}##{options[:setting]}"] = TsortableHash.new if !afters["#{file}##{options[:setting]}"]
+ Chef::Log.debug "AFTERS: #{afters}"
+ if options[:after]
+ if !afters["#{file}##{options[:setting]}"][options[:after]]
+ raise KeyError, 'Cannot configurate a value before the value it comes after'
+ end
+ afters["#{file}##{options[:setting]}"][options[:value]] = [options[:after]]
+ else
+ afters["#{file}##{options[:setting]}"][options[:value]] = []
+ end
+
+ old_contents = ''
+ new_contents = ''
+ found = false
+
+ old_contents = files[file]
+ if old_contents
+ old_contents.each_line do |old_line|
+ new_line = ''
+ if options[:setting]
+ m = old_line.match(Regexp.new("(.*?)#{(options[:prefix] + '(\s*)') if options[:prefix]}#{options[:setting]}(\s*)#{options[:equals]}(\s*)(.*)"))
+ if m
+ next if found # delete duplicate lines defining this same setting
+
+ line_prefix = m[1]
+ if options[:prefix]
+ modifier = 1
+ s_prefix_suffix = m[2]
+ else
+ modifier = 0
+ end
+ equals_prefix = m[2+modifier]
+ equals_suffix = m[3+modifier]
+ line_value = m[4+modifier].split(options[:comment])[0].strip
+ line_suffix = m[4+modifier][line_value.size..-1]
+ # puts "ov: #{line_value}"
+ # puts "ss: #{line_suffix}"
+
+ # If the prefix includes anything besides a options[:comment] symbol or a
+ # specified prefix, or the suffix doesn't start with a options[:comment]
+ # symbol, the line is probably documentation
+ if (
+ line_prefix &&
+ !line_prefix.strip.empty? &&
+ options[:comment] && line_prefix.strip != options[:comment] &&
+ options[:prefix] && line_prefix.strip != options[:prefix]
+ ) ||
+ (
+ line_suffix &&
+ !line_suffix.strip.empty? &&
+ options[:comment] && line_suffix.strip[0] != options[:comment]
+ )
+ new_contents << old_line
+ next
+ end
+
+ # Return the setting's current value if no new value was passed
+ return line_value if !options[:value] && !options[:remove]
+ found = true
+
+ case options[:format]
+ when :space_delimited_list
+ list_items = line_value.gsub('"', '').split(' ').map{|a| a.strip}
+ list_items << options[:value]
+ list_items = list_items.uniq
+ if options[:remove]
+ list_items.delete_if {|a| a == options[:value]}
+ end
+ if afters["#{file}##{options[:setting]}"]
+ list_items.each do |item|
+ afters["#{file}##{options[:setting]}"][item] = [] if !afters["#{file}##{options[:setting]}"][item]
+ end
+ #Chef::Log.debug "afters: #{@@afters}"
+ new_value = "\"#{afters["#{file}##{options[:setting]}"].tsort.join(' ')}\""
+ else
+ #Chef::Log.debug "no afters found"
+ new_value = "\"#{list_items.sort.join(' ')}\""
+ end
+ else
+ if options[:value] == ''
+ new_value = '""' # print an empty string instead of leaving the value blank
+ else
+ new_value = "#{options[:value]}"
+ end
+ end
+ new_line = "#{line_prefix.sub(options[:comment], '')}"
+ new_line += "#{options[:prefix]}#{s_prefix_suffix}" if options[:prefix]
+ new_line += "#{options[:setting]}#{equals_prefix}#{options[:equals]}#{equals_suffix}#{new_value}#{line_suffix}\n"
+ else
+ new_line = old_line
+ end
+ else
+ if old_line.gsub(/\s{2,}/, ' ').include?(options[:value].gsub(/\s{2,}/, ' '))
+ next if found
+ found = true
+ end
+ new_line = old_line
+ end
+ new_contents << new_line unless found && options[:remove]
+ # if old_line != new_line
+ # puts "old: #{old_line}"
+ # puts "new: #{new_line}"
+ # end
+ end
+ end
+
+ if !found && !options[:remove]
+ if options[:setting]
+ case options[:format]
+ when :space_delimited_list
+ new_value = "\"#{options[:value]}\""
+ else
+ if options[:value] == ''
+ new_value = '""' # print an empty string instead of leaving the value blank
+ else
+ new_value = "#{options[:value]}"
+ end
+ end
+ new_line = "#{options[:prefix] + ' ' if options[:prefix]}#{options[:setting]}#{options[:equals]}#{new_value}\n"
+ else
+ new_line = "#{options[:value]}\n"
+ end
+ # puts "new: #{new_line}"
+ new_contents << "\n" if old_contents && !old_contents.empty? && old_contents[-1] != "\n"
+ new_contents << new_line
+ end
+
+ new_contents = new_contents.gsub(/\n{2,}/, "\n\n") # remove extra lines
+ #Chef::Log.debug 'new_contents:'
+
+ if old_contents != new_contents
+ files[file] = new_contents
+ ::File.write('/etc/rc.conf.local', new_contents)
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index 38c0e6fc9a..796a0f8fa6 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -81,6 +81,7 @@ require 'chef/provider/service/gentoo'
require 'chef/provider/service/init'
require 'chef/provider/service/invokercd'
require 'chef/provider/service/debian'
+require 'chef/provider/service/openbsd'
require 'chef/provider/service/redhat'
require 'chef/provider/service/insserv'
require 'chef/provider/service/simple'
diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb
index 36df7c859a..8b2490c3e4 100644
--- a/lib/chef/resource/service.rb
+++ b/lib/chef/resource/service.rb
@@ -42,6 +42,7 @@ class Chef
@reload_command = nil
@init_command = nil
@priority = nil
+ @after = nil
@timeout = nil
@action = "nothing"
@supports = { :restart => false, :reload => false, :status => false }
@@ -158,6 +159,16 @@ class Chef
)
end
+ # after only applies to non-builtin (pkg_scripts enabled) OpenBSD
+ # services
+ def after(arg=nil)
+ set_or_return(
+ :after,
+ arg,
+ :kind_of => String
+ )
+ end
+
# timeout only applies to the windows service manager
def timeout(arg=nil)
set_or_return(