Class: Chef::Knife::ClcServerCreate

Inherits:
Chef::Knife show all
Includes:
ClcBase
Defined in:
lib/chef/knife/clc_server_create.rb

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ClcBase

included

Class Method Details

.bootstrap_command_classObject



579
580
581
# File 'lib/chef/knife/clc_server_create.rb', line 579

def self.bootstrap_command_class
  Chef::Knife::Bootstrap
end

Instance Method Details

#add_bootstrapping_params(launch_params) ⇒ Object



545
546
547
548
# File 'lib/chef/knife/clc_server_create.rb', line 545

def add_bootstrapping_params(launch_params)
  launch_params['packages'] ||= []
  launch_params['packages'] << package_for_async_bootstrap
end

#async_create_serverObject



476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/chef/knife/clc_server_create.rb', line 476

def async_create_server
  launch_params = prepare_launch_params

  if config[:clc_bootstrap]
    add_bootstrapping_params(launch_params)
    ui.info 'Bootstrap has been scheduled'
  end

  ui.info 'Requesting server launch...'
  links = connection.create_server(launch_params)
  ui.info 'Launch request has been sent'
  ui.info "You can check launch operation status with 'knife clc operation show #{links['operation']['id']}'"

  if config[:clc_allowed_protocols]
    ui.info 'Requesting public IP...'
    server = connection.follow(links['resource'])
    ip_links = connection.create_ip_address(server['id'], prepare_ip_params)
    ui.info 'Public IP request has been sent'
    ui.info "You can check assignment operation status with 'knife clc operation show #{ip_links['operation']['id']}'"
  end

  argv = [links['resource']['id'], '--uuid', '--creds']
  argv << '--ports' if config[:clc_allowed_protocols]

  ui.info "You can check server status later with 'knife clc server show #{argv.join(' ')}'"
end

#bootstrap_commandObject



583
584
585
586
587
588
589
590
# File 'lib/chef/knife/clc_server_create.rb', line 583

def bootstrap_command
  command_class = self.class.bootstrap_command_class
  command_class.load_deps
  command = command_class.new
  command.config.merge!(config)
  command.configure_chef
  command
end

#check_bootstrap_connectivity_paramsObject



265
266
267
268
269
270
271
272
273
# File 'lib/chef/knife/clc_server_create.rb', line 265

def check_bootstrap_connectivity_params
  return if indirect_bootstrap?

  if public_ip_requested?
    errors << 'Bootstrapping requires SSH access to the server' unless ssh_access_requested?
  else
    errors << 'Bootstrapping requires public IP access to the server. Ignore this check with --bootstrap-private'
  end
end

#check_bootstrap_node_connectivity_paramsObject



254
255
256
257
258
259
260
261
262
263
# File 'lib/chef/knife/clc_server_create.rb', line 254

def check_bootstrap_node_connectivity_params
  command = bootstrap_command
  # Chef 12.0 does not have bootstrap context accessor and validates key by itself
  return unless command.respond_to?(:bootstrap_context)

  context = command.bootstrap_context
  unless context.validation_key
    errors << "Validatorless async bootstrap is not supported. Validation key #{Chef::Config[:validation_key]} not found"
  end
end

#check_chef_server_connectivityObject



248
249
250
251
252
# File 'lib/chef/knife/clc_server_create.rb', line 248

def check_chef_server_connectivity
  Chef::Node.list
rescue Exception => e
  errors << 'Could not connect to Chef Server: ' + e.message
end

#check_server_platformObject



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/chef/knife/clc_server_create.rb', line 275

def check_server_platform
  return unless config[:clc_group] && config[:clc_source_server]

  if template = find_source_template
    windows_platform = template['osType'] =~ /windows/
  elsif server = find_source_server
    windows_platform = server['os'] =~ /windows/
  end

  if windows_platform
    errors << 'Bootstrapping is available for Linux platform only'
  end
rescue Clc::CloudExceptions::Error => e
  errors << "Could not derive server bootstrap platform: #{e.message}"
