class PuppetLint

Public: The public interface to puppet-lint.

Constants

VERSION

Attributes

code[RW]

Public: Gets/Sets the String manifest code to be checked.

manifest[R]

Public: Gets the String manifest with the errors fixed.

path[RW]

Public: Gets/Sets the String path to the manifest to be checked.

problems[R]

Public: Returns an Array of Hashes describing the problems found in the manifest.

Each Hash will contain *at least*:

:check   - The Symbol name of the check that generated the problem.
:kind    - The Symbol kind of the problem (:error, :warning, or
           :fixed).
:line    - The Integer line number of the location of the problem in
           the manifest.
:column  - The Integer column number of the location of the problem in
           the manifest.
:message - The String message describing the problem that was found.
statistics[R]

Public: Returns a Hash of linter statistics

:error   - An Integer count of errors found in the manifest.
:warning - An Integer count of warnings found in the manifest.
:fixed   - An Integer count of problems found in the manifest that were
           automatically fixed.

Public Class Methods

configuration() click to toggle source

Public: Access PuppetLint's configuration from outside the class.

Returns a PuppetLint::Configuration object.

# File lib/puppet-lint.rb, line 67
def self.configuration
  @configuration ||= PuppetLint::Configuration.new
end
new() click to toggle source

Public: Initialise a new PuppetLint object.

# File lib/puppet-lint.rb, line 58
def initialize
  @code = nil
  @statistics = { :error => 0, :warning => 0, :fixed => 0, :ignored => 0 }
  @manifest = ''
end
new_check(name, &block) click to toggle source

Public: Define a new check.

name - A unique name for the check as a Symbol. block - The check logic. This must contain a `check` method and optionally

a `fix` method.

Returns nothing.

Examples

PuppetLint.new_check(:foo) do
  def check
  end
end
# File lib/puppet-lint.rb, line 231
def self.new_check(name, &block)
  class_name = name.to_s.split('_').map(&:capitalize).join
  klass = PuppetLint.const_set("Check#{class_name}", Class.new(PuppetLint::CheckPlugin))
  klass.const_set('NAME', name)
  klass.class_exec(&block)
  PuppetLint.configuration.add_check(name, klass)
  PuppetLint::Data.ignore_overrides[name] ||= {}
end

Public Instance Methods

configuration() click to toggle source

Public: Access PuppetLint's configuration from inside the class.

Returns a PuppetLint::Configuration object.

# File lib/puppet-lint.rb, line 74
def configuration
  self.class.configuration
end
errors?() click to toggle source

Public: Determine if PuppetLint found any errors in the manifest.

Returns true if errors were found, otherwise returns false.

# File lib/puppet-lint.rb, line 178
def errors?
  @statistics[:error] != 0
end
file=(path) click to toggle source

Public: Set the path of the manifest file to be tested and read the contents of the file.

Returns nothing.

# File lib/puppet-lint.rb, line 82
def file=(path)
  return unless File.exist?(path)

  @path = path
  File.open(path, 'rb:UTF-8') do |f|
    @code = f.read
  end

  # Check if the input is an SE Linux policy package file (which also use
  # the .pp extension), which all have the first 4 bytes 0xf97cff8f.
  @code = '' if @code[0..3].unpack('V').first == 0xf97cff8f
end
format_message(message) click to toggle source

Internal: Format a problem message and print it to STDOUT.

message - A Hash containing all the information about a problem.

Returns nothing.

# File lib/puppet-lint.rb, line 116
def format_message(message)
  format = log_format
  puts format % message

  puts "  #{message[:reason]}" if message[:kind] == :ignored && !message[:reason].nil?
end
get_context(message) click to toggle source

Internal: Get the line of the manifest on which the problem was found

message - A Hash containing all the information about a problem.

Returns the problematic line as a string.

# File lib/puppet-lint.rb, line 128
def get_context(message)
  PuppetLint::Data.manifest_lines[message[:line] - 1].strip
