Zac B
Zac B

Reputation: 4232

How can I mock facts to test a Puppet template that does decimal multiplication on a fact?

Context:

I have a puppet template that addresses the count sub-component of the processors fact when it is rendered. That fact exists on all of my clients.

The use case for the fact is in a template line that performs decimal math on it, e.g.: MyConfigVar=<%= 0.9 * @processors['count'] %> in some .erb file.

I want to:

  1. Get my templated code deployed to production hosts.
  2. Write robust unit tests for my templating so I can be sure it will render properly, given various reasonable values for the fact.

What I've Tried:

First, I tried with sigils: <%= 0.9 * @processors[:count] %>. If I mocked, with rspec-puppet, something like facts = { :processors => { :count => 10 } }, my tests all passed. Manifest application didn't work; it had a "can't multiply nil" error. Sigils apparently are out.

Then, I tried with stringy keys: <%= 0.9 * @processors['count'] %>. My tests with the sigil (facts = { :processors => { :count => 10 } }) weren't picked up, but my value was properly found and multiplied with facts = { :processors => { 'count' => 10 } }. All tests then passed. However, manifest application failed with a Can't coerce String into Int failure.

Then, I tried with stringy values. The template still read <%= 0.9 * @processors['count'].to_i %>, and I tested both string and integer values, e.g.

let(:facts) { :processors => { 'count' => '10' } }
# tests with string value
let(:facts) { :processors => { 'count' => 10 } }
# tests with integer value

The tests all passed, but manifest application rendered 0.0 for the value of the fact.

Questions:

Two main questions:

  1. How can I get inline decimal math working with this (or any) fact, reliably?
  2. How can I reliably unit-test, using rspec-puppet or similar, mocking values in facter with production-representative types?

Upvotes: 3

Views: 1393

Answers (2)

Peter Souter
Peter Souter

Reputation: 5190

There's a few things at play here.

As Josh Souza says, older versions of Facter did not support hashes or arrays. So depending on the version of Puppet and Facter your using, I would check what the value of stringify_facts config.

If this is set to true (which it will be on older Puppet) then facts will not come out as hashes, they get cast to a string in your manifest, leading to a mashed up string with no separation (eg. modelsIntel(R) Core(TM) i7-2760QM CPU @ 2.40GHzcount1physicalcount1). Note the count1, that would have been :count => 1 in the original hash.

You can test this with the stringify_facts setting in your spec_helper in rspec-puppet >= 2.2.

So there a number of legacy facts that aren't hashes that you can see if you run facter -p:

$ facter -p | grep processor
processorcount => 8
processors => {"count"=>8, "speed"=>"2.5 GHz"}
sp_current_processor_speed => 2.5 GHz
sp_number_processors => 4

So I'd recommend using the $::processorcount fact, which should be an integer because that's the value returned, but you could use to_i to be sure in your .erb template. I'm pretty sure it should always return an integer.

If you're feeling really brave, you can create a function to cast the value to a numeric and use that in your manifest (For example https://github.com/kwilczynski/puppet-functions/blob/master/lib/puppet/parser/functions/str2num.rb)

So you'd have code that looks like:

if (is_integer($::processorcount)) {
  $processor_as_int = $::processorcount
} else {
  $processor_as_int = str2num($::processorcount)
}

All in all, it's a bit messy, you can see why in Puppet 4 we have a much tighter typing system!

Upvotes: 2

Josh Souza
Josh Souza

Reputation: 406

For what it's worth, I did some investigation into your issue, and in my test bed the 'processors' fact, while a hash when running facter processors, is actually a string once it makes its way into Puppet/templates and it lacks any separator (I.E. "modelsIntel(R) Core(TM) i7-2760QM CPU @ 2.40GHzcount1physicalcount1" is the raw value). I verified this by using '@processors.class' in my ruby template, as well as a few other tests to be sure. My testbed is running Puppet 3.8.3 and Facter 2.4.6, in case that affected things.

So the solutions I can propose are:

  1. Use the @processorcount fact, which will always be an integer and should work just fine with sigils in your unit tests.
  2. Use Facter directly within your template (Which is very non-puppety, but it works)

    • <%= Facter.value(:processors)['count'] %>
  3. Use regex string parsing (I don't recommend this, as it's fragile and hacky, but it DOES do the trick)

It's quite possible that in different versions of Puppet/Facter this isn't a problem, but hopefully this is enough to get you rolling in the right direction.

Upvotes: 2

Related Questions