end

#ensure_server_powered_on(server) ⇒ Object



536
537
538
539
540
541
542
543
# File 'lib/chef/knife/clc_server_create.rb', line 536

def ensure_server_powered_on(server)
  return unless server['details']['powerState'] == 'stopped'
  ui.info 'Requesting server power on...'
  links = connection.power_on_server(server['id'])
  connection.wait_for(links['operation']['id']) { putc '.' }
  ui.info "\n"
  ui.info 'Server has been powered on'
end

#executeObject



440
441
442
# File 'lib/chef/knife/clc_server_create.rb', line 440

def execute
  config[:clc_wait] ? sync_create_server : async_create_server
end

#find_source_serverObject



299
300
301
# File 'lib/chef/knife/clc_server_create.rb', line 299

def find_source_server
  connection.show_server(config[:clc_source_server])
end

#find_source_templateObject



291
292
293
294
295
296
297
# File 'lib/chef/knife/clc_server_create.rb', line 291

def find_source_template
  group = connection.show_group(config[:clc_group])
  datacenter_id = group['locationId']
  connection.list_templates(datacenter_id).find do |template|
    template['name'] == config[:clc_source_server]
  end
end

#get_server_credentials(server) ⇒ Object



574
575
576
577
# File 'lib/chef/knife/clc_server_create.rb', line 574

def get_server_credentials(server)
  creds_link = server['links'].find { |link| link['rel'] == 'credentials' }
  connection.follow(creds_link) if creds_link
end

#get_server_fqdn(server) ⇒ Object



560
561
562
563
564
565
566
567
568
# File 'lib/chef/knife/clc_server_create.rb', line 560

def get_server_fqdn(server)
  if indirect_bootstrap?
    private_ips = server['details']['ipAddresses'].map { |addr| addr['internal'] }.compact
    private_ips.first
  else
    public_ips = server['details']['ipAddresses'].map { |addr| addr['public'] }.compact
    public_ips.first
  end
end

#indirect_bootstrap?Boolean

Returns:

  • (Boolean)


570
571
572
# File 'lib/chef/knife/clc_server_create.rb', line 570

def indirect_bootstrap?
  config[:clc_bootstrap_private] || config[:ssh_gateway]
end

#package_for_async_bootstrapObject



550
551
552
553
554
555
556
557
558
# File 'lib/chef/knife/clc_server_create.rb', line 550

def package_for_async_bootstrap
  {
    'packageId' => 'a5d9d04369df4276a4f98f2ca7f7872b',
    'parameters' => {
      'Mode' => 'Ssh',
      'Script' => bootstrap_command.render_template
    }
  }
end

#parse_and_validate_parametersObject



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
# File 'lib/chef/knife/clc_server_create.rb', line 186

def parse_and_validate_parameters
  unless config[:clc_name]
    errors << 'Name is required'
  end

  unless config[:clc_group]
    errors << 'Group ID is required'
  end

  unless config[:clc_source_server]
    errors << 'Source server ID is required'
  end

  unless config[:clc_cpu]
    errors << 'Number of CPUs is required'
  end

  unless config[:clc_memory]
    errors << 'Number of memory GBs is required'
  end

  unless config[:clc_type]
    errors << 'Type is required'
  end

  custom_fields = config[:clc_custom_fields]
  if custom_fields && custom_fields.any?
    parse_custom_fields(custom_fields)
  end

  disks = config[:clc_disks]
  if disks && disks.any?
    parse_disks(disks)
  end

  packages = config[:clc_packages]
  if packages && packages.any?
    parse_packages(packages)
  end

  permissions = config[:clc_allowed_protocols]
  if permissions && permissions.any?
    parse_protocol_permissions(permissions)
  end

  sources = config[:clc_sources]
  if sources && sources.any?
    parse_sources(sources)
  end

  bootstrap = config[:clc_bootstrap]
  if bootstrap
    check_chef_server_connectivity
    check_server_platform
    if config[:clc_wait]
      check_bootstrap_connectivity_params
    else
      check_bootstrap_node_connectivity_params
    end
  end
