InSpec conditional testing based on node attributes

Hi chefs, I have a question regarding using Inspec to test my cookbooks.

I have a cookbook that installs numerous Windows features. Some of which aren’t always necessary (such as management tools). So, I wanted to use an attribute so that those certain install components are easily disabled.

To verify that my recipe is working as expected I need a test for when the attribute is true and one when it is false; however, I am not able to access the node attributes from the test. I did try the JSON trick here but Inspec doesn’t let me use the conditional inside the describe block and if I define that method outside of the blocks it runs in the context of my dev workstation.

if node['clustering']['install_ps_tools']
  describe windows_feature('rsat-clustering-powershell') do
    it { should be_installed }
  end
end

Of course, if this is bad design I’d take that criticism as well.

You can access the node from inside the describe and I would use skip which is the idiomatic rspec way for doing what you are trying to achieve. So assuming your node has a foo attribute. and you want to skip the test(s) if its value is bar, you could do this:

describe file(File.join("/tmp", "export-node", "node.json")) do
  let(:parent) { File.join("/tmp", "kitchen") }
  let(:node) { json(File.join(parent, "chef_node.json")).params }

  before do
    skip if node["normal"]["foo"] == "bar"
  end

  it { should be_file }  
  its(:content) {
    should match /^mac: #{node["automatic"]["macaddress"]}$/
  }
end

which yields:

  ○  2 skipped
     skip:
     skip:

Summary: 0 successful, 0 failures, 2 skipped

You could move the skip to individual it blocks if you just want to skip a single example.

1 Like

Perfect, thanks a bunch!

I tried to follow this example, but it resulted in a Ruby error:

Failures:

  1) File /u01/app/oracle/product/11.2.0.4/dbhome_1 
     Failure/Error: skip unless node['oracle']['db_version'] == "11.2.0.4"
     
     NoMethodError:
       undefined method `[]' for nil:NilClass
     # ./test/integration/default/inspec/db_install_spec.rb:10:in `block (2 levels) in load'
     # /Users/skohrs/.chefdk/gem/ruby/2.1.0/gems/inspec-0.27.1/lib/inspec/runner_rspec.rb:78:in `run'
     # /Users/skohrs/.chefdk/gem/ruby/2.1.0/gems/kitchen-inspec-0.15.0/lib/kitchen/verifier/inspec.rb:75:in `call'

Here is the InSpec test:

describe file('/u01/app/oracle/product/11.2.0.4/dbhome_1') do
  let(:node) { json('/tmp/kitchen/chef_node.json').params }

  before do
    skip unless node['oracle']['db_version'] == "11.2.0.4"
  end

  it { should be_directory }
  its('owner') { should eq 'oracle' }
  its('group') { should eq 'oinstall' }
end

Any suggestions?

Try node['automatic']['oracle']['db_version'] keeping in mind that the json includes a level for the attribute type that the node object would conveniently hide in a recipe.

Thank you, Matt!

In my case, node['default']['oracle']['db_version'] held the attribute I was testing against.

Is there a way to use an attribute such as in the following example?

describe file (node[‘normal’][‘path’]) do

Hi,

i need this too. Like this

describe service(node[servicename]) do

end

Unless there’s been a big change to InSpec in the past couple of months, I don’t see how this ever worked.

   [1, 10] in test/recipes/default/default_test.rb
1: require 'byebug'
2: describe package 'nagios-plugins-minion' do
3:   before do
4:     byebug
=>  5:     skip unless ... (redacted)
6:   end
7:
8:   it { should be_installed }
9: end
10:
(byebug) node
*** NameError Exception: undefined local variable or method `node' for #<RSpec::ExampleGroups::SystemPackageNagiosPluginsMinion:0x007fbbc3076440>

The node object is not accessible via the describe block.

Further, is this really how it should work? Wouldn’t it be a lot better if there was a facility to say “in this circumstance, test this” and so on?

Edit: using Test Kitchen. From .kitchen.yml:

verifier:
  name: inspec

I had a look into this, but was unable to reproduce the issue. Here are my files:

$ cat /tmp/attrs.json
{
  "chef_client": {
    "interval": "3600",
    "owner": "root"
  }
}

$ cat /tmp/node-test.rb
describe file('/etc/hosts') do
  let(:node) { json('/tmp/attrs.json').params }
  before do
    skip unless node['chef_client']['interval'] == "3600"
  end
  its('owner') { should eq(node['chef_client']['owner']) }
end

Using InSpec directly with the implicit localhost target:

$ ls -l /tmp/attrs.json
-rw-r--r--  1 root  staff  71 Oct  4 15:48 /tmp/attrs.json

$ inspec version
1.0.0

$ inspec exec /tmp/node-test.rb

Target:  local://

  File /etc/hosts
     ✔  owner should eq "root"

Test Summary: 1 successful, 0 failures, 0 skipped

Using InSpec via kitchen-inspec targetting the converged VM:

$ rm /tmp/attrs.json
$ kitchen verify
-----> Starting Kitchen (v1.13.0)
-----> Verifying <mysuite-centos6>...
       Use `/Users/apop/git/myapache-cookbook/test/integration/mysuite` for testing

Target:  ssh://root@127.0.0.1:2222


  File /etc/hosts
     ✔  owner should eq "root"

Summary: 1 successful, 0 failures, 0 skipped

Behind the scenes InSpec is invoked like this:

inspec exec test/integration/mysuite/ -t ssh://root@127.0.0.1:2222 --password vagrant

This means that when using resources like json(’/tmp/attrs.json’) or command(‘ls /tmp’) in your tests, they will use the target node/container to read ‘/tmp/attrs.json’ or to execute the command on.

Hi
how we can skip any condition based on role attached to that node

describe service(‘xyz’) do
before do
skip if node[“roles”] == "abc"
end
it { should be_installed }
it { should be_enabled }
it { should be_running }
end

this one is giving me error that undefined function node.

describe service(‘xyz’) do
  let(:node) { json('/tmp/kitchen/nodes/default-rhel-73.json').params }
before do
  skip if node['roles'] == "abc"
end
 it { should be_installed }
 it { should be_enabled }
 it { should be_running }
end

/tmp/kitchen/nodes/default-rhel-73.json change it accordingly.!!