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,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