Revision ee647b4c
Added by Eric Helms about 10 years ago
modules/foreman/templates/external_node_v2.rb.erb | ||
---|---|---|
:puppetuser => "<%= @puppet_user %>", # e.g. puppet
|
||
:facts => <%= @facts %>, # true/false to upload facts
|
||
:timeout => 10,
|
||
:threads => nil,
|
||
# if CA is specified, remote Foreman host will be verified
|
||
:ssl_ca => "<%= @ssl_ca -%>", # e.g. /var/lib/puppet/ssl/certs/ca.pem
|
||
# ssl_cert and key are required if require_ssl_puppetmasters is enabled in Foreman
|
||
... | ... | |
SETTINGS[:timeout] || 3
|
||
end
|
||
|
||
def thread_count
|
||
return SETTINGS[:threads].to_i if not SETTINGS[:threads].nil? and SETTINGS[:threads].to_i > 0
|
||
require 'facter'
|
||
processors = Facter.value(:processorcount).to_i
|
||
processors > 0 ? processors : 1
|
||
end
|
||
|
||
class Http_Fact_Requests
|
||
include Enumerable
|
||
|
||
def initialize
|
||
@results_array = []
|
||
end
|
||
|
||
def <<(val)
|
||
@results_array << val
|
||
end
|
||
|
||
def each(&block)
|
||
@results_array.each(&block)
|
||
end
|
||
|
||
def pop
|
||
@results_array.pop
|
||
end
|
||
end
|
||
|
||
require 'etc'
|
||
require 'net/http'
|
||
require 'net/https'
|
||
... | ... | |
end
|
||
end
|
||
|
||
def upload_all_facts
|
||
def process_all_facts(http_requests)
|
||
Dir["#{puppetdir}/yaml/facts/*.yaml"].each do |f|
|
||
certname = File.basename(f, ".yaml")
|
||
# Skip empty host fact yaml files
|
||
if File.size(f) != 0
|
||
upload_facts(certname, f)
|
||
req = generate_fact_request(certname, f)
|
||
if http_requests
|
||
http_requests << [certname, req]
|
||
elsif req
|
||
upload_facts(certname, req)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
... | ... | |
{'facts' => puppet_facts['values'], 'name' => hostname, 'certname' => certname}
|
||
end
|
||
|
||
def upload_facts(certname, filename)
|
||
def initialize_http(uri)
|
||
res = Net::HTTP.new(uri.host, uri.port)
|
||
res.use_ssl = uri.scheme == 'https'
|
||
if res.use_ssl?
|
||
if SETTINGS[:ssl_ca] && !SETTINGS[:ssl_ca].empty?
|
||
res.ca_file = SETTINGS[:ssl_ca]
|
||
res.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||
else
|
||
res.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||
end
|
||
if SETTINGS[:ssl_cert] && !SETTINGS[:ssl_cert].empty? && SETTINGS[:ssl_key] && !SETTINGS[:ssl_key].empty?
|
||
res.cert = OpenSSL::X509::Certificate.new(File.read(SETTINGS[:ssl_cert]))
|
||
res.key = OpenSSL::PKey::RSA.new(File.read(SETTINGS[:ssl_key]), nil)
|
||
end
|
||
end
|
||
res
|
||
end
|
||
|
||
def generate_fact_request(certname, filename)
|
||
# Temp file keeping the last run time
|
||
stat = stat_file("#{certname}-push-facts")
|
||
last_run = File.exists?(stat) ? File.stat(stat).mtime.utc : Time.now - 365*24*60*60
|
||
... | ... | |
req.add_field('Accept', 'application/json,version=2' )
|
||
req.content_type = 'application/json'
|
||
req.body = build_body(certname, filename).to_json
|
||
res = Net::HTTP.new(uri.host, uri.port)
|
||
res.use_ssl = uri.scheme == 'https'
|
||
if res.use_ssl?
|
||
if SETTINGS[:ssl_ca] && !SETTINGS[:ssl_ca].empty?
|
||
res.ca_file = SETTINGS[:ssl_ca]
|
||
res.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||
else
|
||
res.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
||
end
|
||
if SETTINGS[:ssl_cert] && !SETTINGS[:ssl_cert].empty? && SETTINGS[:ssl_key] && !SETTINGS[:ssl_key].empty?
|
||
res.cert = OpenSSL::X509::Certificate.new(File.read(SETTINGS[:ssl_cert]))
|
||
res.key = OpenSSL::PKey::RSA.new(File.read(SETTINGS[:ssl_key]), nil)
|
||
end
|
||
end
|
||
res.start { |http| http.request(req) }
|
||
cache("#{certname}-push-facts", "Facts from this host were last pushed to #{uri} at #{Time.now}\n")
|
||
req
|
||
rescue => e
|
||
raise "Could not send facts to Foreman: #{e}"
|
||
raise "Could not generate facts for Foreman: #{e}"
|
||
end
|
||
end
|
||
end
|
||
... | ... | |
end
|
||
res = http.start { |http| http.request(req) }
|
||
|
||
raise "Error retrieving node #{certname}: #{res.class}" unless res.code == "200"
|
||
raise "Error retrieving node #{certname}: #{res.class}\nCheck Foreman's /var/log/foreman/production.log for more information." unless res.code == "200"
|
||
res.body
|
||
end
|
||
|
||
def upload_facts(certname, req)
|
||
return nil if req.nil?
|
||
uri = URI.parse("#{url}/api/hosts/facts")
|
||
begin
|
||
res = initialize_http(uri)
|
||
res.start { |http| http.request(req) }
|
||
cache("#{certname}-push-facts", "Facts from this host were last pushed to #{uri} at #{Time.now}\n")
|
||
rescue => e
|
||
raise "Could not send facts to Foreman: #{e}"
|
||
end
|
||
end
|
||
|
||
def upload_facts_parallel(http_fact_requests, wait = true)
|
||
t = thread_count.times.map {
|
||
Thread.new(http_fact_requests) do |fact_requests|
|
||
while factref = fact_requests.pop
|
||
certname = factref[0]
|
||
httpobj = factref[1]
|
||
if httpobj
|
||
upload_facts(certname, httpobj)
|
||
end
|
||
end
|
||
end
|
||
}
|
||
if wait
|
||
t.each(&:join)
|
||
end
|
||
end
|
||
|
||
def watch_and_send_facts(parallel)
|
||
begin
|
||
require 'inotify'
|
||
rescue LoadError
|
||
puts "You need the `ruby-inotify` (not inotify!) gem to watch for fact updates"
|
||
exit 2
|
||
end
|
||
|
||
watch_descriptors = []
|
||
pending = []
|
||
threads = thread_count
|
||
last_send = Time.now
|
||
|
||
inotify_limit = `sysctl fs.inotify.max_user_watches`.gsub(/[^\d]/, '').to_i
|
||
|
||
inotify = Inotify.new
|
||
|
||
inotify.add_watch("#{puppetdir}/yaml/facts", Inotify::CREATE)
|
||
|
||
yamls = Dir["#{puppetdir}/yaml/facts/*.yaml"]
|
||
|
||
if yamls.length > inotify_limit
|
||
puts "Looks like your inotify watch limit is #{inotify_limit} but you are asking to watch at least #{yamls.length} fact files."
|
||
puts "Increase the watch limit via the system tunable fs.inotify.max_user_watches, exiting."
|
||
exit 2
|
||
end
|
||
|
||
yamls.each do |f|
|
||
begin
|
||
watch_descriptors[inotify.add_watch(f, Inotify::CLOSE_WRITE)] = f
|
||
end
|
||
end
|
||
|
||
inotify.each_event do |ev|
|
||
fn = watch_descriptors[ev.wd]
|
||
add_watch = false
|
||
|
||
if !fn
|
||
fn = ev.name
|
||
add_watch = true
|
||
end
|
||
|
||
if File.extname(fn) != ".yaml"
|
||
next
|
||
end
|
||
|
||
if add_watch || (ev.mask & Inotify::ONESHOT)
|
||
watch_descriptors[inotify.add_watch(fn, Inotify::CLOSE_WRITE)] = fn
|
||
end
|
||
|
||
if fn
|
||
certname = File.basename(fn, ".yaml")
|
||
req = generate_fact_request certname, fn
|
||
if parallel
|
||
pending << [certname,req]
|
||
else
|
||
upload_facts(certname,req)
|
||
end
|
||
end
|
||
if parallel && (pending.length >= threads || ((last_send + 5) < Time.now))
|
||
if pending.length > 0
|
||
upload_facts_parallel(pending, false)
|
||
pending = []
|
||
end
|
||
last_send = Time.now
|
||
end
|
||
end
|
||
end
|
||
|
||
# Actual code starts here
|
||
|
||
if __FILE__ == $0 then
|
||
... | ... | |
begin
|
||
Process::GID.change_privilege(Etc.getgrnam(puppetuser).gid) unless Etc.getpwuid.name == puppetuser
|
||
Process::UID.change_privilege(Etc.getpwnam(puppetuser).uid) unless Etc.getpwuid.name == puppetuser
|
||
# Facter (in thread_count) tries to read from $HOME, which is still /root after the UID change
|
||
ENV['HOME'] = Etc.getpwnam(puppetuser).dir
|
||
rescue
|
||
$stderr.puts "cannot switch to user #{puppetuser}, continuing as '#{Etc.getpwuid.name}'"
|
||
end
|
||
|
||
begin
|
||
no_env = ARGV.delete("--no-environment")
|
||
if ARGV.delete("--push-facts")
|
||
watch = ARGV.delete("--watch-facts")
|
||
push_facts_parallel = ARGV.delete("--push-facts-parallel")
|
||
push_facts = ARGV.delete("--push-facts")
|
||
if watch && ! ( push_facts || push_facts_parallel )
|
||
raise "Cannot watch for facts without specifying --push-facts or --push-facts-parallel"
|
||
end
|
||
if push_facts
|
||
# push all facts files to Foreman and don't act as an ENC
|
||
upload_all_facts
|
||
process_all_facts(false)
|
||
elsif push_facts_parallel
|
||
http_fact_requests = Http_Fact_Requests.new
|
||
process_all_facts(http_fact_requests)
|
||
upload_facts_parallel(http_fact_requests)
|
||
else
|
||
certname = ARGV[0] || raise("Must provide certname as an argument")
|
||
# send facts to Foreman - enable 'facts' setting to activate
|
||
# if you use this option below, make sure that you don't send facts to foreman via the rake task or push facts alternatives.
|
||
#
|
||
if SETTINGS[:facts]
|
||
upload_facts certname, "#{puppetdir}/yaml/facts/#{certname}.yaml"
|
||
req = generate_fact_request certname, "#{puppetdir}/yaml/facts/#{certname}.yaml"
|
||
upload_facts(certname, req)
|
||
end
|
||
#
|
||
# query External node
|
||
... | ... | |
rescue TimeoutError, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED
|
||
# Read from cache, we got some sort of an error.
|
||
result = read_cache(certname)
|
||
ensure
|
||
end
|
||
|
||
if no_env
|
||
require 'yaml'
|
||
yaml = YAML.load(result)
|
||
if no_env
|
||
yaml.delete('environment')
|
||
end
|
||
yaml.delete('environment')
|
||
# Always reset the result to back to clean yaml on our end
|
||
puts yaml.to_yaml
|
||
else
|
||
puts result
|
||
end
|
||
end
|
||
rescue => e
|
||
warn e
|
||
exit 1
|
||
end
|
||
if watch
|
||
watch_and_send_facts(push_facts_parallel)
|
||
end
|
||
end
|
Also available in: Unified diff
Update puppet modules