1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
|
#--
# Author:: Daniel DeLeo (<dan@opscode.com>)
# Copyright:: Copyright (c) 2012 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
class Chef
module Formatters
module ErrorInspectors
# == CompileErrorInspector
# Wraps exceptions that occur during the compile phase of a Chef run and
# tries to find the code responsible for the error.
class CompileErrorInspector
attr_reader :path
attr_reader :exception
def initialize(path, exception)
@path, @exception = path, exception
@backtrace_lines_in_cookbooks = nil
@file_lines = nil
@culprit_backtrace_entry = nil
@culprit_line = nil
end
def add_explanation(error_description)
error_description.section(exception.class.name, exception.message)
if found_error_in_cookbooks?
traceback = filtered_bt.map {|line| " #{line}"}.join("\n")
error_description.section("Cookbook Trace:", traceback)
error_description.section("Relevant File Content:", context)
end
if exception_message_modifying_frozen?
msg = <<-MESSAGE
Chef calls the freeze method on certain ruby objects to prevent
pollution across multiple instances. Specifically, resource
properties have frozen default values to avoid modifying the
property for all instances of a resource. Try modifying the
particular instance variable or using an instance accessor instead.
MESSAGE
error_description.section("Additional information:", msg.gsub(/^ {6}/, ''))
end
end
def context
context_lines = []
context_lines << "#{culprit_file}:\n\n"
Range.new(display_lower_bound, display_upper_bound).each do |i|
line_nr = (i + 1).to_s.rjust(3)
indicator = (i + 1) == culprit_line ? ">> " : ": "
context_lines << "#{line_nr}#{indicator}#{file_lines[i]}"
end
context_lines.join("")
end
def display_lower_bound
lower = (culprit_line - 8)
lower = 0 if lower < 0
lower
end
def display_upper_bound
upper = (culprit_line + 8)
upper = file_lines.size if upper > file_lines.size
upper
end
def file_lines
@file_lines ||= IO.readlines(culprit_file)
end
def culprit_backtrace_entry
@culprit_backtrace_entry ||= begin
bt_entry = filtered_bt.first
Chef::Log.debug("backtrace entry for compile error: '#{bt_entry}'")
bt_entry
end
end
def culprit_line
@culprit_line ||= begin
line_number = culprit_backtrace_entry[/^(?:.\:)?[^:]+:([\d]+)/,1].to_i
Chef::Log.debug("Line number of compile error: '#{line_number}'")
line_number
end
end
def culprit_file
@culprit_file ||= culprit_backtrace_entry[/^((?:.\:)?[^:]+):([\d]+)/,1]
end
def filtered_bt
backtrace_lines_in_cookbooks.count > 0 ? backtrace_lines_in_cookbooks : exception.backtrace
end
def found_error_in_cookbooks?
!backtrace_lines_in_cookbooks.empty?
end
def backtrace_lines_in_cookbooks
@backtrace_lines_in_cookbooks ||=
begin
filters = Array(Chef::Config.cookbook_path).map {|p| /^#{Regexp.escape(p)}/i }
r = exception.backtrace.select {|line| filters.any? {|filter| line =~ filter }}
Chef::Log.debug("filtered backtrace of compile error: #{r.join(",")}")
r
end
end
def exception_message_modifying_frozen?
exception.message.include?("can't modify frozen")
end
end
end
end
end
|