Context
We have added an extra unit test job to our CI to test our puppet 6 code on puppet 8 to get a first estimate of how much code refactoring would be involved.
Testing our code on puppet 8 with the following configuration (actual scenario detailed in the use case):
classA.pp
class moduleA::classA {
moduleB::defineB { 'some_resource': }
}
defineB.pp
define moduleB::defineB (
paramB = moduleB::params::default_paramB
) {}
now produces the following error:
Error: Unknown variable: 'moduleB::params::default_paramB'.
Questions
Use case
Calling filebeat
(version 4.14.0
) from moduleA :
class moduleA::classA {
filebeat::input { 'resource': }
}
filebeat input.pp
manifest:
define filebeat::input (
...
String $input_type = $filebeat::params::default_input_type,
...
)
$default_input_type
parameter declaration in filebeat params.pp
:
...
if versioncmp($facts['filebeat_version'], '7.10') > 0 {
$default_input_type = 'filestream'
} else {
$default_input_type = 'log'
}
} else {
$default_input_type = 'filestream'
}
unit test
The following unit test works with puppet 6 / 7
require 'spec_helper'
describe 'moduleA::classA' do
on_supported_os.each do |os, facts|
context "On #{os} with default values for all parameters" do
it { is_expected.to compile }
it { is_expected.to contain_class('moduleA::classA') }
end
end
end
test execution from within moduleA directory:
bundle exec rake spec SPEC=spec/classes/classA_spec.rb
Error
Error: Unknown variable: 'filebeat::params::default_input_type'.
Discussion
I already tried a few ways of working around this but my main question is whether rspec is able to pick up default values (and if not how should we handle them) ?
For instance including filebeat's params.pp class as a pre-condition:
require 'spec_helper'
describe 'moduleA::classA' do
on_supported_os.each do |os, facts|
context "On #{os} with default values for all parameters" do
let(:pre_condition) { 'include filebeat::params' }
it { is_expected.to compile }
it { is_expected.to contain_class('moduleA::classA') }
end
end
end
results in another error as it now seems that facts are not properly resolved in the context of filebeat's params.pp
:
Operator '[]' is not applicable to an Undef Value.
which refers to the following line:
$osfamily_fail_message = "${facts['os']['family']} is not supported by filebeat."
Going through the use case error by error :
puppet code
classA references filebeat::input
:
class moduleA::classA {
filebeat::input { 'resource': }
}
filebeat::input
and declared in filebeat::params
:Error: Unknown variable: 'filebeat::params::default_input_type'.
solution
Add filebeat::params
as a pre-condition:
let(:pre_condition) do
<<-EOF
include filebeat::params
EOF
end
filebeat::input
and declared in filebeat class
:Unknown variable: 'filebeat::fields_under_root'.
this variable is declared in filebeat's init.pp class (which is the manifest that declares filebeat
class):
Boolean $fields_under_root = $filebeat::fields_under_root,
classA
references filebeat::input
which in turn needs the filebeat
class to be already declared.
solution
Include the filebeat
class in the :pre-condition
block as well:
let(:pre_condition) do
<<-EOF
include filebeat::params
include filebeat
EOF
end
filebeat::params
:Operator '[]' is not applicable to an Undef Value.
which refers to the following line in filebeat::params
:
$osfamily_fail_message = "${facts['os']['family']} is not supported by filebeat."
solution
As facts are already available via the rspec-puppet-facts
framework I only needed make use of the :facts
block properly:
on_supported_os.each do |os, facts|
let(:facts) do
facts.merge(
filebeat_version: '7',
)
end
I already tried a few ways of working around this but my main question is whether rspec is able to pick up default values (and if not how should we handle them) ?
RSpec itself knows nothing about Puppet at all.
You appear to be using rspec-puppet to support RSpec testing of your Puppet classes, and this is where the Puppet knowledge comes in. Nevertheless, rspec-puppet does not know specifically about default values, and it doesn't need to do. It works by using Puppet itself to compile your manifests into a catalog, and by providing RSpec matchers for evaluating assertions about the resulting catalog's contents. Puppet certainly understands default parameter values.
My best guess would be that this ...
Error: Unknown variable: 'filebeat::params::default_input_type'.
... indicates that class filebeat::params
has not (yet) been declared into the catalog at the point where the error is triggered. It is plausible that this was not an issue for your tests before Puppet 8 because starting in Puppet 8, strict mode is enabled by default. In earlier versions, the variable reference would, I think, have simply expanded to undef
. Depending on how the type under test is intended to be used, this possibly highlights a weakness in the module.
That ...
including filebeat's params.pp class as a pre-condition [...] results in another error
... supports this hypothesis. It's unlikely that that is a new issue. You just didn't run into it before in your test suite because the filebeat::params
class was not being declared at all.
In particular ...
facts are not properly resolved in the context of filebeat's params.pp
... seems unlikely. I would be inclined to think that your test suite simply does not provide the os
fact. rspec-puppet has a mechanism for you to define default facts for your test cases, and one for you to define per-test facts, but it does not provide any facts by itself. For this purpose, you might be interested in the separate rspec-puppet-facts project.