class Chef::Application::Base

This is a temporary class being used as a part of an effort to reduce duplication between Chef::Application::Client and Chef::Application::Solo.

If you are looking to make edits to the Client/Solo behavior please make changes here.

If you are looking to reference or subclass this class, use Chef::Application::Client instead. This base class will be removed once the work is complete and external code will break.

@deprecated use Chef::Application::Client instead, this will be removed in Chef-16

Constants

IMMEDIATE_RUN_SIGNAL
RECONFIGURE_SIGNAL
SELF_PIPE

Mimic self_pipe sleep from Unicorn to capture signals safely

Attributes

chef_client_json[R]

Public Instance Methods

run_application() click to toggle source

Run the chef client, optionally daemonizing or looping at intervals.

# File lib/chef/application/base.rb, line 346
def run_application
  if Chef::Config[:version]
    puts "#{ChefUtils::Dist::Infra::PRODUCT} version: #{::Chef::VERSION}"
  end

  if !Chef::Config[:client_fork] || Chef::Config[:once]
    begin
      # run immediately without interval sleep, or splay
      run_chef_client(Chef::Config[:specific_recipes])
    rescue SystemExit
      raise
    rescue Exception => e
      Chef::Application.fatal!("#{e.class}: #{e.message}", e)
    end
  else
    interval_run_chef_client
  end
end
setup_application() click to toggle source
# File lib/chef/application/base.rb, line 322
def setup_application
  Chef::Daemon.change_privilege
end
setup_signal_handlers() click to toggle source
Calls superclass method Chef::Application#setup_signal_handlers
# File lib/chef/application/base.rb, line 326
def setup_signal_handlers
  super

  unless Chef::Platform.windows?
    SELF_PIPE.replace IO.pipe

    trap("USR1") do
      Chef::Log.info("SIGUSR1 received, will run now or after the current run")
      SELF_PIPE[1].putc(IMMEDIATE_RUN_SIGNAL) # wakeup master process from select
    end

    # Override the trap setup in the parent so we can avoid running reconfigure during a run
    trap("HUP") do
      Chef::Log.info("SIGHUP received, will reconfigure now or after the current run")
      SELF_PIPE[1].putc(RECONFIGURE_SIGNAL) # wakeup master process from select
    end
  end
end

Private Instance Methods

aws_api_region() click to toggle source
# File lib/chef/application/base.rb, line 420
def aws_api_region
  ENV["AWS_REGION"] || Aws.shared_config.region || Aws::EC2Metadata.new.get("/latest/meta-data/placement/region")
end
fetch_recipe_tarball(url, path) click to toggle source
# File lib/chef/application/base.rb, line 381
def fetch_recipe_tarball(url, path)
  require "open-uri" unless defined?(OpenURI)
  uri = URI.parse(url)

  Chef::Log.trace("Download recipes tarball from #{url} to #{path}")
  if File.exist?(url)
    FileUtils.cp(url, path)
  elsif uri.scheme == "s3"
    require "aws-sdk-s3" unless defined?(Aws::S3)

    bucket_name = uri.hostname
    s3 = Aws::S3::Client.new(region: s3_bucket_location(bucket_name))

    object = s3.get_object(bucket: bucket_name, key: uri.path[1..-1])
    File.open(path, "wb") do |f|
      f.write(object.body.read)
    end
  elsif URI::DEFAULT_PARSER.make_regexp.match?(url)
    File.open(path, "wb") do |f|
      URI.open(url) do |r|
        f.write(r.read)
      end
    end
  else
    Chef::Application.fatal! "You specified --recipe-url but the value is neither a valid URL, an S3 bucket nor a path to a file that exists on disk." +
      "Please confirm the location of the tarball and try again."
  end
end
for_ezra() click to toggle source
# File lib/chef/application/base.rb, line 488
  def for_ezra
    puts <<~EOH
      For Ezra Zygmuntowicz:
        The man who brought you Chef Solo
        Early contributor to Chef
        Kind hearted open source advocate
        Rest in peace, Ezra.
    EOH
  end
