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,29 @@
1
+ class Onceover
2
+ class Class
3
+ @@all = []
4
+
5
+ attr_accessor :name
6
+ def initialize(name)
7
+ @name = name
8
+ @@all << self
9
+ end
10
+
11
+ def self.find(class_name)
12
+ @@all.each do |cls|
13
+ if class_name.is_a?(Onceover::Class)
14
+ if cls = class_name
15
+ return cls
16
+ end
17
+ elsif cls.name == class_name
18
+ return cls
19
+ end
20
+ end
21
+ logger.warn "Class #{class_name} not found"
22
+ nil
23
+ end
24
+
25
+ def self.all
26
+ @@all
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ require 'cri'
2
+
3
+ class Onceover
4
+ class CLI
5
+ def self.command
6
+ @cmd ||= Cri::Command.define do
7
+ name 'onceover'
8
+ usage 'onceover <subcommand> [options]'
9
+ summary 'Tool for testing Puppet controlrepos'
10
+
11
+ flag :h, :help, 'Show help for this command' do |value, cmd|
12
+ puts cmd.help
13
+ exit 0
14
+ end
15
+
16
+ flag nil, :trace, 'Display stack traces on application crash'
17
+ flag :d, :debug, 'Enable debug loging'
18
+ optional :p, :path, 'Path to the root of the controlrepo'
19
+ optional nil, :environmentpath, 'Value of environmentpath from puppet.conf'
20
+ optional nil, :puppetfile, 'Location of the Puppetfile'
21
+ optional nil, :environment_conf, 'Location of environment.con'
22
+ optional nil, :facts_dir, 'Directory in which to find factsets'
23
+ optional nil, :spec_dir, 'Directory in which to find spec tests and config'
24
+ optional nil, :facts_files, 'List of factset files to use (Overrides --facts_dir)'
25
+ optional nil, :nodeset_file, 'YAML file containing node definitions'
26
+ optional nil, :tempdir, 'Temp directory to use, defaults to .controlrepo'
27
+ optional nil, :manifest, 'Path fo find manifests'
28
+ optional nil, :controlrepo_yaml, 'Path of controlrepo.yaml'
29
+
30
+ run do |opts, args, cmd|
31
+ puts cmd.help(:verbose => opts[:verbose])
32
+ exit 0
33
+ end
34
+ end
35
+ end
36
+
37
+ # Add the help
38
+ Onceover::CLI.command.add_command(Cri::Command.new_basic_help)
39
+ end
40
+ end
41
+
42
+ # Add all of the other CLI components
43
+ require 'onceover/cli/show'
44
+ require 'onceover/cli/run'
45
+ require 'onceover/cli/init'
46
+ require 'onceover/cli/update'
@@ -0,0 +1,31 @@
1
+ require 'cri'
2
+ require 'onceover/controlrepo'
3
+ require 'onceover/cli'
4
+ require 'onceover/runner'
5
+ require 'onceover/testconfig'
6
+ require 'onceover/logger'
7
+
8
+ class Onceover
9
+ class CLI
10
+ class Init
11
+ def self.command
12
+ @cmd ||= Cri::Command.define do
13
+ name 'init'
14
+ usage 'init'
15
+ summary 'Sets up a controlrepo for testing from scratch'
16
+ description <<-DESCRIPTION
17
+ This will generate all of the config files required for the onceover
18
+ tool to work.
19
+ DESCRIPTION
20
+
21
+ run do |opts, args, cmd|
22
+ Onceover::Controlrepo.init(Onceover::Controlrepo.new(opts))
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ # Register itself
31
+ Onceover::CLI.command.add_command(Onceover::CLI::Init.command)
@@ -0,0 +1,72 @@
1
+ require 'cri'
2
+ require 'onceover/controlrepo'
3
+ require 'onceover/cli'
4
+ require 'onceover/runner'
5
+ require 'onceover/testconfig'
6
+ require 'onceover/logger'
7
+
8
+ class Onceover
9
+ class CLI
10
+ class Run
11
+ def self.command
12
+ @cmd ||= Cri::Command.define do
13
+ name 'run'
14
+ usage 'run [spec|acceptance]'
15
+ summary 'Runs either the spec or acceptance tests'
16
+ description <<-DESCRIPTION
17
+ This will run the full set of spec or acceptance tests.
18
+ This includes deploying using r10k and running all custom tests.
19
+ DESCRIPTION
20
+
21
+ optional :t, :tags, 'A list of tags. Only tests with these tags will be run'
22
+ optional :c, :classes, 'A list of classes. Only tests with these classes will be run'
23
+ optional :n, :nodes, 'A list of nodes. Only tests with these nodes will be run'
24
+
25
+ run do |opts, args, cmd|
26
+ puts cmd.help(:verbose => opts[:verbose])
27
+ exit 0
28
+ end
29
+ end
30
+ end
31
+
32
+ class Spec
33
+ def self.command
34
+ @cmd ||= Cri::Command.define do
35
+ name 'spec'
36
+ usage 'spec'
37
+ summary 'Runs spec tests'
38
+
39
+ run do |opts, args, cmd|
40
+ repo = Onceover::Controlrepo.new(opts)
41
+ runner = Onceover::Runner.new(repo,Onceover::TestConfig.new(repo.controlrepo_yaml,opts),:spec)
42
+ runner.prepare!
43
+ runner.run_spec!
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ class Acceptance
50
+ def self.command
51
+ @cmd ||= Cri::Command.define do
52
+ name 'acceptance'
53
+ usage 'acceptance'
54
+ summary 'Runs acceptance tests'
55
+
56
+ run do |opts, args, cmd|
57
+ repo = Onceover::Controlrepo.new(opts)
58
+ runner = Onceover::Runner.new(repo,Onceover::TestConfig.new(repo.controlrepo_yaml,opts),:acceptance)
59
+ runner.prepare!
60
+ runner.run_acceptance!
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ # Register itself
70
+ Onceover::CLI.command.add_command(Onceover::CLI::Run.command)
71
+ Onceover::CLI::Run.command.add_command(Onceover::CLI::Run::Spec.command)
72
+ Onceover::CLI::Run.command.add_command(Onceover::CLI::Run::Acceptance.command)
@@ -0,0 +1,74 @@
1
+ require 'cri'
2
+ require 'onceover/controlrepo'
3
+ require 'onceover/cli'
4
+ require 'onceover/logger'
5
+
6
+ class Onceover
7
+ class CLI
8
+ class Show
9
+ def self.command
10
+ @cmd ||= Cri::Command.define do
11
+ name 'show'
12
+ usage 'show [controlrepo|puppetfile]'
13
+ summary 'Shows the current state things'
14
+ description <<-DESCRIPTION
15
+ Shows the state of either the controlrepo or the Puppetfile
16
+ DESCRIPTION
17
+
18
+ run do |opts, args, cmd|
19
+ # Print out the description
20
+ puts cmd.help(:verbose => opts[:verbose])
21
+ exit 0
22
+ end
23
+ end
24
+ end
25
+
26
+ class Repo
27
+ def self.command
28
+ @cmd ||= Cri::Command.define do
29
+ name 'repo'
30
+ usage 'repo [options]'
31
+ summary 'Shows the current state of the Controlrepo'
32
+ description <<-DESCRIPTION
33
+ Shows the state of the repo as the tool sees it.
34
+ Useful for debugging.
35
+ DESCRIPTION
36
+
37
+ run do |opts, args, cmd|
38
+ # Print out the description
39
+ puts Onceover::Controlrepo.new(opts).to_s
40
+ exit 0
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ class Puppetfile
47
+ def self.command
48
+ @cmd ||= Cri::Command.define do
49
+ name 'puppetfile'
50
+ usage 'puppetfile [options]'
51
+ summary 'Shows the current state of the puppetfile'
52
+ description <<-DESCRIPTION
53
+ Shows the state of the puppetfile including current versions and
54
+ laetst versions of each module. Great for checking for updates.
55
+ To update all modules run `onceover update puppetfile`. (Hint: once
56
+ you have done the update, run the tests to make sure nothing breaks.)
57
+ DESCRIPTION
58
+
59
+ run do |opts, args, cmd|
60
+ # Print out the description
61
+ Onceover::Controlrepo.new(opts).print_puppetfile_table
62
+ exit 0
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ # Register itself
72
+ Onceover::CLI.command.add_command(Onceover::CLI::Show.command)
73
+ Onceover::CLI::Show.command.add_command(Onceover::CLI::Show::Repo.command)
74
+ Onceover::CLI::Show.command.add_command(Onceover::CLI::Show::Puppetfile.command)
@@ -0,0 +1,48 @@
1
+ require 'cri'
2
+ require 'onceover/controlrepo'
3
+ require 'onceover/cli'
4
+ require 'onceover/logger'
5
+
6
+ class Onceover
7
+ class CLI
8
+ class Update
9
+ def self.command
10
+ @cmd ||= Cri::Command.define do
11
+ name 'update'
12
+ usage 'update puppetfile'
13
+ summary 'Updates stuff, currently only the Puppetfile'
14
+
15
+ run do |opts, args, cmd|
16
+ # Print out the description
17
+ puts cmd.help(:verbose => opts[:verbose])
18
+ exit 0
19
+ end
20
+ end
21
+ end
22
+
23
+ class Puppetfile
24
+ def self.command
25
+ @cmd ||= Cri::Command.define do
26
+ name 'puppetfile'
27
+ usage 'puppetfile'
28
+ summary 'Update all modules in the Puppetfile'
29
+ description <<-DESCRIPTION
30
+ Updates all modules to their latest version and writes that
31
+ file back onto the system over the original Puppetfile.
32
+ DESCRIPTION
33
+
34
+ run do |opts, args, cmd|
35
+ # Print out the description
36
+ Onceover::Controlrepo.new(opts).update_puppetfile
37
+ exit 0
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ # Register itself
47
+ Onceover::CLI.command.add_command(Onceover::CLI::Update.command)
48
+ Onceover::CLI::Update.command.add_command(Onceover::CLI::Update::Puppetfile.command)
@@ -0,0 +1,527 @@
1
+ require 'r10k/puppetfile'
2
+ require 'erb'
3
+ require 'json'
4
+ require 'yaml'
5
+ require 'find'
6
+ require 'pathname'
7
+ require 'onceover/beaker'
8
+ require 'onceover/logger'
9
+ include Onceover::Logger
10
+
11
+ begin
12
+ require 'pry'
13
+ rescue LoadError
14
+ # We don't care if i'ts not here, this is just used for
15
+ # debugging sometimes
16
+ end
17
+
18
+ class Onceover
19
+ class Controlrepo
20
+ attr_accessor :root
21
+ attr_accessor :puppetfile
22
+ attr_accessor :facts_files
23
+ attr_accessor :environmentpath
24
+ attr_accessor :role_regex
25
+ attr_accessor :profile_regex
26
+ attr_accessor :spec_dir
27
+ attr_accessor :temp_modulepath
28
+ attr_accessor :nodeset_file
29
+ attr_accessor :manifest
30
+ attr_accessor :tempdir
31
+ attr_accessor :controlrepo_yaml
32
+ attr_accessor :opts
33
+
34
+ # Create methods on self so that we can access these basic things without
35
+ # having to actually instantiate the class, I'm debating how much stuff
36
+ # I should be putting in here, we don't reeeally need to instantiate the
37
+ # object unless we want to modify it's parameters, so maybe everything.
38
+ # We shall see...
39
+ #
40
+ # And yeah I know this makes little sense, but it will look nicer to type, promise
41
+ #
42
+ # Also it's probably pretty memory hungry, but let's be honest, how many
43
+ # times would be be calling this? If we call it over and over you can just
44
+ # instantiate it anyway
45
+ def self.root
46
+ Onceover::Controlrepo.new.root
47
+ end
48
+
49
+ def self.puppetfile
50
+ Onceover::Controlrepo.new.puppetfile
51
+ end
52
+
53
+ def self.facts_files
54
+ Onceover::Controlrepo.new.facts_files
55
+ end
56
+
57
+ def self.classes
58
+ Onceover::Controlrepo.new.classes
59
+ end
60
+
61
+ def self.roles
62
+ Onceover::Controlrepo.new.roles
63
+ end
64
+
65
+ def self.profiles
66
+ Onceover::Controlrepo.new.profiles
67
+ end
68
+
69
+ def self.config
70
+ Onceover::Controlrepo.new.config
71
+ end
72
+
73
+ def self.facts(filter = nil)
74
+ Onceover::Controlrepo.new.facts(filter)
75
+ end
76
+
77
+ def self.hiera_config_file
78
+ Onceover::Controlrepo.new.hiera_config_file
79
+ end
80
+ #
81
+ # End class methods
82
+ #
83
+
84
+ def initialize(opts = {})
85
+ # When we initialize the object it is going to set some instance vars
86
+ @root = opts[:path] || Dir.pwd
87
+ @environmentpath = opts[:environmentpath] || 'etc/puppetlabs/code/environments'
88
+ @puppetfile = opts[:puppetfile] || File.expand_path('./Puppetfile',@root)
89
+ @environment_conf = opts[:environment_conf] || File.expand_path('./environment.conf',@root)
90
+ @facts_dir = opts[:facts_dir] || File.expand_path('./spec/factsets',@root)
91
+ @spec_dir = opts[:spec_dir] || File.expand_path('./spec',@root)
92
+ @facts_files = opts[:facts_files] || [Dir["#{@facts_dir}/*.json"],Dir["#{File.expand_path('../../../factsets',__FILE__)}/*.json"]].flatten
93
+ @nodeset_file = opts[:nodeset_file] || File.expand_path('./spec/acceptance/nodesets/onceover-nodes.yml',@root)
94
+ @role_regex = /role[s]?:{2}/
95
+ @profile_regex = /profile[s]?:{2}/
96
+ @tempdir = opts[:tempdir] || ENV['CONTROLREPO_temp'] || File.absolute_path('./.onceover')
97
+ $temp_modulepath = nil
98
+ @manifest = opts[:manifest] || config['manifest'] ? File.expand_path(config['manifest'],@root) : nil
99
+ @controlrepo_yaml = opts[:controlrepo_yaml] || "#{@spec_dir}/onceover.yaml"
100
+ @opts = opts
101
+ logger.level = :debug if @opts[:debug]
102
+ end
103
+
104
+ def to_s
105
+ require 'colored'
106
+
107
+ <<-END.gsub(/^\s{4}/,'')
108
+ #{'puppetfile'.green} #{@puppetfile}
109
+ #{'environment_conf'.green} #{@environment_conf}
110
+ #{'facts_dir'.green} #{@facts_dir}
111
+ #{'spec_dir'.green} #{@spec_dir}
112
+ #{'facts_files'.green} #{@facts_files}
113
+ #{'nodeset_file'.green} #{@nodeset_file}
114
+ #{'roles'.green} #{roles}
115
+ #{'profiles'.green} #{profiles}
116
+ #{'controlrepo.yaml'.green} #{@controlrepo_yaml}
117
+ END
118
+ end
119
+
120
+ def roles
121
+ classes.keep_if { |c| c =~ @role_regex }
122
+ end
123
+
124
+ def profiles
125
+ classes.keep_if { |c| c =~ @profile_regex }
126
+ end
127
+
128
+ def classes
129
+ # Get all of the possible places for puppet code and look for classes
130
+ code_dirs = self.config['modulepath']
131
+ # Remove interpolated references
132
+ code_dirs.delete_if { |dir| dir[0] == '$'}
133
+
134
+ # Make sure that the paths are relative to the controlrepo root
135
+ code_dirs.map! do |dir|
136
+ File.expand_path(dir,@root)
137
+ end
138
+
139
+ # Get all the classes from all of the manifests
140
+ classes = []
141
+ code_dirs.each do |dir|
142
+ classes << get_classes(dir)
143
+ end
144
+ classes.flatten
145
+ end
146
+
147
+ def facts(filter = nil)
148
+ # Returns an array facts hashes
149
+ all_facts = []
150
+ logger.debug "Reading factsets"
151
+ @facts_files.each do |file|
152
+ all_facts << read_facts(file)['values']
153
+ end
154
+ if filter
155
+ # Allow us to pass a hash of facts to filter by
156
+ raise "Filter param must be a hash" unless filter.is_a?(Hash)
157
+ all_facts.keep_if do |hash|
158
+ matches = []
159
+ filter.each do |filter_fact,value|
160
+ matches << keypair_is_in_hash(hash,filter_fact,value)
161
+ end
162
+ if matches.include? false
163
+ false
164
+ else
165
+ true
166
+ end
167
+ end
168
+ end
169
+ return all_facts
170
+ end
171
+
172
+ def print_puppetfile_table
173
+ require 'table_print'
174
+ require 'versionomy'
175
+ require 'colored'
176
+ require 'r10k/puppetfile'
177
+
178
+ # Load up the Puppetfile using R10k
179
+ logger.debug "Reading puppetfile from #{@root}"
180
+ puppetfile = R10K::Puppetfile.new(@root)
181
+ logger.debug "Loading modules from Puppetfile"
182
+ puppetfile.load!
183
+
184
+ output_array = []
185
+ puppetfile.modules.each do |mod|
186
+ return_hash = {}
187
+ logger.debug "Loading data for #{mod.full_name}"
188
+ return_hash[:full_name] = mod.full_name
189
+ if mod.is_a?(R10K::Module::Forge)
190
+ return_hash[:current_version] = mod.expected_version
191
+ return_hash[:latest_version] = mod.v3_module.current_release.version
192
+ current = Versionomy.parse(return_hash[:current_version])
193
+ latest = Versionomy.parse(return_hash[:latest_version])
194
+ if current.major < latest.major
195
+ return_hash[:out_of_date] = "Major".red
196
+ elsif current.minor < latest.minor
197
+ return_hash[:out_of_date] = "Minor".yellow
198
+ elsif current.tiny < latest.tiny
199
+ return_hash[:out_of_date] = "Tiny".green
200
+ else
201
+ return_hash[:out_of_date] = "No".green
202
+ end
203
+ else
204
+ return_hash[:current_version] = "N/A"
205
+ return_hash[:latest_version] = "N/A"
206
+ return_hash[:out_of_date] = "N/A"
207
+ end
208
+ output_array << return_hash
209
+ end
210
+
211
+ tp output_array, \
212
+ {:full_name => {:display_name => "Full Name"}}, \
213
+ {:current_version => {:display_name => "Current Version"}}, \
214
+ {:latest_version => {:display_name => "Latest Version"}}, \
215
+ {:out_of_date => {:display_name => "Out of Date?"}}
216
+ end
217
+
218
+ def update_puppetfile
219
+ require 'r10k/puppetfile'
220
+
221
+ # Read in the Puppetfile as a string and as an object
222
+ puppetfile_string = File.read(@puppetfile).split("\n")
223
+ puppetfile = R10K::Puppetfile.new(@root)
224
+ puppetfile.load!
225
+
226
+ # TODO: Make sure we can deal with :latest
227
+
228
+ puppetfile.modules.keep_if {|m| m.is_a?(R10K::Module::Forge)}
229
+ puppetfile.modules.each do |mod|
230
+ line_index = puppetfile_string.index {|l| l =~ /^\s*[^#]*#{mod.owner}[\/-]#{mod.name}/}
231
+ logger.debug "Getting latest version of #{mod.full_name}"
232
+ puppetfile_string[line_index].gsub!(mod.expected_version,mod.v3_module.current_release.version)
233
+ end
234
+ File.open(@puppetfile, 'w') {|f| f.write(puppetfile_string.join("\n")) }
235
+ puts "#{'changed'.yellow} #{@puppetfile}"
236
+ end
237
+
238
+ def fixtures
239
+ # Load up the Puppetfile using R10k
240
+ puppetfile = R10K::Puppetfile.new(@root)
241
+ modules = puppetfile.load
242
+
243
+ # Iterate over everything and seperate it out for the sake of readability
244
+ symlinks = []
245
+ forge_modules = []
246
+ repositories = []
247
+
248
+ modules.each do |mod|
249
+ logger.debug "Converting #{mod.to_s} to .fixtures.yml format"
250
+ # This logic could probably be cleaned up. A lot.
251
+ if mod.is_a? R10K::Module::Forge
252
+ if mod.expected_version.is_a?(Hash)
253
+ # Set it up as a symlink, because we are using local files in the Puppetfile
254
+ symlinks << {
255
+ 'name' => mod.name,
256
+ 'dir' => mod.expected_version[:path]
257
+ }
258
+ elsif mod.expected_version.is_a?(String)
259
+ # Set it up as a normal firge module
260
+ forge_modules << {
261
+ 'name' => mod.name,
262
+ 'repo' => mod.title,
263
+ 'ref' => mod.expected_version
264
+ }
265
+ end
266
+ elsif mod.is_a? R10K::Module::Git
267
+ # Set it up as a git repo
268
+ repositories << {
269
+ 'name' => mod.name,
270
+ # I know I shouldn't be doing this, but trust me, there are no methods
271
+ # anywhere that expose this value, I looked.
272
+ 'repo' => mod.instance_variable_get(:@remote),
273
+ 'ref' => mod.version
274
+ }
275
+ end
276
+ end
277
+
278
+ # also add synlinks for anything that is in environment.conf
279
+ code_dirs = self.config['modulepath']
280
+ code_dirs.delete_if { |dir| dir[0] == '$'}
281
+ code_dirs.each do |dir|
282
+ # We need to traverse down into these directories and create a symlink for each
283
+ # module we find because fixtures.yml is expecting the module's root not the
284
+ # root of modulepath
285
+ Dir["#{dir}/*"].each do |mod|
286
+ symlinks << {
287
+ 'name' => File.basename(mod),
288
+ 'dir' => Pathname.new(File.expand_path(mod)).relative_path_from(Pathname.new(@root))#File.expand_path(mod)
289
+ }
290
+ end
291
+ end
292
+
293
+ # Use an ERB template to write the files
294
+ Onceover::Controlrepo.evaluate_template('.fixtures.yml.erb',binding)
295
+ end
296
+
297
+ def hiera_config_file
298
+ # try to find the hiera.iyaml file
299
+ hiera_config_file = File.expand_path('./hiera.yaml',@spec_dir) if File.exist?(File.expand_path('./hiera.yaml',@spec_dir))
300
+ hiera_config_file = File.expand_path('./hiera.yaml',@root) if File.exist?(File.expand_path('./hiera.yaml',@root))
301
+ hiera_config_file
302
+ end
303
+
304
+ def hiera_config
305
+ begin
306
+ YAML.load_file(hiera_config_file)
307
+ rescue TypeError
308
+ puts "WARNING: Could not find hiera config file, continuing"
309
+ nil
310
+ end
311
+ end
312
+
313
+ def hiera_config=(data)
314
+ File.write(hiera_config_file,data.to_yaml)
315
+ end
316
+
317
+ def hiera_data
318
+ # This is going to try to find your hiera data directory, if you have named it something
319
+ # unexpected it won't work
320
+ possibe_datadirs = Dir["#{@root}/*/"]
321
+ possibe_datadirs.keep_if { |dir| dir =~ /hiera(?:.*data)?/i }
322
+ raise "There were too many directories that looked like hiera data: #{possibe_datadirs}" if possibe_datadirs.count > 1
323
+ File.expand_path(possibe_datadirs[0])
324
+ end
325
+
326
+ def config
327
+ # Parse the file
328
+ logger.debug "Reading #{@environment_conf}"
329
+ env_conf = File.read(@environment_conf)
330
+ env_conf = env_conf.split("\n")
331
+
332
+ # Delete commented out lines
333
+ env_conf.delete_if { |l| l =~ /^\s*#/}
334
+
335
+ # Map the lines into a hash
336
+ environment_config = {}
337
+ env_conf.each do |line|
338
+ environment_config.merge!(Hash[*line.split('=').map { |s| s.strip}])
339
+ end
340
+
341
+ # Finally, split the modulepath values and return
342
+ begin
343
+ environment_config['modulepath'] = environment_config['modulepath'].split(':')
344
+ rescue
345
+ raise "modulepath was not found in environment.conf, don't know where to look for roles & profiles"
346
+ end
347
+ return environment_config
348
+ end
349
+
350
+ def r10k_config_file
351
+ r10k_config_file = File.expand_path('./r10k.yaml',@spec_dir) if File.exist?(File.expand_path('./r10k.yaml',@spec_dir))
352
+ r10k_config_file = File.expand_path('./r10k.yaml',@root) if File.exist?(File.expand_path('./r10k.yaml',@root))
353
+ r10k_config_file
354
+ end
355
+
356
+ def r10k_config
357
+ YAML.load_file(r10k_config_file)
358
+ end
359
+
360
+ def r10k_config=(data)
361
+ File.write(r10k_config_file,data.to_yaml)
362
+ end
363
+
364
+ def temp_manifest
365
+ config['manifest'] ? File.expand_path(config['manifest'],@tempdir) : nil
366
+ end
367
+
368
+ def self.init(repo)
369
+ # This code will initialise a controlrepo with all of the config
370
+ # that it needs
371
+ require 'pathname'
372
+ require 'colored'
373
+
374
+ Onceover::Controlrepo.init_write_file(generate_controlrepo_yaml(repo),repo.controlrepo_yaml)
375
+ Onceover::Controlrepo.init_write_file(generate_nodesets(repo),repo.nodeset_file)
376
+ Onceover::Controlrepo.init_write_file(Onceover::Controlrepo.evaluate_template('pre_conditions_README.md.erb',binding),File.expand_path('./pre_conditions/README.md',repo.spec_dir))
377
+ Onceover::Controlrepo.init_write_file(Onceover::Controlrepo.evaluate_template('factsets_README.md.erb',binding),File.expand_path('./factsets/README.md',repo.spec_dir))
378
+
379
+ # Add .controlrepo to Gitignore
380
+ gitignore_path = File.expand_path('.gitignore',repo.root)
381
+ if File.exists? gitignore_path
382
+ gitignore_content = (File.open(gitignore_path,'r') {|f| f.read }).split("\n")
383
+ message = "#{'changed'.green}"
384
+ else
385
+ message = "#{'created'.green}"
386
+ gitignore_content = []
387
+ end
388
+
389
+ unless gitignore_content.include?(".controlrepo")
390
+ gitignore_content << ".controlrepo\n"
391
+ File.open(gitignore_path,'w') {|f| f.write(gitignore_content.join("\n")) }
392
+ puts "#{message} #{Pathname.new(gitignore_path).relative_path_from(Pathname.new(Dir.pwd)).to_s}"
393
+ end
394
+ end
395
+
396
+ def self.generate_controlrepo_yaml(repo)
397
+ # This will return a controlrepo.yaml that can be written to a file
398
+ Onceover::Controlrepo.evaluate_template('controlrepo.yaml.erb',binding)
399
+ end
400
+
401
+ def self.generate_nodesets(repo)
402
+ require 'onceover/beaker'
403
+ require 'net/http'
404
+ require 'json'
405
+
406
+ hosts_hash = {}
407
+
408
+ repo.facts.each do |fact_set|
409
+ node_name = File.basename(repo.facts_files[repo.facts.index(fact_set)],'.json')
410
+ boxname = Onceover::Beaker.facts_to_vagrant_box(fact_set)
411
+ platform = Onceover::Beaker.facts_to_platform(fact_set)
412
+
413
+ logger.debug "Querying hashicorp API for Vagrant box that matches #{boxname}"
414
+ response = Net::HTTP.get(URI.parse("https://atlas.hashicorp.com/api/v1/box/#{boxname}"))
415
+ url = 'URL goes here'
416
+
417
+ if response =~ /Not Found/i
418
+ comment_out = true
419
+ else
420
+ comment_out = false
421
+ box_info = JSON.parse(response)
422
+ box_info['current_version']['providers'].each do |provider|
423
+ if provider['name'] == 'virtualbox'
424
+ url = provider['original_url']
425
+ end
426
+ end
427
+ end
428
+
429
+ # Add the resulting info to the hosts hash. This is what the
430
+ # template will output
431
+ hosts_hash[node_name] = {
432
+ :platform => platform,
433
+ :boxname => boxname,
434
+ :url => url,
435
+ :comment_out => comment_out
436
+ }
437
+ end
438
+
439
+ # Use an ERB template
440
+ Onceover::Controlrepo.evaluate_template('nodeset.yaml.erb',binding)
441
+ end
442
+
443
+ def self.create_dirs_and_log(dir)
444
+ Pathname.new(dir).descend do |folder|
445
+ unless folder.directory?
446
+ FileUtils.mkdir(folder)
447
+ puts "#{'created'.green} #{folder.relative_path_from(Pathname.new(Dir.pwd)).to_s}"
448
+ end
449
+ end
450
+ end
451
+
452
+ def self.evaluate_template(template_name,bind)
453
+ logger.debug "Evaluating template #{template_name}"
454
+ template_dir = File.expand_path('../../templates',File.dirname(__FILE__))
455
+ template = File.read(File.expand_path("./#{template_name}",template_dir))
456
+ ERB.new(template, nil, '-').result(bind)
457
+ end
458
+
459
+ def self.init_write_file(contents,out_file)
460
+ Onceover::Controlrepo.create_dirs_and_log(File.dirname(out_file))
461
+ if File.exists?(out_file)
462
+ puts "#{'skipped'.yellow} #{Pathname.new(out_file).relative_path_from(Pathname.new(Dir.pwd)).to_s} #{'(exists)'.yellow}"
463
+ else
464
+ File.open(out_file,'w') {|f| f.write(contents)}
465
+ puts "#{'created'.green} #{Pathname.new(out_file).relative_path_from(Pathname.new(Dir.pwd)).to_s}"
466
+ end
467
+ end
468
+
469
+ private
470
+
471
+ def read_facts(facts_file)
472
+ file = File.read(facts_file)
473
+ begin
474
+ result = JSON.parse(file)
475
+ rescue JSON::ParserError
476
+ raise "Could not parse the JSON file, check that it is valid JSON and that the encoding is correct"
477
+ end
478
+ result
479
+ end
480
+
481
+ def keypair_is_in_hash(first_hash, key, value)
482
+ matches = []
483
+ if first_hash.has_key?(key)
484
+ if value.is_a?(Hash)
485
+ value.each do |k,v|
486
+ matches << keypair_is_in_hash(first_hash[key],k,v)
487
+ end
488
+ else
489
+ if first_hash[key] == value
490
+ matches << true
491
+ else
492
+ matches << false
493
+ end
494
+ end
495
+ else
496
+ matches << false
497
+ end
498
+ if matches.include? false
499
+ false
500
+ else
501
+ true
502
+ end
503
+ end
504
+
505
+ def get_classes(dir)
506
+ classes = []
507
+ # Recurse over all the pp files under the dir we are given
508
+ logger.debug "Searching puppet code for roles and profiles"
509
+ Dir["#{dir}/**/*.pp"].each do |manifest|
510
+ classname = find_classname(manifest)
511
+ # Add it to the array as long as it is not nil
512
+ classes << classname if classname
513
+ end
514
+ classes
515
+ end
516
+
517
+ def find_classname(filename)
518
+ file = File.new(filename, "r")
519
+ while (line = file.gets)
520
+ if line =~ /^class (\w+(?:::\w+)*)/
521
+ return $1
522
+ end
523
+ end
524
+ return nil
525
+ end
526
+ end
527
+ end