jamie 0.1.0.alpha2 → 0.1.0.alpha3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://secure.travis-ci.org/jamie-ci/jamie.png)](https://travis-ci.org/jamie-ci/jamie)
|
4
|
+
[![Code Climate](https://codeclimate.com/badge.png)](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:
|