interval_run_chef_client() click to toggle source
# File lib/chef/application/base.rb, line 424
def interval_run_chef_client
  if Chef::Config[:daemonize]
    Chef::Daemon.daemonize(ChefUtils::Dist::Infra::PRODUCT)

    # Start first daemonized run after configured number of seconds
    if Chef::Config[:daemonize].is_a?(Integer)
      sleep_then_run_chef_client(Chef::Config[:daemonize])
    end
  end

  loop do
    sleep_then_run_chef_client(time_to_sleep)
    Chef::Application.exit!("Exiting", 0) unless Chef::Config[:interval]
  end
end
interval_sleep(sec) click to toggle source

sleep and handle queued signals

# File lib/chef/application/base.rb, line 469
def interval_sleep(sec)
  unless SELF_PIPE.empty?
    # mimic sleep with a timeout on IO.select, listening for signals setup in #setup_signal_handlers
    return unless IO.select([ SELF_PIPE[0] ], nil, nil, sec)

    signal = SELF_PIPE[0].getc.chr

    return if signal == IMMEDIATE_RUN_SIGNAL # be explicit about this behavior

    # we need to sleep again after reconfigure to avoid stampeding when logrotate runs out of cron
    if signal == RECONFIGURE_SIGNAL
      reconfigure
      interval_sleep(sec)
    end
  else
    sleep(sec)
  end
end
s3_bucket_location(bucket_name) click to toggle source
# File lib/chef/application/base.rb, line 410
def s3_bucket_location(bucket_name)
  s3 = Aws::S3::Client.new(region: aws_api_region)

  resp = s3.get_bucket_location(bucket: bucket_name)
  resp.location_constraint
rescue Aws::S3::Errors::AccessDenied => _e
  Chef::Log.warn("Missing s3:GetBucketLocation privilege, trying currently configured region #{aws_api_region}")
  aws_api_region
end
sleep_then_run_chef_client(sleep_sec) click to toggle source
# File lib/chef/application/base.rb, line 440
def sleep_then_run_chef_client(sleep_sec)
  Chef::Log.trace("Sleeping for #{sleep_sec} seconds")

  # interval_sleep will return early if we received a signal (unless on windows)
  interval_sleep(sleep_sec)

  run_chef_client(Chef::Config[:specific_recipes])

  reconfigure
rescue SystemExit => e
  raise
rescue Exception => e
  if Chef::Config[:interval]
    Chef::Log.error("#{e.class}: #{e}")
    Chef::Log.trace("#{e.class}: #{e}\n#{e.backtrace.join("\n")}")
    retry
  end

  Chef::Application.fatal!("#{e.class}: #{e.message}", e)
end
time_to_sleep() click to toggle source
# File lib/chef/application/base.rb, line 461
def time_to_sleep
  duration = 0
  duration += rand(Chef::Config[:splay]) if Chef::Config[:splay]
  duration += Chef::Config[:interval] if Chef::Config[:interval]
  duration
end
unforked_interval_error_message() click to toggle source
# File lib/chef/application/base.rb, line 374
def unforked_interval_error_message
  "Unforked #{ChefUtils::Dist::Infra::PRODUCT} interval runs are disabled by default." +
    "\nConfiguration settings:" +
    ("\n  interval  = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]).to_s +
    "\nEnable #{ChefUtils::Dist::Infra::PRODUCT} interval runs by setting `:client_fork = true` in your config file or adding `--fork` to your command line options."
end
windows_interval_error_message() click to toggle source
# File lib/chef/application/base.rb, line 367
def windows_interval_error_message
  "Windows #{ChefUtils::Dist::Infra::PRODUCT} interval runs are not supported in #{ChefUtils::Dist::Infra::PRODUCT} 15 and later." +
    "\nConfiguration settings:" +
    ("\n  interval  = #{Chef::Config[:interval]} seconds" if Chef::Config[:interval]).to_s +
    "\nPlease manage #{ChefUtils::Dist::Infra::PRODUCT} as a scheduled task instead."
end