Skip to content

Commit

Permalink
Add facter::structured_data_fact to allow for structured data
Browse files Browse the repository at this point in the history
  • Loading branch information
ghoneycutt committed Mar 21, 2020
1 parent c44e36e commit 3d9ae52
Show file tree
Hide file tree
Showing 7 changed files with 326 additions and 2 deletions.
38 changes: 36 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,12 @@ and populate it with `facts.txt` which is used for external facts. It
can optionally create a symlink such as `/usr/local/bin/facter` to point
to facter in the puppet package that may not be in your `$PATH`.

It has a defined type that allows for specifying external facts. This
allows you to seed already known information on the system.
It has defined types for specifying external facts. This allows you to
seed already known information on the system. `facter::fact` is for
traditional key=value where the value is a string.
`facter::structured_data_fact` allows values to be structured data such
as arrays and hashes. To achive this, the structured data is converted
to YAML format.

### Beginning with facter

Expand All @@ -39,6 +43,8 @@ class. To specify external facts, use the `facter::fact` defined type.

You can optionally specify a hash of external facts in Hiera.

### `facter::fact`

```yaml
---
facter::facts_hash:
Expand All @@ -62,6 +68,34 @@ It would also produce `/etc/facter/facts.d/location.txt` with the following cont
location=RNB
```
### `facter::structured_data_fact`
```yaml
---
facter::structured_data_facts_hash:
foo:
data:
my_array:
- one
- two
- three
my_hash:
k: v
bar:
data:
bar_array:
- one
- two
- three
file: bar.yaml
facts_dir: /factsdir
```

The above configuration would create `/etc/facter/facts.d/facts.yaml`
with the fact `my_array`. It would create `/factsdir/bar.yaml` with the
fact `bar_array`.


### Minimum usage

