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,85 @@
1
+ require 'onceover/class'
2
+ require 'onceover/node'
3
+
4
+ class Onceover
5
+ class Group
6
+ @@all = []
7
+
8
+ # Work out how to do class veriables so that I can keep track of all the groups easily
9
+ attr_accessor :name
10
+ attr_accessor :members
11
+
12
+ # You need to pass in an array of strings for members, not objects, it will find the objects
13
+ # by itself, and yes it will reference them, not just create additional ones, woo!
14
+
15
+ def initialize(name = nil, members = [])
16
+ @name = name
17
+ @members = []
18
+
19
+ if Onceover::Group.valid_members?(members)
20
+ # If it's already a valid list just chuck it in there
21
+ @members = members
22
+ elsif members.is_a?(Hash)
23
+ # if it's a hash then do subtractive stiff
24
+ @members = Onceover::Group.subtractive_to_list(members)
25
+ else
26
+ # Turn it into a full list
27
+ member_objects = []
28
+
29
+ # This should also handle lists that include groups
30
+ members.each { |member| member_objects << Onceover::TestConfig.find_list(member) }
31
+ member_objects.flatten!
32
+
33
+ # Check that they are all the same type
34
+ unless Onceover::Group.valid_members?(member_objects)
35
+ raise 'Groups must contain either all nodes or all classes. Either there was a mix, or something was spelled wrong'
36
+ end
37
+
38
+ # Smash it into the instance variable
39
+ @members = member_objects
40
+ end
41
+
42
+ # Finally add it to the list of all grops
43
+ @@all << self
44
+ end
45
+
46
+ def self.find(group_name)
47
+ @@all.each do |group|
48
+ if group.name == group_name
49
+ return group
50
+ end
51
+ end
52
+ nil
53
+ end
54
+
55
+ def self.all
56
+ @@all
57
+ end
58
+
59
+ def self.valid_members?(members)
60
+ # Check that they are all the same type
61
+ # Also catch any errors to assume it's invalid
62
+ begin
63
+ if members.all? { |item| item.is_a?(Onceover::Class) }
64
+ return true
65
+ elsif members.all? { |item| item.is_a?(Onceover::Node) }
66
+ return true
67
+ else
68
+ return false
69
+ end
70
+ rescue
71
+ return false
72
+ end
73
+ end
74
+
75
+ def self.subtractive_to_list(subtractive_hash)
76
+ # Take a hash that looks like this:
77
+ # { 'include' => 'somegroup'
78
+ # 'exclude' => 'other'}
79
+ # and return a list of classes/nodes
80
+ include_list = Onceover::TestConfig.find_list(subtractive_hash['include'])
81
+ exclude_list = Onceover::TestConfig.find_list(subtractive_hash['exclude'])
82
+ include_list - exclude_list
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,31 @@
1
+ require 'logging'
2
+
3
+ module Onceover::Logger
4
+ def logger
5
+ unless $logger
6
+ # here we setup a color scheme called 'bright'
7
+ Logging.color_scheme( 'bright',
8
+ :levels => {
9
+ :debug => :cyan,
10
+ :info => :green,
11
+ :warn => :yellow,
12
+ :error => :red,
13
+ :fatal => [:white, :on_red]
14
+ }
15
+ )
16
+
17
+ Logging.appenders.stdout(
18
+ 'stdout',
19
+ :layout => Logging.layouts.pattern(
20
+ :pattern => '%l\t -> %m\n',
21
+ :color_scheme => 'bright'
22
+ )
23
+ )
24
+
25
+ $logger = Logging.logger['Colors']
26
+ $logger.add_appenders 'stdout'
27
+ $logger.level = :warn
28
+ end
29
+ $logger
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ require 'onceover/controlrepo'
2
+
3
+ class Onceover
4
+ class Node
5
+ @@all = []
6
+
7
+
8
+ attr_accessor :name
9
+ attr_accessor :beaker_node
10
+ attr_accessor :fact_set
11
+
12
+ def initialize(name)
13
+ @name = name
14
+ @beaker_node = nil
15
+
16
+ # If we can't find the factset it will fail, so just catch that error and ignore it
17
+ begin
18
+ @fact_set = Onceover::Controlrepo.facts[Onceover::Controlrepo.facts_files.index{|facts_file| File.basename(facts_file,'.json') == name}]
19
+ rescue TypeError
20
+ @fact_set = nil
21
+ end
22
+ @@all << self
23
+
24
+ end
25
+
26
+ def self.find(node_name)
27
+ @@all.each do |node|
28
+ if node_name.is_a?(Onceover::Node)
29
+ if node = node_name
30
+ return node
31
+ end
32
+ elsif node.name == node_name
33
+ return node
34
+ end
35
+ end
36
+ logger.warn "Node #{node_name} not found"
37
+ nil
38
+ end
39
+
40
+ def self.all
41
+ @@all
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,113 @@
1
+ require 'onceover/controlrepo'
2
+ require 'pathname'
3
+
4
+ @repo = nil
5
+ @config = nil
6
+
7
+ task :generate_fixtures do
8
+ repo = Onceover::Controlrepo.new
9
+ raise ".fixtures.yml already exits, we won't overwrite because we are scared" if File.exists?(File.expand_path('./.fixtures.yml',repo.root))
10
+ File.write(File.expand_path('./.fixtures.yml',repo.root),repo.fixtures)
11
+ end
12
+
13
+ task :hiera_setup do
14
+ repo = Onceover::Controlrepo.new
15
+ current_config = repo.hiera_config
16
+ current_config.each do |key, value|
17
+ if value.is_a?(Hash)
18
+ if value.has_key?(:datadir)
19
+ current_config[key][:datadir] = Pathname.new(repo.hiera_data).relative_path_from(Pathname.new(File.expand_path('..',repo.hiera_config_file))).to_s
20
+ end
21
+ end
22
+ end
23
+ puts "Changing hiera config from \n#{repo.hiera_config}\nto\n#{current_config}"
24
+ repo.hiera_config = current_config
25
+ end
26
+
27
+ task :controlrepo_details do
28
+ require 'onceover/controlrepo'
29
+ puts Onceover::Controlrepo.new.to_s
30
+ end
31
+
32
+ task :generate_controlrepo_yaml do
33
+ require 'onceover/controlrepo'
34
+ repo = Onceover::Controlrepo.new
35
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
36
+ controlrepo_yaml_template = File.read(File.expand_path('./controlrepo.yaml.erb',template_dir))
37
+ puts ERB.new(controlrepo_yaml_template, nil, '-').result(binding)
38
+ end
39
+
40
+
41
+ task :generate_nodesets do
42
+ require 'onceover/beaker'
43
+ require 'net/http'
44
+ require 'json'
45
+
46
+ repo = Onceover::Controlrepo.new
47
+
48
+ puts "HOSTS:"
49
+
50
+ repo.facts.each do |fact_set|
51
+ node_name = File.basename(repo.facts_files[repo.facts.index(fact_set)],'.json')
52
+ boxname = Onceover::Beaker.facts_to_vagrant_box(fact_set)
53
+ platform = Onceover::Beaker.facts_to_platform(fact_set)
54
+ response = Net::HTTP.get(URI.parse("https://atlas.hashicorp.com/api/v1/box/#{boxname}"))
55
+ url = 'URL goes here'
56
+
57
+ if response =~ /Not Found/i
58
+ comment_out = true
59
+ else
60
+ comment_out = false
61
+ box_info = JSON.parse(response)
62
+ box_info['current_version']['providers'].each do |provider|
63
+ if provider['name'] == 'virtualbox'
64
+ url = provider['original_url']
65
+ end
66
+ end
67
+ end
68
+
69
+ # Use an ERB template
70
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
71
+ fixtures_template = File.read(File.expand_path('./nodeset.yaml.erb',template_dir))
72
+ puts ERB.new(fixtures_template, nil, '-').result(binding)
73
+ end
74
+
75
+ end
76
+
77
+ task :controlrepo_autotest_prep do
78
+ require 'onceover/testconfig'
79
+ require 'onceover/runner'
80
+ @repo = Onceover::Controlrepo.new
81
+ # TODO: This should be getting the location of controlrepo.yaml from @repo
82
+ @config = Onceover::TestConfig.new("#{@repo.spec_dir}/controlrepo.yaml")
83
+
84
+ @runner = Onceover::Runner.new(@repo, @config)
85
+ @runner.prepare!
86
+ end
87
+
88
+ task :controlrepo_autotest_spec do
89
+ @runner.run_spec!
90
+ end
91
+
92
+ task :controlrepo_autotest_acceptance do
93
+ @runner.run_acceptance!
94
+ end
95
+
96
+ task :controlrepo_spec => [
97
+ :controlrepo_autotest_prep,
98
+ :controlrepo_autotest_spec
99
+ ]
100
+
101
+ task :controlrepo_acceptance => [
102
+ :controlrepo_autotest_prep,
103
+ :controlrepo_autotest_acceptance
104
+ ]
105
+
106
+ task :controlrepo_temp_create do
107
+ require 'onceover/testconfig'
108
+ repo = Onceover::Controlrepo.new
109
+ config = Onceover::TestConfig.new("#{repo.spec_dir}/controlrepo.yaml")
110
+ FileUtils.rm_rf(repo.tempdir)
111
+ # Deploy r10k to a temp dir
112
+ config.r10k_deploy_local(repo)
113
+ end
@@ -0,0 +1,90 @@
1
+ class Onceover
2
+ class Runner
3
+ attr_reader :repo
4
+ attr_reader :config
5
+
6
+ def initialize(repo, config, mode = [:spec, :acceptance])
7
+ @repo = repo
8
+ @config = config
9
+ @mode = [mode].flatten
10
+ end
11
+
12
+ def prepare!
13
+ # Deploy the puppetfile
14
+ @config.r10k_deploy_local(@repo)
15
+
16
+ # Remove the entire spec directory to make sure we have
17
+ # all the latest tests
18
+ FileUtils.rm_rf("#{@repo.tempdir}/spec")
19
+
20
+ # Create the other directories we need
21
+ FileUtils.mkdir_p("#{@repo.tempdir}/spec/classes")
22
+ FileUtils.mkdir_p("#{@repo.tempdir}/spec/acceptance/nodesets")
23
+
24
+ # Copy our entire spec directory over
25
+ FileUtils.cp_r("#{@repo.spec_dir}","#{@repo.tempdir}")
26
+
27
+ # Create the Rakefile so that we can take advantage of the existing tasks
28
+ @config.write_rakefile(@repo.tempdir, "spec/classes/**/*_spec.rb")
29
+
30
+ # Create spec_helper.rb
31
+ @config.write_spec_helper("#{@repo.tempdir}/spec",@repo)
32
+
33
+ # Create spec_helper_accpetance.rb
34
+ @config.write_spec_helper_acceptance("#{@repo.tempdir}/spec",@repo)
35
+
36
+ # TODO: Remove all tests that do not match set tags
37
+
38
+ if @mode.include?(:spec)
39
+ # Verify all of the spec tests
40
+ @config.spec_tests.each { |test| @config.verify_spec_test(@repo,test) }
41
+
42
+ # Deduplicate and write the tests (Spec and Acceptance)
43
+ @config.run_filters(Onceover::Test.deduplicate(@config.spec_tests)).each do |test|
44
+ @config.write_spec_test("#{@repo.tempdir}/spec/classes",test)
45
+ end
46
+ end
47
+
48
+ if @mode.include?(:acceptance)
49
+ # Verify all of the acceptance tests
50
+ @config.acceptance_tests.each { |test| @config.verify_acceptance_test(@repo,test) }
51
+
52
+ # Write them out
53
+ @config.write_acceptance_tests("#{@repo.tempdir}/spec/acceptance",@config.run_filters(Onceover::Test.deduplicate(@config.acceptance_tests)))
54
+ end
55
+
56
+ # Parse the current hiera config, modify, and write it to the temp dir
57
+ unless @repo.hiera_config ==nil
58
+ hiera_config = @repo.hiera_config
59
+ hiera_config.each do |setting,value|
60
+ if value.is_a?(Hash)
61
+ if value.has_key?(:datadir)
62
+ hiera_config[setting][:datadir] = "#{@repo.tempdir}/#{@repo.environmentpath}/production/#{value[:datadir]}"
63
+ end
64
+ end
65
+ end
66
+ File.write("#{@repo.tempdir}/#{@repo.environmentpath}/production/hiera.yaml",hiera_config.to_yaml)
67
+ end
68
+
69
+ @config.create_fixtures_symlinks(@repo)
70
+ end
71
+
72
+ def run_spec!
73
+ Dir.chdir(@repo.tempdir) do
74
+ #`bundle install --binstubs`
75
+ #`bin/rake spec_standalone`
76
+ logger.debug "Running bundle exec rake spec_standalone from #{@repo.tempdir}"
77
+ exec("bundle exec rake spec_standalone")
78
+ end
79
+ end
80
+
81
+ def run_acceptance!
82
+ Dir.chdir(@repo.tempdir) do
83
+ #`bundle install --binstubs`
84
+ #`bin/rake spec_standalone`
85
+ logger.debug "Running bundle exec rake acceptance from #{@repo.tempdir}"
86
+ exec("bundle exec rake acceptance")
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,157 @@
1
+ class Onceover
2
+ class Test
3
+ @@all =[]
4
+
5
+ attr_accessor :nodes
6
+ attr_accessor :classes
7
+ attr_accessor :test_config
8
+ attr_reader :default_test_config
9
+ attr_reader :tags
10
+
11
+ # This can accept a bunch of stuff. It can accept nodes, classes or groups anywhere
12
+ # it will then detect them and expand them out into their respective objects so that
13
+ # we just end up with a list of nodes and classes
14
+ #def initialize(on_this,test_config['classes'],options = {})
15
+ def initialize(on_this,test_this,test_config)
16
+
17
+ @default_test_config = {
18
+ 'check_idempotency' => true,
19
+ 'runs_before_idempotency' => 1
20
+ }
21
+
22
+ # Add defaults if they do not exist
23
+ test_config = @default_test_config.merge(test_config)
24
+
25
+ @nodes = []
26
+ @classes = []
27
+ @test_config = test_config
28
+ @test_config.delete('classes') # remove classes from the config
29
+ @tags = @test_config['tags']
30
+
31
+
32
+ # Make sure that tags are an array
33
+ @test_config['tags'] = [@test_config['tags']].flatten if @test_config['tags']
34
+
35
+ # Get the nodes we are working on
36
+ if Onceover::Group.find(on_this)
37
+ @nodes << Onceover::Group.find(on_this).members
38
+ elsif Onceover::Node.find(on_this)
39
+ @nodes << Onceover::Node.find(on_this)
40
+ else
41
+ raise "#{on_this} was not found in the list of nodes or groups!"
42
+ end
43
+
44
+ @nodes.flatten!
45
+
46
+ # Check that our nodes list contains only nodes
47
+ raise "#{@nodes} contained a non-node object." unless @nodes.all? { |item| item.is_a?(Onceover::Node) }
48
+
49
+ if test_this.is_a?(String)
50
+ # If we have just given a string then grab all the classes it corresponds to
51
+ if Onceover::Group.find(test_this)
52
+ @classes << Onceover::Group.find(test_this).members
53
+ elsif Onceover::Class.find(test_this)
54
+ @classes << Onceover::Class.find(test_this)
55
+ else
56
+ raise "#{test_this} was not found in the list of classes or groups!"
57
+ end
58
+ @classes.flatten!
59
+ elsif test_this.is_a?(Hash)
60
+ # If it is a hash we need to get creative
61
+
62
+ # Get all of the included classes and add them
63
+ if Onceover::Group.find(test_this['include'])
64
+ @classes << Onceover::Group.find(test_this['include']).members
65
+ elsif Onceover::Class.find(test_this['include'])
66
+ @classes << Onceover::Class.find(test_this['include'])
67
+ else
68
+ raise "#{test_this['include']} was not found in the list of classes or groups!"
69
+ end
70
+ @classes.flatten!
71
+
72
+ # Then remove any excluded ones
73
+ if Onceover::Group.find(test_this['exclude'])
74
+ Onceover::Group.find(test_this['exclude']).members.each do |clarse|
75
+ @classes.delete(clarse)
76
+ end
77
+ elsif Onceover::Class.find(test_this['exclude'])
78
+ @classes.delete(Onceover::Class.find(test_this['exclude']))
79
+ else
80
+ raise "#{test_this['exclude']} was not found in the list of classes or groups!"
81
+ end
82
+ elsif test_this.is_a?(Onceover::Class)
83
+ @classes << test_this
84
+ end
85
+ end
86
+
87
+ def eql?(other)
88
+ (@nodes.sort.eql?(other.nodes.sort)) and (@classes.sort.eql?(other.classes.sort))
89
+ end
90
+
91
+ def to_s
92
+ class_msg = ""
93
+ node_msg = ""
94
+ if classes.count > 1
95
+ class_msg = "#{classes.count}_classes"
96
+ else
97
+ class_msg = classes[0].name
98
+ end
99
+
100
+ if nodes.count > 1
101
+ node_msg = "#{nodes.count}_nodes"
102
+ else
103
+ node_msg = nodes[0].name
104
+ end
105
+
106
+ "#{class_msg}_on_#{node_msg}"
107
+ end
108
+
109
+ def self.deduplicate(tests)
110
+ require 'deep_merge'
111
+ # This should take an array of tests and remove any duplicates from them
112
+
113
+ # this will be an array of arrays, or maybe hashes
114
+ combinations = []
115
+ new_tests = []
116
+ tests.each do |test|
117
+ test.nodes.each do |node|
118
+ test.classes.each do |cls|
119
+ combo = {node => cls}
120
+ if combinations.member?(combo)
121
+
122
+ # Find the right test object:
123
+ relevant_test = new_tests[new_tests.index do |a|
124
+ a.nodes[0] == node and a.classes[0] == cls
125
+ end]
126
+
127
+ # Delete all default values in the current options hash
128
+ test.test_config.delete_if do |key,value|
129
+ test.default_test_config[key] == value
130
+ end
131
+
132
+ # Merge the non-default options right on in there
133
+ relevant_test.test_config.deep_merge!(test.test_config)
134
+ else
135
+ combinations << combo
136
+ new_tests << Onceover::Test.new(node,cls,test.test_config)
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ # The array that this returns should be ephemeral, it does not
143
+ # represent anything defined in a controlrepo and should just
144
+ # be passed into the thing doing the testing and then killed,
145
+ # we don't want too many copies of the same shit going around
146
+ #
147
+ # Actually based on the way things are written I don't think this
148
+ # will duplicated node or class objects, just test objects,
149
+ # everything else is passed by reference
150
+ new_tests
151
+ end
152
+
153
+ def self.all
154
+ @@all
155
+ end
156
+ end
157
+ end