1

I'm trying to use ChefSpec to test an implementation of Chef and Hashicorp Vault

Recipe

chef_gem 'vault' do
  compile_time true if Chef::Resource::ChefGem.instance_methods(false).include?(:compile_time)
end

require 'vault'

Vault.address = 'https://address:8200'
Vault.token = citadel['foo/bar']
Vault.auth_token.renew_self

Test

require_relative '../spec_helper'

describe 'wrapper::default' do
  context 'role is foo' do
    let(:chef_run) do
      ChefSpec::SoloRunner.new(platform: 'ubuntu', version: '14.04') do |node|
        node.default['role'] = 'foo'
        const_set(:Vault, Module.new)
      end.converge(described_recipe)
    end

    before(:each) do
      allow_any_instance_of(Chef::Recipe).to receive(:require).and_call_original
      allow_any_instance_of(Chef::Recipe).to receive(:require).with('vault').and_return(true)
      allow_any_instance_of(::Vault).to receive(:address).and_call_original
      allow_any_instance_of(::Vault).to receive(:address).with('https://localhost:8200').and_return(true)
    end

    it 'install vault gem' do
      expect(chef_run).to install_chef_gem('vault')
    end
  end
end

Error

Failure/Error: expect(Chef::Recipe::Vault).to receive(:address).and_call_original

     NameError:
       uninitialized constant Vault

How do I stub the Vault variables? This is Hashicorp Vault, not chef-vault.

Daniel Mann
  • 57,011
  • 13
  • 100
  • 120
jsmickey
  • 718
  • 10
  • 20

2 Answers2

2

i got here and still couldnt figure it out so i ended up moving my vault code into my own lib which i include in the recipe so i couldnt mock it out during ChefSpec run.

so i have under my-cookbook/libraries/my_vault.rb with this code:

require 'vault'

module MyVault
  module Helpers
    def get_vault_secret(secret)
      Vault.configure do |config|
        config.address = "#{node[:vault_url]}"
        config.token = "#{node[:vault_token]}"
      end
      Vault.logical.read(secret)
    end
  end
end

and in my recipe:

Chef::Recipe.send(:include, MyVault::Helpers)

creds = get_vault_secret("secret/jmx")
user = creds.data[:user]
password = creds.data[:password]


template "/etc/app/jmx.password" do
  source "jmx.password.erb"
  mode 0600
  owner "dev"
  group "dev"
  variables({
                :user => user,
                :password => password
            })
end


and my spec test:

require 'chefspec'
require 'chefspec/berkshelf'

describe 'app::metrics' do
  platform 'ubuntu', '14.04'

  before do
    response = Object.new
    allow(response).to receive_messages(:data => {
        :user => "benzi",
        :password => "benzi"
    })
    allow_any_instance_of(Chef::Recipe).to receive(:get_vault_secret).and_return(response)
  end


  describe 'adds jmx.access to app folder' do
    it { is_expected.to create_template('/etc/app/jmx.access')
        .with(
            user: 'dev',
            group: 'dev',
            mode: 0644
        ) }
  end
danfromisrael
  • 2,982
  • 3
  • 30
  • 40
1

I responded to your email already, you want allow_any_instance_of(::Vault) and similar, and you may have to create the module (const_set(:Vault, Module.new)) if it doesn't exist already.

coderanger
  • 52,400
  • 4
  • 52
  • 75
  • Updated my question, I added a constant and took out the Chef::Recipe, still getting uninitialized constant – jsmickey Sep 13 '16 at 13:25
  • Please see the second bit from above where you need to create the module using either `const_set` or `stub_const` before you can stub things on it. – coderanger Sep 13 '16 at 18:37
  • could you share a full snippet? i'm facing a real challenge with making it work... – danfromisrael Jan 30 '20 at 07:17
  • ChefSpec has changed a lot since 2016 so the answer here is no longer relevant. I rewrote the loader subsystem about 2 years ago to make stubbing more natural. – coderanger Jan 30 '20 at 07:57
  • this is what i ended up doing: https://stackoverflow.com/a/59981857/303114 – danfromisrael Jan 30 '20 at 12:56