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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23078b61b4bc939211957232e76b4d94fc8af027584920615fa67cc5df90f5bf
4
- data.tar.gz: c36af419f4b1876da5a05f02bc9b027036d1ed11d1bab6a5eaaeeee7f5be02dc
3
+ metadata.gz: 236e1cb4d9e2ff028feb9d12f49c58359bb91ae5f8d95c4edf3ba3d2ab05a1d5
4
+ data.tar.gz: 251ce8c81341d12bb08a10608d8efaa1f959c0c38249000af7a7c212565ca973
5
5
  SHA512:
6
- metadata.gz: e5d8830af935e9e76aef8db13a4aab6215ffd07e7d08c05021112debce9ad0e56eb3ef79b44c07826e118050a3d11a6544f1ba5ee27444f7a96d6179abdd900e
7
- data.tar.gz: 6d022befb2e5e71d86c72302632725847d4169cc437f199baea9f6fbcacc6b62fbb59a6d4b0f41207e9bb0f473333f9a65da5ae24184782cf7166bb2a00db095
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
@@ -3,3 +3,4 @@ vendor/
3
3
  coverage/
4
4
  cuffsert-*.gem
5
5
  .byebug_history
6
+ Gemfile.lock
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.6.5
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
+ ![Create and update stack](cuffsert.gif)
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.3.0'
19
- spec.add_runtime_dependency 'aws-sdk-s3', '~> 1.8.0'
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 'bundler', '~> 1.12'
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
@@ -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
@@ -5,6 +5,7 @@ require 'cuffsert/confirmation'
5
5
  require 'cuffsert/messages'
6
6
  require 'cuffsert/metadata'
7
7
  require 'cuffsert/presenters'
8
+ require 'cuffsert/rendering'
8
9
  require 'cuffsert/rxcfclient'
9
10
  require 'cuffsert/rxs3client'
10
11
  require 'rx'
@@ -1,3 +1,5 @@
1
+ require 'rx'
2
+
1
3
  module CuffSert
2
4
  class Message
3
5
  attr_reader :message
@@ -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.exists?(stack_path)
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 = {}
@@ -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
@@ -1,4 +1,5 @@
1
1
  require 'aws-sdk-s3'
2
+ require 'cuffsert/messages'
2
3
  require 'rx'
3
4
 
4
5
  module CuffSert
@@ -1,3 +1,3 @@
1
1
  module CuffSert
2
- VERSION = '0.14.2'
2
+ VERSION = '0.15.0'
3
3
  end
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.14.2
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: 2020-03-04 00:00:00.000000000 Z
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.3.0
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.3.0
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.8.0
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.8.0
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: '1.12'
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: '1.12'
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.0.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
@@ -1,6 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.0
4
- - 2.2
5
- script:
6
- - bundle exec rspec -c -fd
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