summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThom May <thom@may.lt>2018-01-29 18:19:32 +0000
committerGitHub <noreply@github.com>2018-01-29 18:19:32 +0000
commitd0d4f0c46ad0898876492ced4ff61d5f9f899bbe (patch)
tree02f1d44b8becce238027507169c7b15f824347be
parent39f9c2e7256c0c5301ae07c1df2889cc0d41754e (diff)
parent06d7c8c54d6134da701d484d2f59a299f67ee9de (diff)
downloadchef-d0d4f0c46ad0898876492ced4ff61d5f9f899bbe.tar.gz
Merge pull request #6804 from jasonwbarnett/feature/add-create-delete-and-configure-actions-to-windows_service
Add create, delete and configure actions to windows_service
-rw-r--r--lib/chef/provider/service/windows.rb268
-rw-r--r--lib/chef/resource/windows_service.rb109
-rw-r--r--lib/chef/win32_service_constants.rb143
-rw-r--r--spec/unit/provider/service/windows_spec.rb451
-rw-r--r--spec/unit/resource/windows_service_spec.rb25
5 files changed, 897 insertions, 99 deletions
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb
index 931e320695..d370ecff51 100644
--- a/lib/chef/provider/service/windows.rb
+++ b/lib/chef/provider/service/windows.rb
@@ -19,6 +19,7 @@
#
require "chef/provider/service/simple"
+require "chef/win32_service_constants"
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
require "chef/win32/error"
require "win32/service"
@@ -30,6 +31,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
include Chef::Mixin::ShellOut
include Chef::ReservedNames::Win32::API::Error rescue LoadError
+ include Chef::Win32ServiceConstants
#Win32::Service.get_start_type
AUTO_START = "auto start"
@@ -50,18 +52,33 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
SERVICE_RIGHT = "SeServiceLogonRight"
def load_current_resource
- @current_resource = Chef::Resource::WindowsService.new(@new_resource.name)
- @current_resource.service_name(@new_resource.service_name)
- @current_resource.running(current_state == RUNNING)
- Chef::Log.debug "#{@new_resource} running: #{@current_resource.running}"
- case current_start_type
- when AUTO_START
- @current_resource.enabled(true)
- when DISABLED
- @current_resource.enabled(false)
+ @current_resource = Chef::Resource::WindowsService.new(new_resource.name)
+ current_resource.service_name(new_resource.service_name)
+
+ if Win32::Service.exists?(current_resource.service_name)
+ current_resource.running(current_state == RUNNING)
+ Chef::Log.debug "#{new_resource} running: #{current_resource.running}"
+ case current_startup_type
+ when :automatic
+ current_resource.enabled(true)
+ when :disabled
+ current_resource.enabled(false)
+ end
+ Chef::Log.debug "#{new_resource} enabled: #{current_resource.enabled}"
+
+ config_info = Win32::Service.config_info(current_resource.service_name)
+ current_resource.service_type(get_service_type(config_info.service_type)) if config_info.service_type
+ current_resource.startup_type(start_type_to_sym(config_info.start_type)) if config_info.start_type
+ current_resource.error_control(get_error_control(config_info.error_control)) if config_info.error_control
+ current_resource.binary_path_name(config_info.binary_path_name) if config_info.binary_path_name
+ current_resource.load_order_group(config_info.load_order_group) if config_info.load_order_group
+ current_resource.dependencies(config_info.dependencies) if config_info.dependencies
+ current_resource.run_as_user(config_info.service_start_name) if config_info.service_start_name
+ current_resource.display_name(config_info.display_name) if config_info.display_name
+ current_resource.delayed_start(current_delayed_start) if current_delayed_start
end
- Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}"
- @current_resource
+
+ current_resource
end
def start_service
@@ -175,8 +192,52 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
end
+ action :create do
+ if Win32::Service.exists?(new_resource.service_name)
+ Chef::Log.debug "#{new_resource} already exists - nothing to do"
+ return
+ end
+
+ converge_by("create service #{new_resource.service_name}") do
+ Win32::Service.new(windows_service_config)
+ end
+
+ converge_delayed_start
+ end
+
+ action :delete do
+ unless Win32::Service.exists?(new_resource.service_name)
+ Chef::Log.debug "#{new_resource} does not exist - nothing to do"
+ return
+ end
+
+ converge_by("delete service #{new_resource.service_name}") do
+ Win32::Service.delete(new_resource.service_name)
+ end
+ end
+
+ action :configure do
+ unless Win32::Service.exists?(new_resource.service_name)
+ Chef::Log.warn "#{new_resource} does not exist. Maybe you need to prepend action :create"
+ return
+ end
+
+ # Until #6300 is solved this is required
+ if new_resource.run_as_user == new_resource.class.properties[:run_as_user].default
+ new_resource.run_as_user = new_resource.class.properties[:run_as_user].default
+ end
+
+ converge_if_changed :service_type, :startup_type, :error_control,
+ :binary_path_name, :load_order_group, :dependencies,
+ :run_as_user, :display_name, :description do
+ Win32::Service.configure(windows_service_config(:configure))
+ end
+
+ converge_delayed_start
+ end
+
def action_enable
- if current_start_type != AUTO_START
+ if current_startup_type != :automatic
converge_by("enable service #{@new_resource}") do
enable_service
Chef::Log.info("#{@new_resource} enabled")
@@ -189,7 +250,7 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
def action_disable
- if current_start_type != DISABLED
+ if current_startup_type != :disabled
converge_by("disable service #{@new_resource}") do
disable_service
Chef::Log.info("#{@new_resource} disabled")
@@ -202,31 +263,13 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
def action_configure_startup
- case @new_resource.startup_type
- when :automatic
- if current_start_type != AUTO_START
- converge_by("set service #{@new_resource} startup type to automatic") do
- set_startup_type(:automatic)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already automatic - nothing to do")
- end
- when :manual
- if current_start_type != MANUAL
- converge_by("set service #{@new_resource} startup type to manual") do
- set_startup_type(:manual)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already manual - nothing to do")
- end
- when :disabled
- if current_start_type != DISABLED
- converge_by("set service #{@new_resource} startup type to disabled") do
- set_startup_type(:disabled)
- end
- else
- Chef::Log.debug("#{@new_resource} startup_type already disabled - nothing to do")
+ startup_type = @new_resource.startup_type
+ if current_startup_type != startup_type
+ converge_by("set service #{@new_resource} startup type to #{startup_type}") do
+ set_startup_type(startup_type)
end
+ else
+ Chef::Log.debug("#{@new_resource} startup_type already #{startup_type} - nothing to do")
end
# Avoid changing enabled from true/false for now
@@ -235,6 +278,14 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
private
+ def current_delayed_start
+ if service = Win32::Service.services.find { |x| x.service_name == new_resource.service_name }
+ service.delayed_start == 0 ? false : true
+ else
+ nil
+ end
+ end
+
def grant_service_logon(username)
begin
Chef::ReservedNames::Win32::Security.add_account_right(canonicalize_username(username), SERVICE_RIGHT)
@@ -260,8 +311,9 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
Win32::Service.status(@new_resource.service_name).current_state
end
- def current_start_type
- Win32::Service.config_info(@new_resource.service_name).start_type
+ def current_startup_type
+ start_type = Win32::Service.config_info(@new_resource.service_name).start_type
+ start_type_to_sym(start_type)
end
# Helper method that waits for a status to change its state since state
@@ -289,21 +341,143 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service
end
end
- # Takes Win32::Service start_types
- def set_startup_type(type)
- # Set-Service Startup Type => Win32::Service Constant
- allowed_types = { :automatic => Win32::Service::AUTO_START,
- :manual => Win32::Service::DEMAND_START,
- :disabled => Win32::Service::DISABLED }
- unless allowed_types.keys.include?(type)
+ # @param type [Symbol]
+ # @return [Integer]
+ # @raise [Chef::Exceptions::ConfigurationError] if the startup type is
+ # not supported.
+ # @see Chef::Resource::WindowsService::ALLOWED_START_TYPES
+ def startup_type_to_int(type)
+ Chef::Resource::WindowsService::ALLOWED_START_TYPES.fetch(type) do
raise Chef::Exceptions::ConfigurationError, "#{@new_resource.name}: Startup type '#{type}' is not supported"
end
+ end
+
+ # Takes Win32::Service start_types
+ def set_startup_type(type)
+ startup_type = startup_type_to_int(type)
Chef::Log.debug "#{@new_resource.name} setting start_type to #{type}"
Win32::Service.configure(
:service_name => @new_resource.service_name,
- :start_type => allowed_types[type]
+ :start_type => startup_type
)
@new_resource.updated_by_last_action(true)
end
+
+ def windows_service_config(action = :create)
+ config = {}
+
+ config[:service_name] = new_resource.service_name
+ config[:display_name] = new_resource.display_name if new_resource.display_name
+ config[:service_type] = new_resource.service_type if new_resource.service_type
+ config[:start_type] = startup_type_to_int(new_resource.startup_type) if new_resource.startup_type
+ config[:error_control] = new_resource.error_control if new_resource.error_control
+ config[:binary_path_name] = new_resource.binary_path_name if new_resource.binary_path_name
+ config[:load_order_group] = new_resource.load_order_group if new_resource.load_order_group
+ config[:dependencies] = new_resource.dependencies if new_resource.dependencies
+ config[:service_start_name] = new_resource.run_as_user unless new_resource.run_as_user.empty?
+ config[:password] = new_resource.run_as_password unless new_resource.run_as_user.empty? || new_resource.run_as_password.empty?
+ config[:description] = new_resource.description if new_resource.description
+
+ case action
+ when :create
+ config[:desired_access] = new_resource.desired_access if new_resource.desired_access
+ end
+
+ config
+ end
+
+ def converge_delayed_start
+ config = {}
+ config[:service_name] = new_resource.service_name
+ config[:delayed_start] = new_resource.delayed_start ? 1 : 0
+
+ # Until #6300 is solved this is required
+ if new_resource.delayed_start == new_resource.class.properties[:delayed_start].default
+ new_resource.delayed_start = new_resource.class.properties[:delayed_start].default
+ end
+
+ converge_if_changed :delayed_start do
+ Win32::Service.configure(config)
+ end
+ end
+
+ # @return [Symbol]
+ def start_type_to_sym(start_type)
+ case start_type
+ when "auto start"
+ :automatic
+ when "boot start"
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ when "demand start"
+ :manual
+ when "disabled"
+ :disabled
+ when "system start"
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ else
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ end
+ end
+
+ def get_service_type(service_type)
+ case service_type
+ when "file system driver"
+ SERVICE_FILE_SYSTEM_DRIVER
+ when "kernel driver"
+ SERVICE_KERNEL_DRIVER
+ when "own process"
+ SERVICE_WIN32_OWN_PROCESS
+ when "share process"
+ SERVICE_WIN32_SHARE_PROCESS
+ when "recognizer driver"
+ SERVICE_RECOGNIZER_DRIVER
+ when "driver"
+ SERVICE_DRIVER
+ when "win32"
+ SERVICE_WIN32
+ when "all"
+ SERVICE_TYPE_ALL
+ when "own process, interactive"
+ SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_OWN_PROCESS
+ when "share process, interactive"
+ SERVICE_INTERACTIVE_PROCESS | SERVICE_WIN32_SHARE_PROCESS
+ else
+ raise("Unsupported service type, #{service_type}. Submit bug request to fix.")
+ end
+ end
+
+ # @return [Integer]
+ def get_start_type(start_type)
+ case start_type
+ when "auto start"
+ SERVICE_AUTO_START
+ when "boot start"
+ SERVICE_BOOT_START
+ when "demand start"
+ SERVICE_DEMAND_START
+ when "disabled"
+ SERVICE_DISABLED
+ when "system start"
+ SERVICE_SYSTEM_START
+ else
+ raise("Unsupported start type, #{start_type}. Submit bug request to fix.")
+ end
+ end
+
+ def get_error_control(error_control)
+ case error_control
+ when "critical"
+ SERVICE_ERROR_CRITICAL
+ when "ignore"
+ SERVICE_ERROR_IGNORE
+ when "normal"
+ SERVICE_ERROR_NORMAL
+ when "severe"
+ SERVICE_ERROR_SEVERE
+ else
+ nil
+ end
+ end
+
end
diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb
index 1c7c61320f..4e14b18c20 100644
--- a/lib/chef/resource/windows_service.rb
+++ b/lib/chef/resource/windows_service.rb
@@ -17,6 +17,7 @@
#
require "chef/resource/service"
+require "chef/win32_service_constants"
class Chef
class Resource
@@ -24,6 +25,13 @@ class Chef
#
# @since 12.0
class WindowsService < Chef::Resource::Service
+ include Chef::Win32ServiceConstants
+
+ ALLOWED_START_TYPES = {
+ automatic: SERVICE_AUTO_START,
+ manual: SERVICE_DEMAND_START,
+ disabled: SERVICE_DISABLED,
+ }
# Until #1773 is resolved, you need to manually specify the windows_service resource
# to use action :configure_startup and attribute startup_type
@@ -31,44 +39,79 @@ class Chef
provides :windows_service, os: "windows"
provides :service, os: "windows"
- allowed_actions :configure_startup
+ allowed_actions :configure_startup, :create, :delete, :configure
identity_attr :service_name
state_attrs :enabled, :running
- def initialize(name, run_context = nil)
- super
- @startup_type = :automatic
- @run_as_user = ""
- @run_as_password = ""
- end
-
- def startup_type(arg = nil)
- # Set-Service arguments are automatic and manual
- # Win32::Service returns 'auto start' or 'demand start' respectively, which the provider currently uses
- set_or_return(
- :startup_type,
- arg,
- :equal_to => [ :automatic, :manual, :disabled ]
- )
- end
-
- def run_as_user(arg = nil)
- set_or_return(
- :run_as_user,
- arg,
- :kind_of => [ String ]
- )
- end
-
- def run_as_password(arg = nil)
- set_or_return(
- :run_as_password,
- arg,
- :kind_of => [ String ]
- )
- end
+ property :service_name, name_property: true
+
+ # The display name to be used by user interface programs to identify the
+ # service. This string has a maximum length of 256 characters.
+ property :display_name, String, regex: /^.{1,256}$/
+
+ # https://github.com/djberg96/win32-service/blob/ffi/lib/win32/windows/constants.rb#L19-L29
+ property :desired_access, Integer, default: SERVICE_ALL_ACCESS
+
+ # https://github.com/djberg96/win32-service/blob/ffi/lib/win32/windows/constants.rb#L31-L41
+ property :service_type, Integer, default: SERVICE_WIN32_OWN_PROCESS
+
+ # Valid options:
+ # - :automatic
+ # - :manual
+ # - :disabled
+ # Reference: https://github.com/djberg96/win32-service/blob/ffi/lib/win32/windows/constants.rb#L49-L54
+ property :startup_type, [Symbol], equal_to: [:automatic, :manual, :disabled], default: :automatic, coerce: proc { |x|
+ if x.is_a?(Integer)
+ ALLOWED_START_TYPES.invert.fetch(x) do
+ Chef::Log.warn("Unsupported startup_type #{x}, falling back to :automatic")
+ :automatic
+ end
+ elsif x.is_a?(String)
+ x.to_sym
+ else
+ x
+ end
+ }
+
+ # This only applies if startup_type is :automatic
+ # 1 == delayed start is enabled
+ # 0 == NO delayed start
+ property :delayed_start, [TrueClass, FalseClass], default: false, coerce: proc { |x|
+ if x.is_a?(Integer)
+ x == 0 ? false : true
+ else
+ x
+ end
+ }
+
+ # https://github.com/djberg96/win32-service/blob/ffi/lib/win32/windows/constants.rb#L43-L47
+ property :error_control, Integer, default: SERVICE_ERROR_NORMAL
+
+ # The fully qualified path to the service binary file. The path can also
+ # include arguments for an auto-start service.
+ #
+ # This is required for :create and :configure actions -- intentionally
+ # not setting required: true here to support other actions
+ property :binary_path_name, String
+
+ # The names of the load ordering group of which this service is a member.
+ # Specify nil or an empty string if the service does not belong to a group.
+ property :load_order_group, String
+
+ # A pointer to a double null-terminated array of null-separated names of
+ # services or load ordering groups that the system must start before this
+ # service. Specify nil or an empty string if the service has no
+ # dependencies. Dependency on a group means that this service can run if
+ # at least one member of the group is running after an attempt to start
+ # all members of the group.
+ property :dependencies, [String, Array]
+
+ property :description, String
+
+ property :run_as_user, String, default: "LocalSystem"
+ property :run_as_password, String, default: ""
end
end
end
diff --git a/lib/chef/win32_service_constants.rb b/lib/chef/win32_service_constants.rb
new file mode 100644
index 0000000000..4b5eb34327
--- /dev/null
+++ b/lib/chef/win32_service_constants.rb
@@ -0,0 +1,143 @@
+class Chef
+ module Win32ServiceConstants
+ SC_MANAGER_ALL_ACCESS = 0xF003F
+ SC_MANAGER_CREATE_SERVICE = 0x0002
+ SC_MANAGER_CONNECT = 0x0001
+ SC_MANAGER_ENUMERATE_SERVICE = 0x0004
+ SC_MANAGER_LOCK = 0x0008
+ SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020
+ SC_MANAGER_QUERY_LOCK_STATUS = 0x0010
+ SC_STATUS_PROCESS_INFO = 0
+ SC_ENUM_PROCESS_INFO = 0
+
+ # Service control action types
+ SC_ACTION_NONE = 0
+ SC_ACTION_RESTART = 1
+ SC_ACTION_REBOOT = 2
+ SC_ACTION_RUN_COMMAND = 3
+
+ # Service access rights
+ SERVICE_ALL_ACCESS = 0xF01FF
+ SERVICE_CHANGE_CONFIG = 0x0002
+ SERVICE_ENUMERATE_DEPENDENTS = 0x0008
+ SERVICE_INTERROGATE = 0x0080
+ SERVICE_PAUSE_CONTINUE = 0x0040
+ SERVICE_QUERY_CONFIG = 0x0001
+ SERVICE_QUERY_STATUS = 0x0004
+ SERVICE_START = 0x0010
+ SERVICE_STOP = 0x0020
+ SERVICE_USER_DEFINED_CONTROL = 0x0100
+
+ # Service types
+ SERVICE_KERNEL_DRIVER = 0x00000001
+ SERVICE_FILE_SYSTEM_DRIVER = 0x00000002
+ SERVICE_ADAPTER = 0x00000004
+ SERVICE_RECOGNIZER_DRIVER = 0x00000008
+ SERVICE_WIN32_OWN_PROCESS = 0x00000010
+ SERVICE_WIN32_SHARE_PROCESS = 0x00000020
+ SERVICE_WIN32 = 0x00000030
+ SERVICE_INTERACTIVE_PROCESS = 0x00000100
+ SERVICE_DRIVER = 0x0000000B
+ SERVICE_TYPE_ALL = 0x0000013F
+
+ # Error control
+ SERVICE_ERROR_IGNORE = 0x00000000
+ SERVICE_ERROR_NORMAL = 0x00000001
+ SERVICE_ERROR_SEVERE = 0x00000002
+ SERVICE_ERROR_CRITICAL = 0x00000003
+
+ # Start types
+ SERVICE_BOOT_START = 0x00000000
+ SERVICE_SYSTEM_START = 0x00000001
+ SERVICE_AUTO_START = 0x00000002
+ SERVICE_DEMAND_START = 0x00000003
+ SERVICE_DISABLED = 0x00000004
+
+ # Service control
+
+ SERVICE_CONTROL_STOP = 0x00000001
+ SERVICE_CONTROL_PAUSE = 0x00000002
+ SERVICE_CONTROL_CONTINUE = 0x00000003
+ SERVICE_CONTROL_INTERROGATE = 0x00000004
+ SERVICE_CONTROL_SHUTDOWN = 0x00000005
+ SERVICE_CONTROL_PARAMCHANGE = 0x00000006
+ SERVICE_CONTROL_NETBINDADD = 0x00000007
+ SERVICE_CONTROL_NETBINDREMOVE = 0x00000008
+ SERVICE_CONTROL_NETBINDENABLE = 0x00000009
+ SERVICE_CONTROL_NETBINDDISABLE = 0x0000000A
+ SERVICE_CONTROL_DEVICEEVENT = 0x0000000B
+ SERVICE_CONTROL_HARDWAREPROFILECHANGE = 0x0000000C
+ SERVICE_CONTROL_POWEREVENT = 0x0000000D
+ SERVICE_CONTROL_SESSIONCHANGE = 0x0000000E
+ SERVICE_CONTROL_PRESHUTDOWN = 0x0000000F
+ SERVICE_CONTROL_TIMECHANGE = 0x00000010
+ SERVICE_CONTROL_TRIGGEREVENT = 0x00000020
+
+ # Service controls accepted
+
+ SERVICE_ACCEPT_STOP = 0x00000001
+ SERVICE_ACCEPT_PAUSE_CONTINUE = 0x00000002
+ SERVICE_ACCEPT_SHUTDOWN = 0x00000004
+ SERVICE_ACCEPT_PARAMCHANGE = 0x00000008
+ SERVICE_ACCEPT_NETBINDCHANGE = 0x00000010
+ SERVICE_ACCEPT_HARDWAREPROFILECHANGE = 0x00000020
+ SERVICE_ACCEPT_POWEREVENT = 0x00000040
+ SERVICE_ACCEPT_SESSIONCHANGE = 0x00000080
+ SERVICE_ACCEPT_PRESHUTDOWN = 0x00000100
+ SERVICE_ACCEPT_TIMECHANGE = 0x00000200
+ SERVICE_ACCEPT_TRIGGEREVENT = 0x00000400
+
+ # Service states
+ SERVICE_ACTIVE = 0x00000001
+ SERVICE_INACTIVE = 0x00000002
+ SERVICE_STATE_ALL = 0x00000003
+
+ # Service current states
+ SERVICE_STOPPED = 0x00000001
+ SERVICE_START_PENDING = 0x00000002
+ SERVICE_STOP_PENDING = 0x00000003
+ SERVICE_RUNNING = 0x00000004
+ SERVICE_CONTINUE_PENDING = 0x00000005
+ SERVICE_PAUSE_PENDING = 0x00000006
+ SERVICE_PAUSED = 0x00000007
+
+ # Info levels
+ SERVICE_CONFIG_DESCRIPTION = 1
+ SERVICE_CONFIG_FAILURE_ACTIONS = 2
+ SERVICE_CONFIG_DELAYED_AUTO_START_INFO = 3
+ SERVICE_CONFIG_FAILURE_ACTIONS_FLAG = 4
+ SERVICE_CONFIG_SERVICE_SID_INFO = 5
+ SERVICE_CONFIG_REQUIRED_PRIVILEGES_INFO = 6
+ SERVICE_CONFIG_PRESHUTDOWN_INFO = 7
+
+ # Configuration
+ SERVICE_NO_CHANGE = 0xffffffff
+
+ # Misc
+
+ WAIT_OBJECT_0 = 0
+ WAIT_TIMEOUT = 0x00000102
+ INFINITE = 0xFFFFFFFF
+
+ IDLE_CONTROL_CODE = 0
+
+ DELETE = 0x00010000
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
+ FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200
+
+ NO_ERROR = 0
+
+ SE_PRIVILEGE_ENABLED = 0x00000002
+ TOKEN_ADJUST_PRIVILEGES = 0x0020
+ TOKEN_QUERY = 0x0008
+
+ # Errors
+
+ ERROR_INSUFFICIENT_BUFFER = 122
+ ERROR_MORE_DATA = 234
+ ERROR_FILE_NOT_FOUND = 2
+ ERROR_RESOURCE_TYPE_NOT_FOUND = 1813
+ ERROR_RESOURCE_NAME_NOT_FOUND = 1814
+ WAIT_FAILED = 0xFFFFFFFF
+ end
+end
diff --git a/spec/unit/provider/service/windows_spec.rb b/spec/unit/provider/service/windows_spec.rb
index d4c451511d..edc409fab0 100644
--- a/spec/unit/provider/service/windows_spec.rb
+++ b/spec/unit/provider/service/windows_spec.rb
@@ -23,7 +23,59 @@ require "mixlib/shellout"
describe Chef::Provider::Service::Windows, "load_current_resource" do
include_context "Win32"
- let(:new_resource) { Chef::Resource::WindowsService.new("chef") }
+ let(:chef_service_name) { "chef-client" }
+ let(:new_resource) { Chef::Resource::WindowsService.new(chef_service_name) }
+
+ # Actual response from Win32::Service.config_info('chef-client')
+ let(:chef_service_binary_path_name) do
+ 'C:\\opscode\\chef\\embedded\\bin\\ruby.exe C:\\opscode\\chef\\bin\\chef-windows-service'
+ end
+ let(:chef_service_config_info) do
+ double("Struct::ServiceConfigInfo",
+ service_type: "own process",
+ start_type: "auto start",
+ error_control: "ignore",
+ binary_path_name: chef_service_binary_path_name,
+ load_order_group: "",
+ tag_id: 0,
+ dependencies: ["Winmgmt"],
+ service_start_name: "LocalSystem",
+ display_name: "Chef Client Service"
+ )
+ end
+
+ # Actual response from Win32::Service.services
+ let(:chef_service_info) do
+ double("Struct::ServiceInfo",
+ service_name: chef_service_name,
+ display_name: "Chef Client Service",
+ service_type: "own process",
+ current_state: "running",
+ controls_accepted: [],
+ win32_exit_code: 1077,
+ service_specific_exit_code: 0,
+ check_point: 0,
+ wait_hint: 0,
+ binary_path_name: chef_service_binary_path_name,
+ start_type: "auto start",
+ error_control: "ignore",
+ load_order_group: "",
+ tag_id: 0,
+ start_name: "LocalSystem",
+ dependencies: ["Winmgmt"],
+ description: "Runs Chef Client on regular, configurable intervals.",
+ interactive: false,
+ pid: 0,
+ service_flags: 0,
+ reset_period: 0,
+ reboot_message: nil,
+ command: nil,
+ num_actions: 0,
+ actions: nil,
+ delayed_start: 1
+ )
+ end
+
let(:provider) do
prvdr = Chef::Provider::Service::Windows.new(new_resource,
Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new))
@@ -43,11 +95,35 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
allow(Win32::Service).to receive(:status).with(new_resource.service_name).and_return(
double("StatusStruct", :current_state => "running"))
- allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
- double("ConfigStruct", :start_type => "auto start"))
+ allow(Win32::Service).to receive(:config_info).with(new_resource.service_name)
+ .and_return(chef_service_config_info)
+
+ # Real response from Win32::Service.services
+ allow(Win32::Service).to receive(:services).and_return([
+ # Add chef_service_info to our stubbed response so our tests that are expecting the service to exist work
+ chef_service_info,
+ double("Struct::ServiceInfo", service_name: "ACPI", display_name: "Microsoft ACPI Driver", service_type: "kernel driver", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: '\\SystemRoot\\System32\\drivers\\ACPI.sys', start_type: "boot start", error_control: "critical", load_order_group: "Core", tag_id: 2, start_name: "", dependencies: [], description: "", interactive: false, pid: 0, service_flags: 0, reset_period: 0, reboot_message: nil, command: nil, num_actions: 0, actions: nil, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "cdrom", display_name: "CD-ROM Driver", service_type: "kernel driver", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: '\\SystemRoot\\System32\\drivers\\cdrom.sys', start_type: "system start", error_control: "normal", load_order_group: "SCSI CDROM Class", tag_id: 3, start_name: "", dependencies: [], description: "", interactive: false, pid: 0, service_flags: 0, reset_period: 0, reboot_message: nil, command: nil, num_actions: 0, actions: nil, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "CryptSvc", display_name: "Cryptographic Services", service_type: "share process", current_state: "running", controls_accepted: ["shutdown", "stop", "session change"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k NetworkService', start_type: "auto start", error_control: "normal", load_order_group: "", tag_id: 0, start_name: 'NT Authority\\NetworkService', dependencies: ["RpcSs"], description: "Provides three management services: Catalog Database Service, which confirms the signatures of Windows files and allows new programs to be installed; Protected Root Service, which adds and removes Trusted Root Certification Authority certificates from this computer; and Automatic Root Certificate Update Service, which retrieves root certificates from Windows Update and enable scenarios such as SSL. If this service is stopped, these management services will not function properly. If this service is disabled, any services that explicitly depend on it will fail to start.", interactive: false, pid: 932, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 60000 }, 2 => { action_type: "none", delay: 0 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "DcomLaunch", display_name: "DCOM Server Process Launcher", service_type: "share process", current_state: "running", controls_accepted: ["session change"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k DcomLaunch', start_type: "auto start", error_control: "normal", load_order_group: "COM Infrastructure", tag_id: 0, start_name: "LocalSystem", dependencies: [], description: "The DCOMLAUNCH service launches COM and DCOM servers in response to object activation requests. If this service is stopped or disabled, programs using COM or DCOM will not function properly. It is strongly recommended that you have the DCOMLAUNCH service running.", interactive: false, pid: 552, service_flags: 0, reset_period: 0, reboot_message: nil, command: nil, num_actions: 1, actions: { 1 => { action_type: "reboot", delay: 60000 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "Dfsc", display_name: "DFS Namespace Client Driver", service_type: "file system driver", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'System32\\Drivers\\dfsc.sys', start_type: "system start", error_control: "normal", load_order_group: "Network", tag_id: 0, start_name: "", dependencies: ["Mup"], description: "Client driver for access to DFS Namespaces", interactive: false, pid: 0, service_flags: 0, reset_period: 0, reboot_message: nil, command: nil, num_actions: 0, actions: nil, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "Dhcp", display_name: "DHCP Client", service_type: "share process", current_state: "running", controls_accepted: %w{shutdown stop}, win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k LocalServiceNetworkRestricted', start_type: "auto start", error_control: "normal", load_order_group: "TDI", tag_id: 0, start_name: 'NT Authority\\LocalService', dependencies: %w{NSI Tdx Afd}, description: "Registers and updates IP addresses and DNS records for this computer. If this service is stopped, this computer will not receive dynamic IP addresses and DNS updates. If this service is disabled, any services that explicitly depend on it will fail to start.", interactive: false, pid: 780, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 120000 }, 2 => { action_type: "restart", delay: 300000 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "EventLog", display_name: "Windows Event Log", service_type: "share process", current_state: "running", controls_accepted: %w{shutdown stop}, win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\System32\\svchost.exe -k LocalServiceNetworkRestricted', start_type: "auto start", error_control: "normal", load_order_group: "Event Log", tag_id: 0, start_name: 'NT AUTHORITY\\LocalService', dependencies: [], description: "This service manages events and event logs. It supports logging events, querying events, subscribing to events, archiving event logs, and managing event metadata. It can display events in both XML and plain text format. Stopping this service may compromise security and reliability of the system.", interactive: false, pid: 780, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 60000 }, 2 => { action_type: "restart", delay: 120000 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "EventSystem", display_name: "COM+ Event System", service_type: "share process", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k LocalService', start_type: "auto start", error_control: "normal", load_order_group: "", tag_id: 0, start_name: 'NT AUTHORITY\\LocalService', dependencies: ["rpcss"], description: "Supports System Event Notification Service (SENS), which provides automatic distribution of events to subscribing Component Object Model (COM) components. If the service is stopped, SENS will close and will not be able to provide logon and logoff notifications. If this service is disabled, any services that explicitly depend on it will fail to start.", interactive: false, pid: 844, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 1000 }, 2 => { action_type: "restart", delay: 5000 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "LanmanWorkstation", display_name: "Workstation", service_type: "share process", current_state: "running", controls_accepted: ["pause continue", "stop", "power event"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\System32\\svchost.exe -k NetworkService', start_type: "auto start", error_control: "normal", load_order_group: "NetworkProvider", tag_id: 0, start_name: 'NT AUTHORITY\\NetworkService', dependencies: %w{Bowser MRxSmb20 NSI}, description: "Creates and maintains client network connections to remote servers using the SMB protocol. If this service is stopped, these connections will be unavailable. If this service is disabled, any services that explicitly depend on it will fail to start.", interactive: false, pid: 932, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 60000 }, 2 => { action_type: "restart", delay: 120000 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "lmhosts", display_name: "TCP/IP NetBIOS Helper", service_type: "share process", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k LocalServiceNetworkRestricted', start_type: "auto start", error_control: "normal", load_order_group: "TDI", tag_id: 0, start_name: 'NT AUTHORITY\\LocalService', dependencies: %w{NetBT Afd}, description: "Provides support for the NetBIOS over TCP/IP (NetBT) service and NetBIOS name resolution for clients on the network, therefore enabling users to share files, print, and log on to the network. If this service is stopped, these functions might be unavailable. If this service is disabled, any services that explicitly depend on it will fail to start.", interactive: false, pid: 780, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 100 }, 2 => { action_type: "restart", delay: 100 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "MpsSvc", display_name: "Windows Firewall", service_type: "share process", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k LocalServiceNoNetwork', start_type: "auto start", error_control: "normal", load_order_group: "NetworkProvider", tag_id: 0, start_name: 'NT Authority\\LocalService', dependencies: %w{mpsdrv bfe}, description: "Windows Firewall helps protect your computer by preventing unauthorized users from gaining access to your computer through the Internet or a network.", interactive: false, pid: 340, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 120000 }, 2 => { action_type: "restart", delay: 300000 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "mrxsmb", display_name: "SMB MiniRedirector Wrapper and Engine", service_type: "file system driver", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'system32\\DRIVERS\\mrxsmb.sys', start_type: "demand start", error_control: "normal", load_order_group: "Network", tag_id: 5, start_name: "", dependencies: ["rdbss"], description: "Implements the framework for the SMB filesystem redirector", interactive: false, pid: 0, service_flags: 0, reset_period: 0, reboot_message: nil, command: nil, num_actions: 0, actions: nil, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "Ntfs", display_name: "Ntfs", service_type: "file system driver", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: "", start_type: "demand start", error_control: "normal", load_order_group: "Boot File System", tag_id: 0, start_name: "", dependencies: [], description: "", interactive: false, pid: 0, service_flags: 0, reset_period: 0, reboot_message: nil, command: nil, num_actions: 0, actions: nil, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "Tcpip", display_name: "TCP/IP Protocol Driver", service_type: "kernel driver", current_state: "running", controls_accepted: ["stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: '\\SystemRoot\\System32\\drivers\\tcpip.sys', start_type: "boot start", error_control: "normal", load_order_group: "PNP_TDI", tag_id: 3, start_name: "", dependencies: [], description: "TCP/IP Protocol Driver", interactive: false, pid: 0, service_flags: 0, reset_period: 0, reboot_message: nil, command: nil, num_actions: 0, actions: nil, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "W32Time", display_name: "Windows Time", service_type: "share process", current_state: "running", controls_accepted: ["netbind change", "param change", "shutdown", "stop", "hardware profile change", "power event"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k LocalService', start_type: "demand start", error_control: "normal", load_order_group: "", tag_id: 0, start_name: 'NT AUTHORITY\\LocalService', dependencies: [], description: "Maintains date and time synchronization on all clients and servers in the network. If this service is stopped, date and time synchronization will be unavailable. If this service is disabled, any services that explicitly depend on it will fail to start.", interactive: false, pid: 844, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 60000 }, 2 => { action_type: "restart", delay: 120000 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "Winmgmt", display_name: "Windows Management Instrumentation", service_type: "share process", current_state: "running", controls_accepted: ["pause continue", "shutdown", "stop"], win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k netsvcs', start_type: "auto start", error_control: "ignore", load_order_group: "", tag_id: 0, start_name: "localSystem", dependencies: ["RPCSS"], description: "Provides a common interface and object model to access management information about operating system, devices, applications and services. If this service is stopped, most Windows-based software will not function properly. If this service is disabled, any services that explicitly depend on it will fail to start.", interactive: false, pid: 812, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 120000 }, 2 => { action_type: "restart", delay: 300000 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ double("Struct::ServiceInfo", service_name: "WinRM", display_name: "Windows Remote Management (WS-Management)", service_type: "share process", current_state: "running", controls_accepted: %w{shutdown stop}, win32_exit_code: 0, service_specific_exit_code: 0, check_point: 0, wait_hint: 0, binary_path_name: 'C:\\Windows\\System32\\svchost.exe -k NetworkService', start_type: "auto start", error_control: "normal", load_order_group: "", tag_id: 0, start_name: 'NT AUTHORITY\\NetworkService', dependencies: %w{RPCSS HTTP}, description: "Windows Remote Management (WinRM) service implements the WS-Management protocol for remote management. WS-Management is a standard web services protocol used for remote software and hardware management. The WinRM service listens on the network for WS-Management requests and processes them. The WinRM Service needs to be configured with a listener using winrm.cmd command line tool or through Group Policy in order for it to listen over the network. The WinRM service provides access to WMI data and enables event collection. Event collection and subscription to events require that the service is running. WinRM messages use HTTP and HTTPS as transports. The WinRM service does not depend on IIS but is preconfigured to share a port with IIS on the same machine. The WinRM service reserves the /wsman URL prefix. To prevent conflicts with IIS, administrators should ensure that any websites hosted on IIS do not use the /wsman URL prefix.", interactive: false, pid: 932, service_flags: 0, reset_period: 86400, reboot_message: nil, command: nil, num_actions: 3, actions: { 1 => { action_type: "restart", delay: 120000 }, 2 => { action_type: "restart", delay: 300000 }, 3 => { action_type: "none", delay: 0 } }, delayed_start: 0),
+ ])
allow(Win32::Service).to receive(:exists?).and_return(true)
allow(Win32::Service).to receive(:configure).and_return(Win32::Service)
allow(Chef::ReservedNames::Win32::Security).to receive(:get_account_right).and_return([])
+ allow(Chef::ReservedNames::Win32::Security).to receive(:add_account_right).with("LocalSystem", "SeServiceLogonRight").and_return(0)
end
after(:each) do
@@ -58,28 +134,365 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
it "sets the current resources service name to the new resources service name" do
provider.load_current_resource
- expect(provider.current_resource.service_name).to eq("chef")
+ expect(provider.current_resource.service_name).to eq(chef_service_name)
end
it "returns the current resource" do
expect(provider.load_current_resource).to equal(provider.current_resource)
end
- it "sets the current resources status" do
- provider.load_current_resource
- expect(provider.current_resource.running).to be_truthy
- end
-
it "sets the current resources start type" do
provider.load_current_resource
expect(provider.current_resource.enabled).to be_truthy
end
- it "does not set the current resources start type if it is neither AUTO START or DISABLED" do
- allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
- double("ConfigStruct", :start_type => "manual"))
- provider.load_current_resource
- expect(provider.current_resource.enabled).to be_nil
+ context "service does not exist" do
+ before do
+ allow(Win32::Service).to receive(:exists?).with(chef_service_name).and_return(false)
+ end
+
+ %w{running enabled startup_type error_control binary_path_name
+ load_order_group dependencies run_as_user display_name }.each do |prop|
+ it "does not set #{prop}" do
+ expect(provider.current_resource.running).to be_nil
+ end
+ end
+ end
+
+ context "service exists" do
+ before do
+ allow(Win32::Service).to receive(:exists?).with(chef_service_name).and_return(true)
+ allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
+ double("Struct::ServiceConfigInfo",
+ service_type: "share process",
+ start_type: "demand start",
+ error_control: "normal",
+ binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k LocalServiceNetworkRestricted',
+ load_order_group: "TDI",
+ tag_id: 0,
+ dependencies: %w{NSI Tdx Afd},
+ service_start_name: 'NT Authority\\LocalService',
+ display_name: "DHCP Client"
+ ))
+ end
+
+ context "startup_type is neither :automatic or :disabled" do
+ before do
+ allow(Win32::Service).to receive(:config_info).with(new_resource.service_name).and_return(
+ double("Struct::ServiceConfigInfo",
+ service_type: "share process",
+ start_type: "demand start",
+ error_control: "normal",
+ binary_path_name: 'C:\\Windows\\system32\\svchost.exe -k LocalServiceNetworkRestricted',
+ load_order_group: "TDI",
+ tag_id: 0,
+ dependencies: %w{NSI Tdx Afd},
+ service_start_name: 'NT Authority\\LocalService',
+ display_name: "DHCP Client"
+ ))
+ end
+
+ it "does not set the current resources enabled" do
+ provider.load_current_resource
+ expect(provider.current_resource.enabled).to be_nil
+ end
+ end
+
+ it "sets the current resources running to true if it's running" do
+ allow(provider).to receive(:current_state).and_return("running")
+ provider.load_current_resource
+ expect(provider.current_resource.running).to be true
+ end
+
+ it "sets the current resources running to false if it's in any other state" do
+ allow(provider).to receive(:current_state).and_return("other state")
+ provider.load_current_resource
+ expect(provider.current_resource.running).to be false
+ end
+
+ it "sets startup_type" do
+ expect(provider.current_resource.startup_type).to be_truthy
+ end
+
+ it "sets error_control" do
+ provider.load_current_resource
+ expect(provider.current_resource.error_control).to be_truthy
+ end
+
+ it "sets binary_path_name" do
+ provider.load_current_resource
+ expect(provider.current_resource.binary_path_name).to be_truthy
+ end
+
+ it "sets load_order_group" do
+ provider.load_current_resource
+ expect(provider.current_resource.load_order_group).to be_truthy
+ end
+
+ it "sets dependencies" do
+ provider.load_current_resource
+ expect(provider.current_resource.dependencies).to be_truthy
+ end
+
+ it "sets run_as_user" do
+ provider.load_current_resource
+ expect(provider.current_resource.run_as_user).to be_truthy
+ end
+
+ it "sets display_name" do
+ provider.load_current_resource
+ expect(provider.current_resource.display_name).to be_truthy
+ end
+
+ it "sets delayed start to true if delayed start is enabled" do
+ allow(chef_service_info).to receive(:delayed_start).and_return(1)
+ provider.load_current_resource
+ expect(provider.current_resource.delayed_start).to be true
+ end
+
+ it "sets delayed start to false if delayed start is disabled" do
+ allow(chef_service_info).to receive(:delayed_start).and_return(0)
+ provider.load_current_resource
+ expect(provider.current_resource.delayed_start).to be false
+ end
+ end
+
+ describe Chef::Provider::Service::Windows, "action_create" do
+ before do
+ provider.new_resource.binary_path_name = chef_service_binary_path_name
+ end
+
+ context "service exists" do
+ before do
+ allow(Win32::Service).to receive(:exists?).with(chef_service_name).and_return(true)
+ end
+
+ it "logs debug message" do
+ expect(Chef::Log).to receive(:debug).with("windows_service[#{chef_service_name}] already exists - nothing to do")
+ provider.action_create
+ end
+
+ it "does not converge" do
+ provider.action_create
+ expect(provider.resource_updated?).to be false
+ end
+
+ it "does not create service" do
+ expect(Win32::Service).to_not receive(:new)
+ provider.action_create
+ end
+
+ it "does not call converge_delayed_start" do
+ expect(provider).to_not receive(:converge_delayed_start)
+ provider.action_create
+ end
+ end
+
+ context "service does not exist" do
+ before do
+ allow(Win32::Service).to receive(:exists?).with(chef_service_name).and_return(false)
+ allow(Win32::Service).to receive(:new).with(anything).and_return(true)
+ end
+
+ it "converges resource" do
+ provider.action_create
+ expect(provider.resource_updated?).to be true
+ end
+
+ it "creates service" do
+ expect(Win32::Service).to receive(:new)
+ provider.action_create
+ end
+
+ it "creates service with correct configuration" do
+ expect(Win32::Service).to receive(:new).with(
+ service_name: chef_service_name,
+ service_type: 16,
+ start_type: 2,
+ error_control: 1,
+ binary_path_name: chef_service_binary_path_name,
+ service_start_name: "LocalSystem",
+ desired_access: 983551
+ )
+ provider.action_create
+ end
+
+ it "calls converge_delayed_start" do
+ expect(provider).to receive(:converge_delayed_start)
+ provider.action_create
+ end
+ end
+ end
+
+ describe Chef::Provider::Service::Windows, "action_delete" do
+ context "service exists" do
+ before do
+ allow(Win32::Service).to receive(:exists?).with(chef_service_name).and_return(true)
+ allow(Win32::Service).to receive(:delete).with(chef_service_name).and_return(true)
+ end
+
+ it "converges resource" do
+ provider.action_delete
+ expect(provider.resource_updated?).to be true
+ end
+
+ it "deletes service" do
+ expect(Win32::Service).to receive(:delete).with(chef_service_name)
+ provider.action_delete
+ end
+ end
+
+ context "service does not exist" do
+ before do
+ allow(Win32::Service).to receive(:exists?).with(chef_service_name).and_return(false)
+ end
+
+ it "logs debug message" do
+ expect(Chef::Log).to receive(:debug).with("windows_service[#{chef_service_name}] does not exist - nothing to do")
+ provider.action_delete
+ end
+
+ it "does not converge" do
+ provider.action_delete
+ expect(provider.resource_updated?).to be false
+ end
+
+ it "does not delete service" do
+ expect(Win32::Service).to_not receive(:delete)
+ provider.action_delete
+ end
+ end
+ end
+
+ describe Chef::Provider::Service::Windows, "action_configure" do
+ context "service exists" do
+ before do
+ allow(Win32::Service).to receive(:exists?).with(chef_service_name).and_return(true)
+ allow(Win32::Service).to receive(:configure).with(anything).and_return(true)
+ end
+
+ it "works around #6300 if run_as_user is default" do
+ new_resource.run_as_user = new_resource.class.properties[:run_as_user].default
+ expect(provider.new_resource).to receive(:run_as_user=)
+ .with(new_resource.class.properties[:run_as_user].default)
+ provider.action_configure
+ end
+
+ # Attributes that are Strings
+ %i{binary_path_name load_order_group dependencies run_as_user
+ display_name description}.each do |attr|
+ it "configures service if #{attr} has changed" do
+ provider.current_resource.send("#{attr}=", "old value")
+ provider.new_resource.send("#{attr}=", "new value")
+
+ expect(Win32::Service).to receive(:configure)
+ provider.action_configure
+ end
+ end
+
+ # Attributes that are Integers
+ %i{service_type error_control}.each do |attr|
+ it "configures service if #{attr} has changed" do
+ provider.current_resource.send("#{attr}=", 1)
+ provider.new_resource.send("#{attr}=", 2)
+
+ expect(Win32::Service).to receive(:configure)
+ provider.action_configure
+ end
+ end
+
+ it "configures service if startup_type has changed" do
+ provider.current_resource.startup_type = :automatic
+ provider.new_resource.startup_type = :manual
+
+ expect(Win32::Service).to receive(:configure)
+ provider.action_configure
+ end
+
+ it "calls converge_delayed_start" do
+ expect(provider).to receive(:converge_delayed_start)
+ provider.action_configure
+ end
+ end
+
+ context "service does not exist" do
+ before do
+ allow(Win32::Service).to receive(:exists?).with(chef_service_name).and_return(false)
+ end
+
+ it "logs warning" do
+ expect(Chef::Log).to receive(:warn)
+ .with("windows_service[#{chef_service_name}] does not exist. Maybe you need to prepend action :create")
+ provider.action_configure
+ end
+
+ it "does not converge" do
+ provider.action_configure
+ expect(provider.resource_updated?).to be false
+ end
+
+ it "does not convigure service" do
+ expect(Win32::Service).to_not receive(:configure)
+ provider.action_configure
+ end
+
+ it "does not call converge_delayed_start" do
+ expect(provider).to_not receive(:converge_delayed_start)
+ provider.action_configure
+ end
+ end
+ end
+
+ describe Chef::Provider::Service::Windows, "converge_delayed_start" do
+ before do
+ allow(Win32::Service).to receive(:configure).and_return(true)
+ end
+
+ it "works around #6300 if delayed_start is default" do
+ new_resource.delayed_start = new_resource.class.properties[:delayed_start].default
+ expect(provider.new_resource).to receive(:delayed_start=)
+ .with(new_resource.class.properties[:delayed_start].default)
+ provider.send(:converge_delayed_start)
+ end
+
+ context "delayed start needs to be updated" do
+ before do
+ provider.current_resource.delayed_start = false
+ provider.new_resource.delayed_start = true
+ end
+
+ it "configures delayed start" do
+ expect(Win32::Service).to receive(:configure)
+ provider.send(:converge_delayed_start)
+ end
+
+ it "configures delayed start with correct params" do
+ expect(Win32::Service).to receive(:configure).with(service_name: chef_service_name, delayed_start: 1)
+ provider.send(:converge_delayed_start)
+ end
+
+ it "converges resource" do
+ provider.send(:converge_delayed_start)
+ expect(provider.resource_updated?).to be true
+ end
+ end
+
+ context "delayed start does not need to be updated" do
+ before do
+ provider.current_resource.delayed_start = false
+ provider.new_resource.delayed_start = false
+ end
+
+ it "does not configure delayed start" do
+ expect(Win32::Service).to_not receive(:configure)
+ provider.send(:converge_delayed_start)
+ end
+
+ it "does not converge" do
+ provider.send(:converge_delayed_start)
+ expect(provider.resource_updated?).to be false
+ end
+ end
end
describe Chef::Provider::Service::Windows, "start_service" do
@@ -90,7 +503,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
end
it "calls the start command if one is specified" do
- new_resource.start_command "sc start chef"
+ new_resource.start_command "sc start #{chef_service_name}"
expect(provider).to receive(:shell_out!).with("#{new_resource.start_command}").and_return("Starting custom service")
provider.start_service
expect(new_resource.updated_by_last_action?).to be_truthy
@@ -185,7 +598,7 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
end
it "calls the stop command if one is specified" do
- new_resource.stop_command "sc stop chef"
+ new_resource.stop_command "sc stop #{chef_service_name}"
expect(provider).to receive(:shell_out!).with("#{new_resource.stop_command}").and_return("Stopping custom service")
provider.stop_service
expect(new_resource.updated_by_last_action?).to be_truthy
@@ -369,17 +782,17 @@ describe Chef::Provider::Service::Windows, "load_current_resource" do
end
describe Chef::Provider::Service::Windows, "action_configure_startup" do
- { :automatic => "auto start", :manual => "demand start", :disabled => "disabled" }.each do |type, win32|
+ %i{automatic manual disabled}.each do |type|
it "sets the startup type to #{type} if it is something else" do
new_resource.startup_type(type)
- allow(provider).to receive(:current_start_type).and_return("fire")
+ allow(provider).to receive(:current_startup_type).and_return(:fire)
expect(provider).to receive(:set_startup_type).with(type)
provider.action_configure_startup
end
it "leaves the startup type as #{type} if it is already set" do
new_resource.startup_type(type)
- allow(provider).to receive(:current_start_type).and_return(win32)
+ allow(provider).to receive(:current_startup_type).and_return(type)
expect(provider).not_to receive(:set_startup_type).with(type)
provider.action_configure_startup
end
diff --git a/spec/unit/resource/windows_service_spec.rb b/spec/unit/resource/windows_service_spec.rb
index f192d8375c..f5de5b6965 100644
--- a/spec/unit/resource/windows_service_spec.rb
+++ b/spec/unit/resource/windows_service_spec.rb
@@ -42,4 +42,29 @@ describe Chef::Resource::WindowsService, "initialize" do
resource.action :configure_startup
expect(resource.action).to eq([:configure_startup])
end
+
+ # Attributes that are Strings
+ %i{description service_name binary_path_name load_order_group dependencies
+ run_as_user run_as_password display_name}.each do |prop|
+ it "support setting #{prop}" do
+ resource.send("#{prop}=", "some value")
+ expect(resource.send(prop)).to eq("some value")
+ end
+ end
+
+ # Attributes that are Integers
+ %i{desired_access error_control service_type}.each do |prop|
+ it "support setting #{prop}" do
+ resource.send("#{prop}=", 1)
+ expect(resource.send(prop)).to eq(1)
+ end
+ end
+
+ # Attributes that are Booleans
+ %i{delayed_start}.each do |prop|
+ it "support setting #{prop}" do
+ resource.send("#{prop}=", true)
+ expect(resource.send(prop)).to eq(true)
+ end
+ end
end