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.
@@ -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