```puppet
Expand Down
10 changes: 10 additions & 0 deletions manifests/init.pp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
# @param facts_hash
# A hash of `facter::fact` entries.
#
# @param structured_data_facts_hash
# A hash of `facter::structured_data_fact` entries.
#
# @param facts_file
# The file in which the text based external facts are stored. This file must
# end with '.txt'.
Expand All @@ -57,6 +60,7 @@
Stdlib::Absolutepath $path_to_facter_symlink = '/usr/local/bin/facter',
Boolean $ensure_facter_symlink = false,
Hash $facts_hash = {},
Hash $structured_data_facts_hash = {},
Pattern[/\.txt*\Z/] $facts_file = 'facts.txt',
String[1] $facts_file_owner = 'root',
String[1] $facts_file_group = 'root',
Expand Down Expand Up @@ -126,4 +130,10 @@
* => $v,
}
}

$structured_data_facts_hash.each |$k, $v| {
facter::structured_data_fact { $k:
* => $v,
}
}
}
36 changes: 36 additions & 0 deletions manifests/structured_data_fact.pp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# @summary Define YAML based external structured data facts.
#
# @param data
# A hash of facts. All keys must be strings.
#
# @param file
# File in which the fact will be placed. The file name must end with '.yaml'.
#
# @param facts_dir
# Directory in which the file will be placed. If not specified, use the
# default facts_d_dir.
#
define facter::structured_data_fact (
Hash[String[1], Data] $data,
Pattern[/\.yaml*\Z/] $file = 'facts.yaml',
Optional[Stdlib::Absolutepath] $facts_dir = undef,
) {

include facter

$facts_dir_path = pick($facts_dir, $facter::facts_d_dir)
if $facts['os']['family'] == 'windows' {
$facts_file_path = "${facts_dir_path}\\${file}"
} else {
$facts_file_path = "${facts_dir_path}/${file}"
}

file { "structured_data_fact_${file}":
ensure => file,
path => $facts_file_path,
content => template('facter/structured_data_fact.erb'),
owner => $facter::facts_file_owner,
group => $facter::facts_file_group,
mode => $facter::facts_file_mode,
}
}
50 changes: 50 additions & 0 deletions spec/acceptance/facter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
facts_d_group = 'NT AUTHORITY\SYSTEM'
facts_d_mode = nil
test_facts_file = 'C:\ProgramData\PuppetLabs\facter\facts.d\\test.txt'
test_yaml_file = 'C:\ProgramData\PuppetLabs\facter\facts.d\\test.yaml'
facts_file = 'C:\ProgramData\PuppetLabs\facter\facts.d\\facts.txt'
facts_file_owner = 'NT AUTHORITY\SYSTEM'
facts_file_group = 'NT AUTHORITY\SYSTEM'
Expand All @@ -18,6 +19,7 @@
facts_d_group = 'root'
facts_d_mode = '755'
test_facts_file = '/etc/facter/facts.d/test.txt'
test_yaml_file = '/etc/facter/facts.d/test.yaml'
facts_file = '/etc/facter/facts.d/facts.txt'
facts_file_owner = 'root'
facts_file_group = 'root'
Expand Down Expand Up @@ -126,6 +128,54 @@ class { 'facter':
end
end

context 'with using facter::structured_data_fact' do
context 'should apply the manifest' do
pp = <<-EOS
facter::structured_data_fact { 'test':
data => {
'my_hash' => {
'a' => 1,
'b' => 2,
},
},
file => 'test.yaml',
}
EOS

if Gem.win_platform?
manifest = 'C:\manifest-facter_yaml.pp'

it 'creates manifest' do
File.open(manifest, 'w') { |f| f.write(pp) }
puts manifest
puts File.read(manifest)
end

describe command("PsExec -accepteula -s 'C:\\Program Files\\Puppet Labs\\Puppet\\bin\\puppet.bat' apply --debug #{manifest}") do
its(:stderr) { should contain('with error code 0') }
end
else
it 'should work with no errors' do
apply_manifest(pp, :catch_failures => true)
end
end
end

context 'and should contain facts' do
describe file(test_yaml_file) do
it { should exist }
end

describe command('puppet facts my_hash.a') do
its(:stdout) { should contain('1') }
end

describe command('puppet facts my_hash.b') do
its(:stdout) { should contain('2') }
end
end
end

context 'with setting ensure_facter_symlink to true' do
if host_inventory['platform'] == 'windows'
before { skip('Windows does not have symlinks. Skipping test.') }
Expand Down
69 changes: 69 additions & 0 deletions spec/classes/init_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,75 @@
it { should contain_exec('mkdir_p-/etc/facter/facts.d') }
end

context 'with structured_data_facts_hash specified' do
let(:params) do
{
:structured_data_facts_hash => {
'foo' => {
'data' => {
'my_array' => ['one', 'two', 'three'],
'my_hash' => { 'k' => 'v' },
},
},
'bar' => {
'data' => {
'bar_array' => ['one', 'two', 'three'],
},
'file' => 'bar.yaml',
'facts_dir' => '/factsdir',
},
}
}
end

foo_content = <<-END.gsub(/^\s+\|/, '')
|# This file is being maintained by Puppet.
|# DO NOT EDIT
|---
|my_array:
|- one
|- two
|- three
|my_hash:
| k: v
END

bar_content = <<-END.gsub(/^\s+\|/, '')
|# This file is being maintained by Puppet.
|# DO NOT EDIT
|---
|bar_array:
|- one
|- two
|- three
END

it { should contain_facter__structured_data_fact('foo') }
it { should contain_facter__structured_data_fact('bar') }

it {
should contain_file('structured_data_fact_facts.yaml').with({
'ensure' => 'file',
'path' => '/etc/facter/facts.d/facts.yaml',
'content' => foo_content,
'owner' => 'root',
'group' => 'root',
'mode' => '0644',
})
}

it {
should contain_file('structured_data_fact_bar.yaml').with({
'ensure' => 'file',
'path' => '/factsdir/bar.yaml',
'content' => bar_content,
'owner' => 'root',
'group' => 'root',
'mode' => '0644',
})
}
end

context 'with facts specified as a hash with different file and facts_dir' do
let(:params) do
{
Expand Down
122 changes: 122 additions & 0 deletions spec/defines/structured_data_fact_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
require 'spec_helper'

describe 'facter::structured_data_fact' do
context 'on RedHat' do
redhat = {
supported_os: [
{
'operatingsystem' => 'RedHat',
'operatingsystemrelease' => ['7'],
},
],
}

on_supported_os(redhat).each do |_os, os_facts|
let(:facts) do
os_facts
end

context 'with file and facts_dir specified' do
let(:title) { 'fact1' }
let(:params) do
{
:data => {
'my_array' => ['one', 'two', 'three'],
'my_hash' => { 'k' => 'v' },
},
:file => 'custom.yaml',
:facts_dir => '/factsdir',
}
end

it { should contain_class('facter') }

# These must exist or the coverage report lists these incorrectly as
# untouched resources. These resources are all from the facter class.
it { should contain_file('facts_file') }
it { should contain_file('facts_d_directory') }
it { should contain_exec('mkdir_p-/etc/facter/facts.d') }

content = <<-END.gsub(/^\s+\|/, '')
|# This file is being maintained by Puppet.
|# DO NOT EDIT
|---
|my_array:
|- one
|- two
|- three
|my_hash:
| k: v
END

it {
should contain_file('structured_data_fact_custom.yaml').with({
'ensure' => 'file',
'path' => '/factsdir/custom.yaml',
'content' => content,
'owner' => 'root',
'group' => 'root',
'mode' => '0644',
})
}
end
end # end on_supported_os(redhat)
end # context "on RedHat"

context 'on windows' do
windows = {
supported_os: [
{
'operatingsystem' => 'windows',
'operatingsystemrelease' => ['2016'],
},
],
}

on_supported_os(windows).each do |_os, os_facts|
let(:facts) do
os_facts
end

context 'with fact and facts_dir specified' do
let(:title) { 'fact1' }
let(:params) do
{
:data => {
'my_array' => ['one', 'two', 'three'],
'my_hash' => { 'k' => 'v' },
},
:file => 'custom.yaml',
:facts_dir => 'C:\factsdir',
}
end

# These must exist or the coverage report lists these incorrectly as
# untouched resources. These resources are all from the facter class.
it { should contain_exec('mkdir_p-C:\ProgramData\PuppetLabs\facter\facts.d') }

content = <<-END.gsub(/^\s+\|/, '')
|# This file is being maintained by Puppet.
|# DO NOT EDIT
|---
|my_array:
|- one
|- two
|- three
|my_hash:
| k: v
END

it {
should contain_file('structured_data_fact_custom.yaml').with({
'ensure' => 'file',
'content' => content,
'path' => 'C:\factsdir\custom.yaml',
'owner' => 'NT AUTHORITY\SYSTEM',
'group' => 'NT AUTHORITY\SYSTEM',
})
}
end
end # end on_supported_os(windows)
end # context "on windows"
end
3 changes: 3 additions & 0 deletions templates/structured_data_fact.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This file is being maintained by Puppet.
# DO NOT EDIT
<%= @data.to_yaml -%>

0 comments on commit 3d9ae52

Please sign in to comment.