diff options
author | Tim Smith <tsmith@chef.io> | 2020-03-23 10:17:46 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-23 10:17:46 -0700 |
commit | 5d10babc7abe54cf556c2edfd12c6376894e1350 (patch) | |
tree | 53c177e0d47a0b3eeb551b25e2efb82be83a159f | |
parent | 85005840999a86453175d2345545bf8e8c22c3bc (diff) | |
parent | 97497d968b806727cfeb00fdef2ec3976fbd53d8 (diff) | |
download | chef-5d10babc7abe54cf556c2edfd12c6376894e1350.tar.gz |
Merge pull request #9507 from chef/btm/yaml
Add support for parsing YAML recipes with chef-apply
-rw-r--r-- | lib/chef/application/apply.rb | 13 | ||||
-rw-r--r-- | lib/chef/recipe.rb | 25 | ||||
-rw-r--r-- | spec/unit/application/apply_spec.rb | 3 | ||||
-rw-r--r-- | spec/unit/recipe_spec.rb | 68 |
4 files changed, 95 insertions, 14 deletions
diff --git a/lib/chef/application/apply.rb b/lib/chef/application/apply.rb index 4718e8750b..7267f4ecfa 100644 --- a/lib/chef/application/apply.rb +++ b/lib/chef/application/apply.rb @@ -105,6 +105,12 @@ class Chef::Application::Apply < Chef::Application 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 #{Chef::Dist::PRODUCT} run (expert only).", @@ -198,7 +204,12 @@ class Chef::Application::Apply < Chef::Application @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) + 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 begin diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb index ddb45de8e3..890edc6d13 100644 --- a/lib/chef/recipe.rb +++ b/lib/chef/recipe.rb @@ -1,7 +1,7 @@ #-- # Author:: Adam Jacob (<adam@chef.io>) # Author:: Christopher Walters (<cw@chef.io>) -# Copyright:: Copyright 2008-2018, Chef Software Inc. +# Copyright:: Copyright 2008-2020, Chef Software Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ # limitations under the License. # +require "yaml" require_relative "dsl/recipe" require_relative "mixin/from_file" require_relative "mixin/deprecation" @@ -88,26 +89,24 @@ class Chef def from_yaml_file(filename) self.source_file = filename if File.file?(filename) && File.readable?(filename) - from_yaml(IO.read(filename)) + yaml_contents = IO.read(filename) + if ::YAML.load_stream(yaml_contents).length > 1 + raise ArgumentError, "YAML recipe '#{filename}' contains multiple documents, only one is supported" + end + + from_yaml(yaml_contents) else - raise IOError, "Cannot open or read #{filename}!" + raise IOError, "Cannot open or read file '#{filename}'!" end end def from_yaml(string) res = ::YAML.safe_load(string) - if res.is_a?(Hash) - from_hash(res) - elsif res.is_a?(Array) - from_array(res) - else - raise "boom" + unless res.is_a?(Hash) && res.key?("resources") + raise ArgumentError, "YAML recipe '#{source_file}' must contain a top-level 'resources' hash (YAML sequence), i.e. 'resources:'" end - end - def from_array(array) - Chef::Log.warn "array yaml files are super duper experimental behavior" - array.each { |e| from_hash(e) } + from_hash(res) end def from_hash(hash) diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index b659f13fe8..f071dbd981 100644 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -46,6 +46,7 @@ describe Chef::Application::Apply do it "should read text properly" do expect(@app.read_recipe_file(@recipe_file_name)[0]).to eq(@recipe_text) end + it "should return a file_handle" do expect(@app.read_recipe_file(@recipe_file_name)[1]).to be_instance_of(RSpec::Mocks::Double) end @@ -57,6 +58,7 @@ describe Chef::Application::Apply do @app.read_recipe_file(nil) end end + describe "when recipe doesn't exist" do before do allow(File).to receive(:exist?).with(@recipe_path).and_return(false) @@ -68,6 +70,7 @@ describe Chef::Application::Apply do end end end + describe "temp_recipe_file" do before do @app.instance_variable_set(:@recipe_text, @recipe_text) diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index 099e6750f6..ef8fb78f90 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -576,6 +576,74 @@ describe Chef::Recipe do end end + describe "from_yaml_file" do + it "raises ArgumentError if the YAML file contains multiple documents" do + filename = "multiple_docs.yaml" + yaml = "---\n- resources:\n - type: false\n---\n-resources:\n - type: false\n" + allow(File).to receive(:file?).and_call_original + allow(File).to receive(:readable?).and_call_original + allow(IO).to receive(:read).and_call_original + allow(File).to receive(:file?).with(filename).and_return(true) + allow(File).to receive(:readable?).with(filename).and_return(true) + allow(IO).to receive(:read).with(filename).and_return(yaml) + expect { recipe.from_yaml_file(filename) }.to raise_error(ArgumentError, /contains multiple documents/) + end + + it "raises IOError if the file does not exist" do + filename = "/nonexistent" + allow(File).to receive(:file?).and_call_original + allow(File).to receive(:file?).with(filename).and_return(false) + expect { recipe.from_yaml_file(filename) }.to raise_error(IOError, /Cannot open or read/) + end + end + + describe "from_yaml" do + it "raises ArgumentError if the YAML is not a top-level hash" do + yaml = <<~YAML + --- + - one + - resources + - three + YAML + expect { recipe.from_yaml(yaml) }.to raise_error(ArgumentError, /must contain a top-level 'resources' hash/) + end + + it "raises ArgumentError if the YAML does not contain a resources hash" do + yaml = <<~YAML + --- + - airplanes: + - type: "execute" + command: "whoami" + YAML + expect { recipe.from_yaml(yaml) }.to raise_error(ArgumentError, /must contain a top-level 'resources' hash/) + end + + it "does not raise if the YAML contains a resources hash" do + yaml = <<~YAML + --- + resources: + - type: "execute" + command: "whoami" + YAML + expect(recipe).to receive(:from_hash).with({ "resources" => [{ "command" => "whoami", "type" => "execute" }] }) + recipe.from_yaml(yaml) + end + end + + describe "from_hash" do + it "declares resources from a hash" do + resources = { "resources" => [ + { "name" => "running some commands", "type" => "execute", "command" => "whoami" }, + { "name" => "preparing the bits", "type" => "service", "action" => "start", "service_name" => "bit_launcher" }, + ] } + + recipe.from_hash(resources) + expect(recipe.resources(execute: "running some commands").command).to eql("whoami") + expect(recipe.resources(service: "preparing the bits").service_name).to eql("bit_launcher") + expect(recipe.resources(service: "preparing the bits").action).to eql([:start]) + end + end + describe "included DSL" do it "should respond to :ps_credential from Chef::DSL::Powershell" do expect(recipe.respond_to?(:ps_credential)).to be true |