jamie 0.1.0.alpha1 → 0.1.0.alpha2
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.
- data/.travis.yml +3 -0
- data/.yardopts +3 -0
- data/Gemfile +5 -0
- data/Rakefile +21 -1
- data/jamie.gemspec +5 -1
- data/lib/jamie.rb +336 -60
- data/lib/jamie/core_ext.rb +74 -0
- data/lib/jamie/driver/vagrant.rb +44 -0
- data/lib/jamie/rake_tasks.rb +55 -0
- data/lib/jamie/vagrant.rb +23 -23
- data/lib/jamie/version.rb +4 -1
- metadata +68 -12
- data/lib/jamie/rake_task.rb +0 -52
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -1 +1,21 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'cane/rake_task'
|
3
|
+
require 'tailor/rake_task'
|
4
|
+
|
5
|
+
desc "Run cane to check quality metrics"
|
6
|
+
Cane::RakeTask.new do |cane|
|
7
|
+
cane.abc_exclude = %w(
|
8
|
+
Jamie::RakeTasks#define
|
9
|
+
Jamie::Vagrant.define_vagrant_vm
|
10
|
+
)
|
11
|
+
cane.style_exclude = %w(
|
12
|
+
lib/jamie/core_ext.rb
|
13
|
+
)
|
14
|
+
cane.doc_exclude = %w(
|
15
|
+
lib/jamie/core_ext.rb
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
Tailor::RakeTask.new
|
20
|
+
|
21
|
+
task :default => [ :cane, :tailor ]
|
data/jamie.gemspec
CHANGED
@@ -17,7 +17,11 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
18
|
gem.require_paths = ["lib"]
|
19
19
|
|
20
|
-
gem.add_dependency 'hashie'
|
21
20
|
gem.add_dependency 'mixlib-shellout'
|
22
21
|
gem.add_dependency 'vagrant', '~> 1.0.5'
|
22
|
+
|
23
|
+
gem.add_development_dependency 'yard'
|
24
|
+
gem.add_development_dependency 'maruku'
|
25
|
+
gem.add_development_dependency 'cane'
|
26
|
+
gem.add_development_dependency 'tailor'
|
23
27
|
end
|
data/lib/jamie.rb
CHANGED
@@ -1,111 +1,387 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
-
require 'hashie/dash'
|
4
|
-
require 'mixlib/shellout'
|
5
3
|
require 'yaml'
|
6
4
|
|
7
|
-
require
|
5
|
+
require 'jamie/core_ext'
|
6
|
+
require 'jamie/version'
|
8
7
|
|
9
8
|
module Jamie
|
10
|
-
class Platform < Hashie::Dash
|
11
|
-
property :name, :required => true
|
12
|
-
property :vagrant_box
|
13
|
-
property :vagrant_box_url
|
14
|
-
property :base_run_list, :default => []
|
15
|
-
end
|
16
|
-
|
17
|
-
class Suite < Hashie::Dash
|
18
|
-
property :name, :required => true
|
19
|
-
property :run_list, :required => true
|
20
|
-
property :json, :default => Hash.new
|
21
|
-
end
|
22
9
|
|
10
|
+
# Base configuration class for Jamie. This class exposes configuration such
|
11
|
+
# as the location of the Jamie YAML file, instances, log_levels, etc.
|
23
12
|
class Config
|
24
|
-
|
13
|
+
|
14
|
+
attr_writer :yaml_file
|
25
15
|
attr_writer :platforms
|
26
16
|
attr_writer :suites
|
27
|
-
attr_writer :backend
|
28
17
|
attr_writer :log_level
|
29
18
|
attr_writer :data_bags_base_path
|
30
19
|
|
31
|
-
|
32
|
-
|
20
|
+
# Default path to the Jamie YAML file
|
21
|
+
DEFAULT_YAML_FILE = File.join(Dir.pwd, '.jamie.yml').freeze
|
22
|
+
|
23
|
+
# Default log level verbosity
|
24
|
+
DEFAULT_LOG_LEVEL = :info
|
25
|
+
|
26
|
+
# Default driver plugin to use
|
27
|
+
DEFAULT_DRIVER_PLUGIN = "vagrant".freeze
|
28
|
+
|
29
|
+
# Default base path which may contain `data_bags/` directories
|
30
|
+
DEFAULT_DATA_BAGS_BASE_PATH = File.join(Dir.pwd, 'test/integration').freeze
|
31
|
+
|
32
|
+
# Creates a new configuration.
|
33
|
+
#
|
34
|
+
# @param yaml_file [String] optional path to Jamie YAML file
|
35
|
+
def initialize(yaml_file = nil)
|
36
|
+
@yaml_file = yaml_file
|
33
37
|
end
|
34
38
|
|
39
|
+
# @return [Array<Platform>] all defined platforms which will be used in
|
40
|
+
# convergence integration
|
35
41
|
def platforms
|
36
|
-
@platforms ||=
|
37
|
-
Array(yaml_data["platforms"]).map { |hash| Platform.new(hash) }
|
42
|
+
@platforms ||= Array(yaml["platforms"]).map { |hash| new_platform(hash) }
|
38
43
|
end
|
39
44
|
|
45
|
+
# @return [Array<Suite>] all defined suites which will be used in
|
46
|
+
# convergence integration
|
40
47
|
def suites
|
41
|
-
@suites ||=
|
42
|
-
Array(yaml_data["suites"]).map { |hash| Suite.new(hash) }
|
48
|
+
@suites ||= Array(yaml["suites"]).map { |hash| Suite.new(hash) }
|
43
49
|
end
|
44
50
|
|
45
|
-
|
46
|
-
|
51
|
+
# @return [Array<Instance>] all instances, resulting from all platform and
|
52
|
+
# suite combinations
|
53
|
+
def instances
|
54
|
+
@instances ||= suites.map { |suite|
|
55
|
+
platforms.map { |platform| Instance.new(suite, platform) }
|
56
|
+
}.flatten
|
47
57
|
end
|
48
58
|
|
59
|
+
# @return [String] path to the Jamie YAML file
|
60
|
+
def yaml_file
|
61
|
+
@yaml_file ||= DEFAULT_YAML_FILE
|
62
|
+
end
|
63
|
+
|
64
|
+
# @return [Symbol] log level verbosity
|
49
65
|
def log_level
|
50
|
-
@log_level ||=
|
66
|
+
@log_level ||= DEFAULT_LOG_LEVEL
|
51
67
|
end
|
52
68
|
|
69
|
+
# @return [String] base path that may contain a common `data_bags/`
|
70
|
+
# directory or an instance's `data_bags/` directory
|
53
71
|
def data_bags_base_path
|
54
|
-
|
72
|
+
@data_bags_path ||= DEFAULT_DATA_BAGS_BASE_PATH
|
73
|
+
end
|
55
74
|
|
56
|
-
|
75
|
+
private
|
76
|
+
|
77
|
+
def new_platform(hash)
|
78
|
+
mpc = merge_platform_config(hash)
|
79
|
+
mpc['driver'] = new_driver(mpc['driver_plugin'], mpc['driver_config'])
|
80
|
+
Platform.new(mpc)
|
57
81
|
end
|
58
82
|
|
59
|
-
def
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
83
|
+
def new_driver(plugin, config)
|
84
|
+
Driver.for_plugin(plugin, config)
|
85
|
+
end
|
86
|
+
|
87
|
+
def yaml
|
88
|
+
@yaml ||= YAML.load_file(File.expand_path(yaml_file))
|
89
|
+
end
|
90
|
+
|
91
|
+
def merge_platform_config(platform_config)
|
92
|
+
default_driver_config.rmerge(common_driver_config.rmerge(platform_config))
|
93
|
+
end
|
94
|
+
|
95
|
+
def default_driver_config
|
96
|
+
{ 'driver_plugin' => DEFAULT_DRIVER_PLUGIN }
|
97
|
+
end
|
98
|
+
|
99
|
+
def common_driver_config
|
100
|
+
yaml.select { |key, value| %w(driver_plugin driver_config).include?(key) }
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# A Chef run_list and attribute hash that will be used in a convergence
|
105
|
+
# integration.
|
106
|
+
class Suite
|
107
|
+
|
108
|
+
# @return [String] logical name of this suite
|
109
|
+
attr_reader :name
|
110
|
+
|
111
|
+
# @return [Array] Array of Chef run_list items
|
112
|
+
attr_reader :run_list
|
113
|
+
|
114
|
+
# @return [Hash] Hash of Chef node attributes
|
115
|
+
attr_reader :json
|
116
|
+
|
117
|
+
# Constructs a new suite.
|
118
|
+
#
|
119
|
+
# @param [Hash] options configuration for a new suite
|
120
|
+
# @option options [String] :name logical name of this suit (**Required**)
|
121
|
+
# @option options [String] :run_list Array of Chef run_list items
|
122
|
+
# (**Required**)
|
123
|
+
# @option options [Hash] :json Hash of Chef node attributes
|
124
|
+
def initialize(options = {})
|
125
|
+
validate_options(options)
|
126
|
+
|
127
|
+
@name = options['name']
|
128
|
+
@run_list = options['run_list']
|
129
|
+
@json = options['json'] || Hash.new
|
130
|
+
end
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def validate_options(options)
|
135
|
+
if options['name'].nil?
|
136
|
+
raise ArgumentError, "The option 'name' is required."
|
137
|
+
end
|
138
|
+
if options['run_list'].nil?
|
139
|
+
raise ArgumentError, "The option 'run_list' is required."
|
65
140
|
end
|
66
|
-
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# A target operating system environment in which convergence integration
|
145
|
+
# will take place. This may represent a specific operating system, version,
|
146
|
+
# and machine architecture.
|
147
|
+
class Platform
|
148
|
+
|
149
|
+
# @return [String] logical name of this platform
|
150
|
+
attr_reader :name
|
151
|
+
|
152
|
+
# @return [Driver::Base] driver object which will manage this platform's
|
153
|
+
# lifecycle actions
|
154
|
+
attr_reader :driver
|
155
|
+
|
156
|
+
# @return [Array] Array of Chef run_list items
|
157
|
+
attr_reader :run_list
|
158
|
+
|
159
|
+
# @return [Hash] Hash of Chef node attributes
|
160
|
+
attr_reader :json
|
161
|
+
|
162
|
+
# Constructs a new platform.
|
163
|
+
#
|
164
|
+
# @param [Hash] options configuration for a new platform
|
165
|
+
# @option options [String] :name logical name of this platform
|
166
|
+
# (**Required**)
|
167
|
+
# @option options [Driver::Base] :driver subclass of Driver::Base which
|
168
|
+
# will manage this platform's lifecycle actions (**Required**)
|
169
|
+
# @option options [Array<String>] :run_list Array of Chef run_list
|
170
|
+
# items
|
171
|
+
# @option options [Hash] :json Hash of Chef node attributes
|
172
|
+
def initialize(options = {})
|
173
|
+
validate_options(options)
|
174
|
+
|
175
|
+
@name = options['name']
|
176
|
+
@driver = options['driver']
|
177
|
+
@run_list = Array(options['run_list'])
|
178
|
+
@json = options['json'] || Hash.new
|
67
179
|
end
|
68
180
|
|
69
181
|
private
|
70
182
|
|
71
|
-
def
|
72
|
-
|
183
|
+
def validate_options(options)
|
184
|
+
if options['name'].nil?
|
185
|
+
raise ArgumentError, "The option 'name' is required."
|
186
|
+
end
|
187
|
+
if options['driver'].nil?
|
188
|
+
raise ArgumentError, "The option 'driver' is required."
|
189
|
+
end
|
73
190
|
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# An instance of a suite running on a platform. A created instance may be a
|
194
|
+
# local virtual machine, cloud instance, container, or even a bare metal
|
195
|
+
# server, which is determined by the platform's driver.
|
196
|
+
class Instance
|
74
197
|
|
75
|
-
|
198
|
+
# @return [Suite] the test suite configuration
|
199
|
+
attr_reader :suite
|
200
|
+
|
201
|
+
# @return [Platform] the target platform configuration
|
202
|
+
attr_reader :platform
|
203
|
+
|
204
|
+
# Creates a new instance, given a suite and a platform.
|
205
|
+
#
|
206
|
+
# @param suite [Suite] a suite
|
207
|
+
# @param platform [Platform] a platform
|
208
|
+
def initialize(suite, platform)
|
209
|
+
@suite = suite
|
210
|
+
@platform = platform
|
211
|
+
end
|
212
|
+
|
213
|
+
# @return [String] name of this instance
|
214
|
+
def name
|
76
215
|
"#{suite.name}-#{platform.name}".gsub(/_/, '-').gsub(/\./, '')
|
77
216
|
end
|
78
217
|
|
79
|
-
|
80
|
-
|
81
|
-
|
218
|
+
# Returns a combined run_list starting with the platform's run_list
|
219
|
+
# followed by the suite's run_list.
|
220
|
+
#
|
221
|
+
# @return [Array] combined run_list from suite and platform
|
222
|
+
def run_list
|
223
|
+
Array(platform.run_list) + Array(suite.run_list)
|
224
|
+
end
|
225
|
+
|
226
|
+
# Returns a merged hash of Chef node attributes with values from the
|
227
|
+
# suite overriding values from the platform.
|
228
|
+
#
|
229
|
+
# @return [Hash] merged hash of Chef node attributes
|
230
|
+
def json
|
231
|
+
platform.json.rmerge(suite.json)
|
232
|
+
end
|
233
|
+
|
234
|
+
# Creates this instance.
|
235
|
+
#
|
236
|
+
# @see Driver::Base#create
|
237
|
+
# @return [self] this instance, used to chain actions
|
238
|
+
#
|
239
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
240
|
+
# to gracfully stop action chaining
|
241
|
+
def create
|
242
|
+
puts "-----> Creating instance #{name}"
|
243
|
+
platform.driver.create(self)
|
244
|
+
puts " Creation of instance #{name} complete."
|
245
|
+
self
|
246
|
+
end
|
247
|
+
|
248
|
+
# Converges this running instance.
|
249
|
+
#
|
250
|
+
# @see Driver::Base#converge
|
251
|
+
# @return [self] this instance, used to chain actions
|
252
|
+
#
|
253
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
254
|
+
# to gracfully stop action chaining
|
255
|
+
def converge
|
256
|
+
puts "-----> Converging instance #{name}"
|
257
|
+
platform.driver.converge(self)
|
258
|
+
puts " Convergence of instance #{name} complete."
|
259
|
+
self
|
260
|
+
end
|
261
|
+
|
262
|
+
# Verifies this converged instance by executing tests.
|
263
|
+
#
|
264
|
+
# @see Driver::Base#verify
|
265
|
+
# @return [self] this instance, used to chain actions
|
266
|
+
#
|
267
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
268
|
+
# to gracfully stop action chaining
|
269
|
+
def verify
|
270
|
+
puts "-----> Verifying instance #{name}"
|
271
|
+
platform.driver.verify(self)
|
272
|
+
puts " Verification of instance #{name} complete."
|
273
|
+
self
|
274
|
+
end
|
275
|
+
|
276
|
+
# Destroys this instance.
|
277
|
+
#
|
278
|
+
# @see Driver::Base#destroy
|
279
|
+
# @return [self] this instance, used to chain actions
|
280
|
+
#
|
281
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
282
|
+
# to gracfully stop action chaining
|
283
|
+
def destroy
|
284
|
+
puts "-----> Destroying instance #{name}"
|
285
|
+
platform.driver.destroy(self)
|
286
|
+
puts " Destruction of instance #{name} complete."
|
287
|
+
self
|
288
|
+
end
|
289
|
+
|
290
|
+
# Tests this instance by creating, converging and verifying. If this
|
291
|
+
# instance is running, it will be pre-emptively destroyed to ensure a
|
292
|
+
# clean slate. The instance will be left post-verify in a running state.
|
293
|
+
#
|
294
|
+
# @see #destroy
|
295
|
+
# @see #create
|
296
|
+
# @see #converge
|
297
|
+
# @see #verify
|
298
|
+
# @return [self] this instance, used to chain actions
|
299
|
+
#
|
300
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
301
|
+
# to gracfully stop action chaining
|
302
|
+
def test
|
303
|
+
puts "-----> Cleaning up any prior instances of #{name}"
|
304
|
+
destroy
|
305
|
+
puts "-----> Testing instance #{name}"
|
306
|
+
create
|
307
|
+
converge
|
308
|
+
verify
|
309
|
+
puts " Testing of instance #{name} complete."
|
310
|
+
self
|
82
311
|
end
|
83
312
|
end
|
84
313
|
|
85
|
-
module
|
86
|
-
|
314
|
+
module Driver
|
315
|
+
|
316
|
+
# Wrapped exception for any internally raised driver exceptions.
|
317
|
+
class ActionFailed < StandardError ; end
|
318
|
+
|
319
|
+
# Returns an instance of a driver given a plugin type string.
|
320
|
+
#
|
321
|
+
# @param plugin [String] a driver plugin type, which will be constantized
|
322
|
+
# @return [Driver::Base] a driver instance
|
323
|
+
def self.for_plugin(plugin, config)
|
324
|
+
require "jamie/driver/#{plugin}"
|
325
|
+
|
326
|
+
klass = self.const_get(plugin.capitalize)
|
327
|
+
klass.new(config)
|
328
|
+
end
|
329
|
+
|
330
|
+
# Base class for a driver. A driver is responsible for carrying out the
|
331
|
+
# lifecycle activities of an instance, such as creating, converging, and
|
332
|
+
# destroying an instance.
|
333
|
+
class Base
|
334
|
+
|
335
|
+
def initialize(config)
|
336
|
+
@config = config
|
337
|
+
self.class.defaults.each do |attr, value|
|
338
|
+
@config[attr] = value unless @config[attr]
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
# Provides hash-like access to configuration keys.
|
343
|
+
#
|
344
|
+
# @param attr [Object] configuration key
|
345
|
+
# @return [Object] value at configuration key
|
346
|
+
def [](attr)
|
347
|
+
@config[attr]
|
348
|
+
end
|
349
|
+
|
350
|
+
# Creates an instance.
|
351
|
+
#
|
352
|
+
# @param instance [Instance] an instance
|
353
|
+
# @raise [ActionFailed] if the action could not be completed
|
354
|
+
def create(instance) ; end
|
355
|
+
|
356
|
+
# Converges a running instance.
|
357
|
+
#
|
358
|
+
# @param instance [Instance] an instance
|
359
|
+
# @raise [ActionFailed] if the action could not be completed
|
360
|
+
def converge(instance) ; end
|
87
361
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
362
|
+
# Destroys an instance.
|
363
|
+
#
|
364
|
+
# @param instance [Instance] an instance
|
365
|
+
# @raise [ActionFailed] if the action could not be completed
|
366
|
+
def destroy(instance) ; end
|
367
|
+
|
368
|
+
# Verifies a converged instance.
|
369
|
+
#
|
370
|
+
# @param instance [Instance] an instance
|
371
|
+
# @raise [ActionFailed] if the action could not be completed
|
372
|
+
def verify(instance)
|
373
|
+
# Subclass may choose to implement
|
374
|
+
puts " Nothing to do!"
|
93
375
|
end
|
94
376
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
377
|
+
private
|
378
|
+
|
379
|
+
def self.defaults
|
380
|
+
@defaults ||= Hash.new
|
99
381
|
end
|
100
382
|
|
101
|
-
def
|
102
|
-
|
103
|
-
shellout = Mixlib::ShellOut.new(
|
104
|
-
cmd, :live_stream => STDOUT, :timeout => 60000
|
105
|
-
)
|
106
|
-
shellout.run_command
|
107
|
-
puts "-----> Command '#{cmd}' ran in #{shellout.execution_time} seconds."
|
108
|
-
shellout.error!
|
383
|
+
def self.default_config(attr, value)
|
384
|
+
defaults[attr] = value
|
109
385
|
end
|
110
386
|
end
|
111
387
|
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#
|
2
|
+
# = Hash Recursive Merge
|
3
|
+
#
|
4
|
+
# Merges a Ruby Hash recursively, Also known as deep merge.
|
5
|
+
# Recursive version of Hash#merge and Hash#merge!.
|
6
|
+
#
|
7
|
+
# Category:: Ruby
|
8
|
+
# Package:: Hash
|
9
|
+
# Author:: Simone Carletti <weppos@weppos.net>
|
10
|
+
# Copyright:: 2007-2008 The Authors
|
11
|
+
# License:: MIT License
|
12
|
+
# Link:: http://www.simonecarletti.com/
|
13
|
+
# Source:: http://gist.github.com/gists/6391/
|
14
|
+
#
|
15
|
+
module HashRecursiveMerge
|
16
|
+
|
17
|
+
#
|
18
|
+
# Recursive version of Hash#merge!
|
19
|
+
#
|
20
|
+
# Adds the contents of +other_hash+ to +hsh+,
|
21
|
+
# merging entries in +hsh+ with duplicate keys with those from +other_hash+.
|
22
|
+
#
|
23
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
24
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
25
|
+
# it merges and returns the values from both arrays.
|
26
|
+
#
|
27
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
28
|
+
# h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
|
29
|
+
# h1.rmerge!(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
30
|
+
#
|
31
|
+
# Simply using Hash#merge! would return
|
32
|
+
#
|
33
|
+
# h1.merge!(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
34
|
+
#
|
35
|
+
def rmerge!(other_hash)
|
36
|
+
merge!(other_hash) do |key, oldval, newval|
|
37
|
+
oldval.class == self.class ? oldval.rmerge!(newval) : newval
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Recursive version of Hash#merge
|
43
|
+
#
|
44
|
+
# Compared with Hash#merge!, this method supports nested hashes.
|
45
|
+
# When both +hsh+ and +other_hash+ contains an entry with the same key,
|
46
|
+
# it merges and returns the values from both arrays.
|
47
|
+
#
|
48
|
+
# Compared with Hash#merge, this method provides a different approch
|
49
|
+
# for merging nasted hashes.
|
50
|
+
# If the value of a given key is an Hash and both +other_hash+ abd +hsh
|
51
|
+
# includes the same key, the value is merged instead replaced with
|
52
|
+
# +other_hash+ value.
|
53
|
+
#
|
54
|
+
# h1 = {"a" => 100, "b" => 200, "c" => {"c1" => 12, "c2" => 14}}
|
55
|
+
# h2 = {"b" => 254, "c" => {"c1" => 16, "c3" => 94}}
|
56
|
+
# h1.rmerge(h2) #=> {"a" => 100, "b" => 254, "c" => {"c1" => 16, "c2" => 14, "c3" => 94}}
|
57
|
+
#
|
58
|
+
# Simply using Hash#merge would return
|
59
|
+
#
|
60
|
+
# h1.merge(h2) #=> {"a" => 100, "b" = >254, "c" => {"c1" => 16, "c3" => 94}}
|
61
|
+
#
|
62
|
+
def rmerge(other_hash)
|
63
|
+
r = {}
|
64
|
+
merge(other_hash) do |key, oldval, newval|
|
65
|
+
r[key] = oldval.class == self.class ? oldval.rmerge(newval) : newval
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
class Hash
|
73
|
+
include HashRecursiveMerge
|
74
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'mixlib/shellout'
|
4
|
+
|
5
|
+
require 'jamie'
|
6
|
+
|
7
|
+
module Jamie
|
8
|
+
|
9
|
+
module Driver
|
10
|
+
|
11
|
+
# Vagrant driver for Jamie. It communicates to Vagrant via the CLI.
|
12
|
+
class Vagrant < Jamie::Driver::Base
|
13
|
+
|
14
|
+
default_config 'memory', '256'
|
15
|
+
|
16
|
+
def create(instance)
|
17
|
+
run "vagrant up #{instance.name} --no-provision"
|
18
|
+
end
|
19
|
+
|
20
|
+
def converge(instance)
|
21
|
+
run "vagrant provision #{instance.name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy(instance)
|
25
|
+
run "vagrant destroy #{instance.name} -f"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def run(cmd)
|
31
|
+
puts " [vagrant command] '#{cmd}'"
|
32
|
+
shellout = Mixlib::ShellOut.new(
|
33
|
+
cmd, :live_stream => STDOUT, :timeout => 60000
|
34
|
+
)
|
35
|
+
shellout.run_command
|
36
|
+
puts " [vagrant command] '#{cmd}' ran " +
|
37
|
+
"in #{shellout.execution_time} seconds."
|
38
|
+
shellout.error!
|
39
|
+
rescue Mixlib::ShellOut::ShellCommandFailed => ex
|
40
|
+
raise ActionFailed, ex.message
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/tasklib'
|
5
|
+
|
6
|
+
require 'jamie'
|
7
|
+
|
8
|
+
module Jamie
|
9
|
+
|
10
|
+
# Jamie Rake task generator.
|
11
|
+
class RakeTasks < ::Rake::TaskLib
|
12
|
+
|
13
|
+
# @return [String] prefix name of all Jamie tasks
|
14
|
+
attr_accessor :name
|
15
|
+
|
16
|
+
# @return [Jamie::Config] a Jamie config object
|
17
|
+
attr_accessor :config
|
18
|
+
|
19
|
+
# Creates Jamie Rake tasks and allows the callee to configure it.
|
20
|
+
#
|
21
|
+
# @yield [self] gives itself to the block
|
22
|
+
def initialize(name = :jamie)
|
23
|
+
@name = name
|
24
|
+
@config = Jamie::Config.new
|
25
|
+
yield self if block_given?
|
26
|
+
define
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def define
|
32
|
+
namespace name do
|
33
|
+
config.instances.each do |instance|
|
34
|
+
desc "Run #{instance.name} test instance"
|
35
|
+
task instance.name do
|
36
|
+
instance.test
|
37
|
+
end
|
38
|
+
|
39
|
+
namespace instance.name do
|
40
|
+
desc "Destroy #{instance.name} test instance"
|
41
|
+
task :destroy do
|
42
|
+
instance.destroy
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Destroy all instances"
|
48
|
+
task :destroy => config.instances.map { |i| "#{i.name}:destroy" }
|
49
|
+
end
|
50
|
+
|
51
|
+
desc "Run Jamie integration"
|
52
|
+
task name => config.instances.map { |i| "#{name}:#{i.name}" }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/jamie/vagrant.rb
CHANGED
@@ -6,12 +6,15 @@ require 'vagrant'
|
|
6
6
|
require 'jamie'
|
7
7
|
|
8
8
|
module Jamie
|
9
|
+
|
9
10
|
module Vagrant
|
11
|
+
|
12
|
+
# A Vagrant confiuration class which wraps a Jamie::Config instance.
|
10
13
|
class Config < ::Vagrant::Config::Base
|
11
14
|
extend Forwardable
|
12
15
|
|
13
|
-
def_delegators :@config, :
|
14
|
-
:
|
16
|
+
def_delegators :@config, :suites, :suites=, :platforms, :platforms=,
|
17
|
+
:instances, :yaml_file, :yaml_file=, :log_level, :log_level=,
|
15
18
|
:data_bags_base_path, :data_bags_base_path=, :yaml_data
|
16
19
|
|
17
20
|
def initialize
|
@@ -19,41 +22,38 @@ module Jamie
|
|
19
22
|
end
|
20
23
|
end
|
21
24
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
# Defines all Vagrant virtual machines, one for each instance.
|
26
|
+
#
|
27
|
+
# @param config [Vagrant::Config::Top] Vagrant top level config object
|
26
28
|
def self.define_vms(config)
|
27
|
-
config.jamie.
|
28
|
-
config
|
29
|
-
define_vagrant_vm(config, suite, platform)
|
30
|
-
end
|
29
|
+
config.jamie.instances.each do |instance|
|
30
|
+
define_vagrant_vm(config, instance)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
-
def self.define_vagrant_vm(config,
|
37
|
-
|
36
|
+
def self.define_vagrant_vm(config, instance)
|
37
|
+
driver = instance.platform.driver
|
38
38
|
|
39
|
-
config.vm.define name do |c|
|
40
|
-
c.vm.box =
|
41
|
-
c.vm.box_url =
|
42
|
-
c.vm.host_name = "#{name}.vagrantup.com"
|
43
|
-
c.vm.customize ["modifyvm", :id, "--memory",
|
39
|
+
config.vm.define instance.name do |c|
|
40
|
+
c.vm.box = driver['box']
|
41
|
+
c.vm.box_url = driver['box_url'] if driver['box_url']
|
42
|
+
c.vm.host_name = "#{instance.name}.vagrantup.com"
|
43
|
+
c.vm.customize ["modifyvm", :id, "--memory", driver['memory']]
|
44
44
|
|
45
45
|
c.vm.provision :chef_solo do |chef|
|
46
46
|
chef.log_level = config.jamie.log_level
|
47
|
-
chef.run_list =
|
48
|
-
chef.json =
|
49
|
-
chef.data_bags_path = calculate_data_bags_path(config,
|
47
|
+
chef.run_list = instance.run_list
|
48
|
+
chef.json = instance.json
|
49
|
+
chef.data_bags_path = calculate_data_bags_path(config, instance)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
def self.calculate_data_bags_path(config,
|
54
|
+
def self.calculate_data_bags_path(config, instance)
|
55
55
|
base_path = config.jamie.data_bags_base_path
|
56
|
-
instance_data_bags_path = File.join(base_path,
|
56
|
+
instance_data_bags_path = File.join(base_path, instance.name, "data_bags")
|
57
57
|
common_data_bags_path = File.join(base_path, "data_bags")
|
58
58
|
|
59
59
|
if File.directory?(instance_data_bags_path)
|
@@ -67,4 +67,4 @@ module Jamie
|
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
70
|
-
Jamie::Vagrant
|
70
|
+
Vagrant.config_keys.register(:jamie) { Jamie::Vagrant::Config }
|
data/lib/jamie/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jamie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.alpha2
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,10 +9,10 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: mixlib-shellout
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
@@ -28,14 +28,30 @@ dependencies:
|
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '0'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
31
|
+
name: vagrant
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.0.5
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.0.5
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: yard
|
32
48
|
requirement: !ruby/object:Gem::Requirement
|
33
49
|
none: false
|
34
50
|
requirements:
|
35
51
|
- - ! '>='
|
36
52
|
- !ruby/object:Gem::Version
|
37
53
|
version: '0'
|
38
|
-
type: :
|
54
|
+
type: :development
|
39
55
|
prerelease: false
|
40
56
|
version_requirements: !ruby/object:Gem::Requirement
|
41
57
|
none: false
|
@@ -44,21 +60,53 @@ dependencies:
|
|
44
60
|
- !ruby/object:Gem::Version
|
45
61
|
version: '0'
|
46
62
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
63
|
+
name: maruku
|
48
64
|
requirement: !ruby/object:Gem::Requirement
|
49
65
|
none: false
|
50
66
|
requirements:
|
51
|
-
- -
|
67
|
+
- - ! '>='
|
52
68
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
54
|
-
type: :
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
55
71
|
prerelease: false
|
56
72
|
version_requirements: !ruby/object:Gem::Requirement
|
57
73
|
none: false
|
58
74
|
requirements:
|
59
|
-
- -
|
75
|
+
- - ! '>='
|
60
76
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: cane
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: tailor
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
62
110
|
description: A Chef convergence integration test harness
|
63
111
|
email:
|
64
112
|
- fnichol@nichol.ca
|
@@ -67,13 +115,17 @@ extensions: []
|
|
67
115
|
extra_rdoc_files: []
|
68
116
|
files:
|
69
117
|
- .gitignore
|
118
|
+
- .travis.yml
|
119
|
+
- .yardopts
|
70
120
|
- Gemfile
|
71
121
|
- LICENSE
|
72
122
|
- README.md
|
73
123
|
- Rakefile
|
74
124
|
- jamie.gemspec
|
75
125
|
- lib/jamie.rb
|
76
|
-
- lib/jamie/
|
126
|
+
- lib/jamie/core_ext.rb
|
127
|
+
- lib/jamie/driver/vagrant.rb
|
128
|
+
- lib/jamie/rake_tasks.rb
|
77
129
|
- lib/jamie/vagrant.rb
|
78
130
|
- lib/jamie/version.rb
|
79
131
|
homepage: ''
|
@@ -88,6 +140,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
88
140
|
- - ! '>='
|
89
141
|
- !ruby/object:Gem::Version
|
90
142
|
version: '0'
|
143
|
+
segments:
|
144
|
+
- 0
|
145
|
+
hash: -4553058254287566395
|
91
146
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
147
|
none: false
|
93
148
|
requirements:
|
@@ -101,3 +156,4 @@ signing_key:
|
|
101
156
|
specification_version: 3
|
102
157
|
summary: A Chef convergence integration test harness
|
103
158
|
test_files: []
|
159
|
+
has_rdoc:
|
data/lib/jamie/rake_task.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
|
3
|
-
require 'rake'
|
4
|
-
require 'rake/tasklib'
|
5
|
-
|
6
|
-
require 'jamie'
|
7
|
-
|
8
|
-
module Jamie
|
9
|
-
module Rake
|
10
|
-
class Tasks < ::Rake::TaskLib
|
11
|
-
attr_accessor :name
|
12
|
-
|
13
|
-
def initialize(name = :jamie)
|
14
|
-
@name = name
|
15
|
-
yield self if block_given?
|
16
|
-
define
|
17
|
-
end
|
18
|
-
|
19
|
-
def define
|
20
|
-
config = Jamie::Config.new
|
21
|
-
|
22
|
-
namespace(name) do
|
23
|
-
config.instances.each do |instance_name|
|
24
|
-
desc "Run #{instance_name} integration"
|
25
|
-
task(instance_name) do
|
26
|
-
puts "-----> Cleaning up any prior instances of #{instance_name}"
|
27
|
-
config.backend.destroy(instance_name)
|
28
|
-
puts "-----> Bringing up instance #{instance_name}"
|
29
|
-
config.backend.up(instance_name)
|
30
|
-
puts "-----> Instance #{instance_name} completed."
|
31
|
-
end
|
32
|
-
|
33
|
-
namespace(instance_name) do
|
34
|
-
desc "Destroy #{instance_name} instance"
|
35
|
-
task :destroy do
|
36
|
-
puts "-----> Destroying any prior instances of #{instance_name}"
|
37
|
-
config.backend.destroy(instance_name)
|
38
|
-
puts "-----> Instance #{instance_name} destruction complete."
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
desc "Destroy all instances"
|
44
|
-
task :destroy => config.instances.map { |i| "#{i}:destroy" }
|
45
|
-
end
|
46
|
-
|
47
|
-
desc "Run Jamie integration"
|
48
|
-
task name => config.instances
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|