blob: 837445ddad6e0cd648adce372db1ff4a499ba536 [file] [log] [blame] [edit]
"""Utilities for working with puppet tools."""
from __future__ import absolute_import
import collections
import re
_ANSI_COLOR_ESCAPE_SEQUENCE = r'\x1b[^m]*m'
def parser_errors_to_comments(errors, directory):
"""Convert errors reported by the puppet parser in Gerrit comments.
An example of an error from the puppet parser validator looks like:
Error: Could not parse for environment prod:
Syntax error at ','; expected '}' at /path/to/puppet/file.pp:27
Args:
errors: List of stderr strings output from the pupper parser.
directory: The directory to strip off of any filenames so file comments
are rooted at the project directory that Gerrit expects.
Returns:
Dict of Gerrit comments:
{ 'filename': [{'line': '27', 'message': 'Error!'}, ...], ... }
"""
comments = collections.defaultdict(list)
for error in errors:
error = re.sub(_ANSI_COLOR_ESCAPE_SEQUENCE, '', error)
error_parts = error.split()
error_location = error_parts[-1] # /path/to/puppet/file.pp:27
abs_filename, line_number = error_location.split(':')
rel_filename_start = abs_filename.index(directory) + len(directory)
rel_filename = abs_filename[rel_filename_start:]
if rel_filename.startswith('/'):
rel_filename = rel_filename[1:]
error_parts[-1] = '{}:{}'.format(rel_filename, line_number)
comment = {'line': line_number, 'message': ' '.join(error_parts)}
comments[rel_filename].append(comment)
return comments
def linter_errors_to_comments(errors, directory):
"""Convert errors reported by puppet-lint in Gerrit comments.
Example json error:
{
"message": "indentation of => is not properly aligned",
"line": 6,
"column": 12,
"token": "#<PuppetLint::Lexer::Token:0x00000001d885a0>",
"indent_depth": 13,
"newline": false,
"newline_indent": " ",
"kind": "warning",
"check": "arrow_alignment",
"fullpath": "/abs/path/to/puppet/manifests/common.pp",
"path": "manifests/common.pp",
"filename": "common.pp",
"KIND": "WARNING"
}
Args:
errors: List of error dicts returned from puppet-lint with --json
directory: The directory to strip off of any filenames so file comments
are rooted at the project directory that Gerrit expects.
Returns:
Dict of Gerrit comments:
{ 'filename': [{'line': '27', 'message': 'Error!'}, ...], ... }
"""
if not errors:
return {}
# puppet-lint output can wrap errors in nested lists, e.g.
# [[error1, error2, ...]], so handle that gracefully.
if isinstance(errors[0], list):
errors = errors[0]
comments = collections.defaultdict(list)
for error in errors:
comment = {'line': str(error['line']), 'message': error['message']}
rel_filename_start = error['path'].index(directory) + len(directory)
rel_filename = error['path'][rel_filename_start:]
if rel_filename.startswith('/'):
rel_filename = rel_filename[1:]
comments[rel_filename].append(comment)
return comments
def get_cron_entries(puppet_lines):
"""Pull out cron entries from a puppet file.
A cron entry is converted into a dict, where the keys are the attributes
and the values are the attribute values (as strings).
In addition, the dict as a special '_line_number' attribute that records
the line number the cron entry started at.
Example
# Puppet entry # As dict
27: cron { 'my_cron': cron_entry = {
28: ensure => present, 'ensure': 'present',
29: hour => 2, ----> 'hour': '2',
30: minute => 0, 'minute': '0',
31: command => $some_script, 'command': '$some_script',
'_line_number': '27',
32: } }
Args:
puppet_lines: List of lines from a puppet_file (e.g. from readlines()).
Returns:
List of dict objects representing the cron entries.
"""
cron_entries = []
within_cron_entry = False
curly_brace_count = 0
for line_number, line in enumerate(puppet_lines, start=1):
if 'cron {' in line:
within_cron_entry = True
curly_brace_count = 1
cron_entry = {'_line_number': str(line_number)}
continue
if not within_cron_entry:
continue
curly_brace_count += line.count('{') - line.count('}')
if curly_brace_count == 0:
within_cron_entry = False
cron_entries.append(cron_entry)
cron_entry = {}
continue
if '=>' in line:
attribute, value = line.split('=>', 1)
if value.rstrip().endswith(','):
value = value.rstrip()[:-1]
cron_entry[attribute.strip()] = value.lstrip()
return cron_entries
def cron_errors_to_comments(errors):
"""Converts |errors| into Gerrit-style comments.
Args:
errors: List of (filename, cron_entry dict)
Returns:
Dict of Gerrit comments:
{ 'filename': [{'line': '27', 'message': 'Error!'}, ...], ... }
"""
comments = collections.defaultdict(list)
for puppet_file, cron_entry in errors:
comment = {'line': cron_entry['_line_number'],
'message': 'cron entry missing "minute" attribute.',}
comments[puppet_file].append(comment)
return comments