Skip to content

Commit b31d566

Browse files
authored
Merge pull request #62 from github/kpaulisse-catalog-diff-api
Add examples for catalog-diff API
2 parents 95bc03d + fcc39fc commit b31d566

5 files changed

Lines changed: 346 additions & 13 deletions

File tree

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env ruby
2+
3+
# ------------------------------------------------------------------------------------
4+
# This is a script that demonstrates the use of the OctocatalogDiff::API::V1.catalog_diff
5+
# method to compare two catalogs.
6+
#
7+
# Run this with:
8+
# bundle exec examples/api/v1/catalog-diff-local-files.rb
9+
#
10+
# In this example, we'll compare two catalogs, based on branches from a git repository.
11+
# ------------------------------------------------------------------------------------
12+
13+
# Once you have installed the gem, you want this:
14+
#
15+
# require 'octocatalog-diff'
16+
#
17+
# To make the script run correctly from within the `examples` directory, this locates
18+
# the `octocatalog-diff` gem directly from this checkout.
19+
require_relative '../../../lib/octocatalog-diff'
20+
21+
# Here are a few variables we'll use to compile this catalog. To ensure that this is a
22+
# working script, it will use resources from the test fixtures. You will need adjust these
23+
# to the actual values in your application.
24+
FACT_FILE = File.expand_path('../../../spec/octocatalog-diff/fixtures/facts/facts.yaml', File.dirname(__FILE__))
25+
NODE = 'rspec-node.github.net'.freeze
26+
GIT_TARBALL = File.expand_path('../../../spec/octocatalog-diff/fixtures/git-repos/simple-repo.tar', File.dirname(__FILE__))
27+
PUPPET_BINARY = File.expand_path('../../../script/puppet', File.dirname(__FILE__))
28+
29+
# Before we get started with the demo, I need to extract the tarball to create the git repo.
30+
# You won't be doing this in your own script, so as the great and powerful Oz says, "pay no
31+
# attention to the man behind the curtain."
32+
require 'fileutils'
33+
git_repo = Dir.mktmpdir
34+
at_exit { FileUtils.remove_entry_secure git_repo if File.directory?(git_repo) }
35+
system "tar -C '#{git_repo}' -xf '#{GIT_TARBALL}'"
36+
37+
# Just FYI, let's show you the checked out git repo.
38+
puts 'Here is the directory containing the git repository.'
39+
puts '$ ls -lR'
40+
system "cd '#{git_repo}/git-repo' && ls -lR"
41+
42+
puts ''
43+
puts '$ git branch'
44+
system "cd '#{git_repo}/git-repo' && git branch"
45+
46+
# Set up a logger
47+
strio = StringIO.new
48+
logger = Logger.new strio
49+
50+
# To get the catalog differences, call the octocatalog-diff API.
51+
puts ''
52+
puts 'Please wait a few seconds for the catalogs to be compiled...'
53+
result = OctocatalogDiff::API::V1.catalog_diff(
54+
basedir: File.join(git_repo, 'git-repo'),
55+
to_env: 'test-branch',
56+
from_env: 'master',
57+
fact_file: FACT_FILE,
58+
to_hiera_config: 'config/hiera.yaml', # Relative to the checkout, and only for the "to" catalog
59+
to_hiera_path: 'hieradata', # Relative to checkout, and only for the "to" catalog
60+
enc: 'config/enc.sh', # Relative to checkout
61+
bootstrap_script: 'script/bootstrap.sh', # Relative to checkout
62+
node: NODE,
63+
puppet_binary: PUPPET_BINARY,
64+
ignore: [
65+
{ type: Regexp.new('\AClass\z'), title: Regexp.new('.*') } # Ignore all type=Class resources
66+
],
67+
logger: logger
68+
)
69+
70+
# Print the log
71+
puts strio.string
72+
73+
# The `catalog-diff-local-files.rb` example explores the data structures of the
74+
# return values. Here, simply report on the results.
75+
76+
puts ''
77+
puts "The from-catalog has #{result.from.resources.size} resources"
78+
# In the catalog, each resource is a hash, and the keys are strings not symbols.
79+
result.from.resources.each do |resource|
80+
puts " #{resource['type']}[#{resource['title']}]"
81+
end
82+
83+
puts "The to-catalog has #{result.to.resources.size} resources"
84+
result.to.resources.each do |resource|
85+
puts " #{resource['type']}[#{resource['title']}]"
86+
end
87+
88+
puts "There are #{result.diffs.size} differences"
89+
90+
result.diffs.each do |diff|
91+
if diff.addition?
92+
puts "Added a #{diff.type} resource called #{diff.title}!"
93+
elsif diff.removal?
94+
puts "Removed a #{diff.type} resource called #{diff.title}!"
95+
elsif diff.change?
96+
puts "Changed the #{diff.type} resource #{diff.title} attribute #{diff.structure.join('::')}"
97+
puts " from #{diff.old_value.inspect} to #{diff.new_value.inspect}"
98+
else
99+
puts 'This is a bug - each entry is an addition, removal, or change.'
100+
end
101+
end
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/usr/bin/env ruby
2+
3+
# ------------------------------------------------------------------------------------
4+
# This is a script that demonstrates the use of the OctocatalogDiff::API::V1.catalog_diff
5+
# method to compare two catalogs.
6+
#
7+
# Run this with:
8+
# bundle exec examples/api/v1/catalog-diff-local-files.rb
9+
#
10+
# In this example, we'll compare two catalogs.
11+
# - The "from" catalog will be a catalog that has already been compiled, and exists as a JSON file.
12+
# We will use one of the JSON files from the spec fixtures.
13+
# - The "to" catalog will be compiled using Puppet from one of the spec fixtures.
14+
#
15+
# This example will NOT show integration with a git repository. The `catalog-diff-git-repo.rb` file in
16+
# this directory shows off those features.
17+
# ------------------------------------------------------------------------------------
18+
19+
# Once you have installed the gem, you want this:
20+
#
21+
# require 'octocatalog-diff'
22+
#
23+
# To make the script run correctly from within the `examples` directory, this locates
24+
# the `octocatalog-diff` gem directly from this checkout.
25+
require_relative '../../../lib/octocatalog-diff'
26+
27+
# Here are a few variables we'll use to compile this catalog. To ensure that this is a
28+
# working script, it will use resources from the test fixtures. You will need adjust these
29+
# to the actual values in your application.
30+
FACT_FILE = File.expand_path('../../../spec/octocatalog-diff/fixtures/facts/facts.yaml', File.dirname(__FILE__))
31+
HIERA_CONFIG = File.expand_path('../../../spec/octocatalog-diff/fixtures/repos/default/config/hiera.yaml', File.dirname(__FILE__))
32+
NODE = 'rspec-node.github.net'.freeze
33+
FROM_CATALOG = File.expand_path('../../../spec/octocatalog-diff/fixtures/catalogs/ignore-tags-new.json', File.dirname(__FILE__))
34+
PUPPET_REPO = File.expand_path('../../../spec/octocatalog-diff/fixtures/repos/ignore-tags-old', File.dirname(__FILE__))
35+
PUPPET_BINARY = File.expand_path('../../../script/puppet', File.dirname(__FILE__))
36+
37+
# To get the catalog differences, call the octocatalog-diff API.
38+
puts 'Please wait a few seconds for the catalogs to be compiled...'
39+
result = OctocatalogDiff::API::V1.catalog_diff(
40+
bootstrapped_to_dir: PUPPET_REPO,
41+
fact_file: FACT_FILE,
42+
from_catalog: FROM_CATALOG,
43+
hiera_config: HIERA_CONFIG,
44+
hiera_path: 'hieradata',
45+
node: NODE,
46+
puppet_binary: PUPPET_BINARY,
47+
ignore: { type: '*', title: '*', attr: 'tag' } # Ignore all attributes named 'tag'
48+
)
49+
50+
# We should get back a structure with 3 keys: `:diffs` will be an array of differences; `:from` will be the "from"
51+
# catalog (which in this case is taken directly from the JSON file), and `:to` will be the "to" catalog (which
52+
# in this case was compiled). We use 'Openstruct' so that you can treat it as a hash, or as an object with methods.
53+
puts "Object returned from OctocatalogDiff::API::V1.catalog_diff is: #{result.class}"
54+
55+
# Let's see what kind of objects we have. First, treating the result as a hash:
56+
puts "The keys are: #{result.to_h.keys.join(', ')}"
57+
[:diffs, :from, :to].each do |key|
58+
puts "result[:#{key}] is a(n) #{result[key].class}"
59+
end
60+
61+
# We can also use these as methods.
62+
[:diffs, :from, :to].each do |key|
63+
puts "result.#{key} is a(n) #{result.send(key).class}"
64+
end
65+
66+
# Let's inspect the :diffs array a bit.
67+
puts "The first element of result.diffs is a(n) #{result.send(:diffs).first.class}"
68+
puts "There are #{result[:diffs].size} diffs reported here"
69+
70+
# Let's print the first diff in JSON format
71+
d = result.diffs.first
72+
puts '--------------------------------------'
73+
puts 'The first diff is:'
74+
puts d.inspect
75+
76+
# Internally, the structure of the diff is an array
77+
puts '--------------------------------------'
78+
puts 'The raw object format of that diff is:'
79+
puts d.raw.inspect

