diff options
author | Jason Barnett <jason.w.barnett@gmail.com> | 2017-11-22 12:59:00 -0500 |
---|---|---|
committer | Jason Barnett <jason.w.barnett@gmail.com> | 2017-11-23 17:28:19 -0500 |
commit | b1b889406efaf65940726c5f6ee2316b785fa600 (patch) | |
tree | ed5932cff675e9055a4c87e52fc36ed82e1389e8 /lib | |
parent | 92448e24b146e0f01063ac0b51bcfa2c621d0b3b (diff) | |
download | chef-b1b889406efaf65940726c5f6ee2316b785fa600.tar.gz |
add :create, :delete, and :configure actions to the windows_service resource.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the
best of my knowledge, is covered under an appropriate open
source license and I have the right under that license to
submit that work with modifications, whether created in whole
or in part by me, under the same open source license (unless
I am permitted to submit under a different license), as
Indicated in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including
all personal information I submit with it, including my
sign-off) is maintained indefinitely and may be redistributed
consistent with this project or the open source license(s)
involved.
Signed-off-by: Jason Barnett <jason.w.barnett@gmail.com>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/chef/provider/service/windows.rb | 186 | ||||
-rw-r--r-- | lib/chef/resource/windows_service.rb | 105 | ||||
-rw-r--r-- | lib/chef/win32_service_constants.rb | 143 |
3 files changed, 390 insertions, 44 deletions
diff --git a/lib/chef/provider/service/windows.rb b/lib/chef/provider/service/windows.rb index 931e320695..8503bd684d 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,36 @@ 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_start_type + when AUTO_START + 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(get_start_type(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] + + if delayed_start = current_delayed_start + current_resource.delayed_start(delayed_start) + end end - Chef::Log.debug "#{@new_resource} enabled: #{@current_resource.enabled}" - @current_resource + + current_resource end def start_service @@ -175,6 +195,44 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service end end + action :create do + return if Win32::Service.exists?(new_resource.service_name) + + converge_by("create service #{new_resource.service_name}") do + Win32::Service.new(windows_service_config) + end + + converge_delayed_start + end + + action :delete do + return unless Win32::Service.exists?(new_resource.service_name) + + 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.debug "#{new_resource} does not exist - nothing to do" + 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 do + Win32::Service.configure(windows_service_config(:configure)) + end + + converge_delayed_start + end + def action_enable if current_start_type != AUTO_START converge_by("enable service #{@new_resource}") do @@ -264,6 +322,14 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service Win32::Service.config_info(@new_resource.service_name).start_type end + def current_delayed_start + if service = Win32::Service.services.find { |x| x.service_name == @new_resource.service_name } + service.delayed_start + else + nil + end + end + # Helper method that waits for a status to change its state since state # changes aren't usually instantaneous. def wait_for_state(desired_state) @@ -306,4 +372,102 @@ class Chef::Provider::Service::Windows < Chef::Provider::Service ) @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] = 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? or 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 + + # 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 + + 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 proces' + 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 + nil + end + end + + 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 + nil + 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 405f7f6dbe..74580bc01d 100644 --- a/lib/chef/resource/windows_service.rb +++ b/lib/chef/resource/windows_service.rb @@ -17,10 +17,18 @@ # require "chef/resource/service" +require "chef/win32_service_constants" class Chef class Resource 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 @@ -28,44 +36,75 @@ 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, Integer, default: SERVICE_AUTO_START, coerce: proc { |x| + if x.is_a?(Integer) + x + elsif x.is_a?(String) or x.is_a?(Symbol) + x = x.to_sym + ALLOWED_START_TYPES.fetch(x) do + Chef::Log.warn("Unsupported startup_type #{x}, falling back to :automatic") + SERVICE_AUTO_START + end + end + } + + # This only applies if startup_type is :automatic + property :delayed_start, [Integer], default: false, coerce: proc { |x| + if x.is_a?(TrueClass) + 1 + elsif x.is_a?(FalseClass) + 0 + elsif x.is_a?(Integer) + x.zero? ? 0 : 1 + 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. + property :binary_path_name, String, required: true + + # 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 |