-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
Copy pathdaemon.rb
executable file
·219 lines (185 loc) · 6.9 KB
/
daemon.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'fileutils'
require 'puppet/util/windows/daemon'
# This file defines utilities for logging to eventlog. While it lives inside
# Puppet, it is completely independent and loads no other parts of Puppet, so we
# can safely require *just* it.
require 'puppet/util/windows/eventlog'
# monkey patches ruby Process to add .create method
require 'puppet/util/windows/monkey_patches/process'
class WindowsDaemon < Puppet::Util::Windows::Daemon
CREATE_NEW_CONSOLE = 0x00000010
@run_thread = nil
@LOG_TO_FILE = false
@loglevel = 0
LOG_FILE = File.expand_path(File.join(ENV.fetch('ALLUSERSPROFILE', nil), 'PuppetLabs', 'puppet', 'var', 'log', 'windows.log'))
LEVELS = [:debug, :info, :notice, :warning, :err, :alert, :emerg, :crit]
LEVELS.each do |level|
define_method("log_#{level}") do |msg|
log(msg, level)
end
end
def service_init
end
def service_main(*argsv)
argsv = (argsv << ARGV).flatten.compact
args = argsv.join(' ')
@loglevel = LEVELS.index(argsv.index('--debug') ? :debug : :notice)
@LOG_TO_FILE = (argsv.index('--logtofile') ? true : false)
if @LOG_TO_FILE
FileUtils.mkdir_p(File.dirname(LOG_FILE))
args = args.gsub("--logtofile", "")
end
base_dir = File.expand_path(File.join(File.dirname(__FILE__), '..'))
load_env(base_dir)
# The puppet installer registers a 'Puppet' event source. For the moment events will be logged with this key, but
# it may be a good idea to split the Service and Puppet events later so it's easier to read in the windows Event Log.
#
# Example code to register an event source;
# eventlogdll = File.expand_path(File.join(basedir, 'puppet', 'ext', 'windows', 'eventlog', 'puppetres.dll'))
# if (File.exist?(eventlogdll))
# Win32::EventLog.add_event_source(
# 'source' => "Application",
# 'key_name' => "Puppet Agent",
# 'category_count' => 3,
# 'event_message_file' => eventlogdll,
# 'category_message_file' => eventlogdll
# )
# end
puppet = File.join(base_dir, 'puppet', 'bin', 'puppet')
ruby = File.join(base_dir, 'puppet', 'bin', 'ruby.exe')
ruby_puppet_cmd = "\"#{ruby}\" \"#{puppet}\""
unless File.exist?(puppet)
log_err("File not found: '#{puppet}'")
return
end
log_debug("Using '#{puppet}'")
cmdline_debug = argsv.index('--debug') ? :debug : nil
@loglevel = parse_log_level(ruby_puppet_cmd, cmdline_debug)
log_notice('Service started')
service = self
@run_thread = Thread.new do
while service.running?
runinterval = service.parse_runinterval(ruby_puppet_cmd)
if service.state == RUNNING or service.state == IDLE
service.log_notice("Executing agent with arguments: #{args}")
pid = Process.create(:command_line => "#{ruby_puppet_cmd} agent --onetime #{args}", :creation_flags => CREATE_NEW_CONSOLE).process_id
service.log_debug("Process created: #{pid}")
else
service.log_debug("Service is paused. Not invoking Puppet agent")
end
service.log_debug("Service worker thread waiting for #{runinterval} seconds")
sleep(runinterval)
service.log_debug('Service worker thread woken up')
end
rescue Exception => e
service.log_exception(e)
end
@run_thread.join
rescue Exception => e
log_exception(e)
ensure
log_notice('Service stopped')
end
def service_stop
log_notice('Service stopping / killing worker thread')
@run_thread.kill if @run_thread
end
def service_pause
log_notice('Service pausing')
end
def service_resume
log_notice('Service resuming')
end
def service_shutdown
log_notice('Host shutting down')
end
# Interrogation handler is just for debug. Can be commented out or removed entirely.
# def service_interrogate
# log_debug('Service is being interrogated')
# end
def log_exception(e)
log_err(e.message)
log_err(e.backtrace.join("\n"))
end
def log(msg, level)
if LEVELS.index(level) >= @loglevel
if @LOG_TO_FILE
# without this change its possible that we get Encoding errors trying to write UTF-8 messages in current codepage
File.open(LOG_FILE, 'a:UTF-8') { |f| f.puts("#{Time.now} Puppet (#{level}): #{msg}") }
end
native_type, native_id = Puppet::Util::Windows::EventLog.to_native(level)
report_windows_event(native_type, native_id, msg.to_s)
end
end
def report_windows_event(type, id, message)
eventlog = nil
eventlog = Puppet::Util::Windows::EventLog.open("Puppet")
eventlog.report_event(
:event_type => type, # EVENTLOG_ERROR_TYPE, etc
:event_id => id, # 0x01 or 0x02, 0x03 etc.
:data => message # "the message"
)
rescue Exception
# Ignore all errors
ensure
unless eventlog.nil?
eventlog.close
end
end
# Parses runinterval.
#
# @param puppet_path [String] The file path for the Puppet executable.
# @return runinterval [Integer] How often to do a Puppet run, in seconds.
def parse_runinterval(puppet_path)
begin
runinterval = %x(#{puppet_path} config --section agent --log_level notice print runinterval).chomp
if runinterval == ''
runinterval = 1800
log_err("Failed to determine runinterval, defaulting to #{runinterval} seconds")
else
# Use Kernel#Integer because to_i will return 0 with non-numeric strings.
runinterval = Integer(runinterval)
end
rescue Exception => e
log_exception(e)
runinterval = 1800
end
runinterval
end
def parse_log_level(puppet_path, cmdline_debug)
begin
loglevel = %x(#{puppet_path} config --section agent --log_level notice print log_level).chomp
unless loglevel && respond_to?("log_#{loglevel}")
loglevel = :notice
log_err("Failed to determine loglevel, defaulting to #{loglevel}")
end
rescue Exception => e
log_exception(e)
loglevel = :notice
end
LEVELS.index(cmdline_debug || loglevel.to_sym)
end
private
def load_env(base_dir)
# ENV that uses backward slashes
ENV['FACTER_env_windows_installdir'] = base_dir.tr('/', '\\')
ENV['PL_BASEDIR'] = base_dir.tr('/', '\\')
ENV['PUPPET_DIR'] = File.join(base_dir, 'puppet').tr('/', '\\')
ENV['OPENSSL_CONF'] = File.join(base_dir, 'puppet', 'ssl', 'openssl.cnf').tr('/', '\\')
ENV['SSL_CERT_DIR'] = File.join(base_dir, 'puppet', 'ssl', 'certs').tr('/', '\\')
ENV['SSL_CERT_FILE'] = File.join(base_dir, 'puppet', 'ssl', 'cert.pem').tr('/', '\\')
ENV['Path'] = [
File.join(base_dir, 'puppet', 'bin'),
File.join(base_dir, 'bin')
].join(';').tr('/', '\\') + ';' + ENV.fetch('Path', nil)
# ENV that uses forward slashes
ENV['RUBYLIB'] = "#{File.join(base_dir, 'puppet', 'lib')};#{ENV.fetch('RUBYLIB', nil)}"
rescue => e
log_exception(e)
end
end
if __FILE__ == $PROGRAM_NAME
WindowsDaemon.mainloop
end