# # Author:: Bryan W. Berry () # Author:: Daniel DeLeo () # Copyright:: Copyright 2012-2016, Bryan W. Berry # Copyright:: Copyright 2012-2016, Daniel DeLeo # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require_relative "../../chef" require_relative "../application" require_relative "../client" require_relative "../config" require_relative "../config_fetcher" require_relative "../log" require "fileutils" unless defined?(FileUtils) require "tempfile" unless defined?(Tempfile) require_relative "../providers" require_relative "../resources" require "chef-utils/dist" unless defined?(ChefUtils::Dist) require "license_acceptance/cli_flags/mixlib_cli" class Chef::Application::Apply < Chef::Application include LicenseAcceptance::CLIFlags::MixlibCLI banner "Usage: #{ChefUtils::Dist::Apply::EXEC} [RECIPE_FILE | -e RECIPE_TEXT | -s] [OPTIONS]" option :execute, short: "-e RECIPE_TEXT", long: "--execute RECIPE_TEXT", description: "Execute resources supplied in a string.", proc: nil option :stdin, short: "-s", long: "--stdin", description: "Execute resources read from STDIN.", boolean: true option :json_attribs, short: "-j JSON_ATTRIBS", long: "--json-attributes JSON_ATTRIBS", description: "Load attributes from a JSON file or URL.", proc: nil option :force_logger, long: "--force-logger", description: "Use logger output instead of formatter output.", boolean: true, default: false option :force_formatter, long: "--force-formatter", description: "Use formatter output instead of logger output.", boolean: true, default: false option :formatter, short: "-F FORMATTER", long: "--format FORMATTER", description: "The output format to use.", proc: lambda { |format| Chef::Config.add_formatter(format) } option :log_level, short: "-l LEVEL", long: "--log_level LEVEL", description: "Set the log level (trace, debug, info, warn, error, fatal).", proc: lambda { |l| l.to_sym } option :log_location_cli, short: "-L LOGLOCATION", long: "--logfile LOGLOCATION", description: "Set the log file location, defaults to STDOUT - recommended for daemonizing." option :always_dump_stacktrace, long: "--[no-]always-dump-stacktrace", boolean: true, default: false, description: "Always dump the stacktrace regardless of the log_level setting." option :help, short: "-h", long: "--help", description: "Show this help message.", on: :tail, boolean: true, show_options: true, exit: 0 option :version, short: "-v", long: "--version", description: "Show #{ChefUtils::Dist::Infra::PRODUCT} version.", boolean: true, proc: lambda { |v| puts "#{ChefUtils::Dist::Infra::PRODUCT}: #{::Chef::VERSION}" }, exit: 0 option :why_run, short: "-W", long: "--why-run", description: "Enable whyrun mode.", boolean: true option :yaml, long: "--yaml", description: "Parse recipe as YAML", boolean: true, default: false option :profile_ruby, long: "--[no-]profile-ruby", description: "Dump complete Ruby call graph stack of entire #{ChefUtils::Dist::Infra::PRODUCT} run (expert only).", boolean: true, default: false option :color, long: "--[no-]color", boolean: true, default: true, description: "Use colored output, defaults to enabled." option :minimal_ohai, long: "--minimal-ohai", description: "Only run the bare minimum Ohai plugins #{ChefUtils::Dist::Infra::PRODUCT} needs to function.", boolean: true attr_reader :json_attribs def initialize super end def reconfigure parse_options Chef::Config.merge!(config) configure_logging Chef::Config.export_proxies Chef::Config.init_openssl parse_json end def parse_json if Chef::Config[:json_attribs] config_fetcher = Chef::ConfigFetcher.new(Chef::Config[:json_attribs]) @json_attribs = config_fetcher.fetch_json end end def read_recipe_file(file_name) if file_name.nil? Chef::Application.fatal!("No recipe file was provided", Chef::Exceptions::RecipeNotFound.new) else recipe_path = File.expand_path(file_name) unless File.exist?(recipe_path) Chef::Application.fatal!("No file exists at #{recipe_path}", Chef::Exceptions::RecipeNotFound.new) end recipe_fh = open(recipe_path) recipe_text = recipe_fh.read [recipe_text, recipe_fh] end end def get_recipe_and_run_context Chef::Config[:solo_legacy_mode] = true @chef_client = Chef::Client.new(@json_attribs) @chef_client.run_ohai @chef_client.load_node @chef_client.build_node run_context = if @chef_client.events.nil? Chef::RunContext.new(@chef_client.node, {}) else Chef::RunContext.new(@chef_client.node, {}, @chef_client.events) end recipe = Chef::Recipe.new("(#{ChefUtils::Dist::Apply::EXEC} cookbook)", "(#{ChefUtils::Dist::Apply::EXEC} recipe)", run_context) [recipe, run_context] end # write recipe to temp file, so in case of error, # user gets error w/ context def temp_recipe_file @recipe_fh = Tempfile.open("recipe-temporary-file") @recipe_fh.write(@recipe_text) @recipe_fh.rewind @recipe_filename = @recipe_fh.path end def run_chef_recipe if config[:execute] @recipe_text = config[:execute] temp_recipe_file elsif config[:stdin] @recipe_text = STDIN.read temp_recipe_file else unless ARGV[0] puts opt_parser Chef::Application.exit! "No recipe file provided", Chef::Exceptions::RecipeNotFound.new end @recipe_filename = ARGV[0] @recipe_text, @recipe_fh = read_recipe_file @recipe_filename end recipe, run_context = get_recipe_and_run_context if config[:yaml] || File.extname(@recipe_filename) == ".yml" logger.info "Parsing recipe as YAML" recipe.from_yaml(@recipe_text) else recipe.instance_eval(@recipe_text, @recipe_filename, 1) end runner = Chef::Runner.new(run_context) catch(:end_client_run_early) do runner.converge ensure @recipe_fh.close end Chef::Platform::Rebooter.reboot_if_needed!(runner) end def run_application parse_options run_chef_recipe Chef::Application.exit! "Exiting", 0 rescue SystemExit raise rescue Exception => e Chef::Application.debug_stacktrace(e) Chef::Application.fatal!("#{e.class}: #{e.message}", e) end # Get this party started def run(enforce_license: false) reconfigure check_license_acceptance if enforce_license run_application end end