knife-ec2 0.5.14 → 0.6.0

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.
@@ -16,7 +16,7 @@ Gem::Specification.new do |s|
16
16
  s.files = `git ls-files`.split("\n")
17
17
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
- s.add_dependency "fog", "~> 1.3"
19
+ s.add_dependency "fog", "~> 1.6"
20
20
  s.add_dependency "chef", ">= 0.10.10"
21
21
  %w(rspec-core rspec-expectations rspec-mocks rspec_junit_formatter).each { |gem| s.add_development_dependency gem }
22
22
 
@@ -49,7 +49,6 @@ class Chef
49
49
  option :region,
50
50
  :long => "--region REGION",
51
51
  :description => "Your AWS region",
52
- :default => "us-east-1",
53
52
  :proc => Proc.new { |key| Chef::Config[:knife][:region] = key }
54
53
  end
55
54
  end
@@ -42,8 +42,7 @@ class Chef
42
42
  :short => "-f FLAVOR",
43
43
  :long => "--flavor FLAVOR",
44
44
  :description => "The flavor of server (m1.small, m1.medium, etc)",
45
- :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f },
46
- :default => "m1.small"
45
+ :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f }
47
46
 
48
47
  option :image,
49
48
  :short => "-I IMAGE",
@@ -73,13 +72,13 @@ class Chef
73
72
  :short => "-Z ZONE",
74
73
  :long => "--availability-zone ZONE",
75
74
  :description => "The Availability Zone",
76
- :default => "us-east-1b",
77
75
  :proc => Proc.new { |key| Chef::Config[:knife][:availability_zone] = key }
78
76
 
79
77
  option :chef_node_name,
80
78
  :short => "-N NAME",
81
79
  :long => "--node-name NAME",
82
- :description => "The Chef node name for your new node"
80
+ :description => "The Chef node name for your new node",
81
+ :proc => Proc.new { |key| Chef::Config[:knife][:chef_node_name] = key }
83
82
 
84
83
  option :ssh_key_name,
85
84
  :short => "-S KEY",
@@ -105,6 +104,13 @@ class Chef
105
104
  :default => "22",
106
105
  :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
107
106
 
107
+ option :ssh_gateway,
108
+ :short => "-w GATEWAY",
109
+ :long => "--ssh-gateway GATEWAY",
110
+ :description => "The ssh gateway server",
111
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_gateway] = key }
112
+
113
+
108
114
  option :identity_file,
109
115
  :short => "-i IDENTITY_FILE",
110
116
  :long => "--identity-file IDENTITY_FILE",
@@ -123,8 +129,7 @@ class Chef
123
129
  :short => "-d DISTRO",
124
130
  :long => "--distro DISTRO",
125
131
  :description => "Bootstrap a distro using a template; default is 'chef-full'",
126
- :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
127
- :default => "chef-full"
132
+ :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d }
128
133
 
129
134
  option :template_file,
130
135
  :long => "--template-file TEMPLATE",
@@ -136,30 +141,31 @@ class Chef
136
141
  :long => "--ebs-size SIZE",
137
142
  :description => "The size of the EBS volume in GB, for EBS-backed instances"
138
143
 
144
+ option :ebs_optimized,
145
+ :long => "--ebs-optimized",
146
+ :description => "Enabled optimized EBS I/O"
147
+
139
148
  option :ebs_no_delete_on_term,
140
149
  :long => "--ebs-no-delete-on-term",
141
- :description => "Do not delete EBS volumn on instance termination"
150
+ :description => "Do not delete EBS volume on instance termination"
142
151
 
143
152
  option :run_list,
144
153
  :short => "-r RUN_LIST",
145
154
  :long => "--run-list RUN_LIST",
146
155
  :description => "Comma separated list of roles/recipes to apply",
147
- :proc => lambda { |o| o.split(/[\s,]+/) },
148
- :default => []
156
+ :proc => lambda { |o| o.split(/[\s,]+/) }
149
157
 
