class ChefApply::Startup

Constants

T

Attributes

argv[R]

Public Class Methods

new(argv) click to toggle source
# File lib/chef_apply/startup.rb, line 28
def initialize(argv)
  @term_init = false
  @argv = argv.clone
  # Enable CLI output via Terminal. This comes first because other startup steps may
  # need to output to the terminal.
  init_terminal
end

Public Instance Methods

create_default_config() click to toggle source
# File lib/chef_apply/startup.rb, line 114
def create_default_config
  UI::Terminal.output T.creating_config(Config.default_location)
  UI::Terminal.output ""
  FileUtils.mkdir_p(Config::WS_BASE_PATH)
  FileUtils.touch(Config.default_location)
end
custom_config_path() click to toggle source

Look for a user-supplied config path by manually parsing the option. Note that we can't use Mixlib::CLI for this. To ensure that ChefApply::CLI initializes with correct option defaults, we need to have configuraton loaded before initializing it.

# File lib/chef_apply/startup.rb, line 172
def custom_config_path
  argv.each_with_index do |arg, index|
    if arg == "--config-path" || arg == "-c"
      next_arg = argv[index + 1]
      raise ConfigPathNotProvided.new if next_arg.nil?
      raise ConfigPathInvalid.new(next_arg) unless File.file?(next_arg) && File.readable?(next_arg)

      return next_arg
    end
  end
  nil
end
first_run_tasks() click to toggle source
# File lib/chef_apply/startup.rb, line 107
def first_run_tasks
  return if Dir.exist?(Config::WS_BASE_PATH)

  create_default_config
  setup_telemetry
end
init_terminal() click to toggle source
# File lib/chef_apply/startup.rb, line 94
def init_terminal
  UI::Terminal.init($stdout)
end
load_config() click to toggle source
# File lib/chef_apply/startup.rb, line 162
def load_config
  path = custom_config_path
  Config.custom_location(path) unless path.nil?
  Config.load
end
run(enforce_license: false) click to toggle source
# File lib/chef_apply/startup.rb, line 36
def run(enforce_license: false)
  # This component is not supported in ChefDK; an exception will be raised
  # if running in that context.
  verify_not_in_chefdk

  # Some tasks we do only once in an installation:
  first_run_tasks

  # Call this every time, so that if we add or change ~/.chef-workstation
  # directory structure, we can be sure that it exists. Even with a
  # custom configuration, the .chef-workstation directory and subdirs
  # are required.
  setup_workstation_user_directories

  # Customize behavior of Ruby and any gems around error handling
  setup_error_handling

  # Startup tasks that may change behavior based on configuration value
  # must be run after load_config
  load_config

  # Init logging using log level out of config
  setup_logging

  # Begin upload of previous session telemetry. (If telemetry is not enabled,
  # in config the uploader will clean up previous session(s) without sending)
  start_telemeter_upload

  # Launch the actual Chef Apply behavior
  start_chef_apply(enforce_license: enforce_license)

# NOTE: Because these exceptions occur outside of the
#       CLI handling, they won't be tracked in telemtry.
#       We can revisit this once the pending error handling rework
#       is underway.
rescue ConfigPathInvalid => e
  UI::Terminal.output(T.error.bad_config_file(e.path))
rescue ConfigPathNotProvided
  UI::Terminal.output(T.error.missing_config_path)
rescue UnsupportedInstallation
  UI::Terminal.output(T.error.unsupported_installation)
rescue Mixlib::Config::UnknownConfigOptionError => e
  # Ideally we'd update the exception in mixlib to include
  # a field with the faulty value, line number, and nested context -
  # it's less fragile than depending on text parsing, which
  # is what we'll do for now.
  if e.message =~ /.*unsupported config value (.*)[.]+$/
    # TODO - levenshteinian distance to figure out
    # what they may have meant instead.
    UI::Terminal.output(T.error.invalid_config_key($1, Config.location))
  else
    # Safety net in case the error text changes from under us.
    UI::Terminal.output(T.error.unknown_config_error(e.message, Config.location))
  end
rescue Tomlrb::ParseError => e
  UI::Terminal.output(T.error.unknown_config_error(e.message, Config.location))
end
setup_error_handling() click to toggle source
# File lib/chef_apply/startup.rb, line 151
def setup_error_handling
  # In Ruby 2.5+ threads print out to stdout when they raise an exception. This is an agressive
  # attempt to ensure debugging information is not lost, but in our case it is not necessary
  # because we handle all the errors ourself. So we disable this to keep output clean.
  # See https://ruby-doc.org/core-2.5.0/Thread.html#method-c-report_on_exception
  #
  # We set this globally so that it applies to all threads we create - we never want any non-UI thread
  # to render error output to the terminal.
  Thread.report_on_exception = false
end
setup_logging() click to toggle source
# File lib/chef_apply/startup.rb, line 185
def setup_logging
  ChefApply::Log.setup(Config.log.location, Config.log.level.to_sym)
  ChefApply::Log.info("Initialized logger")

  ChefConfig.logger = ChefApply::Log
  # Setting the config isn't enough, we need to ensure the logger is
  # initialized with our existing stream or automatic initialization
  # will still go to stdout instead of the log file we opened.
  Chef::Log.init(ChefApply::Log.stream)
  Chef::Log.level = ChefApply::Log.level
end
setup_telemetry() click to toggle source
# File lib/chef_apply/startup.rb, line 121
def setup_telemetry
  require "securerandom" unless defined?(SecureRandom)
  installation_id = SecureRandom.uuid
  File.write(Config.telemetry_installation_identifier_file, installation_id)

  # Tell the user we're anonymously tracking, give brief opt-out
  # and a link to detailed information.
  UI::Terminal.output T.telemetry_enabled(Config.location)
  UI::Terminal.output ""
end
setup_workstation_user_directories() click to toggle source
# File lib/chef_apply/startup.rb, line 143
def setup_workstation_user_directories
  # Note that none of  these paths are customizable in config, so
  # it's safe to do before we load config.
  FileUtils.mkdir_p(Config::WS_BASE_PATH)
  FileUtils.mkdir_p(Config.base_log_directory)
  FileUtils.mkdir_p(Config.telemetry_path)
end
start_chef_apply(enforce_license: false) click to toggle source
# File lib/chef_apply/startup.rb, line 197
def start_chef_apply(enforce_license: false)
  require_relative "cli"
  ChefApply::CLI.new(@argv).run(enforce_license: enforce_license)
end
start_telemeter_upload() click to toggle source
# File lib/chef_apply/startup.rb, line 132
def start_telemeter_upload
  cfg = {
    enabled: Config.telemetry[:enabled],
    dev_mode: Config.telemetry[:dev_mode],
    payload_dir: Config.telemetry_path,
    installation_identifier_file: Config.telemetry_installation_identifier_file,
    session_file: Config.telemetry_session_file,
  }
  Chef::Telemeter.setup(cfg)
end
verify_not_in_chefdk() click to toggle source

Verify that chef-run gem is not executing out of ChefDK by checking the runtime path of this file.

NOTE: This is imperfect - someone could theoretically install chefdk to a path other than the default.

# File lib/chef_apply/startup.rb, line 103
def verify_not_in_chefdk
  raise UnsupportedInstallation.new if script_path =~ /chefdk/
end

Private Instance Methods

script_path() click to toggle source
# File lib/chef_apply/startup.rb, line 204
def script_path
  __dir__
end