knife-openstack 0.5.4 → 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.
- 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
|