controlrepo 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,124 @@
1
+ class Controlrepo
2
+ class Test
3
+ @@all =[]
4
+
5
+ attr_accessor :nodes
6
+ attr_accessor :classes
7
+ attr_accessor :options
8
+
9
+ # This can accept a bunch of stuff. It can accept nodes, classes or groups anywhere
10
+ # it will then detect them and expand them out into their respective objects so that
11
+ # we just end up with a list of nodes and classes
12
+ def initialize(on_these,test_this,options = {})
13
+ @nodes = []
14
+ @classes = []
15
+ @options = options
16
+
17
+ # Get the nodes we are working on
18
+ if Controlrepo::Group.find(on_these)
19
+ @nodes << Controlrepo::Group.find(on_these).members
20
+ elsif Controlrepo::Node.find(on_these)
21
+ @nodes << Controlrepo::Node.find(on_these)
22
+ else
23
+ raise "#{on_these} was not found in the list of nodes or groups!"
24
+ end
25
+
26
+ @nodes.flatten!
27
+
28
+ # Check that our nodes list contains only nodes
29
+ raise "#{@nodes} contained a non-node object." unless @nodes.all? { |item| item.is_a?(Controlrepo::Node) }
30
+
31
+ if test_this.is_a?(String)
32
+ # If we have just given a string then grab all the classes it corresponds to
33
+ if Controlrepo::Group.find(test_this)
34
+ @classes << Controlrepo::Group.find(test_this).members
35
+ elsif Controlrepo::Class.find(test_this)
36
+ @classes << Controlrepo::Class.find(test_this)
37
+ else
38
+ raise "#{test_this} was not found in the list of classes or groups!"
39
+ end
40
+ @classes.flatten!
41
+ elsif test_this.is_a?(Hash)
42
+ # If it is a hash we need to get creative
43
+
44
+ # Get all of the included classes and add them
45
+ if Controlrepo::Group.find(test_this['include'])
46
+ @classes << Controlrepo::Group.find(test_this['include']).members
47
+ elsif Controlrepo::Class.find(test_this['include'])
48
+ @classes << Controlrepo::Class.find(test_this['include'])
49
+ else
50
+ raise "#{test_this['include']} was not found in the list of classes or groups!"
51
+ end
52
+ @classes.flatten!
53
+
54
+ # Then remove any excluded ones
55
+ if Controlrepo::Group.find(test_this['exclude'])
56
+ Controlrepo::Group.find(test_this['exclude']).members.each do |clarse|
57
+ @classes.delete(clarse)
58
+ end
59
+ elsif Controlrepo::Class.find(test_this['exclude'])
60
+ @classes.delete(Controlrepo::Class.find(test_this['exclude']))
61
+ else
62
+ raise "#{test_this['exclude']} was not found in the list of classes or groups!"
63
+ end
64
+ elsif test_this.is_a?(Controlrepo::Class)
65
+ @classes << test_this
66
+ end
67
+ end
68
+
69
+ def eql?(other)
70
+ (@nodes.sort.eql?(other.nodes.sort)) and (@classes.sort.eql?(other.classes.sort))
71
+ end
72
+
73
+ def to_s
74
+ class_msg = ""
75
+ node_msg = ""
76
+ if classes.count > 1
77
+ class_msg = "#{classes.count}_classes"
78
+ else
79
+ class_msg = classes[0].name
80
+ end
81
+
82
+ if nodes.count > 1
83
+ node_msg = "#{nodes.count}_nodes"
84
+ else
85
+ node_msg = nodes[0].name
86
+ end
87
+
88
+ "#{class_msg}_on_#{node_msg}"
89
+ end
90
+
91
+ def self.deduplicate(tests)
92
+ # This should take an array of tests and remove any duplicates from them
93
+
94
+ # this will be an array of arrays, or maybe hashes
95
+ combinations = []
96
+ new_tests = []
97
+ tests.each do |test|
98
+ test.nodes.each do |node|
99
+ test.classes.each do |cls|
100
+ combo = {node => cls}
101
+ unless combinations.member?(combo)
102
+ combinations << combo
103
+ new_tests << Controlrepo::Test.new(node,cls,test.options)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ # The array that this returns should be ephemeral, it does not
110
+ # represent anything defined in a controlrepo and should just
111
+ # be passed into the thing doing the testing and then killed,
112
+ # we don't want too many copies of the same shit going around
113
+ #
114
+ # Actually based on the way things are written I don't think this
115
+ # will duplicated node or class objects, just test objects,
116
+ # everything else is passed by reference
117
+ new_tests
118
+ end
119
+
120
+ def self.all
121
+ @@all
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,136 @@
1
+ require 'controlrepo/class'
2
+ require 'controlrepo/node'
3
+ require 'controlrepo/group'
4
+ require 'controlrepo/test'
5
+
6
+ class Controlrepo
7
+ class TestConfig
8
+ require 'yaml'
9
+
10
+ attr_accessor :classes
11
+ attr_accessor :nodes
12
+ attr_accessor :groups
13
+ attr_accessor :tests
14
+ attr_accessor :environment
15
+
16
+ def initialize(file, environment = 'production')
17
+ begin
18
+ config = YAML.load(File.read(file))
19
+ rescue YAML::ParserError
20
+ raise "Could not parse the YAML file, check that it is valid YAML and that the encoding is correct"
21
+ end
22
+
23
+ @environment = environment
24
+ @classes = []
25
+ @nodes = []
26
+ @groups = []
27
+ @tests = []
28
+
29
+ config['classes'].each { |clarse| @classes << Controlrepo::Class.new(clarse) }
30
+ config['nodes'].each { |node| @nodes << Controlrepo::Node.new(node) }
31
+ config['groups'].each { |name, members| @groups << Controlrepo::Group.new(name, members) }
32
+
33
+ # Add the 'all_classes' and 'all_nodes' default groups
34
+ @groups << Controlrepo::Group.new('all_nodes',@nodes)
35
+ @groups << Controlrepo::Group.new('all_classes',@classes)
36
+
37
+ config['test_matrix'].each do |machines, roles|
38
+ # TODO: Work out some way to set per-test options like idempotency
39
+ @tests << Controlrepo::Test.new(machines,roles)
40
+ end
41
+ end
42
+
43
+ def r10k_deploy_local(repo = Controlrepo.new)
44
+ require 'controlrepo'
45
+ tempdir = Dir.mktmpdir('r10k')
46
+ repo.tempdir = tempdir
47
+
48
+ # Read in the config and change all the directories, then create them
49
+ r10k_config = repo.r10k_config
50
+ r10k_config[:cachedir] = "#{tempdir}#{r10k_config[:cachedir]}"
51
+ FileUtils::mkdir_p(r10k_config[:cachedir])
52
+ r10k_config[:sources].map do |name,source_settings|
53
+ source_settings["basedir"] = "#{tempdir}#{source_settings["basedir"]}"
54
+ FileUtils::mkdir_p(source_settings["basedir"])
55
+ # Yes, I realise this is going to set it many times
56
+ repo.temp_environmentpath = source_settings["basedir"]
57
+ end
58
+ File.write("#{tempdir}/r10k.yaml",r10k_config.to_yaml)
59
+
60
+ # Pull the trigger!
61
+ Dir.chdir(tempdir) do
62
+ `r10k deploy environment #{@environment} -p --color --config #{tempdir}/r10k.yaml --verbose`
63
+ end
64
+
65
+ # Return tempdir for use
66
+ tempdir
67
+ end
68
+
69
+ def write_spec_test(location, test)
70
+ # Use an ERB template to write a spec test
71
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
72
+ spec_template = File.read(File.expand_path('./test_spec.rb.erb',template_dir))
73
+ randomness = (0...6).map { (65 + rand(26)).chr }.join
74
+ File.write("#{location}/#{randomness}_#{test.to_s}_spec.rb",ERB.new(spec_template, nil, '-').result(binding))
75
+ end
76
+
77
+ def write_acceptance_test(location, test)
78
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
79
+ acc_test_template = File.read(File.expand_path('./acceptance_test_spec.rb.erb',template_dir))
80
+ raise 'We only support writing acceptance tests for one node at the moment' unless test.nodes.count == 1
81
+ randomness = (0...6).map { (65 + rand(26)).chr }.join
82
+ File.write("#{location}/#{randomness}_#{test.to_s}_spec.rb",ERB.new(acc_test_template, nil, '-').result(binding))
83
+ end
84
+
85
+ def write_spec_helper_acceptance(location, repo)
86
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
87
+ spec_heler_acc_template = File.read(File.expand_path('./spec_helper_acceptance.rb.erb',template_dir))
88
+ File.write("#{location}/spec_helper_acceptance.rb",ERB.new(spec_heler_acc_template, nil, '-').result(binding))
89
+ end
90
+
91
+ def write_rakefile(location, pattern)
92
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
93
+ rakefile_template = File.read(File.expand_path('./Rakefile.erb',template_dir))
94
+ File.write("#{location}/Rakefile",ERB.new(rakefile_template, nil, '-').result(binding))
95
+ end
96
+
97
+ def write_gemfile(location)
98
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
99
+ gemfile_template = File.read(File.expand_path('./Gemfile.erb',template_dir))
100
+ File.write("#{location}/Gemfile",ERB.new(gemfile_template, nil, '-').result(binding))
101
+ end
102
+
103
+ def write_spec_helper(location, repo)
104
+ environmentpath = repo.temp_environmentpath
105
+ modulepath = repo.config['modulepath']
106
+ modulepath.delete("$basemodulepath")
107
+ modulepath.map! do |path|
108
+ "#{environmentpath}/#{@environment}/#{path}"
109
+ end
110
+ modulepath = modulepath.join(":")
111
+ repo.temp_modulepath = modulepath
112
+
113
+ # Use an ERB template to write a spec test
114
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
115
+ spec_helper_template = File.read(File.expand_path('./spec_helper.rb.erb',template_dir))
116
+ File.write("#{location}/spec_helper.rb",ERB.new(spec_helper_template, nil, '-').result(binding))
117
+ end
118
+
119
+ def create_fixtures_symlinks(repo)
120
+ FileUtils.mkdir_p("#{repo.tempdir}/spec/fixtures/modules")
121
+ repo.temp_modulepath.split(':').each do |path|
122
+ Dir["#{path}/*"].each do |mod|
123
+ modulename = File.basename(mod)
124
+ FileUtils.ln_s(mod, "#{repo.tempdir}/spec/fixtures/modules/#{modulename}")
125
+ end
126
+ end
127
+ end
128
+
129
+ # TODO: Work out the best way to format the output
130
+ # TODO: Look into bundling bundler into the temp dir
131
+ # TODO: Write task for beaker tests *brace yourself* Dont forget about the beaker file you have
132
+ # TODO: Compare the outlout of the beaker helper that I wrote
133
+ # with the output from the templated tests, us ethe better one
134
+ # bearing in minf that beaker has logger options that could help
135
+ end
136
+ 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,5 @@
1
+ gem 'puppet'
2
+ gem 'rspec'
3
+ gem 'rspec-puppet'
4
+ gem 'rake'
5
+ gem 'puppetlabs_spec_helper'
@@ -0,0 +1 @@
1
+ require 'puppetlabs_spec_helper/rake_tasks'
@@ -0,0 +1,18 @@
1
+ require 'spec_helper_acceptance'
2
+
3
+ <% test.nodes.each do |node| -%>
4
+ describe '<%= node.name %>' do
5
+ <% test.classes.each do |cls| %>
6
+ context 'when classified with <%= cls.name %>' do
7
+ role = '<%= cls.name %>'
8
+ apply_manifest(role, :catch_failures => true)
9
+ <% if test.options[:runs_for_idempotency] -%>
10
+ <% (test.options[:runs_for_idempotency] - 1).times do -%>
11
+ apply_manifest(role, :catch_failures => true)
12
+ <% end -%>
13
+ <% end -%>
14
+ apply_manifest(role, :catch_changes => true)
15
+ end
16
+ <% end %>
17
+ end
18
+ <% end %>
@@ -0,0 +1,9 @@
1
+ HOSTS:
2
+ <%= fact_set['fqdn'] %>:
3
+ roles:
4
+ - agent
5
+ type: aio
6
+ platform: <%= platform %>
7
+ box: <%= boxname %>
8
+ box_url: <%= url %>
9
+ hypervisor: vagrant_virtualbox
@@ -0,0 +1,7 @@
1
+ require 'puppetlabs_spec_helper/module_spec_helper'
2
+
3
+ RSpec.configure do |c|
4
+ c.parser = 'future'
5
+ c.module_path = '<%= modulepath %>'
6
+ c.hiera_config = '<%= environmentpath %>/<%= @environment %>/hiera.yaml'
7
+ end
@@ -0,0 +1,24 @@
1
+ require 'beaker-rspec'
2
+
3
+ scp_to hosts, '<%= repo.tempdir %>/etc', '/etc' # Check that this wil work recursively
4
+
5
+ RSpec.configure do |c|
6
+ # Project root
7
+ proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
8
+
9
+ # Readable test descriptions
10
+ c.formatter = :documentation
11
+
12
+ # Configure all nodes in nodeset
13
+ #c.before :suite do
14
+ # # Install module
15
+ # puppet_module_install(:source => proj_root, :module_name => 'virtualbox')
16
+ # hosts.each do |host|
17
+ # on host, puppet('module','install','puppetlabs-stdlib'), { :acceptable_exit_codes => [0,1] }
18
+ # on host, puppet('module','install','puppetlabs-apt'), { :acceptable_exit_codes => [0,1] }
19
+ # on host, puppet('module','install','stahnma-epel'), { :acceptable_exit_codes => [0,1] }
20
+ # on host, puppet('module','install','camptocamp-archive'), { :acceptable_exit_codes => [0,1] }
21
+ # on host, puppet('module','install','darin-zypprepo'), { :acceptable_exit_codes => [0,1] }
22
+ # end
23
+ #end
24
+ end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ <% test.classes.each do |cls| -%>
4
+ describe "<%= cls.name %>" do
5
+ <% test.nodes.each do |node| -%>
6
+ context "using fact set <%= node.name %>" do
7
+ let(:facts) { <%= node.fact_set %> }
8
+ it { should compile }
9
+ end
10
+ <% end -%>
11
+ end
12
+
13
+ <% end -%>
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: controlrepo
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Dylan Ratcliffe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-10-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: beaker-rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-puppet
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: puppet
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: ''
112
+ email:
113
+ - dylan.ratcliffe@puppetlabs.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - README.md
120
+ - controlrepo.gemspec
121
+ - lib/controlrepo.rb
122
+ - lib/controlrepo/beaker.rb
123
+ - lib/controlrepo/class.rb
124
+ - lib/controlrepo/group.rb
125
+ - lib/controlrepo/node.rb
126
+ - lib/controlrepo/rake_tasks.rb
127
+ - lib/controlrepo/test.rb
128
+ - lib/controlrepo/testconfig.rb
129
+ - templates/.fixtures.yml.erb
130
+ - templates/Gemfile.erb
131
+ - templates/Rakefile.erb
132
+ - templates/acceptance_test_spec.rb.erb
133
+ - templates/nodeset.yaml.erb
134
+ - templates/spec_helper.rb.erb
135
+ - templates/spec_helper_acceptance.rb.erb
136
+ - templates/test_spec.rb.erb
137
+ homepage: ''
138
+ licenses:
139
+ - Apache-2.0
140
+ metadata: {}
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: '0'
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubyforge_project:
157
+ rubygems_version: 2.4.7
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: ''
161
+ test_files: []
162
+ has_rdoc: