Class: ChefApply::Startup

Inherits:
Object
  • Object
show all
Defined in:
lib/chef_apply/startup.rb

Defined Under Namespace

Classes: ConfigPathInvalid, ConfigPathNotProvided, UnsupportedInstallation

Constant Summary collapse

T =
ChefApply::Text.cli

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(argv) ⇒ Startup

Returns a new instance of Startup.



28
29
30
31
32
33
34
# 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

Instance Attribute Details

#argvObject (readonly)

Returns the value of attribute argv.



25
26
27
# File 'lib/chef_apply/startup.rb', line 25

def argv
  @argv
end

Instance Method Details

#create_default_configObject



114
115
116
117
118
119
# 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_pathObject

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 configuration loaded before initializing it.



172
173
174
175
176
177
178
179
180
181
182
183
# 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_tasksObject



107
108
109
110
111
112
# 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_terminalObject



94
95
96
# File 'lib/chef_apply/startup.rb', line 94

def init_terminal
  UI::Terminal.init($stdout)
end

#load_configObject



162
163
164
165
166
# 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) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# 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 sub-dirs
  # 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 telemetry.
#       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_handlingObject



151
152
153
154
155
156
157
158
159
160
# 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 aggressive
  # 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_loggingObject



185
186
187
188
189
190
191
192
193
194
195
# 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_telemetryObject



121
122
123
124
125
126
127
128
129
130
# 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_directoriesObject



143
144
145
146
147
148
149
# 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) ⇒ Object



197
198
199
200
# 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_uploadObject



132
133
134
135
136
137
138
139
140
141
# 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_chefdkObject

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.



103
104
105
# File 'lib/chef_apply/startup.rb', line 103

def verify_not_in_chefdk
  raise UnsupportedInstallation.new if script_path =~ /chefdk/
end