jamie 0.1.0.alpha2 → 0.1.0.alpha3
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/README.md +3 -0
- data/lib/jamie/driver/vagrant.rb +27 -2
- data/lib/jamie/vagrant.rb +2 -2
- data/lib/jamie/version.rb +1 -1
- data/lib/jamie.rb +199 -18
- metadata +2 -5
data/README.md
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# Jamie
|
2
2
|
|
3
|
+
[](https://travis-ci.org/jamie-ci/jamie)
|
4
|
+
[](https://codeclimate.com/github/jamie-ci/jamie)
|
5
|
+
|
3
6
|
A Chef convergence integration test harness.
|
4
7
|
|
5
8
|
## Installation
|
data/lib/jamie/driver/vagrant.rb
CHANGED
@@ -21,6 +21,22 @@ module Jamie
|
|
21
21
|
run "vagrant provision #{instance.name}"
|
22
22
|
end
|
23
23
|
|
24
|
+
def setup(instance)
|
25
|
+
if instance.jr.setup_cmd
|
26
|
+
ssh instance, instance.jr.setup_cmd
|
27
|
+
else
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def verify(instance)
|
33
|
+
if instance.jr.run_cmd
|
34
|
+
ssh instance, instance.jr.run_cmd
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
24
40
|
def destroy(instance)
|
25
41
|
run "vagrant destroy #{instance.name} -f"
|
26
42
|
end
|
@@ -28,17 +44,26 @@ module Jamie
|
|
28
44
|
private
|
29
45
|
|
30
46
|
def run(cmd)
|
31
|
-
puts " [vagrant command] '#{cmd}'"
|
47
|
+
puts " [vagrant command] '#{display_cmd(cmd)}'"
|
32
48
|
shellout = Mixlib::ShellOut.new(
|
33
49
|
cmd, :live_stream => STDOUT, :timeout => 60000
|
34
50
|
)
|
35
51
|
shellout.run_command
|
36
|
-
puts " [vagrant command] '#{cmd}' ran " +
|
52
|
+
puts " [vagrant command] '#{display_cmd(cmd)}' ran " +
|
37
53
|
"in #{shellout.execution_time} seconds."
|
38
54
|
shellout.error!
|
39
55
|
rescue Mixlib::ShellOut::ShellCommandFailed => ex
|
40
56
|
raise ActionFailed, ex.message
|
41
57
|
end
|
58
|
+
|
59
|
+
def ssh(instance, cmd)
|
60
|
+
run %{vagrant ssh #{instance.name} --command '#{cmd}'}
|
61
|
+
end
|
62
|
+
|
63
|
+
def display_cmd(cmd)
|
64
|
+
parts = cmd.partition("\n")
|
65
|
+
parts[1] == "\n" ? "#{parts[0]}..." : cmd
|
66
|
+
end
|
42
67
|
end
|
43
68
|
end
|
44
69
|
end
|
data/lib/jamie/vagrant.rb
CHANGED
@@ -15,7 +15,7 @@ module Jamie
|
|
15
15
|
|
16
16
|
def_delegators :@config, :suites, :suites=, :platforms, :platforms=,
|
17
17
|
:instances, :yaml_file, :yaml_file=, :log_level, :log_level=,
|
18
|
-
:
|
18
|
+
:test_base_path, :test_base_path=, :yaml_data
|
19
19
|
|
20
20
|
def initialize
|
21
21
|
@config = Jamie::Config.new
|
@@ -52,7 +52,7 @@ module Jamie
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def self.calculate_data_bags_path(config, instance)
|
55
|
-
base_path = config.jamie.
|
55
|
+
base_path = config.jamie.test_base_path
|
56
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
|
|
data/lib/jamie/version.rb
CHANGED
data/lib/jamie.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
|
3
|
+
require 'base64'
|
4
|
+
require 'digest'
|
5
|
+
require 'net/https'
|
3
6
|
require 'yaml'
|
4
7
|
|
5
8
|
require 'jamie/core_ext'
|
@@ -15,7 +18,7 @@ module Jamie
|
|
15
18
|
attr_writer :platforms
|
16
19
|
attr_writer :suites
|
17
20
|
attr_writer :log_level
|
18
|
-
attr_writer :
|
21
|
+
attr_writer :test_base_path
|
19
22
|
|
20
23
|
# Default path to the Jamie YAML file
|
21
24
|
DEFAULT_YAML_FILE = File.join(Dir.pwd, '.jamie.yml').freeze
|
@@ -27,7 +30,7 @@ module Jamie
|
|
27
30
|
DEFAULT_DRIVER_PLUGIN = "vagrant".freeze
|
28
31
|
|
29
32
|
# Default base path which may contain `data_bags/` directories
|
30
|
-
|
33
|
+
DEFAULT_TEST_BASE_PATH = File.join(Dir.pwd, 'test/integration').freeze
|
31
34
|
|
32
35
|
# Creates a new configuration.
|
33
36
|
#
|
@@ -68,8 +71,8 @@ module Jamie
|
|
68
71
|
|
69
72
|
# @return [String] base path that may contain a common `data_bags/`
|
70
73
|
# directory or an instance's `data_bags/` directory
|
71
|
-
def
|
72
|
-
@
|
74
|
+
def test_base_path
|
75
|
+
@test_base_path ||= DEFAULT_TEST_BASE_PATH
|
73
76
|
end
|
74
77
|
|
75
78
|
private
|
@@ -85,7 +88,22 @@ module Jamie
|
|
85
88
|
end
|
86
89
|
|
87
90
|
def yaml
|
88
|
-
@yaml ||= YAML.load_file(File.expand_path(yaml_file))
|
91
|
+
@yaml ||= YAML.load_file(File.expand_path(yaml_file)).rmerge(local_yaml)
|
92
|
+
end
|
93
|
+
|
94
|
+
def local_yaml_file
|
95
|
+
std = File.expand_path(yaml_file)
|
96
|
+
std.sub(/(#{File.extname(std)})$/, '.local\1')
|
97
|
+
end
|
98
|
+
|
99
|
+
def local_yaml
|
100
|
+
@local_yaml ||= begin
|
101
|
+
if File.exists?(local_yaml_file)
|
102
|
+
YAML.load_file(local_yaml_file)
|
103
|
+
else
|
104
|
+
Hash.new
|
105
|
+
end
|
106
|
+
end
|
89
107
|
end
|
90
108
|
|
91
109
|
def merge_platform_config(platform_config)
|
@@ -131,12 +149,9 @@ module Jamie
|
|
131
149
|
|
132
150
|
private
|
133
151
|
|
134
|
-
def validate_options(
|
135
|
-
|
136
|
-
raise ArgumentError, "
|
137
|
-
end
|
138
|
-
if options['run_list'].nil?
|
139
|
-
raise ArgumentError, "The option 'run_list' is required."
|
152
|
+
def validate_options(opts)
|
153
|
+
%w(name run_list).each do |k|
|
154
|
+
raise ArgumentError, "Attribute '#{attr}' is required." if opts[k].nil?
|
140
155
|
end
|
141
156
|
end
|
142
157
|
end
|
@@ -180,12 +195,9 @@ module Jamie
|
|
180
195
|
|
181
196
|
private
|
182
197
|
|
183
|
-
def validate_options(
|
184
|
-
|
185
|
-
raise ArgumentError, "
|
186
|
-
end
|
187
|
-
if options['driver'].nil?
|
188
|
-
raise ArgumentError, "The option 'driver' is required."
|
198
|
+
def validate_options(opts)
|
199
|
+
%w(name driver).each do |k|
|
200
|
+
raise ArgumentError, "Attribute '#{attr}' is required." if opts[k].nil?
|
189
201
|
end
|
190
202
|
end
|
191
203
|
end
|
@@ -201,6 +213,9 @@ module Jamie
|
|
201
213
|
# @return [Platform] the target platform configuration
|
202
214
|
attr_reader :platform
|
203
215
|
|
216
|
+
# @return [Jr] jr command string generator
|
217
|
+
attr_reader :jr
|
218
|
+
|
204
219
|
# Creates a new instance, given a suite and a platform.
|
205
220
|
#
|
206
221
|
# @param suite [Suite] a suite
|
@@ -208,6 +223,7 @@ module Jamie
|
|
208
223
|
def initialize(suite, platform)
|
209
224
|
@suite = suite
|
210
225
|
@platform = platform
|
226
|
+
@jr = Jr.new(@suite.name)
|
211
227
|
end
|
212
228
|
|
213
229
|
# @return [String] name of this instance
|
@@ -259,7 +275,21 @@ module Jamie
|
|
259
275
|
self
|
260
276
|
end
|
261
277
|
|
262
|
-
#
|
278
|
+
# Sets up this converged instance for suite tests.
|
279
|
+
#
|
280
|
+
# @see Driver::Base#setup
|
281
|
+
# @return [self] this instance, used to chain actions
|
282
|
+
#
|
283
|
+
# @todo rescue Driver::ActionFailed and return some kind of null object
|
284
|
+
# to gracfully stop action chaining
|
285
|
+
def setup
|
286
|
+
puts "-----> Setting up instance #{name}"
|
287
|
+
platform.driver.setup(self)
|
288
|
+
puts " Setup of instance #{name} complete."
|
289
|
+
self
|
290
|
+
end
|
291
|
+
|
292
|
+
# Verifies this set up instance by executing suite tests.
|
263
293
|
#
|
264
294
|
# @see Driver::Base#verify
|
265
295
|
# @return [self] this instance, used to chain actions
|
@@ -294,6 +324,7 @@ module Jamie
|
|
294
324
|
# @see #destroy
|
295
325
|
# @see #create
|
296
326
|
# @see #converge
|
327
|
+
# @see #setup
|
297
328
|
# @see #verify
|
298
329
|
# @return [self] this instance, used to chain actions
|
299
330
|
#
|
@@ -305,12 +336,157 @@ module Jamie
|
|
305
336
|
puts "-----> Testing instance #{name}"
|
306
337
|
create
|
307
338
|
converge
|
339
|
+
setup
|
308
340
|
verify
|
309
341
|
puts " Testing of instance #{name} complete."
|
310
342
|
self
|
311
343
|
end
|
312
344
|
end
|
313
345
|
|
346
|
+
# Command string generator to interface with Jamie Runner (jr). The
|
347
|
+
# commands that are generated are safe to pass to an SSH command or as an
|
348
|
+
# unix command argument (escaped in single quotes).
|
349
|
+
class Jr
|
350
|
+
|
351
|
+
# Constructs a new jr command generator, given a suite name.
|
352
|
+
#
|
353
|
+
# @param [String] suite_name name of suite on which to operate
|
354
|
+
# (**Required**)
|
355
|
+
# @param [Hash] opts optional configuration
|
356
|
+
# @option opts [TrueClass, FalseClass] :use_sudo whether or not to invoke
|
357
|
+
# sudo before commands requiring root access (default: `true`)
|
358
|
+
def initialize(suite_name, opts = {:use_sudo => true})
|
359
|
+
validate_options(suite_name)
|
360
|
+
|
361
|
+
@suite_name = suite_name
|
362
|
+
@use_sudo = opts[:use_sudo]
|
363
|
+
end
|
364
|
+
|
365
|
+
# Returns a command string which installs the Jamie Runner (jr), installs
|
366
|
+
# all required jr plugins for the suite, and transfers all suite test
|
367
|
+
# files.
|
368
|
+
#
|
369
|
+
# If no work needs to be performed, for example if there are no tests for
|
370
|
+
# the given suite, then `nil` will be returned.
|
371
|
+
#
|
372
|
+
# @return [String] a command string to setup the test suite, or nil if no
|
373
|
+
# work needs to be performed
|
374
|
+
def setup_cmd
|
375
|
+
@setup_cmd ||= begin
|
376
|
+
cmd = []
|
377
|
+
cmd << install_cmd if install_cmd
|
378
|
+
cmd << sync_cmd if sync_cmd
|
379
|
+
cmd.empty? ? nil : cmd.join("\n")
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Returns a command string which runs all jr suite tests for the suite.
|
384
|
+
#
|
385
|
+
# If no work needs to be performed, for example if there are no tests for
|
386
|
+
# the given suite, then `nil` will be returned.
|
387
|
+
#
|
388
|
+
# @return [String] a command string to run the test suites, or nil if no
|
389
|
+
# work needs to be performed
|
390
|
+
def run_cmd
|
391
|
+
@run_cmd ||= if plugins.empty?
|
392
|
+
nil
|
393
|
+
else
|
394
|
+
plugins.map { |p| "#{sudo}#{jr_bin} #{p}" }.join(' && ')
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
private
|
399
|
+
|
400
|
+
INSTALL_URL = "https://raw.github.com/jamie-ci/jr/go".freeze
|
401
|
+
DEFAULT_RUBY_BINPATH = "/opt/chef/embedded/bin".freeze
|
402
|
+
DEFAULT_JR_ROOT = "/opt/jr".freeze
|
403
|
+
DEFAULT_TEST_ROOT = File.join(Dir.pwd, "test/integration").freeze
|
404
|
+
|
405
|
+
def validate_options(suite_name)
|
406
|
+
raise ArgumentError, "'suite_name' is required." if suite_name.nil?
|
407
|
+
end
|
408
|
+
|
409
|
+
def install_cmd
|
410
|
+
@install_cmd ||= if plugins.empty?
|
411
|
+
nil
|
412
|
+
else
|
413
|
+
<<-INSTALL_CMD.gsub(/ {10}/, '')
|
414
|
+
#{sudo}#{ruby_bin} -e "$(cat <<"EOF"
|
415
|
+
#{install_script}
|
416
|
+
EOF
|
417
|
+
)"
|
418
|
+
#{sudo}#{jr_bin} install #{plugins.join(' ')}
|
419
|
+
INSTALL_CMD
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def sync_cmd
|
424
|
+
@sync_cmd ||= if local_suite_files.empty?
|
425
|
+
nil
|
426
|
+
else
|
427
|
+
[ "#{sudo}rm -rf $(#{jr_bin} suitepath)/*",
|
428
|
+
local_suite_files.map { |f| stream_file(f, remote_file(f)) }.join
|
429
|
+
].join("\n")
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
def install_script
|
434
|
+
@install_script ||= begin
|
435
|
+
uri = URI.parse(INSTALL_URL)
|
436
|
+
http = Net::HTTP.new(uri.host, 443)
|
437
|
+
http.use_ssl = true
|
438
|
+
response = http.request(Net::HTTP::Get.new(uri.path))
|
439
|
+
response.body
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def plugins
|
444
|
+
Dir.glob(File.join(test_root, @suite_name, "*")).select { |d|
|
445
|
+
File.directory?(d) && File.basename(d) != "data_bags"
|
446
|
+
}.map { |d| File.basename(d) }.sort.uniq
|
447
|
+
end
|
448
|
+
|
449
|
+
def local_suite_files
|
450
|
+
Dir.glob(File.join(test_root, @suite_name, "*/**/*")).reject do |f|
|
451
|
+
f["data_bags"] || File.directory?(f)
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
def remote_file(file)
|
456
|
+
local_prefix = File.join(test_root, @suite_name)
|
457
|
+
"$(#{jr_bin} suitepath)/".concat(file.sub(%r{^#{local_prefix}/}, ''))
|
458
|
+
end
|
459
|
+
|
460
|
+
def stream_file(local_path, remote_path)
|
461
|
+
local_file = IO.read(local_path)
|
462
|
+
md5 = Digest::MD5.hexdigest(local_file)
|
463
|
+
perms = sprintf("%o", File.stat(local_path).mode)[3,3]
|
464
|
+
jr_stream_file = "#{jr_bin} stream-file #{remote_path} #{md5} #{perms}"
|
465
|
+
|
466
|
+
<<-STREAMFILE.gsub(/^ {8}/, '')
|
467
|
+
cat <<"__EOFSTREAM__" | #{sudo}#{jr_stream_file}
|
468
|
+
#{Base64.encode64(local_file)}
|
469
|
+
__EOFSTREAM__
|
470
|
+
STREAMFILE
|
471
|
+
end
|
472
|
+
|
473
|
+
def sudo
|
474
|
+
@use_sudo ? "sudo " : ""
|
475
|
+
end
|
476
|
+
|
477
|
+
def ruby_bin
|
478
|
+
File.join(DEFAULT_RUBY_BINPATH, "ruby")
|
479
|
+
end
|
480
|
+
|
481
|
+
def jr_bin
|
482
|
+
File.join(DEFAULT_JR_ROOT, "bin/jr")
|
483
|
+
end
|
484
|
+
|
485
|
+
def test_root
|
486
|
+
DEFAULT_TEST_ROOT
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
314
490
|
module Driver
|
315
491
|
|
316
492
|
# Wrapped exception for any internally raised driver exceptions.
|
@@ -365,6 +541,11 @@ module Jamie
|
|
365
541
|
# @raise [ActionFailed] if the action could not be completed
|
366
542
|
def destroy(instance) ; end
|
367
543
|
|
544
|
+
def setup(instance)
|
545
|
+
# Subclass may choose to implement
|
546
|
+
puts " Nothing to do!"
|
547
|
+
end
|
548
|
+
|
368
549
|
# Verifies a converged instance.
|
369
550
|
#
|
370
551
|
# @param instance [Instance] an instance
|
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.alpha3
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: mixlib-shellout
|
@@ -140,9 +140,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
140
140
|
- - ! '>='
|
141
141
|
- !ruby/object:Gem::Version
|
142
142
|
version: '0'
|
143
|
-
segments:
|
144
|
-
- 0
|
145
|
-
hash: -4553058254287566395
|
146
143
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
147
144
|
none: false
|
148
145
|
requirements:
|