knife-openstack 0.5.4 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +35 -0
- data/README.md +102 -0
- data/knife-openstack.gemspec +5 -6
- data/lib/chef/knife/openstack_base.rb +108 -0
- data/lib/chef/knife/openstack_flavor_list.rb +11 -42
- data/lib/chef/knife/openstack_image_list.rb +16 -45
- data/lib/chef/knife/openstack_server_create.rb +215 -169
- data/lib/chef/knife/openstack_server_delete.rb +67 -62
- data/lib/chef/knife/openstack_server_list.rb +34 -48
- data/lib/knife-openstack/version.rb +1 -1
- metadata +41 -57
- data/README.rdoc +0 -90
@@ -1,6 +1,7 @@
|
|
1
1
|
#
|
2
2
|
# Author:: Seth Chisamore (<schisamo@opscode.com>)
|
3
|
-
#
|
3
|
+
# Author:: Matt Ray (<matt@opscode.com>)
|
4
|
+
# Copyright:: Copyright (c) 2011-2012 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/openstack_base'
|
20
21
|
|
21
22
|
class Chef
|
22
23
|
class Knife
|
23
24
|
class OpenstackServerCreate < Knife
|
24
25
|
|
26
|
+
include Knife::OpenstackBase
|
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 openstack server create (options)"
|
@@ -37,113 +38,98 @@ class Chef
|
|
37
38
|
attr_accessor :initial_sleep_delay
|
38
39
|
|
39
40
|
option :flavor,
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
:short => "-f FLAVOR_ID",
|
42
|
+
:long => "--flavor FLAVOR_ID",
|
43
|
+
:description => "The flavor ID of server (m1.small, m1.medium, etc)",
|
44
|
+
:proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f }
|
44
45
|
|
45
46
|
option :image,
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
option :security_groups,
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
option :availability_zone,
|
59
|
-
:short => "-Z ZONE",
|
60
|
-
:long => "--availability-zone ZONE",
|
61
|
-
:description => "The Availability Zone",
|
62
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:availability_zone] = key }
|
47
|
+
:short => "-I IMAGE_ID",
|
48
|
+
:long => "--image IMAGE_ID",
|
49
|
+
:description => "The image ID for the server",
|
50
|
+
:proc => Proc.new { |i| Chef::Config[:knife][:image] = i }
|
51
|
+
|
52
|
+
# option :security_groups,
|
53
|
+
# :short => "-G X,Y,Z",
|
54
|
+
# :long => "--groups X,Y,Z",
|
55
|
+
# :description => "The security groups for this server",
|
56
|
+
# :default => ["default"],
|
57
|
+
# :proc => Proc.new { |groups| groups.split(',') }
|
63
58
|
|
64
59
|
option :chef_node_name,
|
65
|
-
|
66
|
-
|
67
|
-
|
60
|
+
:short => "-N NAME",
|
61
|
+
:long => "--node-name NAME",
|
62
|
+
:description => "The Chef node name for your new node"
|
63
|
+
|
64
|
+
option :floating_ip,
|
65
|
+
:short => "-a",
|
66
|
+
:long => "--floating-ip",
|
67
|
+
:boolean => true,
|
68
|
+
:default => false,
|
69
|
+
:description => "Request to associate a floating IP address to the new OpenStack node. Assumes IPs have been allocated to the project."
|
70
|
+
|
71
|
+
option :private_network,
|
72
|
+
:long => "--private-network",
|
73
|
+
:description => "Use the private IP for bootstrapping rather than the public IP",
|
74
|
+
:boolean => true,
|
75
|
+
:default => false
|
68
76
|
|
69
77
|
option :ssh_key_name,
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
78
|
+
:short => "-S KEY",
|
79
|
+
:long => "--ssh-key KEY",
|
80
|
+
:description => "The OpenStack SSH keypair id",
|
81
|
+
:proc => Proc.new { |key| Chef::Config[:knife][:openstack_ssh_key_id] = key }
|
74
82
|
|
75
83
|
option :ssh_user,
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
84
|
+
:short => "-x USERNAME",
|
85
|
+
:long => "--ssh-user USERNAME",
|
86
|
+
:description => "The ssh username",
|
87
|
+
:default => "root"
|
80
88
|
|
81
89
|
option :ssh_password,
|
82
|
-
|
83
|
-
|
84
|
-
|
90
|
+
:short => "-P PASSWORD",
|
91
|
+
:long => "--ssh-password PASSWORD",
|
92
|
+
:description => "The ssh password"
|
85
93
|
|
86
94
|
option :identity_file,
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
option :openstack_access_key_id,
|
92
|
-
:short => "-A ID",
|
93
|
-
:long => "--openstack-access-key-id KEY",
|
94
|
-
:description => "Your OpenStack Access Key ID",
|
95
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:openstack_access_key_id] = key }
|
96
|
-
|
97
|
-
option :openstack_secret_access_key,
|
98
|
-
:short => "-K SECRET",
|
99
|
-
:long => "--openstack-secret-access-key SECRET",
|
100
|
-
:description => "Your OpenStack API Secret Access Key",
|
101
|
-
:proc => Proc.new { |key| Chef::Config[:knife][:openstack_secret_access_key] = key }
|
102
|
-
|
103
|
-
option :openstack_api_endpoint,
|
104
|
-
:long => "--openstack-api-endpoint ENDPOINT",
|
105
|
-
:description => "Your OpenStack API endpoint",
|
106
|
-
:proc => Proc.new { |endpoint| Chef::Config[:knife][:openstack_api_endpoint] = endpoint }
|
95
|
+
:short => "-i IDENTITY_FILE",
|
96
|
+
:long => "--identity-file IDENTITY_FILE",
|
97
|
+
:description => "The SSH identity file used for authentication"
|
107
98
|
|
108
99
|
option :prerelease,
|
109
|
-
|
110
|
-
|
100
|
+
:long => "--prerelease",
|
101
|
+
:description => "Install the pre-release chef gems"
|
111
102
|
|
112
103
|
option :bootstrap_version,
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
option :region,
|
118
|
-
:long => "--region REGION",
|
119
|
-
:description => "Your OpenStack region",
|
120
|
-
:proc => Proc.new { |region| Chef::Config[:knife][:region] = region }
|
104
|
+
:long => "--bootstrap-version VERSION",
|
105
|
+
:description => "The version of Chef to install",
|
106
|
+
:proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
|
121
107
|
|
122
108
|
option :distro,
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
109
|
+
:short => "-d DISTRO",
|
110
|
+
:long => "--distro DISTRO",
|
111
|
+
:description => "Bootstrap a distro using a template; default is 'chef-full'",
|
112
|
+
:proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
|
113
|
+
:default => "chef-full"
|
128
114
|
|
129
115
|
option :template_file,
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
116
|
+
:long => "--template-file TEMPLATE",
|
117
|
+
:description => "Full path to location of template to use",
|
118
|
+
:proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
|
119
|
+
:default => false
|
134
120
|
|
135
121
|
option :run_list,
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
option :
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
122
|
+
:short => "-r RUN_LIST",
|
123
|
+
:long => "--run-list RUN_LIST",
|
124
|
+
:description => "Comma separated list of roles/recipes to apply",
|
125
|
+
:proc => lambda { |o| o.split(/[\s,]+/) },
|
126
|
+
:default => []
|
127
|
+
|
128
|
+
option :host_key_verify,
|
129
|
+
:long => "--[no-]host-key-verify",
|
130
|
+
:description => "Verify host key, enabled by default",
|
131
|
+
:boolean => true,
|
132
|
+
:default => true
|
147
133
|
|
148
134
|
def tcp_test_ssh(hostname)
|
149
135
|
tcp_socket = TCPSocket.new(hostname, 22)
|
@@ -157,103 +143,163 @@ class Chef
|
|
157
143
|
end
|
158
144
|
rescue Errno::ETIMEDOUT
|
159
145
|
false
|
146
|
+
rescue Errno::EPERM
|
147
|
+
false
|
160
148
|
rescue Errno::ECONNREFUSED
|
161
149
|
sleep 2
|
162
150
|
false
|
151
|
+
rescue Errno::EHOSTUNREACH
|
152
|
+
sleep 2
|
153
|
+
false
|
154
|
+
rescue Errno::ENETUNREACH
|
155
|
+
sleep 2
|
156
|
+
false
|
163
157
|
ensure
|
164
158
|
tcp_socket && tcp_socket.close
|
165
159
|
end
|
166
160
|
|
167
161
|
def run
|
168
|
-
|
169
162
|
$stdout.sync = true
|
170
163
|
|
164
|
+
validate!
|
165
|
+
|
171
166
|
connection = Fog::Compute.new(
|
172
|
-
:provider => '
|
173
|
-
:
|
174
|
-
:
|
175
|
-
:
|
176
|
-
:
|
177
|
-
|
167
|
+
:provider => 'OpenStack',
|
168
|
+
:openstack_username => Chef::Config[:knife][:openstack_username],
|
169
|
+
:openstack_api_key => Chef::Config[:knife][:openstack_password],
|
170
|
+
:openstack_auth_url => Chef::Config[:knife][:openstack_auth_url],
|
171
|
+
:openstack_tenant => Chef::Config[:knife][:openstack_tenant]
|
172
|
+
)
|
173
|
+
|
174
|
+
#servers require a name, generate one if not passed
|
175
|
+
node_name = get_node_name(config[:chef_node_name])
|
178
176
|
|
179
177
|
server_def = {
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}"
|
231
|
-
puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}"
|
178
|
+
:name => node_name,
|
179
|
+
:image_ref => locate_config_value(:image),
|
180
|
+
:flavor_ref => locate_config_value(:flavor),
|
181
|
+
# :security_group => locate_config_value(:security_groups),
|
182
|
+
:key_name => Chef::Config[:knife][:openstack_ssh_key_id],
|
183
|
+
:personality => [{
|
184
|
+
"path" => "/etc/chef/ohai/hints/openstack.json",
|
185
|
+
"contents" => ''
|
186
|
+
}]
|
187
|
+
}
|
188
|
+
|
189
|
+
Chef::Log.debug("Name #{node_name}")
|
190
|
+
Chef::Log.debug("Image #{locate_config_value(:image)}")
|
191
|
+
Chef::Log.debug("Flavor #{locate_config_value(:flavor)}")
|
192
|
+
# Chef::Log.debug("Groups #{locate_config_value(:security_groups)}")
|
193
|
+
Chef::Log.debug("Creating server #{server_def}")
|
194
|
+
server = connection.servers.create(server_def)
|
195
|
+
|
196
|
+
msg_pair("Instance Name", server.name)
|
197
|
+
msg_pair("Instance ID", server.id)
|
198
|
+
# msg_pair("Security Groups", server.groups.join(", "))
|
199
|
+
msg_pair("SSH Keypair", server.key_name)
|
200
|
+
|
201
|
+
print "\n#{ui.color("Waiting for server", :magenta)}"
|
202
|
+
|
203
|
+
# wait for it to be ready to do stuff
|
204
|
+
server.wait_for { print "."; ready? }
|
205
|
+
|
206
|
+
puts("\n")
|
207
|
+
|
208
|
+
msg_pair("Flavor", server.flavor['id'])
|
209
|
+
msg_pair("Image", server.image['id'])
|
210
|
+
msg_pair("Public IP Address", server.public_ip_address['addr']) if server.public_ip_address
|
211
|
+
|
212
|
+
if config[:floating_ip]
|
213
|
+
associated = false
|
214
|
+
connection.addresses.each do |address|
|
215
|
+
if address.instance_id.nil?
|
216
|
+
server.associate_address(address.ip)
|
217
|
+
#a bit of a hack, but server.reload takes a long time
|
218
|
+
server.addresses['public'].push({"version"=>4,"addr"=>address.ip})
|
219
|
+
associated = true
|
220
|
+
msg_pair("Floating IP Address", address.ip)
|
221
|
+
break
|
222
|
+
end
|
223
|
+
end
|
224
|
+
unless associated
|
225
|
+
ui.error("Unable to associate floating IP.")
|
226
|
+
exit 1
|
227
|
+
end
|
232
228
|
end
|
229
|
+
Chef::Log.debug("Public IP Address actual #{server.public_ip_address['addr']}") if server.public_ip_address
|
233
230
|
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
246
|
-
bootstrap.config[:environment] = config[:environment]
|
247
|
-
# may be needed for vpc_mode
|
248
|
-
bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
|
249
|
-
bootstrap
|
231
|
+
msg_pair("Private IP Address", server.private_ip_address['addr'])
|
232
|
+
|
233
|
+
#which IP address to bootstrap
|
234
|
+
bootstrap_ip_address = server.public_ip_address['addr'] if server.public_ip_address
|
235
|
+
if config[:private_network]
|
236
|
+
bootstrap_ip_address = server.private_ip_address['addr']
|
237
|
+
end
|
238
|
+
Chef::Log.debug("Bootstrap IP Address #{bootstrap_ip_address}")
|
239
|
+
if bootstrap_ip_address.nil?
|
240
|
+
ui.error("No IP address available for bootstrapping.")
|
241
|
+
exit 1
|
250
242
|
end
|
251
243
|
|
252
|
-
|
253
|
-
|
254
|
-
|
244
|
+
print "\n#{ui.color("Waiting for sshd", :magenta)}"
|
245
|
+
|
246
|
+
print(".") until tcp_test_ssh(bootstrap_ip_address) {
|
247
|
+
sleep @initial_sleep_delay ||= 10
|
248
|
+
puts("done")
|
249
|
+
}
|
250
|
+
|
251
|
+
bootstrap_for_node(server, bootstrap_ip_address).run
|
252
|
+
|
253
|
+
puts "\n"
|
254
|
+
msg_pair("Instance Name", server.name)
|
255
|
+
msg_pair("Instance ID", server.id)
|
256
|
+
msg_pair("Flavor", server.flavor['id'])
|
257
|
+
msg_pair("Image", server.image['id'])
|
258
|
+
# msg_pair("Security Groups", server.groups.join(", "))
|
259
|
+
msg_pair("SSH Keypair", server.key_name)
|
260
|
+
msg_pair("Public IP Address", server.public_ip_address['addr']) if server.public_ip_address
|
261
|
+
msg_pair("Private IP Address", server.private_ip_address['addr'])
|
262
|
+
msg_pair("Environment", config[:environment] || '_default')
|
263
|
+
msg_pair("Run List", config[:run_list].join(', '))
|
264
|
+
end
|
265
|
+
|
266
|
+
def bootstrap_for_node(server, bootstrap_ip_address)
|
267
|
+
bootstrap = Chef::Knife::Bootstrap.new
|
268
|
+
bootstrap.name_args = [bootstrap_ip_address]
|
269
|
+
bootstrap.config[:run_list] = config[:run_list]
|
270
|
+
bootstrap.config[:ssh_user] = config[:ssh_user]
|
271
|
+
bootstrap.config[:identity_file] = config[:identity_file]
|
272
|
+
bootstrap.config[:host_key_verify] = config[:host_key_verify]
|
273
|
+
bootstrap.config[:chef_node_name] = server.name
|
274
|
+
bootstrap.config[:prerelease] = config[:prerelease]
|
275
|
+
bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
|
276
|
+
bootstrap.config[:distro] = locate_config_value(:distro)
|
277
|
+
bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
|
278
|
+
bootstrap.config[:template_file] = locate_config_value(:template_file)
|
279
|
+
bootstrap.config[:environment] = config[:environment]
|
280
|
+
bootstrap
|
281
|
+
end
|
282
|
+
|
283
|
+
def ami
|
284
|
+
@ami ||= connection.images.get(locate_config_value(:image))
|
285
|
+
end
|
286
|
+
|
287
|
+
def validate!
|
288
|
+
|
289
|
+
super([:image, :openstack_ssh_key_id, :openstack_username, :openstack_password, :openstack_auth_url])
|
290
|
+
|
291
|
+
if ami.nil?
|
292
|
+
ui.error("You have not provided a valid image ID. Please note the short option for this value recently changed from '-i' to '-I'.")
|
293
|
+
exit 1
|
255
294
|
end
|
295
|
+
end
|
256
296
|
|
297
|
+
#generate a random name if chef_node_name is empty
|
298
|
+
def get_node_name(chef_node_name)
|
299
|
+
return chef_node_name unless chef_node_name.nil?
|
300
|
+
#lazy uuids
|
301
|
+
chef_node_name = "os-"+rand.to_s.split('.')[1]
|
257
302
|
end
|
258
303
|
end
|
259
304
|
end
|
305
|
+
end
|