150
158
  option :json_attributes,
151
159
  :short => "-j JSON",
152
160
  :long => "--json-attributes JSON",
153
161
  :description => "A JSON string to be added to the first run of chef-client",
154
- :proc => lambda { |o| JSON.parse(o) },
155
- :default => {}
156
-
162
+ :proc => lambda { |o| JSON.parse(o) }
157
163
 
158
164
  option :subnet_id,
159
165
  :short => "-s SUBNET-ID",
160
166
  :long => "--subnet SUBNET-ID",
161
167
  :description => "create node in this Virtual Private Cloud Subnet ID (implies VPC mode)",
162
- :default => false
168
+ :proc => Proc.new { |key| Chef::Config[:knife][:subnet_id] = key }
163
169
 
164
170
  option :host_key_verify,
165
171
  :long => "--[no-]host-key-verify",
@@ -174,8 +180,29 @@ class Chef
174
180
  :proc => Proc.new { |m| Chef::Config[:knife][:aws_user_data] = m },
175
181
  :default => nil
176
182
 
177
- def tcp_test_ssh(hostname)
178
- tcp_socket = TCPSocket.new(hostname, config[:ssh_port])
183
+ Chef::Config[:knife][:hints] ||= {"ec2" => {}}
184
+ option :hint,
185
+ :long => "--hint HINT_NAME[=HINT_FILE]",
186
+ :description => "Specify Ohai Hint to be set on the bootstrap target. Use multiple --hint options to specify multiple hints.",
187
+ :proc => Proc.new { |h|
188
+ name, path = h.split("=")
189
+ Chef::Config[:knife][:hints][name] = path ? JSON.parse(::File.read(path)) : Hash.new
190
+ }
191
+
192
+ option :ephemeral,
193
+ :long => "--ephemeral EPHEMERAL_DEVICES",
194
+ :description => "Comma separated list of device locations (eg - /dev/sdb) to map ephemeral devices",
195
+ :proc => lambda { |o| o.split(/[\s,]+/) },
196
+ :default => []
197
+
198
+ option :server_connect_attribute,
199
+ :long => "--server-connect-attribute ATTRIBUTE",
200
+ :short => "-a ATTRIBUTE",
201
+ :description => "The EC2 server attribute to use for SSH connection",
202
+ :default => nil
203
+
204
+ def tcp_test_ssh(hostname, ssh_port)
205
+ tcp_socket = TCPSocket.new(hostname, ssh_port)
179
206
  readable = IO.select([tcp_socket], nil, nil, 5)
180
207
  if readable
181
208
  Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
@@ -184,23 +211,10 @@ class Chef
184
211
  else
185
212
  false
186
213
  end
187
- rescue SocketError
214
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
188
215
  sleep 2
189
216
  false
190
- rescue Errno::ETIMEDOUT
191
- false
192
- rescue Errno::EPERM
193
- false
194
- rescue Errno::ECONNREFUSED
195
- sleep 2
196
- false
197
- # This happens on EC2 quite often
198
- rescue Errno::EHOSTUNREACH
199
- sleep 2
200
- false
201
- # This happens on EC2 sometimes
202
- rescue Errno::ENETUNREACH
203
- sleep 2
217
+ rescue Errno::EPERM, Errno::ETIMEDOUT
204
218
  false
205
219
  ensure
206
220
  tcp_socket && tcp_socket.close
@@ -236,7 +250,7 @@ class Chef
236
250
  # default security group id at this point unless we look it up, hence
237
251
  # 'default' is printed if no id was specified.
238
252
  printed_security_groups = "default"
239
- printed_security_groups = @server.groups.join(", ") if @server.groups
253
+ printed_security_groups = @server.groups.join(", ") if @server.groups
240
254
  msg_pair("Security Groups", printed_security_groups) unless vpc_mode? or (@server.groups.nil? and @server.security_group_ids)
241
255
 
242
256
  printed_security_group_ids = "default"
