cuffsert 0.14.2 → 0.15.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/.github/workflows/pr.yaml +24 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/README.md +2 -0
- data/cuffsert.gemspec +6 -3
- data/cuffsert.gif +0 -0
- data/examples/imap.yaml +28 -0
- data/lib/cuffsert/main.rb +1 -0
- data/lib/cuffsert/messages.rb +2 -0
- data/lib/cuffsert/metadata.rb +1 -1
- data/lib/cuffsert/presenters.rb +0 -274
- data/lib/cuffsert/rendering.rb +349 -0
- data/lib/cuffsert/rxs3client.rb +1 -0
- data/lib/cuffsert/version.rb +1 -1
- metadata +58 -14
- data/.travis.yml +0 -6
- data/Gemfile.lock +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 236e1cb4d9e2ff028feb9d12f49c58359bb91ae5f8d95c4edf3ba3d2ab05a1d5
|
4
|
+
data.tar.gz: 251ce8c81341d12bb08a10608d8efaa1f959c0c38249000af7a7c212565ca973
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 954b06e2dbfc905e20dceb1d0db2aa782cdcf06a2f338b372c6721150be9eab5cc939d358af57871824a983b0034c5c849fb9a8c1187dea4076bc480aa3df1ef
|
7
|
+
data.tar.gz: 4b609f85662b032e9b25c0606b3cf58bfd2ce9ca26fb0d71d4916b8100e4258e4482fd23d17b607d168e7260c5b5f9330f7221e4496a43a040e3393f4396ca5d
|
@@ -0,0 +1,24 @@
|
|
1
|
+
name: Test
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches: [ master ]
|
6
|
+
pull_request:
|
7
|
+
branches: [ master ]
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
strategy:
|
13
|
+
matrix:
|
14
|
+
ruby-version: [ "3.4.1", "2.6.7" ]
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v4
|
17
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
18
|
+
uses: ruby/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: ${{ matrix.ruby-version }}
|
21
|
+
- name: Install dependencies
|
22
|
+
run: bundle install
|
23
|
+
- name: Run tests
|
24
|
+
run: bundle exec rspec
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.4.1
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
The primary goal of cuffsert is to provide a quick "up-arrow-enter" loading of a CloudFormation stack with good feedback, removing the need to click through three pesky screens each time. It figures out whether the stack needs to be created or rolled-back and whether it needs to be deleted first.
|
4
4
|
|
5
|
+

