lono 5.2.8 → 5.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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