Class: Chef::Knife::Bootstrap

Inherits:
Chef::Knife show all
Includes:
DataBagSecretOptions
Defined in:
lib/chef/knife/bootstrap.rb,
lib/chef/knife/bootstrap/client_builder.rb,
lib/chef/knife/bootstrap/chef_vault_handler.rb

Defined Under Namespace

Classes: ChefVaultHandler, ClientBuilder

Constant Summary

Constants inherited from Chef::Knife

OFFICIAL_PLUGINS

Instance Attribute Summary collapse

Attributes inherited from Chef::Knife

#name_args, #ui

Instance Method Summary collapse

Methods included from DataBagSecretOptions

#encryption_secret_provided?, #encryption_secret_provided_ignore_encrypt_flag?, included, #read_secret, #validate_secrets

Methods included from EncryptedDataBagItem::CheckEncrypted

#encrypted?

Methods inherited from Chef::Knife

#api_key, #apply_computed_config, category, chef_config_dir, #cli_keys, common_name, #config_file_settings, config_loader, #configure_chef, #create_object, #delete_object, dependency_loaders, deps, #format_rest_error, guess_category, #humanize_exception, #humanize_http_exception, inherited, list_commands, load_commands, load_config, load_deps, #maybe_setup_fips, #merge_configs, msg, #noauth_rest, #parse_options, reset_config_loader!, reset_subcommands!, #rest, run, #run_with_pretty_exceptions, #server_url, #show_usage, snake_case_name, subcommand_category, subcommand_class_from, subcommand_files, subcommand_loader, subcommands, subcommands_by_category, #test_mandatory_field, ui, unnamed?, use_separate_defaults?, #username

Methods included from Mixin::ConvertToClassName

#constantize, #convert_to_class_name, #convert_to_snake_case, #filename_to_qualified_string, #normalize_snake_case_name, #snake_case_basename

Methods included from Mixin::PathSanity

#enforce_path_sanity, #sanitized_path

Constructor Details

#initialize(argv = []) ⇒ Bootstrap

Returns a new instance of Bootstrap


267
268
269
270
271
272
273
274
275
276
277
278
# File 'lib/chef/knife/bootstrap.rb', line 267

def initialize(argv = [])
  super
  @client_builder = Chef::Knife::Bootstrap::ClientBuilder.new(
    chef_config: Chef::Config,
    knife_config: config,
    ui: ui
  )
  @chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new(
    knife_config: config,
    ui: ui
  )
end

Instance Attribute Details

#chef_vault_handlerObject

Returns the value of attribute chef_vault_handler


32
33
34
# File 'lib/chef/knife/bootstrap.rb', line 32

def chef_vault_handler
  @chef_vault_handler
end

#client_builderObject

Returns the value of attribute client_builder


31
32
33
# File 'lib/chef/knife/bootstrap.rb', line 31

def client_builder
  @client_builder
end

Instance Method Details

#bootstrap_contextObject


352
353
354
355
356
357
358
359
# File 'lib/chef/knife/bootstrap.rb', line 352

def bootstrap_context
  @bootstrap_context ||= Knife::Core::BootstrapContext.new(
    config,
    config[:run_list],
    Chef::Config,
    secret
  )
end

#bootstrap_templateObject


309
310
311
312
313
314
# File 'lib/chef/knife/bootstrap.rb', line 309

def bootstrap_template
  # The order here is important. We want to check if we have the new Chef 12 option is set first.
  # Knife cloud plugins unfortunately all set a default option for the :distro so it should be at
  # the end.
  config[:bootstrap_template] || config[:template_file] || config[:distro] || default_bootstrap_template
end

#default_bootstrap_templateString

The default bootstrap template to use to bootstrap a server This is a public API hook which knife plugins use or inherit and override.

Returns:

  • (String)

    Default bootstrap template


284
285
286
# File 'lib/chef/knife/bootstrap.rb', line 284

def default_bootstrap_template
  "chef-full"
end

#find_templateObject


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

def find_template
  template = bootstrap_template

  # Use the template directly if it's a path to an actual file
  if File.exists?(template)
    Chef::Log.debug("Using the specified bootstrap template: #{File.dirname(template)}")
    return template
  end

  # Otherwise search the template directories until we find the right one
  bootstrap_files = []
  bootstrap_files << File.join(File.dirname(__FILE__), "bootstrap/templates", "#{template}.erb")
  bootstrap_files << File.join(Knife.chef_config_dir, "bootstrap", "#{template}.erb") if Chef::Knife.chef_config_dir
  Chef::Util::PathHelper.home(".chef", "bootstrap", "#{template}.erb") { |p| bootstrap_files << p }
  bootstrap_files << Gem.find_files(File.join("chef", "knife", "bootstrap", "#{template}.erb"))
  bootstrap_files.flatten!

  template_file = Array(bootstrap_files).find do |bootstrap_template|
    Chef::Log.debug("Looking for bootstrap template in #{File.dirname(bootstrap_template)}")
    File.exists?(bootstrap_template)
  end

  unless template_file
    ui.info("Can not find bootstrap definition for #{template}")
    raise Errno::ENOENT
  end

  Chef::Log.debug("Found bootstrap template in #{File.dirname(template_file)}")

  template_file
end

#first_boot_attributesObject


361
362
363
# File 'lib/chef/knife/bootstrap.rb', line 361

def first_boot_attributes
  @config[:first_boot_attributes] || @config[:first_boot_attributes_from_file] || {}
