puppet 6.0.2-x64-mingw32 → 6.0.3-x64-mingw32

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puppet might be problematic. Click here for more details.

Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -2
  3. data/Gemfile.lock +11 -11
  4. data/lib/puppet/application.rb +5 -0
  5. data/lib/puppet/application/apply.rb +1 -0
  6. data/lib/puppet/application/script.rb +1 -1
  7. data/lib/puppet/application/ssl.rb +119 -49
  8. data/lib/puppet/defaults.rb +9 -27
  9. data/lib/puppet/face/node/clean.rb +0 -1
  10. data/lib/puppet/feature/base.rb +1 -1
  11. data/lib/puppet/file_serving/fileset.rb +1 -1
  12. data/lib/puppet/pops/validation/checker4_0.rb +4 -2
  13. data/lib/puppet/provider/package/windows.rb +2 -2
  14. data/lib/puppet/provider/package/windows/exe_package.rb +3 -10
  15. data/lib/puppet/provider/service/windows.rb +11 -3
  16. data/lib/puppet/provider/user/useradd.rb +2 -10
  17. data/lib/puppet/resource/catalog.rb +1 -5
  18. data/lib/puppet/ssl/host.rb +7 -9
  19. data/lib/puppet/transaction/persistence.rb +1 -1
  20. data/lib/puppet/type/package.rb +1 -1
  21. data/lib/puppet/type/user.rb +4 -1
  22. data/lib/puppet/util.rb +7 -3
  23. data/lib/puppet/util/execution.rb +1 -0
  24. data/lib/puppet/util/logging.rb +3 -2
  25. data/lib/puppet/util/windows/process.rb +6 -2
  26. data/lib/puppet/util/windows/security.rb +14 -0
  27. data/lib/puppet/util/windows/service.rb +217 -74
  28. data/lib/puppet/util/windows/user.rb +3 -5
  29. data/lib/puppet/version.rb +1 -1
  30. data/locales/ja/puppet.po +505 -276
  31. data/locales/puppet.pot +250 -111
  32. data/man/man5/puppet.conf.5 +8 -1
  33. data/man/man8/puppet-ssl.8 +22 -2
  34. data/man/man8/puppet.8 +1 -1
  35. data/spec/integration/parser/collection_spec.rb +4 -8
  36. data/spec/integration/type/file_spec.rb +6 -6
  37. data/spec/integration/util/windows/security_spec.rb +10 -7
  38. data/spec/integration/util/windows/user_spec.rb +37 -17
  39. data/spec/lib/puppet/test_ca.rb +1 -1
  40. data/spec/unit/agent_spec.rb +2 -2
  41. data/spec/unit/application/apply_spec.rb +41 -2
  42. data/spec/unit/application/face_base_spec.rb +1 -1
  43. data/spec/unit/application/ssl_spec.rb +160 -110
  44. data/spec/unit/application_spec.rb +29 -11
  45. data/spec/unit/configurer/downloader_spec.rb +1 -1
  46. data/spec/unit/configurer_spec.rb +5 -5
  47. data/spec/unit/face/node_spec.rb +1 -3
  48. data/spec/unit/file_serving/fileset_spec.rb +11 -11
  49. data/spec/unit/network/http/connection_spec.rb +2 -2
  50. data/spec/unit/pops/validator/validator_spec.rb +24 -10
  51. data/spec/unit/provider/package/windows/exe_package_spec.rb +3 -3
  52. data/spec/unit/provider/package/windows_spec.rb +4 -4
  53. data/spec/unit/provider/service/windows_spec.rb +21 -3
  54. data/spec/unit/provider/user/useradd_spec.rb +2 -2
  55. data/spec/unit/resource/catalog_spec.rb +2 -2
  56. data/spec/unit/ssl/host_spec.rb +1 -1
  57. data/spec/unit/transaction/persistence_spec.rb +4 -4
  58. data/spec/unit/util/execution_spec.rb +19 -1
  59. data/spec/unit/util/logging_spec.rb +58 -0
  60. data/spec/unit/util/windows/service_spec.rb +344 -191
  61. metadata +2 -2
@@ -59,7 +59,6 @@ Puppet::Face.define(:node, '0.0.1') do
59
59
  # clean signed cert for +host+
60
60
  def clean_cert(node)
61
61
  if Puppet.features.puppetserver_ca?
62
- require 'puppetserver/ca/action/clean'
63
62
  Puppetserver::Ca::Action::Clean.new(LoggerIO.new).run({ 'certnames' => [node] })
64
63
  else
65
64
  Puppet.info _("Not managing %{node} certs as this host is not a CA") % { node: node }
@@ -73,4 +73,4 @@ Puppet.features.add(:manages_symlinks) do
73
73
  end
74
74
  end
75
75
 
76
- Puppet.features.add(:puppetserver_ca, libs: ['puppetserver/ca'])
76
+ Puppet.features.add(:puppetserver_ca, libs: ['puppetserver/ca', 'puppetserver/ca/action/clean'])
@@ -122,7 +122,7 @@ class Puppet::FileServing::Fileset
122
122
  def children
123
123
  return [] unless directory?
124
124
 
125
- Dir.entries(path).
125
+ Dir.entries(path, encoding: Encoding::UTF_8).
126
126
  reject { |child| ignore?(child) }.
127
127
  collect { |child| down_level(child) }
128
128
  end
@@ -559,6 +559,8 @@ class Checker4_0 < Evaluator::LiteralEvaluator
559
559
  return if namespace_for_file(file) == NO_NAMESPACE
560
560
 
561
561
  body = prog.body
562
+ return if prog.body.is_a?(Model::Nop) #Ignore empty or comment-only files
563
+
562
564
  if(body.is_a?(Model::BlockExpression))
563
565
  body.statements.each { |s| acceptor.accept(Issues::ILLEGAL_TOP_CONSTRUCT_LOCATION, s) unless valid_top_construct?(s) }
564
566
  else
@@ -623,9 +625,9 @@ class Checker4_0 < Evaluator::LiteralEvaluator
623
625
 
624
626
  def is_parent_dir_of(parent_dir, child_dir)
625
627
  parent_dir_path = Pathname.new(parent_dir)
626
- clean_parent = parent_dir_path.cleanpath
628
+ clean_parent = parent_dir_path.cleanpath.to_s + File::SEPARATOR
627
629
 
628
- return child_dir.to_s.start_with?(clean_parent.to_s)
630
+ return child_dir.to_s.start_with?(clean_parent)
629
631
  end
630
632
 
631
633
  def dir_to_names(relative_path)
@@ -63,14 +63,14 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa
63
63
  installer = Puppet::Provider::Package::Windows::Package.installer_class(resource)
64
64
 
65
65
  command = [installer.install_command(resource), install_options].flatten.compact.join(' ')
66
- output = execute(command, :failonfail => false, :combine => true)
66
+ output = execute(command, :failonfail => false, :combine => true, :cwd => File.dirname(resource[:source]), :suppress_window => true)
67
67
 
68
68
  check_result(output.exitstatus)
69
69
  end
70
70
 
71
71
  def uninstall
72
72
  command = [package.uninstall_command, uninstall_options].flatten.compact.join(' ')
73
- output = execute(command, :failonfail => false, :combine => true)
73
+ output = execute(command, :failonfail => false, :combine => true, :suppress_window => true)
74
74
 
75
75
  check_result(output.exitstatus)
76
76
  end
@@ -42,17 +42,10 @@ class Puppet::Provider::Package::Windows
42
42
  end
43
43
 
44
44
  def self.install_command(resource)
45
- ['cmd.exe', '/c', 'start', '"puppet-install"', '/w', munge(resource[:source])]
45
+ munge(resource[:source])
46
46
  end
47
47
 
48
48
  def uninstall_command
49
- # 1. Launch using cmd /c start because if the executable is a console
50
- # application Windows will automatically display its console window
51
- # 2. Specify a quoted title, otherwise if uninstall_string is quoted,
52
- # start will interpret that to be the title, and get confused
53
- # 3. Specify /w (wait) to wait for uninstall to finish
54
- command = ['cmd.exe', '/c', 'start', '"puppet-uninstall"', '/w']
55
-
56
49
  # Only quote bare uninstall strings, e.g.
57
50
  # C:\Program Files (x86)\Notepad++\uninstall.exe
58
51
  # Don't quote uninstall strings that are already quoted, e.g.
@@ -60,9 +53,9 @@ class Puppet::Provider::Package::Windows
60
53
  # Don't quote uninstall strings that contain arguments:
61
54
  # "C:\Program Files (x86)\Git\unins000.exe" /SILENT
62
55
  if uninstall_string =~ /\A[^"]*.exe\Z/i
63
- command << "\"#{uninstall_string}\""
56
+ command = "\"#{uninstall_string}\""
64
57
  else
65
- command << uninstall_string
58
+ command = uninstall_string
66
59
  end
67
60
 
68
61
  command
@@ -56,6 +56,13 @@ Puppet::Type.type(:service).provide :windows, :parent => :service do
56
56
  end
57
57
 
58
58
  def start
59
+ if status == :paused
60
+ Puppet::Util::Windows::Service.resume(@resource[:name])
61
+ return
62
+ end
63
+
64
+ # status == :stopped here
65
+
59
66
  if enabled? == :false
60
67
  # If disabled and not managing enable, respect disabled and fail.
61
68
  if @resource[:enable].nil?
@@ -81,10 +88,11 @@ Puppet::Type.type(:service).provide :windows, :parent => :service do
81
88
  current_state = Puppet::Util::Windows::Service.service_state(@resource[:name])
82
89
  state = case current_state
83
90
  when :SERVICE_STOPPED,
84
- :SERVICE_PAUSED,
85
- :SERVICE_STOP_PENDING,
86
- :SERVICE_PAUSE_PENDING
91
+ :SERVICE_STOP_PENDING
87
92
  :stopped
93
+ when :SERVICE_PAUSED,
94
+ :SERVICE_PAUSE_PENDING
95
+ :paused
88
96
  when :SERVICE_RUNNING,
89
97
  :SERVICE_CONTINUE_PENDING,
90
98
  :SERVICE_START_PENDING
@@ -134,15 +134,6 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ
134
134
  cmd
135
135
  end
136
136
 
137
- def check_manage_expiry
138
- cmd = []
139
- if @resource[:expiry] and not @resource.forcelocal?
140
- cmd << "-e #{@resource[:expiry]}"
141
- end
142
-
143
- cmd
144
- end
145
-
146
137
  def check_system_users
147
138
  if self.class.system_users? and resource.system?
148
139
  ["-r"]
@@ -188,7 +179,8 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ
188
179
 
189
180
  def modifycmd(param, value)
190
181
  if @resource.forcelocal?
191
- if param == :groups
182
+ case param
183
+ when :groups, :expiry
192
184
  cmd = [command(:modify)]
193
185
  else
194
186
  cmd = [command(property_manages_password_age?(param) ? :localpassword : :localmodify)]
@@ -558,11 +558,7 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph
558
558
  Puppet::FileSystem.open(resourcefile.value, resourcefile.mode.to_i(8), "w:UTF-8") do |f|
559
559
  to_print = resources.map do |resource|
560
560
  next unless resource.managed?
561
- if resource.name_var
562
- "#{resource.type}[#{resource[resource.name_var]}]"
563
- else
564
- "#{resource.ref.downcase}"
565
- end
561
+ "#{resource.ref.downcase}"
566
562
  end.compact
567
563
  f.puts to_print.join("\n")
568
564
  end
@@ -154,15 +154,14 @@ class Puppet::SSL::Host
154
154
  raise Puppet::Error, _("No certificate to validate.") unless cert
155
155
  raise Puppet::Error, _("No private key with which to validate certificate with fingerprint: %{fingerprint}") % { fingerprint: cert.fingerprint } unless key
156
156
  unless cert.content.check_private_key(key.content)
157
- raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: cert.fingerprint, cert_name: Puppet[:certname], ssl_dir: Puppet[:ssldir], cert_dir: Puppet[:certdir].gsub('/', '\\') }
157
+ raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: cert.fingerprint, cert_name: Puppet[:certname] }
158
158
  The certificate retrieved from the master does not match the agent's private key. Did you forget to run as root?
