onceover 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/Gemfile +6 -0
  4. data/Gemfile.lock +17 -0
  5. data/README.md +504 -0
  6. data/Rakefile +2 -0
  7. data/bin/onceover +17 -0
  8. data/controlrepo.gemspec +38 -0
  9. data/factsets/CentOS-5.11-32.json +263 -0
  10. data/factsets/CentOS-5.11-64.json +263 -0
  11. data/factsets/CentOS-6.6-32.json +305 -0
  12. data/factsets/CentOS-6.6-64.json +342 -0
  13. data/factsets/CentOS-7.0-64.json +352 -0
  14. data/factsets/Debian-6.0.10-32.json +322 -0
  15. data/factsets/Debian-6.0.10-64.json +322 -0
  16. data/factsets/Debian-7.8-32.json +338 -0
  17. data/factsets/Debian-7.8-64.json +338 -0
  18. data/factsets/Ubuntu-12.04-32.json +328 -0
  19. data/factsets/Ubuntu-12.04-64.json +328 -0
  20. data/factsets/Ubuntu-14.04-32.json +337 -0
  21. data/factsets/Ubuntu-14.04-64.json +373 -0
  22. data/factsets/Windows_Server-2008r2-64.json +183 -0
  23. data/factsets/Windows_Server-2012r2-64.json +164 -0
  24. data/lib/onceover/beaker.rb +225 -0
  25. data/lib/onceover/beaker/spec_helper.rb +70 -0
  26. data/lib/onceover/class.rb +29 -0
  27. data/lib/onceover/cli.rb +46 -0
  28. data/lib/onceover/cli/init.rb +31 -0
  29. data/lib/onceover/cli/run.rb +72 -0
  30. data/lib/onceover/cli/show.rb +74 -0
  31. data/lib/onceover/cli/update.rb +48 -0
  32. data/lib/onceover/controlrepo.rb +527 -0
  33. data/lib/onceover/group.rb +85 -0
  34. data/lib/onceover/logger.rb +31 -0
  35. data/lib/onceover/node.rb +44 -0
  36. data/lib/onceover/rake_tasks.rb +113 -0
  37. data/lib/onceover/runner.rb +90 -0
  38. data/lib/onceover/test.rb +157 -0
  39. data/lib/onceover/testconfig.rb +233 -0
  40. data/templates/.fixtures.yml.erb +24 -0
  41. data/templates/Rakefile.erb +6 -0
  42. data/templates/acceptance_test_spec.rb.erb +66 -0
  43. data/templates/controlrepo.yaml.erb +38 -0
  44. data/templates/factsets_README.md.erb +7 -0
  45. data/templates/nodeset.yaml.erb +12 -0
  46. data/templates/pre_conditions_README.md.erb +24 -0
  47. data/templates/spec_helper.rb.erb +16 -0
  48. data/templates/spec_helper_acceptance.rb.erb +1 -0
  49. data/templates/test_spec.rb.erb +34 -0
  50. metadata +345 -0
