lono 5.2.8 → 5.3.0

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.
@@ -0,0 +1,73 @@
1
+ module Lono::Cfn::Preview
2
+ class Param < Lono::Cfn::Base
3
+ delegate :required_parameters, :optional_parameters, :parameters, :data,
4
+ to: :output_template
5
+
6
+ include DiffViewer
7
+ include Lono::AwsServices
8
+
9
+ def run
10
+ return unless stack_exists?(@stack_name)
11
+
12
+ puts "Parameter Diff Preview:".color(:green)
13
+ if @options[:noop]
14
+ puts "NOOP CloudFormation parameters preview for #{@stack_name} update"
15
+ return
16
+ end
17
+
18
+ params = generate_all
19
+ write_to_tmp(new_path, params)
20
+ write_to_tmp(existing_path, existing_parameters)
21
+
22
+ show_diff(existing_path, new_path)
23
+ end
24
+
25
+ def existing_parameters
26
+ resp = cfn.describe_stacks(stack_name: @stack_name)
27
+ stack = resp.stacks.first
28
+ parameters = stack.parameters
29
+
30
+ # Remove optional parameters if they match already. Produces better diff.
31
+ optional = optional_parameters.map do |logical_id, attributes|
32
+ {
33
+ "ParameterKey" => logical_id,
34
+ "ParameterValue" => attributes["Default"],
35
+ }
36
+ end
37
+ converted = convert_to_cfn_format(parameters)
38
+ converted - optional
39
+ end
40
+
41
+ private
42
+ def output_template
43
+ Lono::OutputTemplate.new(@blueprint, @template)
44
+ end
45
+ memoize :output_template
46
+
47
+ def write_to_tmp(path, list)
48
+ converted = convert_to_cfn_format(list)
49
+ text = JSON.pretty_generate(converted)
50
+ FileUtils.mkdir_p(File.dirname(path))
51
+ IO.write(path, text)
52
+ end
53
+
54
+ def convert_to_cfn_format(list)
55
+ camelized = list.map(&:to_h).map do |h|
56
+ h.transform_keys {|k| k.to_s.camelize}
57
+ end
58
+ camelized.sort_by { |h| h["ParameterKey"] }
59
+ end
60
+
61
+ def existing_path
62
+ "#{tmp_base}/existing.json"
63
+ end
64
+
65
+ def new_path
66
+ "#{tmp_base}/new.json"
67
+ end
68
+
69
+ def tmp_base
70
+ "/tmp/lono/params-preview"
71
+ end
72
+ end
73
+ end
@@ -27,16 +27,17 @@ class Lono::Cfn
27
27
 
28
28
  options = @options.merge(mute_params: true, mute_using: true, keep: true)
29
29
  # create new copy of preview when update_stack is called because of IAM retry logic
30
- preview = Lono::Cfn::Preview.new(@stack_name, options)
30
+ changeset_preview = Lono::Cfn::Preview::Changeset.new(@stack_name, options)
31
31
 
32
32
  error = nil
33
- diff.run if @options[:diff]
34
- preview.run if @options[:preview]
33
+ param_preview.run if @options[:param_preview]
34
+ codediff_preview.run if @options[:codediff_preview]
35
+ changeset_preview.run if @options[:changeset_preview]
35
36
  are_you_sure?(@stack_name, :update)
36
37
 
37
38
  if @options[:change_set] # defaults to this
38
- message << " via change set: #{preview.change_set_name}"
39
- preview.execute_change_set
39
+ message << " via change set: #{changeset_preview.change_set_name}"
40
+ changeset_preview.execute_change_set
40
41
  else
41
42
  standard_update(params)
42
43
  end
@@ -57,12 +58,18 @@ class Lono::Cfn
57
58
  cfn.update_stack(params)
58
59
  rescue Aws::CloudFormation::Errors::ValidationError => e
59
60
  puts "ERROR: #{e.message}".red
60
- error = true
61
+ false
61
62
  end
62
63
  end
63
64
 