lib/octocatalog-diff/catalog-diff/differ.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,11 @@ def ignore_match?(rule_in, diff_type, hsh, old_val, new_val)
330330
rule = rule_in.dup
331331

332332
# Type matches?
333-
return false unless rule[:type] == '*' || rule[:type].casecmp(hsh[:type]).zero?
333+
if rule[:type].is_a?(Regexp)
334+
return false unless hsh[:type].match(rule[:type])
335+
elsif rule[:type].is_a?(String)
336+
return false unless rule[:type] == '*' || rule[:type].casecmp(hsh[:type]).zero?
337+
end
334338

335339
# Title matches? (Support regexp and string)
336340
if rule[:title].is_a?(Regexp)

spec/octocatalog-diff/integration/examples_spec.rb

Lines changed: 84 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,32 +29,104 @@
2929
end
3030

3131
describe 'examples/api/v1/catalog-builder-local-files.rb' do
32-
let(:script) { File.expand_path('../../../examples/api/v1/catalog-builder-local-files.rb', File.dirname(__FILE__)) }
33-
let(:ls_l) { Open3.capture2e("ls -l '#{script}'").first }
32+
before(:all) do
33+
@script = File.expand_path('../../../examples/api/v1/catalog-builder-local-files.rb', File.dirname(__FILE__))
34+
end
3435