159
159
  Certificate fingerprint: %{fingerprint}
160
160
  To fix this, remove the certificate from both the master and the agent and then start a puppet run, which will automatically regenerate a certificate.
161
161
  On the master:
162
162
  puppetserver ca clean --certname %{cert_name}
163
163
  On the agent:
164
- 1a. On most platforms: find %{ssl_dir} -name %{cert_name}.pem -delete
165
- 1b. On Windows: del "%{cert_dir}\\%{cert_name}.pem" /f
164
+ 1. puppet ssl clean
166
165
  2. puppet agent -t
167
166
  ERROR_STRING
168
167
  end
@@ -237,15 +236,14 @@ ERROR_STRING
237
236
 
238
237
  def validate_local_csr_with_key(csr, key)
239
238
  if key.content.public_key.to_s != csr.content.public_key.to_s
240
- raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: csr.fingerprint, csr_public_key: csr.content.public_key.to_text, agent_public_key: key.content.public_key.to_text, cert_name: Puppet[:certname], ssl_dir: Puppet[:ssldir], cert_dir: Puppet[:certdir].gsub('/', '\\') }
239
+ raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: csr.fingerprint, csr_public_key: csr.content.public_key.to_text, agent_public_key: key.content.public_key.to_text }
241
240
  The local CSR does not match the agent's public key.
242
241
  CSR fingerprint: %{fingerprint}
243
242
  CSR public key: %{csr_public_key}
244
243
  Agent public key: %{agent_public_key}
245
244
  To fix this, remove the CSR from the agent and then start a puppet run, which will automatically regenerate a CSR.
246
245
  On the agent:
247
- 1a. On most platforms: find %{ssl_dir} -name %{cert_name}.pem -delete
248
- 1b. On Windows: del "%{cert_dir}\\%{cert_name}.pem" /f
246
+ 1. puppet ssl clean
249
247
  2. puppet agent -t
250
248
  ERROR_STRING
251
249
  end
@@ -254,7 +252,7 @@ ERROR_STRING
254
252
 
255
253
  def validate_csr_with_key(csr, key)
256
254
  if key.content.public_key.to_s != csr.content.public_key.to_s
257
- raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: csr.fingerprint, csr_public_key: csr.content.public_key.to_text, agent_public_key: key.content.public_key.to_text, cert_name: Puppet[:certname], ssl_dir: Puppet[:ssldir], cert_dir: Puppet[:certdir].gsub('/', '\\') }
255
+ raise Puppet::Error, _(<<ERROR_STRING) % { fingerprint: csr.fingerprint, csr_public_key: csr.content.public_key.to_text, agent_public_key: key.content.public_key.to_text, cert_name: Puppet[:certname] }
258
256
  The CSR retrieved from the master does not match the agent's public key.
259
257
  CSR fingerprint: %{fingerprint}
260
258
  CSR public key: %{csr_public_key}
@@ -263,8 +261,7 @@ To fix this, remove the CSR from both the master and the agent and then start a
263
261
  On the master:
264
262
  puppetserver ca clean --certname %{cert_name}
265
263
  On the agent:
266
- 1a. On most platforms: find %{ssl_dir} -name %{cert_name}.pem -delete
267
- 1b. On Windows: del "%{cert_dir}\\%{cert_name}.pem" /f
264
+ 1. puppet ssl clean
268
265
  2. puppet agent -t
269
266
  ERROR_STRING
270
267
  end
@@ -576,6 +573,7 @@ ERROR_STRING
576
573
  end
577
574
  end
578
575
  end
576
+ public :download_certificate_from_ca
579
577
 
580
578
  # Returns the file path for the named certificate, based on this host's
581
579
  # configuration.
@@ -64,7 +64,7 @@ class Puppet::Transaction::Persistence
64
64
  begin
65
65
  result = Puppet::Util::Yaml.safe_load_file(filename, [Symbol])
66
66
  rescue Puppet::Util::Yaml::YamlLoadError => detail