end

#parse_custom_fields(custom_fields) ⇒ Object



325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/chef/knife/clc_server_create.rb', line 325

def parse_custom_fields(custom_fields)
  custom_fields.map! do |param|
    key, value = param.split('=', 2)

    unless key && value
      errors << "Custom field definition #{param} is malformed"
      next
    end

    { 'id' => key, 'value' => value }
  end
end

#parse_disks(disks) ⇒ Object



338
339
340
341
342
343
344
345
346
347
348
# File 'lib/chef/knife/clc_server_create.rb', line 338

def parse_disks(disks)
  disks.map! do |param|
    path, size, type = param.split(',', 3)

    unless path && size && type
      errors << "Disk definition #{param} is malformed"
    end

    { 'path' => path, 'sizeGB' => size, 'type' => type }
  end
end

#parse_packages(packages) ⇒ Object



350
351
352
353
354
355
356
357
358
359
360
# File 'lib/chef/knife/clc_server_create.rb', line 350

def parse_packages(packages)
  packages.map! do |param|
    begin
      id, package_params = param.split(',', 2)
      package_params = package_params.split(',').map { |pair| Hash[*pair.split('=', 2)] }
      { 'packageId' => id, 'parameters' => package_params }
    rescue Exception => e
      errors << "Package definition #{param} is malformed"
    end
  end
end

#parse_protocol_permissions(permissions) ⇒ Object



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/chef/knife/clc_server_create.rb', line 362

def parse_protocol_permissions(permissions)
  permissions.map! do |param|
    protocol, port_range = param.split(':', 2)

    case protocol.downcase
    when 'ssh', 'sftp' then { 'protocol' => 'tcp', 'port' => 22 }
    when 'rdp' then { 'protocol' => 'tcp', 'port' => 3389 }
    when 'icmp' then { 'protocol' => 'icmp' }
    when 'http' then [{ 'protocol' => 'tcp', 'port' => 80 }, { 'protocol' => 'tcp', 'port' => 8080 }]
    when 'https' then { 'protocol' => 'tcp', 'port' => 443 }
    when 'ftp' then { 'protocol' => 'tcp', 'port' => 21 }
    when 'ftps' then { 'protocol' => 'tcp', 'port' => 990 }
    when 'udp', 'tcp'
      unless port_range
        errors << "No ports specified for #{param}"
      else
        ports = port_range.split('-').map do |port_string|
          Integer(port_string) rescue nil
        end

        if ports.any?(&:nil?) || ports.size > 2 || ports.size < 1
          errors << "Malformed port range for #{param}"
        end

        {
          'protocol' => protocol.downcase,
          'port' => ports[0],
          'portTo' => ports[1]
        }.keep_if { |_, value| value }
      end
    else
      errors << "Unsupported protocol for #{param}"
    end
  end

  permissions.flatten!
end

#parse_sources(sources) ⇒ Object



400
401
402
403
404
# File 'lib/chef/knife/clc_server_create.rb', line 400

def parse_sources(sources)
  sources.map! do |cidr|
    { 'cidr' => cidr }
  end
end

#prepare_ip_paramsObject



433
434
435
436
437
438
# File 'lib/chef/knife/clc_server_create.rb', line 433

def prepare_ip_params
  {
    'ports' => config[:clc_allowed_protocols],
    'sourceRestrictions' => config[:clc_sources]
  }.delete_if { |_, value| value.nil? || value.empty? }
end

#prepare_launch_paramsObject



406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/chef/knife/clc_server_create.rb', line 406