@@ -0,0 +1,233 @@
1
+ require 'onceover/class'
2
+ require 'onceover/node'
3
+ require 'onceover/group'
4
+ require 'onceover/test'
5
+ require 'onceover/logger'
6
+ require 'onceover/controlrepo'
7
+ require 'git'
8
+ include Onceover::Logger
9
+
10
+ class Onceover
11
+ class TestConfig
12
+ require 'yaml'
13
+
14
+ attr_accessor :classes
15
+ attr_accessor :nodes
16
+ attr_accessor :node_groups
17
+ attr_accessor :class_groups
18
+ attr_accessor :spec_tests
19
+ attr_accessor :acceptance_tests
20
+ attr_accessor :environment
21
+ attr_accessor :opts
22
+ attr_accessor :filter_tags
23
+ attr_accessor :filter_classes
24
+ attr_accessor :filter_nodes
25
+ attr_accessor :mock_functions
26
+
27
+ def initialize(file,opts = {})
28
+ begin
29
+ config = YAML.load(File.read(file))
30
+ rescue Errno::ENOENT
31
+ raise "Could not find #{file}"
32
+ rescue Psych::SyntaxError
33
+ raise "Could not #{file}, check that it is valid YAML and that the encoding is correct"
34
+ end
35
+
36
+ @classes = []
37
+ @nodes = []
38
+ @node_groups = []
39
+ @class_groups = []
40
+ @spec_tests = []
41
+ @acceptance_tests = []
42
+ @opts = opts
43
+ @mock_functions = config['functions']
44
+
45
+ # Add the 'all_classes' and 'all_nodes' default groups
46
+ @node_groups << Onceover::Group.new('all_nodes',@nodes)
47
+ @class_groups << Onceover::Group.new('all_classes',@classes)
48
+
49
+ config['classes'].each { |clarse| @classes << Onceover::Class.new(clarse) } unless config['classes'] == nil
50
+ config['nodes'].each { |node| @nodes << Onceover::Node.new(node) } unless config['nodes'] == nil
51
+ config['node_groups'].each { |name, members| @node_groups << Onceover::Group.new(name, members) } unless config['node_groups'] == nil
52
+ config['class_groups'].each { |name, members| @class_groups << Onceover::Group.new(name, members) } unless config['class_groups'] == nil
53
+
54
+ @filter_tags = opts[:tags] ? [opts[:tags].split(',')].flatten : nil
55
+ @filter_classes = opts[:classes] ? [opts[:classes].split(',')].flatten.map {|x| Onceover::Class.find(x)} : nil
56
+ @filter_nodes = opts[:nodes] ? [opts[:nodes].split(',')].flatten.map {|x| Onceover::Node.find(x)} : nil
57
+
58
+ config['test_matrix'].each do |test_hash|
59
+ test_hash.each do |machines, settings|
60
+ if settings['tests'] == 'spec'
61
+ @spec_tests << Onceover::Test.new(machines,settings['classes'],settings)
62
+ elsif settings['tests'] == 'acceptance'
63
+ @acceptance_tests << Onceover::Test.new(machines,settings['classes'],settings)
64
+ elsif settings['tests'] == 'all_tests'
65
+ tst = Onceover::Test.new(machines,settings['classes'],settings)
66
+ @spec_tests << tst
67
+ @acceptance_tests << tst
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ def self.find_list(thing)
74
+ # Takes a string and finds an object or list of objects to match, will
75
+ # take nodes, classes or groups
76
+
77
+ # We want to supress warnings for this bit
78
+ old_level = logger.level
79
+ logger.level = :error
80
+ if Onceover::Group.find(thing)
81
+ logger.level = old_level
82
+ return Onceover::Group.find(thing).members
83
+ elsif Onceover::Class.find(thing)
84
+ logger.level = old_level
85
+ return [Onceover::Class.find(thing)]
86
+ elsif Onceover::Node.find(thing)
87
+ logger.level = old_level
88
+ return [Onceover::Node.find(thing)]
89
+ else
90
+ logger.level = old_level
91
+ raise "Could not find #{thing} in list of classes, nodes or groups"
92
+ end
93
+ end
94
+
95
+ def verify_spec_test(controlrepo,test)
96
+ test.nodes.each do |node|
97
+ unless controlrepo.facts_files.any? { |file| file =~ /\/#{node.name}\.json/ }
98
+ raise "Could not find factset for node: #{node.name}"
99
+ end
100
+ end
101
+ end
102
+
103
+ def verify_acceptance_test(controlrepo,test)
104
+ require 'yaml'
105
+ nodeset = YAML.load_file(controlrepo.nodeset_file)
106
+ test.nodes.each do |node|
107
+ unless nodeset['HOSTS'].has_key?(node.name)
108
+ raise "Could not find nodeset for node: #{node.name}"
109
+ end
110
+ end
111
+ end
112
+
113
+ def pre_condition
114
+ # Read all the pre_conditions and return the string
115
+ spec_dir = Onceover::Controlrepo.new.spec_dir
116
+ puppetcode = []
117
+ Dir["#{spec_dir}/pre_conditions/*.pp"].each do |condition_file|
118
+ logger.debug "Reading pre_conditions from #{condition_file}"
119
+ puppetcode << File.read(condition_file)
120
+ end
121
+ return nil if puppetcode.count == 0
122
+ puppetcode.join("\n")
123
+ end
124
+
125
+ def r10k_deploy_local(repo = Onceover::Controlrepo.new)
126
+ require 'onceover/controlrepo'
127
+ require 'pathname'
128
+ if repo.tempdir == nil
129
+ repo.tempdir = Dir.mktmpdir('r10k')
130
+ else
131
+ logger.debug "Creating #{repo.tempdir}"
132
+ FileUtils.mkdir_p(repo.tempdir)
133
+ end
134
+
135
+ # We need to do the copy to a tempdir then move the tempdir to the
136
+ # destination, just in case we get a recursive copy
137
+ # TODO: Improve this to save I/O
138
+ logger.debug "Creating temp dir as a staging directory for copying the controlrepo to #{repo.tempdir}"
139
+ temp_controlrepo = Dir.mktmpdir('controlrepo')
140
+ FileUtils.cp_r(Dir["#{repo.root}/*"], "#{temp_controlrepo}")
141
+ FileUtils.mkdir_p("#{repo.tempdir}/#{repo.environmentpath}/production")
142
+ FileUtils.mv(Dir["#{temp_controlrepo}/*"], "#{repo.tempdir}/#{repo.environmentpath}/production",:force => true)
143
+ FileUtils.rm_rf(temp_controlrepo)
144
+
145
+ # Pull the trigger! If it's not already been pulled
146
+ if repo.tempdir
147
+ if File.directory?(repo.tempdir)
148
+ # TODO: Change this to call out to r10k directly to do this
149
+ # Probably something like:
150
+ # R10K::Settings.global_settings.evaluate(with_overrides)
151
+ # R10K::Action::Deploy::Environment
152
+ Dir.chdir("#{repo.tempdir}/#{repo.environmentpath}/production") do
153
+ logger.debug "Runing r10k puppetfile install --verbose from #{repo.tempdir}/#{repo.environmentpath}/production"
154
+ system("r10k puppetfile install --verbose --color")
155
+ end
156
+ else
157
+ raise "#{repo.tempdir} is not a directory"
158
+ end
159
+ end
160
+
161
+ # Return repo.tempdir for use
162
+ repo.tempdir
163
+ end
164
+
165
+ def write_spec_test(location, test)
166
+ # Use an ERB template to write a spec test
167
+ File.write("#{location}/#{test.to_s}_spec.rb",Onceover::Controlrepo.evaluate_template('test_spec.rb.erb',binding))
168
+ end
169
+
170
+ def write_acceptance_tests(location, tests)
171
+ File.write("#{location}/acceptance_spec.rb",Onceover::Controlrepo.evaluate_template('acceptance_test_spec.rb.erb',binding))
172
+ end
173
+
174
+ def write_spec_helper_acceptance(location, repo)
175
+ File.write("#{location}/spec_helper_acceptance.rb",Onceover::Controlrepo.evaluate_template('spec_helper_acceptance.rb.erb',binding))
176
+ end
177
+
178
+ def write_rakefile(location, pattern)
179
+ File.write("#{location}/Rakefile",Onceover::Controlrepo.evaluate_template('Rakefile.erb',binding))
180
+ end
181
+
182
+ def write_spec_helper(location, repo)
183
+ environmentpath = "#{repo.tempdir}/#{repo.environmentpath}"
184
+ modulepath = repo.config['modulepath']
185
+ modulepath.delete("$basemodulepath")
186
+ modulepath.map! do |path|
187
+ "#{environmentpath}/production/#{path}"
188
+ end
189
+ modulepath = modulepath.join(":")
190
+ repo.temp_modulepath = modulepath
191
+
192
+ # Use an ERB template to write a spec test
193
+ File.write("#{location}/spec_helper.rb",Onceover::Controlrepo.evaluate_template('spec_helper.rb.erb',binding))
194
+ end
195
+
196
+ def create_fixtures_symlinks(repo)
197
+ logger.debug "Creating fixtures symlinks"
198
+ FileUtils.rm_rf("#{repo.tempdir}/spec/fixtures/modules")
199
+ FileUtils.mkdir_p("#{repo.tempdir}/spec/fixtures/modules")
200
+ repo.temp_modulepath.split(':').each do |path|
201
+ Dir["#{path}/*"].each do |mod|
202
+ modulename = File.basename(mod)
203
+ logger.debug "Symlinking #{mod} to #{repo.tempdir}/spec/fixtures/modules/#{modulename}"
204
+ FileUtils.ln_s(mod, "#{repo.tempdir}/spec/fixtures/modules/#{modulename}")
205
+ end
206
+ end
207
+ end
208
+
209
+ def run_filters(tests)
210
+ # All of this needs to be applied AFTER deduplication but BEFORE writing
211
+ filters = {
212
+ 'tags' => @filter_tags,
213
+ 'classes' => @filter_classes,
214
+ 'nodes' => @filter_nodes
215
+ }
216
+ filters.each do |method,filter_list|
217
+ if filter_list
218
+ # Remove tests that do not have matching tags
219
+ tests.keep_if do |test|
220
+ filter_list.any? do |filter|
221
+ if test.send(method)
222
+ test.send(method).include?(filter)
223
+ else
224
+ false
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ tests
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,24 @@
1
+ ---
2
+ fixtures:
3
+ <% if symlinks.any? then -%>
4
+ symlinks:
5
+ <% symlinks.each do |link| -%>
6
+ <%= link['name'] %>: <%= link['dir'] %>
7
+ <% end -%>
8
+ <% end -%>
9
+ <% if repositories.any? then -%>
10
+ repositories:
11
+ <% repositories.each do |repo| -%>
12
+ <%= repo['name'] %>:
13
+ repo: <%= repo['repo'] %>
14
+ ref: <%= repo['ref'] %>
15
+ <% end -%>
16
+ <% end -%>
17
+ <% if forge_modules.any? then -%>
18
+ forge_modules:
19
+ <% forge_modules.each do |mod| -%>
20
+ <%= mod['name'] %>:
21
+ repo: <%= mod['repo'] %>
22
+ ref: <%= mod['ref'] %>
23
+ <% end -%>
24
+ <% end -%>
@@ -0,0 +1,6 @@
1
+ require 'puppetlabs_spec_helper/rake_tasks'
2
+
3
+ desc "Run acceptance tests"
4
+ RSpec::Core::RakeTask.new(:acceptance) do |t|
5
+ t.pattern = 'spec/acceptance'
6
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper_acceptance'
2
+
3
+ describe "Acceptance Testing" do
4
+ before :each do |test|
5
+ Onceover::Beaker.match_indentation(test,logger)
6
+ end
7
+ <% tests.each do |test| -%>
8
+ <% test.nodes.each do |node| -%>
9
+ <% test.classes.each do |cls| -%>
10
+ describe "<%= cls.name %> on <%= node.name %>" do
11
+ after :all do
12
+ $host.down!
13
+ end
14
+
15
+ describe "provisioning <%= node.name %>" do
16
+ it "should be able to provision using Beaker" do
17
+ expect {
18
+ $host = Onceover::Beaker.host_create(:'<%= node.name %>',OPTIONS)
19
+ }.not_to raise_exception
20
+ end
21
+ end
22
+
23
+ describe "copying code to <%= node.name %>" do
24
+ it "should copy the code successfully" do
25
+ expect {
26
+ scp_to $host, 'etc', '/'
27
+ }.not_to raise_exception
28
+ end
29
+ end
30
+ <% test.test_config['runs_before_idempotency'].times do %>
31
+ describe "running puppet" do
32
+ it "should run with no errors" do
33
+ expect {
34
+ manifest = <<CODE
35
+ $controlrepo_accpetance = true
36
+
37
+ <%= pre_condition %>
38
+
39
+ include <%= cls.name %>
40
+ CODE
41
+ apply_manifest_on($host,manifest,{:catch_failures => true})
42
+ }.not_to raise_exception
43
+ end
44
+ end
45
+ <% end -%>
46
+ <% if test.test_config['check_idempotency'] then %>
47
+ describe "checking for idempotency" do
48
+ it "should run with no changes" do
49
+ expect {
50
+ manifest = <<CODE
51
+ $controlrepo_accpetance = true
52
+
53
+ <%= pre_condition %>
54
+
55
+ include <%= cls.name %>
56
+ CODE
57
+ apply_manifest_on($host,manifest,{:catch_changes => true})
58
+ }.not_to raise_exception
59
+ end
60
+ end
61
+ <% end -%>
62
+ end
63
+ <% end -%>
64
+ <% end -%>
65
+ <% end -%>
66
+ end
@@ -0,0 +1,38 @@
1
+ # Classes to be tested
2
+ classes:
3
+ <% repo.roles.each do |role| -%>
4
+ - <%= role %>
5
+ <% end -%>
6
+
7
+ # Nodes to tests classes on, this refers to a 'factset' or 'nodeset'
8
+ # depending on weather you are running 'spec' or 'acceptance' tests
9
+ nodes:
10
+ <% repo.facts_files.each do |file| -%>
11
+ - <%= File.basename(file,'.json') %>
12
+ <% end -%>
13
+
14
+ # You can group classes here to save typing
15
+ class_groups:
16
+
17
+ # You can group nodes here to save typing
18
+ # We have created a 'non_windows_nodes' group because we can't
19
+ # give you Windows vagrant boxes to test with because licensing,
20
+ # we can give you fact sets though so go crazy with spec testing!
21
+ node_groups:
22
+ windows_nodes:
23
+ <% repo.facts_files.each do |file| -%>
24
+ <% if File.basename(file,'.json') =~ /Windows/i -%>
25
+ - <%= File.basename(file,'.json') %>
26
+ <% end -%>
27
+ <% end -%>
28
+ non_windows_nodes:
29
+ include: 'all_nodes'
30
+ exclude: 'windows_nodes'
31
+
32
+ test_matrix:
33
+ - all_nodes:
34
+ classes: 'all_classes'
35
+ tests: 'spec'
36
+ - non_windows_nodes:
37
+ classes: 'all_classes'
38
+ tests: 'acceptance'
@@ -0,0 +1,7 @@
1
+ # Factsets
2
+
3
+ This directory is where we put any custom factsets that we want to use. They can be generated by running `puppet facts` on the target system.
4
+
5
+ **Hot tip:** If you already have factsets in here when you run `onceover init` they will be picked up and added to the config file Automatically
6
+
7
+ More info: https://github.com/dylanratcliffe/onceover#factsets
@@ -0,0 +1,12 @@
1
+ HOSTS:
2
+ <% hosts_hash.each do |name, info| -%>
3
+ <% if info[:comment_out] then prefix = '#' end-%>
4
+ <%= prefix %><%= name %>:
5
+ <%= prefix %> roles:
6
+ <%= prefix %> - agent
7
+ <%= prefix %> type: aio
8
+ <%= prefix %> platform: <%= info[:platform] %>
9
+ <%= prefix %> box: <%= info[:boxname] %>
10
+ <%= prefix %> box_url: <%= info[:url] %>
11
+ <%= prefix %> hypervisor: vagrant_virtualbox
12
+ <% end -%>
@@ -0,0 +1,24 @@
1
+ # Pre Conditions
2
+
3
+ This folder should contain any \*.pp files that you want to be included in every test.
4
+
5
+ A common use of this is defining resources that may not exist in the catalog when you are running tests. For example, if we are using a resource that tries to restart the `pe-puppetserver` service, unless it is compiled on a Puppet Maser the `pe-puppetserver` service will not exist and the catalog will fail to compile. To get around this we can create a .pp file and define the resource like so:
6
+
7
+ ``` puppet
8
+ # We are not going to actually have this service anywhere on our servers but
9
+ # our code needs to refresh it. This is to trick puppet into doing nothing
10
+ service { 'pe-puppetserver':
11
+ ensure => 'running',
12
+ enable => false,
13
+ hasrestart => false, # Force Puppet to use start and stop to restart
14
+ start => 'echo "Start"', # This will always exit 0
15
+ stop => 'echo "Stop"', # This will also always exit 0
16
+ hasstatus => false, # Force puppet to use our command for status
17
+ status => 'echo "Status"', # This will always exit 0 and therefore Puppet will think the service is running
18
+ provider => 'base',
19
+ }
20
+ ```
21
+
22
+ This will mean that the `pe-puppetserver` service is in the catalog for spec testing and will even allow you to try to restart it during acceptance tests without the service actually being present.
23
+
24
+ More info: https://github.com/dylanratcliffe/onceover#using-workarounds
@@ -0,0 +1,16 @@
1
+ require 'puppetlabs_spec_helper/module_spec_helper'
2
+ require 'rspec-puppet-utils'
3
+ require 'rspec_junit_formatter'
4
+
5
+ RSpec.configure do |c|
6
+ c.parser = 'future'
7
+ c.formatter = 'documentation'
8
+
9
+ # Also add JUnit output in case people want to use that
10
+ c.add_formatter('RSpecJUnitFormatter','<%= repo.tempdir %>/spec.xml')
11
+
12
+ c.environmentpath = '<%= environmentpath %>'
13
+ c.module_path = '<%= modulepath %>'
14
+ c.hiera_config = '<%= environmentpath %>/production/hiera.yaml'
15
+ c.manifest = '<%= repo.temp_manifest %>'
16
+ end
@@ -0,0 +1 @@
1
+ require 'onceover/beaker/spec_helper'
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ <% test.classes.each do |cls| -%>
4
+ describe "<%= cls.name %>" do
5
+
6
+ <% if @mock_functions -%>
7
+ <% @mock_functions.each do |function,params| -%>
8
+ <% if params['type'] == 'statement' -%>
9
+ MockFunction.new('<%= function %>', {:type => :statement})
10
+ <% else -%>
11
+ let!(:<%= function %>) { MockFunction.new('<%= function %>') { |f|
12
+ f.stubbed.returns(<%= params['returns'] %>)
13
+ }
14
+ }
15
+ <% end -%>
16
+
17
+ <% end -%>
18
+ <% end -%>
19
+ <% test.nodes.each do |node| -%>
20
+ context "using fact set <%= node.name %>" do
21
+ let(:facts) { <%= node.fact_set %> }
22
+ <% if pre_condition -%>
23
+ let(:pre_condition) {
24
+ pp = <<END
25
+ <%= pre_condition.chomp %>
26
+ END
27
+ }
28
+ <% end -%>
29
+ it { should compile }
30
+ end
31
+ <% end -%>
32
+ end
33
+
34
+ <% end -%>