67
- Puppet.log_exception(detail, _("Transaction store file %{filename} is corrupt (%{detail}); replacing") % { filename: filename, detail: detail }, { :level => :warning })
67
+ Puppet.log_exception(detail, _("Transaction store file %{filename} is corrupt (%{detail}); replacing") % { filename: filename, detail: detail })
68
68
 
69
69
  begin
70
70
  File.rename(filename, filename + ".bad")
@@ -172,7 +172,7 @@ module Puppet
172
172
  when is == @latest
173
173
  return true
174
174
  when is == :present
175
- # This will only happen on retarded packaging systems
175
+ # This will only happen on packaging systems
176
176
  # that can't query versions.
177
177
  return true
178
178
  else
@@ -409,7 +409,10 @@ module Puppet
409
409
  resource at the same time. For instance, Puppet creates a home directory for a managed
410
410
  user if `ensure => present` and the user does not exist at the time of the Puppet run.
411
411
  If the home directory is then deleted manually, Puppet will not recreate it on the next
412
- run."
412
+ run.
413
+
414
+ Note that on Windows, this manages creation/deletion of the user profile instead of the
415
+ home directory. The user profile is stored in the `C:\Users\<username>` directory."
413
416
 
414
417
  defaultto false
415
418
 
data/lib/puppet/util.rb CHANGED
@@ -478,9 +478,13 @@ module Util
478
478
 
479
479
  def safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block)
480
480
  child_pid = Kernel.fork do
481
- $stdin.reopen(stdin)
482
- $stdout.reopen(stdout)
483
- $stderr.reopen(stderr)
481
+ STDIN.reopen(stdin)
482
+ STDOUT.reopen(stdout)
483
+ STDERR.reopen(stderr)
484
+
485
+ $stdin = STDIN
486
+ $stdout = STDOUT
487
+ $stderr = STDERR
484
488
 
485
489
  begin
486
490
  Dir.foreach('/proc/self/fd') do |f|
@@ -157,6 +157,7 @@ module Puppet::Util::Execution
157
157
  :override_locale => true,
158
158
  :custom_environment => {},
159
159
  :sensitive => false,
160
+ :suppress_window => false,
160
161
  }
161
162
 
162
163
  options = default_options.merge(options)
@@ -49,12 +49,13 @@ module Logging
49
49
  # to take advantage of the backtrace logging.
50
50
  def log_exception(exception, message = :default, options = {})
51
51
  trace = Puppet[:trace] || options[:trace]
52
+ level = options[:level] || :err
52
53
  if message == :default && exception.is_a?(Puppet::ParseErrorWithIssue)
53
54
  # Retain all detailed info and keep plain message and stacktrace separate
54
55
  backtrace = []
55
56
  build_exception_trace(backtrace, exception, trace)
56
57
  Puppet::Util::Log.create({
57
- :level => options[:level] || :err,
58
+ :level => level,
58
59
  :source => log_source,
59
60
  :message => exception.basic_message,
60
61
  :issue_code => exception.issue_code,
@@ -66,7 +67,7 @@ module Logging
66
67
  :node => exception.node
67
68
  }.merge(log_metadata))
68
69
  else
69
- err(format_exception(exception, message, trace))
70
+ send_log(level, format_exception(exception, message, trace))
70
71
  end
71
72
  end
72
73
 
@@ -8,6 +8,8 @@ module Puppet::Util::Windows::Process
8
8
 
9
9
  WAIT_TIMEOUT = 0x102
10
10
  WAIT_INTERVAL = 200
11
+ # https://docs.microsoft.com/en-us/windows/desktop/ProcThread/process-creation-flags
12
+ CREATE_NO_WINDOW = 0x08000000
11
13
 
12
14
  def execute(command, arguments, stdin, stdout, stderr)
13
15
  create_args = {
@@ -17,9 +19,11 @@ module Puppet::Util::Windows::Process
17
19
  :stdout => stdout,
18
20
  :stderr => stderr
19
21
  },
20
- :close_handles => false
22
+ :close_handles => false,
21
23
  }
22
-
24
+ if arguments[:suppress_window]
25
+ create_args[:creation_flags] = CREATE_NO_WINDOW
26
+ end
23
27
  cwd = arguments[:cwd]
24
28
  if cwd
25
29
  Dir.chdir(cwd) { Process.create(create_args) }
@@ -324,6 +324,20 @@ module Puppet::Util::Windows::Security
324
324
  # we don't check S_ISYSTEM_MISSING bit, but automatically carry over existing SYSTEM perms
325
325
  # by default set SYSTEM perms to full
326
326
  system_allow = FILE::FILE_ALL_ACCESS
327
+ else
328
+ # It is possible to set SYSTEM with a mode other than Full Control (7) however this makes no sense and in practical terms
329
+ # should not be done. We can trap these instances and correct them before being applied.
330
+ if (sd.owner == well_known_system_sid) && (owner_allow != FILE::FILE_ALL_ACCESS)
331
+ #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
332
+ Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the owner, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path }
333
+ owner_allow = FILE::FILE_ALL_ACCESS
334
+ end
335
+
336
+ if (sd.group == well_known_system_sid) && (group_allow != FILE::FILE_ALL_ACCESS)
337
+ #TRANSLATORS 'SYSTEM' is a Windows name and should not be translated
338
+ Puppet.debug _("An attempt to set mode %{mode} on item %{path} would result in the group, SYSTEM, to have less than Full Control rights. This attempt has been corrected to Full Control") % { mode: mode.to_s(8), path: path }
339
+ group_allow = FILE::FILE_ALL_ACCESS
340
+ end
327
341
  end
