From 0475cf75df5a186e3ef8fa62a9212f775aae30b3 Mon Sep 17 00:00:00 2001 From: Davide Cavalca Date: Mon, 14 Dec 2015 12:37:04 -0800 Subject: Extend service resource to support systemd user services --- lib/chef/provider/service/systemd.rb | 51 +++++++++++---- lib/chef/resource/service.rb | 9 +++ spec/unit/provider/service/systemd_service_spec.rb | 72 +++++++++++----------- 3 files changed, 85 insertions(+), 47 deletions(-) diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb index 7f90172edb..a6189e4332 100644 --- a/lib/chef/provider/service/systemd.rb +++ b/lib/chef/provider/service/systemd.rb @@ -70,6 +70,24 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple end end + def get_systemctl_options_args + if new_resource.user + uid = node['etc']['passwd'][new_resource.user]['uid'] + options = { + 'environment' => { + 'DBUS_SESSION_BUS_ADDRESS' => "unix:path=/run/user/#{uid}/bus", + }, + 'user' => new_resource.user, + } + args = '--user' + else + options = {} + args = '--system' + end + + return options, args + end + def start_service if current_resource.running Chef::Log.debug("#{new_resource} already running, not starting") @@ -77,7 +95,8 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple if new_resource.start_command super else - shell_out_with_systems_locale!("#{systemctl_path} start #{new_resource.service_name}") + options, args = get_systemctl_options_args + shell_out_with_systems_locale!("#{systemctl_path} #{args} start #{new_resource.service_name}", options) end end end @@ -89,7 +108,8 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple if new_resource.stop_command super else - shell_out_with_systems_locale!("#{systemctl_path} stop #{new_resource.service_name}") + options, args = get_systemctl_options_args + shell_out_with_systems_locale!("#{systemctl_path} #{args} stop #{new_resource.service_name}", options) end end end @@ -98,7 +118,8 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple if new_resource.restart_command super else - shell_out_with_systems_locale!("#{systemctl_path} restart #{new_resource.service_name}") + options, args = get_systemctl_options_args + shell_out_with_systems_locale!("#{systemctl_path} #{args} restart #{new_resource.service_name}", options) end end @@ -107,7 +128,8 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple super else if current_resource.running - shell_out_with_systems_locale!("#{systemctl_path} reload #{new_resource.service_name}") + options, args = get_systemctl_options_args + shell_out_with_systems_locale!("#{systemctl_path} #{args} reload #{new_resource.service_name}", options) else start_service end @@ -115,31 +137,38 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple end def enable_service - shell_out!("#{systemctl_path} enable #{new_resource.service_name}") + options, args = get_systemctl_options_args + shell_out!("#{systemctl_path} #{args} enable #{new_resource.service_name}", options) end def disable_service - shell_out!("#{systemctl_path} disable #{new_resource.service_name}") + options, args = get_systemctl_options_args + shell_out!("#{systemctl_path} #{args} disable #{new_resource.service_name}", options) end def mask_service - shell_out!("#{systemctl_path} mask #{new_resource.service_name}") + options, args = get_systemctl_options_args + shell_out!("#{systemctl_path} #{args} mask #{new_resource.service_name}", options) end def unmask_service - shell_out!("#{systemctl_path} unmask #{new_resource.service_name}") + options, args = get_systemctl_options_args + shell_out!("#{systemctl_path} #{args} unmask #{new_resource.service_name}", options) end def is_active? - shell_out("#{systemctl_path} is-active #{new_resource.service_name} --quiet").exitstatus == 0 + options, args = get_systemctl_options_args + shell_out("#{systemctl_path} #{args} is-active #{new_resource.service_name} --quiet", options).exitstatus == 0 end def is_enabled? - shell_out("#{systemctl_path} is-enabled #{new_resource.service_name} --quiet").exitstatus == 0 + options, args = get_systemctl_options_args + shell_out("#{systemctl_path} #{args} is-enabled #{new_resource.service_name} --quiet", options).exitstatus == 0 end def is_masked? - s = shell_out("#{systemctl_path} is-enabled #{new_resource.service_name}") + options, args = get_systemctl_options_args + s = shell_out("#{systemctl_path} #{args} is-enabled #{new_resource.service_name}", options) s.exitstatus != 0 && s.stdout.include?("masked") end diff --git a/lib/chef/resource/service.rb b/lib/chef/resource/service.rb index 860d5b05ff..1ca4b84af0 100644 --- a/lib/chef/resource/service.rb +++ b/lib/chef/resource/service.rb @@ -47,6 +47,7 @@ class Chef @priority = nil @timeout = nil @run_levels = nil + @user = nil @supports = { :restart => nil, :reload => nil, :status => nil } end @@ -193,6 +194,14 @@ class Chef :kind_of => [ Array ] ) end + def user(arg = nil) + set_or_return( + :user, + arg, + :kind_of => [ String ] + ) + end + def supports(args = {}) if args.is_a? Array args.each { |arg| @supports[arg] = true } diff --git a/spec/unit/provider/service/systemd_service_spec.rb b/spec/unit/provider/service/systemd_service_spec.rb index cf76fdd07b..b4a5015f58 100644 --- a/spec/unit/provider/service/systemd_service_spec.rb +++ b/spec/unit/provider/service/systemd_service_spec.rb @@ -171,14 +171,14 @@ describe Chef::Provider::Service::Systemd do provider.start_service end - it "should call '#{systemctl_path} start service_name' if no start command is specified" do - expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} start #{service_name}").and_return(shell_out_success) + it "should call '#{systemctl_path} --system start service_name' if no start command is specified" do + expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system start #{service_name}", {}).and_return(shell_out_success) provider.start_service end - it "should not call '#{systemctl_path} start service_name' if it is already running" do + it "should not call '#{systemctl_path} --system start service_name' if it is already running" do current_resource.running(true) - expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} start #{service_name}") + expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system start #{service_name}", {}) provider.start_service end @@ -189,9 +189,9 @@ describe Chef::Provider::Service::Systemd do provider.restart_service end - it "should call '#{systemctl_path} restart service_name' if no restart command is specified" do + it "should call '#{systemctl_path} --system restart service_name' if no restart command is specified" do current_resource.running(true) - expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} restart #{service_name}").and_return(shell_out_success) + expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system restart #{service_name}", {}).and_return(shell_out_success) provider.restart_service end @@ -206,9 +206,9 @@ describe Chef::Provider::Service::Systemd do end context "when a reload command is not specified" do - it "should call '#{systemctl_path} reload service_name' if the service is running" do + it "should call '#{systemctl_path} --system reload service_name' if the service is running" do current_resource.running(true) - expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} reload #{service_name}").and_return(shell_out_success) + expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system reload #{service_name}", {}).and_return(shell_out_success) provider.reload_service end @@ -227,15 +227,15 @@ describe Chef::Provider::Service::Systemd do provider.stop_service end - it "should call '#{systemctl_path} stop service_name' if no stop command is specified" do + it "should call '#{systemctl_path} --system stop service_name' if no stop command is specified" do current_resource.running(true) - expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} stop #{service_name}").and_return(shell_out_success) + expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system stop #{service_name}", {}).and_return(shell_out_success) provider.stop_service end - it "should not call '#{systemctl_path} stop service_name' if it is already stopped" do + it "should not call '#{systemctl_path} --system stop service_name' if it is already stopped" do current_resource.running(false) - expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} stop #{service_name}") + expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system stop #{service_name}", {}) provider.stop_service end end @@ -247,13 +247,13 @@ describe Chef::Provider::Service::Systemd do allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end - it "should call '#{systemctl_path} enable service_name' to enable the service" do - expect(provider).to receive(:shell_out!).with("#{systemctl_path} enable #{service_name}").and_return(shell_out_success) + it "should call '#{systemctl_path} --system enable service_name' to enable the service" do + expect(provider).to receive(:shell_out!).with("#{systemctl_path} --system enable #{service_name}", {}).and_return(shell_out_success) provider.enable_service end - it "should call '#{systemctl_path} disable service_name' to disable the service" do - expect(provider).to receive(:shell_out!).with("#{systemctl_path} disable #{service_name}").and_return(shell_out_success) + it "should call '#{systemctl_path} --system disable service_name' to disable the service" do + expect(provider).to receive(:shell_out!).with("#{systemctl_path} --system disable #{service_name}", {}).and_return(shell_out_success) provider.disable_service end end @@ -265,13 +265,13 @@ describe Chef::Provider::Service::Systemd do allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end - it "should call '#{systemctl_path} mask service_name' to mask the service" do - expect(provider).to receive(:shell_out!).with("#{systemctl_path} mask #{service_name}").and_return(shell_out_success) + it "should call '#{systemctl_path} --system mask service_name' to mask the service" do + expect(provider).to receive(:shell_out!).with("#{systemctl_path} --system mask #{service_name}", {}).and_return(shell_out_success) provider.mask_service end - it "should call '#{systemctl_path} unmask service_name' to unmask the service" do - expect(provider).to receive(:shell_out!).with("#{systemctl_path} unmask #{service_name}").and_return(shell_out_success) + it "should call '#{systemctl_path} --system unmask service_name' to unmask the service" do + expect(provider).to receive(:shell_out!).with("#{systemctl_path} --system unmask #{service_name}", {}).and_return(shell_out_success) provider.unmask_service end end @@ -283,13 +283,13 @@ describe Chef::Provider::Service::Systemd do allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end - it "should return true if '#{systemctl_path} is-active service_name' returns 0" do - expect(provider).to receive(:shell_out).with("#{systemctl_path} is-active #{service_name} --quiet").and_return(shell_out_success) + it "should return true if '#{systemctl_path} --system is-active service_name' returns 0" do + expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-active #{service_name} --quiet", {}).and_return(shell_out_success) expect(provider.is_active?).to be true end - it "should return false if '#{systemctl_path} is-active service_name' returns anything except 0" do - expect(provider).to receive(:shell_out).with("#{systemctl_path} is-active #{service_name} --quiet").and_return(shell_out_failure) + it "should return false if '#{systemctl_path} --system is-active service_name' returns anything except 0" do + expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-active #{service_name} --quiet", {}).and_return(shell_out_failure) expect(provider.is_active?).to be false end end @@ -301,13 +301,13 @@ describe Chef::Provider::Service::Systemd do allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end - it "should return true if '#{systemctl_path} is-enabled service_name' returns 0" do - expect(provider).to receive(:shell_out).with("#{systemctl_path} is-enabled #{service_name} --quiet").and_return(shell_out_success) + it "should return true if '#{systemctl_path} --system is-enabled service_name' returns 0" do + expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name} --quiet", {}).and_return(shell_out_success) expect(provider.is_enabled?).to be true end - it "should return false if '#{systemctl_path} is-enabled service_name' returns anything except 0" do - expect(provider).to receive(:shell_out).with("#{systemctl_path} is-enabled #{service_name} --quiet").and_return(shell_out_failure) + it "should return false if '#{systemctl_path} --system is-enabled service_name' returns anything except 0" do + expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name} --quiet", {}).and_return(shell_out_failure) expect(provider.is_enabled?).to be false end end @@ -319,23 +319,23 @@ describe Chef::Provider::Service::Systemd do allow(provider).to receive(:which).with("systemctl").and_return("#{systemctl_path}") end - it "should return true if '#{systemctl_path} is-enabled service_name' returns 'masked' and returns anything except 0" do - expect(provider).to receive(:shell_out).with("#{systemctl_path} is-enabled #{service_name}").and_return(double(:stdout => "masked", :exitstatus => shell_out_failure)) + it "should return true if '#{systemctl_path} --system is-enabled service_name' returns 'masked' and returns anything except 0" do + expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name}", {}).and_return(double(:stdout => "masked", :exitstatus => shell_out_failure)) expect(provider.is_masked?).to be true end - it "should return true if '#{systemctl_path} is-enabled service_name' outputs 'masked-runtime' and returns anything except 0" do - expect(provider).to receive(:shell_out).with("#{systemctl_path} is-enabled #{service_name}").and_return(double(:stdout => "masked-runtime", :exitstatus => shell_out_failure)) + it "should return true if '#{systemctl_path} --system is-enabled service_name' outputs 'masked-runtime' and returns anything except 0" do + expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name}", {}).and_return(double(:stdout => "masked-runtime", :exitstatus => shell_out_failure)) expect(provider.is_masked?).to be true end - it "should return false if '#{systemctl_path} is-enabled service_name' returns 0" do - expect(provider).to receive(:shell_out).with("#{systemctl_path} is-enabled #{service_name}").and_return(double(:stdout => "enabled", :exitstatus => shell_out_success)) + it "should return false if '#{systemctl_path} --system is-enabled service_name' returns 0" do + expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name}", {}).and_return(double(:stdout => "enabled", :exitstatus => shell_out_success)) expect(provider.is_masked?).to be false end - it "should return false if '#{systemctl_path} is-enabled service_name' returns anything except 0 and outputs an error'" do - expect(provider).to receive(:shell_out).with("#{systemctl_path} is-enabled #{service_name}").and_return(double(:stdout => "Failed to get unit file state for #{service_name}: No such file or directory", :exitstatus => shell_out_failure)) + it "should return false if '#{systemctl_path} --system is-enabled service_name' returns anything except 0 and outputs an error'" do + expect(provider).to receive(:shell_out).with("#{systemctl_path} --system is-enabled #{service_name}", {}).and_return(double(:stdout => "Failed to get unit file state for #{service_name}: No such file or directory", :exitstatus => shell_out_failure)) expect(provider.is_masked?).to be false end end -- cgit v1.2.1 From 0520c148d7a9a3b94b4a832974ab3c824f659cb9 Mon Sep 17 00:00:00 2001 From: Davide Cavalca Date: Thu, 3 Mar 2016 10:47:45 -0800 Subject: double quotes to make rubocop happy --- lib/chef/provider/service/systemd.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb index a6189e4332..82b887d48b 100644 --- a/lib/chef/provider/service/systemd.rb +++ b/lib/chef/provider/service/systemd.rb @@ -72,17 +72,17 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple def get_systemctl_options_args if new_resource.user - uid = node['etc']['passwd'][new_resource.user]['uid'] + uid = node["etc"]["passwd"][new_resource.user]["uid"] options = { - 'environment' => { - 'DBUS_SESSION_BUS_ADDRESS' => "unix:path=/run/user/#{uid}/bus", + "environment" => { + "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/#{uid}/bus", }, - 'user' => new_resource.user, + "user" => new_resource.user, } - args = '--user' + args = "--user" else options = {} - args = '--system' + args = "--system" end return options, args -- cgit v1.2.1 From 0174356f01fa20d643042004b20e4d76e0fe0735 Mon Sep 17 00:00:00 2001 From: Davide Cavalca Date: Fri, 4 Mar 2016 13:28:29 -0800 Subject: error out if provider doesn't support user services --- lib/chef/provider/service.rb | 9 +++++++++ lib/chef/provider/service/solaris.rb | 5 +++++ lib/chef/provider/service/systemd.rb | 4 ++++ 3 files changed, 18 insertions(+) diff --git a/lib/chef/provider/service.rb b/lib/chef/provider/service.rb index b848d33083..e693bd2eed 100644 --- a/lib/chef/provider/service.rb +++ b/lib/chef/provider/service.rb @@ -62,7 +62,16 @@ class Chef end end + # subclasses should override this if they do implement user services + def user_services_requirements + requirements.assert(:all_actions) do |a| + a.assertion { @new_resource.user.nil? } + a.failure_message Chef::Exceptions::UnsupportedAction, "#{self} does not support user services" + end + end + def shared_resource_requirements + user_services_requirements end def define_resource_requirements diff --git a/lib/chef/provider/service/solaris.rb b/lib/chef/provider/service/solaris.rb index 0787392094..1e5398eba8 100644 --- a/lib/chef/provider/service/solaris.rb +++ b/lib/chef/provider/service/solaris.rb @@ -49,6 +49,11 @@ class Chef @current_resource end + def define_resource_requirements + # FIXME? need reload from service.rb + shared_resource_requirements + end + def enable_service shell_out!(default_init_command, "clear", @new_resource.service_name) if @maintenance shell_out!(default_init_command, "enable", "-s", @new_resource.service_name) diff --git a/lib/chef/provider/service/systemd.rb b/lib/chef/provider/service/systemd.rb index 82b887d48b..1597d46a3d 100644 --- a/lib/chef/provider/service/systemd.rb +++ b/lib/chef/provider/service/systemd.rb @@ -60,6 +60,10 @@ class Chef::Provider::Service::Systemd < Chef::Provider::Service::Simple current_resource end + # systemd supports user services just fine + def user_services_requirements + end + def define_resource_requirements shared_resource_requirements requirements.assert(:all_actions) do |a| -- cgit v1.2.1 From 656386a80ceaa0f39302761764892d11cb804587 Mon Sep 17 00:00:00 2001 From: Davide Cavalca Date: Fri, 4 Mar 2016 15:24:20 -0800 Subject: add a test for user services --- spec/unit/provider/service/systemd_service_spec.rb | 42 +++++++++++++++++----- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/spec/unit/provider/service/systemd_service_spec.rb b/spec/unit/provider/service/systemd_service_spec.rb index b4a5015f58..06614442fe 100644 --- a/spec/unit/provider/service/systemd_service_spec.rb +++ b/spec/unit/provider/service/systemd_service_spec.rb @@ -21,7 +21,16 @@ require "spec_helper" describe Chef::Provider::Service::Systemd do - let(:node) { Chef::Node.new } + let(:node) { + node = Chef::Node.new + node.default["etc"] = Hash.new + node.default["etc"]["passwd"] = { + "joe" => { + "uid" => 10000 + } + } + node + } let(:events) { Chef::EventDispatch::Dispatcher.new } @@ -171,15 +180,32 @@ describe Chef::Provider::Service::Systemd do provider.start_service end - it "should call '#{systemctl_path} --system start service_name' if no start command is specified" do - expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system start #{service_name}", {}).and_return(shell_out_success) - provider.start_service + context "when a user is not specified" do + it "should call '#{systemctl_path} --system start service_name' if no start command is specified" do + expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system start #{service_name}", {}).and_return(shell_out_success) + provider.start_service + end + + it "should not call '#{systemctl_path} --system start service_name' if it is already running" do + current_resource.running(true) + expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system start #{service_name}", {}) + provider.start_service + end end - it "should not call '#{systemctl_path} --system start service_name' if it is already running" do - current_resource.running(true) - expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --system start #{service_name}", {}) - provider.start_service + context "when a user is specified" do + it "should call '#{systemctl_path} --user start service_name' if no start command is specified" do + current_resource.user("joe") + expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --user start #{service_name}", {"environment"=>{"DBUS_SESSION_BUS_ADDRESS"=>"unix:path=/run/user/10000/bus"}, "user" => "joe"}).and_return(shell_out_success) + provider.start_service + end + + it "should not call '#{systemctl_path} --user start service_name' if it is already running" do + current_resource.running(true) + current_resource.user("joe") + expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --user start #{service_name}", {"environment"=>{"DBUS_SESSION_BUS_ADDRESS"=>"unix:path=/run/user/10000/bus"}, "user" => "joe"}) + provider.start_service + end end it "should call the restart command if one is specified" do -- cgit v1.2.1 From 7f8af07e287282aefdb55bea57f50c05318787f2 Mon Sep 17 00:00:00 2001 From: Davide Cavalca Date: Mon, 7 Mar 2016 10:05:16 -0800 Subject: rubocop fixes --- spec/unit/provider/service/systemd_service_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/unit/provider/service/systemd_service_spec.rb b/spec/unit/provider/service/systemd_service_spec.rb index 06614442fe..e0a94127b7 100644 --- a/spec/unit/provider/service/systemd_service_spec.rb +++ b/spec/unit/provider/service/systemd_service_spec.rb @@ -26,8 +26,8 @@ describe Chef::Provider::Service::Systemd do node.default["etc"] = Hash.new node.default["etc"]["passwd"] = { "joe" => { - "uid" => 10000 - } + "uid" => 10000, + }, } node } @@ -196,14 +196,14 @@ describe Chef::Provider::Service::Systemd do context "when a user is specified" do it "should call '#{systemctl_path} --user start service_name' if no start command is specified" do current_resource.user("joe") - expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --user start #{service_name}", {"environment"=>{"DBUS_SESSION_BUS_ADDRESS"=>"unix:path=/run/user/10000/bus"}, "user" => "joe"}).and_return(shell_out_success) + expect(provider).to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --user start #{service_name}", { "environment" => { "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/10000/bus" }, "user" => "joe" }).and_return(shell_out_success) provider.start_service end it "should not call '#{systemctl_path} --user start service_name' if it is already running" do current_resource.running(true) current_resource.user("joe") - expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --user start #{service_name}", {"environment"=>{"DBUS_SESSION_BUS_ADDRESS"=>"unix:path=/run/user/10000/bus"}, "user" => "joe"}) + expect(provider).not_to receive(:shell_out_with_systems_locale!).with("#{systemctl_path} --user start #{service_name}", { "environment" => { "DBUS_SESSION_BUS_ADDRESS" => "unix:path=/run/user/10000/bus" }, "user" => "joe" }) provider.start_service end end -- cgit v1.2.1