64
- def diff
65
- @diff ||= Lono::Cfn::Diff.new(@stack_name, @options.merge(mute_params: true, mute_using: true))
65
+ def codediff_preview
66
+ Lono::Cfn::Preview::Codediff.new(@stack_name, @options.merge(mute_params: true, mute_using: true))
66
67
  end
68
+ memoize :codediff_preview
69
+
70
+ def param_preview
71
+ Lono::Cfn::Preview::Param.new(@stack_name, @options)
72
+ end
73
+ memoize :param_preview
67
74
  end
68
75
  end
@@ -1,4 +1,4 @@
1
1
  module Lono
2
- class Inspector
2
+ module Inspector
3
3
  end
4
4
  end
@@ -1,48 +1,42 @@
1
- class Lono::Inspector::Base
2
- include Lono::Blueprint::Root
1
+ module Lono::Inspector
2
+ class Base
3
+ delegate :required_parameters, :optional_parameters, :parameters, :data,
4
+ to: :output_template
3
5
 
4
- def initialize(blueprint, template, options)
5
- @blueprint, @template, @options = blueprint, template, options
6
- end
6
+ extend Memoist
7
+ include Lono::Blueprint::Root
7
8
 
8
- def run
9
- blueprints = Lono::Blueprint::Find.one_or_all(@blueprint)
10
- blueprints.each do |blueprint|
11
- @blueprint_name = blueprint
9
+ def initialize(blueprint, template, options)
10
+ @blueprint, @template, @options = blueprint, template, options
11
+ end
12
12
 
13
- generate_templates
14
- set_blueprint_root(blueprint)
15
- templates = @template ? [@template] : all_templates
16
- templates.each do |template_name|
17
- perform(template_name)
13
+ def run
14
+ blueprints = Lono::Blueprint::Find.one_or_all(@blueprint)
15
+ blueprints.each do |blueprint|
16
+ @blueprint = blueprint # intentional overwrite
17
+ generate_templates
18
+ set_blueprint_root(blueprint)
19
+ templates = @template_name ? [@template_name] : all_templates
20
+ templates.each do |template_name|
21
+ perform(template_name)
22
+ end
18
23
  end
19
24
  end
20
- end
21
-
22
- def generate_templates
23
- Lono::Template::Generator.new(@blueprint_name, @options.clone.merge(quiet: false)).run
24
- end
25
25
 
26
- def all_templates
27
- templates_path = "#{Lono.config.output_path}/#{@blueprint_name}/templates"
28
- Dir.glob("#{templates_path}/**").map do |path|
29
- path.sub("#{templates_path}/", '').sub('.yml','') # template_name
26
+ def generate_templates
27
+ Lono::Template::Generator.new(@blueprint, @options.clone.merge(quiet: false)).run
30
28
  end
31
- end
32
29
 
33
- def data
34
- template_path = "#{Lono.config.output_path}/#{@blueprint_name}/templates/#{@template_name}.yml"
35
- check_template_exists(template_path)
36
- YAML.load(IO.read(template_path))
37
- end
30
+ def all_templates
31
+ templates_path = "#{Lono.config.output_path}/#{@blueprint}/templates"
32
+ Dir.glob("#{templates_path}/**").map do |path|
33
+ path.sub("#{templates_path}/", '').sub('.yml','') # template_name
34
+ end
35
+ end
38
36
 
39
- # Check if the template exists and print friendly error message. Exits if it
40
- # does not exist.
41
- def check_template_exists(template_path)
42
- unless File.exist?(template_path)
43
- puts "The template #{template_path} does not exist. Are you sure you use the right template name? The template name does not require the extension.".color(:red)
44
- exit 1
37
+ def output_template
38
+ Lono::OutputTemplate.new(@blueprint, @template)
45
39
  end
40
+ memoize :output_template
46
41
  end
47
-
48
42
  end
@@ -1,124 +1,125 @@
1
1
  require "yaml"
2
2
  require "graph"
3
3
 
