summaryrefslogtreecommitdiff
path: root/lib/chef/cookbook
diff options
context:
space:
mode:
authorsersut <serdar@opscode.com>2014-03-30 13:45:00 -0700
committersersut <serdar@opscode.com>2014-03-30 13:45:00 -0700
commit7a1778fb309423114462d578e01ba0e00108010f (patch)
tree792e1fbafdca12725edf923cdad22536f3124fc3 /lib/chef/cookbook
parent0d097217dda26ac5551d1ad24132d9e53a62e0fb (diff)
parentcacf2a53a3b789829dd6b5b2956e07cc1aa42931 (diff)
downloadchef-11.12.0.rc.0.tar.gz
Merge branch 'master' into 11-stable11.12.0.rc.0
Merging mater branch for RC version.
Diffstat (limited to 'lib/chef/cookbook')
-rw-r--r--lib/chef/cookbook/chefignore.rb12
-rw-r--r--lib/chef/cookbook/metadata.rb34
-rw-r--r--lib/chef/cookbook/synchronizer.rb4
-rw-r--r--lib/chef/cookbook/syntax_check.rb121
4 files changed, 154 insertions, 17 deletions
diff --git a/lib/chef/cookbook/chefignore.rb b/lib/chef/cookbook/chefignore.rb
index 17c000350d..aa9345e64e 100644
--- a/lib/chef/cookbook/chefignore.rb
+++ b/lib/chef/cookbook/chefignore.rb
@@ -25,7 +25,11 @@ class Chef
attr_reader :ignores
def initialize(ignore_file_or_repo)
+ # Check the 'ignore_file_or_repo' path first and then look in the parent directory
+ # to handle both the chef repo cookbook layout and a standalone cookbook
@ignore_file = find_ignore_file(ignore_file_or_repo)
+ @ignore_file = find_ignore_file(File.dirname(ignore_file_or_repo)) unless readable_file_or_symlink?(@ignore_file)
+
@ignores = parse_ignore_file
end
@@ -43,8 +47,7 @@ class Chef
def parse_ignore_file
ignore_globs = []
- if File.exist?(@ignore_file) && File.readable?(@ignore_file) &&
- (File.file?(@ignore_file) || File.symlink?(@ignore_file))
+ if readable_file_or_symlink?(@ignore_file)
File.foreach(@ignore_file) do |line|
ignore_globs << line.strip unless line =~ COMMENTS_AND_WHITESPACE
end
@@ -61,6 +64,11 @@ class Chef
File.join(path, 'chefignore')
end
end
+
+ def readable_file_or_symlink?(path)
+ File.exist?(@ignore_file) && File.readable?(@ignore_file) &&
+ (File.file?(@ignore_file) || File.symlink?(@ignore_file))
+ end
end
end
end
diff --git a/lib/chef/cookbook/metadata.rb b/lib/chef/cookbook/metadata.rb
index b9b32c8224..32597490d3 100644
--- a/lib/chef/cookbook/metadata.rb
+++ b/lib/chef/cookbook/metadata.rb
@@ -391,14 +391,14 @@ class Chef
:description => { :kind_of => String },
:choice => { :kind_of => [ Array ], :default => [] },
:calculated => { :equal_to => [ true, false ], :default => false },
- :type => { :equal_to => [ "string", "array", "hash", "symbol" ], :default => "string" },
+ :type => { :equal_to => [ "string", "array", "hash", "symbol", "boolean", "numeric" ], :default => "string" },
:required => { :equal_to => [ "required", "recommended", "optional", true, false ], :default => "optional" },
:recipes => { :kind_of => [ Array ], :default => [] },
- :default => { :kind_of => [ String, Array, Hash ] }
+ :default => { :kind_of => [ String, Array, Hash, Symbol, Numeric, TrueClass, FalseClass ] }
}
)
options[:required] = remap_required_attribute(options[:required]) unless options[:required].nil?
- validate_string_array(options[:choice])
+ validate_choice_array(options)
validate_calculated_default_rule(options)
validate_choice_default_rule(options)
@@ -546,6 +546,34 @@ INVALID
end
end
+ # Validate the choice of the options hash
+ #
+ # Raise an exception if the members of the array do not match the defaults
+ # === Parameters
+ # opts<Hash>:: The options hash
+ def validate_choice_array(opts)
+ if opts[:choice].kind_of?(Array)
+ case opts[:type]
+ when "string"
+ validator = [ String ]
+ when "array"
+ validator = [ Array ]
+ when "hash"
+ validator = [ Hash ]
+ when "symbol"
+ validator = [ Symbol ]
+ when "boolean"
+ validator = [ TrueClass, FalseClass ]
+ when "numeric"
+ validator = [ Numeric ]
+ end
+
+ opts[:choice].each do |choice|
+ validate( {:choice => choice}, {:choice => {:kind_of => validator}} )
+ end
+ end
+ end
+
# For backwards compatibility, remap Boolean values to String
# true is mapped to "required"
# false is mapped to "optional"
diff --git a/lib/chef/cookbook/synchronizer.rb b/lib/chef/cookbook/synchronizer.rb
index 4522323fac..fc5d16617c 100644
--- a/lib/chef/cookbook/synchronizer.rb
+++ b/lib/chef/cookbook/synchronizer.rb
@@ -92,7 +92,7 @@ class Chef
# === Returns
# true:: Always returns true
def sync_cookbooks
- Chef::Log.info("Loading cookbooks [#{cookbook_names.sort.join(', ')}]")
+ Chef::Log.info("Loading cookbooks [#{cookbooks.map {|ckbk| ckbk.name + '@' + ckbk.version}.join(', ')}]")
Chef::Log.debug("Cookbooks detail: #{cookbooks.inspect}")
clear_obsoleted_cookbooks
@@ -136,7 +136,7 @@ class Chef
# valid_cache_entries<Hash>:: Out-param; Added to this hash are the files that
# were referred to by this cookbook
def sync_cookbook(cookbook)
- Chef::Log.debug("Synchronizing cookbook #{cookbook.name}")
+ Chef::Log.debug("Synchronizing cookbook #{cookbook.name} #{cookbook.version}")
# files and templates are lazily loaded, and will be done later.
diff --git a/lib/chef/cookbook/syntax_check.rb b/lib/chef/cookbook/syntax_check.rb
index 59888e2ba3..effc7dd01d 100644
--- a/lib/chef/cookbook/syntax_check.rb
+++ b/lib/chef/cookbook/syntax_check.rb
@@ -17,6 +17,8 @@
#
require 'pathname'
+require 'stringio'
+require 'erubis'
require 'chef/mixin/shell_out'
require 'chef/mixin/checksum'
@@ -75,6 +77,8 @@ class Chef
# validated.
attr_reader :validated_files
+ attr_reader :chefignore
+
# Creates a new SyntaxCheck given the +cookbook_name+ and a +cookbook_path+.
# If no +cookbook_path+ is given, +Chef::Config.cookbook_path+ is used.
def self.for_cookbook(cookbook_name, cookbook_path=nil)
@@ -90,11 +94,9 @@ class Chef
# cookbook_path::: the (on disk) path to the cookbook
def initialize(cookbook_path)
@cookbook_path = cookbook_path
- @validated_files = PersistentSet.new
- end
+ @chefignore ||= Chefignore.new(cookbook_path)
- def chefignore
- @chefignore ||= Chefignore.new(File.dirname(cookbook_path))
+ @validated_files = PersistentSet.new
end
def remove_ignored_files(file_list)
@@ -161,28 +163,127 @@ class Chef
def validate_template(erb_file)
Chef::Log.debug("Testing template #{erb_file} for syntax errors...")
- result = shell_out("erubis -x #{erb_file} | ruby -c")
+ if validate_inline?
+ validate_erb_file_inline(erb_file)
+ else
+ validate_erb_via_subcommand(erb_file)
+ end
+ end
+
+ def validate_ruby_file(ruby_file)
+ Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
+ if validate_inline?
+ validate_ruby_file_inline(ruby_file)
+ else
+ validate_ruby_by_subcommand(ruby_file)
+ end
+ end
+
+ # Whether or not we're running on a version of ruby that can support
+ # inline validation. Inline validation relies on the `RubyVM` features
+ # introduced with ruby 1.9, so 1.8 cannot be supported.
+ def validate_inline?
+ defined?(RubyVM::InstructionSequence)
+ end
+
+ # Validate the ruby code in an erb template. Uses RubyVM to do syntax
+ # checking, so callers should check #validate_inline? before calling.
+ def validate_erb_file_inline(erb_file)
+ old_stderr = $stderr
+
+ engine = Erubis::Eruby.new
+ engine.convert!(IO.read(erb_file))
+
+ ruby_code = engine.src
+
+ # Even when we're compiling the code w/ RubyVM, syntax errors just
+ # print to $stderr. We want to capture this and handle the printing
+ # ourselves, so we must temporarily swap $stderr to capture the output.
+ tmp_stderr = $stderr = StringIO.new
+
+ abs_path = File.expand_path(erb_file)
+ RubyVM::InstructionSequence.new(ruby_code, erb_file, abs_path, 0)
+
+ true
+ rescue SyntaxError
+ $stderr = old_stderr
+ invalid_erb_file(erb_file, tmp_stderr.string)
+ false
+ ensure
+ # be paranoid about setting stderr back to the old value.
+ $stderr = old_stderr if defined?(old_stderr) && old_stderr
+ end
+
+ # Validate the ruby code in an erb template. Pipes the output of `erubis
+ # -x` to `ruby -c`, so it works with any ruby version, but is much slower
+ # than the inline version.
+ # --
+ # TODO: This can be removed when ruby 1.8 support is dropped.
+ def validate_erb_via_subcommand(erb_file)
+ result = shell_out("erubis -x #{erb_file} | #{ruby} -c")
result.error!
true
rescue Mixlib::ShellOut::ShellCommandFailed
+ invalid_erb_file(erb_file, result.stderr)
+ false
+ end
+
+ # Debug a syntax error in a template.
+ def invalid_erb_file(erb_file, error_message)
file_relative_path = erb_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
Chef::Log.fatal("Erb template #{file_relative_path} has a syntax error:")
- result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
+ error_message.each_line { |l| Chef::Log.fatal(l.chomp) }
+ nil
+ end
+
+ # Validate the syntax of a ruby file. Uses (Ruby 1.9+ only) RubyVM to
+ # compile the code without evaluating it or spawning a new process.
+ # Callers should check #validate_inline? before calling.
+ def validate_ruby_file_inline(ruby_file)
+ # Even when we're compiling the code w/ RubyVM, syntax errors just
+ # print to $stderr. We want to capture this and handle the printing
+ # ourselves, so we must temporarily swap $stderr to capture the output.
+ old_stderr = $stderr
+ tmp_stderr = $stderr = StringIO.new
+ abs_path = File.expand_path(ruby_file)
+ file_content = IO.read(abs_path)
+ RubyVM::InstructionSequence.new(file_content, ruby_file, abs_path, 0)
+ true
+ rescue SyntaxError
+ $stderr = old_stderr
+ invalid_ruby_file(ruby_file, tmp_stderr.string)
false
+ ensure
+ # be paranoid about setting stderr back to the old value.
+ $stderr = old_stderr if defined?(old_stderr) && old_stderr
end
- def validate_ruby_file(ruby_file)
- Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
- result = shell_out("ruby -c #{ruby_file}")
+ # Validate the syntax of a ruby file by shelling out to `ruby -c`. Should
+ # work for all ruby versions, but is slower and uses more resources than
+ # the inline strategy.
+ def validate_ruby_by_subcommand(ruby_file)
+ result = shell_out("#{ruby} -c #{ruby_file}")
result.error!
true
rescue Mixlib::ShellOut::ShellCommandFailed
+ invalid_ruby_file(ruby_file, result.stderr)
+ false
+ end
+
+ # Debugs ruby syntax errors by printing the path to the file and any
+ # diagnostic info given in +error_message+
+ def invalid_ruby_file(ruby_file, error_message)
file_relative_path = ruby_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
Chef::Log.fatal("Cookbook file #{file_relative_path} has a ruby syntax error:")
- result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
+ error_message.each_line { |l| Chef::Log.fatal(l.chomp) }
false
end
+ # Returns the full path to the running ruby.
+ def ruby
+ Gem.ruby
+ end
+
end
end
end