Skip to content

Instantly share code, notes, and snippets.

@jzohrab
Last active October 2, 2017 15:07
Show Gist options
  • Save jzohrab/69b823eb5faa7256ec8e to your computer and use it in GitHub Desktop.
Save jzohrab/69b823eb5faa7256ec8e to your computer and use it in GitHub Desktop.
A quick-and-dirty Chef attribute provenance reporter using ChefSpec
# Attribute Provenance reporter
# -----------------------------
#
# Runs as a ChefSpec test and dumps two sections of data:
# 1. all attributes (data from `node.debug_value()`)
# 2. where each attribute is set. You can specify the attributes
# you want included with an ATTRS environment variable.
#
# Sample run:
#
# `ATTRS=text,mail chef exec rspec spec/unit/recipes/attribute_provenance_spec.rb`
#
# outputs
#
# =======================
# All attribute settings:
# [["set_unless_enabled?", false],
# ["default",
# {"apt"=>
# {"cacher-client"=>{"restrict_environment"=>false},
# ...
# =======================
# Cookbook attributes:
# Attribute: mail
# nil (cookbooks/apt/attributes/default.rb:42)
# Attribute: text
# "hello there" (cookbooks/ABC/attributes/default.rb:2)
#
#
# NOTES:
# * this is not a general solution. Specifically, the code has
# `runner.converge('ABC::default')`, so change that for your recipe.
# You may also need to add other things, eg attributes, stub methods, etc.
# * For node.debug_value() info, see
# https://www.chef.io/blog/2013/02/05/chef-11-in-depth-attributes-changes/
require 'spec_helper'
require 'pp'
#########################
# Track attributes.
# Attributes set during run.
$attributes = Hash.new { |h, k| h[k] = [] }
# Re-open VividMash to track attribute changes.
class Chef
class Node
class VividMash
alias_method :old_insert, :[]=
def []=(key, value)
c = caller.select { |c| c =~ /cookbook/i && c !~ /ruby\/gems/ && c !~ /#{__FILE__}/ }
$attributes[key] << [ value, c ] if c.size > 0
old_insert(key, value)
end
end
end
end
#########################
# Reporting
def clean_callstack(callstack)
callstack.map do |c|
c.gsub(/^.*?cookbooks/, 'cookbooks').gsub(/.in .from_file./, '')
end
end
def report_attribute(attr, vals_and_callstacks)
puts "Attribute: #{attr}"
vals_and_callstacks.each do |value, callstack|
puts " #{value.inspect} (#{clean_callstack(callstack).join('\n')})"
end
end
def report_attributes(attrs, only_include = [])
attrs.keys.each do |k|
report_attribute(k, attrs[k]) if (only_include.size == 0 || only_include.include?(k))
end
end
def get_targets(env_attrs)
return [] if env_attrs.nil?
return env_attrs.split(',')
end
############
# Report on used attributes.
describe 'recipe' do
context 'checking attributes' do
it 'dumps attributes' do
runner = ChefSpec::ServerRunner.new
runner.converge('ABC::default')
puts "======================="
puts "All attribute settings:"
pp runner.node.debug_value()
puts "======================="
puts "Cookbook attributes:"
t = get_targets(ENV['ATTRS'])
report_attributes($attributes, t)
puts "======================="
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment