jamie 0.1.0.beta1 → 0.1.0.beta2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/jamie.rb +104 -42
- data/lib/jamie/cli.rb +29 -2
- data/lib/jamie/rake_tasks.rb +1 -0
- data/lib/jamie/thor_tasks.rb +1 -0
- data/lib/jamie/version.rb +1 -1
- data/spec/jamie_spec.rb +5 -5
- metadata +2 -2
data/lib/jamie.rb
CHANGED
@@ -65,6 +65,38 @@ module Jamie
|
|
65
65
|
end
|
66
66
|
end
|
67
67
|
|
68
|
+
module Error ; end
|
69
|
+
|
70
|
+
# Base exception class from which all Jamie exceptions derive. This class
|
71
|
+
# nests an exception when this class is re-raised from a rescue block.
|
72
|
+
class StandardError < ::StandardError
|
73
|
+
|
74
|
+
include Error
|
75
|
+
|
76
|
+
attr_reader :original
|
77
|
+
|
78
|
+
def initialize(msg, original = $!)
|
79
|
+
super(msg)
|
80
|
+
@original = original
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Base exception class for all exceptions that are caused by user input
|
85
|
+
# errors.
|
86
|
+
class UserError < StandardError ; end
|
87
|
+
|
88
|
+
# Base exception class for all exceptions that are caused by incorrect use
|
89
|
+
# of an API.
|
90
|
+
class ClientError < StandardError ; end
|
91
|
+
|
92
|
+
# Base exception class for exceptions that are caused by external library
|
93
|
+
# failures which may be temporary.
|
94
|
+
class TransientFailure < StandardError ; end
|
95
|
+
|
96
|
+
# Exception class for any exceptions raised when performing an instance
|
97
|
+
# action.
|
98
|
+
class ActionFailed < TransientFailure ; end
|
99
|
+
|
68
100
|
# Base configuration class for Jamie. This class exposes configuration such
|
69
101
|
# as the location of the Jamie YAML file, instances, log_levels, etc.
|
70
102
|
#
|
@@ -75,6 +107,7 @@ module Jamie
|
|
75
107
|
attr_writer :platforms
|
76
108
|
attr_writer :suites
|
77
109
|
attr_writer :log_level
|
110
|
+
attr_writer :supervised
|
78
111
|
attr_writer :test_base_path
|
79
112
|
|
80
113
|
# Default path to the Jamie YAML file
|
@@ -126,6 +159,10 @@ module Jamie
|
|
126
159
|
end
|
127
160
|
end
|
128
161
|
|
162
|
+
def supervised
|
163
|
+
@supervised.nil? ? @supervised = true : @supervised
|
164
|
+
end
|
165
|
+
|
129
166
|
# @return [String] base path that may contain a common `data_bags/`
|
130
167
|
# directory or an instance's `data_bags/` directory
|
131
168
|
def test_base_path
|
@@ -210,17 +247,28 @@ module Jamie
|
|
210
247
|
def new_instance(suite, platform, index)
|
211
248
|
platform_hash = platform_driver_hash(platform.name)
|
212
249
|
driver = new_driver(merge_driver_hash(platform_hash))
|
213
|
-
|
214
|
-
|
215
|
-
supervisor = Instance.supervise_as(
|
216
|
-
"instance_#{index}".to_sym,
|
250
|
+
actor_name = "instance_#{index}".to_sym
|
251
|
+
opts = {
|
217
252
|
:suite => suite,
|
218
253
|
:platform => platform,
|
219
254
|
:driver => driver,
|
220
255
|
:jr => Jr.new(suite.name),
|
221
256
|
:logger => new_instance_logger(index)
|
222
|
-
|
223
|
-
|
257
|
+
}
|
258
|
+
|
259
|
+
FileUtils.mkdir_p(log_root)
|
260
|
+
new_instance_supervised_or_not(actor_name, opts)
|
261
|
+
end
|
262
|
+
|
263
|
+
def new_instance_supervised_or_not(actor_name, opts)
|
264
|
+
if supervised
|
265
|
+
supervisor = Instance.supervise_as(actor_name, opts)
|
266
|
+
actor = supervisor.actors.first
|
267
|
+
Jamie.logger.debug("Supervising #{actor.to_str} with #{supervisor}")
|
268
|
+
actor
|
269
|
+
else
|
270
|
+
Celluloid::Actor[actor_name] = Instance.new(opts)
|
271
|
+
end
|
224
272
|
end
|
225
273
|
|
226
274
|
def log_root
|
@@ -505,7 +553,7 @@ module Jamie
|
|
505
553
|
|
506
554
|
def validate_options(opts)
|
507
555
|
[ :name, :run_list ].each do |k|
|
508
|
-
raise
|
556
|
+
raise ClientError, "Suite#new requires option :#{k}" if opts[k].nil?
|
509
557
|
end
|
510
558
|
end
|
511
559
|
end
|
@@ -546,7 +594,7 @@ module Jamie
|
|
546
594
|
|
547
595
|
def validate_options(opts)
|
548
596
|
[ :name ].each do |k|
|
549
|
-
raise
|
597
|
+
raise ClientError, "Platform#new requires option :#{k}" if opts[k].nil?
|
550
598
|
end
|
551
599
|
end
|
552
600
|
end
|
@@ -726,11 +774,15 @@ module Jamie
|
|
726
774
|
Kernel.exec(command, *args)
|
727
775
|
end
|
728
776
|
|
777
|
+
def last_action
|
778
|
+
load_state[:last_action]
|
779
|
+
end
|
780
|
+
|
729
781
|
private
|
730
782
|
|
731
783
|
def validate_options(opts)
|
732
784
|
[ :suite, :platform, :driver, :jr, :logger ].each do |k|
|
733
|
-
raise
|
785
|
+
raise ClientError, "Instance#new requires option :#{k}" if opts[k].nil?
|
734
786
|
end
|
735
787
|
end
|
736
788
|
|
@@ -785,8 +837,12 @@ module Jamie
|
|
785
837
|
elapsed = Benchmark.measure do
|
786
838
|
synchronize_or_call(what, state, &block)
|
787
839
|
end
|
788
|
-
state[:last_action] = what
|
840
|
+
state[:last_action] = what
|
789
841
|
elapsed
|
842
|
+
rescue ActionFailed
|
843
|
+
raise
|
844
|
+
rescue Exception => e
|
845
|
+
raise ActionFailed, "Failed to complete ##{what} action: [#{e.message}]"
|
790
846
|
ensure
|
791
847
|
dump_state(state)
|
792
848
|
end
|
@@ -828,10 +884,6 @@ module Jamie
|
|
828
884
|
))
|
829
885
|
end
|
830
886
|
|
831
|
-
def last_action
|
832
|
-
load_state[:last_action]
|
833
|
-
end
|
834
|
-
|
835
887
|
# The simplest finite state machine pseudo-implementation needed to manage
|
836
888
|
# an Instance.
|
837
889
|
#
|
@@ -952,7 +1004,7 @@ module Jamie
|
|
952
1004
|
DEFAULT_TEST_ROOT = File.join(Dir.pwd, "test/integration").freeze
|
953
1005
|
|
954
1006
|
def validate_options(suite_name)
|
955
|
-
raise
|
1007
|
+
raise ClientError, "Jr#new requires a suite_name" if suite_name.nil?
|
956
1008
|
end
|
957
1009
|
|
958
1010
|
def install_script
|
@@ -1063,7 +1115,7 @@ module Jamie
|
|
1063
1115
|
module ShellOut
|
1064
1116
|
|
1065
1117
|
# Wrapped exception for any interally raised shell out commands.
|
1066
|
-
class ShellCommandFailed <
|
1118
|
+
class ShellCommandFailed < TransientFailure ; end
|
1067
1119
|
|
1068
1120
|
# Executes a command in a subshell on the local running system.
|
1069
1121
|
#
|
@@ -1071,6 +1123,8 @@ module Jamie
|
|
1071
1123
|
# @param use_sudo [TrueClass, FalseClass] whether or not to use sudo
|
1072
1124
|
# @param log_subject [String] used in the output or log header for clarity
|
1073
1125
|
# and context
|
1126
|
+
# @raise [ShellCommandFailed] if the command fails
|
1127
|
+
# @raise [Error] for all other unexpected exceptions
|
1074
1128
|
def run_command(cmd, use_sudo = false, log_subject = "local")
|
1075
1129
|
cmd = "sudo #{cmd}" if use_sudo
|
1076
1130
|
subject = "[#{log_subject} command]"
|
@@ -1082,6 +1136,9 @@ module Jamie
|
|
1082
1136
|
sh.error!
|
1083
1137
|
rescue Mixlib::ShellOut::ShellCommandFailed => ex
|
1084
1138
|
raise ShellCommandFailed, ex.message
|
1139
|
+
rescue Exception => error
|
1140
|
+
error.extend(Jamie::Error)
|
1141
|
+
raise
|
1085
1142
|
end
|
1086
1143
|
|
1087
1144
|
private
|
@@ -1096,18 +1153,23 @@ module Jamie
|
|
1096
1153
|
|
1097
1154
|
module Driver
|
1098
1155
|
|
1099
|
-
# Wrapped exception for any internally raised driver exceptions.
|
1100
|
-
class ActionFailed < StandardError ; end
|
1101
|
-
|
1102
1156
|
# Returns an instance of a driver given a plugin type string.
|
1103
1157
|
#
|
1104
1158
|
# @param plugin [String] a driver plugin type, which will be constantized
|
1105
1159
|
# @return [Driver::Base] a driver instance
|
1160
|
+
# @raise [ClientError] if a driver instance could not be created
|
1106
1161
|
def self.for_plugin(plugin, config)
|
1107
1162
|
require "jamie/driver/#{plugin}"
|
1108
1163
|
|
1109
|
-
|
1164
|
+
str_const = Util.to_camel_case(plugin)
|
1165
|
+
klass = self.const_get(str_const)
|
1110
1166
|
klass.new(config)
|
1167
|
+
rescue LoadError
|
1168
|
+
raise ClientError, "Could not require '#{plugin}' plugin from load path"
|
1169
|
+
rescue NameError
|
1170
|
+
raise ClientError, "No class 'Jamie::Driver::#{str_const}' could be found"
|
1171
|
+
rescue
|
1172
|
+
raise ClientError, "Failed to create a driver for '#{plugin}' plugin"
|
1111
1173
|
end
|
1112
1174
|
|
1113
1175
|
# Base class for a driver. A driver is responsible for carrying out the
|
@@ -1218,7 +1280,7 @@ module Jamie
|
|
1218
1280
|
def self.no_parallel_for(*methods)
|
1219
1281
|
Array(methods).each do |meth|
|
1220
1282
|
if ! ACTION_METHODS.include?(meth)
|
1221
|
-
raise
|
1283
|
+
raise ClientError, "##{meth} is not a valid no_parallel_for method"
|
1222
1284
|
end
|
1223
1285
|
end
|
1224
1286
|
|
@@ -1236,7 +1298,7 @@ module Jamie
|
|
1236
1298
|
class SSHBase < Base
|
1237
1299
|
|
1238
1300
|
def create(state)
|
1239
|
-
raise
|
1301
|
+
raise ClientError, "#{self.class}#create must be implemented"
|
1240
1302
|
end
|
1241
1303
|
|
1242
1304
|
def converge(state)
|
@@ -1266,7 +1328,7 @@ module Jamie
|
|
1266
1328
|
end
|
1267
1329
|
|
1268
1330
|
def destroy(state)
|
1269
|
-
raise
|
1331
|
+
raise ClientError, "#{self.class}#destroy must be implemented"
|
1270
1332
|
end
|
1271
1333
|
|
1272
1334
|
def login_command(state)
|
@@ -1295,9 +1357,9 @@ module Jamie
|
|
1295
1357
|
end
|
1296
1358
|
|
1297
1359
|
def install_omnibus(ssh_args)
|
1298
|
-
ssh(ssh_args, <<-INSTALL)
|
1360
|
+
ssh(ssh_args, <<-INSTALL.gsub(/^ {10}/, ''))
|
1299
1361
|
if [ ! -d "/opt/chef" ] ; then
|
1300
|
-
curl -
|
1362
|
+
curl -sSL https://www.opscode.com/chef/install.sh | sudo bash
|
1301
1363
|
fi
|
1302
1364
|
INSTALL
|
1303
1365
|
end
|
@@ -1320,6 +1382,7 @@ module Jamie
|
|
1320
1382
|
end
|
1321
1383
|
|
1322
1384
|
def ssh(ssh_args, cmd)
|
1385
|
+
debug("[SSH] #{ssh_args[1]}@#{ssh_args[0]} (#{cmd})")
|
1323
1386
|
Net::SSH.start(*ssh_args) do |ssh|
|
1324
1387
|
exit_code = ssh_exec_with_exit!(ssh, cmd)
|
1325
1388
|
|
@@ -1336,6 +1399,9 @@ module Jamie
|
|
1336
1399
|
def ssh_exec_with_exit!(ssh, cmd)
|
1337
1400
|
exit_code = nil
|
1338
1401
|
ssh.open_channel do |channel|
|
1402
|
+
|
1403
|
+
channel.request_pty
|
1404
|
+
|
1339
1405
|
channel.exec(cmd) do |ch, success|
|
1340
1406
|
|
1341
1407
|
channel.on_data do |ch, data|
|
@@ -1462,44 +1528,40 @@ module Jamie
|
|
1462
1528
|
|
1463
1529
|
def prepare_tmpdir(tmpdir)
|
1464
1530
|
if File.exists?(File.join(jamie_root, "Berksfile"))
|
1465
|
-
|
1531
|
+
run_resolver("Berkshelf", "berks", tmpdir)
|
1466
1532
|
elsif File.exists?(File.join(jamie_root, "Cheffile"))
|
1467
|
-
|
1533
|
+
run_resolver("Librarian", "librarian-chef", tmpdir)
|
1468
1534
|
elsif File.directory?(File.join(jamie_root, "cookbooks"))
|
1469
1535
|
cp_cookbooks(tmpdir)
|
1470
1536
|
else
|
1471
1537
|
FileUtils.rmtree(tmpdir)
|
1472
|
-
|
1538
|
+
fatal("Berksfile, Cheffile or cookbooks/ must exist in #{jamie_root}")
|
1539
|
+
raise UserError, "Cookbooks could not be found"
|
1473
1540
|
end
|
1474
1541
|
end
|
1475
1542
|
|
1476
|
-
def
|
1543
|
+
def run_resolver(name, bin, tmpdir)
|
1477
1544
|
begin
|
1478
|
-
run_command "if ! command -v
|
1545
|
+
run_command "if ! command -v #{bin} >/dev/null; then exit 1; fi"
|
1479
1546
|
rescue Jamie::ShellOut::ShellCommandFailed
|
1480
|
-
|
1547
|
+
fatal("#{name} must be installed, add it to your Gemfile.")
|
1548
|
+
raise UserError, "#{bin} command not found"
|
1481
1549
|
end
|
1482
|
-
run_command "
|
1550
|
+
run_command "#{bin} install --path #{tmpdir}"
|
1483
1551
|
end
|
1484
1552
|
|
1485
|
-
def
|
1486
|
-
|
1487
|
-
|
1488
|
-
rescue Jamie::ShellOut::ShellCommandFailed
|
1489
|
-
abort ">>>>>> Librarian must be installed, add it to your Gemfile."
|
1490
|
-
end
|
1491
|
-
run_command "librarian-chef install --path #{tmpdir}"
|
1553
|
+
def cp_cookbooks(tmpdir)
|
1554
|
+
FileUtils.cp_r(File.join(jamie_root, "cookbooks", "."), tmpdir)
|
1555
|
+
cp_this_cookbook(tmpdir) if File.exists?(File.expand_path('metadata.rb'))
|
1492
1556
|
end
|
1493
1557
|
|
1494
|
-
def
|
1558
|
+
def cp_this_cookbook(tmpdir)
|
1495
1559
|
metadata_rb = File.join(jamie_root, "metadata.rb")
|
1496
1560
|
cb_name = MetadataChopper.extract(metadata_rb).first
|
1497
|
-
abort ">>>>>> name attribute must be set in metadata.rb." if cb_name.nil?
|
1498
1561
|
cb_path = File.join(tmpdir, cb_name)
|
1499
1562
|
glob = Dir.glob("#{jamie_root}/{metadata.rb,README.*," +
|
1500
1563
|
"attributes,files,libraries,providers,recipes,resources,templates}")
|
1501
1564
|
|
1502
|
-
FileUtils.cp_r(File.join(jamie_root, "cookbooks", "."), tmpdir)
|
1503
1565
|
FileUtils.mkdir_p(cb_path)
|
1504
1566
|
FileUtils.cp_r(glob, cb_path)
|
1505
1567
|
end
|
data/lib/jamie/cli.rb
CHANGED
@@ -37,12 +37,23 @@ module Jamie
|
|
37
37
|
super
|
38
38
|
$stdout.sync = true
|
39
39
|
@config = Jamie::Config.new(ENV['JAMIE_YAML'])
|
40
|
+
@config.supervised = false
|
40
41
|
end
|
41
42
|
|
42
43
|
desc "list [(all|<REGEX>)]", "List all instances"
|
44
|
+
method_option :bare, :aliases => "-b", :type => :boolean,
|
45
|
+
:desc => "List the name of each instance only, one per line"
|
43
46
|
def list(*args)
|
44
47
|
result = parse_subcommand(args.first)
|
45
|
-
|
48
|
+
if options[:bare]
|
49
|
+
say Array(result).map{ |i| i.name }.join("\n")
|
50
|
+
else
|
51
|
+
table = [
|
52
|
+
[ set_color("Instance", :green), set_color("Last Action", :green) ]
|
53
|
+
]
|
54
|
+
table += Array(result).map { |i| display_instance(i) }
|
55
|
+
print_table(table)
|
56
|
+
end
|
46
57
|
end
|
47
58
|
|
48
59
|
[:create, :converge, :setup, :verify, :destroy].each do |action|
|
@@ -189,6 +200,18 @@ module Jamie
|
|
189
200
|
result
|
190
201
|
end
|
191
202
|
|
203
|
+
def display_instance(instance)
|
204
|
+
action = case instance.last_action
|
205
|
+
when :create then set_color("Created", :cyan)
|
206
|
+
when :converge then set_color("Converged", :magenta)
|
207
|
+
when :setup then set_color("Set Up", :blue)
|
208
|
+
when :verify then set_color("Verified", :yellow)
|
209
|
+
when nil then set_color("<Not Created>", :red)
|
210
|
+
else set_color("<Unknown>", :white)
|
211
|
+
end
|
212
|
+
[ set_color(instance.name, :white), action ]
|
213
|
+
end
|
214
|
+
|
192
215
|
def die(task, msg)
|
193
216
|
error "\n#{msg}\n\n"
|
194
217
|
help(task)
|
@@ -266,7 +289,11 @@ module Jamie
|
|
266
289
|
}
|
267
290
|
end
|
268
291
|
end.flatten
|
269
|
-
cookbook_name =
|
292
|
+
cookbook_name = if File.exists?(File.expand_path('metadata.rb'))
|
293
|
+
MetadataChopper.extract('metadata.rb').first
|
294
|
+
else
|
295
|
+
nil
|
296
|
+
end
|
270
297
|
run_list = cookbook_name ? "recipe[#{cookbook_name}]" : nil
|
271
298
|
attributes = cookbook_name ? { cookbook_name => nil } : nil
|
272
299
|
|
data/lib/jamie/rake_tasks.rb
CHANGED
data/lib/jamie/thor_tasks.rb
CHANGED
data/lib/jamie/version.rb
CHANGED
data/spec/jamie_spec.rb
CHANGED
@@ -314,12 +314,12 @@ describe Jamie::Suite do
|
|
314
314
|
|
315
315
|
it "raises an ArgumentError if name is missing" do
|
316
316
|
opts.delete(:name)
|
317
|
-
proc { Jamie::Suite.new(opts) }.must_raise
|
317
|
+
proc { Jamie::Suite.new(opts) }.must_raise Jamie::ClientError
|
318
318
|
end
|
319
319
|
|
320
320
|
it "raises an ArgumentError if run_list is missing" do
|
321
321
|
opts.delete(:run_list)
|
322
|
-
proc { Jamie::Suite.new(opts) }.must_raise
|
322
|
+
proc { Jamie::Suite.new(opts) }.must_raise Jamie::ClientError
|
323
323
|
end
|
324
324
|
|
325
325
|
it "returns an empty Hash given no attributes" do
|
@@ -352,7 +352,7 @@ describe Jamie::Platform do
|
|
352
352
|
|
353
353
|
it "raises an ArgumentError if name is missing" do
|
354
354
|
opts.delete(:name)
|
355
|
-
proc { Jamie::Platform.new(opts) }.must_raise
|
355
|
+
proc { Jamie::Platform.new(opts) }.must_raise Jamie::ClientError
|
356
356
|
end
|
357
357
|
|
358
358
|
it "returns an empty Array given no run_list" do
|
@@ -399,12 +399,12 @@ describe Jamie::Instance do
|
|
399
399
|
|
400
400
|
it "raises an ArgumentError if suite is missing" do
|
401
401
|
opts.delete(:suite)
|
402
|
-
proc { Jamie::Instance.new(opts) }.must_raise
|
402
|
+
proc { Jamie::Instance.new(opts) }.must_raise Jamie::ClientError
|
403
403
|
end
|
404
404
|
|
405
405
|
it "raises an ArgumentError if platform is missing" do
|
406
406
|
opts.delete(:platform)
|
407
|
-
proc { Jamie::Instance.new(opts) }.must_raise
|
407
|
+
proc { Jamie::Instance.new(opts) }.must_raise Jamie::ClientError
|
408
408
|
end
|
409
409
|
|
410
410
|
it "returns suite" do
|
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.beta2
|
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: 2013-01-
|
12
|
+
date: 2013-01-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: celluloid
|