controlrepo 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 074cfbffbc367cb76428c30a900b1bc1b2dc7525
4
+ data.tar.gz: f2901badd0097e028d2cc835ae0fe5c5eae7bc8a
5
+ SHA512:
6
+ metadata.gz: ceb858045e2bbc73fab0873d65a617338b980c1d2ecfd06aa37dcb60be0c821ec91f1671fb19a1155438735cce20cace0bf2a9206097f63f799879487e93b995
7
+ data.tar.gz: 281451f8f1c470f8ff9a31c8c57f1f2a4f51566b26bf72833a7ce731fac1647575a0cb608daab0671fe2f2d00f6c47c61fd3fdd80dee0a9acf82f51d1dac08ae
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ .DS_Store
data/README.md ADDED
@@ -0,0 +1,274 @@
1
+ # Controlrepo Toolset
2
+
3
+ ## Overview
4
+
5
+ This gem gives you a bunch of tools to use for testing and generally managing Puppet controlrepos. The main purpose of this project is to provide a set of tools to help smooth out the process of setting up and running both spec and acceptance tests for a controlrepo. Due to the fact that controlrepos are fairly standardise in nature it seemed ridiculous that you would need to set up the same testing framework that we would normally use within a module for a controlrepo. This is because at this level we are normally just running very basic tests that test a lot of code. It would also mean that we would need to essentially duplicated our `Puppetfile` into a `.fixtures.yml` file, along with a few other things.
6
+
7
+ This toolset has two distinct ways of being used, easy mode and hard mode.
8
+
9
+ ### Easy Mode
10
+
11
+ The object of *easy mode* is to allow people to run simple `it { should compile }` acceptance tests without needing to set up any of the extra stuff required by the rspec-puppet testing framework.
12
+
13
+ `rake tasks go here once they are done`
14
+
15
+ A stretch goal is to also include acceptance testing, allowing people to spin up boxes for each role they have and test them before merging code into development environments or production. At the moment we can't do this, hold tight.
16
+
17
+ #### Easy mode config
18
+
19
+ The whole idea of easy mode is that we should just be able to write down which classes we want to test on which machines and this tool should be able to do the rest. This all has to be set up somewhere, this is **spec/controlrepo.yaml** which looks something like this:
20
+
21
+ ```yaml
22
+ classes:
23
+ - 'roles::backend_dbserver'
24
+ - 'roles::frontend_webserver'
25
+ - 'roles::load_balancer'
26
+ - 'roles::syd_f5_load_balancer'
27
+ - 'roles::windows_server'
28
+
29
+ nodes:
30
+ - centos6a
31
+ - centos7b
32
+ - server2008r2a
33
+ - ubuntu1404a
34
+
35
+ groups:
36
+ windows_roles:
37
+ - 'roles::windows_server'
38
+ - 'roles::backend_dbserver'
39
+ centos_servers:
40
+ - centos6a
41
+ - centos7b
42
+
43
+ test_matrix:
44
+ server2008r2a: windows_roles
45
+ ubuntu1404a: 'roles::frontend_webserver'
46
+ centos_servers:
47
+ include: all_classes
48
+ exclude: windows_roles
49
+ ```
50
+
51
+ It consists of the following sections:
52
+
53
+ ##### Classes:
54
+
55
+ This is where we list all of the classes that we want to test, normally this will just be a list of roles. Note that these classes must *actually exist* for reasons that should be obvious.
56
+
57
+ ##### Nodes:
58
+
59
+ Each node in the list refers one of two things depending on weather we are running **spec** or **acceptance** tests. If we are running **spec** tests each node refers to the name of a [fact set](#fact-sets) because this will be the set of facts that the `it { should compile }` test will be run against. If we are are running **acceptance** tests then each node will refer to a *nodeset* file which we can generate (or at least try to) using the `generate_nodesets` rake task. For acceptance testing the nodeset file will tell us how to spin up the VMs for each machine.
60
+
61
+ ##### Groups:
62
+
63
+ Groups are used to save us a bit of time and code (if you can call yaml that). Unsurprisingly a group is a way to bundle either classes or nodes into a group that we can refer to but it's name instead of repeating ourselves a whole bunch. There are 2 **default groups:**
64
+
65
+ - all_nodes
66
+ - all_classes
67
+
68
+ You can guess what they are for I hope.
69
+
70
+ *Note that groups CANNOT contain a mix of classes and nodes, only one or the other.*
71
+
72
+ ##### Test Matrix:
73
+
74
+ This is the section of th config file that makes the magic happen. In the test matrix we choose on which nodes we will tests which classes. You can use groups anywhere here as you can see in the example above. We also have the option of using *include* and *exclude* which will be useful if you have a lot of groups.
75
+
76
+ For example if we want to test all our non-windows roles on all of our linux boxes we can do something like this:
77
+
78
+ ```yaml
79
+ test_matrix:
80
+ linux_nodes:
81
+ include: 'all_classes'
82
+ exclude: 'windows_roles'
83
+ ```
84
+
85
+ This is assuming that you have all of your linux nodes in the `linux_nodes` group and all of your Windows roles in the `windows_roles` group.
86
+
87
+ When setting up your tests matrix don't worry too much about using groups that will cause duplicate combinations of `node -> class` pairs. The rake tasks run deduplication before running any of the tests to make sure that we are not wasting time. This happens at runtime and does not affect the file or anything.
88
+
89
+ #### Lets go!
90
+
91
+ Now to run the spec tests just do a `bundler exec rake controlrepo_spec`
92
+
93
+
94
+ ### Hard mode
95
+
96
+ The point of *hard mode* is to give people who are familiar with RSpec testing with puppet a set of useful tools that they can mix into their tests to save some hassle. We also want to help in getting your tests set up by automatically generating `.fixtures.yml` and nodesets.
97
+
98
+ ## Fact sets
99
+
100
+ This gem introduces the concept of fact sets. Instead of manually specifying facts in each rspec test we can just dump the actual facts from the actual machines in our environment into a folder then test against them. To do this we first need to dump the facts into a json file:
101
+
102
+ `puppet facts > server01.json`
103
+
104
+ Then grab this file and put it in `spec/facts` inside your control repo. It doesn't matter what you name these files, as long as they are in that directory and they end with `json` we will be able to find them. Fact sets are also heavily used by **easy mode**.
105
+
106
+ **That's it!**
107
+
108
+ Now we can access all of these fact sets using `Controlrepo.facts`. Normally it would be implemented something like this:
109
+
110
+ ```ruby
111
+ Controlrepo.facts.each do |facts|
112
+ context "on #{facts['fqdn']}" do
113
+ let(:facts) { facts }
114
+ it { should compile }
115
+ end
116
+ end
117
+ ```
118
+
119
+
120
+ ## Rake tasks
121
+
122
+ I have included a couple of little rake tasks to help get you started with testing your control repos. Set them up by adding this to your `Rakefile`
123
+
124
+ ```ruby
125
+ require 'controlrepo/rake_tasks'
126
+ ```
127
+
128
+ The tasks are as follows:
129
+
130
+ ### generate_fixtures
131
+
132
+ `bundle exec rake generate_fixtures`
133
+
134
+ This task will go though your Puppetfile, grab all of the modules in there and convert them into a `.fixtures.yml` file. It will also take the `environment.conf` file into account, check to see if you have any relative pathed directories and also include them into the `.fixtures.yml` as symlinks. e.g. If your files look like this:
135
+
136
+ **Puppetfile**
137
+ ```ruby
138
+ forge "http://forgeapi.puppetlabs.com"
139
+
140
+ # Modules from the Puppet Forge
141
+ mod "puppetlabs/stdlib", "4.6.0"
142
+ mod "puppetlabs/apache", "1.5.0"
143
+ ```
144
+
145
+ **environment.conf**
146
+ ```ini
147
+ modulepath = site:modules:$basemodulepath
148
+ environment_timeout = 0
149
+ ```
150
+
151
+ Then the `.fixtures.yml` file that this rake task will create will look like this:
152
+
153
+ ```yaml
154
+ ---
155
+ fixtures:
156
+ symlinks:
157
+ profiles: site/profiles
158
+ roles: site/roles
159
+ forge_modules:
160
+ stdlib:
161
+ repo: puppetlabs/stdlib
162
+ ref: 4.6.0
163
+ apache:
164
+ repo: puppetlabs/apache
165
+ ref: 1.5.0
166
+ ```
167
+
168
+ Notice that the symlinks are not the ones that we provided in `environment.conf`? This is because the rake task will go into each of directories, find the modules and create a symlink for each of them (This is what rspec expects).
169
+
170
+ ### generate_nodesets
171
+
172
+ `bundle exec rake generate_nodesets`
173
+
174
+ This task will generate nodeset file required by beaker, based on the fact sets that exist in the repository. If you have any fact sets for which puppetlabs has a compatible vagrant box (i.e. centos, debian, ubuntu) it will detect the version specifics and set up the nodeset file, complete with box URL. If it doesn't know how to deal with a fact set it will output a boilerplate nodeset file that will need to be altered before it can be used.
175
+
176
+ ### hiera_setup
177
+
178
+ `bundle exec rake hiera_setup`
179
+
180
+ Automatically modifies your hiera.yaml to point at the hieradata relative to it's position.
181
+
182
+ This rake task will look for a hiera.yaml file (Using the same method we use for [this](#using-hiera-data)). It will then look for a hieradata directory in the root for your control repo (needs to match [this](http://rubular.com/?regex=%2Fhiera%28%3F%3A.%2Adata%29%3F%2Fi)). It will then modify the datadirs of any backends it finds in `hiera.yaml` to point at these directories.
183
+
184
+ ## Running the tests (Hard mode)
185
+
186
+ This gem allows us to easily and dynamically test all of the roles and profiles in our environment against fact sets from all of the nodes to which they will be applied. All we need to do is create a spec test that calls out to the `Controlrepo` ruby class:
187
+
188
+ ```ruby
189
+ require 'spec_helper'
190
+ require 'controlrepo'
191
+
192
+ Controlrepo.roles.each do |role|
193
+ describe role do
194
+ Controlrepo.facts.each do |facts|
195
+ context "on #{facts['fqdn']}" do
196
+ let(:facts) { facts }
197
+ it { should compile }
198
+ end
199
+ end
200
+ end
201
+ end
202
+ ```
203
+
204
+ This will iterate over each role in the controlrepo and test that it compiles with each set of facts.
205
+
206
+ The same can also be done with profiles just by using the profiles method instead:
207
+
208
+ ```ruby
209
+ require 'spec_helper'
210
+ require 'controlrepo'
211
+
212
+ Controlrepo.profiles.each do |profile|
213
+ describe profile do
214
+ Controlrepo.facts.each do |facts|
215
+ context "on #{facts['fqdn']}" do
216
+ let(:facts) { facts }
217
+ it { should compile }
218
+ end
219
+ end
220
+ end
221
+ end
222
+ ```
223
+
224
+ It is not limited to just doing simple "It should compile" tests. You can put any tests you want in here.
225
+
226
+ Also since the `profiles`, `roles` and `facts` methods simply return arrays, you can iterate over them however you would like i.e. you could write a different set of tests for each profile and then just use the `facts` method to run those tests on every fact set.
227
+
228
+ ## Filtering (Hard mode)
229
+
230
+ You can also filter your fact sets based on the value of any fact, including structured facts. (It will drill down into nested hashes and match those too, it's not just a dumb equality match)
231
+
232
+ Just pass a hash to the `facts` method and it will return only the fact sets with facts that match the hash e.g. Testing a certain profile on against only your Windows fact sets:
233
+
234
+ ```ruby
235
+ require 'spec_helper'
236
+ require 'controlrepo'
237
+
238
+ describe 'profile::windows_appserver' do
239
+ Controlrepo.facts({
240
+ 'kernel' => 'windows'
241
+ }).each do |facts|
242
+ context "on #{facts['fqdn']}" do
243
+ let(:facts) { facts }
244
+ it { should compile }
245
+ end
246
+ end
247
+ end
248
+ ```
249
+
250
+ ## Using hiera data (Hard Mode)
251
+
252
+ You can also point these tests at your hiera data, you do this as you [normally would](https://github.com/rodjek/rspec-puppet#enabling-hiera-lookups) with rspec tests. However we do provide one helper to make this marginally easier. `Controlrepo.hiera_config` will look for hiera.yaml in the root of your control repo and also the spec directory, you will however need to set up the file itself e.g.
253
+
254
+ ```ruby
255
+ require 'controlrepo'
256
+
257
+ RSpec.configure do |c|
258
+ c.hiera_config = Controlrepo.hiera_config_file
259
+ end
260
+ ```
261
+
262
+
263
+ ## Extra Configuration
264
+
265
+ You can modify the regexes that the gem uses to filter classes that it finds into roles and profiles. Just set up a Controlrepo object and pass regexes to the below settings.
266
+
267
+ ```ruby
268
+ repo = Controlrepo.new()
269
+ repo.role_regex = /.*/ # Tells the class how to find roles, will be applied to repo.classes
270
+ repo.profile_regex = /.*/ # Tells the class how to find profiles, will be applied to repo.classes
271
+ ```
272
+
273
+ Note that you will need to call the `roles` and `profiles` methods on the object you just instantiated, not the main class e.g. `repo.roles` not `Controlrepo.roles`
274
+
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "controlrepo"
6
+ s.version = "1.0.0"
7
+ s.authors = ["Dylan Ratcliffe"]
8
+ s.email = ["dylan.ratcliffe@puppetlabs.com"]
9
+ s.homepage = ""
10
+ s.summary = ""
11
+ s.description = ""
12
+ s.licenses = 'Apache-2.0'
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ #s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+
18
+ # Runtime dependencies, but also probably dependencies of requiring projects
19
+ s.add_runtime_dependency 'rake'
20
+ s.add_runtime_dependency 'json'
21
+ s.add_runtime_dependency 'beaker-rspec'
22
+ s.add_runtime_dependency 'rspec-puppet'
23
+ s.add_runtime_dependency 'rspec'
24
+ s.add_runtime_dependency 'bundler'
25
+ s.add_runtime_dependency 'puppet'
26
+ end
@@ -0,0 +1,323 @@
1
+ require 'pry'
2
+ require 'r10k/puppetfile'
3
+ require 'erb'
4
+ require 'json'
5
+ require 'yaml'
6
+ require 'find'
7
+ require 'pathname'
8
+ require 'controlrepo/beaker'
9
+
10
+ class Controlrepo
11
+ attr_accessor :root
12
+ attr_accessor :puppetfile
13
+ attr_accessor :facts_files
14
+ attr_accessor :role_regex
15
+ attr_accessor :profile_regex
16
+ attr_accessor :temp_environmentpath
17
+ attr_accessor :tempdir
18
+ attr_accessor :spec_dir
19
+ attr_accessor :temp_modulepath
20
+
21
+ # Create methods on self so that we can access these basic things without
22
+ # having to actually instantiate the class, I'm debating how much stuff
23
+ # I should be putting in here, we don't reeeally need to instantiate the
24
+ # object unless we want to modify it's parameters, so maybe everything.
25
+ # We shall see...
26
+ #
27
+ # And yeah I know this makes little sense, but it will look nicer to type, promise
28
+ #
29
+ # Also it's probably pretty memory hungry, but let's be honest, how many
30
+ # times would be be calling this? If we call it over and over you can just
31
+ # instantiate it anyway
32
+ def self.root
33
+ Controlrepo.new.root
34
+ end
35
+
36
+ def self.puppetfile
37
+ Controlrepo.new.puppetfile
38
+ end
39
+
40
+ def self.facts_files
41
+ Controlrepo.new.facts_files
42
+ end
43
+
44
+ def self.classes
45
+ Controlrepo.new.classes
46
+ end
47
+
48
+ def self.roles
49
+ Controlrepo.new.roles
50
+ end
51
+
52
+ def self.profiles
53
+ Controlrepo.new.profiles
54
+ end
55
+
56
+ def self.config
57
+ Controlrepo.new.config
58
+ end
59
+
60
+ def self.facts(filter = nil)
61
+ Controlrepo.new.facts(filter)
62
+ end
63
+
64
+ def self.hiera_config_file
65
+ Controlrepo.new.hiera_config_file
66
+ end
67
+ #
68
+ # End class methods
69
+ #
70
+
71
+ def initialize(search_path = Dir.pwd)
72
+ # When we initialize the object it is going to set some instance vars
73
+ begin
74
+ # Find the root of the control repo by traversing up
75
+ until File.exist?(File.expand_path('./Puppetfile',search_path)) do
76
+ search_path = File.expand_path('..',search_path)
77
+ end
78
+ rescue => e
79
+ raise " Could not find Puppetfile"
80
+ raise e
81
+ end
82
+ @root = search_path
83
+ @puppetfile = File.expand_path('./Puppetfile',@root)
84
+ @environment_conf = File.expand_path('./environment.conf',@root)
85
+ @facts_dir = File.expand_path('./spec/facts',@root)
86
+ @spec_dir = File.expand_path('./spec',@root)
87
+ @facts_files = Dir["#{@facts_dir}/*.json"]
88
+ @role_regex = /role[s]?:{2}/
89
+ @profile_regex = /profile[s]?:{2}/
90
+ @temp_environmentpath = nil
91
+ @tempdir = nil
92
+ $temp_modulepath = nil
93
+ end
94
+
95
+ def roles
96
+ classes.keep_if { |c| c =~ @role_regex }
97
+ end
98
+
99
+ def profiles
100
+ classes.keep_if { |c| c =~ @profile_regex }
101
+ end
102
+
103
+ def classes
104
+ # Get all of the possible places for puppet code and look for classes
105
+ code_dirs = self.config['modulepath']
106
+ # Remove relative references
107
+ code_dirs.delete_if { |dir| dir[0] == '$'}
108
+
109
+ # Get all the classes from all of the manifests
110
+ classes = []
111
+ code_dirs.each do |dir|
112
+ classes << get_classes(dir)
113
+ end
114
+ classes.flatten
115
+ end
116
+
117
+ def facts(filter = nil)
118
+ # Returns an array facts hashes
119
+ all_facts = []
120
+ @facts_files.each do |file|
121
+ all_facts << read_facts(file)['values']
122
+ end
123
+ if filter
124
+ # Allow us to pass a hash of facts to filter by
125
+ raise "Filter param must be a hash" unless filter.is_a?(Hash)
126
+ all_facts.keep_if do |hash|
127
+ matches = []
128
+ filter.each do |filter_fact,value|
129
+ matches << keypair_is_in_hash(hash,filter_fact,value)
130
+ end
131
+ if matches.include? false
132
+ false
133
+ else
134
+ true
135
+ end
136
+ end
137
+ end
138
+ return all_facts
139
+ end
140
+
141
+ def fixtures
142
+ # Load up the Puppetfile using R10k
143
+ puppetfile = R10K::Puppetfile.new(@root)
144
+ modules = puppetfile.load
145
+
146
+ # Iterate over everything and seperate it out for the sake of readability
147
+ symlinks = []
148
+ forge_modules = []
149
+ repositories = []
150
+
151
+ modules.each do |mod|
152
+ # This logic could probably be cleaned up. A lot.
153
+ if mod.is_a? R10K::Module::Forge
154
+ if mod.expected_version.is_a?(Hash)
155
+ # Set it up as a symlink, because we are using local files in the Puppetfile
156
+ symlinks << {
157
+ 'name' => mod.name,
158
+ 'dir' => mod.expected_version[:path]
159
+ }
160
+ elsif mod.expected_version.is_a?(String)
161
+ # Set it up as a normal firge module
162
+ forge_modules << {
163
+ 'name' => mod.name,
164
+ 'repo' => mod.title,
165
+ 'ref' => mod.expected_version
166
+ }
167
+ end
168
+ elsif mod.is_a? R10K::Module::Git
169
+ # Set it up as a git repo
170
+ repositories << {
171
+ 'name' => mod.name,
172
+ # I know I shouldn't be doing this, but trust me, there are no methods
173
+ # anywhere that expose this value, I looked.
174
+ 'repo' => mod.instance_variable_get(:@remote),
175
+ 'ref' => mod.version
176
+ }
177
+ end
178
+ end
179
+
180
+ # also add synlinks for anything that is in environment.conf
181
+ code_dirs = self.config['modulepath']
182
+ code_dirs.delete_if { |dir| dir[0] == '$'}
183
+ code_dirs.each do |dir|
184
+ # We need to traverse down into these directories and create a symlink for each
185
+ # module we find because fixtures.yml is expecting the module's root not the
186
+ # root of modulepath
187
+ Dir["#{dir}/*"].each do |mod|
188
+ symlinks << {
189
+ 'name' => File.basename(mod),
190
+ 'dir' => Pathname.new(File.expand_path(mod)).relative_path_from(Pathname.new(@root))#File.expand_path(mod)
191
+ }
192
+ end
193
+ end
194
+
195
+ # Use an ERB template to write the files
196
+ template_dir = File.expand_path('../templates',File.dirname(__FILE__))
197
+ fixtures_template = File.read(File.expand_path('./.fixtures.yml.erb',template_dir))
198
+ fixtures_yaml = ERB.new(fixtures_template, nil, '-').result(binding)
199
+ return fixtures_yaml
200
+ end
201
+
202
+ def hiera_config_file
203
+ # try to find the hiera.iyaml file
204
+ hiera_config_file = File.expand_path('./hiera.yaml',@spec_dir) if File.exist?(File.expand_path('./hiera.yaml',@spec_dir))
205
+ hiera_config_file = File.expand_path('./hiera.yaml',@root) if File.exist?(File.expand_path('./hiera.yaml',@root))
206
+ hiera_config_file
207
+ end
208
+
209
+ def hiera_config
210
+ YAML.load_file(hiera_config_file)
211
+ end
212
+
213
+ def hiera_config=(data)
214
+ File.write(hiera_config_file,data.to_yaml)
215
+ end
216
+
217
+ def hiera_data
218
+ # This is going to try to find your hiera data directory, if you have named it something
219
+ # unexpected it won't work
220
+ possibe_datadirs = Dir["#{@root}/*/"]
221
+ possibe_datadirs.keep_if { |dir| dir =~ /hiera(?:.*data)?/i }
222
+ raise "There were too many directories that looked like hiera data: #{possibe_datadirs}" if possibe_datadirs.count > 1
223
+ File.expand_path(possibe_datadirs[0])
224
+ end
225
+
226
+ def config
227
+ # Parse the file
228
+ env_conf = File.read(@environment_conf)
229
+ env_conf = env_conf.split("\n")
230
+
231
+ # Map the lines into a hash
232
+ environment_config = {}
233
+ env_conf.each do |line|
234
+ environment_config.merge!(Hash[*line.split('=').map { |s| s.strip}])
235
+ end
236
+
237
+ # Finally, split the modulepath values and return
238
+ begin
239
+ environment_config['modulepath'] = environment_config['modulepath'].split(':')
240
+ rescue
241
+ raise "modulepath was not found in environment.conf, don't know where to look for roles & profiles"
242
+ end
243
+ return environment_config
244
+ end
245
+
246
+ def r10k_config_file
247
+ r10k_config_file = File.expand_path('./r10k.yaml',@spec_dir) if File.exist?(File.expand_path('./r10k.yaml',@spec_dir))
248
+ r10k_config_file = File.expand_path('./r10k.yaml',@root) if File.exist?(File.expand_path('./r10k.yaml',@root))
249
+ r10k_config_file
250
+ end
251
+
252
+ def r10k_config
253
+ YAML.load_file(r10k_config_file)
254
+ end
255
+
256
+ def r10k_config=(data)
257
+ File.write(r10k_config_file,data.to_yaml)
258
+ end
259
+
260
+ private
261
+
262
+ def read_facts(facts_file)
263
+ file = File.read(facts_file)
264
+ begin
265
+ result = JSON.parse(file)
266
+ rescue JSON::ParserError
267
+ raise "Could not parse the JSON file, check that it is valid JSON and that the encoding is correct"
268
+ end
269
+ result
270
+ end
271
+
272
+ def keypair_is_in_hash(first_hash, key, value)
273
+ matches = []
274
+ if first_hash.has_key?(key)
275
+ if value.is_a?(Hash)
276
+ value.each do |k,v|
277
+ matches << keypair_is_in_hash(first_hash[key],k,v)
278
+ end
279
+ else
280
+ if first_hash[key] == value
281
+ matches << true
282
+ else
283
+ matches << false
284
+ end
285
+ end
286
+ else
287
+ matches << false
288
+ end
289
+ if matches.include? false
290
+ false
291
+ else
292
+ true
293
+ end
294
+ end
295
+
296
+ def get_classes(dir)
297
+ classes = []
298
+ # Recurse over all the pp files under the dir we are given
299
+ Dir["#{dir}/**/*.pp"].each do |manifest|
300
+ classname = find_classname(manifest)
301
+ # Add it to the array as long as it is not nil
302
+ classes << classname if classname
303
+ end
304
+ classes
305
+ end
306
+
307
+ def find_classname(filename)
308
+ file = File.new(filename, "r")
309
+ while (line = file.gets)
310
+ if line =~ /^class (\w+(?:::\w+)*)/
311
+ return $1
312
+ end
313
+ end
314
+ return nil
315
+ end
316
+ end
317
+
318
+
319
+
320
+
321
+
322
+
323
+