jamie 0.1.0.beta1 → 0.1.0.beta2
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/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
|