terraform_landscape 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/landscape +7 -0
- data/grammar/terraform_plan.treetop +67 -0
- data/lib/terraform_landscape.rb +7 -0
- data/lib/terraform_landscape/arguments_parser.rb +58 -0
- data/lib/terraform_landscape/cli.rb +51 -0
- data/lib/terraform_landscape/constants.rb +9 -0
- data/lib/terraform_landscape/errors.rb +33 -0
- data/lib/terraform_landscape/output.rb +118 -0
- data/lib/terraform_landscape/printer.rb +56 -0
- data/lib/terraform_landscape/terraform_plan.rb +188 -0
- data/lib/terraform_landscape/version.rb +6 -0
- metadata +113 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3adeba30b54a83c182ee6956d97f42e16fe1bb14
|
4
|
+
data.tar.gz: ab302a4d55105e6a0a3bc70e31c497e64dc3b489
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 065d407040083b704ddef29a67080db8b618f557ed498c9623ff26ced98ddbee28538fad417253370155d904974e06abea1a5d1741acaee63e1d6b15f7ceed44
|
7
|
+
data.tar.gz: 7602f283d8a02775dc4e07c5b93fd6c5763cc4e1d5f939fae3d1c042f47d0e3c387f48a2d3e472c9cb1d7610595f46829953f60bb7a356a9fbbfaaa28f2d4e72
|
data/bin/landscape
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Defines grammar for parsing `terraform plan` output.
|
2
|
+
|
3
|
+
grammar TerraformPlan
|
4
|
+
rule plan
|
5
|
+
[\s]* list:resource_list "\n"* {
|
6
|
+
def to_ast
|
7
|
+
list.to_ast
|
8
|
+
end
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
rule resource_list
|
13
|
+
item:resource "\n\n" list:resource_list {
|
14
|
+
def to_ast
|
15
|
+
[item.to_ast] + list.to_ast
|
16
|
+
end
|
17
|
+
}
|
18
|
+
/
|
19
|
+
item:resource "\n" {
|
20
|
+
def to_ast
|
21
|
+
[item.to_ast]
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
rule resource
|
27
|
+
header:resource_header "\n" attrs:attribute_list {
|
28
|
+
def to_ast
|
29
|
+
header.to_ast.merge(attributes: attrs.to_ast)
|
30
|
+
end
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
rule resource_header
|
35
|
+
change:('~' / '-' / '+') _ type:[a-zA-Z0-9_-]+ '.' name:[a-zA-Z0-9_-]+ {
|
36
|
+
def to_ast
|
37
|
+
{ change: change.text_value.to_sym, resource_type: type.text_value, resource_name: name.text_value }
|
38
|
+
end
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
rule attribute_list
|
43
|
+
_ item:attribute "\n" attrs:attribute_list {
|
44
|
+
def to_ast
|
45
|
+
item.to_ast.merge(attrs.to_ast)
|
46
|
+
end
|
47
|
+
}
|
48
|
+
/
|
49
|
+
_ item:attribute {
|
50
|
+
def to_ast
|
51
|
+
item.to_ast
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
rule attribute
|
57
|
+
attribute_name:[^\s:]* ':' _? attribute_value:[^\n]+ {
|
58
|
+
def to_ast
|
59
|
+
{ attribute_name.text_value => attribute_value.text_value }
|
60
|
+
end
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
rule _
|
65
|
+
[ ]+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
require 'terraform_landscape/constants'
|
2
|
+
require 'terraform_landscape/version'
|
3
|
+
require 'terraform_landscape/output'
|
4
|
+
require 'terraform_landscape/errors'
|
5
|
+
require 'terraform_landscape/printer'
|
6
|
+
require 'terraform_landscape/terraform_plan'
|
7
|
+
require 'terraform_landscape/cli'
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module TerraformLandscape
|
4
|
+
# Handles option parsing for the command line application.
|
5
|
+
class ArgumentsParser
|
6
|
+
# Parses command line options into an options hash.
|
7
|
+
#
|
8
|
+
# @param args [Array<String>] arguments passed via the command line
|
9
|
+
#
|
10
|
+
# @return [Hash] parsed options
|
11
|
+
def parse(args)
|
12
|
+
@options = {}
|
13
|
+
@options[:command] = :pretty_print # Default command
|
14
|
+
|
15
|
+
OptionParser.new do |parser|
|
16
|
+
parser.banner = "Usage: landscape [options] [plan-output-file]"
|
17
|
+
|
18
|
+
add_info_options parser
|
19
|
+
end.parse!(args)
|
20
|
+
|
21
|
+
# Any remaining arguments are assumed to be the output file
|
22
|
+
@options[:plan_output_file] = args.first
|
23
|
+
|
24
|
+
@options
|
25
|
+
rescue OptionParser::InvalidOption => ex
|
26
|
+
raise InvalidCliOptionError,
|
27
|
+
"#{ex.message}\nRun `landscape --help` to " \
|
28
|
+
'see a list of available options.'
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# Register informational flags.
|
34
|
+
def add_info_options(parser)
|
35
|
+
parser.on('--[no-]color', 'Force output to be colorized') do |color|
|
36
|
+
@options[:color] = color
|
37
|
+
end
|
38
|
+
|
39
|
+
parser.on('-d', '--debug', 'Enable debug mode for more verbose output') do
|
40
|
+
@options[:debug] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
parser.on_tail('-h', '--help', 'Display help documentation') do
|
44
|
+
@options[:command] = :display_help
|
45
|
+
@options[:help_message] = parser.help
|
46
|
+
end
|
47
|
+
|
48
|
+
parser.on_tail('-v', '--version', 'Display version') do
|
49
|
+
@options[:command] = :display_version
|
50
|
+
end
|
51
|
+
|
52
|
+
parser.on_tail('-V', '--verbose-version', 'Display verbose version information') do
|
53
|
+
@options[:command] = :display_version
|
54
|
+
@options[:verbose_version] = true
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'commander'
|
2
|
+
|
3
|
+
module TerraformLandscape
|
4
|
+
# Command line application interface.
|
5
|
+
class CLI
|
6
|
+
include Commander::Methods
|
7
|
+
|
8
|
+
def initialize(output)
|
9
|
+
@output = output
|
10
|
+
end
|
11
|
+
|
12
|
+
# Parses the given command line arguments and executes appropriate logic
|
13
|
+
# based on those arguments.
|
14
|
+
#
|
15
|
+
# @param args [Array<String>] command line arguments
|
16
|
+
#
|
17
|
+
# @return [Integer] exit status code
|
18
|
+
def run(args)
|
19
|
+
program :name, 'Terraform Landscape'
|
20
|
+
program :version, VERSION
|
21
|
+
program :description, 'Pretty-print your Terraform plan output'
|
22
|
+
|
23
|
+
define_commands
|
24
|
+
|
25
|
+
run!
|
26
|
+
0 # OK
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def define_commands
|
32
|
+
command :print do |c|
|
33
|
+
c.action do
|
34
|
+
print
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
global_option '--no-color', 'Do not output any color' do
|
39
|
+
String.disable_colorization = true
|
40
|
+
@output.color_enabled = false
|
41
|
+
end
|
42
|
+
|
43
|
+
default_command :print
|
44
|
+
end
|
45
|
+
|
46
|
+
def print
|
47
|
+
printer = Printer.new(@output)
|
48
|
+
printer.process_stream(STDIN)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Global application constants.
|
4
|
+
module TerraformLandscape
|
5
|
+
HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
|
6
|
+
|
7
|
+
REPO_URL = 'https://github.com/coinbase/terraform_landscape'.freeze
|
8
|
+
BUG_REPORT_URL = "#{REPO_URL}/issues".freeze
|
9
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Collection of errors that can be raised by the framework.
|
2
|
+
module TerraformLandscape
|
3
|
+
# Abstract error. Separates LintTrappings errors from other kinds of
|
4
|
+
# errors in the exception hierarchy.
|
5
|
+
#
|
6
|
+
# @abstract
|
7
|
+
class Error < StandardError
|
8
|
+
# Returns the status code that should be output if this error goes
|
9
|
+
# unhandled.
|
10
|
+
#
|
11
|
+
# Ideally these should resemble exit codes from the sysexits documentation
|
12
|
+
# where it makes sense.
|
13
|
+
def self.exit_status(*args)
|
14
|
+
if args.any?
|
15
|
+
@exit_status = args.first
|
16
|
+
elsif @exit_status
|
17
|
+
@exit_status
|
18
|
+
else
|
19
|
+
ancestors.each do |ancestor|
|
20
|
+
return 70 if ancestor == TerraformLandscape::Error # No exit status defined
|
21
|
+
return ancestor.exit_status if ancestor.exit_status
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exit_status
|
27
|
+
self.class.exit_status
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Raised when there was a problem parsing a document.
|
32
|
+
class ParseError < Error; end
|
33
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module TerraformLandscape
|
2
|
+
# Encapsulates all communication to an output source.
|
3
|
+
class Output
|
4
|
+
# Whether colored output via ANSI escape sequences is enabled.
|
5
|
+
# @return [true,false]
|
6
|
+
attr_accessor :color_enabled
|
7
|
+
|
8
|
+
# Creates a logger which outputs nothing.
|
9
|
+
# @return [TerraformLandscape::Output]
|
10
|
+
def self.silent
|
11
|
+
new(File.open(File::NULL, 'w'))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Creates a new {SlimLint::Logger} instance.
|
15
|
+
#
|
16
|
+
# @param out [IO] the output destination.
|
17
|
+
def initialize(out)
|
18
|
+
@out = out
|
19
|
+
@color_enabled = tty?
|
20
|
+
end
|
21
|
+
|
22
|
+
# Print the specified output.
|
23
|
+
#
|
24
|
+
# @param output [String] the output to send
|
25
|
+
# @param newline [true,false] whether to append a newline
|
26
|
+
def puts(output, newline = true)
|
27
|
+
@out.print(output)
|
28
|
+
@out.print("\n") if newline
|
29
|
+
end
|
30
|
+
|
31
|
+
# Print the specified output without a newline.
|
32
|
+
#
|
33
|
+
# @param output [String] the output to send
|
34
|
+
def print(output)
|
35
|
+
puts(output, false)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Print the specified output in bold face.
|
39
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
40
|
+
#
|
41
|
+
# @param args [Array<String>]
|
42
|
+
def bold(*args)
|
43
|
+
color('1', *args)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Print the specified output in a color indicative of error.
|
47
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
48
|
+
#
|
49
|
+
# @param args [Array<String>]
|
50
|
+
def error(*args)
|
51
|
+
color(31, *args)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Print the specified output in a bold face and color indicative of error.
|
55
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
56
|
+
#
|
57
|
+
# @param args [Array<String>]
|
58
|
+
def bold_error(*args)
|
59
|
+
color('1;31', *args)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Print the specified output in a color indicative of success.
|
63
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
64
|
+
#
|
65
|
+
# @param args [Array<String>]
|
66
|
+
def success(*args)
|
67
|
+
color(32, *args)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Print the specified output in a color indicative of a warning.
|
71
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
72
|
+
#
|
73
|
+
# @param args [Array<String>]
|
74
|
+
def warning(*args)
|
75
|
+
color(33, *args)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Print the specified output in a color indicating something worthy of
|
79
|
+
# notice.
|
80
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
81
|
+
#
|
82
|
+
# @param args [Array<String>]
|
83
|
+
def notice(*args)
|
84
|
+
color(35, *args)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Print the specified output in a color indicating information.
|
88
|
+
# If output destination is not a TTY, behaves the same as {#log}.
|
89
|
+
#
|
90
|
+
# @param args [Array<String>]
|
91
|
+
def info(*args)
|
92
|
+
color(36, *args)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Print a blank line.
|
96
|
+
def newline
|
97
|
+
puts('')
|
98
|
+
end
|
99
|
+
|
100
|
+
# Whether this logger is outputting to a TTY.
|
101
|
+
#
|
102
|
+
# @return [true,false]
|
103
|
+
def tty?
|
104
|
+
@out.respond_to?(:tty?) && @out.tty?
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
# Print output in the specified color.
|
110
|
+
#
|
111
|
+
# @param code [Integer,String] ANSI color code
|
112
|
+
# @param output [String] output to print
|
113
|
+
# @param newline [Boolean] whether to append a newline
|
114
|
+
def color(code, output, newline = true)
|
115
|
+
puts(color_enabled ? "\033[#{code}m#{output}\033[0m" : output, newline)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
module TerraformLandscape
|
4
|
+
class Printer
|
5
|
+
def initialize(output)
|
6
|
+
@output = output
|
7
|
+
end
|
8
|
+
|
9
|
+
def process_stream(io)
|
10
|
+
buffer = StringIO.new
|
11
|
+
begin
|
12
|
+
block_size = 1024
|
13
|
+
|
14
|
+
done = false
|
15
|
+
until done
|
16
|
+
readable_fds, = IO.select([io])
|
17
|
+
next unless readable_fds
|
18
|
+
|
19
|
+
readable_fds.each do |f|
|
20
|
+
begin
|
21
|
+
buffer << f.read_nonblock(block_size)
|
22
|
+
rescue IO::WaitReadable # rubocop:disable Lint/HandleExceptions
|
23
|
+
# Ignore; we'll call IO.select again
|
24
|
+
rescue EOFError
|
25
|
+
done = true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
ensure
|
30
|
+
io.close
|
31
|
+
end
|
32
|
+
|
33
|
+
plan_output = buffer.string
|
34
|
+
scrubbed_output = plan_output.gsub(/\e\[\d+m/, '')
|
35
|
+
|
36
|
+
# Remove preface
|
37
|
+
if (match = scrubbed_output.match(/^Path:[^\n]+/))
|
38
|
+
scrubbed_output = scrubbed_output[match.end(0)..-1]
|
39
|
+
else
|
40
|
+
raise ParseError, 'Output does not contain proper preface'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Remove postface
|
44
|
+
if (match = scrubbed_output.match(/^Plan:[^\n]+/))
|
45
|
+
plan_summary = scrubbed_output[match.begin(0)..match.end(0)]
|
46
|
+
scrubbed_output = scrubbed_output[0...match.begin(0)]
|
47
|
+
else
|
48
|
+
raise ParseError, 'Output does not container proper postface'
|
49
|
+
end
|
50
|
+
|
51
|
+
plan = TerraformPlan.from_output(scrubbed_output)
|
52
|
+
plan.display(@output)
|
53
|
+
@output.puts plan_summary
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'diffy'
|
3
|
+
require 'json'
|
4
|
+
require 'treetop'
|
5
|
+
|
6
|
+
########################################################################
|
7
|
+
# Represents the parsed output of `terraform plan`.
|
8
|
+
#
|
9
|
+
# This allows us to easily inspect the plan and present a more readable
|
10
|
+
# explanation of the plan to the user.
|
11
|
+
########################################################################
|
12
|
+
class TerraformLandscape::TerraformPlan
|
13
|
+
GRAMMAR_FILE = File.expand_path(File.join(File.dirname(__FILE__),
|
14
|
+
'..', '..', 'grammar',
|
15
|
+
'terraform_plan.treetop'))
|
16
|
+
|
17
|
+
CHANGE_SYMBOL_TO_COLOR = {
|
18
|
+
:~ => :yellow,
|
19
|
+
:- => :red,
|
20
|
+
:+ => :green
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
DEFAULT_DIFF_CONTEXT_LINES = 5
|
24
|
+
|
25
|
+
class ParseError < StandardError; end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def from_output(string)
|
29
|
+
return new([]) if string.strip.empty?
|
30
|
+
tree = parser.parse(string)
|
31
|
+
raise ParseError, parser.failure_reason unless tree
|
32
|
+
new(tree.to_ast)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def parser
|
38
|
+
@parser ||=
|
39
|
+
begin
|
40
|
+
Treetop.load(GRAMMAR_FILE)
|
41
|
+
TerraformPlanParser.new
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Create a plan from an abstract syntax tree (AST).
|
47
|
+
def initialize(plan_ast, options = {})
|
48
|
+
@ast = plan_ast
|
49
|
+
@diff_context_lines = options.fetch(:diff_context_lines, DEFAULT_DIFF_CONTEXT_LINES)
|
50
|
+
end
|
51
|
+
|
52
|
+
def display(output)
|
53
|
+
@out = output
|
54
|
+
@ast.each do |resource|
|
55
|
+
display_resource(resource)
|
56
|
+
@out.newline
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def display_resource(resource)
|
63
|
+
change_color = CHANGE_SYMBOL_TO_COLOR[resource[:change]]
|
64
|
+
|
65
|
+
@out.puts "#{resource[:change]} #{resource[:resource_type]}." \
|
66
|
+
"#{resource[:resource_name]}".colorize(change_color)
|
67
|
+
|
68
|
+
# Determine longest attribute name so we align all values at same indentation
|
69
|
+
attribute_value_indent_amount = attribute_indent_amount_for_resource(resource)
|
70
|
+
|
71
|
+
resource[:attributes].each do |attribute_name, attribute_value|
|
72
|
+
display_attribute(resource,
|
73
|
+
change_color,
|
74
|
+
attribute_name,
|
75
|
+
attribute_value,
|
76
|
+
attribute_value_indent_amount)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def attribute_indent_amount_for_resource(resource)
|
81
|
+
longest_name_length = resource[:attributes].keys.reduce(0) do |longest, name|
|
82
|
+
name.length > longest ? name.length : longest
|
83
|
+
end
|
84
|
+
longest_name_length + 8
|
85
|
+
end
|
86
|
+
|
87
|
+
def json?(value)
|
88
|
+
['{', '['].include?(value.to_s[0]) &&
|
89
|
+
(JSON.parse(value) rescue nil) # rubocop:disable Style/RescueModifier
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_pretty_json(value)
|
93
|
+
JSON.pretty_generate(JSON.parse(value),
|
94
|
+
{
|
95
|
+
indent: ' ',
|
96
|
+
space: ' ',
|
97
|
+
object_nl: "\n",
|
98
|
+
array_nl: "\n"
|
99
|
+
})
|
100
|
+
end
|
101
|
+
|
102
|
+
def display_diff(old, new, indent)
|
103
|
+
@out.print Diffy::Diff.new(old, new, { context: @diff_context_lines })
|
104
|
+
.to_s(String.disable_colorization ? :text : :color)
|
105
|
+
.gsub("\n", "\n" + indent)
|
106
|
+
.strip
|
107
|
+
end
|
108
|
+
|
109
|
+
def display_attribute(
|
110
|
+
resource,
|
111
|
+
change_color,
|
112
|
+
attribute_name,
|
113
|
+
attribute_value,
|
114
|
+
attribute_value_indent_amount
|
115
|
+
)
|
116
|
+
attribute_value_indent = ' ' * attribute_value_indent_amount
|
117
|
+
|
118
|
+
if resource[:change] == :~
|
119
|
+
display_modified_attribute(change_color,
|
120
|
+
attribute_name,
|
121
|
+
attribute_value,
|
122
|
+
attribute_value_indent,
|
123
|
+
attribute_value_indent_amount)
|
124
|
+
else
|
125
|
+
display_added_or_removed_attribute(change_color,
|
126
|
+
attribute_name,
|
127
|
+
attribute_value,
|
128
|
+
attribute_value_indent,
|
129
|
+
attribute_value_indent_amount)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def display_modified_attribute( # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
134
|
+
change_color,
|
135
|
+
attribute_name,
|
136
|
+
attribute_value,
|
137
|
+
attribute_value_indent,
|
138
|
+
attribute_value_indent_amount
|
139
|
+
)
|
140
|
+
# Since the attribute line is always of the form
|
141
|
+
# "old value" => "new value", we can add curly braces and parse with
|
142
|
+
# `eval` to obtain a hash with a single key/value.
|
143
|
+
old, new = eval("{#{attribute_value}}").to_a.first # rubocop:disable Lint/Eval
|
144
|
+
|
145
|
+
return if old == new # Don't show unchanged attributes
|
146
|
+
|
147
|
+
@out.print " #{attribute_name}:".ljust(attribute_value_indent_amount, ' ')
|
148
|
+
.colorize(change_color)
|
149
|
+
|
150
|
+
if json?(new)
|
151
|
+
# Value looks like JSON, so prettify it to make it more readable
|
152
|
+
fancy_old = "#{to_pretty_json(old)}\n"
|
153
|
+
fancy_new = "#{to_pretty_json(new)}\n"
|
154
|
+
display_diff(fancy_old, fancy_new, attribute_value_indent)
|
155
|
+
elsif old.include?("\n") || new.include?("\n")
|
156
|
+
# Multiline content, so display nicer diff
|
157
|
+
display_diff("#{old}\n", "#{new}\n", attribute_value_indent)
|
158
|
+
else
|
159
|
+
# Typical values, so just show before/after
|
160
|
+
@out.print '"' + old.colorize(:red) + '"'
|
161
|
+
@out.print ' => '.colorize(:light_black)
|
162
|
+
@out.print '"' + new.colorize(:green) + '"'
|
163
|
+
end
|
164
|
+
|
165
|
+
@out.newline
|
166
|
+
end
|
167
|
+
|
168
|
+
def display_added_or_removed_attribute(
|
169
|
+
change_color,
|
170
|
+
attribute_name,
|
171
|
+
attribute_value,
|
172
|
+
attribute_value_indent,
|
173
|
+
attribute_value_indent_amount
|
174
|
+
)
|
175
|
+
@out.print " #{attribute_name}:".ljust(attribute_value_indent_amount, ' ')
|
176
|
+
.colorize(change_color)
|
177
|
+
|
178
|
+
evaluated_string = eval(attribute_value) # rubocop:disable Lint/Eval
|
179
|
+
if json?(evaluated_string)
|
180
|
+
@out.print to_pretty_json(evaluated_string).gsub("\n", "\n" + attribute_value_indent)
|
181
|
+
.colorize(change_color)
|
182
|
+
else
|
183
|
+
@out.print "\"#{evaluated_string.colorize(change_color)}\""
|
184
|
+
end
|
185
|
+
|
186
|
+
@out.newline
|
187
|
+
end
|
188
|
+
end
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: terraform_landscape
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Coinbase
|
8
|
+
- Shane da Silva
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2017-02-28 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: colorize
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0.7'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0.7'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: commander
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '4.4'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '4.4'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: diffy
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '3.0'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '3.0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: treetop
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - "~>"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '1.6'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.6'
|
70
|
+
description: Improve output of Terraform plans with color and indentation
|
71
|
+
email:
|
72
|
+
- shane@coinbase.com
|
73
|
+
executables:
|
74
|
+
- landscape
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- bin/landscape
|
79
|
+
- grammar/terraform_plan.treetop
|
80
|
+
- lib/terraform_landscape.rb
|
81
|
+
- lib/terraform_landscape/arguments_parser.rb
|
82
|
+
- lib/terraform_landscape/cli.rb
|
83
|
+
- lib/terraform_landscape/constants.rb
|
84
|
+
- lib/terraform_landscape/errors.rb
|
85
|
+
- lib/terraform_landscape/output.rb
|
86
|
+
- lib/terraform_landscape/printer.rb
|
87
|
+
- lib/terraform_landscape/terraform_plan.rb
|
88
|
+
- lib/terraform_landscape/version.rb
|
89
|
+
homepage: https://github.com/coinbase/terraform_landscape
|
90
|
+
licenses:
|
91
|
+
- Apache 2.0
|
92
|
+
metadata: {}
|
93
|
+
post_install_message:
|
94
|
+
rdoc_options: []
|
95
|
+
require_paths:
|
96
|
+
- lib
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '2'
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
requirements: []
|
108
|
+
rubyforge_project:
|
109
|
+
rubygems_version: 2.6.10
|
110
|
+
signing_key:
|
111
|
+
specification_version: 4
|
112
|
+
summary: Pretty-print Terraform plan output
|
113
|
+
test_files: []
|