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 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
@@ -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
- :data_bags_base_path, :data_bags_base_path=, :yaml_data
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.data_bags_base_path
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Jamie
4
4
 
5
- VERSION = "0.1.0.alpha2"
5
+ VERSION = "0.1.0.alpha3"
6
6
  end
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 :data_bags_base_path
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
- DEFAULT_DATA_BAGS_BASE_PATH = File.join(Dir.pwd, 'test/integration').freeze
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 data_bags_base_path
72
- @data_bags_path ||= DEFAULT_DATA_BAGS_BASE_PATH
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(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."
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(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."
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
- # Verifies this converged instance by executing tests.
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.alpha2
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-03 00:00:00.000000000 Z
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: