sparkle_formation 2.1.8 → 3.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/docs/README.md +1 -0
- data/docs/helper-methods.md +1 -0
- data/docs/provider-restrictions.md +174 -0
- data/docs/translation.md +3 -0
- data/lib/sparkle_formation.rb +3 -0
- data/lib/sparkle_formation/function_struct.rb +132 -4
- data/lib/sparkle_formation/provider.rb +1 -0
- data/lib/sparkle_formation/provider/aws.rb +4 -4
- data/lib/sparkle_formation/provider/azure.rb +4 -4
- data/lib/sparkle_formation/provider/google.rb +200 -0
- data/lib/sparkle_formation/provider/heat.rb +4 -4
- data/lib/sparkle_formation/resources.rb +106 -21
- data/lib/sparkle_formation/resources/aws.rb +252 -0
- data/lib/sparkle_formation/resources/aws_resources.json +117 -29
- data/lib/sparkle_formation/resources/google.rb +47 -0
- data/lib/sparkle_formation/resources/google_resources.json +667 -0
- data/lib/sparkle_formation/sparkle.rb +145 -120
- data/lib/sparkle_formation/sparkle_attribute.rb +60 -21
- data/lib/sparkle_formation/sparkle_attribute/aws.rb +27 -18
- data/lib/sparkle_formation/sparkle_attribute/azure.rb +25 -12
- data/lib/sparkle_formation/sparkle_attribute/google.rb +149 -0
- data/lib/sparkle_formation/sparkle_attribute/heat.rb +33 -14
- data/lib/sparkle_formation/sparkle_collection.rb +37 -15
- data/lib/sparkle_formation/sparkle_formation.rb +48 -22
- data/lib/sparkle_formation/sparkle_struct.rb +43 -0
- data/lib/sparkle_formation/version.rb +1 -1
- metadata +7 -2
@@ -26,7 +26,7 @@ class SparkleFormation
|
|
26
26
|
# @yieldparam resource [AttributeStruct] the stack resource
|
27
27
|
# @yieldparam s_name [String] stack resource name
|
28
28
|
# @yieldreturn [Hash] key/values to be merged into resource properties
|
29
|
-
# @return [
|
29
|
+
# @return [SparkleFormation::SparkleStruct] compiled structure
|
30
30
|
def apply_deep_nesting(*args, &block)
|
31
31
|
outputs = collect_outputs
|
32
32
|
nested_stacks(:with_resource).each do |stack, resource|
|
@@ -44,7 +44,7 @@ class SparkleFormation
|
|
44
44
|
if(block_given?)
|
45
45
|
extract_templates(&block)
|
46
46
|
end
|
47
|
-
compile
|
47
|
+
compile
|
48
48
|
end
|
49
49
|
|
50
50
|
# Apply shallow nesting. This style of nesting will bubble
|
@@ -55,7 +55,7 @@ class SparkleFormation
|
|
55
55
|
# @yieldparam resource_name [String] name of stack resource
|
56
56
|
# @yieldparam stack [SparkleFormation] nested stack
|
57
57
|
# @yieldreturn [String] Remote URL storage for template
|
58
|
-
# @return [
|
58
|
+
# @return [SparkleFormation::SparkleStruct] compiled structure
|
59
59
|
def apply_shallow_nesting(*args, &block)
|
60
60
|
parameters = compile.parameters
|
61
61
|
output_map = {}
|
@@ -68,7 +68,7 @@ class SparkleFormation
|
|
68
68
|
compile.outputs._set(o_name).value compile._stack_output(*o_val)
|
69
69
|
end
|
70
70
|
end
|
71
|
-
compile
|
71
|
+
compile
|
72
72
|
end
|
73
73
|
|
74
74
|
# Extract output to make available for stack parameter usage at the
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'sparkle_formation'
|
2
|
+
|
3
|
+
class SparkleFormation
|
4
|
+
module Provider
|
5
|
+
# Google specific implementation
|
6
|
+
module Google
|
7
|
+
|
8
|
+
# Always return as nested since nesting is our final form
|
9
|
+
def nested?(*_)
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
# Extract nested stack templates and store in root level files
|
14
|
+
#
|
15
|
+
# @param template_hash [Hash] template hash to process
|
16
|
+
# @param dump_copy [Smash] translated dump
|
17
|
+
# @param parent_names [Array<String>] name of parent resources
|
18
|
+
# @return [Smash] dump_copy
|
19
|
+
def google_template_extractor(template_hash, dump_copy, parent_names=[])
|
20
|
+
template_hash.fetch('resources', []).each do |t_resource|
|
21
|
+
if(t_resource['type'] == stack_resource_type)
|
22
|
+
full_names = parent_names + [t_resource['name']]
|
23
|
+
stack = t_resource['properties'].delete('stack')
|
24
|
+
if(t_resource['properties'].empty?)
|
25
|
+
t_resource.delete('properties')
|
26
|
+
end
|
27
|
+
google_template_extractor(stack, dump_copy, full_names)
|
28
|
+
new_type = generate_template_files(full_names.join('-'), stack, dump_copy)
|
29
|
+
t_resource['type'] = new_type
|
30
|
+
end
|
31
|
+
end
|
32
|
+
dump_copy
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sets stack template files into target copy and extracts parameters
|
36
|
+
# into schema files if available
|
37
|
+
#
|
38
|
+
# @param r_name [String] name used for template file name
|
39
|
+
# @param r_stack [Hash] template to store
|
40
|
+
# @param dump_copy [Smash] translated dump
|
41
|
+
# @return [String] new type for stack
|
42
|
+
def generate_template_files(r_name, r_stack, dump_copy)
|
43
|
+
f_name = "#{r_name}.jinja"
|
44
|
+
r_parameters = r_stack.delete('parameters')
|
45
|
+
dump_copy[:imports].push(
|
46
|
+
Smash.new(
|
47
|
+
:name => f_name,
|
48
|
+
:content => r_stack
|
49
|
+
)
|
50
|
+
)
|
51
|
+
if(r_parameters)
|
52
|
+
dump_copy[:imports].push(
|
53
|
+
Smash.new(
|
54
|
+
:name => "#{f_name}.schema",
|
55
|
+
:content => Smash.new.tap{|schema|
|
56
|
+
schema.set(:info, :title, "#{f_name} template")
|
57
|
+
schema.set(:info, :description, "#{f_name} template schema")
|
58
|
+
schema.set(:properties, r_parameters)
|
59
|
+
}
|
60
|
+
)
|
61
|
+
)
|
62
|
+
end
|
63
|
+
f_name
|
64
|
+
end
|
65
|
+
|
66
|
+
# Customized dump to break out templates into consumable structures for
|
67
|
+
# passing to the deployment manager API
|
68
|
+
#
|
69
|
+
# @return [Hash]
|
70
|
+
def google_dump
|
71
|
+
result = non_google_dump
|
72
|
+
if(root?)
|
73
|
+
dump_copy = Smash.new(:imports => [])
|
74
|
+
google_template_extractor(result, dump_copy)
|
75
|
+
dump_copy.set(:config, :content, result)
|
76
|
+
dump_copy.set(:config, :content, :imports,
|
77
|
+
dump_copy[:imports].map{|i| i[:name]}
|
78
|
+
)
|
79
|
+
dump_copy.to_hash
|
80
|
+
else
|
81
|
+
result
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Properly remap dumping methods
|
86
|
+
def self.included(klass)
|
87
|
+
klass.class_eval do
|
88
|
+
alias_method :non_google_dump, :dump
|
89
|
+
alias_method :dump, :google_dump
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Properly remap dumping methods
|
94
|
+
def self.extended(klass)
|
95
|
+
klass.instance_eval do
|
96
|
+
alias :non_google_dump :dump
|
97
|
+
alias :dump :google_dump
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [String] Type string for Google Deployment Manager stack resource
|
102
|
+
# @note Nested templates aren't defined as a specific type thus no "real"
|
103
|
+
# type exists. So we'll create a custom one!
|
104
|
+
def stack_resource_type
|
105
|
+
'sparkleformation.stack'
|
106
|
+
end
|
107
|
+
|
108
|
+
# Generate policy for stack
|
109
|
+
#
|
110
|
+
# @return [Hash]
|
111
|
+
def generate_policy
|
112
|
+
{}
|
113
|
+
end
|
114
|
+
|
115
|
+
# Apply deeply nested stacks. This is the new nesting approach and
|
116
|
+
# does not bubble parameters up to the root stack. Parameters are
|
117
|
+
# isolated to the stack resource itself and output mapping is
|
118
|
+
# automatically applied.
|
119
|
+
#
|
120
|
+
# @yieldparam stack [SparkleFormation] stack instance
|
121
|
+
# @yieldparam resource [AttributeStruct] the stack resource
|
122
|
+
# @yieldparam s_name [String] stack resource name
|
123
|
+
# @yieldreturn [Hash] key/values to be merged into resource properties
|
124
|
+
# @return [SparkleFormation::SparkleStruct] compiled structure
|
125
|
+
def apply_deep_nesting(*args, &block)
|
126
|
+
outputs = collect_outputs
|
127
|
+
nested_stacks(:with_resource).each do |stack, resource|
|
128
|
+
unless(stack.nested_stacks.empty?)
|
129
|
+
stack.apply_deep_nesting(*args)
|
130
|
+
end
|
131
|
+
stack.compile.parameters.keys!.each do |parameter_name|
|
132
|
+
if(output_name = output_matched?(parameter_name, outputs.keys))
|
133
|
+
next if outputs[output_name] == stack
|
134
|
+
stack_output = stack.make_output_available(output_name, outputs, self)
|
135
|
+
# NOTE: Only set value if not already explicitly set
|
136
|
+
if(resource.properties._set(parameter_name).nil?)
|
137
|
+
resource.properties._set(parameter_name, stack_output)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
if(block_given?)
|
143
|
+
extract_templates(&block)
|
144
|
+
end
|
145
|
+
self
|
146
|
+
end
|
147
|
+
|
148
|
+
# Forcibly disable shallow nesting as support for it with Google templates doesn't
|
149
|
+
# really make much sense.
|
150
|
+
def apply_shallow_nesting(*args, &block)
|
151
|
+
raise NotImplementedError.new 'Shallow nesting is not supported for this provider!'
|
152
|
+
end
|
153
|
+
|
154
|
+
# Extract output to make available for stack parameter usage at the
|
155
|
+
# current depth
|
156
|
+
#
|
157
|
+
# @param output_name [String] name of output
|
158
|
+
# @param outputs [Hash] listing of outputs
|
159
|
+
# @param source_stack [SparkleFormation] requesting stack
|
160
|
+
# @reutrn [Hash] reference to output value (used for setting parameter)
|
161
|
+
def make_output_available(output_name, outputs, source_stack)
|
162
|
+
bubble_path = outputs[output_name].root_path - root_path
|
163
|
+
drip_path = root_path - outputs[output_name].root_path
|
164
|
+
bubble_path.each_slice(2) do |base_sparkle, ref_sparkle|
|
165
|
+
next unless ref_sparkle
|
166
|
+
base_sparkle.compile.outputs._set(output_name)._set(
|
167
|
+
:value, base_sparkle.compile._stack_output(
|
168
|
+
ref_sparkle.name, output_name
|
169
|
+
)
|
170
|
+
)
|
171
|
+
end
|
172
|
+
if(bubble_path.empty?)
|
173
|
+
if(drip_path.size == 1)
|
174
|
+
parent = drip_path.first.parent
|
175
|
+
if(parent && !parent.compile.parameters._set(output_name).nil?)
|
176
|
+
return compile.parameter!(output_name)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
raise ArgumentError.new "Failed to detect available bubbling path for output `#{output_name}`. " <<
|
180
|
+
'This may be due to a circular dependency! ' <<
|
181
|
+
"(Output Path: #{outputs[output_name].root_path.map(&:name).join(' > ')} " <<
|
182
|
+
"Requester Path: #{root_path.map(&:name).join(' > ')})"
|
183
|
+
end
|
184
|
+
result = source_stack.compile._stack_output(bubble_path.first.name, output_name)
|
185
|
+
if(drip_path.size > 1)
|
186
|
+
parent = drip_path.first.parent
|
187
|
+
drip_path.unshift(parent) if parent
|
188
|
+
drip_path.each_slice(2) do |base_sparkle, ref_sparkle|
|
189
|
+
next unless ref_sparkle
|
190
|
+
base_sparkle.compile.resources[ref_sparkle.name].properties.parameters.value._set(output_name, result)
|
191
|
+
ref_sparkle.compile.parameters._set(output_name).type 'string' # TODO: <<<<------ type check and prop
|
192
|
+
result = compile._parameter(output_name)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
result
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -26,7 +26,7 @@ class SparkleFormation
|
|
26
26
|
# @yieldparam resource [AttributeStruct] the stack resource
|
27
27
|
# @yieldparam s_name [String] stack resource name
|
28
28
|
# @yieldreturn [Hash] key/values to be merged into resource properties
|
29
|
-
# @return [
|
29
|
+
# @return [SparkleFormation::SparkleStruct] compiled structure
|
30
30
|
def apply_deep_nesting(*args, &block)
|
31
31
|
outputs = collect_outputs
|
32
32
|
nested_stacks(:with_resource).each do |stack, resource|
|
@@ -44,7 +44,7 @@ class SparkleFormation
|
|
44
44
|
if(block_given?)
|
45
45
|
extract_templates(&block)
|
46
46
|
end
|
47
|
-
compile
|
47
|
+
compile
|
48
48
|
end
|
49
49
|
|
50
50
|
# Apply shallow nesting. This style of nesting will bubble
|
@@ -55,7 +55,7 @@ class SparkleFormation
|
|
55
55
|
# @yieldparam resource_name [String] name of stack resource
|
56
56
|
# @yieldparam stack [SparkleFormation] nested stack
|
57
57
|
# @yieldreturn [String] Remote URL storage for template
|
58
|
-
# @return [
|
58
|
+
# @return [SparkleFormation::SparkleStruct] compiled structure
|
59
59
|
def apply_shallow_nesting(*args, &block)
|
60
60
|
parameters = compile.parameters
|
61
61
|
output_map = {}
|
@@ -68,7 +68,7 @@ class SparkleFormation
|
|
68
68
|
compile.outputs._set(o_name).value compile._stack_output(*o_val)
|
69
69
|
end
|
70
70
|
end
|
71
|
-
compile
|
71
|
+
compile
|
72
72
|
end
|
73
73
|
|
74
74
|
# Extract output to make available for stack parameter usage at the
|
@@ -6,6 +6,7 @@ class SparkleFormation
|
|
6
6
|
|
7
7
|
autoload :Aws, 'sparkle_formation/resources/aws'
|
8
8
|
autoload :Azure, 'sparkle_formation/resources/azure'
|
9
|
+
autoload :Google, 'sparkle_formation/resources/google'
|
9
10
|
autoload :Heat, 'sparkle_formation/resources/heat'
|
10
11
|
autoload :Rackspace, 'sparkle_formation/resources/rackspace'
|
11
12
|
|
@@ -13,6 +14,64 @@ class SparkleFormation
|
|
13
14
|
RESOURCE_TYPE_TR = '_'
|
14
15
|
# String to split for resource namespacing
|
15
16
|
RESOURCE_TYPE_NAMESPACE_SPLITTER = '::'
|
17
|
+
# Property update conditionals
|
18
|
+
# Format: Smash.new(RESOURCE_TYPE => {PROPERTY_NAME => [PropertyConditional]})
|
19
|
+
PROPERTY_UPDATE_CONDITIONALS = Smash.new
|
20
|
+
|
21
|
+
# Defines a resource type
|
22
|
+
#
|
23
|
+
# @param name [String] name of resource type
|
24
|
+
# @param properties [Array<Property>] resource properties
|
25
|
+
# @param raw [Hash] raw resource information
|
26
|
+
Resource = Struct.new(:name, :properties, :raw) do
|
27
|
+
# Get property by name
|
28
|
+
#
|
29
|
+
# @param name [String] name of property
|
30
|
+
# @return [Property, NilClass]
|
31
|
+
def property(name)
|
32
|
+
properties.detect do |prop|
|
33
|
+
prop.name == name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Defines conditional result for cause of property update
|
39
|
+
#
|
40
|
+
# @param update_causes [String] one of: 'replacement', 'interrupt', 'unknown', 'none'
|
41
|
+
# @param conditional [Proc, TrueClass] condition logic. passed two values: Hash of resource "final" state and
|
42
|
+
# Hash of resource "original" state
|
43
|
+
UpdateCausesConditional = Struct.new(:update_causes, :conditional)
|
44
|
+
|
45
|
+
# Defines a resource property
|
46
|
+
#
|
47
|
+
# @param name [String] property name
|
48
|
+
# @param description [String] property descrition
|
49
|
+
# @param type [String] property data type
|
50
|
+
# @param required [TrueClass, FalseClass] property is required
|
51
|
+
# @param update_causes [String] one of: 'replacement', 'interrupt', 'unknown', 'none'
|
52
|
+
# @param conditionals [Array<UpdateCausesConditional>] conditionals for update causes
|
53
|
+
Property = Struct.new(:name, :description, :type, :required, :update_causes, :conditionals) do
|
54
|
+
# Determine result of property update
|
55
|
+
#
|
56
|
+
# @param final_resource [Hash] desired resource structure containing this property
|
57
|
+
# @return ['replacement', 'interrupt', 'unknown', 'none']
|
58
|
+
def update_causes(final_resource=nil, original_resource=nil)
|
59
|
+
if(conditionals && final_resource)
|
60
|
+
final_resource = final_resource.to_smash
|
61
|
+
original_resource = original_resource.to_smash
|
62
|
+
result = conditionals.detect do |p_cond|
|
63
|
+
p_cond == true || p_cond.conditional.call(final_resource, original_resource)
|
64
|
+
end
|
65
|
+
if(result)
|
66
|
+
result.update_causes
|
67
|
+
else
|
68
|
+
'unknown'
|
69
|
+
end
|
70
|
+
else
|
71
|
+
self[:update_causes]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
16
75
|
|
17
76
|
class << self
|
18
77
|
|
@@ -80,27 +139,31 @@ class SparkleFormation
|
|
80
139
|
# @param key [String, Symbol]
|
81
140
|
# @return [String, NilClass]
|
82
141
|
def registry_key(key)
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
snake_parts =
|
89
|
-
|
90
|
-
|
91
|
-
snake_parts.
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
split_ref = ref.downcase.split(resource_type_splitter)
|
98
|
-
ref = split_ref.slice(split_ref.size - snake_parts.size, split_ref.size).join('')
|
99
|
-
key == ref
|
142
|
+
if(registry[key])
|
143
|
+
result = key
|
144
|
+
else
|
145
|
+
o_key = key
|
146
|
+
key = key.to_s.tr(self.const_get(:RESOURCE_TYPE_TR), '') # rubocop:disable Style/RedundantSelf
|
147
|
+
snake_parts = nil
|
148
|
+
result = @@registry[base_key].keys.detect do |ref|
|
149
|
+
ref = ref.downcase
|
150
|
+
snake_parts = ref.split(resource_type_splitter)
|
151
|
+
until(snake_parts.empty?)
|
152
|
+
break if snake_parts.join('') == key
|
153
|
+
snake_parts.shift
|
154
|
+
end
|
155
|
+
!snake_parts.empty?
|
100
156
|
end
|
101
|
-
if(
|
102
|
-
|
103
|
-
|
157
|
+
if(result)
|
158
|
+
collisions = @@registry[base_key].keys.find_all do |ref|
|
159
|
+
split_ref = ref.downcase.split(resource_type_splitter)
|
160
|
+
ref = split_ref.slice(split_ref.size - snake_parts.size, split_ref.size).join('')
|
161
|
+
key == ref
|
162
|
+
end
|
163
|
+
if(collisions.size > 1)
|
164
|
+
raise ArgumentError.new 'Ambiguous dynamic name returned multiple matches! ' \
|
165
|
+
"`#{o_key.inspect}` -> #{collisions.sort.join(', ')}"
|
166
|
+
end
|
104
167
|
end
|
105
168
|
end
|
106
169
|
result
|
@@ -121,7 +184,7 @@ class SparkleFormation
|
|
121
184
|
# @param key [String, Symbol]
|
122
185
|
# @return [Hashish, NilClass]
|
123
186
|
def lookup(key)
|
124
|
-
@@registry[base_key][registry_key(key)]
|
187
|
+
@@registry[base_key][key] || @@registry[base_key][registry_key(key)]
|
125
188
|
end
|
126
189
|
|
127
190
|
# @return [Hashish] currently loaded AWS registry
|
@@ -142,6 +205,28 @@ class SparkleFormation
|
|
142
205
|
struct
|
143
206
|
end
|
144
207
|
|
208
|
+
# Information about specific resource type
|
209
|
+
#
|
210
|
+
# @param type [String] resource type
|
211
|
+
# @return [Resource]
|
212
|
+
def resource_lookup(type)
|
213
|
+
result = registry[type]
|
214
|
+
if(result)
|
215
|
+
properties = result.fetch('full_properties', {}).map do |p_name, p_info|
|
216
|
+
Property.new(p_name,
|
217
|
+
p_info[:description],
|
218
|
+
p_info[:type],
|
219
|
+
p_info[:required],
|
220
|
+
p_info[:update_causes],
|
221
|
+
self.const_get(:PROPERTY_UPDATE_CONDITIONALS).get(type, p_name)
|
222
|
+
)
|
223
|
+
end
|
224
|
+
Resource.new(type, properties, result)
|
225
|
+
else
|
226
|
+
raise KeyError.new "Failed to locate requested resource type: `#{type}`"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
145
230
|
end
|
146
231
|
end
|
147
232
|
end
|
@@ -8,6 +8,258 @@ class SparkleFormation
|
|
8
8
|
# AWS specific resources collection
|
9
9
|
class Aws < Resources
|
10
10
|
|
11
|
+
# Conditionals for property updates
|
12
|
+
PROPERTY_UPDATE_CONDITIONALS = Smash.new(
|
13
|
+
'AWS::DynamoDB::Table' => {
|
14
|
+
'GlobalSecondaryIndexes' => [
|
15
|
+
# Updates not really supported here. Set as unknown to
|
16
|
+
# prompt user to investigate
|
17
|
+
UpdateCausesConditional.new('unknown', true)
|
18
|
+
]
|
19
|
+
},
|
20
|
+
'AWS::EC2::EIPAssociation' => {
|
21
|
+
'AllocationId' => [
|
22
|
+
UpdateCausesConditional.new('replacement',
|
23
|
+
lambda{|final, original|
|
24
|
+
original.get('Properties', 'InstanceId') != final.get('Properties', 'InstanceId') ||
|
25
|
+
original.get('Properties', 'NetworkInterfaceId') != final.get('Properties', 'NewtorkInterfaceId')
|
26
|
+
}
|
27
|
+
),
|
28
|
+
UpdateCausesConditional.new('none', true)
|
29
|
+
],
|
30
|
+
'EIP' => [
|
31
|
+
UpdateCausesConditional.new('replacement',
|
32
|
+
lambda{|final, original|
|
33
|
+
original.get('Properties', 'InstanceId') != final.get('Properties', 'InstanceId') ||
|
34
|
+
original.get('Properties', 'NetworkInterfaceId') != final.get('Properties', 'NewtorkInterfaceId')
|
35
|
+
}
|
36
|
+
),
|
37
|
+
UpdateCausesConditional.new('none', true)
|
38
|
+
],
|
39
|
+
'InstanceId' => [
|
40
|
+
UpdateCausesConditional.new('replacement',
|
41
|
+
lambda{|final, original|
|
42
|
+
original.get('Properties', 'AllocationId') != final.get('Properties', 'AllocationId') ||
|
43
|
+
original.get('Properties', 'EIP') != final.get('Properties', 'EIP')
|
44
|
+
}
|
45
|
+
),
|
46
|
+
UpdateCausesConditional.new('none', true)
|
47
|
+
],
|
48
|
+
'NetworkInterfaceId' => [
|
49
|
+
UpdateCausesConditional.new('replacement',
|
50
|
+
lambda{|final, original|
|
51
|
+
original.get('Properties', 'AllocationId') != final.get('Properties', 'AllocationId') ||
|
52
|
+
original.get('Properties', 'EIP') != final.get('Properties', 'EIP')
|
53
|
+
}
|
54
|
+
),
|
55
|
+
UpdateCausesConditional.new('none', true)
|
56
|
+
]
|
57
|
+
},
|
58
|
+
'AWS::EC2::Instance' => {
|
59
|
+
'AdditionalInfo' => [
|
60
|
+
UpdateCausesConditional.new('unknown', true) # EBS AMI dependent
|
61
|
+
],
|
62
|
+
'BlockDeviceMappings' => [
|
63
|
+
UpdateCausesConditional.new('replacement',
|
64
|
+
lambda{|final, original|
|
65
|
+
f_maps = final.fetch('Properties', 'BlockDeviceMappings', [])
|
66
|
+
o_maps = original.fetch('Properties', 'BlockDeviceMappings', [])
|
67
|
+
f_maps.map! do |m|
|
68
|
+
m.delete('DeleteOnTermination')
|
69
|
+
m.to_smash(:sorted)
|
70
|
+
end
|
71
|
+
o_maps.map! do |m|
|
72
|
+
m.delete('DeleteOnTermination')
|
73
|
+
m.to_smash(:sorted)
|
74
|
+
end
|
75
|
+
f_maps.size != o_maps.size ||
|
76
|
+
!f_maps.all?{|m| o_maps.include?(m)}
|
77
|
+
}
|
78
|
+
),
|
79
|
+
UpdateCausesConditional.new('none', true)
|
80
|
+
],
|
81
|
+
'EbsOptimized' => [
|
82
|
+
UpdateCausesConditional.new('unknown', true) # EBS AMI dependent
|
83
|
+
],
|
84
|
+
'InstanceType' => [
|
85
|
+
UpdateCausesConditional.new('unknown', true) # EBS AMI dependent
|
86
|
+
],
|
87
|
+
'KernelId' => [
|
88
|
+
UpdateCausesConditional.new('unknown', true) # EBS AMI dependent
|
89
|
+
],
|
90
|
+
'RamdiskId' => [
|
91
|
+
UpdateCausesConditional.new('unknown', true) # EBS AMI dependent
|
92
|
+
],
|
93
|
+
'SecurityGroupIds' => [
|
94
|
+
UpdateCausesConditional.new('none',
|
95
|
+
lambda{|final, _orig|
|
96
|
+
final.get('Properties', 'SubnetId') ||
|
97
|
+
final.fetch('Properties', 'NetworkInterface', {}).values.include?('SubnetId')
|
98
|
+
}
|
99
|
+
),
|
100
|
+
UpdateCausesConditional.new('replacement', true)
|
101
|
+
],
|
102
|
+
'UserData' => [
|
103
|
+
UpdateCausesConditional.new('unknown', true) # EBS AMI dependent
|
104
|
+
]
|
105
|
+
},
|
106
|
+
'AWS::EC2::NetworkInterface' => {
|
107
|
+
'PrivateIpAddresses' => [
|
108
|
+
UpdateCausesConditional.new('replacement',
|
109
|
+
lambda{|final, original|
|
110
|
+
f_primary = final.fetch('Properties', 'PrivateIpAddresses', []).detect do |addr|
|
111
|
+
addr['Primary']
|
112
|
+
end || Smash.new
|
113
|
+
o_primary = original.fetch('Properties', 'PrivateIpAddresses', []).detect do |addr|
|
114
|
+
addr['Primary']
|
115
|
+
end || Smash.new
|
116
|
+
f_primary.to_smash(:sorted) != o_primary.to_smash(:sorted)
|
117
|
+
}
|
118
|
+
),
|
119
|
+
UpdateCausesConditional.new('none', true)
|
120
|
+
]
|
121
|
+
},
|
122
|
+
'AWS::ElastiCache::CacheCluster' => {
|
123
|
+
'NumCacheNodes' => [
|
124
|
+
UpdateCausesConditional.new('replacement',
|
125
|
+
lambda{|final, original|
|
126
|
+
[
|
127
|
+
final.get('Properties', 'PreferredAvailabilityZone'),
|
128
|
+
final.get('Properties', 'PreferredAvailabilityZones'),
|
129
|
+
original.get('Properties', 'PreferredAvailabilityZone'),
|
130
|
+
original.get('Properties', 'PreferredAvailabilityZones')
|
131
|
+
].all?{|i| i.nil? || i.empty? }
|
132
|
+
}
|
133
|
+
),
|
134
|
+
UpdateCausesConditional.new('none', true)
|
135
|
+
],
|
136
|
+
'PreferredAvailabilityZones' => [
|
137
|
+
UpdateCausesConditional.new('interrupt',
|
138
|
+
lambda{|final, original|
|
139
|
+
original.get('Properties', 'PreferredAvailabilityZones') ||
|
140
|
+
final.fetch('Properties', 'PreferredAvailabilityZones', []).include?(
|
141
|
+
original.get('Properties', 'PreferredAvailabilityZone')
|
142
|
+
)
|
143
|
+
}
|
144
|
+
),
|
145
|
+
UpdateCausesConditional.new('replacement', true)
|
146
|
+
]
|
147
|
+
},
|
148
|
+
'AWS::ElasticLoadBalancing::LoadBalancer' => {
|
149
|
+
'AvailabilityZones' => [
|
150
|
+
UpdateCausesConditional.new('replacement',
|
151
|
+
lambda{|final, original|
|
152
|
+
original.fetch('Properties', 'AvailabilityZones', []).empty? ||
|
153
|
+
final.fetch('Properties', 'AvailabilityZones', []).empty?
|
154
|
+
}
|
155
|
+
),
|
156
|
+
UpdateCausesConditional.new('none', true)
|
157
|
+
],
|
158
|
+
'HealthCheck' => [
|
159
|
+
UpdateCausesConditional.new('replacement',
|
160
|
+
lambda{|final, original|
|
161
|
+
original.fetch('Properties', 'HealthCheck', {}).empty? ||
|
162
|
+
final.fetch('Properties', 'HealthCheck', {}).empty?
|
163
|
+
}
|
164
|
+
),
|
165
|
+
UpdateCausesConditional.new('none', true)
|
166
|
+
],
|
167
|
+
'Subnets' => [
|
168
|
+
UpdateCausesConditional.new('replacement',
|
169
|
+
lambda{|final, original|
|
170
|
+
original.fetch('Properties', 'Subnets', []).empty? ||
|
171
|
+
final.fetch('Properties', 'Subnets', []).empty?
|
172
|
+
}
|
173
|
+
),
|
174
|
+
UpdateCausesConditional.new('none', true)
|
175
|
+
]
|
176
|
+
},
|
177
|
+
'AWS::RDS::DBCluster' => {
|
178
|
+
'BackupRetentionPeriod' => [
|
179
|
+
UpdateCausesConditional.new('interrupt',
|
180
|
+
lambda{|final, original|
|
181
|
+
fp = final.get('Properties', 'BackupRetentionPeriod').to_i
|
182
|
+
op = original.get('Properties', 'BackupRetentionPeriod').to_i
|
183
|
+
(fp == 0 && op != 0) ||
|
184
|
+
(op == 0 && fp != 0)
|
185
|
+
}
|
186
|
+
),
|
187
|
+
UpdateCausesConditional.new('none', true)
|
188
|
+
],
|
189
|
+
'PreferredMaintenanceWindow' => [
|
190
|
+
# can interrupt if apply immediately is set on api call but
|
191
|
+
# no way to know
|
192
|
+
UpdateCausesConditional.new('unknown', true)
|
193
|
+
]
|
194
|
+
},
|
195
|
+
'AWS::RDS::DBClusterParameterGroup' => {
|
196
|
+
'Parameters' => [
|
197
|
+
# dependent on what parameters have been changed. doesn't
|
198
|
+
# look like parameter modifications are applied immediately?
|
199
|
+
# set as unknown for safety
|
200
|
+
UpdateCausesConditional.new('unknown', true)
|
201
|
+
]
|
202
|
+
},
|
203
|
+
'AWS::RDS::DBInstance' => {
|
204
|
+
'AutoMinorVersionUpgrade' => [
|
205
|
+
# can cause interrupts based on future actions (enables
|
206
|
+
# auto patching) so leave as unknown for safety
|
207
|
+
UpdateCausesConditional.new('unknown', true)
|
208
|
+
],
|
209
|
+
'BackupRetentionPeriod' => [
|
210
|
+
UpdateCausesConditional.new('interrupt',
|
211
|
+
lambda{|final, original|
|
212
|
+
fp = final.get('Properties', 'BackupRetentionPeriod').to_i
|
213
|
+
op = original.get('Properties', 'BackupRetentionPeriod').to_i
|
214
|
+
(fp == 0 && op != 0) ||
|
215
|
+
(op == 0 && fp != 0)
|
216
|
+
}
|
217
|
+
),
|
218
|
+
UpdateCausesConditional.new('none', true)
|
219
|
+
],
|
220
|
+
'DBParameterGroupName' => [
|
221
|
+
# changes are not applied until reboot, but it could
|
222
|
+
# still be considered an interrupt? setting as unknown
|
223
|
+
# for safety
|
224
|
+
UpdateCausesConditional.new('unknown', true)
|
225
|
+
],
|
226
|
+
'PreferredMaintenanceWindow' => [
|
227
|
+
# can interrupt if apply immediately is set on api call but
|
228
|
+
# no way to know
|
229
|
+
UpdateCausesConditional.new('unknown', true)
|
230
|
+
]
|
231
|
+
},
|
232
|
+
'AWS::RDS::DBParameterGroup' => {
|
233
|
+
'Parameters' => [
|
234
|
+
# dependent on what parameters have been changed. doesn't
|
235
|
+
# look like parameter modifications are applied immediately?
|
236
|
+
# set as unknown for safety
|
237
|
+
UpdateCausesConditional.new('unknown', true)
|
238
|
+
]
|
239
|
+
},
|
240
|
+
'AWS::RDS::EventSubscription' => {
|
241
|
+
'SourceType' => [
|
242
|
+
UpdateCausesConditional.new('replacement',
|
243
|
+
lambda{|final, original|
|
244
|
+
!final.get('Properties', 'SourceType')
|
245
|
+
}
|
246
|
+
),
|
247
|
+
UpdateCausesConditional.new('none', true)
|
248
|
+
]
|
249
|
+
},
|
250
|
+
'AWS::Route53::HostedZone' => {
|
251
|
+
'VPCs' => [
|
252
|
+
UpdateCausesConditional.new('replacement',
|
253
|
+
lambda{|final, original|
|
254
|
+
!final.get('Properties', 'VPCs') ||
|
255
|
+
!original.get('Properties', 'VPCs')
|
256
|
+
}
|
257
|
+
),
|
258
|
+
UpdateCausesConditional.new('none', true)
|
259
|
+
]
|
260
|
+
}
|
261
|
+
)
|
262
|
+
|
11
263
|
class << self
|
12
264
|
|
13
265
|
include Bogo::Memoization
|