From e7f2fc54cebfbb2522b2c2a39e2cfbaf7cf6c861 Mon Sep 17 00:00:00 2001
From: Dimadozen <114386621+Dimadozen@users.noreply.github.com>
Date: Fri, 12 Apr 2024 10:52:04 +0200
Subject: [PATCH 1/6] Add ENC description
---
README.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.md b/README.md
index 4f12c9fa..69d2e0ec 100644
--- a/README.md
+++ b/README.md
@@ -97,6 +97,8 @@ The following environment variables are supported:
| **CA_ALLOW_SUBJECT_ALT_NAMES** | Whether or not SSL certificates containing Subject Alternative Names should be signed by the CA. Does nothing unless `CA_ENABLED=true`.
`false` |
| **PUPPET_REPORTS** | Sets `reports` in puppet.conf
`puppetdb` |
| **PUPPET_STORECONFIGS** | Sets `storeconfigs` in puppet.conf
`true` |
+| **PUPPET_NODE_TERMINUS** | Sets `node_terminus` in puppet.conf
Defaults to unset. |
+| **PUPPET_EXTERNAL_NODES** | Sets `external_nodes` in puppet.conf
Defaults to unset. |
| **PUPPET_STORECONFIGS_BACKEND** | Sets `storeconfigs_backend` in puppet.conf
`puppetdb` |
| **PUPPETSERVER_MAX_ACTIVE_INSTANCES** | The maximum number of JRuby instances allowed
`1` |
| **PUPPETSERVER_MAX_REQUESTS_PER_INSTANCE** | The maximum HTTP requests a JRuby instance will handle in its lifetime (disable instance flushing)
`0` |
From 73d3cefa9a87123b4006cae3311e50809f951966 Mon Sep 17 00:00:00 2001
From: Dimadozen <114386621+Dimadozen@users.noreply.github.com>
Date: Fri, 12 Apr 2024 10:53:30 +0200
Subject: [PATCH 2/6] Copy ENC files to image
---
puppetserver/Dockerfile | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/puppetserver/Dockerfile b/puppetserver/Dockerfile
index a261e9a7..4ae89a1a 100644
--- a/puppetserver/Dockerfile
+++ b/puppetserver/Dockerfile
@@ -125,6 +125,11 @@ COPY conf.d/product.conf /etc/puppetlabs/puppetserver/conf.d/
COPY puppetdb.conf /var/tmp/puppet/
+COPY enc/* /etc/puppetlabs/puppet
+COPY reports/* /opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/reports
+RUN chmod +x /etc/puppetlabs/puppet/*.rb && \
+ chmod +x /opt/puppetlabs/puppet/lib/ruby/vendor_ruby/puppet/reports/*.rb
+
LABEL org.label-schema.name="Puppet Server ($build_type)" \
org.label-schema.version="$PUPPETSERVER_VERSION" \
org.label-schema.vcs-ref="$vcs_ref" \
From c55d684115caabc3e444abbf5d87393504b66182 Mon Sep 17 00:00:00 2001
From: Dimadozen <114386621+Dimadozen@users.noreply.github.com>
Date: Fri, 12 Apr 2024 10:54:11 +0200
Subject: [PATCH 3/6] Add Foreman ENC file
---
puppetserver/enc/foreman.rb | 470 ++++++++++++++++++++++++++++++++++++
1 file changed, 470 insertions(+)
create mode 100644 puppetserver/enc/foreman.rb
diff --git a/puppetserver/enc/foreman.rb b/puppetserver/enc/foreman.rb
new file mode 100644
index 00000000..a8411b5f
--- /dev/null
+++ b/puppetserver/enc/foreman.rb
@@ -0,0 +1,470 @@
+#!/usr/bin/env ruby
+
+# Script usually acts as an ENC for a single host, with the certname supplied as argument
+# if 'facts' is true, the YAML facts for the host are uploaded
+# ENC output is printed and cached
+#
+# If --push-facts is given as the only arg, it uploads facts for all hosts and then exits.
+# Useful in scenarios where the ENC isn't used.
+
+require 'rbconfig'
+require 'yaml'
+
+if RbConfig::CONFIG['host_os'] =~ /freebsd|dragonfly/i
+ $settings_file ||= '/usr/local/etc/puppet/foreman.yaml'
+else
+ $settings_file ||= File.exist?('/etc/puppetlabs/puppet/foreman.yaml') ? '/etc/puppetlabs/puppet/foreman.yaml' : '/etc/puppet/foreman.yaml'
+end
+
+SETTINGS = YAML.load_file($settings_file)
+
+# Default external encoding
+if defined?(Encoding)
+ Encoding.default_external = Encoding::UTF_8
+end
+
+def url
+ SETTINGS[:url] || raise("Must provide URL in #{$settings_file}")
+end
+
+def puppetdir
+ SETTINGS[:puppetdir] || raise("Must provide puppet base directory in #{$settings_file}")
+end
+
+def puppetuser
+ SETTINGS[:puppetuser] || 'puppet'
+end
+
+def fact_extension
+ SETTINGS[:fact_extension] || 'yaml'
+end
+
+def fact_directory
+ data_dir = fact_extension == 'yaml' ? 'yaml' : 'server_data'
+ File.join(puppetdir, data_dir, 'facts')
+end
+
+def fact_file(certname)
+ File.join(fact_directory, "#{certname}.#{fact_extension}")
+end
+
+def fact_files
+ Dir[File.join(fact_directory, "*.#{fact_extension}")]
+end
+
+def certname_from_filename(filename)
+ File.basename(filename, ".#{fact_extension}")
+end
+
+def stat_file(certname)
+ FileUtils.mkdir_p "#{puppetdir}/yaml/foreman/"
+ "#{puppetdir}/yaml/foreman/#{certname}.yaml"
+end
+
+def tsecs
+ SETTINGS[:timeout] || 10
+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
+
+class FactUploadError < StandardError; end
+class NodeRetrievalError < StandardError; end
+
+require 'etc'
+require 'net/http'
+require 'net/https'
+require 'fileutils'
+require 'timeout'
+begin
+ require 'json'
+rescue LoadError
+ # Debian packaging guidelines state to avoid needing rubygems, so
+ # we only try to load it if the first require fails (for RPMs)
+ begin
+ require 'rubygems' rescue nil
+ require 'json'
+ rescue LoadError => e
+ puts "You need the `json` gem to use the Foreman ENC script"
+ # code 1 is already used below
+ exit 2
+ end
+end
+
+def parse_file(filename, mac_address_workaround = false)
+ case File.extname(filename)
+ when '.yaml'
+ data = File.read(filename)
+ quote_macs!(data) if mac_address_workaround && YAML.load('22:22:22:22:22:22').is_a?(Integer)
+ YAML.safe_load(data.gsub(/\!ruby\/object.*$/,''), permitted_classes: [Symbol, Time])
+ when '.json'
+ JSON.parse(File.read(filename))
+ else
+ raise "Unknown extension for file '#{filename}'"
+ end
+end
+
+def empty_values_hash?(facts_file)
+ puppet_facts = parse_file(facts_file)
+ puppet_facts['values'].empty?
+end
+
+def process_host_facts(certname)
+ f = fact_file(certname)
+ if File.size(f) != 0
+ if empty_values_hash?(f)
+ puts "Empty values hash in fact file #{f}, not uploading"
+ return 0
+ end
+
+ req = generate_fact_request(certname, f)
+ begin
+ upload_facts(certname, req) if req
+ return 0
+ rescue => e
+ $stderr.puts "During fact upload occurred an exception: #{e}"
+ return 1
+ end
+ else
+ $stderr.puts "Fact file #{f} does not contain any facts"
+ return 2
+ end
+end
+
+def process_all_facts(http_requests)
+ fact_files.each do |f|
+ # Skip empty host fact files
+ if File.size(f) != 0
+ if empty_values_hash?(f)
+ puts "Empty values hash in fact file #{f}, not uploading"
+ next
+ end
+
+ certname = certname_from_filename(f)
+ req = generate_fact_request(certname, f)
+ if http_requests
+ http_requests << [certname, req]
+ elsif req
+ upload_facts(certname, req)
+ end
+ else
+ $stderr.puts "Fact file #{f} does not contain any fact"
+ end
+ end
+end
+
+def quote_macs! facts
+ # Adds single quotes to all unquoted mac addresses in the raw yaml fact string
+ # if they might otherwise be interpreted as base60 ints
+ facts.gsub!(/: ([0-5][0-9](:[0-5][0-9]){5})$/,": '\\1'")
+end
+
+def build_body(certname,filename)
+ puppet_facts = parse_file(filename, true)
+ hostname = puppet_facts['values']['fqdn'] || certname
+
+ # if there is no environment in facts
+ # get it from node file ({puppetdir}/yaml/node/
+ unless puppet_facts['values'].key?('environment') || puppet_facts['values'].key?('agent_specified_environment')
+ node_filename = filename.sub('/facts/', '/node/')
+ if File.exist?(node_filename)
+ node_data = parse_file(node_filename)
+
+ if node_data.key?('environment')
+ puppet_facts['values']['environment'] = node_data['environment']
+ end
+ end
+ end
+
+ begin
+ require 'facter'
+ puppet_facts['values']['puppetmaster_fqdn'] = Facter.value(:fqdn).to_s
+ rescue LoadError
+ puppet_facts['values']['puppetmaster_fqdn'] = `hostname -f`.strip
+ end
+
+ # filter any non-printable char from the value, if it is a String
+ puppet_facts['values'].each do |key, val|
+ if val.is_a? String
+ puppet_facts['values'][key] = val.scan(/[[:print:]]/).join
+ end
+ end
+
+ {'facts' => puppet_facts['values'], 'name' => hostname, 'certname' => certname}
+end
+
+def initialize_http(uri)
+ res = Net::HTTP.new(uri.host, uri.port)
+ res.open_timeout = SETTINGS[:timeout]
+ res.read_timeout = SETTINGS[:timeout]
+ 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
+ last_fact = File.exists?(filename) ? File.stat(filename).mtime.utc : Time.at(0)
+ if last_fact > last_run
+ begin
+ uri = URI.parse("#{url}/api/hosts/facts")
+ req = Net::HTTP::Post.new(uri.request_uri)
+ req.add_field('Accept', 'application/json,version=2' )
+ req.content_type = 'application/json'
+ req.body = build_body(certname, filename).to_json
+ req
+ rescue => e
+ raise "Could not generate facts for Foreman: #{e}"
+ end
+ end
+end
+
+def cache(certname, result)
+ File.open(stat_file(certname), 'w') {|f| f.write(result) }
+end
+
+def read_cache(certname)
+ File.read(stat_file(certname))
+rescue => e
+ raise "Unable to read from Cache file: #{e}"
+end
+
+def enc(certname)
+ uri = URI.parse("#{url}/node/#{certname}?format=yml")
+ req = Net::HTTP::Get.new(uri.request_uri)
+ initialize_http(uri).start do |http|
+ response = http.request(req)
+
+ unless response.code == "200"
+ raise NodeRetrievalError, "Error retrieving node #{certname}: #{response.class}\nCheck Foreman's /var/log/foreman/production.log for more information."
+ end
+ response.body
+ end
+end
+
+def upload_facts(certname, req)
+ return nil if req.nil?
+ uri = URI.parse("#{url}/api/hosts/facts")
+ begin
+ initialize_http(uri).start do |http|
+ response = http.request(req)
+ if response.code.start_with?('2')
+ cache("#{certname}-push-facts", "Facts from this host were last pushed to #{uri} at #{Time.now}\n")
+ else
+ $stderr.puts "#{certname}: During the fact upload the server responded with: #{response.code} #{response.message}. Error is ignored and the execution continues."
+ $stderr.puts response.body
+ end
+ end
+ rescue => e
+ $stderr.puts "During fact upload occured an exception: #{e}"
+ raise FactUploadError, "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
+
+ fact_dir = fact_directory
+
+ # actually we need only MOVED_TO events because puppet uses File.rename after tmp file created and flushed.
+ # see lib/puppet/util.rb near line 469
+ inotify.add_watch(fact_dir, Inotify::CREATE | Inotify::MOVED_TO )
+
+ files = fact_files
+
+ if files.length > inotify_limit
+ puts "Looks like your inotify watch limit is #{inotify_limit} but you are asking to watch at least #{files.length} fact files."
+ puts "Increase the watch limit via the system tunable fs.inotify.max_user_watches, exiting."
+ exit 2
+ end
+
+ files.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
+
+ unless fn
+ # inotify returns basename for renamed file as ev.name
+ # but we need full path
+ fn = File.join(fact_dir, ev.name)
+ add_watch = true
+ end
+
+ if File.extname(fn) != ".#{fact_extension}"
+ next
+ end
+
+ if add_watch || (ev.mask & Inotify::ONESHOT)
+ watch_descriptors[inotify.add_watch(fn, Inotify::CLOSE_WRITE)] = fn
+ end
+
+ if fn
+ certname = certname_from_filename(fn)
+ 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
+ # Setuid to puppet user if we can
+ 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
+ # Change CWD to the determined home directory before continuing to make
+ # sure we don't reside in /root or anywhere else we don't have access
+ # permissions
+ Dir.chdir ENV['HOME']
+ rescue
+ $stderr.puts "cannot switch to user #{puppetuser}, continuing as '#{Etc.getpwuid.name}'"
+ end
+
+ begin
+ no_env = ARGV.delete("--no-environment")
+ 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
+ if ARGV.empty?
+ process_all_facts(false)
+ else
+ process_host_facts(ARGV[0])
+ end
+ 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")
+ #
+ # query External node
+ begin
+ result = ""
+ Timeout.timeout(tsecs) do
+ # 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]
+ req = generate_fact_request(certname, fact_file(certname))
+ upload_facts(certname, req)
+ end
+
+ result = enc(certname)
+ cache(certname, result)
+ end
+ rescue TimeoutError, SocketError, Errno::EHOSTUNREACH, Errno::ECONNREFUSED, NodeRetrievalError, FactUploadError => e
+ $stderr.puts "Serving cached ENC: #{e}"
+ # Read from cache, we got some sort of an error.
+ result = read_cache(certname)
+ end
+
+ if no_env
+ require 'yaml'
+ yaml = YAML.load(result)
+ 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
From d8230415bda950cab3e88fde162622a1020181b1 Mon Sep 17 00:00:00 2001
From: Dimadozen <114386621+Dimadozen@users.noreply.github.com>
Date: Fri, 12 Apr 2024 10:54:50 +0200
Subject: [PATCH 4/6] Add Foreman reports script
---
puppetserver/reports/foreman.rb | 199 ++++++++++++++++++++++++++++++++
1 file changed, 199 insertions(+)
create mode 100644 puppetserver/reports/foreman.rb
diff --git a/puppetserver/reports/foreman.rb b/puppetserver/reports/foreman.rb
new file mode 100644
index 00000000..960a6a44
--- /dev/null
+++ b/puppetserver/reports/foreman.rb
@@ -0,0 +1,199 @@
+# copy this file to your report dir - e.g. /usr/lib/ruby/1.8/puppet/reports/
+# add this report in your puppetmaster reports - e.g, in your puppet.conf add:
+# reports=log, foreman # (or any other reports you want)
+# configuration is in /etc/puppet/foreman.yaml
+
+require 'puppet'
+require 'net/http'
+require 'net/https'
+require 'rbconfig'
+require 'uri'
+require 'yaml'
+begin
+ require 'json'
+rescue LoadError
+ # Debian packaging guidelines state to avoid needing rubygems, so
+ # we only try to load it if the first require fails (for RPMs)
+ begin
+ require 'rubygems' rescue nil
+ require 'json'
+ rescue LoadError => e
+ puts "You need the `json` gem to use the Foreman ENC script"
+ # code 1 is already used below
+ exit 2
+ end
+end
+
+if RbConfig::CONFIG['host_os'] =~ /freebsd|dragonfly/i
+ $settings_file ||= '/usr/local/etc/puppet/foreman.yaml'
+else
+ $settings_file ||= File.exist?('/etc/puppetlabs/puppet/foreman.yaml') ? '/etc/puppetlabs/puppet/foreman.yaml' : '/etc/puppet/foreman.yaml'
+end
+
+SETTINGS = YAML.load_file($settings_file)
+
+Puppet::Reports.register_report(:foreman) do
+ desc "Sends reports directly to Foreman"
+
+ def process
+ # Default retry limit is 1.
+ retry_limit = SETTINGS[:report_retry_limit] ? SETTINGS[:report_retry_limit] : 1
+ tries = 0
+ begin
+ # check for report metrics
+ raise(Puppet::ParseError, "Invalid report: can't find metrics information for #{self.host}") if self.metrics.nil?
+
+ uri = URI.parse(foreman_url)
+ http = Net::HTTP.new(uri.host, uri.port)
+ if SETTINGS[:report_timeout]
+ http.open_timeout = SETTINGS[:report_timeout]
+ http.read_timeout = SETTINGS[:report_timeout]
+ end
+ http.use_ssl = uri.scheme == 'https'
+ if http.use_ssl?
+ if SETTINGS[:ssl_ca] && !SETTINGS[:ssl_ca].empty?
+ http.ca_file = SETTINGS[:ssl_ca]
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ else
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+ if SETTINGS[:ssl_cert] && !SETTINGS[:ssl_cert].empty? && SETTINGS[:ssl_key] && !SETTINGS[:ssl_key].empty?
+ http.cert = OpenSSL::X509::Certificate.new(File.read(SETTINGS[:ssl_cert]))
+ http.key = OpenSSL::PKey::RSA.new(File.read(SETTINGS[:ssl_key]), nil)
+ end
+ end
+ req = Net::HTTP::Post.new("#{uri.path}/api/config_reports")
+ req.add_field('Accept', 'application/json,version=2' )
+ req.content_type = 'application/json'
+ req.body = {'config_report' => generate_report}.to_json
+ response = http.request(req)
+ rescue Exception => e
+ if (tries += 1) < retry_limit
+ Puppet.err "Could not send report to Foreman at #{foreman_url}/api/config_reports (attempt #{tries}/#{retry_limit}). Retrying... Stacktrace: #{e}\n#{e.backtrace}"
+ retry
+ else
+ raise Puppet::Error, "Could not send report to Foreman at #{foreman_url}/api/config_reports: #{e}\n#{e.backtrace}"
+ end
+ end
+ end
+
+ def generate_report
+ report = {}
+ set_report_format
+ report['host'] = self.host
+ # Time.to_s behaves differently in 1.8 / 1.9 so we explicity set the 1.9 format
+ report['reported_at'] = self.time.utc.strftime("%Y-%m-%d %H:%M:%S UTC")
+ report['status'] = metrics_to_hash(self)
+ report['metrics'] = m2h(self.metrics)
+ report['logs'] = logs_to_array(self.logs)
+
+ report
+ end
+
+ private
+
+ METRIC = %w[applied restarted failed failed_restarts skipped pending]
+
+ def metrics_to_hash(report)
+ report_status = {}
+ metrics = self.metrics
+
+ # find our metric values
+ METRIC.each do |m|
+ if @format == 0
+ report_status[m] = metrics["resources"][m.to_sym] unless metrics["resources"].nil?
+ else
+ h=translate_metrics_to26(m)
+ mv = metrics[h[:type]]
+ report_status[m] = mv[h[:name].to_sym] + mv[h[:name].to_s] rescue nil
+ end
+ report_status[m] ||= 0
+ end
+
+ # special fix for false warning about skips
+ # sometimes there are skip values, but there are no error messages, we ignore them.
+ if report_status["skipped"] > 0 and ((report_status.values.inject(:+)) - report_status["skipped"] == report.logs.size)
+ report_status["skipped"] = 0
+ end
+ # fix for reports that contain no metrics (i.e. failed catalog)
+ if @format > 1 and report.respond_to?(:status) and report.status == "failed"
+ report_status["failed"] += 1
+ end
+ # fix for Puppet non-resource errors (i.e. failed catalog fetches before falling back to cache)
+ report_status["failed"] += report.logs.find_all {|l| l.source =~ /Puppet$/ && l.level.to_s == 'err' }.count
+
+ return report_status
+ end
+
+ def m2h metrics
+ h = {}
+ metrics.each do |title, mtype|
+ h[mtype.name] ||= {}
+ mtype.values.each{|m| h[mtype.name].merge!({m[0].to_s => m[2]})}
+ end
+ return h
+ end
+
+ def logs_to_array logs
+ h = []
+ logs.each do |log|
+ # skipping debug messages, we dont want them in Foreman's db
+ next if log.level == :debug
+
+ # skipping catalog summary run messages, we dont want them in Foreman's db
+ next if log.message =~ /^Finished catalog run in \d+.\d+ seconds$/
+
+ # Match Foreman's slightly odd API format...
+ l = { 'log' => { 'sources' => {}, 'messages' => {} } }
+ l['log']['level'] = log.level.to_s
+ l['log']['messages']['message'] = log.message
+ l['log']['sources']['source'] = log.source
+ h << l
+ end
+ return h
+ end
+
+ # The metrics layout has changed in Puppet 2.6.x release,
+ # this method attempts to align the bit value metrics and the new name scheme in 2.6.x
+ # returns a hash of { :type => "metric type", :name => "metric_name"}
+ def translate_metrics_to26 metric
+ case metric
+ when "applied"
+ case @format
+ when 0..1
+ { :type => "total", :name => :changes}
+ else
+ { :type => "changes", :name => "total"}
+ end
+ when "failed_restarts"
+ case @format
+ when 0..1
+ { :type => "resources", :name => metric}
+ else
+ { :type => "resources", :name => "failed_to_restart"}
+ end
+ when "pending"
+ { :type => "events", :name => "noop" }
+ else
+ { :type => "resources", :name => metric}
+ end
+ end
+
+ def set_report_format
+ @format ||= case
+ when self.instance_variables.detect {|v| v.to_s == "@environment"}
+ @format = 3
+ when self.instance_variables.detect {|v| v.to_s == "@report_format"}
+ @format = 2
+ when self.instance_variables.detect {|v| v.to_s == "@resource_statuses"}
+ @format = 1
+ else
+ @format = 0
+ end
+ end
+
+ def foreman_url
+ SETTINGS[:url] || raise(Puppet::Error, "Must provide URL in #{$settings_file}")
+ end
+
+end
From 1f0bbd5072725437ef179d39d116d7b449bfdf4c Mon Sep 17 00:00:00 2001
From: Dimadozen <114386621+Dimadozen@users.noreply.github.com>
Date: Fri, 12 Apr 2024 11:13:31 +0200
Subject: [PATCH 5/6] Add setup script
---
puppetserver/docker-entrypoint.d/86-setup-enc.sh | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 puppetserver/docker-entrypoint.d/86-setup-enc.sh
diff --git a/puppetserver/docker-entrypoint.d/86-setup-enc.sh b/puppetserver/docker-entrypoint.d/86-setup-enc.sh
new file mode 100644
index 00000000..fbf40d79
--- /dev/null
+++ b/puppetserver/docker-entrypoint.d/86-setup-enc.sh
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+if [ -n "$PUPPET_NODE_TERMINUS" ]; then
+ puppet config set node_terminus $PUPPET_NODE_TERMINUS --section master
+fi
+
+if [ -n "$PUPPET_EXTERNAL_NODES" ]; then
+ puppet config set external_nodes $PUPPET_EXTERNAL_NODES --section master
+fi
From 4b62b984c7b443472359622c2b03fd30d15db221 Mon Sep 17 00:00:00 2001
From: Dimadozen <114386621+Dimadozen@users.noreply.github.com>
Date: Fri, 12 Apr 2024 14:38:39 +0200
Subject: [PATCH 6/6] User section server in stead of deprecated master
---
puppetserver/docker-entrypoint.d/86-setup-enc.sh | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/puppetserver/docker-entrypoint.d/86-setup-enc.sh b/puppetserver/docker-entrypoint.d/86-setup-enc.sh
index fbf40d79..b722bb97 100644
--- a/puppetserver/docker-entrypoint.d/86-setup-enc.sh
+++ b/puppetserver/docker-entrypoint.d/86-setup-enc.sh
@@ -1,9 +1,9 @@
#!/bin/sh
if [ -n "$PUPPET_NODE_TERMINUS" ]; then
- puppet config set node_terminus $PUPPET_NODE_TERMINUS --section master
+ puppet config set node_terminus $PUPPET_NODE_TERMINUS --section server
fi
if [ -n "$PUPPET_EXTERNAL_NODES" ]; then
- puppet config set external_nodes $PUPPET_EXTERNAL_NODES --section master
+ puppet config set external_nodes $PUPPET_EXTERNAL_NODES --section server
fi