diff --git a/.fixtures.yml b/.fixtures.yml index c5c50d9..7262377 100644 --- a/.fixtures.yml +++ b/.fixtures.yml @@ -3,5 +3,7 @@ --- fixtures: forge_modules: + stdlib: puppetlabs/stdlib # dep of puppetlabs/concat augeas: camptocamp/augeas reboot: puppetlabs/reboot + concat: puppetlabs/concat diff --git a/examples/config.pp b/examples/config.pp new file mode 100644 index 0000000..2cbcee6 --- /dev/null +++ b/examples/config.pp @@ -0,0 +1 @@ +include pi::config diff --git a/examples/config_fragment.pp b/examples/config_fragment.pp new file mode 100644 index 0000000..5882865 --- /dev/null +++ b/examples/config_fragment.pp @@ -0,0 +1,14 @@ +class { 'pi::config': + reboot => false, # reboots are problematic for acceptance testing +} + +pi::config::fragment { 'disable_overscan=1': } +pi::config::fragment { 'dtparam=audio=on': + order => 1, +} +pi::config::fragment { 'hdmi_mode=1': + order => 99, +} +pi::config::fragment { 'foo': + content => "hdmi_safe=1\n", +} diff --git a/examples/fragments.pp b/examples/fragments.pp new file mode 100644 index 0000000..0184009 --- /dev/null +++ b/examples/fragments.pp @@ -0,0 +1,20 @@ +class { 'pi::config': + reboot => false, # reboots are problematic for acceptance testing + fragments => { + 'dtoverlay=pps-gpio,gpiopin=4' => { + 'order' => 99, + }, + 'dtparam=i2c_arm=on' => { + 'order' => 1, + }, + 'dtparam=spi=on' => {}, + 'serial port' => { + # lint:ignore:strict_indent + 'content' => @("CONTENT"), + enable_uart=1 + init_uart_baud=9600 + | CONTENT + # lint:endignore + }, + }, +} diff --git a/manifests/config.pp b/manifests/config.pp new file mode 100644 index 0000000..3fe94fb --- /dev/null +++ b/manifests/config.pp @@ -0,0 +1,33 @@ +# @summary +# Manages `/boot/config.txt` +# +# @param fragments +# A hash of profile::pi::config::fragments to be concatenated into +# `/boot/config.txt`. +# +# @param reboot +# Whether or not to force a reboot when `/boot/config.txt` changes. +# +class pi::config ( + Hash[String[1], Hash] $fragments = {}, + Boolean $reboot = true, +) { + concat { '/boot/config.txt': + ensure => present, + mode => '0755', # this is the default, +x seems odd + } + + $fragments.each | String $name, Hash $conf | { + pi::config::fragment { $name: + * => $conf, + } + } + + if ($reboot) { + reboot { '/boot/config.txt': + apply => finished, + message => 'Rebooting to apply /boot/config.txt changes', + when => refreshed, + } + } +} diff --git a/manifests/config/fragment.pp b/manifests/config/fragment.pp new file mode 100644 index 0000000..f85ca82 --- /dev/null +++ b/manifests/config/fragment.pp @@ -0,0 +1,30 @@ +# @summary +# Create a /boot/config.txt fragment +# +# @param content +# /boot/config.txt configuration fragment +# +# @param order +# Order of the fragment within /boot/config.txt +# +define pi::config::fragment ( + Optional[String[1]] $content = undef, + Integer[1] $order = 50, +) { + include pi::config + + $_real_content = $content ? { + undef => "${name}\n", + default => $content, + } + + concat::fragment { $name: + target => '/boot/config.txt', + content => $_real_content, + order => $order, + } + + if Class['pi::config']['reboot'] { + Concat::Fragment[$name] ~> Reboot['/boot/config.txt'] + } +} diff --git a/metadata.json b/metadata.json index a61eebe..9e748c0 100644 --- a/metadata.json +++ b/metadata.json @@ -13,6 +13,10 @@ { "name": "puppetlabs/reboot", "version_requirement": ">= 5.0.0 < 6.0.0" + }, + { + "name": "puppetlabs/concat", + "version_requirement": ">= 9.0.0 < 10.0.0" } ], "operatingsystem_support": [ diff --git a/spec/acceptance/config/fragment_spec.rb b/spec/acceptance/config/fragment_spec.rb new file mode 100644 index 0000000..1bb95f5 --- /dev/null +++ b/spec/acceptance/config/fragment_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'pi::config::fragment define' do + let(:config_txt) do + <<~CONFIG + dtparam=audio=on + disable_overscan=1 + hdmi_safe=1 + hdmi_mode=1 + CONFIG + end + + include_context 'config.txt test setup' + include_examples 'the example', 'config_fragment.pp' + + describe file('/boot/config.txt') do + it { is_expected.to be_file } + its(:content) { is_expected.to match config_txt } + end +end diff --git a/spec/acceptance/config_spec.rb b/spec/acceptance/config_spec.rb new file mode 100644 index 0000000..9b856aa --- /dev/null +++ b/spec/acceptance/config_spec.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +require 'spec_helper_acceptance' + +describe 'pi::config class' do + context 'without any parameters' do + include_examples 'the example', 'config.pp' + end + + context 'with parameters =>' do + let(:config_txt) do + <<~CONFIG + dtparam=i2c_arm=on + dtparam=spi=on + enable_uart=1 + init_uart_baud=9600 + dtoverlay=pps-gpio,gpiopin=4 + CONFIG + end + + include_context 'config.txt test setup' + include_examples 'the example', 'fragments.pp' + + describe file('/boot/config.txt') do + it { is_expected.to be_file } + its(:content) { is_expected.to match config_txt } + end + end +end diff --git a/spec/classes/config_spec.rb b/spec/classes/config_spec.rb new file mode 100644 index 0000000..d05a265 --- /dev/null +++ b/spec/classes/config_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'pi::config' do + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } + + it { is_expected.to compile.with_all_deps } + + it { is_expected.to contain_concat('/boot/config.txt').with_mode('0755') } + it { is_expected.to contain_reboot('/boot/config.txt') } + + context 'with fragments param' do + let(:params) do + { + fragments: { + 'foo' => { + 'content' => 'foo=bar', + }, + 'baz' => { + 'content' => 'baz=quix', + }, + }, + } + end + + it { is_expected.to contain_concat__fragment('foo').with_content('foo=bar') } + it { is_expected.to contain_concat__fragment('baz').with_content('baz=quix') } + end + + context 'with reboot => false' do + let(:params) do + { + reboot: false, + } + end + + it { is_expected.not_to contain_reboot('/boot/config.txt') } + end + end + end +end diff --git a/spec/defines/config/fragment_spec.rb b/spec/defines/config/fragment_spec.rb new file mode 100644 index 0000000..689083d --- /dev/null +++ b/spec/defines/config/fragment_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'pi::config::fragment' do + on_supported_os.each do |os, os_facts| + context "on #{os}" do + let(:facts) { os_facts } + let(:title) { 'foo' } + let(:params) { { 'content' => 'foo=bar' } } + + it { is_expected.to compile.with_all_deps } + + it do + is_expected.to contain_concat__fragment('foo').with( + target: '/boot/config.txt', + content: 'foo=bar', + order: 50 + ).that_notifies('Reboot[/boot/config.txt]') + end + + context 'with reboot => false' do + let(:pre_condition) do + <<-PP + class { 'pi::config': + reboot => false, + } + PP + end + + it do + is_expected.to contain_concat__fragment('foo').with( + target: '/boot/config.txt', + content: 'foo=bar', + order: 50 + ) + end + end + end + end +end diff --git a/spec/support/acceptance/config.rb b/spec/support/acceptance/config.rb new file mode 100644 index 0000000..cad4a55 --- /dev/null +++ b/spec/support/acceptance/config.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +shared_context 'config.txt test setup' do + before(:context) do + shell('mkdir -p /boot') + end + + after(:context) do + shell('rm -rf /boot/config.txt') + end +end