onceover 3.0.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.
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 -%>