@@ -264,14 +278,9 @@ class Chef
264
278
 
265
279
  print "\n#{ui.color("Waiting for sshd", :magenta)}"
266
280
 
267
- fqdn = vpc_mode? ? @server.private_ip_address : @server.dns_name
281
+ wait_for_sshd(ssh_connect_host)
268
282
 
269
- print(".") until tcp_test_ssh(fqdn) {
270
- sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10)
271
- puts("done")
272
- }
273
-
274
- bootstrap_for_node(@server,fqdn).run
283
+ bootstrap_for_node(@server,ssh_connect_host).run
275
284
 
276
285
  puts "\n"
277
286
  msg_pair("Instance ID", @server.id)
@@ -300,6 +309,9 @@ class Chef
300
309
  end
301
310
  end
302
311
  end
312
+ if config[:ebs_optimized]
313
+ msg_pair("EBS is Optimized", @server.ebs_optimized.to_s)
314
+ end
303
315
  if vpc_mode?
304
316
  msg_pair("Subnet ID", @server.subnet_id)
305
317
  else
@@ -309,22 +321,23 @@ class Chef
309
321
  end
310
322
  msg_pair("Private IP Address", @server.private_ip_address)
311
323
  msg_pair("Environment", config[:environment] || '_default')
312
- msg_pair("Run List", config[:run_list].join(', '))
313
- msg_pair("JSON Attributes",config[:json_attributes]) unless config[:json_attributes].empty?
324
+ msg_pair("Run List", (config[:run_list] || []).join(', '))
325
+ msg_pair("JSON Attributes",config[:json_attributes]) unless !config[:json_attributes] || config[:json_attributes].empty?
314
326
  end
315
327
 
316
- def bootstrap_for_node(server,fqdn)
328
+ def bootstrap_for_node(server,ssh_host)
317
329
  bootstrap = Chef::Knife::Bootstrap.new
318
- bootstrap.name_args = [fqdn]
319
- bootstrap.config[:run_list] = config[:run_list]
330
+ bootstrap.name_args = [ssh_host]
331
+ bootstrap.config[:run_list] = locate_config_value(:run_list) || []
320
332
  bootstrap.config[:ssh_user] = config[:ssh_user]
321
333
  bootstrap.config[:ssh_port] = config[:ssh_port]
334
+ bootstrap.config[:ssh_gateway] = config[:ssh_gateway]
322
335
  bootstrap.config[:identity_file] = config[:identity_file]
323
- bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.id
336
+ bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server.id
324
337
  bootstrap.config[:prerelease] = config[:prerelease]
325
338
  bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
326
- bootstrap.config[:first_boot_attributes] = config[:json_attributes]
327
- bootstrap.config[:distro] = locate_config_value(:distro)
339
+ bootstrap.config[:first_boot_attributes] = locate_config_value(:json_attributes) || {}
340
+ bootstrap.config[:distro] = locate_config_value(:distro) || "chef-full"
328
341
  bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
329
342
  bootstrap.config[:template_file] = locate_config_value(:template_file)
330
343
  bootstrap.config[:environment] = config[:environment]
@@ -336,7 +349,7 @@ class Chef
336
349
  def vpc_mode?
337
350
  # Amazon Virtual Private Cloud requires a subnet_id. If
338
351
  # present, do a few things differently
339
- !!config[:subnet_id]
352
+ !!locate_config_value(:subnet_id)
340
353
  end
341
354
 
342
355
  def ami
@@ -351,7 +364,7 @@ class Chef
351
364
  ui.error("You have not provided a valid image (AMI) value. Please note the short option for this value recently changed from '-i' to '-I'.")
352
365
  exit 1
353
366
  end
354
-
367
+
355
368
  if vpc_mode? and !!config[:security_groups]
356
369
  ui.error("You are using a VPC, security groups specified with '-G' are not allowed, specify one or more security group ids with '-g' instead.")
357
370
  exit 1