end
get_heredoc_segment(string, eos_text, interpolate = true) click to toggle source

Internal: Splits a heredoc String into segments if it is to be interpolated.

string - The String heredoc. eos_text - The String endtext for the heredoc. interpolate - A Boolean that specifies whether this heredoc can contain

interpolated values (defaults to True).

Returns an Array consisting of two Strings, the String up to the first terminator and the terminator that was found.

# File lib/puppet-lint/lexer.rb, line 552
def get_heredoc_segment(string, eos_text, interpolate = true)
  regexp = if interpolate
             %r{(([^\]|^|[^\])([\]{2})*[$]+|\|?\s*-?#{Regexp.escape(eos_text)})}
           else
             %r{\|?\s*-?#{Regexp.escape(eos_text)}}
           end

  str = string.scan_until(regexp)
  begin
    str =~ %r{\A(.*?)([$]+|\|?\s*-?#{Regexp.escape(eos_text)})\Z}m
    [Regexp.last_match(1), Regexp.last_match(2)]
  rescue
    [nil, nil]
  end
end
get_string_segment(string, terminators) click to toggle source

Internal: Split a string on multiple terminators, excluding escaped terminators.

string - The String to be split. terminators - The String of terminators that the String should be split

on.

Returns an Array consisting of two Strings, the String up to the first terminator and the terminator that was found.

# File lib/puppet-lint/lexer.rb, line 414
def get_string_segment(string, terminators)
  str = string.scan_until(%r{([^\]|^|[^\])([\]{2})*[#{terminators}]+})
  begin
    [str[0..-2], str[-1, 1]]
  rescue
    [nil, nil]
  end
end
interpolate_heredoc(string, name) click to toggle source

Internal: Tokenise the contents of a heredoc.

string - The String to be tokenised. name - The String name/endtext of the heredoc.

Returns nothing.

# File lib/puppet-lint/lexer.rb, line 493
def interpolate_heredoc(string, name)
  ss = StringScanner.new(string)
  eos_text = name[%r{\A"?(.+?)"?(:.+?)?(/.*)?\Z}, 1]
  first = true
  interpolate = name.start_with?('"')
  value, terminator = get_heredoc_segment(ss, eos_text, interpolate)
  until value.nil?
    if terminator =~ %r{\A\|?\s*-?\s*#{Regexp.escape(eos_text)}}
      if first
        tokens << new_token(:HEREDOC, value, :raw => "#{value}#{terminator}")
        first = false
      else
        tokens << new_token(:HEREDOC_POST, value, :raw => "#{value}#{terminator}")
      end
    else
      if first
        tokens << new_token(:HEREDOC_PRE, value)
        first = false
      else
        tokens << new_token(:HEREDOC_MID, value)
      end
      if ss.scan(%r{\{}).nil?
        var_name = ss.scan(%r{(::)?(\w+(-\w+)*::)*\w+(-\w+)*})
        tokens << if var_name.nil?
                    new_token(:HEREDOC_MID, '$')
                  else
                    new_token(:UNENC_VARIABLE, var_name)
                  end
      else
        contents = ss.scan_until(%r{\}})[0..-2]
        raw = contents.dup
        if contents.match(%r{\A(::)?([\w-]+::)*[\w-]|(\[.+?\])*}) && !contents.match(%r{\A\w+\(})
          contents = "$#{contents}" unless contents.start_with?('$')
        end

        lexer = PuppetLint::Lexer.new
        lexer.tokenise(contents)
        lexer.tokens.each do |token|
          tokens << new_token(token.type, token.value)
        end
        if lexer.tokens.length == 1 && lexer.tokens[0].type == :VARIABLE
          tokens.last.raw = raw
        end
      end
    end
    value, terminator = get_heredoc_segment(ss, eos_text, interpolate)
  end
end
interpolate_string(string, line, column) click to toggle source

Internal: Tokenise the contents of a double quoted string.

string - The String to be tokenised. line - The Integer line number of the start of the passed string. column - The Integer column number of the start of the passed string.

Returns nothing.

# File lib/puppet-lint/lexer.rb, line 430
def interpolate_string(string, line, column)
  ss = StringScanner.new(string)
  first = true
  value, terminator = get_string_segment(ss, '"$')
  until value.nil?
    if terminator == '"'
      if first
        tokens << new_token(:STRING, value, :line => line, :column => column)
        first = false
      else
        token_column = column + (ss.pos - value.size)
        tokens << new_token(:DQPOST, value, :line => line, :column => token_column)
        line += value.scan(LINE_END_RE).size
        @column = column + ss.pos + 1
        @line_no = line
      end
    else
      if first
        tokens << new_token(:DQPRE, value, :line => line, :column => column)
        first = false
      else
        token_column = column + (ss.pos - value.size)
        tokens << new_token(:DQMID, value, :line => line, :column => token_column)
        line += value.scan(LINE_END_RE).size
      end
      if ss.scan(%r{\{}).nil?
        var_name = ss.scan(%r{(::)?(\w+(-\w+)*::)*\w+(-\w+)*})
        if var_name.nil?
          token_column = column + ss.pos - 1
          tokens << new_token(:DQMID, '$', :line => line, :column => token_column)
        else
          token_column = column + (ss.pos - var_name.size)
          tokens << new_token(:UNENC_VARIABLE, var_name, :line => line, :column => token_column)
        end
      else
        line += value.scan(LINE_END_RE).size
        contents = ss.scan_until(%r{\}})[0..-2]
        raw = contents.dup
        if contents.match(%r{\A(::)?([\w-]+::)*[\w-]+(\[.+?\])*}) && !contents.match(%r{\A\w+\(})
          contents = "$#{contents}"
        end
        lexer = PuppetLint::Lexer.new
        lexer.tokenise(contents)
        lexer.tokens.each do |token|
          tok_col = column + token.column + (ss.pos - contents.size - 1)
          tok_line = token.line + line - 1
          tokens << new_token(token.type, token.value, :line => tok_line, :column => tok_col)
        end
        if lexer.tokens.length == 1 && lexer.tokens[0].type == :VARIABLE
          tokens.last.raw = raw
        end
      end
    end
    value, terminator = get_string_segment(ss, '"$')
  end
end
log_format() click to toggle source

Internal: Retrieve the format string to be used when writing problems to STDOUT. If the user has not specified a custom log format, build one for them.

Returns a format String to be used with String#%.

# File lib/puppet-lint.rb, line 100
def log_format
  if configuration.log_format.nil? || configuration.log_format.empty?
    ## recreate previous old log format as far as thats possible.
    format = '%{KIND}: %{message} on line %{line}'
    format.prepend('%{path} - ') if configuration.with_filename
    configuration.log_format = format
  end

  configuration.log_format
end
new_token(type, value, *args) click to toggle source

Internal: Create a new PuppetLint::Lexer::Token object, calculate its line number and column and then add it to the Linked List of tokens.

type - The Symbol token type. value - The token value. opts - A Hash of additional values required to determine line number and

      column:
:line   - The Integer line number if calculated externally.
:column - The Integer column number if calculated externally.
:raw    - The String raw value of the token (if necessary).

Returns the instantiated PuppetLint::Lexer::Token object.

# File lib/puppet-lint/lexer.rb, line 352
def new_token(type, value, *args)
  # This bit of magic is used instead of an "opts = {}" argument so that we
  # can safely deprecate the old "length" parameter that might still be
  # passed by 3rd party plugins that haven't updated yet.
  opts = args.last.is_a?(Hash) ? args.last : {}

  # column number is calculated at the end of this method by calling
  # to_manifest on the token. Because the string tokens (DQPRE, DQMID etc)
  # are parsed before the variable token, they default to assuming that
  # they are followed by an enclosed variable and we need to remove 2 from
  # the column number if we encounter an unenclosed variable because of the
  # missing ${ at the end of the token value.
  @column -= 2 if type == :UNENC_VARIABLE

  column = opts[:column] || @column
  line_no = opts[:line] || @line_no

  token = Token.new(type, value, line_no, column)
  unless tokens.last.nil?
    token.prev_token = tokens.last
    tokens.last.next_token = token

    unless FORMATTING_TOKENS.include?(token.type)
      prev_nf_idx = tokens.rindex { |r| !FORMATTING_TOKENS.include?(r.type) }
      unless prev_nf_idx.nil?
        prev_nf_token = tokens[prev_nf_idx]
        prev_nf_token.next_code_token = token
        token.prev_code_token = prev_nf_token
      end
    end
  end

  token.raw = opts[:raw] if opts[:raw]

  if type == :NEWLINE
    @line_no += 1
    @column = 1
  else
    lines = token.to_manifest.split(LINE_END_RE, -1)
    @line_no += lines.length - 1
    if lines.length > 1
      # if the token renders to multiple lines, set the column state to the
      # length of the last line plus 1 (because column numbers are
      # 1 indexed)
      @column = lines.last.size + 1
    else
      @column += (lines.last || '').size
    end
  end

  token
end
possible_regex?() click to toggle source

Internal: Given the tokens already processed, determine if the next token could be a regular expression.

Returns true if the next token could be a regex, otherwise return false.

# File lib/puppet-lint/lexer.rb, line 330
def possible_regex?
  prev_token = tokens.reject { |r|
    FORMATTING_TOKENS.include?(r.type)
  }.last

  return true if prev_token.nil?

  REGEX_PREV_TOKENS.include?(prev_token.type)
end
print_context(message) click to toggle source

Internal: Print out the line of the manifest on which the problem was found as well as a marker pointing to the location on the line.

message - A Hash containing all the information about a problem.

Returns nothing.

print_problems() click to toggle source

Public: Print any problems that were found out to stdout.

Returns nothing.

report(problems) click to toggle source

Internal: Print the reported problems with a manifest to stdout.

problems - An Array of problem Hashes as returned by

PuppetLint::Checks#run.

Returns nothing.

# File lib/puppet-lint.rb, line 153
def report(problems)
  json = []
  problems.each do |message|
    next if message[:kind] == :ignored && !PuppetLint.configuration.show_ignored

    message[:KIND] = message[:kind].to_s.upcase

    if message[:kind] == :fixed || [message[:kind], :all].include?(configuration.error_level)
      if configuration.json
        message['context'] = get_context(message) if configuration.with_context
        json << message
      else
        format_message(message)
        print_context(message) if configuration.with_context
      end
    end
  end
  puts JSON.pretty_generate(json) if configuration.json

  $stderr.puts 'Try running `puppet parser validate <file>`' if problems.any? { |p| p[:check] == :syntax }
end
run() click to toggle source

Public: Run the loaded manifest code through the lint checks and print the results of the checks to stdout.

Returns nothing. Raises PuppetLint::NoCodeError if no manifest code has been loaded.

# File lib/puppet-lint.rb, line 194
def run
  raise PuppetLint::NoCodeError if @code.nil?

  if @code.empty?
    @problems = []
    @manifest = ''
    return
  end

  linter = PuppetLint::Checks.new
  @problems = linter.run(@path, @code)
  @problems.each { |problem| @statistics[problem[:kind]] += 1 }

  @manifest = linter.manifest if PuppetLint.configuration.fix
end
warnings?() click to toggle source

Public: Determine if PuppetLint found any warnings in the manifest.

Returns true if warnings were found, otherwise returns false.

# File lib/puppet-lint.rb, line 185
def warnings?
  @statistics[:warning] != 0
end