|
6
|
+
|
5
7
|
## Getting started
|
6
8
|
|
7
9
|
Update a stack from a provided template without changing any parameters on the stack:
|
data/cuffsert.gemspec
CHANGED
@@ -15,15 +15,18 @@ Gem::Specification.new do |spec|
|
|
15
15
|
|
16
16
|
spec.required_ruby_version = '>= 2.0.0'
|
17
17
|
|
18
|
-
spec.add_runtime_dependency 'aws-sdk-cloudformation', '~> 1.
|
19
|
-
spec.add_runtime_dependency 'aws-sdk-s3', '~> 1.
|
18
|
+
spec.add_runtime_dependency 'aws-sdk-cloudformation', '~> 1.67'
|
19
|
+
spec.add_runtime_dependency 'aws-sdk-s3', '~> 1.112'
|
20
20
|
spec.add_runtime_dependency 'colorize'
|
21
21
|
spec.add_runtime_dependency 'hashdiff', '~> 1.0'
|
22
|
+
spec.add_runtime_dependency 'nokogiri'
|
22
23
|
spec.add_runtime_dependency 'ruby-termios'
|
23
24
|
spec.add_runtime_dependency 'rx'
|
24
25
|
|
25
|
-
spec.add_development_dependency '
|
26
|
+
spec.add_development_dependency 'base64'
|
27
|
+
spec.add_development_dependency 'bundler', '~> 2.3'
|
26
28
|
spec.add_development_dependency 'byebug'
|
29
|
+
spec.add_development_dependency 'logger'
|
27
30
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
28
31
|
spec.add_development_dependency 'rx-rspec', '~> 0.4.3'
|
29
32
|
spec.add_development_dependency 'simplecov'
|
data/cuffsert.gif
ADDED
Binary file
|
data/examples/imap.yaml
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
AWSTemplateFormatVersion: 2010-09-09
|
2
|
+
Resources:
|
3
|
+
ImapServer:
|
4
|
+
Type: AWS::AutoScaling::AutoScalingGroup
|
5
|
+
Properties:
|
6
|
+
AutoScalingGroupName: imap-server
|
7
|
+
LaunchConfigurationName: !Ref ImapConfig
|
8
|
+
MinSize: 0
|
9
|
+
MaxSize: 1
|
10
|
+
VPCZoneIdentifier:
|
11
|
+
- subnet-f8597791
|
12
|
+
ImapConfig:
|
13
|
+
Type: AWS::AutoScaling::LaunchConfiguration
|
14
|
+
Properties:
|
15
|
+
ImageId: ami-0c6b1d09930fac512
|
16
|
+
SecurityGroups:
|
17
|
+
- !Ref ImapSG
|
18
|
+
InstanceType: t2.nano
|
19
|
+
ImapSG:
|
20
|
+
Type: AWS::EC2::SecurityGroup
|
21
|
+
Properties:
|
22
|
+
VpcId: vpc-fc702695
|
23
|
+
GroupDescription: Global IMAPS access
|
24
|
+
SecurityGroupIngress:
|
25
|
+
- CidrIp: 0.0.0.0/0
|
26
|
+
IpProtocol: tcp
|
27
|
+
FromPort: 993
|
28
|
+
ToPort: 993
|
data/lib/cuffsert/main.rb
CHANGED
data/lib/cuffsert/messages.rb
CHANGED
data/lib/cuffsert/metadata.rb
CHANGED
@@ -84,7 +84,7 @@ module CuffSert
|
|
84
84
|
|
85
85
|
def self.meta_defaults(cli_args)
|
86
86
|
stack_path = (cli_args[:stack_path] || [])[0]
|
87
|
-
if stack_path && File.
|
87
|
+
if stack_path && File.exist?(stack_path)
|
88
88
|
nil_params = CuffBase.empty_from_template(open(stack_path))
|
89
89
|
else
|
90
90
|
nil_params = {}
|
data/lib/cuffsert/presenters.rb
CHANGED
@@ -1,18 +1,7 @@
|
|
1
1
|
require 'aws-sdk-cloudformation'
|
2
|
-
require 'colorize'
|
3
2
|
require 'cuffsert/cfstates'
|
4
3
|
require 'cuffsert/errors'
|
5
4
|
require 'cuffsert/messages'
|
6
|
-
require 'hashdiff'
|
7
|
-
require 'rx'
|
8
|
-
|
9
|
-
# TODO: Animate in-progress states
|
10
|
-
# - Present the error message in change_set properly - and abort
|
11
|
-
# - badness goes to stderr
|
12
|
-
# - change sets should present modification details indented under each entry
|
13
|
-
# - property direct modification
|
14
|
-
# - properties through parameter change
|
15
|
-
# - indirect change through other resource ("causing_entity": "Lb.DNSName")
|
16
5
|
|
17
6
|
module CuffSert
|
18
7
|
class BasePresenter
|
@@ -142,267 +131,4 @@ module CuffSert
|
|
142
131
|
@index.clear
|
143
132
|
end
|
144
133
|
end
|
145
|
-
|
146
|
-
class BaseRenderer
|
147
|
-
def initialize(output = STDOUT, error = STDERR, options = {})
|
148
|
-
@output = output
|
149
|
-
@error = error
|
150
|
-
@verbosity = options[:verbosity] || 1
|
151
|
-
end
|
152
|
-
|
153
|
-
def templates(current, pending) ; end
|
154
|
-
def change_set(change_set) ; end
|
155
|
-
def event(event, resource) ; end
|
156
|
-
def clear ; end
|
157
|
-
def resource(resource) ; end
|
158
|
-
def report(message) ; end
|
159
|
-
def abort(message) ; end
|
160
|
-
def done(event) ; end
|
161
|
-
end
|
162
|
-
|
163
|
-
class JsonRenderer < BaseRenderer
|
164
|
-
def templates(current, pending)
|
165
|
-
if @verbosity >= 1
|
166
|
-
@output.write(current.to_json)
|
167
|
-
@output.write(pending.to_json)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
def change_set(change_set)
|
172
|
-
@output.write(change_set.to_h.to_json) unless @verbosity < 1
|
173
|
-
end
|
174
|
-
|
175
|
-
def event(event, resource)
|
176
|
-
@output.write(event.to_h.to_json) unless @verbosity < 1
|
177
|
-
end
|
178
|
-
|
179
|
-
def stack(event, stack)
|
180
|
-
@output.write(stack.to_json) unless @verbosity < 1
|
181
|
-
end
|
182
|
-
|
183
|
-
def report(event)
|
184
|
-
@output.write(event.message + "\n") unless @verbosity < 2
|
185
|
-
end
|
186
|
-
|
187
|
-
def abort(event)
|
188
|
-
@error.write(event.message + "\n") unless @verbosity < 1
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
ACTION_ORDER = ['Add', 'Modify', 'Replace?', 'Replace!', 'Remove']
|
193
|
-
|
194
|
-
class ProgressbarRenderer < BaseRenderer
|
195
|
-
def change_set(change_set)
|
196
|
-
@output.write(sprintf("Updating stack %s\n", change_set[:stack_name]))
|
197
|
-
change_set[:changes].sort do |l, r|
|
198
|
-
lr = l[:resource_change]
|
199
|
-
rr = r[:resource_change]
|
200
|
-
[
|
201
|
-
ACTION_ORDER.index(action(lr)),
|
202
|
-
lr[:logical_resource_id]
|
203
|
-
] <=> [
|
204
|
-
ACTION_ORDER.index(action(rr)),
|
205
|
-
rr[:logical_resource_id]
|
206
|
-
]
|
207
|
-
end.map do |change|
|
208
|
-
rc = change[:resource_change]
|
209
|
-
sprintf("%s[%s] %-10s %s\n%s",
|
210
|
-
rc[:logical_resource_id],
|
211
|
-
rc[:resource_type],
|
212
|
-
action_color(action(rc)),
|
213
|
-
scope_desc(rc),
|
214
|
-
change_details(rc)
|
215
|
-
)
|
216
|
-
end.each { |row| @output.write(row) }
|
217
|
-
end
|
218
|
-
|
219
|
-
def action(rc)
|
220
|
-
if rc[:action] == 'Modify'
|
221
|
-
if ['True', 'Always'].include?(rc[:replacement])
|
222
|
-
'Replace!'
|
223
|
-
elsif ['False', 'Never'].include?(rc[:replacement])
|
224
|
-
'Modify'
|
225
|
-
elsif rc[:replacement] == 'Conditional'
|
226
|
-
'Replace?'
|
227
|
-
else
|
228
|
-
"#{rc[:action]}/#{rc[:replacement]}"
|
229
|
-
end
|
230
|
-
else
|
231
|
-
rc[:action]
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
def action_color(action)
|
236
|
-
action.colorize(
|
237
|
-
case action
|
238
|
-
when 'Add' then :green
|
239
|
-
when 'Modify' then :yellow
|
240
|
-
else :red
|
241
|
-
end
|
242
|
-
)
|
243
|
-
end
|
244
|
-
|
245
|
-
def scope_desc(rc)
|
246
|
-
(rc[:scope] || []).map do |scope|
|
247
|
-
case scope
|
248
|
-
when 'Properties'
|
249
|
-
properties = rc[:details]
|
250
|
-
.select { |detail| detail[:target][:attribute] == 'Properties' }
|
251
|
-
.map { |detail| detail[:target][:name] }
|
252
|
-
.uniq
|
253
|
-
.join(", ")
|
254
|
-
sprintf("Properties: %s", properties)
|
255
|
-
else
|
256
|
-
rc[:scope]
|
257
|
-
end
|
258
|
-
end
|
259
|
-
.join("; ")
|
260
|
-
end
|
261
|
-
|
262
|
-
def event(event, resource)
|
263
|
-
return if @verbosity == 0
|
264
|
-
return if resource[:states][-1] != :bad && @verbosity <= 1
|
265
|
-
color, _ = interpret_states(resource)
|
266
|
-
message = sprintf('%s %s %s[%s] %s',
|
267
|
-
event[:resource_status],
|
268
|
-
event[:timestamp].strftime('%H:%M:%S%z'),
|
269
|
-
event[:logical_resource_id],
|
270
|
-
event[:resource_type].sub(/.*::/, ''),
|
271
|
-
event[:resource_status_reason] || ""
|
272
|
-
).colorize(color)
|
273
|
-
@output.write("\r#{message}\n")
|
274
|
-
end
|
275
|
-
|
276
|
-
def stack(event, stack)
|
277
|
-
case event
|
278
|
-
when :create
|
279
|
-
@output.write("Creating stack #{stack}\n")
|
280
|
-
when :recreate
|
281
|
-
message = sprintf(
|
282
|
-
"Deleting and re-creating stack %s",
|
283
|
-
stack[:stack_name]
|
284
|
-
)
|
285
|
-
@output.write(message.colorize(:red) + "\n")
|
286
|
-
else
|
287
|
-
puts event, stack
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def clear
|
292
|
-
@output.write("\r") unless @verbosity < 1
|
293
|
-
end
|
294
|
-
|
295
|
-
def resource(resource)
|
296
|
-
return if @verbosity < 1
|
297
|
-
color, symbol = interpret_states(resource)
|
298
|
-
table = {
|
299
|
-
:check => "+",
|
300
|
-
:tripple_dot => ".", # "\u2026"
|
301
|
-
:cross => "!",
|
302
|
-
:qmark => "?",
|
303
|
-
}
|
304
|
-
|
305
|
-
@output.write(table[symbol].colorize(
|
306
|
-
:color => :white,
|
307
|
-
:background => color
|
308
|
-
))
|
309
|
-
end
|
310
|
-
|
311
|
-
def templates(current, pending)
|
312
|
-
@current_template = current
|
313
|
-
@pending_template = pending
|
314
|
-
@template_changes = Hashdiff.best_diff(current, pending, array_path: true)
|
315
|
-
@template_changes.each {|c| p c} if ENV['CUFFSERT_EXPERIMENTAL']
|
316
|
-
present_changes(extract_changes(@template_changes, 'Conditions'), 'Conditions') unless @verbosity < 1
|
317
|
-
present_changes(extract_changes(@template_changes, 'Parameters'), 'Parameters') unless @verbosity < 1
|
318
|
-
present_changes(extract_changes(@template_changes, 'Mappings'), 'Mappings') unless @verbosity < 1
|
319
|
-
present_changes(extract_changes(@template_changes, 'Outputs'), 'Outputs') unless @verbosity < 1
|
320
|
-
end
|
321
|
-
|
322
|
-
def report(event)
|
323
|
-
@output.write(event.message.colorize(:white) + "\n") unless @verbosity < 2
|
324
|
-
end
|
325
|
-
|
326
|
-
def abort(event)
|
327
|
-
@error.write("\n" + event.message.colorize(:red) + "\n") unless @verbosity < 1
|
328
|
-
end
|
329
|
-
|
330
|
-
def done(event)
|
331
|
-
@output.write(event.message.colorize(:green) + "\n") unless @verbosity < 1
|
332
|
-
end
|
333
|
-
|
334
|
-
private
|
335
|
-
|
336
|
-
def change_details(rc)
|
337
|
-
(rc[:details] || []).flat_map do |detail|
|
338
|
-
target_path = case detail[:target][:attribute]
|
339
|
-
when 'Properties'
|
340
|
-
[rc[:logical_resource_id], detail[:target][:attribute], detail[:target][:name]]
|
341
|
-
when 'Tags'
|
342
|
-
[rc[:logical_resource_id], 'Properties', detail[:target][:attribute]]
|
343
|
-
else
|
344
|
-
nil
|
345
|
-
end
|
346
|
-
extract_changes(@template_changes, 'Resources', *target_path)
|
347
|
-
end
|
348
|
-
.map do |(ch, path, l, r)|
|
349
|
-
format_change(ch, path[3..-1], l, r)
|
350
|
-
end
|
351
|
-
.join
|
352
|
-
end
|
353
|
-
|
354
|
-
def extract_changes(changes, type, *target_path)
|
355
|
-
changes
|
356
|
-
.select {|(_, path, _)| path[0..target_path.size] == [type, *target_path] }
|
357
|
-
.map {|(ch, path, *rest)| [ch, path, *rest] }
|
358
|
-
end
|
359
|
-
|
360
|
-
def present_changes(changes, type)
|
361
|
-
return unless changes.size > 0
|
362
|
-
@output.write("#{type}:\n")
|
363
|
-
changes.each do |(ch, path, l, r)|
|
364
|
-
@output.write(format_change(ch, path, l, r))
|
365
|
-
end
|
366
|
-
end
|
367
|
-
|
368
|
-
def format_change(ch, path, l, r = nil)
|
369
|
-
sprintf("%s %s: %s\n",
|
370
|
-
change_color(ch),
|
371
|
-
path.join('/'),
|
372
|
-
ch == '~' ? "#{l} -> #{r}" : l,
|
373
|
-
)
|
374
|
-
end
|
375
|
-
|
376
|
-
def change_color(ch)
|
377
|
-
ch.colorize(
|
378
|
-
case ch
|
379
|
-
when '-' then :red
|
380
|
-
when '+' then :green
|
381
|
-
when '~' then :yellow
|
382
|
-
else :white
|
383
|
-
end
|
384
|
-
)
|
385
|
-
end
|
386
|
-
|
387
|
-
def interpret_states(resource)
|
388
|
-
case resource[:states]
|
389
|
-
when [:progress]
|
390
|
-
[:yellow, :tripple_dot]
|
391
|
-
when [:good]
|
392
|
-
[:green, :check]
|
393
|
-
when [:bad]
|
394
|
-
[:red, :cross]
|
395
|
-
when [:good, :progress]
|
396
|
-
[:light_white, :tripple_dot]
|
397
|
-
when [:bad, :progress]
|
398
|
-
[:red, :tripple_dot]
|
399
|
-
when [:good, :good], [:bad, :good]
|
400
|
-
[:light_white, :check]
|
401
|
-
when [:good, :bad], [:bad, :bad]
|
402
|
-
[:red, :qmark]
|
403
|
-
else
|
404
|
-
raise "Unexpected :states in #{resource.inspect}"
|
405
|
-
end
|
406
|
-
end
|
407
|
-
end
|
408
134
|
end
|
@@ -0,0 +1,349 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
require 'cuffsert/cfstates'
|
3
|
+
require 'cuffsert/messages'
|
4
|
+
require 'hashdiff'
|
5
|
+
|
6
|
+
module CuffSert
|
7
|
+
class BaseRenderer
|
8
|
+
def initialize(output = STDOUT, error = STDERR, options = {})
|
9
|
+
@output = output
|
10
|
+
@error = error
|
11
|
+
@verbosity = options[:verbosity] || 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def templates(current, pending) ; end
|
15
|
+
def change_set(change_set) ; end
|
16
|
+
def event(event, resource) ; end
|
17
|
+
def clear ; end
|
18
|
+
def resource(resource) ; end
|
19
|
+
def report(message) ; end
|
20
|
+
def abort(message) ; end
|
21
|
+
def done(event) ; end
|
22
|
+
end
|
23
|
+
|
24
|
+
class JsonRenderer < BaseRenderer
|
25
|
+
def templates(current, pending)
|
26
|
+
if @verbosity >= 1
|
27
|
+
@output.write(current.to_json)
|
28
|
+
@output.write(pending.to_json)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def change_set(change_set)
|
33
|
+
@output.write(change_set.to_h.to_json) unless @verbosity < 1
|
34
|
+
end
|
35
|
+
|
36
|
+
def event(event, resource)
|
37
|
+
@output.write(event.to_h.to_json) unless @verbosity < 1
|
38
|
+
end
|
39
|
+
|
40
|
+
def stack(event, stack)
|
41
|
+
@output.write(stack.to_json) unless @verbosity < 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def report(event)
|
45
|
+
@output.write(event.message + "\n") unless @verbosity < 2
|
46
|
+
end
|
47
|
+
|
48
|
+
def abort(event)
|
49
|
+
@error.write(event.message + "\n") unless @verbosity < 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
ACTION_ORDER = ['Add', 'Modify', 'Replace?', 'Replace!', 'Remove']
|
54
|
+
|
55
|
+
class ProgressbarRenderer < BaseRenderer
|
56
|
+
def change_set(change_set)
|
57
|
+
@output.write(sprintf("Updating stack %s\n", change_set[:stack_name]))
|
58
|
+
change_set[:changes].sort do |l, r|
|
59
|
+
lr = l[:resource_change]
|
60
|
+
rr = r[:resource_change]
|
61
|
+
[
|
62
|
+
ACTION_ORDER.index(action(lr)),
|
63
|
+
lr[:logical_resource_id]
|
64
|
+
] <=> [
|
65
|
+
ACTION_ORDER.index(action(rr)),
|
66
|
+
rr[:logical_resource_id]
|
67
|
+
]
|
68
|
+
end.map do |change|
|
69
|
+
rc = change[:resource_change]
|
70
|
+
sprintf("%s[%s] %-10s %s\n%s",
|
71
|
+
rc[:logical_resource_id],
|
72
|
+
rc[:resource_type],
|
73
|
+
action_color(action(rc)),
|
74
|
+
scope_desc(rc),
|
75
|
+
change_details(rc)
|
76
|
+
)
|
77
|
+
end.each { |row| @output.write(row) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def action(rc)
|
81
|
+
if rc[:action] == 'Modify'
|
82
|
+
if ['True', 'Always'].include?(rc[:replacement])
|
83
|
+
'Replace!'
|
84
|
+
elsif ['False', 'Never'].include?(rc[:replacement])
|
85
|
+
'Modify'
|
86
|
+
elsif rc[:replacement] == 'Conditional'
|
87
|
+
'Replace?'
|
88
|
+
else
|
89
|
+
"#{rc[:action]}/#{rc[:replacement]}"
|
90
|
+
end
|
91
|
+
else
|
92
|
+
rc[:action]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def action_color(action)
|
97
|
+
action.colorize(
|
98
|
+
case action
|
99
|
+
when 'Add' then :green
|
100
|
+
when 'Modify' then :yellow
|
101
|
+
else :red
|
102
|
+
end
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
def scope_desc(rc)
|
107
|
+
(rc[:scope] || []).map do |scope|
|
108
|
+
case scope
|
109
|
+
when 'Properties'
|
110
|
+
properties = rc[:details]
|
111
|
+
.select { |detail| detail[:target][:attribute] == 'Properties' }
|
112
|
+
.map { |detail| detail[:target][:name] }
|
113
|
+
.uniq
|
114
|
+
.join(", ")
|
115
|
+
sprintf("Properties: %s", properties)
|
116
|
+
else
|
117
|
+
rc[:scope]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
.join("; ")
|
121
|
+
end
|
122
|
+
|
123
|
+
def event(event, resource)
|
124
|
+
return if @verbosity == 0
|
125
|
+
return if resource[:states][-1] != :bad && @verbosity <= 1
|
126
|
+
color, _ = interpret_states(resource)
|
127
|
+
message = sprintf('%s %s %s[%s] %s',
|
128
|
+
event[:resource_status],
|
129
|
+
event[:timestamp].strftime('%H:%M:%S%z'),
|
130
|
+
event[:logical_resource_id],
|
131
|
+
event[:resource_type].sub(/.*::/, ''),
|
132
|
+
event[:resource_status_reason] || ""
|
133
|
+
).colorize(color)
|
134
|
+
@output.write("\r#{message}\n")
|
135
|
+
end
|
136
|
+
|
137
|
+
def stack(event, stack)
|
138
|
+
case event
|
139
|
+
when :create
|
140
|
+
@output.write("Creating stack #{stack}\n")
|
141
|
+
when :recreate
|
142
|
+
message = sprintf(
|
143
|
+
"Deleting and re-creating stack %s",
|
144
|
+
stack[:stack_name]
|
145
|
+
)
|
146
|
+
@output.write(message.colorize(:red) + "\n")
|
147
|
+
else
|
148
|
+
puts event, stack
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def clear
|
153
|
+
@output.write("\r") unless @verbosity < 1
|
154
|
+
end
|
155
|
+
|
156
|
+
def resource(resource)
|
157
|
+
return if @verbosity < 1
|
158
|
+
color, symbol = interpret_states(resource)
|
159
|
+
table = {
|
160
|
+
:check => "+",
|
161
|
+
:tripple_dot => ".", # "\u2026"
|
162
|
+
:cross => "!",
|
163
|
+
:qmark => "?",
|
164
|
+
}
|
165
|
+
|
166
|
+
@output.write(table[symbol].colorize(
|
167
|
+
:color => :white,
|
168
|
+
:background => color
|
169
|
+
))
|
170
|
+
end
|
171
|
+
|
172
|
+
def templates(current, pending)
|
173
|
+
@current_template = current
|
174
|
+
@pending_template = pending
|
175
|
+
@template_changes = Hashdiff.best_diff(current, pending, array_path: true)
|
176
|
+
@template_changes.each {|c| p c} if ENV['CUFFSERT_EXPERIMENTAL']
|
177
|
+
present_changes(extract_changes(@template_changes, 'Conditions'), 'Conditions') unless @verbosity < 1
|
178
|
+
present_changes(extract_changes(@template_changes, 'Parameters'), 'Parameters') unless @verbosity < 1
|
179
|
+
present_changes(extract_changes(@template_changes, 'Mappings'), 'Mappings') unless @verbosity < 1
|
180
|
+
present_changes(extract_changes(@template_changes, 'Outputs'), 'Outputs') unless @verbosity < 1
|
181
|
+
end
|
182
|
+
|
183
|
+
def report(event)
|
184
|
+
@output.write(event.message.colorize(:white) + "\n") unless @verbosity < 2
|
185
|
+
end
|
186
|
+
|
187
|
+
def abort(event)
|
188
|
+
@error.write("\n" + event.message.colorize(:red) + "\n") unless @verbosity < 1
|
189
|
+
end
|
190
|
+
|
191
|
+
def done(event)
|
192
|
+
@output.write(event.message.colorize(:green) + "\n") unless @verbosity < 1
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
def change_details(rc)
|
198
|
+
(rc[:details] || []).flat_map do |detail|
|
199
|
+
target_path = case detail[:target][:attribute]
|
200
|
+
when 'Properties'
|
201
|
+
[rc[:logical_resource_id], detail[:target][:attribute], detail[:target][:name]]
|
202
|
+
when 'Tags'
|
203
|
+
[rc[:logical_resource_id], 'Properties', detail[:target][:attribute]]
|
204
|
+
else
|
205
|
+
nil
|
206
|
+
end
|
207
|
+
extract_changes(@template_changes, 'Resources', *target_path)
|
208
|
+
end
|
209
|
+
.map do |(ch, path, l, r)|
|
210
|
+
format_change(ch, path[3..-1], l, r)
|
211
|
+
end
|
212
|
+
.join
|
213
|
+
end
|
214
|
+
|
215
|
+
def extract_changes(changes, type, *target_path)
|
216
|
+
changes
|
217
|
+
.select {|(_, path, _)| path[0..target_path.size] == [type, *target_path] }
|
218
|
+
.map {|(ch, path, *rest)| [ch, path, *rest] }
|
219
|
+
end
|
220
|
+
|
221
|
+
def present_changes(changes, type)
|
222
|
+
return unless changes.size > 0
|
223
|
+
@output.write("#{type}:\n")
|
224
|
+
changes.each do |(ch, path, l, r)|
|
225
|
+
@output.write(format_change(ch, path, l, r))
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def format_change(ch, path, l, r = nil)
|
230
|
+
sprintf("%s %s: %s\n",
|
231
|
+
change_color(ch),
|
232
|
+
path.join('/'),
|
233
|
+
format_changed_value(ch, l, r),
|
234
|
+
)
|
235
|
+
end
|
236
|
+
|
237
|
+
def change_color(ch)
|
238
|
+
ch.colorize(
|
239
|
+
case ch
|
240
|
+
when '-' then :red
|
241
|
+
when '+' then :green
|
242
|
+
when '~' then :yellow
|
243
|
+
else :white
|
244
|
+
end
|
245
|
+
)
|
246
|
+
end
|
247
|
+
|
248
|
+
def format_changed_value(ch, l, r)
|
249
|
+
if ch == '~'
|
250
|
+
if l.is_a?(String) and r.is_a?(String)
|
251
|
+
l_lines = l.each_line(chomp: true).to_a
|
252
|
+
r_lines = r.each_line(chomp: true).to_a
|
253
|
+
if l_lines.size > 1 or r_lines.size > 1
|
254
|
+
differ = DiffWithContext.new(color: true)
|
255
|
+
return sprintf(
|
256
|
+
"String diff:\n%s",
|
257
|
+
differ.generate_for(l_lines, r_lines)
|
258
|
+
)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
"#{l} -> #{r}"
|
262
|
+
else
|
263
|
+
l
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def interpret_states(resource)
|
268
|
+
case resource[:states]
|
269
|
+
when [:progress]
|
270
|
+
[:yellow, :tripple_dot]
|
271
|
+
when [:good]
|
272
|
+
[:green, :check]
|
273
|
+
when [:bad]
|
274
|
+
[:red, :cross]
|
275
|
+
when [:good, :progress]
|
276
|
+
[:light_white, :tripple_dot]
|
277
|
+
when [:bad, :progress]
|
278
|
+
[:red, :tripple_dot]
|
279
|
+
when [:good, :good], [:bad, :good]
|
280
|
+
[:light_white, :check]
|
281
|
+
when [:good, :bad], [:bad, :bad]
|
282
|
+
[:red, :qmark]
|
283
|
+
else
|
284
|
+
raise "Unexpected :states in #{resource.inspect}"
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
class DiffWithContext
|
290
|
+
def initialize(ctx_size: 3, color: false)
|
291
|
+
@ctx_size = ctx_size
|
292
|
+
@color = color
|
293
|
+
end
|
294
|
+
|
295
|
+
def generate_for(left, right)
|
296
|
+
buf = StringIO.new
|
297
|
+
lineno_size = left.size.to_s.size
|
298
|
+
diff = Hashdiff.best_diff(left, right, array_path: true)
|
299
|
+
with_context(diff, left) do |ch, lineno, line|
|
300
|
+
if ch == '!'
|
301
|
+
change = "...\n"
|
302
|
+
elsif ch == '-'
|
303
|
+
change = sprintf("%s %#{lineno_size}d %s\n", ch, lineno, line)
|
304
|
+
change = change.colorize(:red) if @color
|
305
|
+
elsif ch == '+'
|
306
|
+
change = sprintf("%s #{' ' * lineno_size} %s\n", ch, line)
|
307
|
+
change = change.colorize(:green) if @color
|
308
|
+
else
|
309
|
+
change = sprintf(" %#{lineno_size}d %s\n", lineno, line)
|
310
|
+
end
|
311
|
+
buf << change
|
312
|
+
end
|
313
|
+
buf.string
|
314
|
+
end
|
315
|
+
|
316
|
+
private
|
317
|
+
|
318
|
+
def with_context(diff, left)
|
319
|
+
current_lineno = 0
|
320
|
+
diff.chain([[nil, [left.size], nil]]).each_cons(2) do |(ch, path_this, line), (_, path_next, _)|
|
321
|
+
lineno_this = path_this[0]
|
322
|
+
lineno_next = path_next[0]
|
323
|
+
# skip forward
|
324
|
+
if lineno_this - @ctx_size > current_lineno
|
325
|
+
if current_lineno > 0
|
326
|
+
yield '!', nil, nil
|
327
|
+
end
|
328
|
+
current_lineno = lineno_this - @ctx_size
|
329
|
+
end
|
330
|
+
# emit context before
|
331
|
+
left[current_lineno...lineno_this].each do |c|
|
332
|
+
current_lineno += 1
|
333
|
+
yield nil, current_lineno, c
|
334
|
+
end
|
335
|
+
# emit change
|
336
|
+
if ch != '+'
|
337
|
+
current_lineno += 1
|
338
|
+
end
|
339
|
+
yield ch, current_lineno, line
|
340
|
+
# emit context after
|
341
|
+
last_ctx_lineno = [current_lineno + @ctx_size, lineno_next].min
|
342
|
+
left[current_lineno...last_ctx_lineno].each do |c|
|
343
|
+
current_lineno += 1
|
344
|
+
yield nil, current_lineno, c
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
data/lib/cuffsert/rxs3client.rb
CHANGED
data/lib/cuffsert/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cuffsert
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Anders Qvist
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk-cloudformation
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.
|
19
|
+
version: '1.67'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.
|
26
|
+
version: '1.67'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: aws-sdk-s3
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.
|
33
|
+
version: '1.112'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.
|
40
|
+
version: '1.112'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: colorize
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '1.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: nokogiri
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: ruby-termios
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,20 +108,34 @@ dependencies:
|
|
94
108
|
- - ">="
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: base64
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: bundler
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
100
128
|
requirements:
|
101
129
|
- - "~>"
|
102
130
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
131
|
+
version: '2.3'
|
104
132
|
type: :development
|
105
133
|
prerelease: false
|
106
134
|
version_requirements: !ruby/object:Gem::Requirement
|
107
135
|
requirements:
|
108
136
|
- - "~>"
|
109
137
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
138
|
+
version: '2.3'
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
140
|
name: byebug
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,6 +150,20 @@ dependencies:
|
|
122
150
|
- - ">="
|
123
151
|
- !ruby/object:Gem::Version
|
124
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: logger
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
125
167
|
- !ruby/object:Gem::Dependency
|
126
168
|
name: rspec
|
127
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -175,17 +217,18 @@ executables:
|
|
175
217
|
extensions: []
|
176
218
|
extra_rdoc_files: []
|
177
219
|
files:
|
220
|
+
- ".github/workflows/pr.yaml"
|
178
221
|
- ".gitignore"
|
179
222
|
- ".ruby-version"
|
180
|
-
- ".travis.yml"
|
181
223
|
- Gemfile
|
182
|
-
- Gemfile.lock
|
183
224
|
- LICENSE
|
184
225
|
- README.md
|
185
226
|
- bin/cuffdown
|
186
227
|
- bin/cuffsert
|
187
228
|
- bin/cuffup
|
188
229
|
- cuffsert.gemspec
|
230
|
+
- cuffsert.gif
|
231
|
+
- examples/imap.yaml
|
189
232
|
- lib/cuffbase.rb
|
190
233
|
- lib/cuffdown/main.rb
|
191
234
|
- lib/cuffsert/actions.rb
|
@@ -198,6 +241,7 @@ files:
|
|
198
241
|
- lib/cuffsert/messages.rb
|
199
242
|
- lib/cuffsert/metadata.rb
|
200
243
|
- lib/cuffsert/presenters.rb
|
244
|
+
- lib/cuffsert/rendering.rb
|
201
245
|
- lib/cuffsert/rxcfclient.rb
|
202
246
|
- lib/cuffsert/rxs3client.rb
|
203
247
|
- lib/cuffsert/version.rb
|
@@ -207,7 +251,7 @@ homepage: https://github.com/bittrance/cuffsert
|
|
207
251
|
licenses:
|
208
252
|
- MIT
|
209
253
|
metadata: {}
|
210
|
-
post_install_message:
|
254
|
+
post_install_message:
|
211
255
|
rdoc_options: []
|
212
256
|
require_paths:
|
213
257
|
- lib
|
@@ -222,8 +266,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
222
266
|
- !ruby/object:Gem::Version
|
223
267
|
version: '0'
|
224
268
|
requirements: []
|
225
|
-
rubygems_version: 3.
|
226
|
-
signing_key:
|
269
|
+
rubygems_version: 3.4.20
|
270
|
+
signing_key:
|
227
271
|
specification_version: 4
|
228
272
|
summary: Cuffsert provides a quick up-arrow-enter loading of a CloudFormation stack
|
229
273
|
with good feedback
|
data/.travis.yml
DELETED
data/Gemfile.lock
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
cuffsert (0.13.0)
|
5
|
-
aws-sdk-cloudformation (~> 1.3.0)
|
6
|
-
aws-sdk-s3 (~> 1.8.0)
|
7
|
-
colorize
|
8
|
-
hashdiff (~> 1.0)
|
9
|
-
ruby-termios
|
10
|
-
rx
|
11
|
-
|
12
|
-
GEM
|
13
|
-
remote: https://rubygems.org/
|
14
|
-
specs:
|
15
|
-
aws-eventstream (1.0.0)
|
16
|
-
aws-partitions (1.87.0)
|
17
|
-
aws-sdk-cloudformation (1.3.0)
|
18
|
-
aws-sdk-core (~> 3)
|
19
|
-
aws-sigv4 (~> 1.0)
|
20
|
-
aws-sdk-core (3.21.2)
|
21
|
-
aws-eventstream (~> 1.0)
|
22
|
-
aws-partitions (~> 1.0)
|
23
|
-
aws-sigv4 (~> 1.0)
|
24
|
-
jmespath (~> 1.0)
|
25
|
-
aws-sdk-kms (1.5.0)
|
26
|
-
aws-sdk-core (~> 3)
|
27
|
-
aws-sigv4 (~> 1.0)
|
28
|
-
aws-sdk-s3 (1.8.2)
|
29
|
-
aws-sdk-core (~> 3)
|
30
|
-
aws-sdk-kms (~> 1)
|
31
|
-
aws-sigv4 (~> 1.0)
|
32
|
-
aws-sigv4 (1.0.2)
|
33
|
-
byebug (9.0.6)
|
34
|
-
colorize (0.8.1)
|
35
|
-
diff-lcs (1.2.5)
|
36
|
-
docile (1.1.5)
|
37
|
-
jmespath (1.3.1)
|
38
|
-
hashdiff (1.0.0)
|
39
|
-
json (2.0.2)
|
40
|
-
rspec (3.5.0)
|
41
|
-
rspec-core (~> 3.5.0)
|
42
|
-
rspec-expectations (~> 3.5.0)
|
43
|
-
rspec-mocks (~> 3.5.0)
|
44
|
-
rspec-core (3.5.4)
|
45
|
-
rspec-support (~> 3.5.0)
|
46
|
-
rspec-expectations (3.5.0)
|
47
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
-
rspec-support (~> 3.5.0)
|
49
|
-
rspec-mocks (3.5.0)
|
50
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
51
|
-
rspec-support (~> 3.5.0)
|
52
|
-
rspec-support (3.5.0)
|
53
|
-
ruby-termios (1.0.2)
|
54
|
-
rx (0.0.3)
|
55
|
-
rx-rspec (0.4.3)
|
56
|
-
rx
|
57
|
-
simplecov (0.12.0)
|
58
|
-
docile (~> 1.1.0)
|
59
|
-
json (>= 1.8, < 3)
|
60
|
-
simplecov-html (~> 0.10.0)
|
61
|
-
simplecov-html (0.10.0)
|
62
|
-
|
63
|
-
PLATFORMS
|
64
|
-
ruby
|
65
|
-
|
66
|
-
DEPENDENCIES
|
67
|
-
bundler (~> 1.12)
|
68
|
-
byebug
|
69
|
-
cuffsert!
|
70
|
-
rspec (~> 3.0)
|
71
|
-
rx-rspec (~> 0.4.3)
|
72
|
-
simplecov
|
73
|
-
|
74
|
-
BUNDLED WITH
|
75
|
-
1.13.6
|