cuffsert 0.14.2 → 0.15.0

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