Class: Knifecosmic::CosmicServerCreate

Inherits:
Chef::Knife show all
Includes:
Chef::Knife::KnifecosmicBase, Chef::Knife::WinrmBase
Defined in:
lib/chef/knife/cosmic_server_create.rb

Constant Summary collapse

BOOTSTRAP_DELAY =

Seconds to delay between detecting ssh and initiating the bootstrap

20
WINRM_BOOTSTRAP_DELAY =

The machine will reboot once so we need to handle that

200
SSH_POLL_INTERVAL =

Seconds to wait between ssh pings

10

Instance Method Summary collapse

Methods included from Chef::Knife::KnifecosmicBase

included

Instance Method Details

#bootstrap(server) ⇒ Object



590
591
592
593
594
595
596
597
598
599
# File 'lib/chef/knife/cosmic_server_create.rb', line 590

def bootstrap(server)
  public_ip = server['public_ip']
  if @windows_image
    Chef::Log.debug("Windows Bootstrapping")
    bootstrap_for_windows_node(server, public_ip)
  else
    Chef::Log.debug("Linux Bootstrapping")
    bootstrap_for_node(server, public_ip)
  end
end

#bootstrap_common_params(bootstrap) ⇒ Object



635
636
637
638
639
640
641
642
643
644
645
646
# File 'lib/chef/knife/cosmic_server_create.rb', line 635

def bootstrap_common_params(bootstrap)
  bootstrap.config[:run_list] = config[:run_list]
  bootstrap.config[:prerelease] = config[:prerelease]
  bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
  bootstrap.config[:distro] = locate_config_value(:distro)
  bootstrap.config[:template_file] = locate_config_value(:template_file)
  bootstrap.config[:first_boot_attributes] = locate_config_value(:first_boot_attributes)
  bootstrap.config[:environment] = locate_config_value(:environment)
  bootstrap.config[:bootstrap_wget_options] = locate_config_value(:bootstrap_wget_options)
  bootstrap.config[:bootstrap_proxy] = locate_config_value(:bootstrap_proxy)
  bootstrap
end

#bootstrap_for_node(server, fqdn) ⇒ Object



648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
# File 'lib/chef/knife/cosmic_server_create.rb', line 648

def bootstrap_for_node(server,fqdn)
  bootstrap = Chef::Knife::Bootstrap.new
  bootstrap.name_args = [fqdn]
  if locate_config_value(:cosmic_password)
    bootstrap.config[:ssh_user] = locate_config_value(:ssh_user) || 'root'
  else
    bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
  end
  locate_config_value(:cosmic_password) ? bootstrap.config[:ssh_password] = server['password'] : bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
  bootstrap.config[:ssh_port] = locate_config_value(:ssh_port) || 22
  bootstrap.config[:identity_file] = locate_config_value(:identity_file)
  bootstrap.config[:secret_file] = locate_config_value(:secret_file)
  bootstrap.config[:secret] = locate_config_value(:secret)
  bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server["name"]
  bootstrap.config[:use_sudo] = true unless locate_config_value(:ssh_user) == 'root'

  # may be needed for vpc_mode
  bootstrap.config[:host_key_verify] = config[:host_key_verify]
  bootstrap_common_params(bootstrap)
end

#bootstrap_for_windows_node(server, fqdn) ⇒ Object



601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
# File 'lib/chef/knife/cosmic_server_create.rb', line 601