328
342
 
329
343
  # even though FILE_DELETE_CHILD only applies to directories, it can be set on files
@@ -40,6 +40,26 @@ module Puppet::Util::Windows
40
40
  SERVICE_CONTROL_PRESHUTDOWN = 0x0000000F
41
41
  SERVICE_CONTROL_TIMECHANGE = 0x00000010
42
42
  SERVICE_CONTROL_TRIGGEREVENT = 0x00000020
43
+ SERVICE_CONTROL_SIGNALS = {
44
+ SERVICE_CONTROL_STOP => :SERVICE_CONTROL_STOP,
45
+ SERVICE_CONTROL_PAUSE => :SERVICE_CONTROL_PAUSE,
46
+ SERVICE_CONTROL_CONTINUE => :SERVICE_CONTROL_CONTINUE,
47
+ SERVICE_CONTROL_INTERROGATE => :SERVICE_CONTROL_INTERROGATE,
48
+ SERVICE_CONTROL_SHUTDOWN => :SERVICE_CONTROL_SHUTDOWN,
49
+ SERVICE_CONTROL_PARAMCHANGE => :SERVICE_CONTROL_PARAMCHANGE,
50
+ SERVICE_CONTROL_NETBINDADD => :SERVICE_CONTROL_NETBINDADD,
51
+ SERVICE_CONTROL_NETBINDREMOVE => :SERVICE_CONTROL_NETBINDREMOVE,
52
+ SERVICE_CONTROL_NETBINDENABLE => :SERVICE_CONTROL_NETBINDENABLE,
53
+ SERVICE_CONTROL_NETBINDDISABLE => :SERVICE_CONTROL_NETBINDDISABLE,
54
+ SERVICE_CONTROL_DEVICEEVENT => :SERVICE_CONTROL_DEVICEEVENT,
55
+ SERVICE_CONTROL_HARDWAREPROFILECHANGE => :SERVICE_CONTROL_HARDWAREPROFILECHANGE,
56
+ SERVICE_CONTROL_POWEREVENT => :SERVICE_CONTROL_POWEREVENT,
57
+ SERVICE_CONTROL_SESSIONCHANGE => :SERVICE_CONTROL_SESSIONCHANGE,
58
+ SERVICE_CONTROL_PRESHUTDOWN => :SERVICE_CONTROL_PRESHUTDOWN,
59
+ SERVICE_CONTROL_TIMECHANGE => :SERVICE_CONTROL_TIMECHANGE,
60
+ SERVICE_CONTROL_TRIGGEREVENT => :SERVICE_CONTROL_TRIGGEREVENT
61
+ }
62
+
43
63
 
44
64
  # Service start type codes
45
65
  # https://docs.microsoft.com/en-us/windows/desktop/api/Winsvc/nf-winsvc-changeserviceconfigw
@@ -81,7 +101,14 @@ module Puppet::Util::Windows
81
101
  SERVICE_START_PENDING = 0x00000002
82
102
  SERVICE_STOP_PENDING = 0x00000003
83
103
  SERVICE_STOPPED = 0x00000001
