Class: Chef::Knife::GandiServerCreate

Inherits:
Chef::Knife show all
Defined in:
lib/chef/knife/gandi_server_create.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



140
141
142
# File 'lib/chef/knife/gandi_server_create.rb', line 140

def api_key
  @api_key
end

#connectionObject (readonly)

Returns the value of attribute connection.



140
141
142
# File 'lib/chef/knife/gandi_server_create.rb', line 140

def connection
  @connection
end

Instance Method Details

#bootstrap_for_node(server_spec, server, fqdn) ⇒ Object



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/chef/knife/gandi_server_create.rb', line 308

def bootstrap_for_node(server_spec, server, fqdn)
  bootstrap = Chef::Knife::Bootstrap.new
  bootstrap.name_args = [fqdn]
  bootstrap.config[:run_list] = config[:run_list]
  bootstrap.config[:ssh_user] = 'root'
  bootstrap.config[:ssh_password] = server_spec[:password]
  bootstrap.config[:identity_file] = config[:identity_file]
  bootstrap.config[:chef_node_name] = server[:chef_node_name] || "#{server['hostname']}_gandi-#{server['id']}"
  bootstrap.config[:prerelease] = config[:prerelease]
  bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
  bootstrap.config[:distro] = locate_config_value(:distro)
  # bootstrap will run as root...sudo (by default) also messes up Ohai on CentOS boxes
  bootstrap.config[:use_sudo] = config[:use_sudo]
  bootstrap.config[:template_file] = locate_config_value(:template_file)
  bootstrap.config[:environment] = config[:environment]
  bootstrap
end

#runObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
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
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/chef/knife/gandi_server_create.rb', line 142