def prepare_launch_params
  {
    'name' => config[:clc_name],
    'description' => config[:clc_description],
    'groupId' => config[:clc_group],
    'sourceServerId' => config[:clc_source_server],
    'isManagedOS' => config[:clc_managed],
    'isManagedBackup' => config[:clc_managed_backup],
    'primaryDns' => config[:clc_primary_dns],
    'secondaryDns' => config[:clc_secondary_dns],
    'networkId' => config[:clc_network],
    'ipAddress' => config[:clc_ip],
    'password' => config[:clc_server_password],
    'sourceServerPassword' => config[:clc_source_server_password],
    'cpu' => config[:clc_cpu].to_i,
    'cpuAutoscalePolicyId' => config[:clc_cpu_autoscale_policy],
    'memoryGB' => config[:clc_memory].to_i,
    'type' => config[:clc_type],
    'storageType' => config[:clc_storage_type],
    'antiAffinityPolicyId' => config[:clc_anti_affinity_policy],
    'customFields' => config[:clc_custom_fields],
    'additionalDisks' => config[:clc_disks],
    'ttl' => config[:clc_ttl],
    'packages' => config[:clc_packages],
  }.delete_if { |_, value| !value.kind_of?(Integer) && (value.nil? || value.empty?) }
end

#public_ip_requested?Boolean

Returns:

  • (Boolean)


303
304
305
# File 'lib/chef/knife/clc_server_create.rb', line 303

def public_ip_requested?
  config[:clc_allowed_protocols] && config[:clc_allowed_protocols].any?
end

#requested_ssh_portObject



321
322
323
# File 'lib/chef/knife/clc_server_create.rb', line 321

def requested_ssh_port
  (config[:ssh_port] && Integer(config[:ssh_port])) || 22
end

#retry_on_timeouts(tries = 2, &block) ⇒ Object



523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/chef/knife/clc_server_create.rb', line 523

def retry_on_timeouts(tries = 2, &block)
  yield
rescue Errno::ETIMEDOUT => e
  tries -= 1

  if tries > 0
    ui.info 'Retrying host connection...'
    retry
  else
    raise
  end
end

#ssh_access_requested?Boolean

Returns:

  • (Boolean)


307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/chef/knife/clc_server_create.rb', line 307

def ssh_access_requested?
  ssh_port = requested_ssh_port

  config[:clc_allowed_protocols].find do |permission|
    protocol, from, to = permission.values_at('protocol', 'port', 'portTo')
    next unless protocol == 'tcp'
    next unless from

    to ||= from

    Range.new(from, to).include? ssh_port
  end
end

#sync_bootstrap(uuid) ⇒ Object



503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
# File 'lib/chef/knife/clc_server_create.rb', line 503

def sync_bootstrap(uuid)
  server = connection.show_server(uuid, true)

  ensure_server_powered_on(server)

  command = bootstrap_command

  command.name_args = [get_server_fqdn(server)]

  username, password = config.values_at(:ssh_user, :ssh_password)
  unless username && password
    creds = get_server_credentials(server)
    command.config.merge!(:ssh_user => creds['userName'], :ssh_password => creds['password'])
  end

  command.config[:chef_node_name] ||= server['name']

  retry_on_timeouts { command.run }
end

#sync_create_serverObject



444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
# File 'lib/chef/knife/clc_server_create.rb', line 444

def sync_create_server
  ui.info 'Requesting server launch...'
  links = connection.create_server(prepare_launch_params)
  connection.wait_for(links['operation']['id']) { putc '.' }
  ui.info "\n"
  ui.info "Server has been launched"

  if config[:clc_allowed_protocols]
    ui.info 'Requesting public IP...'
    server = connection.follow(links['resource'])
    ip_links = connection.create_ip_address(server['id'], prepare_ip_params)
    connection.wait_for(ip_links['operation']['id']) { putc '.' }
    ui.info "\n"
    ui.info 'Public IP has been assigned'
  end

  if config[:clc_bootstrap]
    sync_bootstrap(links['resource']['id'])
  end

  argv = [links['resource']['id'], '--uuid', '--creds']
  if config[:clc_allowed_protocols]
    argv << '--ports'
  end

  if (username = config[:clc_username]) && (password = config[:clc_password])
    argv.concat(['--username', username, '--password', password])
  end

  Chef::Knife::ClcServerShow.new(argv).run
end