# # 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 "chef" require "chef/application" require "chef/client" require "chef/config" require "chef/log" require "fileutils" require "tempfile" require "chef/providers" require "chef/resources" class Chef::Application::Apply < Chef::Application banner "Usage: chef-apply [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: "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 :help, short: "-h", long: "--help", description: "Show this message", on: :tail, boolean: true, show_options: true, exit: 0 option :version, short: "-v", long: "--version", description: "Show chef version", boolean: true, proc: lambda { |v| puts "Chef: #{::Chef::VERSION}" }, exit: 0 option :why_run, short: "-W", long: "--why-run", description: "Enable whyrun mode", boolean: true option :profile_ruby, long: "--[no-]profile-ruby", description: "Dump complete Ruby call graph stack of entire Chef 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 chef 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("(chef-apply cookbook)", "(chef-apply 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 if !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 recipe.instance_eval(@recipe_text, @recipe_filename, 1) runner = Chef::Runner.new(run_context) begin 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 reconfigure run_application end end