diff options
author | robuye <rulejczyk@gmail.com> | 2019-09-17 11:54:23 +0200 |
---|---|---|
committer | robuye <rulejczyk@gmail.com> | 2019-09-17 14:44:26 +0200 |
commit | ec177edf55504e56de1ba1319f5eca8351614e46 (patch) | |
tree | 0fddc22c69ca0444a1ccf1384fe71f93914871d5 | |
parent | 1c642e292f1356454e0e226c7d33c3bb45dec8fb (diff) | |
download | chef-ec177edf55504e56de1ba1319f5eca8351614e46.tar.gz |
bring back support for legacy update-rc.d syntax
Legacy syntax is required to support start & stop priorities on very old
systems running SysVinit. If an old enough version is detected Chef will
prefer legacy syntax, otherwise it will use modern syntax and the
priorities will be for the most part ignored.
See https://lists.debian.org/debian-devel/2013/05/msg01109.html for
additional context.
To use legacy syntax sysv-rc package must be installed and it's version
must be < 2.88. If the package is not installed at all then update-rc.d
is coming from 'init-system-helpers' package and it's at least
2.88dsf-59.2 so it supports only modern syntax.
Note that start|stop syntax has been dropped in 2.88dsf-42 and older
2.88 versions could work, but the email linked above indicates it didn't
do anything.
Signed-off-by: Rob Ulejczyk <rulejczyk@gmail.com>
-rw-r--r-- | lib/chef/provider/service/debian.rb | 86 | ||||
-rw-r--r-- | spec/unit/provider/service/debian_service_spec.rb | 185 |
2 files changed, 215 insertions, 56 deletions
diff --git a/lib/chef/provider/service/debian.rb b/lib/chef/provider/service/debian.rb index 6774b214ae..0fea83ab0f 100644 --- a/lib/chef/provider/service/debian.rb +++ b/lib/chef/provider/service/debian.rb @@ -149,43 +149,103 @@ class Chef end def enable_service + # We call the same command regardless if we're enabling or disabling + # Users passing a Hash are responsible for setting their own stop priorities if new_resource.priority.is_a? Hash - # we call the same command regardless of we're enabling or disabling - # users passing a Hash are responsible for setting their own start priorities set_priority - else # No priority or Integer. Either way go with update-rc.d defaults - shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") - shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults") + return end + + start_priority = new_resource.priority.is_a?(Integer) ? new_resource.priority : 20 + # Stop processes in reverse order of start using '100 - start_priority'. + stop_priority = 100 - start_priority + + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults #{start_priority} #{stop_priority}") end def disable_service if new_resource.priority.is_a? Hash - # we call the same command regardless of we're enabling or disabling - # users passing a Hash are responsible for setting their own stop priorities + # We call the same command regardless if we're enabling or disabling + # Users passing a Hash are responsible for setting their own stop priorities set_priority - else # No priority or Integer. Either way disable in all levels. - shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + return + end + + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + + # Use legacy syntax if update-rc.d supports it for backward compatibility. + if use_legacy_update_rc_d? + # If no priority was given assume 20 (update-rc.d default). + start_priority = new_resource.priority.is_a?(Integer) ? new_resource.priority : 20 + # Stop processes in reverse order of start using '100 - start_priority'. + stop_priority = 100 - start_priority + + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} stop #{stop_priority} 2 3 4 5 .") + else shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults") shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} disable") end end def set_priority + shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") + + # Use legacy syntax if update-rc.d supports it for backward compatibility. + if use_legacy_update_rc_d? + args = "" + new_resource.priority.each do |level, o| + action = o[0] + priority = o[1] + args += "#{action} #{priority} #{level} . " + end + shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} #{args}") + return + end + + # Use modern syntax, ignoring priorities as update-rc.d does not support it. + # # Reset priorities to default values before applying customizations. This way # the final state will always be consistent, regardless if all runlevels were # provided. - shell_out!("/usr/sbin/update-rc.d -f #{new_resource.service_name} remove") shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} defaults") - - # Note the `_priority` is not used. update-rc.d does not support priorities - # anymore, this feature has been dropped in sysvinit 2.88dsf-42. new_resource.priority.each do |level, (action, _priority)| disable_or_enable = (action == :start ? "enable" : "disable") shell_out!("/usr/sbin/update-rc.d #{new_resource.service_name} #{disable_or_enable} #{level}") end end + + # Ancient Debian releases used run levels and priorities to manage dependencies ordering. + # Old syntax no longer works and new syntax does not support priorities. If Chef detects + # ancient update-rc.d it will prefer legacy syntax so priorities can be set correctly in + # case the host is in fact running SysVinit. + # + # Additional context: https://lists.debian.org/debian-devel/2013/05/msg01109.html + def use_legacy_update_rc_d? + @sysv_rc_version ||= shell_out!("dpkg-query -W --showformat '${Version}' sysv-rc").stdout.strip + + # sysv-rc is not installed therefore we're on modern Debian and legacy syntax does not work + if @sysv_rc_version.empty? + logger.trace("sysv-rc package is not installed. update-rc.d will use modern syntax") + return false + end + + # sysv-rc is installed and update-rc.d is old enough to support legacy syntax and features + if @sysv_rc_version.to_f < 2.88 + logger.trace("sysv-rc #{@sysv_rc_version} detected. update-rc.d will use legacy syntax") + return true + end + + # sysv-rc 2.88dsf-42 drops the legacy syntax + if @sysv_rc_version.to_f == 2.88 && @sysv_rc_version[8..9].to_i < 42 + logger.trace("sysv-rc #{@sysv_rc_version} detected. update-rc.d will use legacy syntax") + return true + end + + # default to modern syntax + false + end end end end diff --git a/spec/unit/provider/service/debian_service_spec.rb b/spec/unit/provider/service/debian_service_spec.rb index 0aef4bc760..a69909107c 100644 --- a/spec/unit/provider/service/debian_service_spec.rb +++ b/spec/unit/provider/service/debian_service_spec.rb @@ -183,88 +183,187 @@ describe Chef::Provider::Service::Debian do describe "enable_service" do let(:service_name) { @new_resource.service_name } context "when the service doesn't set a priority" do - it "calls update-rc.d remove => defaults" do + it "assumes default priority 20 and calls update-rc.d remove => defaults 20 80" do expect_commands(@provider, [ "/usr/sbin/update-rc.d -f #{service_name} remove", - "/usr/sbin/update-rc.d #{service_name} defaults", + "/usr/sbin/update-rc.d #{service_name} defaults 20 80", ]) @provider.enable_service end end - context "when the service sets a simple priority" do + context "when the service sets a simple priority 75" do before do @new_resource.priority(75) end - it "calls update-rc.d remove => defaults" do + it "calls update-rc.d remove => defaults 75 25" do expect_commands(@provider, [ "/usr/sbin/update-rc.d -f #{service_name} remove", - "/usr/sbin/update-rc.d #{service_name} defaults", + "/usr/sbin/update-rc.d #{service_name} defaults 75 25", ]) @provider.enable_service end end - context "when the service sets complex priorities" do + context "when the service sets complex priorities using Hash" do before do @new_resource.priority(2 => [:start, 20], 3 => [:stop, 55]) end - it "calls update-rc.d remove => defaults => enable|disable <runlevel>" do - expect_commands(@provider, [ - "/usr/sbin/update-rc.d -f #{service_name} remove", - "/usr/sbin/update-rc.d #{service_name} defaults", - "/usr/sbin/update-rc.d #{service_name} enable 2", - "/usr/sbin/update-rc.d #{service_name} disable 3", - ]) - @provider.enable_service + context "when modern update-rc.d is detected" do + before do + expect(@provider).to receive(:use_legacy_update_rc_d?).and_return(false) + end + + it "calls update-rc.d remove => defaults => enable|disable <runlevel>" do + expect_commands(@provider, [ + "/usr/sbin/update-rc.d -f #{service_name} remove", + "/usr/sbin/update-rc.d #{service_name} defaults", + "/usr/sbin/update-rc.d #{service_name} enable 2", + "/usr/sbin/update-rc.d #{service_name} disable 3", + ]) + @provider.enable_service + end + end + + context "when legacy update-rc.d is detected" do + before do + expect(@provider).to receive(:use_legacy_update_rc_d?).and_return(true) + end + + it "calls update-rc.d remove => start|stop <priority> <levels> ." do + expect_commands(@provider, [ + "/usr/sbin/update-rc.d -f #{service_name} remove", + "/usr/sbin/update-rc.d #{service_name} start 20 2 . stop 55 3 . ", + ]) + @provider.disable_service + end end end end describe "disable_service" do let(:service_name) { @new_resource.service_name } - context "when the service doesn't set a priority" do - it "calls update-rc.d remove => defaults => disable" do - expect_commands(@provider, [ - "/usr/sbin/update-rc.d -f #{service_name} remove", - "/usr/sbin/update-rc.d #{service_name} defaults", - "/usr/sbin/update-rc.d #{service_name} disable", - ]) - @provider.disable_service - end - end - context "when the service sets a simple priority" do + context "when modern update-rc.d is detected" do before do - @new_resource.priority(75) + expect(@provider).to receive(:use_legacy_update_rc_d?).and_return(false) end - it "calls update-rc.d remove => defaults => disable" do - expect_commands(@provider, [ - "/usr/sbin/update-rc.d -f #{service_name} remove", - "/usr/sbin/update-rc.d #{service_name} defaults", - "/usr/sbin/update-rc.d #{service_name} disable", - ]) - @provider.disable_service + context "when the service doesn't set a priority" do + it "calls update-rc.d remove => defaults => disable" do + expect_commands(@provider, [ + "/usr/sbin/update-rc.d -f #{service_name} remove", + "/usr/sbin/update-rc.d #{service_name} defaults", + "/usr/sbin/update-rc.d #{service_name} disable", + ]) + @provider.disable_service + end + end + + context "when the service sets a simple priority 75" do + before do + @new_resource.priority(75) + end + + it "ignores priority and calls update-rc.d remove => defaults => disable" do + expect_commands(@provider, [ + "/usr/sbin/update-rc.d -f #{service_name} remove", + "/usr/sbin/update-rc.d #{service_name} defaults", + "/usr/sbin/update-rc.d #{service_name} disable", + ]) + @provider.disable_service + end + end + + context "when the service sets complex priorities using Hash" do + before do + @new_resource.priority(2 => [:start, 20], 3 => [:stop, 55]) + end + + it "ignores priority and calls update-rc.d remove => defaults => enable|disable <runlevel>" do + expect_commands(@provider, [ + "/usr/sbin/update-rc.d -f #{service_name} remove", + "/usr/sbin/update-rc.d #{service_name} defaults", + "/usr/sbin/update-rc.d #{service_name} enable 2", + "/usr/sbin/update-rc.d #{service_name} disable 3", + ]) + @provider.disable_service + end end end - context "when the service sets complex priorities" do + context "when legacy update-rc.d is detected" do before do - @new_resource.priority(2 => [:start, 20], 3 => [:stop, 55]) + expect(@provider).to receive(:use_legacy_update_rc_d?).and_return(true) end - it "calls update-rc.d remove => defaults => enable|disable <runlevel>" do - expect_commands(@provider, [ - "/usr/sbin/update-rc.d -f #{service_name} remove", - "/usr/sbin/update-rc.d #{service_name} defaults", - "/usr/sbin/update-rc.d #{service_name} enable 2", - "/usr/sbin/update-rc.d #{service_name} disable 3", - ]) - @provider.disable_service + context "when the service doesn't set a priority" do + it "assumes default priority 20 and calls update-rc.d remove => stop 80 2 3 4 5 ." do + expect_commands(@provider, [ + "/usr/sbin/update-rc.d -f #{service_name} remove", + "/usr/sbin/update-rc.d -f #{service_name} stop 80 2 3 4 5 .", + ]) + @provider.disable_service + end + end + + context "when the service sets a simple priority 75" do + before do + @new_resource.priority(75) + end + + it "reverses priority and calls update-rc.d remove => stop 25 2 3 4 5 ." do + expect_commands(@provider, [ + "/usr/sbin/update-rc.d -f #{service_name} remove", + "/usr/sbin/update-rc.d -f #{service_name} stop #{100 - @new_resource.priority} 2 3 4 5 .", + ]) + @provider.disable_service + end end + + context "when the service sets complex priorities using Hash" do + before do + @new_resource.priority(2 => [:start, 20], 3 => [:stop, 55]) + end + + it "calls update-rc.d remove => start|stop <priority> <levels> ." do + expect_commands(@provider, [ + "/usr/sbin/update-rc.d -f #{service_name} remove", + "/usr/sbin/update-rc.d #{service_name} start 20 2 . stop 55 3 . ", + ]) + @provider.disable_service + end + end + end + end + + describe "use_legacy_update_rc_d?" do + let(:dpkg_query_cmd) { "dpkg-query -W --showformat '${Version}' sysv-rc" } + + it "is true when svsv-rc package version < 2.88 is installed" do + shellout_result = double(stdout: "2.86.ds1-34\n") + expect(@provider).to receive(:shell_out!).with(dpkg_query_cmd).and_return(shellout_result) + expect(@provider.use_legacy_update_rc_d?).to eq(true) + end + + it "is true when sysv-rc is 2.88 and patch level < ('dsf-')42" do + shellout_result = double(stdout: "2.88.dsf-41\n") + expect(@provider).to receive(:shell_out!).with(dpkg_query_cmd).and_return(shellout_result) + expect(@provider.use_legacy_update_rc_d?).to eq(true) + end + + it "is false when sysv-rc package version >= 2.88 is installed" do + shellout_result = double(stdout: "2.88dsf-59.9\n") + expect(@provider).to receive(:shell_out!).with(dpkg_query_cmd).and_return(shellout_result) + expect(@provider.use_legacy_update_rc_d?).to eq(false) + end + + it "is false when sysv-rc package is not installed" do + shellout_result = double(stdout: "\n") + expect(@provider).to receive(:shell_out!).with(dpkg_query_cmd).and_return(shellout_result) + expect(@provider.use_legacy_update_rc_d?).to eq(false) end end end |