4
- class Lono::Inspector::Graph < Lono::Inspector::Base
5
- def initialize(blueprint, template, options)
6
- super
7
- @nodes = [] # lookup map
8
- end
9
-
10
- def perform(template_name)
11
- # little dirty but @template_name is used in data method
12
- # so we dont have to pass it to the data method
13
- @template_name = template_name
14
-
15
- puts "Generating dependencies tree for template #{@template_name}..."
16
- return if @options[:noop]
17
-
18
- # First loop through top level nodes and build set depends_on property
19
- node_list = [] # top level node list
20
- resources = data["Resources"]
21
- resources.each do |logical_id, resource|
22
- node = Node.new(logical_id)
23
- node.depends_on = normalize_depends_on(resource)
24
- node.resource_type = normalize_resource_type(resource)
25
- node_list << node
4
+ module Lono::Inspector
5
+ class Graph < Base
6
+ def initialize(blueprint, template, options)
7
+ super
8
+ @nodes = [] # lookup map
26
9
  end
27
10
 
28
- # Now that we have loop through all the top level resources once
29
- # we can use the depends_on attribute on each node and set the
30
- # children property since the identity nodes are in memory.
31
- node_list.each do |node|
32
- node.children = normalize_children(node_list, node)
33
- end
11
+ def perform(template)
12
+ # little dirty but @template is used in data method so we dont have to pass it to the data method
13
+ @template = template
14
+
15
+ puts "Generating dependencies tree for template #{@template}..."
16
+ return if @options[:noop]
17
+
18
+ # First loop through top level nodes and build set depends_on property
19
+ node_list = [] # top level node list
20
+ resources = data["Resources"]
21
+ resources.each do |logical_id, resource|
22
+ node = Node.new(logical_id)
23
+ node.depends_on = normalize_depends_on(resource)
24
+ node.resource_type = normalize_resource_type(resource)
25
+ node_list << node
26
+ end
34
27
 
35
- # At this point we have a tree of nodes.
36
- if @options[:display] == "text"
37
- puts "CloudFormation Dependencies:"
38
- node_list.each { |node| print_tree(node) }
39
- else
40
- print_graph(node_list)
41
- puts "CloudFormation Dependencies graph generated."
42
- end
43
- end
28
+ # Now that we have loop through all the top level resources once
29
+ # we can use the depends_on attribute on each node and set the
30
+ # children property since the identity nodes are in memory.
31
+ node_list.each do |node|
32
+ node.children = normalize_children(node_list, node)
33
+ end
44
34
 
45
- # normalized DependOn attribute to an Array of Strings
46
- def normalize_depends_on(resource)
47
- dependencies = resource["DependOn"] || []
48
- [dependencies].flatten
49
- end
35
+ # At this point we have a tree of nodes.
36
+ if @options[:display] == "text"
37
+ puts "CloudFormation Dependencies:"
38
+ node_list.each { |node| print_tree(node) }
39
+ else
40
+ print_graph(node_list)
41
+ puts "CloudFormation Dependencies graph generated."
42
+ end
43
+ end
50
44
 
51
- def normalize_resource_type(resource)
52
- type = resource["Type"]
53
- type.sub("AWS::", "") # strip out AWS to make less verbose
54
- end
45
+ # normalized DependOn attribute to an Array of Strings
46
+ def normalize_depends_on(resource)
47
+ dependencies = resource["DependOn"] || []
48
+ [dependencies].flatten
49
+ end
55
50
 
56
- # It is possible with bad CloudFormation templates that the dependency is not
57
- # resolved, but we wont deal with that. Users can validate their CloudFormation
58
- # template before using this tool.
59
- def normalize_children(node_list, node)
60
- kids = []
61
- node.depends_on.each do |dependent_logical_id|
62
- node = node_list.find { |n| n.name == dependent_logical_id }
63
- kids << node
51
+ def normalize_resource_type(resource)
52
+ type = resource["Type"]
53
+ type.sub("AWS::", "") # strip out AWS to make less verbose
64
54
  end
65
- kids
66
- end
67
55
 
