class TerraformLandscape::TerraformPlan
Represents the parsed output of `terraform plan`.
This allows us to easily inspect the plan and present a more readable explanation of the plan to the user.
Constants
- CHANGE_SYMBOL_TO_COLOR
- DEFAULT_DIFF_CONTEXT_LINES
- GRAMMAR_FILE
- UNICODE_ESCAPER
Public Class Methods
from_output(string)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 34 def from_output(string) # Our grammar assumes output with Unix line endings string = string.gsub("\r\n", "\n") return new([]) if string.strip.empty? tree = parser.parse(string) raise ParseError, parser.failure_reason unless tree new(tree.to_ast) end
new(plan_ast, options = {})
click to toggle source
Create a plan from an abstract syntax tree (AST).
# File lib/terraform_landscape/terraform_plan.rb, line 56 def initialize(plan_ast, options = {}) @ast = plan_ast @diff_context_lines = options.fetch(:diff_context_lines, DEFAULT_DIFF_CONTEXT_LINES) end
Private Class Methods
parser()
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 46 def parser @parser ||= begin Treetop.load(GRAMMAR_FILE) TerraformPlanParser.new end end
Public Instance Methods
display(output)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 61 def display(output) @out = output @ast.each do |resource| display_resource(resource) @out.newline end end
Private Instance Methods
attribute_indent_amount_for_resource(resource)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 102 def attribute_indent_amount_for_resource(resource) longest_name_length = resource[:attributes].keys.reduce(0) do |longest, name| name.length > longest ? name.length : longest end longest_name_length + 8 end
display_added_or_removed_attribute( change_color, attribute_name, attribute_value, attribute_value_indent, attribute_value_indent_amount )
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 219 def display_added_or_removed_attribute( change_color, attribute_name, attribute_value, attribute_value_indent, attribute_value_indent_amount ) @out.print " #{attribute_name}:".ljust(attribute_value_indent_amount, ' ') .colorize(change_color) evaluated_string = undump(attribute_value) if json?(evaluated_string) @out.print to_pretty_json(evaluated_string).gsub("\n", "\n#{attribute_value_indent}") .colorize(change_color) else @out.print "\"#{evaluated_string.colorize(change_color)}\"" end @out.newline end
display_attribute( resource, change_color, attribute_name, attribute_value, attribute_change_reason, attribute_value_indent_amount )
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 152 def display_attribute( # rubocop:disable Metrics/ParameterLists resource, change_color, attribute_name, attribute_value, attribute_change_reason, attribute_value_indent_amount ) attribute_value_indent = ' ' * attribute_value_indent_amount if %i[~ -/+].include?(resource[:change]) display_modified_attribute(change_color, attribute_name, attribute_value, attribute_change_reason, attribute_value_indent, attribute_value_indent_amount) else display_added_or_removed_attribute(change_color, attribute_name, attribute_value, attribute_value_indent, attribute_value_indent_amount) end end
display_diff(old, new, indent)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 145 def display_diff(old, new, indent) @out.print Diffy::Diff.new(old, new, { context: @diff_context_lines }) .to_s(String.disable_colorization ? :text : :color) .gsub("\n", "\n" + indent) .strip end
display_modified_attribute( change_color, attribute_name, attribute_value, attribute_change_reason, attribute_value_indent, attribute_value_indent_amount )
click to toggle source
rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
# File lib/terraform_landscape/terraform_plan.rb, line 179 def display_modified_attribute( change_color, attribute_name, attribute_value, attribute_change_reason, attribute_value_indent, attribute_value_indent_amount ) # Since the attribute line is always of the form "old value" => "new value" attribute_value =~ /^ *(".*") *=> *(".*") *$/ old = undump(Regexp.last_match[1]) new = undump(Regexp.last_match[2]) return if old == new && new != '<sensitive>' # Don't show unchanged attributes @out.print " #{attribute_name}:".ljust(attribute_value_indent_amount, ' ') .colorize(change_color) if json?(new) # Value looks like JSON, so prettify it to make it more readable fancy_old = "#{to_pretty_json(old)}\n" fancy_new = "#{to_pretty_json(new)}\n" display_diff(fancy_old, fancy_new, attribute_value_indent) elsif old.include?("\n") || new.include?("\n") # Multiline content, so display nicer diff display_diff("#{old}\n", "#{new}\n", attribute_value_indent) else # Typical values, so just show before/after @out.print '"' + old.colorize(:red) + '"' @out.print ' => '.colorize(:light_black) @out.print '"' + new.colorize(:green) + '"' end if attribute_change_reason @out.print " (#{attribute_change_reason})".colorize(:magenta) end @out.newline end
display_resource(resource)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 71 def display_resource(resource) # rubocop:disable Metrics/MethodLength change_color = CHANGE_SYMBOL_TO_COLOR[resource[:change]] resource_header = "#{resource[:change]} #{resource[:resource_type]}." \ "#{resource[:resource_name]}".colorize(change_color) if resource[:reason] resource_header += " (#{resource[:reason]})".colorize(:magenta) end if resource[:additional_reason] resource_header += " (#{resource[:additional_reason]})".colorize(:magenta) end @out.puts resource_header # Determine longest attribute name so we align all values at same indentation attribute_value_indent_amount = attribute_indent_amount_for_resource(resource) resource[:attributes].each do |attribute_name, attribute_value_and_reason| attribute_value = attribute_value_and_reason[:value] attribute_change_reason = attribute_value_and_reason[:reason] display_attribute(resource, change_color, attribute_name, attribute_value, attribute_change_reason, attribute_value_indent_amount) end end
json?(value)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 113 def json?(value) ['{', '['].include?(value.to_s[0]) && (JSON.parse(value) rescue nil) # rubocop:disable Style/RescueModifier end
recursive_sort(obj)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 132 def recursive_sort(obj) case obj when Array obj.map { |item| recursive_sort(item) } when Hash obj.keys.sort.each_with_object({}) do |key, hash| hash[key] = recursive_sort(obj[key]) end else obj end end
to_pretty_json(value)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 118 def to_pretty_json(value) # Can't JSON.parse an empty string, so handle it separately return '' if value.strip.empty? sorted = recursive_sort(JSON.parse(value)) JSON.pretty_generate(sorted, { indent: ' ', space: ' ', object_nl: "\n", array_nl: "\n" }) end
undump(value)
click to toggle source
# File lib/terraform_landscape/terraform_plan.rb, line 109 def undump(value) value.encode('ASCII', fallback: UNICODE_ESCAPER).undump end