3536
it 'should exist' do
36-
expect(File.file?(script)).to eq(true), ls_l
37+
ls_l = Open3.capture2e("ls -l '#{@script}'").first
38+
expect(File.file?(@script)).to eq(true), ls_l
3739
end
3840

3941
context 'executing' do
40-
before(:each) do
41-
@stdout_obj = StringIO.new
42-
@old_stdout = $stdout
43-
$stdout = @stdout_obj
42+
before(:all) do
43+
@stdout, @stderr, @exitcode = Open3.capture3(@script)
4444
end
4545

46-
after(:each) do
47-
$stdout = @old_stdout
46+
it 'should run without error' do
47+
expect(@exitcode.exitstatus).to eq(0)
4848
end
4949

50-
it 'should compile and run' do
51-
load script
52-
output = @stdout_obj.string.split("\n")
50+
it 'should print the expected output' do
51+
output = @stdout.split("\n")
5352
expect(output).to include('Object returned from OctocatalogDiff::API::V1.catalog is: OctocatalogDiff::API::V1::Catalog')
5453
expect(output).to include('The catalog is valid.')
5554
expect(output).to include('The catalog was built using OctocatalogDiff::Catalog::Computed')
5655
expect(output).to include('- System::User - bob')
5756
expect(output).to include('The resources are equal!')
5857
end
58+
59+
it 'should not output to STDERR' do
60+
expect(@stderr).to eq('')
61+
end
62+
end
63+
end
64+
65+
describe 'examples/api/v1/catalog-diff-local-files.rb' do
66+
before(:all) do
67+
@script = File.expand_path('../../../examples/api/v1/catalog-diff-local-files.rb', File.dirname(__FILE__))
68+
end
69+
70+
it 'should exist' do
71+
ls_l = Open3.capture2e("ls -l '#{@script}'").first
72+
expect(File.file?(@script)).to eq(true), ls_l
73+
end
74+
75+
context 'executing' do
76+
before(:all) do
77+
@stdout, @stderr, @exitcode = Open3.capture3(@script)
78+
end
79+
80+
it 'should run without error' do
81+
expect(@exitcode.exitstatus).to eq(0)
82+
end
83+
84+
it 'should print the expected output' do
85+
output = @stdout.split("\n")
86+
expect(output).to include('Object returned from OctocatalogDiff::API::V1.catalog_diff is: OpenStruct')
87+
expect(output).to include('The keys are: diffs, from, to')
88+
expect(output).to include('There are 30 diffs reported here')
89+
end
90+
91+
it 'should not output to STDERR' do
92+
expect(@stderr).to eq('')
93+
end
94+
end
95+
end
96+
97+
describe 'examples/api/v1/catalog-diff-git-repo.rb' do
98+
before(:all) do
99+
@script = File.expand_path('../../../examples/api/v1/catalog-diff-git-repo.rb', File.dirname(__FILE__))
100+
end
101+
102+
it 'should exist' do
103+
ls_l = Open3.capture2e("ls -l '#{@script}'").first
104+
expect(File.file?(@script)).to eq(true), ls_l
105+
end
106+
107+
context 'executing' do
108+
before(:all) do
109+
@stdout, @stderr, @exitcode = Open3.capture3(@script)
110+
end
111+
112+
it 'should run without error' do
113+
expect(@exitcode.exitstatus).to eq(0)
114+
end
115+
116+
it 'should print the expected output' do
117+
output = @stdout.split("\n")
118+
expect(output).to include('Here is the directory containing the git repository.')
119+
expect(@stdout).to match(/DEBUG -- : Compiling catalogs for rspec-node.github.net/)
120+
expect(@stdout).to match(/Entering catdiff; catalog sizes: 6, 9/)
121+
expect(output).to include('The from-catalog has 6 resources')
122+
expect(output).to include('The to-catalog has 9 resources')
123+
expect(output).to include('There are 6 differences')
124+
expect(output).to include('Added a File resource called /tmp/bar!')
125+
expect(output).to include('Changed the File resource /tmp/foo attribute parameters::group')
126+
end
127+
128+
it 'should not output to STDERR' do
129+
expect(@stderr).to eq('')
130+
end
59131
end
60132
end

