diff options
author | Tim Smith <tsmith84@gmail.com> | 2021-01-22 19:54:12 -0800 |
---|---|---|
committer | Tim Smith <tsmith84@gmail.com> | 2021-02-03 16:09:37 -0800 |
commit | 7677a88c72f9097f8cc5455e2a16e19bf2bf81a7 (patch) | |
tree | 5fc6825df88fd17416825158a3e77610dfa33b7b | |
parent | b14a3e76165e243fcabe61db60be80f8c13c36a1 (diff) | |
download | chef-7677a88c72f9097f8cc5455e2a16e19bf2bf81a7.tar.gz |
Chef 17: Remove windows service manager capabilities
This is something we've recommended against using for many years now. Windows users should instead run Chef Infra Client using a Scheduled Task which is far more reliable and has the added benefit of requiring zero CPU or memory while not running.
Signed-off-by: Tim Smith <tsmith@chef.io>
-rwxr-xr-x | chef-bin/bin/chef-service-manager | 37 | ||||
-rwxr-xr-x | chef-bin/bin/chef-windows-service | 33 | ||||
-rw-r--r-- | lib/chef/application/windows_service.rb | 338 | ||||
-rw-r--r-- | lib/chef/application/windows_service_manager.rb | 205 | ||||
-rw-r--r-- | spec/functional/resource/windows_service_spec.rb | 105 | ||||
-rw-r--r-- | spec/functional/win32/service_manager_spec.rb | 220 | ||||
-rw-r--r-- | spec/spec_helper.rb | 5 | ||||
-rw-r--r-- | spec/support/chef_helpers.rb | 16 | ||||
-rw-r--r-- | spec/support/shared/functional/execute_resource.rb | 4 | ||||
-rw-r--r-- | spec/support/shared/functional/win32_service.rb | 57 | ||||
-rw-r--r-- | spec/support/shared/functional/windows_script.rb | 4 | ||||
-rw-r--r-- | spec/unit/windows_service_spec.rb | 118 |
12 files changed, 6 insertions, 1136 deletions
diff --git a/chef-bin/bin/chef-service-manager b/chef-bin/bin/chef-service-manager index dcaae80141..336b1c4715 100755 --- a/chef-bin/bin/chef-service-manager +++ b/chef-bin/bin/chef-service-manager @@ -1,38 +1,3 @@ #!/usr/bin/env ruby # -# ./chef-service-manager - Control chef-service on Windows platforms. -# -# Author:: Serdar Sutay (serdar@chef.io) -# Copyright:: Copyright 2013-2018, Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -$:.unshift(File.join(__dir__, "..", "lib")) -require "chef" -require "chef/application/windows_service_manager" -require "chef-utils/dist" unless defined?(ChefUtils::Dist) - -if Chef::Platform.windows? - chef_client_service = { - service_name: ChefUtils::Dist::Infra::CLIENT, - service_display_name: "#{ChefUtils::Dist::Infra::PRODUCT} Service", - service_description: "Runs #{ChefUtils::Dist::Infra::PRODUCT} on regular, configurable intervals.", - service_file_path: File.expand_path("../chef-windows-service", $PROGRAM_NAME), - delayed_start: true, - dependencies: ["Winmgmt"], - } - Chef::Application::WindowsServiceManager.new(chef_client_service).run -else - puts "chef-service-manager is only available on Windows platforms." -end +puts "As of Chef Infra Client 17 the chef-service-manager is no longer available. Please run Chef Infra Client as a scheduled task instead for more reliable operation with lower memory and CPU overhead." diff --git a/chef-bin/bin/chef-windows-service b/chef-bin/bin/chef-windows-service index ce1a30baae..e84cbe2e7a 100755 --- a/chef-bin/bin/chef-windows-service +++ b/chef-bin/bin/chef-windows-service @@ -1,34 +1,3 @@ #!/usr/bin/env ruby # -# Author:: Jay Mundrawala (<jdm@chef.io>) -# -# Copyright:: 2014-2018, Chef Software, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# Note: -# The file is used by appbundler to generate a ruby file that -# we can execute using the correct gems. The batch file we -# generate will call that file, and will be registered as -# a windows service. - -$:.unshift(File.join(__dir__, "..", "lib")) -require "chef" -require "chef/application/windows_service" - -if Chef::Platform.windows? - Chef::Application::WindowsService.mainloop -else - puts "chef-windows-service is only available on Windows platforms." -end +puts "As of Chef Infra Client 17 the chef-windows-service is no longer available. Please run Chef Infra Client as a scheduled task instead for more reliable operation with lower memory and CPU overhead." diff --git a/lib/chef/application/windows_service.rb b/lib/chef/application/windows_service.rb deleted file mode 100644 index 8975556f75..0000000000 --- a/lib/chef/application/windows_service.rb +++ /dev/null @@ -1,338 +0,0 @@ -# -# Author:: Christopher Maier (<maier@lambda.local>) -# Copyright:: Copyright (c) Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require_relative "../../chef" -require_relative "../monologger" -require_relative "../application" -require_relative "../client" -require_relative "../config" -require_relative "../handler/error_report" -require_relative "../log" -require_relative "../http" -require "mixlib/cli" unless defined?(Mixlib::CLI) -require "socket" unless defined?(Socket) -require "uri" unless defined?(URI) -require "win32/daemon" -require_relative "../mixin/shell_out" -require "chef-utils/dist" unless defined?(ChefUtils::Dist) - -class Chef - class Application - class WindowsService < ::Win32::Daemon - include Mixlib::CLI - include Chef::Mixin::ShellOut - - option :config_file, - short: "-c CONFIG", - long: "--config CONFIG", - default: "#{Chef::Config.etc_chef_dir}/client.rb", - description: "The configuration file to use for #{ChefUtils::Dist::Infra::PRODUCT} runs." - - option :log_location, - short: "-L LOGLOCATION", - long: "--logfile LOGLOCATION", - description: "Set the log file location." - - option :splay, - short: "-s SECONDS", - long: "--splay SECONDS", - description: "The splay time for running at intervals, in seconds.", - proc: lambda { |s| s.to_i } - - option :interval, - short: "-i SECONDS", - long: "--interval SECONDS", - description: "Set the number of seconds to wait between #{ChefUtils::Dist::Infra::PRODUCT} runs.", - proc: lambda { |s| s.to_i } - - DEFAULT_LOG_LOCATION ||= "#{Chef::Config.c_chef_dir}/client.log".freeze - - def service_init - @service_action_mutex = Mutex.new - @service_signal = ConditionVariable.new - - reconfigure - Chef::Log.info("#{ChefUtils::Dist::Infra::CLIENT} Service initialized") - end - - def service_main(*startup_parameters) - # Chef::Config is initialized during service_init - # Set the initial timeout to splay sleep time - timeout = rand Chef::Config[:splay] - - while running? - # Grab the service_action_mutex to make a chef-client run - @service_action_mutex.synchronize do - - Chef::Log.info("Next #{ChefUtils::Dist::Infra::CLIENT} run will happen in #{timeout} seconds") - @service_signal.wait(@service_action_mutex, timeout) - - # Continue only if service is RUNNING - next if state != RUNNING - - # Reconfigure each time through to pick up any changes in the client file - Chef::Log.info("Reconfiguring with startup parameters") - reconfigure(startup_parameters) - timeout = Chef::Config[:interval] - - # Honor splay sleep config - timeout += rand Chef::Config[:splay] - - # run chef-client only if service is in RUNNING state - next if state != RUNNING - - Chef::Log.info("#{ChefUtils::Dist::Infra::CLIENT} service is starting a #{ChefUtils::Dist::Infra::CLIENT} run...") - run_chef_client - rescue SystemExit => e - # Do not raise any of the errors here in order to - # prevent service crash - Chef::Log.error("#{e.class}: #{e}") - rescue Exception => e - Chef::Log.error("#{e.class}: #{e}") - - end - end - - # Daemon class needs to have all the signal callbacks return - # before service_main returns. - Chef::Log.trace("Giving signal callbacks some time to exit...") - sleep 1 - Chef::Log.trace("Exiting service...") - end - - ################################################################################ - # Control Signal Callback Methods - ################################################################################ - - def service_stop - run_warning_displayed = false - Chef::Log.info("STOP request from operating system.") - loop do - # See if a run is in flight - if @service_action_mutex.try_lock - # Run is not in flight. Wake up service_main to exit. - @service_signal.signal - @service_action_mutex.unlock - break - else - unless run_warning_displayed - Chef::Log.info("Currently a #{ChefUtils::Dist::Infra::PRODUCT} run is happening on this system.") - Chef::Log.info("Service will stop when run is completed.") - run_warning_displayed = true - end - - Chef::Log.trace("Waiting for #{ChefUtils::Dist::Infra::PRODUCT} run...") - sleep 1 - end - end - Chef::Log.info("Service is stopping....") - end - - def service_pause - Chef::Log.info("PAUSE request from operating system.") - - # We don't need to wake up the service_main if it's waiting - # since this is a PAUSE signal. - - if @service_action_mutex.locked? - Chef::Log.info("Currently a #{ChefUtils::Dist::Infra::PRODUCT} run is happening.") - Chef::Log.info("Service will pause once it's completed.") - else - Chef::Log.info("Service is pausing....") - end - end - - def service_resume - # We don't need to wake up the service_main if it's waiting - # since this is a RESUME signal. - - Chef::Log.info("RESUME signal received from the OS.") - Chef::Log.info("Service is resuming....") - end - - def service_shutdown - Chef::Log.info("SHUTDOWN signal received from the OS.") - - # Treat shutdown similar to stop. - - service_stop - end - - ################################################################################ - # Internal Methods - ################################################################################ - - private - - # Initializes Chef::Client instance and runs it - def run_chef_client - # The chef client will be started in a new process. We have used shell_out to start the chef-client. - # The log_location and config_file of the parent process is passed to the new chef-client process. - # We need to add the --no-fork, as by default it is set to fork=true. - - Chef::Log.info "Starting #{ChefUtils::Dist::Infra::CLIENT} in a new process" - # Pass config params to the new process - config_params = " --no-fork" - config_params += " -c #{Chef::Config[:config_file]}" unless Chef::Config[:config_file].nil? - # log_location might be an event logger and if so we cannot pass as a command argument - # but shed no tears! If the logger is an event logger, it must have been configured - # as such in the config file and chef-client will use that when no arg is passed here - config_params += " -L #{resolve_log_location}" if resolve_log_location.is_a?(String) - - # Starts a new process and waits till the process exits - - result = shell_out( - "#{ChefUtils::Dist::Infra::CLIENT}.bat #{config_params}", - timeout: Chef::Config[:windows_service][:watchdog_timeout], - logger: Chef::Log - ) - Chef::Log.trace (result.stdout).to_s - Chef::Log.trace (result.stderr).to_s - rescue Mixlib::ShellOut::CommandTimeout => e - Chef::Log.error "#{ChefUtils::Dist::Infra::CLIENT} timed out\n(#{e})" - Chef::Log.error(<<-EOF) - Your #{ChefUtils::Dist::Infra::CLIENT} run timed out. You can increase the time #{ChefUtils::Dist::Infra::CLIENT} is given - to complete by configuring windows_service.watchdog_timeout in your client.rb. - EOF - rescue Mixlib::ShellOut::ShellCommandFailed => e - Chef::Log.warn "Not able to start #{ChefUtils::Dist::Infra::CLIENT} in new process (#{e})" - rescue => e - Chef::Log.error e - ensure - # Once process exits, we log the current process' pid - Chef::Log.info "Child process exited (pid: #{Process.pid})" - end - - def apply_config(config_file_path) - Chef::Config.from_file(config_file_path) - Chef::Config.merge!(config) - end - - # Lifted from Chef::Application, with addition of optional startup parameters - # for playing nicely with Windows Services - def reconfigure(startup_parameters = []) - configure_chef startup_parameters - configure_logging - - Chef::Config[:chef_server_url] = config[:chef_server_url] if config.key? :chef_server_url - unless Chef::Config[:exception_handlers].any? { |h| Chef::Handler::ErrorReport === h } - Chef::Config[:exception_handlers] << Chef::Handler::ErrorReport.new - end - - Chef::Config[:interval] ||= 1800 - end - - # Lifted from application.rb - # See application.rb for related comments. - - def configure_logging - Chef::Log.init(MonoLogger.new(resolve_log_location)) - if want_additional_logger? - configure_stdout_logger - end - Chef::Log.level = resolve_log_level - end - - def configure_stdout_logger - stdout_logger = MonoLogger.new(STDOUT) - stdout_logger.formatter = Chef::Log.logger.formatter - Chef::Log.loggers << stdout_logger - end - - # Based on config and whether or not STDOUT is a tty, should we setup a - # secondary logger for stdout? - def want_additional_logger? - ( Chef::Config[:log_location] != STDOUT ) && STDOUT.tty? && !Chef::Config[:daemonize] - end - - # Use of output formatters is assumed if `force_formatter` is set or if - # `force_logger` is not set - def using_output_formatter? - Chef::Config[:force_formatter] || !Chef::Config[:force_logger] - end - - def auto_log_level? - Chef::Config[:log_level] == :auto - end - - def resolve_log_location - # STDOUT is the default log location, but makes no sense for a windows service - Chef::Config[:log_location] == STDOUT ? DEFAULT_LOG_LOCATION : Chef::Config[:log_location] - end - - # if log_level is `:auto`, convert it to :warn (when using output formatter) - # or :info (no output formatter). See also +using_output_formatter?+ - def resolve_log_level - if auto_log_level? - if using_output_formatter? - :warn - else - :info - end - else - Chef::Config[:log_level] - end - end - - def configure_chef(startup_parameters) - # Bit of a hack ahead: - # It is possible to specify a service's binary_path_name with arguments, like "foo.exe -x argX". - # It is also possible to specify startup parameters separately, either via the Services manager - # or by using the registry (I think). - - # In order to accommodate all possible sources of parameterization, we first parse any command line - # arguments. We then parse any startup parameters. This works, because Mixlib::CLI reuses its internal - # 'config' hash; thus, anything in startup parameters will override any command line parameters that - # might be set via the service's binary_path_name - # - # All these parameters then get layered on top of those from Chef::Config - - parse_options # Operates on ARGV by default - parse_options startup_parameters - - begin - case config[:config_file] - when %r{^(http|https)://} - Chef::HTTP.new("").streaming_request(config[:config_file]) { |f| apply_config(f.path) } - else - ::File.open(config[:config_file]) { |f| apply_config(f.path) } - end - rescue Errno::ENOENT - Chef::Log.warn("*****************************************") - Chef::Log.warn("Did not find config file: #{config[:config_file]}. Using command line options instead.") - Chef::Log.warn("*****************************************") - - Chef::Config.merge!(config) - rescue SocketError - Chef::Application.fatal!("Error getting config file #{Chef::Config[:config_file]}") - rescue Chef::Exceptions::ConfigurationError => error - Chef::Application.fatal!("Error processing config file #{Chef::Config[:config_file]} with error #{error.message}") - rescue Exception => error - Chef::Application.fatal!("Unknown error processing config file #{Chef::Config[:config_file]} with error #{error.message}") - end - end - - end - end -end - -# To run this file as a service, it must be called as a script from within -# the Windows Service framework. In that case, kick off the main loop! -if __FILE__ == $0 - Chef::Application::WindowsService.mainloop -end diff --git a/lib/chef/application/windows_service_manager.rb b/lib/chef/application/windows_service_manager.rb deleted file mode 100644 index 4f0de26411..0000000000 --- a/lib/chef/application/windows_service_manager.rb +++ /dev/null @@ -1,205 +0,0 @@ -# -# Author:: Seth Chisamore (<schisamo@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -if RUBY_PLATFORM.match?(/mswin|mingw32|windows/) - require "win32/service" -end -require_relative "../config" -require "mixlib/cli" unless defined?(Mixlib::CLI) -require "chef-utils/dist" unless defined?(ChefUtils::Dist) - -class Chef - class Application - # - # This class is used to create and manage a windows service. - # Service should be created using Daemon class from - # win32/service gem. - # For an example see: Chef::Application::WindowsService - # - # Outside programs are expected to use this class to manage - # windows services. - # - class WindowsServiceManager - include Mixlib::CLI - - option :action, - short: "-a ACTION", - long: "--action ACTION", - default: "status", - description: "Action to carry out on #{ChefUtils::Dist::Infra::SHORT}-service (install, uninstall, status, start, stop, pause, or resume)." - - option :config_file, - short: "-c CONFIG", - long: "--config CONFIG", - default: "#{ChefConfig::Config.c_chef_dir}/client.rb", - description: "The configuration file to use for #{ChefUtils::Dist::Infra::PRODUCT} runs." - - option :log_location, - short: "-L LOGLOCATION", - long: "--logfile LOGLOCATION", - description: "Set the log file location for #{ChefUtils::Dist::Infra::SHORT}-service." - - option :help, - short: "-h", - long: "--help", - description: "Show this help message.", - on: :tail, - boolean: true, - show_options: true, - exit: 0 - - option :version, - short: "-v", - long: "--version", - description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.", - boolean: true, - proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" }, - exit: 0 - - def initialize(service_options) - # having to call super in initialize is the most annoying - # anti-pattern :( - super() - - raise ArgumentError, "Service definition is not provided" if service_options.nil? - - required_options = %i{service_name service_display_name service_description service_file_path} - - required_options.each do |req_option| - unless service_options.key?(req_option) - raise ArgumentError, "Service definition doesn't contain required option #{req_option}" - end - end - - @service_name = service_options[:service_name] - @service_display_name = service_options[:service_display_name] - @service_description = service_options[:service_description] - @service_file_path = service_options[:service_file_path] - @service_start_name = service_options[:run_as_user] - @password = service_options[:run_as_password] - @delayed_start = service_options[:delayed_start] - @dependencies = service_options[:dependencies] - end - - def run(params = ARGV) - parse_options(params) - - case config[:action] - when "install" - if service_exists? - puts "Service #{@service_name} already exists on the system." - else - ruby = File.join(RbConfig::CONFIG["bindir"], "ruby") - - opts = "" - opts << " -c #{config[:config_file]}" if config[:config_file] - opts << " -L #{config[:log_location]}" if config[:log_location] - - # Quote the full paths to deal with possible spaces in the path name. - # Also ensure all forward slashes are backslashes - cmd = "\"#{ruby}\" \"#{@service_file_path}\" #{opts}".gsub(File::SEPARATOR, File::ALT_SEPARATOR) - - ::Win32::Service.new( - service_name: @service_name, - display_name: @service_display_name, - description: @service_description, - # Prior to 0.8.5, win32-service creates interactive services by default, - # and we don't want that, so we need to override the service type. - service_type: ::Win32::Service::SERVICE_WIN32_OWN_PROCESS, - start_type: ::Win32::Service::SERVICE_AUTO_START, - binary_path_name: cmd, - service_start_name: @service_start_name, - password: @password, - dependencies: @dependencies - ) - unless @delayed_start.nil? - ::Win32::Service.configure( - service_name: @service_name, - delayed_start: @delayed_start - ) - end - puts "Service '#{@service_name}' has successfully been installed." - end - when "status" - if !service_exists? - puts "Service #{@service_name} doesn't exist on the system." - else - puts "State of #{@service_name} service is: #{current_state}" - end - when "start" - # TODO: allow override of startup parameters here? - take_action("start", RUNNING) - when "stop" - take_action("stop", STOPPED) - when "uninstall", "delete" - take_action("stop", STOPPED) - unless service_exists? - puts "Service #{@service_name} doesn't exist on the system." - else - ::Win32::Service.delete(@service_name) - puts "Service #{@service_name} deleted" - end - when "pause" - take_action("pause", PAUSED) - when "resume" - take_action("resume", RUNNING) - end - end - - private - - # Just some state constants - STOPPED = "stopped".freeze - RUNNING = "running".freeze - PAUSED = "paused".freeze - - def service_exists? - ::Win32::Service.exists?(@service_name) - end - - def take_action(action = nil, desired_state = nil) - if service_exists? - if current_state != desired_state - ::Win32::Service.send(action, @service_name) - wait_for_state(desired_state) - puts "Service '#{@service_name}' is now '#{current_state}'." - else - puts "Service '#{@service_name}' is already '#{desired_state}'." - end - else - puts "Cannot '#{action}' service '#{@service_name}'" - puts "Service #{@service_name} doesn't exist on the system." - end - end - - def current_state - ::Win32::Service.status(@service_name).current_state - 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) - while current_state != desired_state - puts "One moment... #{current_state}" - sleep 1 - end - end - - end - end -end diff --git a/spec/functional/resource/windows_service_spec.rb b/spec/functional/resource/windows_service_spec.rb deleted file mode 100644 index 4c0c3acb58..0000000000 --- a/spec/functional/resource/windows_service_spec.rb +++ /dev/null @@ -1,105 +0,0 @@ -# -# Author:: Chris Doherty (<cdoherty@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "spec_helper" - -describe Chef::Resource::WindowsService, :windows_only, :system_windows_service_gem_only do - - include_context "using Win32::Service" - - let(:username) { "service_spec_user" } - let(:qualified_username) { "#{ENV["COMPUTERNAME"]}\\#{username}" } - let(:password) { "1a2b3c4X!&narf" } - - let(:user_resource) do - r = Chef::Resource::User::WindowsUser.new(username, run_context) - r.username(username) - r.password(password) - r.comment("temp spec user") - r - end - - let(:global_service_file_path) do - "#{ENV["WINDIR"]}\\temp\\#{File.basename(test_service[:service_file_path])}" - end - - let(:service_params) do - - id = "#{$$}_#{rand(1000)}" - - test_service.merge( { - run_as_user: qualified_username, - run_as_password: password, - service_name: "spec_service_#{id}", - service_display_name: "windows_service spec #{id}}", - service_description: "Test service for running the windows_service functional spec.", - service_file_path: global_service_file_path, - } ) - end - - let(:manager) do - Chef::Application::WindowsServiceManager.new(service_params) - end - - let(:service_resource) do - r = Chef::Resource::WindowsService.new(service_params[:service_name], run_context) - %i{run_as_user run_as_password}.each { |prop| r.send(prop, service_params[prop]) } - r - end - - let(:run_context) do - Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) - end - - before do - user_resource.run_action(:create) - - # the service executable has to be outside the current user's home - # directory in order for the logon user to execute it. - FileUtils.copy_file(test_service[:service_file_path], global_service_file_path) - - # if you don't make the file executable by the service user, you'll get - # the not-very-helpful "service did not respond fast enough" error. - - # #mode may break in a post-Windows 8.1 release, and have to be replaced - # with the rights stuff in the file resource. - file = Chef::Resource::File.new(global_service_file_path, run_context) - file.mode("0777") - - file.run_action(:create) - - manager.run(%w{--action install}) - end - - after do - user_resource.run_action(:remove) - manager.run(%w{--action uninstall}) - File.delete(global_service_file_path) - end - - describe "logon as a service" do - it "successfully runs a service as another user" do - service_resource.run_action(:start) - end - - it "grants the user the log on as service right" do - service_resource.run_action(:start) - expect(Chef::ReservedNames::Win32::Security.get_account_right(qualified_username)).to include("SeServiceLogonRight") - end - end -end diff --git a/spec/functional/win32/service_manager_spec.rb b/spec/functional/win32/service_manager_spec.rb deleted file mode 100644 index 5746283b78..0000000000 --- a/spec/functional/win32/service_manager_spec.rb +++ /dev/null @@ -1,220 +0,0 @@ -# -# Author:: Serdar Sutay (<serdar@chef.io>) -# Copyright:: Copyright (c) Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require "spec_helper" -if ChefUtils.windows? - require "chef/application/windows_service_manager" -end - -# -# ATTENTION: -# This test creates a windows service for testing purposes and runs it -# as Local System (or an otherwise specified user) on windows boxes. -# This test will fail if you run the tests inside a Windows VM by -# sharing the code from your host since Local System account by -# default can't see the mounted partitions. -# Run this test by copying the code to a local VM directory or setup -# Local System account to see the maunted partitions for the shared -# directories. -# - -describe "Chef::Application::WindowsServiceManager", :windows_only, :system_windows_service_gem_only do - - include_context "using Win32::Service" - - context "with invalid service definition" do - it "throws an error when initialized with no service definition" do - expect { Chef::Application::WindowsServiceManager.new(nil) }.to raise_error(ArgumentError) - end - - it "throws an error with required missing options" do - %i{service_name service_display_name service_description service_file_path}.each do |key| - service_def = test_service.dup - service_def.delete(key) - - expect { Chef::Application::WindowsServiceManager.new(service_def) }.to raise_error(ArgumentError) - end - end - end - - context "with valid definition" do - before(:each) do - @service_manager_output = [ ] - # Uncomment below lines to debug this test - # original_puts = $stdout.method(:puts) - allow($stdout).to receive(:puts) do |message| - @service_manager_output << message - # original_puts.call(message) - end - end - - after(:each) do - cleanup - end - - context "when service doesn't exist" do - it "default => should say service don't exist" do - service_manager.run - - expect(@service_manager_output.grep(/doesn't exist on the system/).length).to be > 0 - end - - it "install => should install the service" do - service_manager.run(["-a", "install"]) - - expect(test_service_exists?).to be_truthy - end - - it "other actions => should say service doesn't exist" do - %w{delete start stop pause resume uninstall}.each do |action| - service_manager.run(["-a", action]) - expect(@service_manager_output.grep(/doesn't exist on the system/).length).to be > 0 - @service_manager_output = [ ] - end - end - end - - context "when service exists" do - before(:each) do - service_manager.run(["-a", "install"]) - end - - it "should have an own-process, non-interactive type" do - status = ::Win32::Service.status("spec-service") - expect(status[:service_type]).to eq("own process") - expect(status[:interactive]).to be_falsey - end - - it "install => should say service already exists" do - service_manager.run(["-a", "install"]) - expect(@service_manager_output.grep(/already exists/).length).to be > 0 - end - - context "and service is stopped" do - %w{delete uninstall}.each do |action| - it "#{action} => should remove the service", :volatile do - service_manager.run(["-a", action]) - expect(test_service_exists?).to be_falsey - end - end - - it "default, status => should say service is stopped" do - service_manager.run([ ]) - expect(@service_manager_output.grep(/stopped/).length).to be > 0 - @service_manager_output = [ ] - - service_manager.run(["-a", "status"]) - expect(@service_manager_output.grep(/stopped/).length).to be > 0 - end - - it "start should start the service", :volatile do - service_manager.run(["-a", "start"]) - expect(test_service_state).to eq("running") - expect(File.exist?(test_service_file)).to be_truthy - end - - it "stop should not affect the service" do - service_manager.run(["-a", "stop"]) - expect(test_service_state).to eq("stopped") - end - - %w{pause resume}.each do |action| - it "#{action} => should raise error" do - expect { service_manager.run(["-a", action]) }.to raise_error(SystemCallError) - end - end - - context "and service is started", :volatile do - before(:each) do - service_manager.run(["-a", "start"]) - end - - %w{delete uninstall}.each do |action| - it "#{action} => should remove the service", :volatile do - service_manager.run(["-a", action]) - expect(test_service_exists?).to be_falsey - end - end - - it "default, status => should say service is running" do - service_manager.run([ ]) - expect(@service_manager_output.grep(/running/).length).to be > 0 - @service_manager_output = [ ] - - service_manager.run(["-a", "status"]) - expect(@service_manager_output.grep(/running/).length).to be > 0 - end - - it "stop should stop the service" do - service_manager.run(["-a", "stop"]) - expect(test_service_state).to eq("stopped") - end - - it "pause should pause the service" do - service_manager.run(["-a", "pause"]) - expect(test_service_state).to eq("paused") - end - - it "resume should have no affect" do - service_manager.run(["-a", "resume"]) - expect(test_service_state).to eq("running") - end - end - - context "and service is paused", :volatile do - before(:each) do - service_manager.run(["-a", "start"]) - service_manager.run(["-a", "pause"]) - end - - actions = %w{delete uninstall} - actions.each do |action| - it "#{action} => should remove the service" do - service_manager.run(["-a", action]) - expect(test_service_exists?).to be_falsey - end - end - - it "default, status => should say service is paused" do - service_manager.run([ ]) - expect(@service_manager_output.grep(/paused/).length).to be > 0 - @service_manager_output = [ ] - - service_manager.run(["-a", "status"]) - expect(@service_manager_output.grep(/paused/).length).to be > 0 - end - - it "stop should stop the service" do - service_manager.run(["-a", "stop"]) - expect(test_service_state).to eq("stopped") - end - - it "pause should not affect the service" do - service_manager.run(["-a", "pause"]) - expect(test_service_state).to eq("paused") - end - - it "start should raise an error" do - expect { service_manager.run(["-a", "start"]) }.to raise_error(::Win32::Service::Error) - end - - end - end - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 52f17788bb..95a4c62a77 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -159,12 +159,7 @@ RSpec.configure do |config| config.filter_run_excluding windows_powershell_no_dsc_only: true if windows_powershell_dsc? config.filter_run_excluding windows_domain_joined_only: true unless windows_domain_joined? config.filter_run_excluding windows_not_domain_joined_only: true if windows_domain_joined? - # We think this line was causing rspec tests to not run on the Jenkins windows - # testers. If we ever fix it we should restore it. - # config.filter_run_excluding :windows_service_requires_assign_token => true if !STDOUT.isatty && !windows_user_right?("SeAssignPrimaryTokenPrivilege") - config.filter_run_excluding windows_service_requires_assign_token: true config.filter_run_excluding solaris_only: true unless solaris? - config.filter_run_excluding system_windows_service_gem_only: true unless system_windows_service_gem? config.filter_run_excluding unix_only: true unless unix? config.filter_run_excluding linux_only: true unless linux? config.filter_run_excluding aix_only: true unless aix? diff --git a/spec/support/chef_helpers.rb b/spec/support/chef_helpers.rb index 6a0ca878cd..3738c4a41e 100644 --- a/spec/support/chef_helpers.rb +++ b/spec/support/chef_helpers.rb @@ -40,22 +40,6 @@ def make_tmpname(prefix_suffix, n = nil) path << suffix end -# This is a helper to determine if the ruby in the PATH contains -# win32/service gem. windows_service_manager tests create a windows -# service that starts with the system ruby and requires this gem. -def system_windows_service_gem? - windows_service_gem_check_command = %{ruby -r "win32/daemon" -e ":noop" > #{File::NULL} 2>&1} - if defined?(Bundler) - Bundler.with_unbundled_env do - # This returns true if the gem can be loaded - system(windows_service_gem_check_command) - end - else - # This returns true if the gem can be loaded - system(windows_service_gem_check_command) - end -end - # This is a helper to canonicalize paths that we're using in the file # tests. def canonicalize_path(path) diff --git a/spec/support/shared/functional/execute_resource.rb b/spec/support/shared/functional/execute_resource.rb index 9d1c29dfac..3f870d0fb6 100644 --- a/spec/support/shared/functional/execute_resource.rb +++ b/spec/support/shared/functional/execute_resource.rb @@ -68,7 +68,7 @@ shared_context "a command that can be executed as an alternate user" do end shared_examples_for "an execute resource that supports alternate user identity" do - context "when running on Windows", :windows_only, :windows_service_requires_assign_token do + context "when running on Windows", :windows_only do include_context "a command that can be executed as an alternate user" @@ -102,7 +102,7 @@ shared_examples_for "an execute resource that supports alternate user identity" end shared_examples_for "a resource with a guard specifying an alternate user identity" do - context "when running on Windows", :windows_only, :windows_service_requires_assign_token do + context "when running on Windows", :windows_only do include_context "alternate user identity" let(:resource_command_property) { :command } diff --git a/spec/support/shared/functional/win32_service.rb b/spec/support/shared/functional/win32_service.rb deleted file mode 100644 index 890c28de2c..0000000000 --- a/spec/support/shared/functional/win32_service.rb +++ /dev/null @@ -1,57 +0,0 @@ - -require "chef/application/windows_service_manager" - -shared_context "using Win32::Service" do - # Some helper methods. - - def test_service_exists? - ::Win32::Service.exists?("spec-service") - end - - def test_service_state - ::Win32::Service.status("spec-service").current_state - end - - def service_manager - Chef::Application::WindowsServiceManager.new(test_service) - end - - def cleanup - # Uninstall if the test service is installed. - if test_service_exists? - - # We can only uninstall when the service is stopped. - if test_service_state != "stopped" - ::Win32::Service.send("stop", "spec-service") - sleep 1 while test_service_state != "stopped" - end - - ::Win32::Service.delete("spec-service") - end - - # Delete the test_service_file if it exists - if File.exist?(test_service_file) - File.delete(test_service_file) - end - end - - # Definition for the test-service - - let(:test_service) do - { - service_name: "spec-service", - service_display_name: "Spec Test Service", - service_description: "Service for testing Chef::Application::WindowsServiceManager.", - service_file_path: File.expand_path(File.join(__dir__, "../../platforms/win32/spec_service.rb")), - delayed_start: true, - } - end - - # Test service creates a file for us to verify that it is running. - # Since our test service is running as Local System we should look - # for the file it creates under SYSTEM temp directory - - let(:test_service_file) do - "#{ENV["SystemDrive"]}\\windows\\temp\\spec_service_file" - end -end diff --git a/spec/support/shared/functional/windows_script.rb b/spec/support/shared/functional/windows_script.rb index 151ad2387c..9f328f0b92 100644 --- a/spec/support/shared/functional/windows_script.rb +++ b/spec/support/shared/functional/windows_script.rb @@ -168,11 +168,11 @@ shared_context Chef::Resource::WindowsScript do resource.run_action(:run) end - context "the script is executed with the identity of the current user", :windows_service_requires_assign_token do + context "the script is executed with the identity of the current user" do it_behaves_like "a script that cannot be accessed by other users if they are not administrators" end - context "the script is executed with an alternate non-admin identity", :windows_service_requires_assign_token do + context "the script is executed with an alternate non-admin identity" do include_context "alternate user identity" before do diff --git a/spec/unit/windows_service_spec.rb b/spec/unit/windows_service_spec.rb deleted file mode 100644 index 02a795426d..0000000000 --- a/spec/unit/windows_service_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -# -# Author:: Mukta Aphale (<mukta.aphale@clogeny.com>) -# Copyright:: Copyright (c) Chef Software Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -require "spec_helper" -if ChefUtils.windows? - require "chef/application/windows_service" -end - -describe "Chef::Application::WindowsService", :windows_only do - let(:shell_out_result) { double("shellout", stdout: nil, stderr: nil) } - let(:config_options) do - { - log_location: STDOUT, - config_file: "test_config_file", - log_level: :info, - } - end - let(:timeout) { 7200 } - let(:shellout_options) do - { - timeout: timeout, - logger: Chef::Log, - } - end - - before do - monologger = instance_double("MonoLogger", :level= => nil, :add => nil, :formatter= => nil, :formatter => nil) - allow(MonoLogger).to receive(:new).and_return(monologger) - - Chef::Config.merge!(config_options) - allow(subject).to receive(:configure_chef) - allow(subject).to receive(:parse_options) - allow(subject).to receive(:running?).and_return(true, false) - allow(subject).to receive(:state).and_return(4) - subject.service_init - end - - subject { Chef::Application::WindowsService.new } - - it "passes DEFAULT_LOG_LOCATION to chef-client instead of STDOUT" do - expect(subject).to receive(:shell_out).with( - "chef-client.bat --no-fork -c test_config_file -L #{Chef::Application::WindowsService::DEFAULT_LOG_LOCATION}", - shellout_options - ).and_return(shell_out_result) - subject.service_main - end - - context "has a log location configured" do - let(:tempfile) { Tempfile.new "log_file" } - let(:config_options) do - { - log_location: tempfile.path, - config_file: "test_config_file", - log_level: :info, - } - end - - after do - tempfile.unlink - end - - it "uses the configured log location" do - expect(subject).to receive(:shell_out).with( - "chef-client.bat --no-fork -c test_config_file -L #{tempfile.path}", - shellout_options - ).and_return(shell_out_result) - subject.service_main - end - - context "configured to Event Logger" do - let(:config_options) do - { - log_location: Chef::Log::WinEvt.new, - config_file: "test_config_file", - log_level: :info, - } - end - - it "does not pass log location to new process" do - expect(subject).to receive(:shell_out).with( - "chef-client.bat --no-fork -c test_config_file", - shellout_options - ).and_return(shell_out_result) - subject.service_main - end - end - end - - context "configures a watchdog timeout" do - let(:timeout) { 10 } - - before do - Chef::Config[:windows_service][:watchdog_timeout] = 10 - end - - it "passes watchdog timeout to new process" do - expect(subject).to receive(:shell_out).with( - "chef-client.bat --no-fork -c test_config_file -L #{Chef::Application::WindowsService::DEFAULT_LOG_LOCATION}", - shellout_options - ).and_return(shell_out_result) - subject.service_main - end - end -end |