def run
  # Unsual to extend here but enables 'plugin_helper' to be require'd lazily (in deps)
  extend KnifeGandi::PluginHelper
  $stdout.sync = true
  
  # Necessary changes to xmlrpc's defaults, prevent warnings from showing up in prompt
  suppress_warnings do
    XMLRPC::Config.const_set(:ENABLE_NIL_PARSER, true)
    XMLRPC::Config.const_set(:ENABLE_NIL_CREATE, true)
  end
  
  # The connection used to communicate with the Gandi API throught this command invokation
  @connection = XMLRPC::Client.new2(KnifeGandi::API_ENDPOINT_URL)
  @api_key    = Chef::Config[:knife][:gandi_api_key] || config[:gandi_api_key] || raise("Please provide an API key")
  
  print "\n"
  
  # Server spec
  # Sent as a param to the Gandi API
  # For mandatory params that were not passed in through the command line, ask for them interactively
  # The code may try to validate given values but we expect the API will throw errors.
  server_spec = Hash.new
  
  # Server Name
  server_spec[:hostname]   = locate_config_value(:server_name)
  server_spec[:hostname] ||= ui.ask('Name of the server: ') do |question| 
    question.answer_type  = String
  end
  
  # CPU Cores
  server_spec[:cores]   = locate_config_value(:cores)
  server_spec[:cores] ||= ui.ask('Number of CPU cores: ') do |question|
    question.answer_type  = Integer
    question.default      = 1
    question.in           = 1..6
  end
  
  # Memory
  server_spec[:memory]   = locate_config_value(:memory)
  server_spec[:memory] ||= ui.ask('Amount of memory (MB): ') do |question|
    question.answer_type  = Integer
    question.default      = 256
    #  Make sure this is a valid memory value
    question.validate     = lambda { |ans| ans.to_i.modulo(64) == 0 }
    question.in           = 256..12288
    question.responses[:not_valid] = "Your answer isn't valid (must be a valid memory value ex: 256, 512, 2048, ...)"
  end
  
  # Network Bandwidth
  bandwidth   = locate_config_value(:bandwidth)
  bandwidth ||= ui.ask('Bandwidth (MB): ') do |question|
    question.answer_type = Integer
    question.default     = 5
  end
  server_spec[:bandwidth] = bandwidth * 1024
  
  # IP Version
  server_spec[:ip_version]   = locate_config_value(:ip_version)
  server_spec[:ip_version] ||= ui.ask('IP protocol version: ') do |question|
    question.answer_type  = Integer
    question.default      = 4
    question.in           = [4, 6]
  end
  
  # Datacenter
  # TODO: Query the API and list the datacenters inline, there is only two of them currently.
  server_spec[:datacenter_id]   = locate_config_value(:datacenter_id)
  server_spec[:datacenter_id] ||= ui.ask('Datacenter id: ') do |question|
    question.answer_type  = Integer
  end
  
  # User Name
  # WARN: The Gandi hosting platform will not allow use of 'root'
  server_spec[:login]   = locate_config_value(:login)
  server_spec[:login] ||= ui.ask('Username: ') do |question|
    question.answer_type  = String
    question.default      = 'admin'
  end
  
  # User Password
  server_spec[:password]   = locate_config_value(:password)
  server_spec[:password] ||= ui.ask('Password: ') do |question|
    question.answer_type  = String
    question.validate     = /^[ -~]{8,64}$/
  end
  
  # Server Image
  # To create a server instance, Gandi requires a disk spec of the server's system disk
  disk_spec = Hash.new
  disk_spec[:datacenter_id] = server_spec[:datacenter_id]
  disk_spec[:name] = locate_config_value(:disk_name) || "disk_#{server_spec[:hostname]}"
  
  server_image_id   = locate_config_value(:image_id)
  server_image_id ||= ui.ask('Image id (see `knife gandi image list` command output): ') do |question|
    question.answer_type = Integer
  end
  
  # Lookup the server image
  server_image = connection.call('image.info', api_key, server_image_id)

  # Create the server
  # 'vm.create_from' is a shortcut method that creates the vm, its system disk and network interface
  # in one method call. It returns three operations, one describing each create operation.
  create_operations   = connection.call('vm.create_from', api_key, server_spec, disk_spec, server_image['disk_id'])
  vm_create_operation = create_operations.find { |op| op['type'] == 'vm_create' }

  print "\n"
  print ui.color("Creating the server.", :magenta)
  
  # Wait for it to be created to do stuff...
  until_done(vm_create_operation) { print '.' }
  
  # Server is created, now get its specs
  server = connection.call('vm.info', api_key, vm_create_operation['vm_id'])
  # Server's public IP resource representation (IP address, reverse dns, ...). See Gandi API docs
  public_ip_info = ip_objects_of(server, :type => 'public', :version => server_spec[:ip_version]).first
  
  
  puts "\n"
  puts "#{ui.color("Public DNS Name", :cyan)}: #{public_ip_info['reverse']}"
  puts "#{ui.color("Public IP Address", :cyan)}: #{public_ip_info['ip']}"
  puts "#{ui.color("Password", :cyan)}: #{server_spec[:password]}"

  print "\n"
  print ui.color("Waiting for sshd.", :magenta)

  print(".") until tcp_test_ssh(public_ip_info['reverse']) { sleep @initial_sleep_delay ||= 10; puts("done") }
  
  # Now that we have SSH access, bootstrap Chef on the server 
  # and hand over control to the Chef infrastructure
  bootstrap_for_node(server_spec, server, public_ip_info['reverse']).run

  puts "\n"
  puts "#{ui.color("Instance ID", :cyan)}: #{server['id']}"
  puts "#{ui.color("Name", :cyan)}: #{server['hostname']}"
  puts "#{ui.color("Memory", :cyan)}: #{server['memory']}"
  puts "#{ui.color("Cores", :cyan)}: #{server['cores']}"
  puts "#{ui.color("Image", :cyan)}: #{server_image['label']}"
  puts "#{ui.color("Public DNS Name", :cyan)}: #{public_ip_info['reverse']}"
  puts "#{ui.color("Public IP Address", :cyan)}: #{public_ip_info['ip']}"
  puts "#{ui.color("Password", :cyan)}: #{server_spec[:password]}"
  puts "#{ui.color("Environment", :cyan)}: #{config[:environment] || '_default'}"
  puts "#{ui.color("Run List", :cyan)}: #{config[:run_list].join(', ')}"
end

#tcp_test_ssh(hostname) ⇒ Object

Test if an SSH deamon is listening. AKA: is my server up and running?



289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/chef/knife/gandi_server_create.rb', line 289

def tcp_test_ssh(hostname)
  tcp_socket = TCPSocket.new(hostname, 22)
  readable = IO.select([tcp_socket], nil, nil, 5)
  if readable
    Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
    yield
    true
  else
    false
  end
rescue Errno::ETIMEDOUT
  false
rescue Errno::ECONNREFUSED
  sleep 2
  false
ensure
  tcp_socket && tcp_socket.close
end