def bootstrap_for_windows_node(server, fqdn)
  if locate_config_value(:bootstrap_protocol) == 'winrm'
    bootstrap = Chef::Knife::BootstrapWindowsWinrm.new
    if locate_config_value(:kerberos_realm)
      #Fetch AD/WINS based fqdn if any for Kerberos-based Auth
      private_ip_address = connection.get_server_default_nic(server)["ipaddress"]
      fqdn = locate_config_value(:fqdn) || fetch_server_fqdn(private_ip_address)
    end
    bootstrap.name_args = [fqdn]
    bootstrap.config[:winrm_user] = locate_config_value(:winrm_user) || 'Administrator'
    locate_config_value(:cosmic_password) ? bootstrap.config[:winrm_password] = server['password'] : bootstrap.config[:winrm_password] = locate_config_value(:winrm_password)
    bootstrap.config[:winrm_transport] = locate_config_value(:winrm_transport)
    bootstrap.config[:winrm_port] = locate_config_value(:winrm_port)
  elsif locate_config_value(:bootstrap_protocol) == 'ssh'
    bootstrap = Chef::Knife::BootstrapWindowsSsh.new
    if locate_config_value(:cosmic_password)
      bootstrap.config[:ssh_user] = locate_config_value(:ssh_user) || 'Administrator'
    else
      bootstrap.config[:ssh_user] = locate_config_value(:ssh_user)
    end
    locate_config_value(:cosmic_password) ? bootstrap.config[:ssh_password] = server['password'] : bootstrap.config[:ssh_password] = locate_config_value(:ssh_password)
    bootstrap.config[:ssh_port] = locate_config_value(:ssh_port)
    bootstrap.config[:identity_file] = locate_config_value(:identity_file)
    bootstrap.config[:no_host_key_verify] = locate_config_value(:no_host_key_verify)
  else
    ui.error("Unsupported Bootstrapping Protocol. Supported : winrm, ssh")
    exit 1
  end
  bootstrap.config[:chef_node_name] = locate_config_value(:chef_node_name) || server["name"]
  bootstrap.config[:encrypted_data_bag_secret] = locate_config_value(:secret)
  bootstrap.config[:encrypted_data_bag_secret_file] = locate_config_value(:secret_file)
  bootstrap_common_params(bootstrap)
end

#create_firewall_rules(ip_address, connection) ⇒ Object



505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
# File 'lib/chef/knife/cosmic_server_create.rb', line 505