@@ -372,12 +385,12 @@ class Chef
372
385
  server_def = {
373
386
  :image_id => locate_config_value(:image),
374
387
  :groups => config[:security_groups],
375
- :security_group_ids => config[:security_group_ids],
388
+ :security_group_ids => locate_config_value(:security_group_ids),
376
389
  :flavor_id => locate_config_value(:flavor),
377
390
  :key_name => Chef::Config[:knife][:aws_ssh_key_id],
378
391
  :availability_zone => locate_config_value(:availability_zone)
379
392
  }
380
- server_def[:subnet_id] = config[:subnet_id] if config[:subnet_id]
393
+ server_def[:subnet_id] = locate_config_value(:subnet_id) if vpc_mode?
381
394
 
382
395
  if Chef::Config[:knife][:aws_user_data]
383
396
  begin
@@ -387,6 +400,12 @@ class Chef
387
400
  end
388
401
  end
389
402
 
403
+ if config[:ebs_optimized]
404
+ server_def[:ebs_optimized] = "true"
405
+ else
406
+ server_def[:ebs_optimized] = "false"
407
+ end
408
+
390
409
  if ami.root_device_type == "ebs"
391
410
  ami_map = ami.block_device_mapping.first
392
411
  ebs_size = begin
@@ -405,6 +424,7 @@ class Chef
405
424
  else
406
425
  ami_map["deleteOnTermination"]
407
426
  end
427
+
408
428
  server_def[:block_device_mapping] =
409
429
  [{
410
430
  'DeviceName' => ami_map["deviceName"],
@@ -413,8 +433,55 @@ class Chef
413
433
  }]
414
434
  end
415
435
 
436
+ (config[:ephemeral] || []).each_with_index do |device_name, i|
437
+ server_def[:block_device_mapping] = (server_def[:block_device_mapping] || []) << {'VirtualName' => "ephemeral#{i}", 'DeviceName' => device_name}
438
+ end
439
+
416
440
  server_def
417
441
  end
442
+
443
+ def wait_for_sshd(hostname)
444
+ config[:ssh_gateway] ? wait_for_tunnelled_sshd(hostname) : wait_for_direct_sshd(hostname, config[:ssh_port])
445
+ end
446
+
447
+ def wait_for_tunnelled_sshd(hostname)
448
+ print(".")
449
+ print(".") until tunnel_test_ssh(ssh_connect_host) {
450
+ sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10)
451
+ puts("done")
452
+ }
453
+ end
454
+
455
+ def tunnel_test_ssh(hostname, &block)
456
+ gw_host, gw_user = config[:ssh_gateway].split('@').reverse
457
+ gw_host, gw_port = gw_host.split(':')
458
+ gateway = Net::SSH::Gateway.new(gw_host, gw_user, :port => gw_port || 22)
459
+ status = false
460
+ gateway.open(hostname, config[:ssh_port]) do |local_tunnel_port|
461
+ status = tcp_test_ssh('localhost', local_tunnel_port, &block)
462
+ end
463
+ status
464
+ rescue SocketError, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, IOError
465
+ sleep 2
466
+ false
467
+ rescue Errno::EPERM, Errno::ETIMEDOUT
468
+ false
469
+ end
470
+
471
+ def wait_for_direct_sshd(hostname, ssh_port)
472
+ print(".") until tcp_test_ssh(ssh_connect_host, ssh_port) {
473
+ sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10)
474
+ puts("done")
475
+ }
476
+ end
477
+
478
+ def ssh_connect_host
479
+ @ssh_connect_host ||= if config[:server_connect_attribute]
480
+ server.send(config[:server_connect_attribute])
481
+ else
482
+ vpc_mode? ? server.private_ip_address : server.dns_name
483
+ end
484
+ end
418
485
  end
419
486
  end
420
487
  end
@@ -27,6 +27,18 @@ class Chef
27
27
 
28
28
  banner "knife ec2 server list (options)"
