puppet-catalog-test 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Rene Lengwinat
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,176 @@
1
+ # Test all your puppet catalogs for compiler errors
2
+
3
+ ## Installation
4
+
5
+ gem install puppet-catalog-tests
6
+
7
+ ## Usage
8
+ ```bash
9
+ $ puppet-catalog-test -h
10
+ USAGE: puppet-catalog-test [options]
11
+ -m, --module-paths PATHS Location of puppet modules, separated by collon
12
+ -M, --manifest-path PATH Location of the puppet manifests (site.pp)
13
+ -f, --filter PATTERN Filter test cases by pattern
14
+ -s, --scenario FILE Scenario definition to use
15
+ -h, --help Show this message
16
+ ```
17
+ ## Examples
18
+
19
+ ### CLI - successfull compile run
20
+ ```bash
21
+ $ puppet-catalog-test -m test/cases/working/modules -M test/cases/working/site.pp
22
+ [INFO] Using puppet 3.0.2
23
+ [PASSED] foo (compile time: 0.168182 seconds)
24
+ [PASSED] default (compile time: 0.003451 seconds)
25
+
26
+ ----------------------------------------
27
+ Compiled 2 catalogs in 0.1717 seconds (avg: 0.0858 seconds)
28
+ ```
29
+
30
+ ### CLI - failed compile run
31
+ ```bash
32
+ $ puppet-catalog-test -m test/cases/failing/modules -M test/cases/failing/site.pp
33
+ [INFO] Using puppet 3.0.2
34
+ [FAILED] foo (compile time: 0.17113 seconds)
35
+ [FAILED] default (compile time: 0.002951 seconds)
36
+
37
+ ----------------------------------------
38
+ Compiled 2 catalogs in 0.1741 seconds (avg: 0.0871 seconds)
39
+ 2 test cases failed.
40
+
41
+ [F] foo:
42
+ Duplicate declaration: Package[myapp-pkg] is already declared in file /Users/rlengwin/devel/github/puppet-catalog-test/test/cases/failing/modules/myapp/manifests/init.pp at line 4; cannot redeclare on node foo
43
+
44
+ [F] default:
45
+ Duplicate declaration: Package[myapp-pkg] is already declared in file /Users/rlengwin/devel/github/puppet-catalog-test/test/cases/failing/modules/myapp/manifests/init.pp at line 4; cannot redeclare on node default
46
+
47
+ 2 / 2 FAILED
48
+ ```
49
+
50
+ ## Rake integration
51
+
52
+ ### Testing all catalogs with default facts
53
+
54
+ When not setting any filters or scenarios puppet-catalog-test will test all nodes defined in site.pp.
55
+
56
+ ```ruby
57
+ require 'puppet-catalog-test'
58
+
59
+ namespace :catalog do
60
+ PuppetCatalogTest::RakeTask.new(:all) do |t|
61
+ t.module_paths = ["modules"]
62
+ t.manifest_path = File.join("scripts", "site.pp")
63
+
64
+ t.filter = ENV["filter"] if ENV["filter"]
65
+ end
66
+ end
67
+ ```
68
+
69
+ In the case above no facts weren't defined so puppet-catalog-test falls back to some basic facts to satisfy the most basic requirements of puppet. Currently these facts are:
70
+
71
+ ```ruby
72
+ {
73
+ 'architecture' => 'x86_64',
74
+ 'ipaddress' => '127.0.0.1',
75
+ 'local_run' => 'true',
76
+ 'disable_asserts' => 'true',
77
+ 'interfaces' => 'eth0'
78
+ }
79
+ ```
80
+
81
+ ### Testing all catalogs with custom facts
82
+
83
+ It is also possible to define a custom set of facts. In this case the fallback facts listed in previous example won't be used.
84
+
85
+ *NOTE:* When using custom facts the fact 'fqdn' always has to be set!
86
+
87
+ ```ruby
88
+ require 'puppet-catalog-test'
89
+
90
+ namespace :catalog do
91
+ PuppetCatalogTest::RakeTask.new(:all) do |t|
92
+ t.module_paths = ["modules"]
93
+ t.manifest_path = File.join("scripts", "site.pp")
94
+ t.facts = {
95
+ "fqdn" => "foo.local",
96
+ "operatingsystem" => "RedHat"
97
+ }
98
+
99
+ t.filter = ENV["filter"] if ENV["filter"]
100
+ end
101
+ end
102
+ ```
103
+
104
+ ## Scenario testing
105
+
106
+ Scenarios allow testing of more complex catalogs, e.g. having conditional branches depending on custom facts.
107
+
108
+ ```ruby
109
+ require 'puppet-catalog-test'
110
+
111
+ namespace :catalog do
112
+ PuppetCatalogTest::RakeTask.new(:scenarios) do |t|
113
+ t.module_paths = [File.join("modules")]
114
+ t.manifest_path = File.join("scripts", "site.pp")
115
+
116
+ t.scenario_yaml = "scenarios.yml"
117
+
118
+ t.filter = ENV["filter"] if ENV["filter"]
119
+ end
120
+ end
121
+ ```
122
+
123
+ ```yaml
124
+ __default_facts: &default_facts
125
+ architecture: x86_64
126
+ ipaddress: 127.0.0.1
127
+ operatingsystem: SLES
128
+ operatingsystemrelease: 11
129
+ local_run: true
130
+ disable_asserts: true
131
+ interfaces: eth0
132
+
133
+ SLES_tomcat:
134
+ <<: *default_facts
135
+ fqdn: tomcat-a001.foo.local
136
+
137
+ REDHAT_tomcat:
138
+ <<: *default_facts
139
+ fqdn: tomcat-a001.foo.local
140
+ operatingsystem: RedHat
141
+
142
+ SLES_db-dev:
143
+ <<: *default_facts
144
+ fqdn: db-a001.foo.local
145
+
146
+ REDHAT_db-dev:
147
+ <<: *default_facts
148
+ fqdn: db-a001.foo.local
149
+ operatingsystem: RedHat
150
+ ```
151
+
152
+ *NOTE:* Scenarios starting with two underscores (like __default_facts) will be ignored.
153
+
154
+ ## Reporters (output plugins)
155
+
156
+ Per default puppet-catalog-test will use the StdoutReporter which will represent the result like in the examples above. Besides this you can use in your own Reporter.
157
+
158
+ Puppet-Catalog-Test also ships a JunitXmlReporter which creates a junit compatible xml report.
159
+
160
+ ```ruby
161
+ require 'puppet-catalog-test'
162
+ require 'puppet-catalog-test/junit_xml_reporter'
163
+
164
+ namespace :catalog do
165
+ PuppetCatalogTest::RakeTask.new(:all) do |t|
166
+ t.module_paths = [File.join("modules")]
167
+ t.manifest_path = File.join("scripts", "site.pp")
168
+
169
+ t.reporter = PuppetCatalogTest::JunitXmlReporter.new("puppet-vagrant-playground", "reports/puppet_catalog.xml")
170
+ end
171
+ end
172
+ ```
173
+
174
+ # Credits
175
+
176
+ Code is based upon the previous work done on https://github.com/oldNoakes/puppetTesting
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "rake/testtask"
2
+ require "bundler/gem_tasks"
3
+
4
+ desc "Clean workspace"
5
+ task :clean do
6
+ sh "rm -vrf *.gem pkg/"
7
+ end
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs << "test"
11
+ t.test_files = FileList['test/*_test.rb']
12
+ t.verbose = true
13
+ end
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "puppet-catalog-test"
4
+ require "optparse"
5
+
6
+ options = {
7
+ :filter => PuppetCatalogTest::DEFAULT_FILTER
8
+ }
9
+
10
+ opt_parser = OptionParser.new do |opts|
11
+ opts.banner = "USAGE: #{File.basename($0)} [options]"
12
+
13
+ opts.on("-m", "--module-paths PATHS", "Location of puppet modules, separated by collon") do |arg|
14
+ options[:module_paths] = arg.split(":")
15
+ end
16
+
17
+ opts.on("-M", "--manifest-path PATH", "Location of the puppet manifests (site.pp)") do |arg|
18
+ options[:manifest_path] = arg
19
+ end
20
+
21
+ opts.on("-f", "--filter PATTERN", "Filter test cases by pattern") do |arg|
22
+ options[:filter] = arg
23
+ end
24
+
25
+ opts.on("-s", "--scenario FILE", "Scenario definition to use") do |arg|
26
+ options[:scenario] = arg
27
+ end
28
+
29
+ opts.on_tail("-h", "--help", "Show this message") do
30
+ puts opts
31
+ exit
32
+ end
33
+ end
34
+
35
+ opt_parser.parse!
36
+
37
+ if options.empty?
38
+ puts opt_parser.help
39
+ exit
40
+ end
41
+
42
+ pct = PuppetCatalogTest::TestRunner.new(options[:manifest_path], options[:module_paths])
43
+
44
+ if options[:scenario]
45
+ pct.load_scenario_yaml(options[:scenario], options[:filter])
46
+ else
47
+ pct.load_all(options[:filter])
48
+ end
49
+
50
+ pct.run_tests!
@@ -0,0 +1,8 @@
1
+ module PuppetCatalogTest
2
+ VERSION = "0.1.0"
3
+
4
+ DEFAULT_FILTER = /.*/
5
+
6
+ require "puppet-catalog-test/test_runner"
7
+ require "puppet-catalog-test/rake_task"
8
+ end
@@ -0,0 +1,35 @@
1
+ require "builder"
2
+
3
+ module PuppetCatalogTest
4
+ class JunitXmlReporter < PuppetCatalogTest::StdoutReporter
5
+ def initialize(project_name, report_file)
6
+ @project_name = project_name
7
+ @report_file = report_file
8
+
9
+ target_dir = File.dirname(report_file)
10
+
11
+ FileUtils.mkdir_p(target_dir)
12
+
13
+ @out = $stdout
14
+ end
15
+
16
+ def summarize(tr)
17
+ failed_nodes = tr.test_cases.select { |tc| tc.passed == false }
18
+ builder = Builder::XmlMarkup.new
19
+
20
+ xml = builder.testsuite(:failures => failed_nodes.size, :tests => tr.test_cases.size) do |ts|
21
+ tr.test_cases.each do |tc|
22
+ ts.testcase(:classname => @project_name, :name => tc.name, :time => tc.duration) do |tc_node|
23
+ if tc.error
24
+ tc_node.failure tc.error
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ File.open(@report_file, "w") do |fp|
31
+ fp.puts xml
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,62 @@
1
+ require 'rake'
2
+ require 'rake/tasklib'
3
+
4
+ module PuppetCatalogTest
5
+ class RakeTask < ::Rake::TaskLib
6
+ include ::Rake::DSL if defined?(::Rake::DSL)
7
+
8
+ attr_accessor :module_paths
9
+ attr_accessor :manifest_path
10
+ attr_accessor :scenario_yaml
11
+ attr_accessor :filter
12
+ attr_accessor :facts
13
+ attr_accessor :reporter
14
+
15
+ def initialize(name, &task_block)
16
+ desc "Compile all puppet catalogs" unless ::Rake.application.last_comment
17
+
18
+ @filter = PuppetCatalogTest::DEFAULT_FILTER
19
+
20
+ task name do
21
+ task_block.call(self) if task_block
22
+ setup
23
+ end
24
+ end
25
+
26
+ def setup
27
+ pct = TestRunner.new(@manifest_path, @module_paths)
28
+
29
+ if @scenario_yaml
30
+ pct.load_scenario_yaml(@scenario_yaml, @filter)
31
+ else
32
+ nodes = pct.collect_puppet_nodes(@filter)
33
+ test_facts = @facts || fallback_facts
34
+
35
+ nodes.each do |nodename|
36
+ facts = test_facts.merge({
37
+ 'hostname' => nodename,
38
+ 'fqdn' => "#{nodename}.localdomain"
39
+ })
40
+
41
+ pct.add_test_case(nodename, facts)
42
+ end
43
+ end
44
+
45
+ pct.reporter = @reporter if @reporter
46
+
47
+ pct.run_tests!
48
+ end
49
+
50
+ private
51
+
52
+ def fallback_facts
53
+ {
54
+ 'architecture' => 'x86_64',
55
+ 'ipaddress' => '127.0.0.1',
56
+ 'local_run' => 'true',
57
+ 'disable_asserts' => 'true',
58
+ 'interfaces' => 'eth0'
59
+ }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ module PuppetCatalogTest
2
+ class StdoutReporter
3
+ def initialize(stdout_target = $stdout)
4
+ @out = stdout_target
5
+ end
6
+
7
+ def report_passed_test_case(tc)
8
+ @out.puts "[PASSED] #{tc.name} (compile time: #{tc.duration} seconds)"
9
+ end
10
+
11
+ def report_failed_test_case(tc)
12
+ @out.puts "[FAILED] #{tc.name} (compile time: #{tc.duration} seconds)"
13
+ end
14
+
15
+ def summarize(test_run)
16
+ failed_cases = test_run.test_cases.select { |tc| tc.passed == false }
17
+ avg_time = test_run.total_duration / test_run.test_cases.size
18
+
19
+ @out.puts
20
+ @out.puts "-" * 40
21
+
22
+ @out.puts "Compiled %d catalogs in %.4f seconds (avg: %.4f seconds)" % [
23
+ test_run.test_cases.size,
24
+ test_run.total_duration,
25
+ avg_time
26
+ ]
27
+
28
+ if !failed_cases.empty?
29
+ @out.puts "#{failed_cases.size} test cases failed."
30
+ @out.puts
31
+
32
+ failed_cases.each do |tc|
33
+ @out.puts " [F] #{tc.name}:"
34
+ @out.puts " #{tc.error}"
35
+ @out.puts
36
+ end
37
+
38
+ @out.puts "#{failed_cases.size} / #{test_run.test_cases.size} FAILED"
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,4 @@
1
+ module PuppetCatalogTest
2
+ class TestCase < Struct.new(:name, :facts, :passed, :error, :duration)
3
+ end
4
+ end
@@ -0,0 +1,131 @@
1
+ require "yaml"
2
+ require "puppet"
3
+
4
+ require "puppet-catalog-test/test_case"
5
+ require "puppet-catalog-test/stdout_reporter"
6
+
7
+ module PuppetCatalogTest
8
+ class TestRunner
9
+ attr_accessor :exit_on_fail
10
+ attr_accessor :reporter
11
+
12
+ attr_reader :test_cases
13
+ attr_reader :total_duration
14
+
15
+ def initialize(manifest_path, module_paths, stdout_target = $stdout)
16
+ @test_cases = []
17
+ @exit_on_fail = true
18
+ @out = stdout_target
19
+
20
+ @reporter = StdoutReporter.new(stdout_target)
21
+
22
+ @total_duration = nil
23
+
24
+ raise ArgumentError, "[ERROR] manifest_path must be specified" if !manifest_path
25
+ raise ArgumentError, "[ERROR] manifest_path (#{manifest_path}) does not exist" if !FileTest.exist?(manifest_path)
26
+
27
+ raise ArgumentError, "[ERROR] module_path must be specified" if !module_paths
28
+ module_paths.each do |mp|
29
+ raise ArgumentError, "[ERROR] module_path (#{mp}) does not exist" if !FileTest.directory?(mp)
30
+ end
31
+
32
+ Puppet.settings.handlearg("--config", ".")
33
+ Puppet.settings.handlearg("--manifest", manifest_path)
34
+
35
+ module_path = module_paths.join(":")
36
+
37
+ Puppet.settings.handlearg("--modulepath", module_path)
38
+
39
+ Puppet.parse_config
40
+ end
41
+
42
+ def load_scenario_yaml(yaml_file, filter = nil)
43
+ scenarios = YAML.load_file(yaml_file)
44
+
45
+ scenarios.each do |tc_name, facts|
46
+ next if tc_name =~ /^__/
47
+ next if filter && !tc_name.match(filter)
48
+
49
+ add_test_case(tc_name, facts)
50
+ end
51
+ end
52
+
53
+ def load_all(filter = PuppetCatalogTest::DEFAULT_FILTER, facts = {})
54
+ nodes = collect_puppet_nodes(filter)
55
+
56
+ nodes.each do |n|
57
+ node_facts = facts.dup
58
+
59
+ if !node_facts.has_key?('fqdn')
60
+ node_facts['fqdn'] = n
61
+ end
62
+
63
+ add_test_case(n, node_facts)
64
+ end
65
+ end
66
+
67
+ def add_test_case(tc_name, facts)
68
+ tc = TestCase.new
69
+ tc.name = tc_name
70
+ tc.facts = facts
71
+
72
+ @test_cases << tc
73
+ end
74
+
75
+ def compile_catalog(node_fqdn, facts = {})
76
+ hostname = node_fqdn.split('.').first
77
+ facts['hostname'] = hostname
78
+
79
+ node = Puppet::Node.new(hostname)
80
+ node.merge(facts)
81
+
82
+ Puppet::Parser::Compiler.compile(node)
83
+ end
84
+
85
+ def collect_puppet_nodes(filter)
86
+ parser = Puppet::Parser::Parser.new("environment")
87
+ nodes = parser.environment.known_resource_types.nodes.keys
88
+ nodes.select { |node| node.match(filter) }
89
+ end
90
+
91
+ def run_tests!
92
+ @out.puts "[INFO] Using puppet #{Puppet::PUPPETVERSION}"
93
+
94
+ run_start = Time.now
95
+
96
+ @test_cases.each do |tc|
97
+ begin
98
+ tc_start_time = Time.now
99
+
100
+ if tc.facts['fqdn'].nil?
101
+ raise "fact 'fqdn' must be defined"
102
+ else
103
+ compile_catalog(tc.facts['fqdn'], tc.facts)
104
+ tc.duration = Time.now - tc_start_time
105
+
106
+ tc.passed = true
107
+
108
+ @reporter.report_passed_test_case(tc)
109
+ end
110
+ rescue => error
111
+ tc.duration = Time.now - tc_start_time
112
+ tc.error = error.message
113
+ tc.passed = false
114
+
115
+ @reporter.report_failed_test_case(tc)
116
+ end
117
+ end
118
+
119
+ @total_duration = Time.now - run_start
120
+
121
+ @reporter.summarize(self)
122
+
123
+ if test_cases.any? { |tc| tc.passed == false }
124
+ exit 1 if @exit_on_fail
125
+ return false
126
+ end
127
+
128
+ true
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'puppet-catalog-test'
3
+ s.version = '0.1.0'
4
+ s.homepage = 'https://github.com/invadersmustdie/puppet-catalog-test/'
5
+ s.summary = 'Tests all your puppet catalogs for compiler errors'
6
+ s.description = 'Tests all your puppet catalogs for compiler errors.'
7
+
8
+ s.executables = ['puppet-catalog-test']
9
+
10
+ s.files = [
11
+ 'bin/puppet-catalog-test',
12
+ 'LICENSE',
13
+ 'Rakefile',
14
+ 'README.md',
15
+ 'puppet-catalog-test.gemspec'
16
+ ]
17
+
18
+ s.files += Dir["lib/**/*"]
19
+
20
+ s.add_dependency 'puppet'
21
+
22
+ s.authors = ['Rene Lengwinat']
23
+ s.email = 'rene.lengwinat@googlemail.com'
24
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: puppet-catalog-test
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Rene Lengwinat
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-01-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: puppet
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ description: Tests all your puppet catalogs for compiler errors.
31
+ email: rene.lengwinat@googlemail.com
32
+ executables:
33
+ - puppet-catalog-test
34
+ extensions: []
35
+ extra_rdoc_files: []
36
+ files:
37
+ - bin/puppet-catalog-test
38
+ - LICENSE
39
+ - Rakefile
40
+ - README.md
41
+ - puppet-catalog-test.gemspec
42
+ - lib/puppet-catalog-test/junit_xml_reporter.rb
43
+ - lib/puppet-catalog-test/rake_task.rb
44
+ - lib/puppet-catalog-test/stdout_reporter.rb
45
+ - lib/puppet-catalog-test/test_case.rb
46
+ - lib/puppet-catalog-test/test_runner.rb
47
+ - lib/puppet-catalog-test.rb
48
+ homepage: https://github.com/invadersmustdie/puppet-catalog-test/
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.8.24
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Tests all your puppet catalogs for compiler errors
72
+ test_files: []