knife-cloudformation 0.1.22 → 0.2.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 +7 -0
- data/CHANGELOG.md +6 -0
- data/README.md +56 -2
- data/knife-cloudformation.gemspec +4 -7
- data/lib/chef/knife/cloudformation_create.rb +105 -245
- data/lib/chef/knife/cloudformation_describe.rb +50 -26
- data/lib/chef/knife/cloudformation_destroy.rb +17 -18
- data/lib/chef/knife/cloudformation_events.rb +48 -14
- data/lib/chef/knife/cloudformation_export.rb +117 -34
- data/lib/chef/knife/cloudformation_import.rb +124 -18
- data/lib/chef/knife/cloudformation_inspect.rb +159 -71
- data/lib/chef/knife/cloudformation_list.rb +20 -24
- data/lib/chef/knife/cloudformation_promote.rb +40 -0
- data/lib/chef/knife/cloudformation_update.rb +132 -15
- data/lib/chef/knife/cloudformation_validate.rb +35 -0
- data/lib/knife-cloudformation.rb +28 -0
- data/lib/knife-cloudformation/cache.rb +213 -35
- data/lib/knife-cloudformation/knife.rb +9 -0
- data/lib/knife-cloudformation/knife/base.rb +179 -0
- data/lib/knife-cloudformation/knife/stack.rb +94 -0
- data/lib/knife-cloudformation/knife/template.rb +174 -0
- data/lib/knife-cloudformation/monkey_patch.rb +8 -0
- data/lib/knife-cloudformation/monkey_patch/stack.rb +195 -0
- data/lib/knife-cloudformation/provider.rb +225 -0
- data/lib/knife-cloudformation/utils.rb +18 -98
- data/lib/knife-cloudformation/utils/animal_strings.rb +28 -0
- data/lib/knife-cloudformation/utils/debug.rb +31 -0
- data/lib/knife-cloudformation/utils/json.rb +64 -0
- data/lib/knife-cloudformation/utils/object_storage.rb +28 -0
- data/lib/knife-cloudformation/utils/output.rb +79 -0
- data/lib/knife-cloudformation/utils/path_selector.rb +99 -0
- data/lib/knife-cloudformation/utils/ssher.rb +29 -0
- data/lib/knife-cloudformation/utils/stack_exporter.rb +271 -0
- data/lib/knife-cloudformation/utils/stack_parameter_scrubber.rb +35 -0
- data/lib/knife-cloudformation/utils/stack_parameter_validator.rb +124 -0
- data/lib/knife-cloudformation/version.rb +2 -4
- metadata +47 -94
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -90
- data/knife-cloudformation-0.1.20.gem +0 -0
- data/lib/knife-cloudformation/aws_commons.rb +0 -267
- data/lib/knife-cloudformation/aws_commons/stack.rb +0 -435
- data/lib/knife-cloudformation/aws_commons/stack_parameter_validator.rb +0 -79
- data/lib/knife-cloudformation/cloudformation_base.rb +0 -168
- data/lib/knife-cloudformation/export.rb +0 -174
Binary file
|
@@ -1,267 +0,0 @@
|
|
1
|
-
require 'fog'
|
2
|
-
require 'knife-cloudformation/utils'
|
3
|
-
require 'knife-cloudformation/cache'
|
4
|
-
|
5
|
-
Dir.glob(File.join(File.dirname(__FILE__), 'aws_commons/*.rb')).each do |item|
|
6
|
-
require "knife-cloudformation/aws_commons/#{File.basename(item).sub('.rb', '')}"
|
7
|
-
end
|
8
|
-
|
9
|
-
module KnifeCloudformation
|
10
|
-
class AwsCommons
|
11
|
-
|
12
|
-
class << self
|
13
|
-
def logger=(l)
|
14
|
-
@logger = l
|
15
|
-
end
|
16
|
-
|
17
|
-
def logger
|
18
|
-
unless(@logger)
|
19
|
-
require 'logger'
|
20
|
-
@logger = Logger.new($stdout)
|
21
|
-
@logger.level = ENV['DEBUG'] ? Logger::DEBUG : Logger::INFO
|
22
|
-
end
|
23
|
-
@logger
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
include KnifeCloudformation::Utils::AnimalStrings
|
28
|
-
include KnifeCloudformation::Utils::Debug
|
29
|
-
|
30
|
-
FOG_MAP = {
|
31
|
-
:ec2 => :compute
|
32
|
-
}
|
33
|
-
|
34
|
-
attr_reader :credentials
|
35
|
-
|
36
|
-
def initialize(args={})
|
37
|
-
@ui = args[:ui]
|
38
|
-
@credentials = @creds = args[:fog]
|
39
|
-
@disconnect_long_jobs = args[:disconnect_long_jobs]
|
40
|
-
@throttled = []
|
41
|
-
@connections = {}
|
42
|
-
@memo = Cache.new(credentials)
|
43
|
-
@local = {:stacks => {}}
|
44
|
-
end
|
45
|
-
|
46
|
-
def logger
|
47
|
-
@logger || self.class.logger
|
48
|
-
end
|
49
|
-
|
50
|
-
def logger=(l)
|
51
|
-
@logger = l
|
52
|
-
end
|
53
|
-
|
54
|
-
def cache
|
55
|
-
@memo
|
56
|
-
end
|
57
|
-
|
58
|
-
def clear_cache(*types)
|
59
|
-
@memo.clear!(*types)
|
60
|
-
true
|
61
|
-
end
|
62
|
-
|
63
|
-
def build_connection(type)
|
64
|
-
type = type.to_sym
|
65
|
-
type = FOG_MAP[type] if FOG_MAP[type]
|
66
|
-
unless(@connections[type])
|
67
|
-
case type
|
68
|
-
when :compute
|
69
|
-
@connections[:compute] = Fog::Compute::AWS.new(@creds)
|
70
|
-
when :dns
|
71
|
-
dns_creds = @creds.dup
|
72
|
-
dns_creds.delete(:region) || dns_creds.delete('region')
|
73
|
-
@connections[:dns] = Fog::DNS::AWS.new(dns_creds)
|
74
|
-
else
|
75
|
-
begin
|
76
|
-
Fog.credentials = Fog.symbolize_credentials(@creds)
|
77
|
-
@connections[type] = Fog::AWS[type]
|
78
|
-
Fog.credentials = {}
|
79
|
-
rescue NameError
|
80
|
-
klass = [camel(type.to_s), 'AWS'].inject(Fog) do |memo, item|
|
81
|
-
memo.const_defined?(item) ? memo.const_get(item) : break
|
82
|
-
end
|
83
|
-
if(klass)
|
84
|
-
@connections[type] = klass.new(Fog.symbolize_credentials(@creds))
|
85
|
-
else
|
86
|
-
raise
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
if(block_given?)
|
92
|
-
throttleable do
|
93
|
-
yield @connections[type]
|
94
|
-
end
|
95
|
-
else
|
96
|
-
@connections[type]
|
97
|
-
end
|
98
|
-
end
|
99
|
-
alias_method :aws, :build_connection
|
100
|
-
|
101
|
-
DEFAULT_STACK_STATUS = %w(
|
102
|
-
CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED
|
103
|
-
ROLLBACK_IN_PROGRESS ROLLBACK_COMPLETE ROLLBACK_FAILED
|
104
|
-
UPDATE_IN_PROGRESS UPDATE_COMPLETE UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
|
105
|
-
UPDATE_ROLLBACK_IN_PROGRESS UPDATE_ROLLBACK_FAILED
|
106
|
-
UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_ROLLBACK_COMPLETE
|
107
|
-
DELETE_IN_PROGRESS DELETE_FAILED
|
108
|
-
)
|
109
|
-
|
110
|
-
def stacks(args={})
|
111
|
-
status = Array(args[:status] || DEFAULT_STACK_STATUS).flatten.compact.map do |stat|
|
112
|
-
stat.to_s.upcase
|
113
|
-
end
|
114
|
-
@memo.init(:stacks_lock, :lock)
|
115
|
-
@memo.init(:stacks, :stamped)
|
116
|
-
if(args[:cache_time])
|
117
|
-
@memo[:stacks].stamp
|
118
|
-
else
|
119
|
-
if(args[:refresh_every])
|
120
|
-
cache.apply_limit(:stacks, args[:refresh_every].to_i)
|
121
|
-
end
|
122
|
-
if(@memo[:stacks].update_allowed? || args[:force_refresh])
|
123
|
-
long_running_job(:stacks) do
|
124
|
-
logger.debug 'Populating full cloudformation list from remote end point'
|
125
|
-
stack_result = throttleable do
|
126
|
-
aws(:cloud_formation).describe_stacks.body['Stacks']
|
127
|
-
end
|
128
|
-
if(stack_result)
|
129
|
-
@memo[:stacks_lock].lock do
|
130
|
-
@memo[:stacks].value = stack_result
|
131
|
-
end
|
132
|
-
end
|
133
|
-
logger.debug 'Full cloudformation list from remote end point complete'
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
if(@memo[:stacks].value)
|
138
|
-
@memo[:stacks].value.find_all do |s|
|
139
|
-
status.include?(s['StackStatus'])
|
140
|
-
end
|
141
|
-
else
|
142
|
-
[]
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
def throttleable
|
147
|
-
if(@throttled.size > 0)
|
148
|
-
if(Time.now.to_i - @throttled.last < Time.now.to_i - @throttled.size * 15)
|
149
|
-
logger.error "Currently being throttled. Not running request!"
|
150
|
-
return nil
|
151
|
-
end
|
152
|
-
end
|
153
|
-
begin
|
154
|
-
result = yield
|
155
|
-
@throttled.clear
|
156
|
-
result
|
157
|
-
rescue Fog::Service::Error => e
|
158
|
-
if(e.message == 'Throttling => Rate exceeded')
|
159
|
-
logger.error "Remote end point is is currently throttling. Rate has been exceeded."
|
160
|
-
@throttled << Time.now.to_i
|
161
|
-
end
|
162
|
-
nil
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
def long_running_job(name)
|
167
|
-
if(@disconnect_long_jobs)
|
168
|
-
logger.debug "Disconnected long running jobs enabled. Starting: #{name}"
|
169
|
-
lock_key = "long_jobs_lock_#{name}".to_sym
|
170
|
-
@memo.init(lock_key, :lock)
|
171
|
-
Thread.new do
|
172
|
-
begin
|
173
|
-
@memo[lock_key].lock do
|
174
|
-
begin
|
175
|
-
logger.info "Long running job started disconnected (#{name})"
|
176
|
-
yield
|
177
|
-
rescue => e
|
178
|
-
logger.error "Long running job failure (#{name}): #{e.class} - #{e}\n#{e.backtrace.join("\n")}"
|
179
|
-
end
|
180
|
-
end
|
181
|
-
rescue Redis::Lock::Timeout
|
182
|
-
logger.warn "Long running process failed to aquire lock. Request not run (#{name})"
|
183
|
-
end
|
184
|
-
end
|
185
|
-
else
|
186
|
-
logger.debug "Disconnected long running jobs disabled. Starting #{name} inline"
|
187
|
-
yield
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def name_from_stack_id(s_id)
|
192
|
-
found = stacks.detect do |s|
|
193
|
-
s['StackId'] == s_id
|
194
|
-
end
|
195
|
-
found ? found['StackName'] : raise(IndexError.new("Failed to locate stack with ID: #{s_id}"))
|
196
|
-
end
|
197
|
-
|
198
|
-
def id_from_stack_name(name)
|
199
|
-
found = stacks.detect do |s|
|
200
|
-
s['StackName'] == name
|
201
|
-
end
|
202
|
-
found ? found['StackId'] : raise(IndexError.new("Failed to locate stack with name: #{name}"))
|
203
|
-
end
|
204
|
-
|
205
|
-
def stack(*names)
|
206
|
-
direct_load = names.delete(:ignore_seeds)
|
207
|
-
result = names.map do |name|
|
208
|
-
[name, name.start_with?('arn:') || direct_load ? name : id_from_stack_name(name)]
|
209
|
-
end.map do |name, s_id|
|
210
|
-
unless(@local[:stacks][s_id])
|
211
|
-
unless(direct_load)
|
212
|
-
seed = stacks.detect do |stk|
|
213
|
-
stk['StackId'] == s_id
|
214
|
-
end
|
215
|
-
end
|
216
|
-
if(seed)
|
217
|
-
logger.debug "Requested stack (#{name}) loaded via cached seed"
|
218
|
-
else
|
219
|
-
logger.debug "Requested stack (#{name}) loaded directly with no seed"
|
220
|
-
end
|
221
|
-
@local[:stacks][s_id] = Stack.new(name, self, seed)
|
222
|
-
end
|
223
|
-
@local[:stacks][s_id]
|
224
|
-
end
|
225
|
-
result.size == 1 ? result.first : result
|
226
|
-
end
|
227
|
-
|
228
|
-
def create_stack(name, definition)
|
229
|
-
Stack.create(name, definition, self)
|
230
|
-
end
|
231
|
-
|
232
|
-
# Output Helpers
|
233
|
-
|
234
|
-
def process(things, args={})
|
235
|
-
@event_ids ||= []
|
236
|
-
processed = things.reverse.map do |thing|
|
237
|
-
next if @event_ids.include?(thing['EventId'])
|
238
|
-
@event_ids.push(thing['EventId']).compact!
|
239
|
-
if(args[:attributes])
|
240
|
-
args[:attributes].map do |key|
|
241
|
-
thing[key].to_s
|
242
|
-
end
|
243
|
-
else
|
244
|
-
thing.values
|
245
|
-
end
|
246
|
-
end
|
247
|
-
args[:flat] ? processed.flatten : processed
|
248
|
-
end
|
249
|
-
|
250
|
-
def get_titles(thing, args={})
|
251
|
-
attrs = args[:attributes] || []
|
252
|
-
if(attrs.empty?)
|
253
|
-
hash = thing.is_a?(Array) ? thing.first : thing
|
254
|
-
hash ||= {}
|
255
|
-
attrs = hash.keys
|
256
|
-
end
|
257
|
-
titles = attrs.map do |key|
|
258
|
-
key.gsub(/([a-z])([A-Z])/, '\1 \2')
|
259
|
-
end.compact
|
260
|
-
if(args[:format])
|
261
|
-
titles.map{|s| @ui.color(s, :bold)}
|
262
|
-
else
|
263
|
-
titles
|
264
|
-
end
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
@@ -1,435 +0,0 @@
|
|
1
|
-
require 'knife-cloudformation/cache'
|
2
|
-
require 'knife-cloudformation/aws_commons'
|
3
|
-
require 'digest/sha2'
|
4
|
-
|
5
|
-
module KnifeCloudformation
|
6
|
-
class AwsCommons
|
7
|
-
class Stack
|
8
|
-
|
9
|
-
include KnifeCloudformation::Utils::Debug
|
10
|
-
include KnifeCloudformation::Utils::JSON
|
11
|
-
include KnifeCloudformation::Utils::AnimalStrings
|
12
|
-
|
13
|
-
attr_reader :name, :raw_stack, :raw_resources, :common
|
14
|
-
|
15
|
-
class << self
|
16
|
-
|
17
|
-
ALLOWED_PARAMETER_ATTRIBUTES = %w(
|
18
|
-
Type Default NoEcho AllowedValues AllowedPattern
|
19
|
-
MaxLength MinLength MaxValue MinValue Description
|
20
|
-
ConstraintDescription
|
21
|
-
)
|
22
|
-
|
23
|
-
include KnifeCloudformation::Utils::JSON
|
24
|
-
|
25
|
-
def create(name, definition, aws_common)
|
26
|
-
aws_common.aws(:cloud_formation).create_stack(name, definition)
|
27
|
-
new(name, aws_common)
|
28
|
-
end
|
29
|
-
|
30
|
-
def build_stack_definition(template, options={})
|
31
|
-
stack = Mash.new
|
32
|
-
options.each do |key, value|
|
33
|
-
format_key = key.to_s.split('_').map do |k|
|
34
|
-
"#{k.slice(0,1).upcase}#{k.slice(1,k.length)}"
|
35
|
-
end.join
|
36
|
-
stack[format_key] = value
|
37
|
-
end
|
38
|
-
enable_capabilities!(stack, template)
|
39
|
-
clean_parameters!(template)
|
40
|
-
stack['TemplateBody'] = _to_json(template)
|
41
|
-
stack
|
42
|
-
end
|
43
|
-
|
44
|
-
# Currently only checking for IAM resources since that's all
|
45
|
-
# that is supported for creation
|
46
|
-
def enable_capabilities!(stack, template)
|
47
|
-
found = Array(template['Resources']).detect do |resource_name, resource|
|
48
|
-
resource['Type'].start_with?('AWS::IAM')
|
49
|
-
end
|
50
|
-
stack['Capabilities'] = ['CAPABILITY_IAM'] if found
|
51
|
-
nil
|
52
|
-
end
|
53
|
-
|
54
|
-
def clean_parameters!(template)
|
55
|
-
template['Parameters'].each do |name, options|
|
56
|
-
options.delete_if do |attribute, value|
|
57
|
-
!ALLOWED_PARAMETER_ATTRIBUTES.include?(attribute)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
|
-
def initialize(name, common, raw_stack=nil)
|
65
|
-
@name = name
|
66
|
-
@common = common
|
67
|
-
@memo = Cache.new(common.credentials.merge(:stack => name))
|
68
|
-
reset_local
|
69
|
-
@memo.init(:raw_stack, :stamped)
|
70
|
-
if(raw_stack)
|
71
|
-
if(@memo[:stacks])
|
72
|
-
if(@memo[:stacks].stamp > @memo[:raw_stack].stamp)
|
73
|
-
@memo[:raw_stack].value = raw_stack
|
74
|
-
end
|
75
|
-
else
|
76
|
-
@memo[:raw_stack].value = raw_stack
|
77
|
-
end
|
78
|
-
end
|
79
|
-
load_stack
|
80
|
-
@force_refresh = false
|
81
|
-
@force_refresh = in_progress?
|
82
|
-
end
|
83
|
-
|
84
|
-
## Actions ##
|
85
|
-
|
86
|
-
def update(definition)
|
87
|
-
if(definition.keys.detect{|k|k.is_a?(Symbol)})
|
88
|
-
definition = format_definition(definition)
|
89
|
-
end
|
90
|
-
res = common.aws(:cloud_formation).update_stack(name, definition)
|
91
|
-
reload!
|
92
|
-
res
|
93
|
-
end
|
94
|
-
|
95
|
-
def format_definition(def_hash)
|
96
|
-
new_hash = {}
|
97
|
-
def_hash.each do |k,v|
|
98
|
-
new_hash[camel(k)] = v
|
99
|
-
end
|
100
|
-
if(new_hash['TemplateBody'].is_a?(Hash))
|
101
|
-
new_hash['TemplateBody'] = _to_json(new_hash['TemplateBody'])
|
102
|
-
end
|
103
|
-
new_hash
|
104
|
-
end
|
105
|
-
|
106
|
-
def destroy
|
107
|
-
res = common.aws(:cloud_formation).delete_stack(name)
|
108
|
-
reload!
|
109
|
-
res
|
110
|
-
end
|
111
|
-
|
112
|
-
def load_stack(*args)
|
113
|
-
@memo.init(:raw_stack, :stamped)
|
114
|
-
begin
|
115
|
-
@memo.init(:raw_stack_lock, :lock)
|
116
|
-
@memo[:raw_stack_lock].lock do
|
117
|
-
if(args.include?(:force) || @memo[:raw_stack].update_allowed?)
|
118
|
-
@memo[:raw_stack].value = common.aws(:cloud_formation)
|
119
|
-
.describe_stacks('StackName' => name)
|
120
|
-
.body['Stacks'].first
|
121
|
-
end
|
122
|
-
end
|
123
|
-
rescue => e
|
124
|
-
if(defined?(Redis) && e.is_a?(Redis::Lock::LockTimeout))
|
125
|
-
# someone else is updating
|
126
|
-
debug 'Got lock timeout on stack load'
|
127
|
-
else
|
128
|
-
raise
|
129
|
-
end
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
def load_resources
|
134
|
-
@memo.init(:raw_resources, :stamped)
|
135
|
-
begin
|
136
|
-
@memo.init(:raw_resources_lock, :lock)
|
137
|
-
@memo[:raw_resources_lock].lock do
|
138
|
-
if(@memo[:raw_resources].update_allowed?)
|
139
|
-
@memo[:raw_resources].value = common.aws(:cloud_formation)
|
140
|
-
.describe_stack_resources('StackName' => name)
|
141
|
-
.body['StackResources']
|
142
|
-
end
|
143
|
-
end
|
144
|
-
rescue => e
|
145
|
-
if(defined?(Redis) && e.is_a?(Redis::Lock::LockTimeout))
|
146
|
-
debug 'Got lock timeout on resource load'
|
147
|
-
else
|
148
|
-
raise e
|
149
|
-
end
|
150
|
-
end
|
151
|
-
end
|
152
|
-
|
153
|
-
def refresh?(bool=nil)
|
154
|
-
bool || (bool.nil? && @force_refresh)
|
155
|
-
end
|
156
|
-
|
157
|
-
def reset_local
|
158
|
-
@local = {
|
159
|
-
:nodes => []
|
160
|
-
}
|
161
|
-
end
|
162
|
-
|
163
|
-
def reload!
|
164
|
-
@memo.clear! do
|
165
|
-
load_stack(:force)
|
166
|
-
load_resources
|
167
|
-
@force_refresh = in_progress?
|
168
|
-
end
|
169
|
-
true
|
170
|
-
end
|
171
|
-
|
172
|
-
## Information ##
|
173
|
-
|
174
|
-
def serialize
|
175
|
-
_to_json(to_hash)
|
176
|
-
end
|
177
|
-
|
178
|
-
def to_hash(extra_data={})
|
179
|
-
{
|
180
|
-
:template_body => template,
|
181
|
-
:parameters => parameters,
|
182
|
-
:capabilities => capabilities,
|
183
|
-
:disable_rollback => disable_rollback,
|
184
|
-
:notification_ARNs => notification_arns,
|
185
|
-
:timeout_in_minutes => timeout
|
186
|
-
}.merge(extra_data)
|
187
|
-
end
|
188
|
-
|
189
|
-
def template
|
190
|
-
@memo.init(:template, :value)
|
191
|
-
unless(@memo[:template].value)
|
192
|
-
@memo[:template].value = _from_json(
|
193
|
-
common.aws(:cloud_formation)
|
194
|
-
.get_template(name).body['TemplateBody']
|
195
|
-
)
|
196
|
-
end
|
197
|
-
@memo[:template].value
|
198
|
-
end
|
199
|
-
|
200
|
-
## Stack metadata ##
|
201
|
-
def parameters(raw=false)
|
202
|
-
if(raw)
|
203
|
-
@memo[:raw_stack].value['Parameters']
|
204
|
-
else
|
205
|
-
@memo.init(:parameters, :value)
|
206
|
-
unless(@memo[:parameters].value)
|
207
|
-
@memo[:parameters].value = Hash[*(
|
208
|
-
@memo[:raw_stack].value['Parameters'].map do |ary|
|
209
|
-
[ary['ParameterKey'], ary['ParameterValue']]
|
210
|
-
end.flatten
|
211
|
-
)]
|
212
|
-
end
|
213
|
-
@memo[:parameters].value
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def capabilities
|
218
|
-
@memo[:raw_stack].value['Capabilities']
|
219
|
-
end
|
220
|
-
|
221
|
-
def disable_rollback
|
222
|
-
@memo[:raw_stack].value['DisableRollback']
|
223
|
-
end
|
224
|
-
|
225
|
-
def notification_arns
|
226
|
-
@memo[:raw_stack].value['NotificationARNs']
|
227
|
-
end
|
228
|
-
|
229
|
-
def timeout_in_minutes
|
230
|
-
@memo[:raw_stack].value['TimeoutInMinutes']
|
231
|
-
end
|
232
|
-
alias_method :timeout, :timeout_in_minutes
|
233
|
-
|
234
|
-
def stack_id
|
235
|
-
@memo[:raw_stack].value['StackId']
|
236
|
-
end
|
237
|
-
alias_method :id, :stack_id
|
238
|
-
|
239
|
-
def creation_time
|
240
|
-
@memo[:raw_stack].value['CreationTime']
|
241
|
-
end
|
242
|
-
alias_method :created_at, :creation_time
|
243
|
-
|
244
|
-
def status(force_refresh=nil)
|
245
|
-
load_stack if refresh?(force_refresh)
|
246
|
-
@memo[:raw_stack].value['StackStatus']
|
247
|
-
end
|
248
|
-
|
249
|
-
def resources(force_refresh=nil)
|
250
|
-
load_resources if @memo[:raw_resources].nil? || refresh?(force_refresh)
|
251
|
-
@memo[:raw_resources].value
|
252
|
-
end
|
253
|
-
|
254
|
-
def events(all=false)
|
255
|
-
@memo.init(:events, :stamped)
|
256
|
-
res = []
|
257
|
-
if(@memo[:events].value.nil? || refresh?)
|
258
|
-
begin
|
259
|
-
@memo.init(:events_lock, :lock)
|
260
|
-
@memo[:events_lock].lock do
|
261
|
-
if(@memo[:events].update_allowed?)
|
262
|
-
res = common.aws(:cloud_formation).describe_stack_events(name).body['StackEvents']
|
263
|
-
current = @memo[:events].value || []
|
264
|
-
current_events = current.map{|e| e['EventId']}
|
265
|
-
res.delete_if{|e| current_events.include?(e['EventId'])}
|
266
|
-
current += res
|
267
|
-
current.uniq!
|
268
|
-
current.sort!{|x,y| x['Timestamp'] <=> y['Timestamp']}
|
269
|
-
@memo[:events].value = current
|
270
|
-
end
|
271
|
-
end
|
272
|
-
rescue => e
|
273
|
-
if(defined?(Redis) && e.is_a?(Redis::Lock::LockTimeout))
|
274
|
-
debug 'Got lock timeout on events'
|
275
|
-
else
|
276
|
-
raise
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
280
|
-
all ? @memo[:events].value : res
|
281
|
-
end
|
282
|
-
|
283
|
-
def outputs(style=:unformatted)
|
284
|
-
case style
|
285
|
-
when :formatted
|
286
|
-
Hash[*(
|
287
|
-
@memo[:raw_stack].value['Outputs'].map do |item|
|
288
|
-
[item['OutputKey'].gsub(/(?<![A-Z])([A-Z])/, '_\1').sub(/^_/, '').downcase.to_sym, item['OutputValue']]
|
289
|
-
end.flatten
|
290
|
-
)]
|
291
|
-
when :unformatted
|
292
|
-
Hash[*(
|
293
|
-
@memo[:raw_stack].value['Outputs'].map do |item|
|
294
|
-
[item['OutputKey'], item['OutputValue']]
|
295
|
-
end.flatten
|
296
|
-
)]
|
297
|
-
else
|
298
|
-
@memo[:raw_stack].value['Outputs']
|
299
|
-
end
|
300
|
-
end
|
301
|
-
|
302
|
-
def event_start_index(given_events, status)
|
303
|
-
Array(given_events).flatten.compact.rindex do |e|
|
304
|
-
e['ResourceType'] == 'AWS::CloudFormation::Stack' &&
|
305
|
-
e['ResourceStatus'] == status.to_s.upcase
|
306
|
-
end.to_i
|
307
|
-
end
|
308
|
-
|
309
|
-
# min:: do not return value lower than this (defaults to 5)
|
310
|
-
# Returns Numeric < 100 to represent completed resources
|
311
|
-
# percentage (never returns less than 5)
|
312
|
-
def percent_complete(min=5)
|
313
|
-
if(complete?)
|
314
|
-
100
|
315
|
-
else
|
316
|
-
all_events = events(:all)
|
317
|
-
if(all_events)
|
318
|
-
total_expected = template['Resources'].size
|
319
|
-
action = performing
|
320
|
-
start = event_start_index(all_events, "#{action}_in_progress".to_sym)
|
321
|
-
finished = all_events.find_all do |e|
|
322
|
-
e['ResourceStatus'] == "#{action}_complete".upcase ||
|
323
|
-
e['ResourceStatus'] == "#{action}_failed".upcase
|
324
|
-
end.size
|
325
|
-
calculated = ((finished / total_expected.to_f) * 100).to_i
|
326
|
-
calculated < min ? min : calculated
|
327
|
-
else
|
328
|
-
100 # Assume deletion and no events == complete
|
329
|
-
end
|
330
|
-
end
|
331
|
-
end
|
332
|
-
|
333
|
-
## State ##
|
334
|
-
|
335
|
-
def in_progress?
|
336
|
-
status.to_s.downcase.end_with?('in_progress')
|
337
|
-
end
|
338
|
-
|
339
|
-
def complete?
|
340
|
-
stat = status.to_s.downcase
|
341
|
-
stat.end_with?('complete') || stat.end_with?('failed')
|
342
|
-
end
|
343
|
-
|
344
|
-
def failed?
|
345
|
-
stat = status.to_s.downcase
|
346
|
-
stat.end_with?('failed') || (stat.include?('rollback') && stat.end_with?('complete'))
|
347
|
-
end
|
348
|
-
|
349
|
-
def success?
|
350
|
-
!failed? && complete?
|
351
|
-
end
|
352
|
-
|
353
|
-
def creating?
|
354
|
-
in_progress? && status.to_s.downcase.start_with?('create')
|
355
|
-
end
|
356
|
-
|
357
|
-
def deleting?
|
358
|
-
in_progress? && status.to_s.downcase.start_with?('delete')
|
359
|
-
end
|
360
|
-
|
361
|
-
def updating?
|
362
|
-
in_progress? && status.to_s.downcase.start_with?('update')
|
363
|
-
end
|
364
|
-
|
365
|
-
def rollbacking?
|
366
|
-
in_progress? && status.to_s.downcase.start_with?('rollback')
|
367
|
-
end
|
368
|
-
|
369
|
-
def performing
|
370
|
-
if(in_progress?)
|
371
|
-
status.to_s.downcase.split('_').first.to_sym
|
372
|
-
end
|
373
|
-
end
|
374
|
-
|
375
|
-
# Lets build in some color coding!
|
376
|
-
def red?
|
377
|
-
failed? || deleting?
|
378
|
-
end
|
379
|
-
|
380
|
-
def yellow?
|
381
|
-
!red? && !green?
|
382
|
-
end
|
383
|
-
|
384
|
-
def green?
|
385
|
-
success? || creating? || updating?
|
386
|
-
end
|
387
|
-
|
388
|
-
## Fog instance helpers ##
|
389
|
-
|
390
|
-
RESOURCE_FILTER_KEYS = {
|
391
|
-
:auto_scaling_group => 'AutoScalingGroupNames'
|
392
|
-
}
|
393
|
-
|
394
|
-
def expand_resource(resource)
|
395
|
-
kind = resource['ResourceType'].split('::')[1]
|
396
|
-
kind_snake = snake(kind)
|
397
|
-
aws = common.aws(kind_snake)
|
398
|
-
aws.send("#{snake(resource['ResourceType'].split('::').last).to_s.split('_').last}s").get(resource['PhysicalResourceId'])
|
399
|
-
end
|
400
|
-
|
401
|
-
def nodes
|
402
|
-
if(@local[:nodes].empty?)
|
403
|
-
as_resources = resources.find_all do |r|
|
404
|
-
r['ResourceType'] == 'AWS::AutoScaling::AutoScalingGroup'
|
405
|
-
end
|
406
|
-
value = as_resources.map do |as_resource|
|
407
|
-
as_group = expand_resource(as_resource)
|
408
|
-
as_group.instances.map do |inst|
|
409
|
-
common.aws(:ec2).servers.get(inst.id)
|
410
|
-
end
|
411
|
-
end.flatten
|
412
|
-
@local[:nodes] = value unless in_progress?
|
413
|
-
end
|
414
|
-
value || @local[:nodes]
|
415
|
-
end
|
416
|
-
|
417
|
-
def nodes_data(*args)
|
418
|
-
cache_key = ['nd', name, Digest::SHA256.hexdigest(args.map(&:to_s).join)].join('_')
|
419
|
-
@memo.init(cache_key, :value)
|
420
|
-
unless(@memo[cache_key].value)
|
421
|
-
data = nodes.map do |n|
|
422
|
-
[:id, args].flatten.compact.map do |k|
|
423
|
-
n.send(k)
|
424
|
-
end
|
425
|
-
end
|
426
|
-
end
|
427
|
-
unless(data.empty?)
|
428
|
-
@memo[cache_key].value = data unless in_progress?
|
429
|
-
end
|
430
|
-
data || @memo[cache_key].value
|
431
|
-
end
|
432
|
-
|
433
|
-
end
|
434
|
-
end
|
435
|
-
end
|