diff options
-rw-r--r-- | lib/chef/platform/provider_mapping.rb | 3 | ||||
-rw-r--r-- | lib/chef/provider/service/openbsd.rb | 466 | ||||
-rw-r--r-- | lib/chef/providers.rb | 1 | ||||
-rw-r--r-- | lib/chef/resource/service.rb | 11 | ||||
-rw-r--r-- | spec/unit/provider/service/openbsd_service_spec.rb | 521 |
5 files changed, 1001 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( diff --git a/spec/unit/provider/service/openbsd_service_spec.rb b/spec/unit/provider/service/openbsd_service_spec.rb new file mode 100644 index 0000000000..55fd904671 --- /dev/null +++ b/spec/unit/provider/service/openbsd_service_spec.rb @@ -0,0 +1,521 @@ +# +# Author:: Bryan McLellan (btm@loftninjas.org) +# Author:: Scott Bonds (scott@ggr.com) +# Copyright:: Copyright (c) 2009 Bryan McLellan +# 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 'spec_helper' + +class Chef::Provider::Service::Openbsd + public :builtin_service_enable_variable_name + public :determine_enabled_status! + public :determine_current_status! + public :files + public :files= + public :afters= +end + +describe Chef::Provider::Service::Openbsd do + let(:node) do + node = Chef::Node.new + node.automatic_attrs[:command] = {:ps => "ps -ax"} + node + end + + let(:new_resource) do + new_resource = Chef::Resource::Service.new("sndiod") + new_resource.pattern("sndiod") + new_resource.supports({:status => false}) + new_resource + end + + let(:current_resource) do + current_resource = Chef::Resource::Service.new("sndiod") + current_resource + end + + let(:provider) do + events = Chef::EventDispatch::Dispatcher.new + run_context = Chef::RunContext.new(node, {}, events) + provider = Chef::Provider::Service::Openbsd.new(new_resource,run_context) + provider.action = :start + provider + end + + before do + allow(Chef::Resource::Service).to receive(:new).and_return(current_resource) + end + + def stub_etc_rcd_script + allow(::File).to receive(:exist?).and_return(false) + expect(::File).to receive(:exist?).with("/etc/rc.d/#{new_resource.service_name}").and_return(true) + end + + def run_load_current_resource + stub_etc_rcd_script + provider.load_current_resource + end + + describe Chef::Provider::Service::Openbsd, "initialize" do + it "should find /etc/rc.d init scripts" do + stub_etc_rcd_script + expect(provider.init_command).to eql "/etc/rc.d/sndiod" + end + + it "should set init_command to nil if it can't find anything" do + allow(::File).to receive(:exist?).and_return(false) + expect(provider.init_command).to be nil + end + end + + describe Chef::Provider::Service::Openbsd, "determine_current_status!" do + before do + stub_etc_rcd_script + provider.current_resource = current_resource + current_resource.service_name(new_resource.service_name) + end + + context "when a status command has been specified" do + let(:status) { double(:stdout => "", :exitstatus => 0) } + + before do + new_resource.status_command("/bin/chefhasmonkeypants status") + end + + it "should run the services status command if one has been specified" do + expect(provider).to receive(:shell_out).with("/bin/chefhasmonkeypants status").and_return(status) + provider.determine_current_status! + end + end + + context "when the service supports status" do + let(:status) { double(:stdout => "", :exitstatus => 0) } + + before do + new_resource.supports({:status => true}) + end + + it "should run '/etc/rc.d/service_name status'" do + expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_return(status) + provider.determine_current_status! + end + + it "should set running to true if the status command returns 0" do + expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_return(status) + provider.determine_current_status! + expect(current_resource.running).to be true + end + + it "should set running to false if the status command returns anything except 0" do + expect(provider).to receive(:shell_out).with("/etc/rc.d/#{new_resource.service_name} check").and_raise(Mixlib::ShellOut::ShellCommandFailed) + provider.determine_current_status! + expect(current_resource.running).to be false + end + end + + context "when we have a 'ps' attribute" do + let(:stdout) do + StringIO.new(<<-PS_SAMPLE) +413 ?? Ss 0:02.51 /usr/sbin/syslogd -s +539 ?? Is 0:00.14 /usr/sbin/sshd +545 ?? Ss 0:17.53 sendmail: accepting connections (sendmail) +PS_SAMPLE + end + let(:status) { double(:stdout => stdout, :exitstatus => 0) } + + before do + node.automatic_attrs[:command] = {:ps => "ps -ax"} + end + + it "should shell_out! the node's ps command" do + expect(provider).to receive(:shell_out!).with(node[:command][:ps]).and_return(status) + provider.determine_current_status! + end + + it "should read stdout of the ps command" do + allow(provider).to receive(:shell_out!).and_return(status) + expect(stdout).to receive(:each_line).and_return(true) + provider.determine_current_status! + end + + context "when the regex matches the output" do + let(:stdout) do + StringIO.new(<<-PS_SAMPLE) +555 ?? Ss 0:05.16 /usr/sbin/cron -s + 9881 ?? S<s 0:06.67 /usr/bin/sndiod + PS_SAMPLE + end + + it "should set running to true" do + allow(provider).to receive(:shell_out!).and_return(status) + provider.determine_current_status! + expect(current_resource.running).to be_true + end + end + + it "should set running to false if the regex doesn't match" do + allow(provider).to receive(:shell_out!).and_return(status) + provider.determine_current_status! + expect(current_resource.running).to be_false + end + + it "should set running to nil if ps fails" do + allow(provider).to receive(:shell_out!).and_raise(Mixlib::ShellOut::ShellCommandFailed) + provider.determine_current_status! + expect(current_resource.running).to be_nil + expect(provider.status_load_success).to be_nil + end + + context "when ps command is nil" do + before do + node.automatic_attrs[:command] = {:ps => nil} + end + + it "should set running to nil" do + pending "superclass raises no conversion of nil to string which seems broken" + provider.determine_current_status! + expect(current_resource.running).to be_nil + end + end + + context "when ps is empty string" do + before do + node.automatic_attrs[:command] = {:ps => ""} + end + + it "should set running to nil" do + provider.determine_current_status! + expect(current_resource.running).to be_nil + end + end + end + end + + describe Chef::Provider::Service::Openbsd, "determine_enabled_status!" do + before do + stub_etc_rcd_script + provider.current_resource = current_resource + current_resource.service_name(new_resource.service_name) + + allow(provider).to receive(:service_enable_variable_name).and_return("#{new_resource.service_name}_enable") + end + + context "when the service is not builtin" do + before do + expect(::File).to receive(:exist?).with("/etc/rc.conf").and_return(false) + end + pending + end + + context "when the service is builtin" do + before do + provider.files = { + '/etc/rc.conf' => "#{provider.builtin_service_enable_variable_name}=NO", + '/etc/rc.conf.local' => lines.join("\n") + } + end + + %w{YES Yes yes yEs YeS}.each do |setting| + context "when the enable variable is set to #{setting}" do + let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}="#{setting}"} ] } + it "sets enabled to true" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be true + end + end + end + + %w{No NO no nO None NONE none nOnE}.each do |setting| + context "when the enable variable is set to #{setting}" do + let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}="#{setting}"} ] } + it "sets enabled to false" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be false + end + end + end + + context "when the enable variable is garbage" do + let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}_enable="alskdjflasdkjflakdfj"} ] } + it "sets enabled to false" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be false + end + end + + context "when the enable variable partial matches (left) some other service and we are disabled" do + let(:lines) { [ + %Q{thing_#{provider.builtin_service_enable_variable_name}="YES"}, + %Q{#{provider.builtin_service_enable_variable_name}="NO"}, + ] } + it "sets enabled based on the exact match (false)" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be false + end + end + + context "when the enable variable partial matches (right) some other service and we are disabled" do + let(:lines) { [ + %Q{#{provider.builtin_service_enable_variable_name}_thing="YES"}, + %Q{#{provider.builtin_service_enable_variable_name}}, + ] } + it "sets enabled based on the exact match (false)" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be false + end + end + + context "when the enable variable partial matches (left) some other disabled service and we are enabled" do + let(:lines) { [ + %Q{thing_#{provider.builtin_service_enable_variable_name}="NO"}, + %Q{#{provider.builtin_service_enable_variable_name}="YES"}, + ] } + it "sets enabled based on the exact match (true)" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be true + end + end + + context "when the enable variable partial matches (right) some other disabled service and we are enabled" do + let(:lines) { [ + %Q{#{provider.builtin_service_enable_variable_name}_thing="NO"}, + %Q{#{provider.builtin_service_enable_variable_name}="YES"}, + ] } + it "sets enabled based on the exact match (true)" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be true + end + end + + context "when the enable variable only partial matches (left) some other enabled service" do + let(:lines) { [ %Q{thing_#{provider.builtin_service_enable_variable_name}_enable="YES"} ] } + it "sets enabled to false" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be false + end + end + + context "when the enable variable only partial matches (right) some other enabled service" do + let(:lines) { [ %Q{#{provider.builtin_service_enable_variable_name}_thing_enable="YES"} ] } + it "sets enabled to false" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be false + end + end + + context "when nothing matches" do + let(:lines) { [] } + it "sets enabled to true" do + provider.determine_enabled_status! + expect(current_resource.enabled).to be false + end + end + end + end + + describe Chef::Provider::Service::Openbsd, "load_current_resource" do + before(:each) do + stub_etc_rcd_script + expect(provider).to receive(:determine_current_status!) + current_resource.running(false) + allow(provider).to receive(:service_enable_variable_name).and_return "#{new_resource.service_name}_enable" + end + + it "should create a current resource with the name of the new resource" do + expect(Chef::Resource::Service).to receive(:new).and_return(current_resource) + provider.load_current_resource + end + + it "should set the current resources service name to the new resources service name" do + provider.load_current_resource + expect(current_resource.service_name).to eq(new_resource.service_name) + end + + it "should return the current resource" do + expect(provider.load_current_resource).to eql(current_resource) + end + + end + + context "when testing actions" do + before(:each) do + stub_etc_rcd_script + expect(provider).to receive(:determine_current_status!) + current_resource.running(false) + expect(provider).to receive(:determine_enabled_status!) + current_resource.enabled(false) + provider.load_current_resource + end + + describe Chef::Provider::Service::Openbsd, "start_service" do + it "should call the start command if one is specified" do + new_resource.start_command("/etc/rc.d/chef startyousillysally") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/chef startyousillysally") + provider.start_service() + end + + it "should call '/usr/local/etc/rc.d/service_name faststart' if no start command is specified" do + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} start") + provider.start_service() + end + end + + describe Chef::Provider::Service::Openbsd, "stop_service" do + it "should call the stop command if one is specified" do + new_resource.stop_command("/etc/init.d/chef itoldyoutostop") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef itoldyoutostop") + provider.stop_service() + end + + it "should call '/usr/local/etc/rc.d/service_name faststop' if no stop command is specified" do + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} stop") + provider.stop_service() + end + end + + describe Chef::Provider::Service::Openbsd, "restart_service" do + it "should call 'restart' on the service_name if the resource supports it" do + new_resource.supports({:restart => true}) + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/rc.d/#{new_resource.service_name} restart") + provider.restart_service() + end + + it "should call the restart_command if one has been specified" do + new_resource.restart_command("/etc/init.d/chef restartinafire") + expect(provider).to receive(:shell_out_with_systems_locale!).with("/etc/init.d/chef restartinafire") + provider.restart_service() + end + + it "otherwise it should call stop and start" do + expect(provider).to receive(:stop_service) + expect(provider).to receive(:start_service) + provider.restart_service() + end + end + end + + describe Chef::Provider::Service::Openbsd, "define_resource_requirements" do + before do + provider.current_resource = current_resource + end + + context "when the init script is not found" do + before do + provider.init_command = nil + allow(provider).to receive(:service_enable_variable_name).and_return("#{new_resource.service_name}_enable") + end + + [ "start", "reload", "restart", "enable" ].each do |action| + it "should raise an exception when the action is #{action}" do + provider.define_resource_requirements + provider.action = action + expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) + end + end + + [ "stop", "disable" ].each do |action| + it "should not raise an error when the action is #{action}" do + provider.define_resource_requirements + provider.action = action + expect { provider.process_resource_requirements }.not_to raise_error + end + end + end + + context "when the init script is found, but the service_enable_variable_name is nil" do + before do + provider.init_command = nil + allow(provider).to receive(:service_enable_variable_name).and_return(nil) + end + + [ "start", "reload", "restart", "enable" ].each do |action| + it "should raise an exception when the action is #{action}" do + provider.action = action + provider.define_resource_requirements + expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) + end + end + + [ "stop", "disable" ].each do |action| + it "should not raise an error when the action is #{action}" do + provider.action = action + provider.define_resource_requirements + expect { provider.process_resource_requirements }.not_to raise_error + end + end + end + end + + describe Chef::Provider::Service::Openbsd, "enable_service" do + before do + provider.current_resource = current_resource + provider.afters = {} + end + + it "should enable the service if it is not enabled" do + expect(provider).to receive(:is_builtin).and_return(true) + provider.files = {'/etc/rc.conf' => "#{provider.builtin_service_enable_variable_name}_flags=NO\n"} + expect(::File).to receive(:write).with('/etc/rc.conf.local', "#{provider.builtin_service_enable_variable_name}=\n") + provider.enable_service + end + + it "should not partial match an already enabled service" do + provider.files = {'/etc/rc.conf.local' => "pkg_scripts=\"#{new_resource.service_name}_thing\"\n"} + expect(::File).to receive(:write).with('/etc/rc.conf.local', "pkg_scripts=\"#{new_resource.service_name} #{new_resource.service_name}_thing\"\n") + provider.enable_service + end + + it "should enable the service if it is not enabled and not already specified in the rc.conf file" do + provider.files = {} + expect(::File).to receive(:write).with('/etc/rc.conf.local', "pkg_scripts=\"#{new_resource.service_name}\"\n") + provider.enable_service + end + + it "should not enable the service if it is already enabled" do + provider.files = {'/etc/rc.conf.local' => "pkg_scripts=\"#{new_resource.service_name}\"\n"} + expect(::File).not_to receive(:write) + provider.enable_service + end + end + + describe Chef::Provider::Service::Openbsd, "disable_service" do + before do + provider.current_resource = current_resource + expect(provider).to receive(:is_builtin).and_return(true) + allow(::File).to receive(:exists?).with('/etc/rc.conf').and_return(true) + allow(::File).to receive(:exists?).with('/etc/rc.conf.local').and_return(true) + end + + it "should disable the service if it is not disabled" do + provider.files = {'/etc/rc.conf.local' => "#{provider.builtin_service_enable_variable_name}=\"YES\"\n"} + expect(::File).to receive(:write).with('/etc/rc.conf.local', '') + provider.disable_service() + end + + it "should not disable an enabled service that partially matches" do + provider.files = {'/etc/rc.conf.local' => "#{provider.builtin_service_enable_variable_name}_thing=\"YES\""} + expect(::File).not_to receive(:write) + provider.disable_service() + end + + it "should not disable the service if it is already disabled" do + provider.files = {'/etc/rc.conf.local' => ""} + expect(::File).not_to receive(:write) + provider.disable_service + end + end +end |