29
29
 
30
+ option :name,
31
+ :short => "-n",
32
+ :long => "--no-name",
33
+ :boolean => true,
34
+ :default => true,
35
+ :description => "Do not display name tag in output"
36
+
37
+ option :tags,
38
+ :short => "-t TAG1,TAG2",
39
+ :long => "--tags TAG1,TAG2",
40
+ :description => "List of tags to output"
41
+
30
42
  def run
31
43
  $stdout.sync = true
32
44
 
@@ -34,22 +46,49 @@ class Chef
34
46
 
35
47
  server_list = [
36
48
  ui.color('Instance ID', :bold),
49
+
50
+ if config[:name]
51
+ ui.color("Name", :bold)
52
+ end,
53
+
37
54
  ui.color('Public IP', :bold),
38
55
  ui.color('Private IP', :bold),
39
56
  ui.color('Flavor', :bold),
40
57
  ui.color('Image', :bold),
41
58
  ui.color('SSH Key', :bold),
42
59
  ui.color('Security Groups', :bold),
60
+
61
+ if config[:tags]
62
+ config[:tags].split(",").collect do |tag_name|
63
+ ui.color("Tag:#{tag_name}", :bold)
64
+ end
65
+ end,
66
+
43
67
  ui.color('State', :bold)
44
- ]
68
+ ].flatten.compact
69
+
70
+ output_column_count = server_list.length
71
+
45
72
  connection.servers.all.each do |server|
46
73
  server_list << server.id.to_s
74
+
75
+ if config[:name]
76
+ server_list << server.tags["Name"].to_s
77
+ end
78
+
47
79
  server_list << server.public_ip_address.to_s
48
80
  server_list << server.private_ip_address.to_s
49
81
  server_list << server.flavor_id.to_s
50
82
  server_list << server.image_id.to_s
51
83
  server_list << server.key_name.to_s
52
84
  server_list << server.groups.join(", ")
85
+
86
+ if config[:tags]
87
+ config[:tags].split(",").each do |tag_name|
88
+ server_list << server.tags[tag_name].to_s
89
+ end
90
+ end
91
+
53
92
  server_list << begin
54
93
  state = server.state.to_s.downcase
55
94
  case state
@@ -62,11 +101,9 @@ class Chef
62
101
  end
63
102
  end
64
103
  end
65
- puts ui.list(server_list, :uneven_columns_across, 8)
104
+ puts ui.list(server_list, :uneven_columns_across, output_column_count)
66
105
 
67
106
  end
68
107
  end
69
108
  end
70
109
  end
71
-
72
-
@@ -1,6 +1,6 @@
1
1
  module Knife
2
2
  module Ec2
3
- VERSION = "0.5.14"
3
+ VERSION = "0.6.0"
4
4
  MAJOR, MINOR, TINY = VERSION.split('.')
5
5
  end
6
6
  end
@@ -128,6 +128,7 @@ describe Chef::Knife::Ec2ServerCreate do
128
128
  @knife_ec2_create.config[:ssh_user] = "ubuntu"
129
129
  @knife_ec2_create.config[:identity_file] = "~/.ssh/aws-key.pem"
130
130
  @knife_ec2_create.config[:ssh_port] = 22
131
+ @knife_ec2_create.config[:ssh_gateway] = 'bastion.host.com'
131
132
  @knife_ec2_create.config[:chef_node_name] = "blarf"
132
133
  @knife_ec2_create.config[:template_file] = '~/.chef/templates/my-bootstrap.sh.erb'
133
134
  @knife_ec2_create.config[:distro] = 'ubuntu-10.04-magic-sparkles'
@@ -153,6 +154,10 @@ describe Chef::Knife::Ec2ServerCreate do
153
154
  @bootstrap.config[:ssh_user].should == 'ubuntu'
154
155
  end
155
156
 
