class Chef::Cookbook::SyntaxCheck

Chef::Cookbook::SyntaxCheck

Encapsulates the process of validating the ruby syntax of files in Chef cookbooks.

Attributes

chefignore[R]
cookbook_path[R]
validated_files[R]

A PersistentSet object that tracks which files have already been validated.

Public Class Methods

for_cookbook(cookbook_name, cookbook_path = nil) click to toggle source

Creates a new SyntaxCheck given the cookbook_name and a cookbook_path. If no cookbook_path is given, Chef::Config.cookbook_path is used.

# File lib/chef/cookbook/syntax_check.rb, line 85
def self.for_cookbook(cookbook_name, cookbook_path = nil)
  cookbook_path ||= Chef::Config.cookbook_path
  unless cookbook_path
    raise ArgumentError, "Cannot find cookbook #{cookbook_name} unless Chef::Config.cookbook_path is set or an explicit cookbook path is given"
  end
  new(File.join(cookbook_path, cookbook_name.to_s))
end
new(cookbook_path) click to toggle source

Create a new SyntaxCheck object

Arguments

#cookbook_path:

the (on disk) path to the cookbook

# File lib/chef/cookbook/syntax_check.rb, line 96
def initialize(cookbook_path)
  @cookbook_path = cookbook_path
  @chefignore ||= Chefignore.new(cookbook_path)

  @validated_files = PersistentSet.new
end

Public Instance Methods

invalid_erb_file(erb_file, error_message) click to toggle source

Debug a syntax error in a template.

# File lib/chef/cookbook/syntax_check.rb, line 211
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:")
  error_message.each_line { |l| Chef::Log.fatal(l.chomp) }
  nil
end
invalid_ruby_file(ruby_file, error_message) click to toggle source

Debugs ruby syntax errors by printing the path to the file and any diagnostic info given in error_message

# File lib/chef/cookbook/syntax_check.rb, line 246
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:")
  error_message.each_line { |l| Chef::Log.fatal(l.chomp) }
  false
end
remove_ignored_files(file_list) click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 103
def remove_ignored_files(file_list)
  return file_list if chefignore.ignores.empty?

  file_list.reject do |full_path|
    relative_pn = Chef::Util::PathHelper.relative_path_from(cookbook_path, full_path)
    chefignore.ignored?(relative_pn.to_s)
  end
end
remove_uninteresting_ruby_files(file_list) click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 112
def remove_uninteresting_ruby_files(file_list)
  file_list.reject { |f| f =~ %r{#{Regexp.quote(cookbook_path)}/(files|templates)/} }
end
ruby() click to toggle source

Returns the full path to the running ruby.

# File lib/chef/cookbook/syntax_check.rb, line 254
def ruby
  Gem.ruby
end
ruby_files() click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 116
def ruby_files
  path  = Chef::Util::PathHelper.escape_glob_dir(cookbook_path)
  files = Dir[File.join(path, "**", "*.rb")]
  files = remove_ignored_files(files)
  files = remove_uninteresting_ruby_files(files)
  files
end
template_files() click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 135
def template_files
  remove_ignored_files Dir[File.join(Chef::Util::PathHelper.escape_glob_dir(cookbook_path), "**/templates/**", "*.erb")]
end
untested_ruby_files() click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 124
def untested_ruby_files
  ruby_files.reject do |file|
    if validated?(file)
      Chef::Log.trace("Ruby file #{file} is unchanged, skipping syntax check")
      true
    else
      false
    end
  end
end
untested_template_files() click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 139
def untested_template_files
  template_files.reject do |file|
    if validated?(file)
      Chef::Log.trace("Template #{file} is unchanged, skipping syntax check")
      true
    else
      false
    end
  end
end
validate_erb_file_inline(erb_file) click to toggle source

Validate the ruby code in an erb template. Uses RubyVM to do syntax checking.

# File lib/chef/cookbook/syntax_check.rb, line 184
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_ruby_file(ruby_file) click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 177
def validate_ruby_file(ruby_file)
  Chef::Log.trace("Testing #{ruby_file} for syntax errors...")
  validate_ruby_file_inline(ruby_file)
end
validate_ruby_file_inline(ruby_file) click to toggle source

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.

# File lib/chef/cookbook/syntax_check.rb, line 220
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)
  # We have to wrap this in a block so the user code evaluates in a
  # similar context as what Chef does normally. Otherwise RubyVM
  # will reject some common idioms, like using `return` to end evaluation
  # of a recipe. See also CHEF-5199
  wrapped_content = "Object.new.instance_eval do\n#{file_content}\nend\n"
  RubyVM::InstructionSequence.new(wrapped_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
validate_ruby_files() click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 158
def validate_ruby_files
  untested_ruby_files.each do |ruby_file|
    return false unless validate_ruby_file(ruby_file)
    validated(ruby_file)
  end
end
validate_template(erb_file) click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 172
def validate_template(erb_file)
  Chef::Log.trace("Testing template #{erb_file} for syntax errors...")
  validate_erb_file_inline(erb_file)
end
validate_templates() click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 165
def validate_templates
  untested_template_files.each do |template|
    return false unless validate_template(template)
    validated(template)
  end
end
validated(file) click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 154
def validated(file)
  validated_files.add(checksum(file))
end
validated?(file) click to toggle source
# File lib/chef/cookbook/syntax_check.rb, line 150
def validated?(file)
  validated_files.include?(checksum(file))
end