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.
@@ -1,6 +1,7 @@
1
1
  #
2
2
  # Author:: Seth Chisamore (<schisamo@opscode.com>)
3
- # Copyright:: Copyright (c) 2011 Opscode, Inc.
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
- :short => "-f FLAVOR",
41
- :long => "--flavor FLAVOR",
42
- :description => "The flavor of server (m1.small, m1.medium, etc)",
43
- :proc => Proc.new { |f| Chef::Config[:knife][:flavor] = f }
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
- :short => "-I IMAGE",
47
- :long => "--image IMAGE",
48
- :description => "The AMI for the server",
49
- :proc => Proc.new { |i| Chef::Config[:knife][:image] = i }
50
-
51
- option :security_groups,
52
- :short => "-G X,Y,Z",
53
- :long => "--groups X,Y,Z",
54
- :description => "The security groups for this server",
55
- :default => ["default"],
56
- :proc => Proc.new { |groups| groups.split(',') }
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
- :short => "-N NAME",
66
- :long => "--node-name NAME",
67
- :description => "The Chef node name for your new node"
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
- :short => "-S KEY",
71
- :long => "--ssh-key KEY",
72
- :description => "The OpenStack SSH key id",
73
- :proc => Proc.new { |key| Chef::Config[:knife][:openstack_ssh_key_id] = key }
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
- :short => "-x USERNAME",
77
- :long => "--ssh-user USERNAME",
78
- :description => "The ssh username",
79
- :default => "root"
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
- :short => "-P PASSWORD",
83
- :long => "--ssh-password PASSWORD",
84
- :description => "The ssh password"
90
+ :short => "-P PASSWORD",
91
+ :long => "--ssh-password PASSWORD",
92
+ :description => "The ssh password"
85
93
 
86
94
  option :identity_file,
87
- :short => "-i IDENTITY_FILE",
88
- :long => "--identity-file IDENTITY_FILE",
89
- :description => "The SSH identity file used for authentication"
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
- :long => "--prerelease",
110
- :description => "Install the pre-release chef gems"
100
+ :long => "--prerelease",
101
+ :description => "Install the pre-release chef gems"
111
102
 
112
103
  option :bootstrap_version,
113
- :long => "--bootstrap-version VERSION",
114
- :description => "The version of Chef to install",
115
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
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
- :short => "-d DISTRO",
124
- :long => "--distro DISTRO",
125
- :description => "Bootstrap a distro using a template",
126
- :proc => Proc.new { |d| Chef::Config[:knife][:distro] = d },
127
- :default => "ubuntu10.04-gems"
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
- :long => "--template-file TEMPLATE",
131
- :description => "Full path to location of template to use",
132
- :proc => Proc.new { |t| Chef::Config[:knife][:template_file] = t },
133
- :default => false
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
- :short => "-r RUN_LIST",
137
- :long => "--run-list RUN_LIST",
138
- :description => "Comma separated list of roles/recipes to apply",
139
- :proc => lambda { |o| o.split(/[\s,]+/) },
140
- :default => []
141
-
142
- option :no_host_key_verify,
143
- :long => "--no-host-key-verify",
144
- :description => "Disable host key verification",
145
- :boolean => true,
146
- :default => false
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 => 'AWS',
173
- :aws_access_key_id => Chef::Config[:knife][:openstack_access_key_id],
174
- :aws_secret_access_key => Chef::Config[:knife][:openstack_secret_access_key],
175
- :endpoint => Chef::Config[:knife][:openstack_api_endpoint],
176
- :region => locate_config_value(:region)
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
- :image_id => locate_config_value(:image),
181
- :groups => config[:security_groups],
182
- :flavor_id => locate_config_value(:flavor),
183
- :key_name => Chef::Config[:knife][:openstack_ssh_key_id],
184
- :availability_zone => Chef::Config[:knife][:availability_zone]
185
- }
186
-
187
- server = connection.servers.create(server_def)
188
-
189
- puts "#{ui.color("Instance ID", :cyan)}: #{server.id}"
190
- puts "#{ui.color("Flavor", :cyan)}: #{server.flavor_id}"
191
- puts "#{ui.color("Image", :cyan)}: #{server.image_id}"
192
- puts "#{ui.color("Availability Zone", :cyan)}: #{server.availability_zone}"
193
- puts "#{ui.color("Security Groups", :cyan)}: #{server.groups.join(", ")}"
194
- puts "#{ui.color("SSH Key", :cyan)}: #{server.key_name}"
195
-
196
- print "\n#{ui.color("Waiting for server", :magenta)}"
197
-
198
- display_name = server.dns_name
199
-
200
- # wait for it to be ready to do stuff
201
- server.wait_for { print "."; ready? }
202
-
203
- puts("\n")
204
-
205
- puts "#{ui.color("Public DNS Name", :cyan)}: #{server.dns_name}"
206
- puts "#{ui.color("Public IP Address", :cyan)}: #{server.public_ip_address}"
207
- puts "#{ui.color("Private DNS Name", :cyan)}: #{server.private_dns_name}"
208
- puts "#{ui.color("Private IP Address", :cyan)}: #{server.private_ip_address}"
209
-
210
- print "\n#{ui.color("Waiting for sshd", :magenta)}"
211
-
212
- print(".") until tcp_test_ssh(display_name) {
213
- sleep @initial_sleep_delay ||= 10
214
- puts("done")
215
- }
216
-
217
- bootstrap_for_node(server).run
218
-
219
- puts "\n"
220
- puts "#{ui.color("Instance ID", :cyan)}: #{server.id}"
221
- puts "#{ui.color("Flavor", :cyan)}: #{server.flavor_id}"
222
- puts "#{ui.color("Image", :cyan)}: #{server.image_id}"
223
- puts "#{ui.color("Availability Zone", :cyan)}: #{server.availability_zone}"
224
- puts "#{ui.color("Security Groups", :cyan)}: #{server.groups.join(", ")}"
225
- puts "#{ui.color("Public DNS Name", :cyan)}: #{server.dns_name}"
226
- puts "#{ui.color("Public IP Address", :cyan)}: #{server.public_ip_address}"
227
- puts "#{ui.color("Private DNS Name", :cyan)}: #{server.private_dns_name}"
228
- puts "#{ui.color("SSH Key", :cyan)}: #{server.key_name}"
229
- puts "#{ui.color("Private IP Address", :cyan)}: #{server.private_ip_address}"
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
- def bootstrap_for_node(server)
235
- bootstrap = Chef::Knife::Bootstrap.new
236
- bootstrap.name_args = [server.dns_name]
237
- bootstrap.config[:run_list] = config[:run_list]
238
- bootstrap.config[:ssh_user] = config[:ssh_user]
239
- bootstrap.config[:identity_file] = config[:identity_file]
240
- bootstrap.config[:chef_node_name] = config[:chef_node_name] || server.id
241
- bootstrap.config[:prerelease] = config[:prerelease]
242
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
243
- bootstrap.config[:distro] = locate_config_value(:distro)
244
- bootstrap.config[:use_sudo] = true
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
- def locate_config_value(key)
253
- key = key.to_sym
254
- Chef::Config[:knife][key] || config[key]
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