end

#host_descriptorObject


288
289
290
# File 'lib/chef/knife/bootstrap.rb', line 288

def host_descriptor
  Array(@name_args).first
end

#knife_sshObject


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

def knife_ssh
  ssh = Chef::Knife::Ssh.new
  ssh.ui = ui
  ssh.name_args = [ server_name, ssh_command ]
  ssh.config[:ssh_user] = user_name || config[:ssh_user]
  ssh.config[:ssh_password] = config[:ssh_password]
  ssh.config[:ssh_port] = config[:ssh_port]
  ssh.config[:ssh_gateway] = config[:ssh_gateway]
  ssh.config[:ssh_gateway_identity] = config[:ssh_gateway_identity]
  ssh.config[:forward_agent] = config[:forward_agent]
  ssh.config[:ssh_identity_file] = config[:ssh_identity_file] || config[:identity_file]
  ssh.config[:manual] = true
  ssh.config[:host_key_verify] = config[:host_key_verify]
  ssh.config[:on_error] = :raise
  ssh
end

#knife_ssh_with_password_authObject


455
456
457
458
459
460
# File 'lib/chef/knife/bootstrap.rb', line 455

def knife_ssh_with_password_auth
  ssh = knife_ssh
  ssh.config[:ssh_identity_file] = nil
  ssh.config[:ssh_password] = ssh.get_password
  ssh
end

#render_templateObject


365
366
367
368
369
370
# File 'lib/chef/knife/bootstrap.rb', line 365

def render_template
  @config[:first_boot_attributes] = first_boot_attributes
  template_file = find_template
  template = IO.read(template_file).chomp
  Erubis::Eruby.new(template).evaluate(bootstrap_context)
end

#runObject


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
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
# File 'lib/chef/knife/bootstrap.rb', line 372

def run
  if @config[:first_boot_attributes] && @config[:first_boot_attributes_from_file]
    raise Chef::Exceptions::BootstrapCommandInputError
  end

  validate_name_args!
  validate_options!

  $stdout.sync = true

  # chef-vault integration must use the new client-side hawtness, otherwise to use the
  # new client-side hawtness, just delete your validation key.
  if chef_vault_handler.doing_chef_vault? ||
      (Chef::Config[:validation_key] && !File.exist?(File.expand_path(Chef::Config[:validation_key])))

    unless config[:chef_node_name]
      ui.error("You must pass a node name with -N when bootstrapping with user credentials")
      exit 1
    end

    client_builder.run

    chef_vault_handler.run(client_builder.client)

    bootstrap_context.client_pem = client_builder.client_path
  else
    ui.info("Doing old-style registration with the validation key at #{Chef::Config[:validation_key]}...")
    ui.info("Delete your validation key in order to use your user credentials instead")
    ui.info("")
  end

  ui.info("Connecting to #{ui.color(server_name, :bold)}")

  begin
    knife_ssh.run
  rescue Net::SSH::AuthenticationFailed
    if config[:ssh_password]
      raise
    else
      ui.info("Failed to authenticate #{knife_ssh.config[:ssh_user]} - trying password auth")
      knife_ssh_with_password_auth.run
    end
  end
end

#secretObject


348
349
350
# File 'lib/chef/knife/bootstrap.rb', line 348

def secret
  @secret ||= encryption_secret_provided_ignore_encrypt_flag? ? read_secret : nil
end

#server_nameString

The server_name is the DNS or IP we are going to connect to, it is not necessarily the node name, the fqdn, or the hostname of the server. This is a public API hook which knife plugins use or inherit and override.

Returns:

  • (String)

    The DNS or IP that bootstrap will connect to


297
298
299
300
301
# File 'lib/chef/knife/bootstrap.rb', line 297

def server_name
  if host_descriptor
    @server_name ||= host_descriptor.split("@").reverse[0]
  end
end

#ssh_commandObject


462
463
464
465
466
467
468
469
470
471
# File 'lib/chef/knife/bootstrap.rb', line 462

def ssh_command
  command = render_template

  if config[:use_sudo]
    sudo_prefix = config[:use_sudo_password] ? "echo '#{config[:ssh_password]}' | sudo -S " : "sudo "
    command = config[:preserve_home] ? "#{sudo_prefix} #{command}" : "#{sudo_prefix} -H #{command}"
  end

  command
end

#user_nameObject


303
304
305
306
307
# File 'lib/chef/knife/bootstrap.rb', line 303

def user_name
  if host_descriptor
    @user_name ||= host_descriptor.split("@").reverse[1]
  end
end

#validate_name_args!Object


417
418
419
420
421
422
423
424
425
# File 'lib/chef/knife/bootstrap.rb', line 417

def validate_name_args!
  if server_name.nil?
    ui.error("Must pass an FQDN or ip to bootstrap")
    exit 1
  elsif server_name == "windows"
    # catches "knife bootstrap windows" when that command is not installed
    ui.warn("Hostname containing 'windows' specified. Please install 'knife-windows' if you are attempting to bootstrap a Windows node via WinRM.")
  end
end

#validate_options!Object


427
428
429
430
431
432
433
434
435
436
# File 'lib/chef/knife/bootstrap.rb', line 427

def validate_options!
  if incomplete_policyfile_options?
    ui.error("--policy-name and --policy-group must be specified together")
    exit 1
  elsif policyfile_and_run_list_given?
    ui.error("Policyfile options and --run-list are exclusive")
    exit 1
  end
  true
end