68
- def print_tree(node, depth=0)
69
- spacing = " " * depth
70
- puts "#{spacing}#{node.name}"
71
- node.children.each do |node|
72
- print_tree(node, depth+1)
56
+ # It is possible with bad CloudFormation templates that the dependency is not
57
+ # resolved, but we wont deal with that. Users can validate their CloudFormation
58
+ # template before using this tool.
59
+ def normalize_children(node_list, node)
60
+ kids = []
61
+ node.depends_on.each do |dependent_logical_id|
62
+ node = node_list.find { |n| n.name == dependent_logical_id }
63
+ kids << node
64
+ end
65
+ kids
73
66
  end
74
- end
75
67
 
76
- def print_graph(node_list)
77
- check_graphviz_installed
78
- digraph do
79
- # graph_attribs << 'size="6,6"'
80
- node_attribs << lightblue << filled
68
+ def print_tree(node, depth=0)
69
+ spacing = " " * depth
70
+ puts "#{spacing}#{node.name}"
71
+ node.children.each do |node|
72
+ print_tree(node, depth+1)
73
+ end
74
+ end
81
75
 
82
- node_list.each do |n|
83
- node(n.graph_name)
84
- n.children.each do |child|
85
- edge n.graph_name, child.graph_name
76
+ def print_graph(node_list)
77
+ check_graphviz_installed
78
+ digraph do
79
+ # graph_attribs << 'size="6,6"'
80
+ node_attribs << lightblue << filled
81
+
82
+ node_list.each do |n|
83
+ node(n.graph_name)
84
+ n.children.each do |child|
85
+ edge n.graph_name, child.graph_name
86
+ end
86
87
  end
87
- end
88
88
 
89
- random = (0...8).map { (65 + rand(26)).chr }.join
90
- path = "/tmp/cloudformation-depends-on-#{random}"
91
- save path, "png"
92
- # Check if open command exists and use it to open the image.
93
- system "open #{path}.png" if system("type open > /dev/null")
89
+ random = (0...8).map { (65 + rand(26)).chr }.join
90
+ path = "/tmp/cloudformation-depends-on-#{random}"
91
+ save path, "png"
92
+ # Check if open command exists and use it to open the image.
93
+ system "open #{path}.png" if system("type open > /dev/null")
94
+ end
94
95
  end
95
- end
96
96
 
97
- # Check if Graphiz is installed and prints a user friendly message if it is not installed.
98
- # Provide instructions if on macosx.
99
- def check_graphviz_installed
100
- installed = system("type dot > /dev/null") # dot is a command that is part of the graphviz package
101
- unless installed
102
- puts "It appears that the Graphviz is not installed. Please install it to generate the graph."
103
- if RUBY_PLATFORM =~ /darwin/
104
- puts "You can install Graphviz with homebrew:"
105
- puts " brew install brew install graphviz"
97
+ # Check if Graphiz is installed and prints a user friendly message if it is not installed.
98
+ # Provide instructions if on macosx.
99
+ def check_graphviz_installed
100
+ installed = system("type dot > /dev/null") # dot is a command that is part of the graphviz package
101
+ unless installed
102
+ puts "It appears that the Graphviz is not installed. Please install it to generate the graph."
103
+ if RUBY_PLATFORM =~ /darwin/
104
+ puts "You can install Graphviz with homebrew:"
105
+ puts " brew install brew install graphviz"
106
+ end
107
+ exit 1
106
108
  end
107
- exit 1
108
109
  end
109
- end
110
110
 
111
- class Node
112
- attr_accessor :name, :resource_type, :children, :depends_on
113
- def initialize(name)
114
- @name = name
115
- @children = []
116
- @depends_on = []
117
- end
111
+ class Node
112
+ attr_accessor :name, :resource_type, :children, :depends_on
113
+ def initialize(name)
114
+ @name = name
115
+ @children = []
116
+ @depends_on = []
117
+ end
118
118
 
119
- def graph_name
120
- type = "(#{resource_type})" if resource_type
121
- [name, type].compact.join("\n")
119
+ def graph_name
120
+ type = "(#{resource_type})" if resource_type
121
+ [name, type].compact.join("\n")
122
+ end
122
123
  end
123
124
  end
124
125
  end
