knife-ec2 0.5.6 → 0.5.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +34 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/README.rdoc +4 -0
- data/Rakefile +56 -0
- data/knife-ec2.gemspec +22 -0
- data/lib/chef/knife/ec2_base.rb +98 -0
- data/lib/chef/knife/ec2_server_create.rb +129 -128
- data/lib/chef/knife/ec2_server_delete.rb +28 -52
- data/lib/chef/knife/ec2_server_list.rb +18 -34
- data/lib/knife-ec2/version.rb +5 -2
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/ec2_server_create_spec.rb +130 -0
- metadata +20 -30
data/.gitignore
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
.rake_tasks~
|
2
|
+
tags
|
3
|
+
coverage
|
4
|
+
rdoc
|
5
|
+
pkg
|
6
|
+
test/tmp
|
7
|
+
test/version_tmp
|
8
|
+
tmp
|
9
|
+
pkg
|
10
|
+
*.gem
|
11
|
+
*.rbc
|
12
|
+
lib/bundler/man
|
13
|
+
spec/reports
|
14
|
+
.config
|
15
|
+
InstalledFiles
|
16
|
+
.bundle
|
17
|
+
|
18
|
+
# YARD artifacts
|
19
|
+
.yardoc
|
20
|
+
_yardoc
|
21
|
+
doc/
|
22
|
+
|
23
|
+
.DS_Store
|
24
|
+
Icon?
|
25
|
+
|
26
|
+
# Thumbnails
|
27
|
+
._*
|
28
|
+
|
29
|
+
# Files that might appear on external disk
|
30
|
+
.Spotlight-V100
|
31
|
+
.Trashes
|
32
|
+
|
33
|
+
*.swp
|
34
|
+
*.swo
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.rdoc
CHANGED
@@ -6,6 +6,10 @@ This is the official Opscode Knife plugin for EC2. This plugin gives knife the a
|
|
6
6
|
|
7
7
|
= INSTALLATION:
|
8
8
|
|
9
|
+
Be sure you are running the latest version Chef. Versions earlier than 0.10.0 don't support plugins:
|
10
|
+
|
11
|
+
gem install chef
|
12
|
+
|
9
13
|
This plugin is distributed as a Ruby Gem. To install it, run:
|
10
14
|
|
11
15
|
gem install knife-ec2
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
+
# Author:: Daniel DeLeo (<dan@opscode.com>)
|
4
|
+
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
5
|
+
# Copyright:: Copyright (c) 2008, 2010 Opscode, Inc.
|
6
|
+
# License:: Apache License, Version 2.0
|
7
|
+
#
|
8
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
+
# you may not use this file except in compliance with the License.
|
10
|
+
# You may obtain a copy of the License at
|
11
|
+
#
|
12
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
+
#
|
14
|
+
# Unless required by applicable law or agreed to in writing, software
|
15
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
+
# See the License for the specific language governing permissions and
|
18
|
+
# limitations under the License.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'bundler'
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
# require 'rubygems'
|
25
|
+
# require 'rake/gempackagetask'
|
26
|
+
require 'rake/rdoctask'
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'sdoc'
|
30
|
+
|
31
|
+
Rake::RDocTask.new do |rdoc|
|
32
|
+
rdoc.title = "Chef Ruby API Documentation"
|
33
|
+
rdoc.main = "README.rdoc"
|
34
|
+
rdoc.options << '--fmt' << 'shtml' # explictly set shtml generator
|
35
|
+
rdoc.template = 'direct' # lighter template
|
36
|
+
rdoc.rdoc_files.include("README.rdoc", "LICENSE", "spec/tiny_server.rb", "lib/**/*.rb")
|
37
|
+
rdoc.rdoc_dir = "rdoc"
|
38
|
+
end
|
39
|
+
rescue LoadError
|
40
|
+
puts "sdoc is not available. (sudo) gem install sdoc to generate rdoc documentation."
|
41
|
+
end
|
42
|
+
|
43
|
+
begin
|
44
|
+
require 'rspec/core/rake_task'
|
45
|
+
|
46
|
+
task :default => :spec
|
47
|
+
|
48
|
+
desc "Run all specs in spec directory"
|
49
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
50
|
+
t.pattern = 'spec/unit/**/*_spec.rb'
|
51
|
+
end
|
52
|
+
|
53
|
+
rescue LoadError
|
54
|
+
STDERR.puts "\n*** RSpec not available. (sudo) gem install rspec to run unit tests. ***\n\n"
|
55
|
+
end
|
56
|
+
|
data/knife-ec2.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "knife-ec2/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "knife-ec2"
|
7
|
+
s.version = Knife::Ec2::VERSION
|
8
|
+
s.has_rdoc = true
|
9
|
+
s.authors = ["Adam Jacob","Seth Chisamore"]
|
10
|
+
s.email = ["adam@opscode.com","schisamo@opscode.com"]
|
11
|
+
s.homepage = "http://wiki.opscode.com/display/chef"
|
12
|
+
s.summary = "Rackspace Support for Chef's Knife Command"
|
13
|
+
s.description = s.summary
|
14
|
+
s.extra_rdoc_files = ["README.rdoc", "LICENSE" ]
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.add_dependency "fog", "~> 0.8.2"
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2011 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require 'chef/knife'
|
20
|
+
|
21
|
+
class Chef
|
22
|
+
class Knife
|
23
|
+
module Ec2Base
|
24
|
+
|
25
|
+
# :nodoc:
|
26
|
+
# Would prefer to do this in a rational way, but can't be done b/c of
|
27
|
+
# Mixlib::CLI's design :(
|
28
|
+
def self.included(includer)
|
29
|
+
includer.class_eval do
|
30
|
+
|
31
|
+
deps do
|
32
|
+
require 'fog'
|
33
|
+
require 'readline'
|
34
|
+
require 'chef/json_compat'
|
35
|
+
end
|
36
|
+
|
37
|
+
option :aws_access_key_id,
|
38
|
+
:short => "-A ID",
|
39
|
+
:long => "--aws-access-key-id KEY",
|
40
|
+
:description => "Your AWS Access Key ID",
|
41
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key }
|
42
|
+
|
43
|
+
option :aws_secret_access_key,
|
44
|
+
:short => "-K SECRET",
|
45
|
+
:long => "--aws-secret-access-key SECRET",
|
46
|
+
:description => "Your AWS API Secret Access Key",
|
47
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key }
|
48
|
+
|
49
|
+
option :region,
|
50
|
+
:long => "--region REGION",
|
51
|
+
:description => "Your AWS region",
|
52
|
+
:default => "us-east-1",
|
53
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:region] = key }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def connection
|
58
|
+
@connection ||= begin
|
59
|
+
connection = Fog::Compute.new(
|
60
|
+
:provider => 'AWS',
|
61
|
+
:aws_access_key_id => Chef::Config[:knife][:aws_access_key_id],
|
62
|
+
:aws_secret_access_key => Chef::Config[:knife][:aws_secret_access_key],
|
63
|
+
:region => locate_config_value(:region)
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def locate_config_value(key)
|
69
|
+
key = key.to_sym
|
70
|
+
Chef::Config[:knife][key] || config[key]
|
71
|
+
end
|
72
|
+
|
73
|
+
def msg_pair(label, value, color=:cyan)
|
74
|
+
if value && !value.empty?
|
75
|
+
puts "#{ui.color(label, color)}: #{value}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate!(keys=[:aws_access_key_id, :aws_secret_access_key])
|
80
|
+
errors = []
|
81
|
+
|
82
|
+
keys.each do |k|
|
83
|
+
pretty_key = k.to_s.gsub(/_/, ' ').gsub(/\w+/){ |w| (w =~ /(ssh)|(aws)/i) ? w.upcase : w.capitalize }
|
84
|
+
if Chef::Config[:knife][k].nil?
|
85
|
+
errors << "You did not provided a valid '#{pretty_key}' value."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
if errors.each{|e| ui.error(e)}.any?
|
90
|
+
exit 1
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
@@ -1,6 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
-
#
|
3
|
+
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
4
|
+
# Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
|
4
5
|
# License:: Apache License, Version 2.0
|
5
6
|
#
|
6
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -16,20 +17,20 @@
|
|
16
17
|
# limitations under the License.
|
17
18
|
#
|
18
19
|
|
19
|
-
require 'chef/knife'
|
20
|
+
require 'chef/knife/ec2_base'
|
20
21
|
|
21
22
|
class Chef
|
22
23
|
class Knife
|
23
24
|
class Ec2ServerCreate < Knife
|
24
25
|
|
26
|
+
include Knife::Ec2Base
|
27
|
+
|
25
28
|
deps do
|
26
|
-
require 'chef/knife/bootstrap'
|
27
|
-
Chef::Knife::Bootstrap.load_deps
|
28
29
|
require 'fog'
|
29
|
-
require 'socket'
|
30
|
-
require 'net/ssh/multi'
|
31
30
|
require 'readline'
|
32
31
|
require 'chef/json_compat'
|
32
|
+
require 'chef/knife/bootstrap'
|
33
|
+
Chef::Knife::Bootstrap.load_deps
|
33
34
|
end
|
34
35
|
|
35
36
|
banner "knife ec2 server create (options)"
|
@@ -90,18 +91,6 @@ class Chef
|
|
90
91
|
:long => "--identity-file IDENTITY_FILE",
|
91
92
|
:description => "The SSH identity file used for authentication"
|
92
93
|
|
93
|
-
option :aws_access_key_id,
|
94
|
-
:short => "-A ID",
|
95
|
-
:long => "--aws-access-key-id KEY",
|
96
|
-
:description => "Your AWS Access Key ID",
|
97
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key }
|
98
|
-
|
99
|
-
option :aws_secret_access_key,
|
100
|
-
:short => "-K SECRET",
|
101
|
-
:long => "--aws-secret-access-key SECRET",
|
102
|
-
:description => "Your AWS API Secret Access Key",
|
103
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key }
|
104
|
-
|
105
94
|
option :prerelease,
|
106
95
|
:long => "--prerelease",
|
107
96
|
:description => "Install the pre-release chef gems"
|
@@ -111,12 +100,6 @@ class Chef
|
|
111
100
|
:description => "The version of Chef to install",
|
112
101
|
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
113
102
|
|
114
|
-
option :region,
|
115
|
-
:long => "--region REGION",
|
116
|
-
:description => "Your AWS region",
|
117
|
-
:default => "us-east-1",
|
118
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:region] = key }
|
119
|
-
|
120
103
|
option :distro,
|
121
104
|
:short => "-d DISTRO",
|
122
105
|
:long => "--distro DISTRO",
|
@@ -157,6 +140,13 @@ class Chef
|
|
157
140
|
:boolean => true,
|
158
141
|
:default => false
|
159
142
|
|
143
|
+
option :aws_user_data,
|
144
|
+
:long => "--user-data USER_DATA_FILE",
|
145
|
+
:short => "-u USER_DATA_FILE",
|
146
|
+
:description => "The EC2 User Data file to provision the instance with",
|
147
|
+
:proc => Proc.new { |m| Chef::Config[:knife][:aws_user_data] = m },
|
148
|
+
:default => nil
|
149
|
+
|
160
150
|
def tcp_test_ssh(hostname)
|
161
151
|
tcp_socket = TCPSocket.new(hostname, 22)
|
162
152
|
readable = IO.select([tcp_socket], nil, nil, 5)
|
@@ -167,6 +157,9 @@ class Chef
|
|
167
157
|
else
|
168
158
|
false
|
169
159
|
end
|
160
|
+
rescue SocketError
|
161
|
+
sleep 2
|
162
|
+
false
|
170
163
|
rescue Errno::ETIMEDOUT
|
171
164
|
false
|
172
165
|
rescue Errno::EPERM
|
@@ -183,134 +176,87 @@ class Chef
|
|
183
176
|
end
|
184
177
|
|
185
178
|
def run
|
186
|
-
|
187
179
|
$stdout.sync = true
|
188
180
|
|
189
|
-
|
190
|
-
:provider => 'AWS',
|
191
|
-
:aws_access_key_id => Chef::Config[:knife][:aws_access_key_id],
|
192
|
-
:aws_secret_access_key => Chef::Config[:knife][:aws_secret_access_key],
|
193
|
-
:region => locate_config_value(:region)
|
194
|
-
)
|
195
|
-
|
196
|
-
ami = connection.images.get(locate_config_value(:image))
|
197
|
-
|
198
|
-
if ami.nil?
|
199
|
-
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'.")
|
200
|
-
exit 1
|
201
|
-
end
|
202
|
-
|
203
|
-
server_def = {
|
204
|
-
:image_id => locate_config_value(:image),
|
205
|
-
:groups => config[:security_groups],
|
206
|
-
:flavor_id => locate_config_value(:flavor),
|
207
|
-
:key_name => Chef::Config[:knife][:aws_ssh_key_id],
|
208
|
-
:availability_zone => Chef::Config[:knife][:availability_zone]
|
209
|
-
}
|
210
|
-
server_def[:subnet_id] = config[:subnet_id] if config[:subnet_id]
|
181
|
+
validate!
|
211
182
|
|
212
|
-
|
213
|
-
ami_map = ami.block_device_mapping.first
|
214
|
-
ebs_size = begin
|
215
|
-
if config[:ebs_size]
|
216
|
-
Integer(config[:ebs_size]).to_s
|
217
|
-
else
|
218
|
-
ami_map["volumeSize"].to_s
|
219
|
-
end
|
220
|
-
rescue ArgumentError
|
221
|
-
puts "--ebs-size must be an integer"
|
222
|
-
msg opt_parser
|
223
|
-
exit 1
|
224
|
-
end
|
225
|
-
delete_term = if config[:ebs_no_delete_on_term]
|
226
|
-
"false"
|
227
|
-
else
|
228
|
-
ami_map["deleteOnTermination"]
|
229
|
-
end
|
230
|
-
server_def[:block_device_mapping] =
|
231
|
-
[{
|
232
|
-
'DeviceName' => ami_map["deviceName"],
|
233
|
-
'Ebs.VolumeSize' => ebs_size,
|
234
|
-
'Ebs.DeleteOnTermination' => delete_term
|
235
|
-
}]
|
236
|
-
end
|
237
|
-
server = connection.servers.create(server_def)
|
183
|
+
server = connection.servers.create(create_server_def)
|
238
184
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
185
|
+
msg_pair("Instance ID", server.id)
|
186
|
+
msg_pair("Flavor", server.flavor_id)
|
187
|
+
msg_pair("Image", server.image_id)
|
188
|
+
msg_pair("Region", connection.instance_variable_get(:@region))
|
189
|
+
msg_pair("Availability Zone", server.availability_zone)
|
190
|
+
msg_pair("Security Groups", server.groups.join(", "))
|
191
|
+
msg_pair("SSH Key", server.key_name)
|
246
192
|
|
247
193
|
print "\n#{ui.color("Waiting for server", :magenta)}"
|
248
194
|
|
249
|
-
display_name = if vpc_mode?
|
250
|
-
server.private_ip_address
|
251
|
-
else
|
252
|
-
server.dns_name
|
253
|
-
end
|
254
|
-
|
255
195
|
# wait for it to be ready to do stuff
|
256
196
|
server.wait_for { print "."; ready? }
|
257
197
|
|
258
198
|
puts("\n")
|
259
|
-
|
260
|
-
if
|
261
|
-
|
262
|
-
|
263
|
-
|
199
|
+
|
200
|
+
if vpc_mode?
|
201
|
+
msg_pair("Subnet ID", server.subnet_id)
|
202
|
+
else
|
203
|
+
msg_pair("Public DNS Name", server.dns_name)
|
204
|
+
msg_pair("Public IP Address", server.public_ip_address)
|
205
|
+
msg_pair("Private DNS Name", server.private_dns_name)
|
264
206
|
end
|
265
|
-
|
207
|
+
msg_pair("Private IP Address", server.private_ip_address)
|
266
208
|
|
267
209
|
print "\n#{ui.color("Waiting for sshd", :magenta)}"
|
268
|
-
|
269
|
-
|
270
|
-
|
210
|
+
|
211
|
+
fqdn = vpc_mode? ? server.private_ip_address : server.dns_name
|
212
|
+
|
213
|
+
print(".") until tcp_test_ssh(fqdn) {
|
271
214
|
sleep @initial_sleep_delay ||= (vpc_mode? ? 40 : 10)
|
272
215
|
puts("done")
|
273
216
|
}
|
274
217
|
|
275
|
-
bootstrap_for_node(server).run
|
218
|
+
bootstrap_for_node(server,fqdn).run
|
276
219
|
|
277
220
|
puts "\n"
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
puts "#{ui.color("Public DNS Name", :cyan)}: #{server.dns_name}"
|
287
|
-
puts "#{ui.color("Public IP Address", :cyan)}: #{server.public_ip_address}"
|
288
|
-
puts "#{ui.color("Private DNS Name", :cyan)}: #{server.private_dns_name}"
|
289
|
-
end
|
290
|
-
puts "#{ui.color("SSH Key", :cyan)}: #{server.key_name}"
|
291
|
-
puts "#{ui.color("Private IP Address", :cyan)}: #{server.private_ip_address}"
|
292
|
-
puts "#{ui.color("Root Device Type", :cyan)}: #{server.root_device_type}"
|
221
|
+
msg_pair("Instance ID", server.id)
|
222
|
+
msg_pair("Flavor", server.flavor_id)
|
223
|
+
msg_pair("Image", server.image_id)
|
224
|
+
msg_pair("Region", connection.instance_variable_get(:@region))
|
225
|
+
msg_pair("Availability Zone", server.availability_zone)
|
226
|
+
msg_pair("Security Groups", server.groups.join(", "))
|
227
|
+
msg_pair("SSH Key", server.key_name)
|
228
|
+
msg_pair("Root Device Type", server.root_device_type)
|
293
229
|
if server.root_device_type == "ebs"
|
294
230
|
device_map = server.block_device_mapping.first
|
295
|
-
|
296
|
-
|
297
|
-
|
231
|
+
msg_pair("Root Volume ID", device_map['volumeId'])
|
232
|
+
msg_pair("Root Device Name", device_map['deviceName'])
|
233
|
+
msg_pair("Root Device Delete on Terminate", device_map['deleteOnTermination'])
|
234
|
+
|
298
235
|
if config[:ebs_size]
|
299
236
|
if ami.block_device_mapping.first['volumeSize'].to_i < config[:ebs_size].to_i
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
237
|
+
volume_too_large_warning = "#{config[:ebs_size]}GB " +
|
238
|
+
"EBS volume size is larger than size set in AMI of " +
|
239
|
+
"#{ami.block_device_mapping.first['volumeSize']}GB.\n" +
|
240
|
+
"Use file system tools to make use of the increased volume size."
|
241
|
+
msg_pair("Warning", volume_too_large_warning, :yellow)
|
304
242
|
end
|
305
243
|
end
|
306
244
|
end
|
307
|
-
|
308
|
-
|
245
|
+
if vpc_mode?
|
246
|
+
msg_pair("Subnet ID", server.subnet_id)
|
247
|
+
else
|
248
|
+
msg_pair("Public DNS Name", server.dns_name)
|
249
|
+
msg_pair("Public IP Address", server.public_ip_address)
|
250
|
+
msg_pair("Private DNS Name", server.private_dns_name)
|
251
|
+
end
|
252
|
+
msg_pair("Private IP Address", server.private_ip_address)
|
253
|
+
msg_pair("Environment", config[:environment] || '_default')
|
254
|
+
msg_pair("Run List", config[:run_list].join(', '))
|
309
255
|
end
|
310
256
|
|
311
|
-
def bootstrap_for_node(server)
|
257
|
+
def bootstrap_for_node(server,fqdn)
|
312
258
|
bootstrap = Chef::Knife::Bootstrap.new
|
313
|
-
bootstrap.name_args = [
|
259
|
+
bootstrap.name_args = [fqdn]
|
314
260
|
bootstrap.config[:run_list] = config[:run_list]
|
315
261
|
bootstrap.config[:ssh_user] = config[:ssh_user]
|
316
262
|
bootstrap.config[:identity_file] = config[:identity_file]
|
@@ -318,7 +264,7 @@ class Chef
|
|
318
264
|
bootstrap.config[:prerelease] = config[:prerelease]
|
319
265
|
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
320
266
|
bootstrap.config[:distro] = locate_config_value(:distro)
|
321
|
-
bootstrap.config[:use_sudo] = true
|
267
|
+
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
322
268
|
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
323
269
|
bootstrap.config[:environment] = config[:environment]
|
324
270
|
# may be needed for vpc_mode
|
@@ -326,17 +272,72 @@ class Chef
|
|
326
272
|
bootstrap
|
327
273
|
end
|
328
274
|
|
329
|
-
def locate_config_value(key)
|
330
|
-
key = key.to_sym
|
331
|
-
Chef::Config[:knife][key] || config[key]
|
332
|
-
end
|
333
|
-
|
334
275
|
def vpc_mode?
|
335
276
|
# Amazon Virtual Private Cloud requires a subnet_id. If
|
336
277
|
# present, do a few things differently
|
337
278
|
!!config[:subnet_id]
|
338
279
|
end
|
339
280
|
|
281
|
+
def ami
|
282
|
+
@ami ||= connection.images.get(locate_config_value(:image))
|
283
|
+
end
|
284
|
+
|
285
|
+
def validate!
|
286
|
+
|
287
|
+
super([:image, :aws_ssh_key_id, :aws_access_key_id, :aws_secret_access_key])
|
288
|
+
|
289
|
+
if ami.nil?
|
290
|
+
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'.")
|
291
|
+
exit 1
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
def create_server_def
|
296
|
+
server_def = {
|
297
|
+
:image_id => locate_config_value(:image),
|
298
|
+
:groups => config[:security_groups],
|
299
|
+
:flavor_id => locate_config_value(:flavor),
|
300
|
+
:key_name => Chef::Config[:knife][:aws_ssh_key_id],
|
301
|
+
:availability_zone => locate_config_value(:availability_zone)
|
302
|
+
}
|
303
|
+
server_def[:subnet_id] = config[:subnet_id] if config[:subnet_id]
|
304
|
+
|
305
|
+
if Chef::Config[:knife][:aws_user_data]
|
306
|
+
begin
|
307
|
+
server_def.merge!(:user_data => File.read(Chef::Config[:knife][:aws_user_data]))
|
308
|
+
rescue
|
309
|
+
ui.warn("Cannot read #{Chef::Config[:knife][:aws_user_data]}: #{$!.inspect}. Ignoring option.")
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
if ami.root_device_type == "ebs"
|
314
|
+
ami_map = ami.block_device_mapping.first
|
315
|
+
ebs_size = begin
|
316
|
+
if config[:ebs_size]
|
317
|
+
Integer(config[:ebs_size]).to_s
|
318
|
+
else
|
319
|
+
ami_map["volumeSize"].to_s
|
320
|
+
end
|
321
|
+
rescue ArgumentError
|
322
|
+
puts "--ebs-size must be an integer"
|
323
|
+
msg opt_parser
|
324
|
+
exit 1
|
325
|
+
end
|
326
|
+
delete_term = if config[:ebs_no_delete_on_term]
|
327
|
+
"false"
|
328
|
+
else
|
329
|
+
ami_map["deleteOnTermination"]
|
330
|
+
end
|
331
|
+
server_def[:block_device_mapping] =
|
332
|
+
[{
|
333
|
+
'DeviceName' => ami_map["deviceName"],
|
334
|
+
'Ebs.VolumeSize' => ebs_size,
|
335
|
+
'Ebs.DeleteOnTermination' => delete_term
|
336
|
+
}]
|
337
|
+
end
|
338
|
+
|
339
|
+
server_def
|
340
|
+
end
|
340
341
|
end
|
341
342
|
end
|
342
343
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
-
#
|
3
|
+
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
4
|
+
# Copyright:: Copyright (c) 2009-2011 Opscode, Inc.
|
4
5
|
# License:: Apache License, Version 2.0
|
5
6
|
#
|
6
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -16,73 +17,48 @@
|
|
16
17
|
# limitations under the License.
|
17
18
|
#
|
18
19
|
|
19
|
-
require 'chef/knife'
|
20
|
+
require 'chef/knife/ec2_base'
|
20
21
|
|
21
22
|
class Chef
|
22
23
|
class Knife
|
23
24
|
class Ec2ServerDelete < Knife
|
24
25
|
|
25
|
-
|
26
|
-
require 'fog'
|
27
|
-
require 'net/ssh/multi'
|
28
|
-
require 'readline'
|
29
|
-
require 'chef/json_compat'
|
30
|
-
end
|
26
|
+
include Knife::Ec2Base
|
31
27
|
|
32
28
|
banner "knife ec2 server delete SERVER [SERVER] (options)"
|
33
29
|
|
34
|
-
option :aws_access_key_id,
|
35
|
-
:short => "-A ID",
|
36
|
-
:long => "--aws-access-key-id KEY",
|
37
|
-
:description => "Your AWS Access Key ID",
|
38
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key }
|
39
|
-
|
40
|
-
option :aws_secret_access_key,
|
41
|
-
:short => "-K SECRET",
|
42
|
-
:long => "--aws-secret-access-key SECRET",
|
43
|
-
:description => "Your AWS API Secret Access Key",
|
44
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key }
|
45
|
-
|
46
|
-
option :region,
|
47
|
-
:long => "--region REGION",
|
48
|
-
:description => "Your AWS region",
|
49
|
-
:default => "us-east-1",
|
50
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:region] = key }
|
51
|
-
|
52
30
|
def run
|
53
|
-
|
54
|
-
|
55
|
-
:aws_access_key_id => Chef::Config[:knife][:aws_access_key_id],
|
56
|
-
:aws_secret_access_key => Chef::Config[:knife][:aws_secret_access_key],
|
57
|
-
:region => Chef::Config[:knife][:region] || config[:region]
|
58
|
-
)
|
31
|
+
|
32
|
+
validate!
|
59
33
|
|
60
34
|
@name_args.each do |instance_id|
|
61
|
-
server = connection.servers.get(instance_id)
|
62
35
|
|
63
|
-
|
64
|
-
|
65
|
-
msg("Image", server.image_id)
|
66
|
-
msg("Availability Zone", server.availability_zone)
|
67
|
-
msg("Security Groups", server.groups.join(", "))
|
68
|
-
msg("SSH Key", server.key_name)
|
69
|
-
msg("Public DNS Name", server.dns_name)
|
70
|
-
msg("Public IP Address", server.public_ip_address)
|
71
|
-
msg("Private DNS Name", server.private_dns_name)
|
72
|
-
msg("Private IP Address", server.private_ip_address)
|
36
|
+
begin
|
37
|
+
server = connection.servers.get(instance_id)
|
73
38
|
|
74
|
-
|
75
|
-
|
39
|
+
msg_pair("Instance ID", server.id)
|
40
|
+
msg_pair("Flavor", server.flavor_id)
|
41
|
+
msg_pair("Image", server.image_id)
|
42
|
+
msg_pair("Region", connection.instance_variable_get(:@region))
|
43
|
+
msg_pair("Availability Zone", server.availability_zone)
|
44
|
+
msg_pair("Security Groups", server.groups.join(", "))
|
45
|
+
msg_pair("SSH Key", server.key_name)
|
46
|
+
msg_pair("Root Device Type", server.root_device_type)
|
47
|
+
msg_pair("Public DNS Name", server.dns_name)
|
48
|
+
msg_pair("Public IP Address", server.public_ip_address)
|
49
|
+
msg_pair("Private DNS Name", server.private_dns_name)
|
50
|
+
msg_pair("Private IP Address", server.private_ip_address)
|
76
51
|
|
77
|
-
|
52
|
+
puts "\n"
|
53
|
+
confirm("Do you really want to delete this server")
|
78
54
|
|
79
|
-
|
80
|
-
|
81
|
-
|
55
|
+
server.destroy
|
56
|
+
|
57
|
+
ui.warn("Deleted server #{server.id}")
|
82
58
|
|
83
|
-
|
84
|
-
|
85
|
-
|
59
|
+
rescue NoMethodError
|
60
|
+
ui.error("Could not locate server '#{instance_id}'. Please verify it was provisioned in the '#{locate_config_value(:region)}' region.")
|
61
|
+
end
|
86
62
|
end
|
87
63
|
end
|
88
64
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
-
#
|
3
|
+
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
4
|
+
# Copyright:: Copyright (c) 2010-2011 Opscode, Inc.
|
4
5
|
# License:: Apache License, Version 2.0
|
5
6
|
#
|
6
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -16,48 +17,20 @@
|
|
16
17
|
# limitations under the License.
|
17
18
|
#
|
18
19
|
|
19
|
-
require 'chef/knife'
|
20
|
+
require 'chef/knife/ec2_base'
|
20
21
|
|
21
22
|
class Chef
|
22
23
|
class Knife
|
23
24
|
class Ec2ServerList < Knife
|
24
25
|
|
25
|
-
|
26
|
-
require 'fog'
|
27
|
-
require 'net/ssh/multi'
|
28
|
-
require 'readline'
|
29
|
-
require 'chef/json_compat'
|
30
|
-
end
|
26
|
+
include Knife::Ec2Base
|
31
27
|
|
32
28
|
banner "knife ec2 server list (options)"
|
33
29
|
|
34
|
-
option :aws_access_key_id,
|
35
|
-
:short => "-A ID",
|
36
|
-
:long => "--aws-access-key-id KEY",
|
37
|
-
:description => "Your AWS Access Key ID",
|
38
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key }
|
39
|
-
|
40
|
-
option :aws_secret_access_key,
|
41
|
-
:short => "-K SECRET",
|
42
|
-
:long => "--aws-secret-access-key SECRET",
|
43
|
-
:description => "Your AWS API Secret Access Key",
|
44
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key }
|
45
|
-
|
46
|
-
option :region,
|
47
|
-
:long => "--region REGION",
|
48
|
-
:description => "Your AWS region",
|
49
|
-
:default => "us-east-1",
|
50
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:region] = key }
|
51
|
-
|
52
30
|
def run
|
53
31
|
$stdout.sync = true
|
54
32
|
|
55
|
-
|
56
|
-
:provider => 'AWS',
|
57
|
-
:aws_access_key_id => Chef::Config[:knife][:aws_access_key_id],
|
58
|
-
:aws_secret_access_key => Chef::Config[:knife][:aws_secret_access_key],
|
59
|
-
:region => Chef::Config[:knife][:region] || config[:region]
|
60
|
-
)
|
33
|
+
validate!
|
61
34
|
|
62
35
|
server_list = [
|
63
36
|
ui.color('Instance ID', :bold),
|
@@ -65,6 +38,7 @@ class Chef
|
|
65
38
|
ui.color('Private IP', :bold),
|
66
39
|
ui.color('Flavor', :bold),
|
67
40
|
ui.color('Image', :bold),
|
41
|
+
ui.color('SSH Key', :bold),
|
68
42
|
ui.color('Security Groups', :bold),
|
69
43
|
ui.color('State', :bold)
|
70
44
|
]
|
@@ -74,10 +48,20 @@ class Chef
|
|
74
48
|
server_list << (server.private_ip_address == nil ? "" : server.private_ip_address)
|
75
49
|
server_list << (server.flavor_id == nil ? "" : server.flavor_id)
|
76
50
|
server_list << (server.image_id == nil ? "" : server.image_id)
|
51
|
+
server_list << server.key_name
|
77
52
|
server_list << server.groups.join(", ")
|
78
|
-
server_list <<
|
53
|
+
server_list << begin
|
54
|
+
case server.state.downcase
|
55
|
+
when 'shutting-down','terminated','stopping','stopped'
|
56
|
+
ui.color(server.state.downcase, :red)
|
57
|
+
when 'pending'
|
58
|
+
ui.color(server.state.downcase, :yellow)
|
59
|
+
else
|
60
|
+
ui.color(server.state.downcase, :green)
|
61
|
+
end
|
62
|
+
end
|
79
63
|
end
|
80
|
-
puts ui.list(server_list, :columns_across,
|
64
|
+
puts ui.list(server_list, :columns_across, 8)
|
81
65
|
|
82
66
|
end
|
83
67
|
end
|
data/lib/knife-ec2/version.rb
CHANGED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Thomas Bishop (<bishop.thomas@gmail.com>)
|
3
|
+
# Copyright:: Copyright (c) 2010 Thomas Bishop
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
|
19
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
20
|
+
|
21
|
+
describe Chef::Knife::Ec2ServerCreate do
|
22
|
+
before do
|
23
|
+
@knife_ec2_create = Chef::Knife::Ec2ServerCreate.new()
|
24
|
+
@knife_ec2_create.name_args = ['role[base]']
|
25
|
+
@knife_ec2_create.initial_sleep_delay = 0
|
26
|
+
@knife_ec2_create.stub!(:tcp_test_ssh).and_return(true)
|
27
|
+
|
28
|
+
@ec2_connection = mock()
|
29
|
+
@ec2_servers = mock()
|
30
|
+
@new_ec2_server = mock()
|
31
|
+
|
32
|
+
@ec2_server_attribs = { :id => 'i-39382318',
|
33
|
+
:flavor_id => 'm1.small',
|
34
|
+
:image_id => 'ami-47241231',
|
35
|
+
:availability_zone => 'us-west-1',
|
36
|
+
:key_name => 'my_ssh_key',
|
37
|
+
:groups => ['group1', 'group2'],
|
38
|
+
:dns_name => 'ec2-75.101.253.10.compute-1.amazonaws.com',
|
39
|
+
:ip_address => '75.101.253.10',
|
40
|
+
:private_dns_name => 'ip-10-251-75-20.ec2.internal',
|
41
|
+
:private_ip_address => '10.251.75.20' }
|
42
|
+
|
43
|
+
@ec2_server_attribs.each_pair do |attrib, value|
|
44
|
+
@new_ec2_server.stub!(attrib).and_return(value)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "run" do
|
49
|
+
|
50
|
+
it "creates an EC2 instance and bootstraps it" do
|
51
|
+
@new_ec2_server.should_receive(:wait_for).and_return(true)
|
52
|
+
@ec2_servers.should_receive(:create).and_return(@new_ec2_server)
|
53
|
+
@ec2_connection.should_receive(:servers).and_return(@ec2_servers)
|
54
|
+
|
55
|
+
Fog::AWS::Compute.should_receive(:new).and_return(@ec2_connection)
|
56
|
+
|
57
|
+
@knife_ec2_create.stub!(:puts)
|
58
|
+
@knife_ec2_create.stub!(:print)
|
59
|
+
@knife_ec2_create.config[:image] = '12345'
|
60
|
+
|
61
|
+
|
62
|
+
@bootstrap = Chef::Knife::Bootstrap.new
|
63
|
+
Chef::Knife::Bootstrap.stub!(:new).and_return(@bootstrap)
|
64
|
+
@bootstrap.should_receive(:run)
|
65
|
+
@knife_ec2_create.run
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "when configuring the bootstrap process" do
|
71
|
+
before do
|
72
|
+
@knife_ec2_create.config[:ssh_user] = "ubuntu"
|
73
|
+
@knife_ec2_create.config[:identity_file] = "~/.ssh/aws-key.pem"
|
74
|
+
@knife_ec2_create.config[:chef_node_name] = "blarf"
|
75
|
+
@knife_ec2_create.config[:template_file] = '~/.chef/templates/my-bootstrap.sh.erb'
|
76
|
+
@knife_ec2_create.config[:distro] = 'ubuntu-10.04-magic-sparkles'
|
77
|
+
|
78
|
+
@bootstrap = @knife_ec2_create.bootstrap_for_node(@new_ec2_server)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should set the bootstrap 'name argument' to the hostname of the EC2 server" do
|
82
|
+
@bootstrap.name_args.should == ['ec2-75.101.253.10.compute-1.amazonaws.com']
|
83
|
+
end
|
84
|
+
|
85
|
+
it "configures sets the bootstrap's run_list" do
|
86
|
+
@bootstrap.config[:run_list].should == ['role[base]']
|
87
|
+
end
|
88
|
+
|
89
|
+
it "configures the bootstrap to use the correct ssh_user login" do
|
90
|
+
@bootstrap.config[:ssh_user].should == 'ubuntu'
|
91
|
+
end
|
92
|
+
|
93
|
+
it "configures the bootstrap to use the correct ssh identity file" do
|
94
|
+
@bootstrap.config[:identity_file].should == "~/.ssh/aws-key.pem"
|
95
|
+
end
|
96
|
+
|
97
|
+
it "configures the bootstrap to use the configured node name if provided" do
|
98
|
+
@bootstrap.config[:chef_node_name].should == 'blarf'
|
99
|
+
end
|
100
|
+
|
101
|
+
it "configures the bootstrap to use the EC2 server id if no explicit node name is set" do
|
102
|
+
@knife_ec2_create.config[:chef_node_name] = nil
|
103
|
+
|
104
|
+
bootstrap = @knife_ec2_create.bootstrap_for_node(@new_ec2_server)
|
105
|
+
bootstrap.config[:chef_node_name].should == @new_ec2_server.id
|
106
|
+
end
|
107
|
+
|
108
|
+
it "configures the bootstrap to use prerelease versions of chef if specified" do
|
109
|
+
@bootstrap.config[:prerelease].should be_false
|
110
|
+
|
111
|
+
@knife_ec2_create.config[:prerelease] = true
|
112
|
+
|
113
|
+
bootstrap = @knife_ec2_create.bootstrap_for_node(@new_ec2_server)
|
114
|
+
bootstrap.config[:prerelease].should be_true
|
115
|
+
end
|
116
|
+
|
117
|
+
it "configures the bootstrap to use the desired distro-specific bootstrap script" do
|
118
|
+
@bootstrap.config[:distro].should == 'ubuntu-10.04-magic-sparkles'
|
119
|
+
end
|
120
|
+
|
121
|
+
it "configures the bootstrap to use sudo" do
|
122
|
+
@bootstrap.config[:use_sudo].should be_true
|
123
|
+
end
|
124
|
+
|
125
|
+
it "configured the bootstrap to use the desired template" do
|
126
|
+
@bootstrap.config[:template_file].should == '~/.chef/templates/my-bootstrap.sh.erb'
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
metadata
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
name: knife-ec2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 0.5.
|
5
|
+
version: 0.5.8
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Adam Jacob
|
9
|
+
- Seth Chisamore
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
13
|
|
13
|
-
date: 2011-
|
14
|
+
date: 2011-08-15 00:00:00 -04:00
|
14
15
|
default_executable:
|
15
16
|
dependencies:
|
16
17
|
- !ruby/object:Gem::Dependency
|
@@ -21,33 +22,13 @@ dependencies:
|
|
21
22
|
requirements:
|
22
23
|
- - ~>
|
23
24
|
- !ruby/object:Gem::Version
|
24
|
-
version: 0.
|
25
|
+
version: 0.8.2
|
25
26
|
type: :runtime
|
26
27
|
version_requirements: *id001
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
none: false
|
32
|
-
requirements:
|
33
|
-
- - ~>
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version: 2.1.3
|
36
|
-
type: :runtime
|
37
|
-
version_requirements: *id002
|
38
|
-
- !ruby/object:Gem::Dependency
|
39
|
-
name: net-ssh-multi
|
40
|
-
prerelease: false
|
41
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
|
-
requirements:
|
44
|
-
- - ~>
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: 1.0.1
|
47
|
-
type: :runtime
|
48
|
-
version_requirements: *id003
|
49
|
-
description: EC2 Support for Chef's Knife Command
|
50
|
-
email: adam@opscode.com
|
28
|
+
description: Rackspace Support for Chef's Knife Command
|
29
|
+
email:
|
30
|
+
- adam@opscode.com
|
31
|
+
- schisamo@opscode.com
|
51
32
|
executables: []
|
52
33
|
|
53
34
|
extensions: []
|
@@ -56,13 +37,21 @@ extra_rdoc_files:
|
|
56
37
|
- README.rdoc
|
57
38
|
- LICENSE
|
58
39
|
files:
|
40
|
+
- .gitignore
|
41
|
+
- .rspec
|
42
|
+
- Gemfile
|
59
43
|
- LICENSE
|
60
44
|
- README.rdoc
|
45
|
+
- Rakefile
|
46
|
+
- knife-ec2.gemspec
|
47
|
+
- lib/chef/knife/ec2_base.rb
|
61
48
|
- lib/chef/knife/ec2_instance_data.rb
|
62
49
|
- lib/chef/knife/ec2_server_create.rb
|
63
50
|
- lib/chef/knife/ec2_server_delete.rb
|
64
51
|
- lib/chef/knife/ec2_server_list.rb
|
65
52
|
- lib/knife-ec2/version.rb
|
53
|
+
- spec/spec_helper.rb
|
54
|
+
- spec/unit/ec2_server_create_spec.rb
|
66
55
|
has_rdoc: true
|
67
56
|
homepage: http://wiki.opscode.com/display/chef
|
68
57
|
licenses: []
|
@@ -90,6 +79,7 @@ rubyforge_project:
|
|
90
79
|
rubygems_version: 1.6.2
|
91
80
|
signing_key:
|
92
81
|
specification_version: 3
|
93
|
-
summary:
|
94
|
-
test_files:
|
95
|
-
|
82
|
+
summary: Rackspace Support for Chef's Knife Command
|
83
|
+
test_files:
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
- spec/unit/ec2_server_create_spec.rb
|