diff --git a/README.md b/README.md index 4bd988d6f..4e13d84f5 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,76 @@ class { '::puppet': } ``` +# Manage Facter/Openfact configuration + +This module can manage the Facter/Openfact configuration if requested. + +```puppet +class { 'puppet': + # ... + facter_config => { + blocklist => [ + 'EC2', # Remove if running in AWS cloud + 'az_metadata', # Remove if running in Azure cloud + 'cloud', # Remove if running in any cloud + 'load_averages', + 'memory_noise', + 'mountpoints', + 'processors.speed', + 'system_uptime', + ], + ttls => [ + { 'timezone' => '7 days' }, + { 'networking.fqdn' => '1 hour' }, + ], + 'fact-groups' => { + 'memory_noise' => [ + 'memory.swap.available', + 'memory.swap.available_bytes', + 'memory.swap.capacity', + 'memory.swap.used', + 'memory.swap.used_bytes', + 'memory.system.available', + 'memory.system.available_bytes', + 'memory.system.capacity', + 'memory.system.used', + 'memory.system.used_bytes', + ], + } + } +} +``` + +This is also possible with Hiera: + +```yaml +puppet::facter_config: + blocklist: + - 'EC2', # Remove if running in AWS cloud + - 'az_metadata', # Remove if running in Azure cloud + - 'cloud', # Remove if running in any cloud + - 'load_averages', + - 'memory_noise', + - 'mountpoints', + - 'processors.speed', + - 'system_uptime', + ttls: + - timezone: 30 days + - 'networking.fqdn': 1 hour + fact-groups: + memory_noise: + - 'memory.swap.available', + - 'memory.swap.available_bytes', + - 'memory.swap.capacity', + - 'memory.swap.used', + - 'memory.swap.used_bytes', + - 'memory.system.available', + - 'memory.system.available_bytes', + - 'memory.system.capacity', + - 'memory.system.used', + - 'memory.system.used_bytes', +``` + # Contributing * Fork the project diff --git a/manifests/agent.pp b/manifests/agent.pp index 8ad790c9b..e1b4bdf91 100644 --- a/manifests/agent.pp +++ b/manifests/agent.pp @@ -5,6 +5,11 @@ contain puppet::agent::config contain puppet::agent::service + if $puppet::facter_config.length > 0 { + contain puppet::agent::facter + Class['puppet::agent::install'] -> Class['puppet::agent::facter'] + } + Class['puppet::agent::install'] ~> Class['puppet::agent::config', 'puppet::agent::service'] Class['puppet::config', 'puppet::agent::config'] ~> Class['puppet::agent::service'] } diff --git a/manifests/agent/facter.pp b/manifests/agent/facter.pp new file mode 100644 index 000000000..0fb18e5af --- /dev/null +++ b/manifests/agent/facter.pp @@ -0,0 +1,26 @@ +# Manage Facter/Openfact configuration +# @param config_dir +# Override configuration directory +# @param config_file +# Override configuration file name +# @param config +# Override configuration +class puppet::agent::facter ( + Stdlib::Absolutepath $config_dir = $puppet::facter_config_dir, + String[1] $config_file = 'facter.conf', + Puppet::Facter::Config $config = $puppet::facter_config, +) { + $config_text = @("CONFIG") + # Managed by Puppet + ${config.stdlib::to_json_pretty()} + |-CONFIG + + file { $config_dir: + ensure => 'directory', + } + + file { "${config_dir}/${config_file}": + ensure => 'file', + content => $config_text, + } +} diff --git a/manifests/init.pp b/manifests/init.pp index 0b3b321c4..3ee899d13 100644 --- a/manifests/init.pp +++ b/manifests/init.pp @@ -566,6 +566,10 @@ # $server_jolokia_metrics_allowlist:: The allowlist of clients that # can query the jolokia /metrics/v2 endpoint # +# $facter_config_dir:: Override Facter/Openfact configuration directory +# +# $facter_config:: A hash representing Facter/Openfact configuration. +# # === Usage: # # * Simple usage: @@ -772,6 +776,8 @@ Optional[Stdlib::Absolutepath] $server_versioned_code_content = undef, Array[String[1]] $server_jolokia_metrics_allowlist = [], Stdlib::Filemode $puppetconf_mode = $puppet::params::puppetconf_mode, + Stdlib::Absolutepath $facter_config_dir = $puppet::params::facter_config_dir, + Puppet::Facter::Config $facter_config = {}, ) inherits puppet::params { contain puppet::config diff --git a/manifests/params.pp b/manifests/params.pp index 59a60c5dd..03bfe162c 100644 --- a/manifests/params.pp +++ b/manifests/params.pp @@ -72,6 +72,7 @@ $server_ruby_load_paths = [] $server_jruby_gem_home = undef $puppetconf_mode = '0674' + $facter_config_dir = 'C:/ProgramData/PuppetLabs/facter/etc' } /^(FreeBSD|DragonFly)$/ : { @@ -92,6 +93,7 @@ $server_ruby_load_paths = [$facts['ruby']['sitedir'], "${ruby_gem_dir}/facter-${facts['facterversion']}/lib"] $server_jruby_gem_home = '/var/puppet/server/data/puppetserver/jruby-gems' $puppetconf_mode = '0644' + $facter_config_dir = '/usr/local/etc/facter' } 'Archlinux' : { @@ -111,6 +113,7 @@ $server_ruby_load_paths = [] $server_jruby_gem_home = undef $puppetconf_mode = '0644' + $facter_config_dir = '/etc/puppetlabs/facter' } default : { @@ -129,6 +132,7 @@ $server_puppetserver_logdir = '/var/log/puppetlabs/puppetserver' $server_ruby_load_paths = ['/opt/puppetlabs/puppet/lib/ruby/vendor_ruby'] $server_jruby_gem_home = '/opt/puppetlabs/server/data/puppetserver/jruby-gems' + $facter_config_dir = '/etc/puppetlabs/facter' } else { $dir = '/etc/puppet' $codedir = $facts['os']['family'] ? { @@ -156,6 +160,7 @@ $server_ruby_load_paths = [] $server_jruby_gem_home = '/var/lib/puppet/jruby-gems' } + $facter_config_dir = '/etc/facter' } $root_group = undef $puppetconf_mode = '0644' diff --git a/spec/classes/puppet_agent_spec.rb b/spec/classes/puppet_agent_spec.rb index 1964752fd..5f8528f7f 100644 --- a/spec/classes/puppet_agent_spec.rb +++ b/spec/classes/puppet_agent_spec.rb @@ -11,21 +11,25 @@ client_package = "puppet#{puppet_major}" confdir = '/usr/local/etc/puppet' package_provider = nil + facter_config_dir = '/usr/local/etc/facter' when 'windows' bindir = 'C:/ProgramData/PuppetLabs/puppet/bin' client_package = 'puppet-agent' confdir = 'C:/ProgramData/PuppetLabs/puppet/etc' package_provider = 'chocolatey' + facter_config_dir = 'C:/ProgramData/PuppetLabs/facter/etc' when 'Archlinux' bindir = '/usr/bin' client_package = 'puppet' confdir = '/etc/puppetlabs/puppet' package_provider = nil + facter_config_dir = '/etc/puppetlabs/facter' else bindir = '/opt/puppetlabs/bin' client_package = 'puppet-agent' confdir = '/etc/puppetlabs/puppet' package_provider = nil + facter_config_dir = '/etc/puppetlabs/facter' end let(:facts) do @@ -407,6 +411,23 @@ is_expected.not_to contain_puppet__config__agent('environment') end end + + context 'with facter_config set' do + let(:params) { super().merge(facter_config: { facts: { blocklist: ["EC2"] } }) } + + it 'sets facter configuration properly' do + # Using get_content() helper from spec_helper.rb + json_text = get_content(catalogue, "#{facter_config_dir}/facter.conf") + parsed = JSON.parse(json_text.join("\n")) + expect(parsed).to eq({ 'facts' => { 'blocklist' => ["EC2"] } }) + end + + context 'with facter_config_dir set' do + let(:params) { super().merge(facter_config_dir: '/target/etc/puppetlabs/facter') } + + it { is_expected.to contain_file('/target/etc/puppetlabs/facter/facter.conf') } + end + end end end end diff --git a/spec/type_aliases/facter/config/ttl_spec.rb b/spec/type_aliases/facter/config/ttl_spec.rb new file mode 100644 index 000000000..ffb3de82f --- /dev/null +++ b/spec/type_aliases/facter/config/ttl_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe 'Puppet::Facter::Config::TTL' do + [ + 'ns', + 'nano', + 'nanos', + 'nanosecond', + 'nanoseconds', + 'us', + 'micro', + 'micros', + 'microsecond', + 'microseconds', + 'ms', + 'mili', + 'milis', + 'millisecond', + 'milliseconds', + 's', + 'second', + 'seconds', + 'm', + 'minute', + 'minutes', + 'h', + 'hours', + 'd', + 'day', + 'days', + ].each do |t| + it { is_expected.to allow_value("123 #{t}") } + end +end diff --git a/types/facter/config.pp b/types/facter/config.pp new file mode 100644 index 000000000..edb195dec --- /dev/null +++ b/types/facter/config.pp @@ -0,0 +1,7 @@ +# @summary Facter configuration type +type Puppet::Facter::Config = Struct[{ + Optional['facts'] => Puppet::Facter::Config::Facts, + Optional['global'] => Puppet::Facter::Config::Global, + Optional['cli'] => Puppet::Facter::Config::CLI, + Optional['fact-groups'] => Hash[String[1], Array[String[1]]], +}] diff --git a/types/facter/config/cli.pp b/types/facter/config/cli.pp new file mode 100644 index 000000000..18a7f64cf --- /dev/null +++ b/types/facter/config/cli.pp @@ -0,0 +1,9 @@ +# @summary Facter `cli` configuration type +# +# @note All these settings are ignored when called from the Ruby API (by Puppet/OpenVox) +type Puppet::Facter::Config::CLI = Struct[{ + Optional['debug'] => Boolean, + Optional['trace'] => Boolean, + Optional['verbose'] => Boolean, + Optional['log-level'] => Enum['none','trace','debug','info','warn','error','fatal'], +}] diff --git a/types/facter/config/facts.pp b/types/facter/config/facts.pp new file mode 100644 index 000000000..b3357be42 --- /dev/null +++ b/types/facter/config/facts.pp @@ -0,0 +1,5 @@ +# @summary Facter `facts` configuration type +type Puppet::Facter::Config::Facts = Struct[{ + Optional['blocklist'] => Array[String[1]], + Optional['ttls'] => Array[Hash[String[1], Puppet::Facter::Config::TTL]], +}] diff --git a/types/facter/config/global.pp b/types/facter/config/global.pp new file mode 100644 index 000000000..749e698ac --- /dev/null +++ b/types/facter/config/global.pp @@ -0,0 +1,12 @@ +# @summary Facter `global` configuration type +# +# @note Options below cannot be `true` when using Facter/Openfact with Puppet/OpenVox: +# * `no-custom-facts` +# * `no-ruby` +type Puppet::Facter::Config::Global = Struct[{ + Optional['external-dir'] => Array[Stdlib::Absolutepath], + Optional['custom-dir'] => Array[Stdlib::Absolutepath], + Optional['no-external-facts'] => Boolean, + Optional['no-custom-facts'] => Boolean[false], # Cannot be true + Optional['no-ruby'] => Boolean[false], # Cannot be true +}] diff --git a/types/facter/config/ttl.pp b/types/facter/config/ttl.pp new file mode 100644 index 000000000..171002a40 --- /dev/null +++ b/types/facter/config/ttl.pp @@ -0,0 +1,7 @@ +# @summary Facter `facts.ttls` ttl value type +type Puppet::Facter::Config::TTL = Variant[ + Integer[0], + # See STRING_TO_SECONDS and ttls_to_seconds() in + # https://github.com/OpenVoxProject/openfact/blob/main/lib/facter/framework/config/fact_groups.rb + Pattern[/^\d+( +(ns|nano(s)?|nanosecond(s)?|us|micro(s)?|microsecond(s)?|ms|mili(s)?|millisecond(s)?|s|second(s)?|m|minute(s)?|h|hour(s)?|d|day(s)?))?$/] +]