puppet 6.0.2-universal-darwin → 6.0.3-universal-darwin
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.
- checksums.yaml +4 -4
- data/Gemfile +2 -2
- data/Gemfile.lock +11 -11
- data/lib/puppet/application.rb +5 -0
- data/lib/puppet/application/apply.rb +1 -0
- data/lib/puppet/application/script.rb +1 -1
- data/lib/puppet/application/ssl.rb +119 -49
- data/lib/puppet/defaults.rb +9 -27
- data/lib/puppet/face/node/clean.rb +0 -1
- data/lib/puppet/feature/base.rb +1 -1
- data/lib/puppet/file_serving/fileset.rb +1 -1
- data/lib/puppet/pops/validation/checker4_0.rb +4 -2
- data/lib/puppet/provider/package/windows.rb +2 -2
- data/lib/puppet/provider/package/windows/exe_package.rb +3 -10
- data/lib/puppet/provider/service/windows.rb +11 -3
- data/lib/puppet/provider/user/useradd.rb +2 -10
- data/lib/puppet/resource/catalog.rb +1 -5
- data/lib/puppet/ssl/host.rb +7 -9
- data/lib/puppet/transaction/persistence.rb +1 -1
- data/lib/puppet/type/package.rb +1 -1
- data/lib/puppet/type/user.rb +4 -1
- data/lib/puppet/util.rb +7 -3
- data/lib/puppet/util/execution.rb +1 -0
- data/lib/puppet/util/logging.rb +3 -2
- data/lib/puppet/util/windows/process.rb +6 -2
- data/lib/puppet/util/windows/security.rb +14 -0
- data/lib/puppet/util/windows/service.rb +217 -74
- data/lib/puppet/util/windows/user.rb +3 -5
- data/lib/puppet/version.rb +1 -1
- data/locales/ja/puppet.po +505 -276
- data/locales/puppet.pot +250 -111
- data/man/man5/puppet.conf.5 +8 -1
- data/man/man8/puppet-ssl.8 +22 -2
- data/man/man8/puppet.8 +1 -1
- data/spec/integration/parser/collection_spec.rb +4 -8
- data/spec/integration/type/file_spec.rb +6 -6
- data/spec/integration/util/windows/security_spec.rb +10 -7
- data/spec/integration/util/windows/user_spec.rb +37 -17
- data/spec/lib/puppet/test_ca.rb +1 -1
- data/spec/unit/agent_spec.rb +2 -2
- data/spec/unit/application/apply_spec.rb +41 -2
- data/spec/unit/application/face_base_spec.rb +1 -1
- data/spec/unit/application/ssl_spec.rb +160 -110
- data/spec/unit/application_spec.rb +29 -11
- data/spec/unit/configurer/downloader_spec.rb +1 -1
- data/spec/unit/configurer_spec.rb +5 -5
- data/spec/unit/face/node_spec.rb +1 -3
- data/spec/unit/file_serving/fileset_spec.rb +11 -11
- data/spec/unit/network/http/connection_spec.rb +2 -2
- data/spec/unit/pops/validator/validator_spec.rb +24 -10
- data/spec/unit/provider/package/windows/exe_package_spec.rb +3 -3
- data/spec/unit/provider/package/windows_spec.rb +4 -4
- data/spec/unit/provider/service/windows_spec.rb +21 -3
- data/spec/unit/provider/user/useradd_spec.rb +2 -2
- data/spec/unit/resource/catalog_spec.rb +2 -2
- data/spec/unit/ssl/host_spec.rb +1 -1
- data/spec/unit/transaction/persistence_spec.rb +4 -4
- data/spec/unit/util/execution_spec.rb +19 -1
- data/spec/unit/util/logging_spec.rb +58 -0
- data/spec/unit/util/windows/service_spec.rb +344 -191
- 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 }
|
data/lib/puppet/feature/base.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
56
|
+
command = "\"#{uninstall_string}\""
|
64
57
|
else
|
65
|
-
command
|
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
|
-
:
|
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
|
-
|
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
|
-
|
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
|
data/lib/puppet/ssl/host.rb
CHANGED
@@ -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]
|
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
|
-
|
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
|
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
|
-
|
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]
|
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
|
-
|
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 }
|
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")
|
data/lib/puppet/type/package.rb
CHANGED
data/lib/puppet/type/user.rb
CHANGED
@@ -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
|
-
|
482
|
-
|
483
|
-
|
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|
|
data/lib/puppet/util/logging.rb
CHANGED
@@ -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 =>
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
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
|
-
#
|
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
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
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
|
-
#
|
499
|
-
#
|
556
|
+
# Transition the service to the specified state. The block should perform
|
557
|
+
# the actual transition.
|
500
558
|
#
|
501
|
-
# @param [
|
502
|
-
# @param [
|
503
|
-
#
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
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
|
-
#
|
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 [
|
603
|
-
# @
|
604
|
-
|
605
|
-
|
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
|
-
|
611
|
-
|
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
|
-
|
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 :
|
761
|
+
private :wait_on_state_transition
|
619
762
|
|
620
763
|
# @api private
|
621
|
-
#
|
622
|
-
#
|
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]
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
644
|
-
#
|
645
|
-
#
|
646
|
-
#
|
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
|
-
|
653
|
-
timeout = DEFAULT_TIMEOUT
|
654
|
-
|
655
|
-
|
656
|
-
|
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
|
-
|
663
|
-
|
664
|
-
|
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 :
|
810
|
+
private :wait_on_pending_state
|
668
811
|
|
669
812
|
# @api private
|
670
813
|
#
|