@@ -1,76 +1,65 @@
1
- class Lono::Inspector::Summary < Lono::Inspector::Base
2
- def perform(template_name)
3
- # little dirty but @template_name is used in data method
4
- # so we dont have to pass it to the data method
5
- @template_name = template_name
1
+ module Lono::Inspector
2
+ class Summary < Base
3
+ def perform(template)
4
+ # little dirty but @template is used in data method so we dont have to pass it to the data method
5
+ @template = template
6
6
 
7
- puts "=> CloudFormation Template Summary for template #{@template_name.color(:sienna)}:"
8
- return if @options[:noop]
7
+ puts "=> CloudFormation Template Summary for template #{@template.color(:sienna)}:"
8
+ return if @options[:noop]
9
9
 
10
- print_parameters_summary
10
+ print_parameters_summary
11
11
 
12
- puts "Resources:"
13
- print_resource_types
14
- end
12
+ puts "Resources:"
13
+ print_resource_types
14
+ end
15
15
 
16
- def print_parameters_summary
17
- if parameters.empty?
18
- puts "There are no parameters in this template."
19
- else
20
- print_parameters("Required Parameters", required_parameters)
21
- print_parameters("Optional Parameters", optional_parameters)
16
+ def print_parameters_summary
17
+ if parameters.empty?
18
+ puts "There are no parameters in this template."
19
+ else
20
+ print_parameters("Required Parameters", required_parameters)
21
+ print_parameters("Optional Parameters", optional_parameters)
22
+ end
22
23
  end
23
- end
24
24
 
25
- def print_parameters(label, parameters)
26
- puts "#{label}:"
27
- if parameters.empty?
28
- puts " There are no #{label.downcase} parameters"
29
- else
30
- parameters.each do |logical_id, p|
31
- output = " #{logical_id} (#{p["Type"]})"
32
- if p["Default"]
33
- output << " Default: #{p["Default"]}"
25
+ def print_parameters(label, parameters)
26
+ puts "#{label}:"
27
+ if parameters.empty?
28
+ puts " There are no #{label.downcase} parameters"
29
+ else
30
+ parameters.each do |logical_id, p|
31
+ output = " #{logical_id} (#{p["Type"]})"
32
+ if p["Default"]
33
+ output << " Default: #{p["Default"]}"
34
+ end
35
+ puts output
34
36
  end
35
- puts output
36
37
  end
37
38
  end
38
- end
39
-
40
- def required_parameters
41
- parameters.reject { |logical_id, p| p["Default"] }
42
- end
43
-
44
- def optional_parameters
45
- parameters.select { |logical_id, p| p["Default"] }
46
- end
47
-
48
- def parameters
49
- data["Parameters"] || []
50
- end
51
39
 
52
- def resource_types
53
- resources = data["Resources"]
54
- return unless resources
40
+ def resource_types
41
+ resources = data["Resources"]
42
+ return unless resources
55
43
 
56
- types = Hash.new(0)
57
- resources.each do |logical_id, resource|
58
- types[resource["Type"]] += 1
44
+ types = Hash.new(0)
45
+ resources.each do |logical_id, resource|
46
+ types[resource["Type"]] += 1
47
+ end
48
+ types
59
49
  end
60
- types
61
- end
62
50
 
63
- def print_resource_types
64
- unless resource_types
65
- puts "No resources found."
66
- return
67
- end
51
+ def print_resource_types
52
+ unless resource_types
53
+ puts "No resources found."
54
+ return
55
+ end
68
56
 
69
- types = resource_types.sort_by {|r| r[1] * -1} # Hash -> 2D Array
70
- types.each do |a|
71
- type, count = a
72
- printf "%3s %s\n", count, type
57
+ types = resource_types.sort_by {|r| r[1] * -1} # Hash -> 2D Array
58
+ types.each do |a|
59
+ type, count = a
60
+ printf "%3s %s\n", count, type
61
+ end
62
+ printf "%3s %s\n", resource_types.size, "Total"
73
63
  end
74
- printf "%3s %s\n", resource_types.size, "Total"
75
64
  end
76
65
  end