def create_firewall_rules(ip_address, connection)
  Chef::Log.debug("Creating Firewall Rule")
  rules = locate_config_value(:fw_rules)
  return unless rules
  icmptype={
    '0' => {'code' => [0]},
    '8' => {'code' => [0]},
    '3' => {'code' => [0, 1]}
  }
  rules.each do |rule|
    args = rule.split(':')
    protocol = args[0]
    cidr_list = (args[1].nil? || args[1].length == 0) ? "0.0.0.0/0" : args[1]
    startport = args[2]
    endport = args[3] || args[2]
    if protocol == "ICMP"
      icmptype.each do |type, value|
        value['code'].each do |code_id|
          Chef::Log.debug("Creating Firewall Rule for
            #{ip_address['ipaddress']} with protocol: #{protocol}, icmptype: #{type}, icmpcode: #{code_id}, cidrList: #{cidr_list}")
          connection.create_firewall_rule(ip_address['id'], protocol, type, code_id, cidr_list)
        end
      end
    else
      Chef::Log.debug("Creating Firewall Rule for
        #{ip_address['ipaddress']} with protocol: #{protocol}, startport: #{startport}, endport: #{endport}, cidrList: #{cidr_list}")
      connection.create_firewall_rule(ip_address['id'], protocol, startport, endport, cidr_list)
    end
  end
end

#create_ip_forwarding_rules(ip_address, connection) ⇒ Object



488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
# File 'lib/chef/knife/cosmic_server_create.rb', line 488

def create_ip_forwarding_rules(ip_address, connection)
  Chef::Log.debug("Creating IP Forwarding Rule")
  rules = locate_config_value(:ipfwd_rules)
  return unless rules
  rules.each do |rule|
    args = rule.split(':')
    startport = args[0]
    endport = args[1] || args[0]
    protocol = args[2] || "TCP"
    if locate_config_value :static_nat
      Chef::Log.debug("Creating IP Forwarding Rule for
          #{ip_address['ipaddress']} with protocol: #{protocol}, startport: #{startport}, endport: #{endport}")
      connection.create_ip_fwd_rule(ip_address['id'], protocol, startport, endport)
    end
  end
end

#create_port_forwarding_rules(ip_address, server_id, connection) ⇒ Object



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/chef/knife/cosmic_server_create.rb', line 457

def create_port_forwarding_rules(ip_address, server_id, connection)
  Chef::Log.debug("Creating IP Forwarding Rule")
  rules = locate_config_value(:port_rules) || []
  if config[:bootstrap]
    if @bootstrap_protocol == 'ssh'
      rules += ["#{locate_config_value(:ssh_port)}"] #SSH Port
    elsif @bootstrap_protocol == 'winrm'
      rules +=[locate_config_value(:winrm_port)]
    else
      puts("\nUnsupported bootstrap protocol : #{@bootstrap_protocol}")
      exit 1
    end
  end
  return if rules.empty?
  rules.each do |rule|
    args = rule.split(':')
    public_port = args[0]
    private_port = args[1] || args[0]
    protocol = args[2] || "TCP"
    if locate_config_value :static_nat
      Chef::Log.debug("Creating IP Forwarding Rule for
        #{ip_address['ipaddress']} with protocol: #{protocol}, public port: #{public_port}")
      connection.create_ip_fwd_rule(ip_address['id'], protocol, public_port, public_port)
    else
      Chef::Log.debug("Creating Port Forwarding Rule for #{ip_address['id']} with protocol: #{protocol},
        public port: #{public_port} and private port: #{private_port} and server: #{server_id}")
      connection.create_port_forwarding_rule(ip_address['id'], private_port, protocol, public_port, server_id)
    end
  end
end

#fetch_server_fqdn(ip_addr) ⇒ Object



380
381
382
383
# File 'lib/chef/knife/cosmic_server_create.rb', line 380

def fetch_server_fqdn(ip_addr)
    require 'resolv'
    Resolv.getname(ip_addr)
end

#find_or_create_public_ip(server, connection) ⇒ Object



436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/chef/knife/cosmic_server_create.rb', line 436

def find_or_create_public_ip(server, connection)
  nic = connection.get_server_default_nic(server) || {}
  if (config[:public_ip] == false)
    nic['ipaddress']
  else
    puts("\nAllocate ip address, create forwarding rules")
    ip_address = connection.associate_ip_address(server['zoneid'], locate_config_value(:cosmic_networks))
    puts("\nAllocated IP Address: #{ip_address['ipaddress']}")
    Chef::Log.debug("IP Address Info: #{ip_address}")

    if locate_config_value :static_nat
      Chef::Log.debug("Enabling static NAT for IP Address : #{ip_address['ipaddress']}")
      connection.enable_static_nat(ip_address['id'], server['id'])
    end
    create_port_forwarding_rules(ip_address, server['id'], connection)
    create_ip_forwarding_rules(ip_address, connection)
    create_firewall_rules(ip_address, connection)
    ip_address['ipaddress']
  end
end

#is_image_windows?Boolean

Returns:

  • (Boolean)


385
386
387
388
389
390
391
392
393
394
# File 'lib/chef/knife/cosmic_server_create.rb', line 385

def is_image_windows?
    template_name = locate_config_value(:cosmic_template)
    template = connection.get_template(template_name, locate_config_value(:cosmic_zone))
    template = connection.get_iso(template_name, locate_config_value(:cosmic_zone)) unless template
    if !template
      ui.error("Template: #{template_name} does not exist!")
      exit 1
    end
    return template['ostypename'].scan('Windows').length > 0
end

#is_platform_windows?Boolean

Returns:

  • (Boolean)


586
587
588
# File 'lib/chef/knife/cosmic_server_create.rb', line 586

def is_platform_windows?
  return RUBY_PLATFORM.scan('w32').size > 0
end

#is_ssh_open?(ip) ⇒ Boolean

noinspection RubyArgCount,RubyResolve

Returns:

  • (Boolean)


558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
# File 'lib/chef/knife/cosmic_server_create.rb', line 558

def is_ssh_open?(ip)
  s = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
  sa = Socket.sockaddr_in(locate_config_value(:ssh_port), ip)

  begin
    s.connect_nonblock(sa)
  rescue Errno::EINPROGRESS
    resp = IO.select(nil, [s], nil, 1)
    if resp.nil?
      sleep SSH_POLL_INTERVAL
      return false
    end

    begin
      s.connect_nonblock(sa)
    rescue Errno::EISCONN
      Chef::Log.debug("sshd accepting connections on #{ip}")
      yield
      return true
    rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
      sleep SSH_POLL_INTERVAL
      return false
    end
  ensure
    s && s.close
  end
end

#runObject



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/chef/knife/cosmic_server_create.rb', line 264

def run
  validate_base_options

  Chef::Log.debug("Validate hostname and options")
  hostname = @name_args.first
  unless /^[a-zA-Z0-9][a-zA-Z0-9-]*$/.match hostname then
    ui.error "Invalid hostname. Please specify a short hostname, not an fqdn (e.g. 'myhost' instead of 'myhost.domain.com')."
    exit 1
  end
  validate_options

  # This little peace of code sets the Chef node-name to the VM name when a node-name is not specifically given
  unless locate_config_value :chef_node_name
    config[:chef_node_name] = @name_args.first
  end

  if @windows_image and locate_config_value(:kerberos_realm)
    Chef::Log.debug("Load additional gems for AD/Kerberos Authentication")
    if @windows_platform
      require 'em-winrs'
    else
      require 'gssapi'
    end
  end

  $stdout.sync = true

  Chef::Log.info("Creating instance with
    service : #{locate_config_value(:cosmic_service)}
    template : #{locate_config_value(:cosmic_template)}
    disk : #{locate_config_value(:cosmic_disk)}
    zone : #{locate_config_value(:cosmic_zone)}
    project: #{locate_config_value(:cosmic_project)}
    network: #{locate_config_value(:cosmic_networks)}")

  print "\n#{ui.color("Waiting for Server to be created", :magenta)}"
  params = {}
  params['hypervisor'] = locate_config_value(:cosmic_hypervisor) if locate_config_value(:cosmic_hypervisor)

  params['keypair'] = locate_config_value :keypair  if locate_config_value :keypair
  params['affinitygroupnames'] = locate_config_value :aag if locate_config_value :aag
  params['displayname'] = if locate_config_value :set_display_name and locate_config_value :chef_node_name then locate_config_value :chef_node_name else hostname end
  params['ipaddress'] = locate_config_value(:ik_private_ip) if locate_config_value(:ik_private_ip)
  params['size'] = locate_config_value(:size) if locate_config_value(:size)
  params['startvm'] = 'true'
  params['startvm'] = config[:startvm].to_s if config[:startvm]
  params['diskcontroller'] = locate_config_value(:cosmic_diskcontroller) if locate_config_value(:cosmic_diskcontroller)

  @server = connection.create_server(
      hostname,
      locate_config_value(:cosmic_service),
      locate_config_value(:cosmic_template),
      locate_config_value(:cosmic_disk),
      locate_config_value(:cosmic_zone),
      locate_config_value(:cosmic_networks),
      params
  )

  zone_name = locate_config_value(:cosmic_zone)
  zone = zone_name ? connection.get_zone(zone_name) : connection.get_default_zone

  config[:public_ip] = false if zone['networktype'] == 'Basic'
  @server['public_ip'] = find_or_create_public_ip(@server, connection)

  object_fields = []
  object_fields << ui.color("Name:", :cyan)
  object_fields << @server['name'].to_s
  object_fields << ui.color("Password:", :cyan) if locate_config_value(:cosmic_password)
  object_fields << @server['password'] if locate_config_value(:cosmic_password)
  object_fields << ui.color("Public IP:", :cyan)
  object_fields << @server['public_ip']
  object_fields << ui.color("id:", :cyan)
  object_fields << @server['id']

  puts "\n"
  puts ui.list(object_fields, :uneven_columns_across, 2)
  puts "\n"

  return unless config[:bootstrap]

  if @bootstrap_protocol == 'ssh'
    print "\n#{ui.color("Waiting for sshd on: #{public_ip}", :magenta)}"

    print(".") until is_ssh_open?(public_ip) {
      sleep BOOTSTRAP_DELAY
      puts "\n"
    }
  elsif @bootstrap_protocol == 'winrm'
    print "\n#{ui.color("Waiting for winrm to be active on: #{public_ip}", :magenta)}"
    print(".") until tcp_test_winrm(public_ip,locate_config_value(:winrm_port)) {
      sleep WINRM_BOOTSTRAP_DELAY
      puts("\n")
    }
  else
    puts "Cannot determine the bootstrap protocol."
    puts "Please specify either ssh or winrm as bootstrap protocol."
    exit 1
  end

  object_fields = []
  object_fields << ui.color("Name:", :cyan)
  object_fields << @server['name'].to_s
  object_fields << ui.color("Public IP:", :cyan)
  object_fields << @server['public_ip']
  object_fields << ui.color("Environment:", :cyan)
  object_fields << (config[:environment] || '_default')
  object_fields << ui.color("Run List:", :cyan)
  object_fields << config[:run_list].join(', ')

  puts "\n"
  puts ui.list(object_fields, :uneven_columns_across, 2)
  puts "\n"

  bootstrap(@server).run
end

#serverObject



669
670
671
# File 'lib/chef/knife/cosmic_server_create.rb', line 669

def server
  @server
end

#tcp_test_winrm(hostname, port) ⇒ Object



536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
# File 'lib/chef/knife/cosmic_server_create.rb', line 536

def tcp_test_winrm(hostname, port)
  TCPSocket.new(hostname, port)
  return true
  rescue SocketError
    sleep 2
    false
  rescue Errno::ETIMEDOUT
    false
  rescue Errno::EPERM
    false
  rescue Errno::ECONNREFUSED
    sleep 2
    false
  rescue Errno::EHOSTUNREACH
    sleep 2
    false
  rescue Errno::ENETUNREACH
    sleep 2
    false
end

#validate_optionsObject



396
397
398
399
400
401
402
403
404
405
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
432
433
434
# File 'lib/chef/knife/cosmic_server_create.rb', line 396

def validate_options
  unless locate_config_value :cosmic_template
    ui.error "cosmic template not specified"
    exit 1
  end
  @windows_image = is_image_windows?
  @windows_platform = is_platform_windows?

  unless locate_config_value :cosmic_service
    ui.error "cosmic service offering not specified"
    exit 1
  end
  if config[:bootstrap]
    if locate_config_value(:bootstrap_protocol) == 'ssh'
      identity_file = locate_config_value :identity_file
      ssh_user = locate_config_value :ssh_user
      ssh_password = locate_config_value :ssh_password
      unless identity_file || (ssh_user && ssh_password) || locate_config_value(:cosmic_password)
        ui.error("You must specify either an ssh identity file or an ssh user and password")
        exit 1
      end
      @bootstrap_protocol = 'ssh'
    elsif locate_config_value(:bootstrap_protocol) == 'winrm'
      if not @windows_image
        ui.error("Only Windows Images support WinRM protocol for bootstrapping.")
        exit 1
      end
      winrm_user = locate_config_value :winrm_user
      winrm_password = locate_config_value :winrm_password
      winrm_transport = locate_config_value :winrm_transport
      winrm_port = locate_config_value :winrm_port
      unless (winrm_user && winrm_transport && winrm_port) && (locate_config_value(:cosmic_password) || winrm_password)
        ui.error("WinRM User, Password, Transport and Port are compulsory parameters")
        exit 1
      end
      @bootstrap_protocol = 'winrm'
    end
  end
end