84
- SERVICE_STATES = {
104
+ UNSAFE_PENDING_STATES = [SERVICE_START_PENDING, SERVICE_STOP_PENDING]
105
+ FINAL_STATES = {
106
+ SERVICE_CONTINUE_PENDING => SERVICE_RUNNING,
107
+ SERVICE_PAUSE_PENDING => SERVICE_PAUSED,
108
+ SERVICE_START_PENDING => SERVICE_RUNNING,
109
+ SERVICE_STOP_PENDING => SERVICE_STOPPED
110
+ }
111
+ SERVICE_STATES = {
85
112
  SERVICE_CONTINUE_PENDING => :SERVICE_CONTINUE_PENDING,
86
113
  SERVICE_PAUSE_PENDING => :SERVICE_PAUSE_PENDING,
87
114
  SERVICE_PAUSED => :SERVICE_PAUSED,
@@ -266,37 +293,68 @@ module Puppet::Util::Windows
266
293
  end
267
294
  module_function :exists?
268
295
 
269
- # Start a windows service, assume that the service is already in the stopped state
296
+ # Start a windows service
270
297
  #
271
298
  # @param [:string] service_name name of the service to start
272
299
  def start(service_name)
273
- open_service(service_name, SC_MANAGER_CONNECT, SERVICE_START | SERVICE_QUERY_STATUS) do |service|
274
- transition_service_state(service, SERVICE_STOP_PENDING, SERVICE_STOPPED, SERVICE_START_PENDING, SERVICE_RUNNING) do
275
- if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE
276
- raise Puppet::Util::Windows::Error.new(_("Failed to start the service"))
277
- end
300
+ Puppet.debug _("Starting the %{service_name} service") % { service_name: service_name }
301
+
302
+ valid_initial_states = [
303
+ SERVICE_STOP_PENDING,
304
+ SERVICE_STOPPED,
305
+ SERVICE_START_PENDING
306
+ ]
307
+
308
+ transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING) do |service|
309
+ if StartServiceW(service, 0, FFI::Pointer::NULL) == FFI::WIN32_FALSE
310
+ raise Puppet::Util::Windows::Error, _("Failed to start the service")
278
311
  end
279
312
  end
313
+
314
+ Puppet.debug _("Successfully started the %{service_name} service") % { service_name: service_name }
280
315
  end
281
316
  module_function :start
282
317
 
283
- # Use ControlService to send a stop signal to a windows service
318
+ # Stop a windows service
284
319
  #
285
320
  # @param [:string] service_name name of the service to stop
286
321
  def stop(service_name)
287
- open_service(service_name, SC_MANAGER_CONNECT, SERVICE_STOP | SERVICE_QUERY_STATUS) do |service|
288
- transition_service_state(service, SERVICE_START_PENDING, SERVICE_RUNNING, SERVICE_STOP_PENDING, SERVICE_STOPPED) do
289
- FFI::MemoryPointer.new(SERVICE_STATUS.size) do |status_ptr|
290
- status = SERVICE_STATUS.new(status_ptr)
291
- if ControlService(service, SERVICE_CONTROL_STOP, status) == FFI::WIN32_FALSE
292
- raise Puppet::Util::Windows::Error.new(_("Failed to send stop control to service, current state is %{current_state}. Failed with") % { current_state: status[:dwCurrentState].to_s })
293
- end
294
- end
295
- end
322
+ Puppet.debug _("Stopping the %{service_name} service") % { service_name: service_name }
323
+
324
+ valid_initial_states = SERVICE_STATES.keys - [SERVICE_STOPPED]
325
+
326
+ transition_service_state(service_name, valid_initial_states, SERVICE_STOPPED) do |service|
327
+ send_service_control_signal(service, SERVICE_CONTROL_STOP)
296
328
  end
329
+
330
+ Puppet.debug _("Successfully stopped the %{service_name} service") % { service_name: service_name }
297
331
  end
298
332
  module_function :stop
299
333
 
334
+ # Resume a paused windows service
335
+ #
336
+ # @param [:string] service_name name of the service to resume
337
+ def resume(service_name)
338
+ Puppet.debug _("Resuming the %{service_name} service") % { service_name: service_name }
339
+
340
+ valid_initial_states = [
341
+ SERVICE_PAUSE_PENDING,
342
+ SERVICE_PAUSED,
343
+ SERVICE_CONTINUE_PENDING
344
+ ]
345
+
346
+ transition_service_state(service_name, valid_initial_states, SERVICE_RUNNING) do |service|
347
+ # The SERVICE_CONTROL_CONTINUE signal can only be sent when
348
+ # the service is in the SERVICE_PAUSED state
349
+ wait_on_pending_state(service, SERVICE_PAUSE_PENDING)
350
+
351
+ send_service_control_signal(service, SERVICE_CONTROL_CONTINUE)
352
+ end
353
+
354
+ Puppet.debug _("Successfully resumed the %{service_name} service") % { service_name: service_name }
355
+ end
356
+ module_function :resume
357
+
300
358
  # Query the state of a service using QueryServiceStatusEx
301
359
  #
302
360
  # @param [:string] service_name name of the service to query
@@ -495,24 +553,74 @@ module Puppet::Util::Windows
495
553
  private :open_scm
496
554
 
497
555
  # @api private
498
- # Wait for preceeding_transition, then execute a block, then wait for resulting_transition.
499
- # Do not fail on preceeding_transition,
556
+ # Transition the service to the specified state. The block should perform
557
+ # the actual transition.
500
558
  #
501
- # @param [:handle] service handle of the service to query
502
- # @param [:Integer] preceeding_transition integer value corresponding to the transition state to wait for
503
- # before executing the block
504
- # @param [:Integer] resulting_transition integer value corresponding to the transition state to wait for
505
- # after executing the block
506
- def transition_service_state(service, preceeding_transition, preceeding_state, resulting_transition, resulting_state, &block)
507
- # ignore the return from the pending transition, we don't care what state the service
508
- # is in after this point, only that we waited on preceeding_pending_state if that's where
509
- # the service was
510
- wait_on_pending_transition(service, preceeding_transition, preceeding_state, raise_on_timeout: false)
511
- yield
512
- # After returning from the block, wait to see a pending or active state.
513
- wait_for_state_transition(service, [resulting_transition, resulting_state])
514
- # After that If the state is pending, attempt to wait for the pending operation to finish
515
- wait_on_pending_transition(service, resulting_transition, resulting_state, raise_on_timeout: true)
559
+ # @param [String] service_name the name of the service to transition
560
+ # @param [[Integer]] valid_initial_states an array of valid states that the service can transition from
561
+ # @param [:Integer] final_state the state that the service will transition to
562
+ def transition_service_state(service_name, valid_initial_states, final_state, &block)
563
+ service_access = SERVICE_START | SERVICE_STOP | SERVICE_PAUSE_CONTINUE | SERVICE_QUERY_STATUS
564
+ open_service(service_name, SC_MANAGER_CONNECT, service_access) do |service|
565
+ status = query_status(service)
566
+ initial_state = status[:dwCurrentState]
567
+
568
+ # If the service is already in the final_state, then
569
+ # no further work needs to be done
570
+ if initial_state == final_state
571
+ Puppet.debug _("The service is already in the %{final_state} state. No further work needs to be done.") % { final_state: SERVICE_STATES[final_state] }
572
+
573
+ next
574
+ end
575
+
576
+ # Check that initial_state corresponds to a valid
577
+ # initial state
578
+ unless valid_initial_states.include?(initial_state)
579
+ valid_initial_states_str = valid_initial_states.map do |state|
580
+ SERVICE_STATES[state]
581
+ end.join(", ")
582
+
583
+ raise Puppet::Error, _("The service must be in one of the %{valid_initial_states} states to perform this transition. It is currently in the %{current_state} state.") % { valid_initial_states: valid_initial_states_str, current_state: SERVICE_STATES[initial_state] }
584
+ end
585
+
586
+ # Check if there's a pending transition to the final_state. If so, then wait for
587
+ # that transition to finish.
588
+ possible_pending_states = FINAL_STATES.keys.select do |pending_state|
589
+ # SERVICE_RUNNING has two pending states, SERVICE_START_PENDING and
590
+ # SERVICE_CONTINUE_PENDING. That is why we need the #select here
591
+ FINAL_STATES[pending_state] == final_state
592
+ end
593
+ if possible_pending_states.include?(initial_state)
594
+ Puppet.debug _("There is already a pending transition to the %{final_state} state for the %{service_name} service.") % { final_state: SERVICE_STATES[final_state], service_name: service_name }
595
+ wait_on_pending_state(service, initial_state)
596
+
597
+ next
598
+ end
599
+
600
+ # If we are in an unsafe pending state like SERVICE_START_PENDING
601
+ # or SERVICE_STOP_PENDING, then we want to wait for that pending
602
+ # transition to finish before transitioning the service state.
603
+ # The reason we do this is because SERVICE_START_PENDING is when
604
+ # the service thread is being created and initialized, while
605
+ # SERVICE_STOP_PENDING is when the service thread is being cleaned
606
+ # up and destroyed. Thus there is a chance that when the service is
607
+ # in either of these states, its service thread may not yet be ready
608
+ # to perform the state transition (it may not even exist).
609
+ if UNSAFE_PENDING_STATES.include?(initial_state)
610
+ Puppet.debug _("The service is in the %{pending_state} state, which is an unsafe pending state.") % { pending_state: SERVICE_STATES[initial_state] }
611
+ wait_on_pending_state(service, initial_state)
612
+ initial_state = FINAL_STATES[initial_state]
613
+ end
614
+
615
+ Puppet.debug _("Transitioning the %{service_name} service from %{initial_state} to %{final_state}") % { service_name: service_name, initial_state: SERVICE_STATES[initial_state], final_state: SERVICE_STATES[final_state] }
616
+
617
+ yield service
618
+
619
+ Puppet.debug _("Waiting for the transition to finish")
620
+ wait_on_state_transition(service, initial_state, final_state)
621
+ end
622
+ rescue => detail
623
+ raise Puppet::Error, _("Failed to transition the %{service_name} service to the %{final_state} state. Detail: %{detail}") % { service_name: service_name, final_state: SERVICE_STATES[final_state], detail: detail }, detail.backtrace
516
624
  end
517
625
  private :transition_service_state
518
626
 
@@ -596,75 +704,110 @@ module Puppet::Util::Windows
596
704
  private :query_config
597
705
 
598
706
  # @api private
599
- # waits for a windows service to report one of the acceptable_states
707
+ # Sends a service control signal to a service
708
+ #
709
+ # @param [:handle] service handle to the service
710
+ # @param [Integer] signal the service control signal to send
711
+ def send_service_control_signal(service, signal)
712
+ FFI::MemoryPointer.new(SERVICE_STATUS.size) do |status_ptr|
713
+ status = SERVICE_STATUS.new(status_ptr)
714
+ if ControlService(service, signal, status) == FFI::WIN32_FALSE
715
+ raise Puppet::Util::Windows::Error, _("Failed to send the %{control_signal} signal to the service. Its current state is %{current_state}. Failed with") % { control_signal: SERVICE_CONTROL_SIGNALS[signal], current_state: SERVICE_STATES[status[:dwCurrentState]] }
716
+ end
717
+ end
718
+ end
719
+
720
+ # @api private
721
+ # Waits for a service to transition from one state to
722
+ # another state.
600
723
  #
601
724
  # @param [:handle] service handle to the service to wait on
602
- # @param [Array<Integer>] acceptable_states array of acceptable states to wait for
603
- # @return [bool] 'true' once the service is reporting an acceptable_state,
604
- # 'false' if the service never reached one of the acceptable_states
605
- def wait_for_state_transition(service, acceptable_states)
725
+ # @param [Integer] initial_state the state that the service is transitioning from.
726
+ # @param [Integer] final_state the state that the service is transitioning to
727
+ def wait_on_state_transition(service, initial_state, final_state)
728
+ # Get the pending state for this transition. Note that SERVICE_RUNNING
729
+ # has two possible pending states, which is why we need this logic.
730
+ if final_state != SERVICE_RUNNING
731
+ pending_state = FINAL_STATES.key(final_state)
732
+ elsif initial_state == SERVICE_STOPPED
733
+ # SERVICE_STOPPED => SERVICE_RUNNING
734
+ pending_state = SERVICE_START_PENDING
735
+ else
736
+ # SERVICE_PAUSED => SERVICE_RUNNING
737
+ pending_state = SERVICE_CONTINUE_PENDING
738
+ end
739
+
740
+ # Wait for the transition to finish
741
+ state = nil
606
742
  elapsed_time = 0
607
743
  while elapsed_time <= DEFAULT_TIMEOUT
608
744
  status = query_status(service)
609
745
  state = status[:dwCurrentState]
610
- if acceptable_states.include?(state)
611
- return true
746
+
747
+ return if state == final_state
748
+ if state == pending_state
749
+ Puppet.debug _("The service transitioned to the %{pending_state} state.") % { pending_state: SERVICE_STATES[pending_state] }
750
+ wait_on_pending_state(service, pending_state)
751
+ return
612
752
  end
753
+
613
754
  sleep(1)
614
755
  elapsed_time += 1
615
756
  end
616
- raise Puppet::Error.new(_("Transition timed out, service still in %{current_state}") % { current_state: SERVICE_STATES[state] })
757
+
758
+ # Timed out while waiting for the transition to finish. Raise an error
759
+ raise Puppet::Error, _("Timed out while waiting for the service to transition from %{initial_state} to %{final_state} OR from %{initial_state} to %{pending_state} to %{final_state}. The service's current state is %{current_state}.") % { initial_state: SERVICE_STATES[initial_state], final_state: SERVICE_STATES[final_state], pending_state: SERVICE_STATES[pending_state], current_state: SERVICE_STATES[state] }
617
760
  end
618
- private :wait_for_state_transition
761
+ private :wait_on_state_transition
619
762
 
620
763
  # @api private
621
- # waits for a windows service to report final_state if it
622
- # is in pending_state
764
+ # Waits for a service to finish transitioning from
765
+ # a pending state. The service must be in the pending state
766
+ # before invoking this routine.
623
767
  #
624
768
  # @param [:handle] service handle to the service to wait on
625
- # @param [Integer] pending_states array of acceptable states to wait on
626
- # @param [Integer] final_state the state indicating the transition is finished
627
- # @return [bool] 'true' once the service is reporting final_state,
628
- # 'false' if the service never reached final_state or was not in pending_states
629
- def wait_on_pending_transition(service, pending_state, final_state, raise_on_timeout: false)
769
+ # @param [Integer] pending_state the pending state
770
+ def wait_on_pending_state(service, pending_state)
771
+ final_state = FINAL_STATES[pending_state]
772
+
773
+ Puppet.debug _("Waiting for the pending transition to the %{final_state} state to finish.") % { final_state: SERVICE_STATES[final_state] }
774
+
630
775
  elapsed_time = 0
631
776
  last_checkpoint = -1
632
777
  loop do
633
778
  status = query_status(service)
634
779
  state = status[:dwCurrentState]
635
- return true if state == final_state
780
+
781
+ # Check if our service has finished transitioning to
782
+ # the final_state OR if an unexpected transition
783
+ # has occurred
784
+ return if state == final_state
636
785
  unless state == pending_state
637
- if raise_on_timeout
638
- raise Puppet::Error.new(_("Service was not in pending state: %{pending_state}, current state is %{current_state}") % { pending_state: SERVICE_STATES[pending_state], current_state: SERVICE_STATES[state] })
639
- else
640
- return false
641
- end
786
+ raise Puppet::Error, _("Unexpected transition to the %{current_state} state while waiting for the pending transition from %{pending_state} to %{final_state} to finish.") % { current_state: SERVICE_STATES[state], pending_state: SERVICE_STATES[pending_state], final_state: SERVICE_STATES[final_state] }
642
787
  end
643
- # When the service is in the pending state we need to do the following:
644
- # 1. check if any progress has been made since dwWaitHint using dwCheckPoint,
645
- # and fail if no progress was made
646
- # 2. if progress has been made, increment elapsed_time and set last_checkpoint
647
- # 3. sleep, then loop again if there was progress.
648
- time_to_wait = wait_hint_to_wait_time(status[:dwWaitHint])
788
+
789
+ # Check if any progress has been made since our last sleep
790
+ # using the dwCheckPoint. If no progress has been made then
791
+ # check if we've timed out, and raise an error if so
649
792
  if status[:dwCheckPoint] > last_checkpoint
650
793
  elapsed_time = 0
794
+ last_checkpoint = status[:dwCheckPoint]
651
795
  else
652
- timeout = milliseconds_to_seconds(status[:dwWaitHint]);
653
- timeout = DEFAULT_TIMEOUT if timeout < DEFAULT_TIMEOUT
654
- if elapsed_time >= (timeout)
655
- if raise_on_timeout
656
- raise Puppet::Error.new(_("Pending operation timed out, service still in %{current_state}") % { current_state: SERVICE_STATES[state] })
657
- else
658
- return false
659
- end
796
+ wait_hint = milliseconds_to_seconds(status[:dwWaitHint])
797
+ timeout = wait_hint < DEFAULT_TIMEOUT ? DEFAULT_TIMEOUT : wait_hint
798
+
799
+ if elapsed_time >= timeout
800
+ raise Puppet::Error, _("Timed out while waiting for the pending transition from %{pending_state} to %{final_state} to finish. The current state is %{current_state}.") % { pending_state: SERVICE_STATES[pending_state], final_state: SERVICE_STATES[final_state], current_state: SERVICE_STATES[state] }
660
801
  end
661
802
  end
662
- last_checkpoint = status[:dwCheckPoint]
663
- sleep(time_to_wait)
664
- elapsed_time += time_to_wait
803
+
804
+ # Wait a bit before rechecking the service's state
805
+ wait_time = wait_hint_to_wait_time(status[:dwWaitHint])
806
+ sleep(wait_time)
807
+ elapsed_time += wait_time
665
808
  end
666
809
  end
667
- private :wait_on_pending_transition
810
+ private :wait_on_pending_state
668
811
 
669
812
  # @api private
670
813
  #