puppet-catalog-test 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []