# 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 84
    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 95
    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 232
    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 280
    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 102
    def remove_ignored_files(file_list)
      return file_list unless chefignore.ignores.length > 0
      file_list.reject do |full_path|
        cookbook_pn = Pathname.new cookbook_path
        full_pn = Pathname.new full_path
        relative_pn = full_pn.relative_path_from cookbook_pn
        chefignore.ignored? relative_pn.to_s
      end
    end

ruby() click to toggle source

Returns the full path to the running ruby.

    # File lib/chef/cookbook/syntax_check.rb, line 288
    def ruby
      Gem.ruby
    end

ruby_files() click to toggle source

    # File lib/chef/cookbook/syntax_check.rb, line 112
    def ruby_files
      remove_ignored_files Dir[File.join(cookbook_path, '**', '*.rb')]
    end

template_files() click to toggle source

    # File lib/chef/cookbook/syntax_check.rb, line 127
    def template_files
      remove_ignored_files Dir[File.join(cookbook_path, '**', '*.erb')]
    end

untested_ruby_files() click to toggle source

    # File lib/chef/cookbook/syntax_check.rb, line 116
    def untested_ruby_files
      ruby_files.reject do |file|
        if validated?(file)
          Chef::Log.debug("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 131
    def untested_template_files
      template_files.reject do |file|
        if validated?(file)
          Chef::Log.debug("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, so callers should check validate_inline? before calling.

    # File lib/chef/cookbook/syntax_check.rb, line 191
    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_erb_via_subcommand(erb_file) click to toggle source

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.

    # File lib/chef/cookbook/syntax_check.rb, line 222
    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

validate_inline?() click to toggle source

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.

    # File lib/chef/cookbook/syntax_check.rb, line 185
    def validate_inline?
      defined?(RubyVM::InstructionSequence)
    end

validate_ruby_by_subcommand(ruby_file) click to toggle source

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.

    # File lib/chef/cookbook/syntax_check.rb, line 269
    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

validate_ruby_file(ruby_file) click to toggle source

    # File lib/chef/cookbook/syntax_check.rb, line 173
    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

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. Callers should check validate_inline? before calling.

    # File lib/chef/cookbook/syntax_check.rb, line 242
    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 150
    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 164
    def validate_template(erb_file)
      Chef::Log.debug("Testing template #{erb_file} for syntax errors...")
      if validate_inline?
        validate_erb_file_inline(erb_file)
      else
        validate_erb_via_subcommand(erb_file)
      end
    end

validate_templates() click to toggle source

    # File lib/chef/cookbook/syntax_check.rb, line 157
    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 146
    def validated(file)
      validated_files.add(checksum(file))
    end

validated?(file) click to toggle source

    # File lib/chef/cookbook/syntax_check.rb, line 142
    def validated?(file)
      validated_files.include?(checksum(file))
    end