157
+ it "configures the bootstrap to use the correct ssh_gateway host" do
158
+ @bootstrap.config[:ssh_gateway].should == 'bastion.host.com'
159
+ end
160
+
156
161
  it "configures the bootstrap to use the correct ssh identity file" do
157
162
  @bootstrap.config[:identity_file].should == "~/.ssh/aws-key.pem"
158
163
  end
@@ -192,6 +197,10 @@ describe Chef::Knife::Ec2ServerCreate do
192
197
  it "configured the bootstrap to use the desired template" do
193
198
  @bootstrap.config[:template_file].should == '~/.chef/templates/my-bootstrap.sh.erb'
194
199
  end
200
+
201
+ it "configured the bootstrap to set an ec2 hint (via Chef::Config)" do
202
+ Chef::Config[:knife][:hints]["ec2"].should_not be_nil
203
+ end
195
204
  end
196
205
 
197
206
  describe "when validating the command-line parameters" do
@@ -251,6 +260,47 @@ describe Chef::Knife::Ec2ServerCreate do
251
260
 
252
261
  server_def[:availability_zone].should == "dis-one"
253
262
  end
263
+
264
+ it "adds the specified ephemeral device mappings" do
265
+ @knife_ec2_create.config[:ephemeral] = [ "/dev/sdb", "/dev/sdc", "/dev/sdd", "/dev/sde" ]
266
+ server_def = @knife_ec2_create.create_server_def
267
+
268
+ server_def[:block_device_mapping].should == [{ "VirtualName" => "ephemeral0", "DeviceName" => "/dev/sdb" },
269
+ { "VirtualName" => "ephemeral1", "DeviceName" => "/dev/sdc" },
270
+ { "VirtualName" => "ephemeral2", "DeviceName" => "/dev/sdd" },
271
+ { "VirtualName" => "ephemeral3", "DeviceName" => "/dev/sde" }]
272
+ end
254
273
  end
255
274
 
275
+ describe "ssh_connect_host" do
276
+ before(:each) do
277
+ @new_ec2_server.stub!(
278
+ :dns_name => 'public_name',
279
+ :private_ip_address => 'private_ip',
280
+ :custom => 'custom'
281
+ )
282
+ @knife_ec2_create.stub!(:server => @new_ec2_server)
283
+ end
284
+
285
+ describe "by default" do
286
+ it 'should use public dns name' do
287
+ @knife_ec2_create.ssh_connect_host.should == 'public_name'
288
+ end
289
+ end
290
+
291
+ describe "with vpc_mode?" do
292
+ it 'should use private ip' do
293
+ @knife_ec2_create.stub!(:vpc_mode? => true)
294
+ @knife_ec2_create.ssh_connect_host.should == 'private_ip'
295
+ end
296
+
297
+ end
298
+
299
+ describe "with custom server attribute" do
300
+ it 'should use custom server attribute' do
301
+ @knife_ec2_create.config[:server_connect_attribute] = 'custom'
302
+ @knife_ec2_create.ssh_connect_host.should == 'custom'
303
+ end
304
+ end
305
+ end
256
306
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: knife-ec2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.14
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-07-26 00:00:00.000000000 Z
13
+ date: 2012-10-16 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: fog
@@ -19,7 +19,7 @@ dependencies:
19
19
  requirements:
20
20
  - - ~>
21
21
  - !ruby/object:Gem::Version
22
- version: '1.3'
22
+ version: '1.6'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,7 +27,7 @@ dependencies:
27
27
  requirements:
28
28
  - - ~>
29
29
  - !ruby/object:Gem::Version
30
- version: '1.3'
30
+ version: '1.6'
31
31
  - !ruby/object:Gem::Dependency
32
32
  name: chef
33
33
  requirement: !ruby/object:Gem::Requirement
@@ -162,3 +162,4 @@ summary: EC2 Support for Chef's Knife Command
162
162
  test_files:
163
163
  - spec/spec_helper.rb
164
164
  - spec/unit/ec2_server_create_spec.rb
165
+ has_rdoc: true