spec/octocatalog-diff/tests/catalog-diff/differ_spec.rb

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,83 @@
12101210
end
12111211
end
12121212

1213+
describe '#ignore_match?' do
1214+
let(:resource) { { type: 'Apple', title: 'delicious', attr: "parameters\fcolor" } }
1215+
let(:testobj) { described_class.allocate }
1216+
1217+
context 'type regex' do
1218+
it 'should filter matching resource' do
1219+
rule = { type: Regexp.new('A.+e\z'), title: '*', attr: '*' }
1220+
logger, logger_str = OctocatalogDiff::Spec.setup_logger
1221+
testobj.instance_variable_set('@logger', logger)
1222+
expect(testobj.send(:"ignore_match?", rule, '+', resource, 'old_value', 'new_value')).to eq(true)
1223+
expect(logger_str.string).to match(%r[Ignoring .+ matches {:type=>/A.+e\\z/, :title=>"\*", :attr=>"\*"}])
1224+
end
1225+
1226+
it 'should not filter non-matching resource' do
1227+
rule = { type: Regexp.new('A.+b\z'), title: '*', attr: '*' }
1228+
logger, logger_str = OctocatalogDiff::Spec.setup_logger
1229+
testobj.instance_variable_set('@logger', logger)
1230+
expect(testobj.send(:"ignore_match?", rule, '+', resource, 'old_value', 'new_value')).to eq(false)
1231+
expect(logger_str.string).not_to match(%r[Ignoring .+ matches {:type=>/A.+b\\z/, :title=>"\*", :attr=>"\*"}])
1232+
end
1233+
end
1234+
1235+
context 'type string' do
1236+
it 'should filter matching resource' do
1237+
rule = { type: 'Apple', title: '*', attr: '*' }
1238+
logger, logger_str = OctocatalogDiff::Spec.setup_logger
1239+
testobj.instance_variable_set('@logger', logger)
1240+
expect(testobj.send(:"ignore_match?", rule, '+', resource, 'old_value', 'new_value')).to eq(true)
1241+
expect(logger_str.string).to match(/Ignoring .+ matches {:type=>"Apple", :title=>"\*", :attr=>"\*"}/)
1242+
end
1243+
1244+
it 'should not filter non-matching resource' do
1245+
rule = { type: 'Banana', title: '*', attr: '*' }
1246+
logger, logger_str = OctocatalogDiff::Spec.setup_logger
1247+
testobj.instance_variable_set('@logger', logger)
1248+
expect(testobj.send(:"ignore_match?", rule, '+', resource, 'old_value', 'new_value')).to eq(false)
1249+
expect(logger_str.string).not_to match(/Ignoring .+ matches {:type=>"Banana", :title=>"\*", :attr=>"\*"}/)
1250+
end
1251+
end
1252+
1253+
context 'title regex' do
1254+
it 'should filter matching resource' do
1255+
rule = { type: '*', title: Regexp.new('del.+ous'), attr: '*' }
1256+
logger, logger_str = OctocatalogDiff::Spec.setup_logger
1257+
testobj.instance_variable_set('@logger', logger)
1258+
expect(testobj.send(:"ignore_match?", rule, '+', resource, 'old_value', 'new_value')).to eq(true)
1259+
expect(logger_str.string).to match(%r[Ignoring .+ matches {:type=>"\*", :title=>/del\.\+ous/, :attr=>"\*"}])
1260+
end
1261+
1262+
it 'should not filter non-matching resource' do
1263+
rule = { type: '*', title: Regexp.new('dell.+ous'), attr: '*' }
1264+
logger, logger_str = OctocatalogDiff::Spec.setup_logger
1265+
testobj.instance_variable_set('@logger', logger)
1266+
expect(testobj.send(:"ignore_match?", rule, '+', resource, 'old_value', 'new_value')).to eq(false)
1267+
expect(logger_str.string).not_to match(%r[Ignoring .+ matches {:type=>"\*", :title=>/dell\.\+ous/, :attr=>"\*"}])
1268+
end
1269+
end
1270+
1271+
context 'title string' do
1272+
it 'should filter matching resource' do
1273+
rule = { type: '*', title: 'delicious', attr: '*' }
1274+
logger, logger_str = OctocatalogDiff::Spec.setup_logger
1275+
testobj.instance_variable_set('@logger', logger)
1276+
expect(testobj.send(:"ignore_match?", rule, '+', resource, 'old_value', 'new_value')).to eq(true)
1277+
expect(logger_str.string).to match(/Ignoring .+ matches {:type=>"\*", :title=>"delicious", :attr=>"\*"}/)
1278+
end
1279+
1280+
it 'should not filter non-matching resource' do
1281+
rule = { type: '*', title: 'dell', attr: '*' }
1282+
logger, logger_str = OctocatalogDiff::Spec.setup_logger
1283+
testobj.instance_variable_set('@logger', logger)
1284+
expect(testobj.send(:"ignore_match?", rule, '+', resource, 'old_value', 'new_value')).to eq(false)
1285+
expect(logger_str.string).not_to match(/Ignoring .+ matches {:type=>"\*", :title=>"dell", :attr=>"\*"}/)
1286+
end
1287+
end
1288+
end
1289+
12131290
describe '#hashdiff_nested_changes' do
12141291
it 'should return array with proper results' do
12151292
hashdiff_add_remove = [

0 commit comments

Comments
 (0)