aly 0.2.1 → 0.3.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: 31c380fbca73a1136b7c8dea73e13ddd28f03a8e6e3a9a395bd55d225215ee56
4
- data.tar.gz: b320bcbb2b06fadd45f1ba005526fba07657287dbdf81e7ba48c50c3e4d24723
3
+ metadata.gz: 9db212d7233b62974d4d2b74909f00090561d6dbe874950e580d92d84be67b5f
4
+ data.tar.gz: de5bdb35a47c607db653a5befb8f55a82f53dbaca1c28646c00b54be263dd52d
5
5
  SHA512:
6
- metadata.gz: 3ea21f6de6ea31ded361e64a40b97f7f48fe1e8652994effebbd99d1b380c1b8a08d11519cdb4dd2d58196f66fd24adde2cc5d4d2069367809c8e7766cab4906
7
- data.tar.gz: 1a5f4e03792bded3c18cb798d03c574a35f96bda022c36265f0ff281cd7d52f8fe3edd987dbc36826768272f824b20dd42b320da63e30fa576b4a7032b4f8034
6
+ metadata.gz: b95c2203787b28ff2abeb5cfe2b54015619c6289a21ef631b8dc8c6c6dbb599600fa9a1113afb99300d6262c8452d41b81e82d117c9a88f98b4fe80ccd67cfd1
7
+ data.tar.gz: 3d2178da37d57a8c90a585640e6ed60aa9cafff01c449165c10929fc8c0c362a00b06fbb5c2708c02ef931e565a3368955fc3c3bcaa900f66bc15b21e50dbd20
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- aly (0.2.1)
4
+ aly (0.3.0)
5
5
  terminal-table (~> 1.8.0)
6
6
  thor (~> 0.20.0)
7
7
 
data/lib/aly/app.rb CHANGED
@@ -10,7 +10,7 @@ class Array
10
10
  Terminal::Table.new { |t|
11
11
  t << header
12
12
  t << :separator
13
- each { |row| t << row.values }
13
+ each { |row| t << (row.nil? ? :separator : row.values) }
14
14
  }
15
15
  end
16
16
  end
@@ -23,7 +23,7 @@ module Aly
23
23
 
24
24
  def ecs(*args, **options)
25
25
  raw_out = exec('ecs', 'DescribeInstances', '--pager', **options)
26
- selected = raw_out['Instances']['Instance'].each do |item|
26
+ selected = (raw_out['Instances']['Instance'] || []).each do |item|
27
27
  item['PrivateIP'] = (item['NetworkInterfaces']['NetworkInterface'] || []).map { |ni| ni['PrimaryIpAddress'] }.join(',')
28
28
  item['PublicIP'] = item['EipAddress']['IpAddress'] || ''
29
29
  item['PublicIP'] = item['PublicIpAddress']['IpAddress'].join(',') if item['PublicIP'] == ''
@@ -63,6 +63,7 @@ module Aly
63
63
  end
64
64
  end
65
65
 
66
+
66
67
  if options['detail']
67
68
  puts JSON.pretty_generate(selected)
68
69
  else
@@ -90,11 +91,137 @@ module Aly
90
91
  end
91
92
  end
92
93
 
94
+ def full_slb(lbs, eips, **options)
95
+ lbs.each do |lb|
96
+ puts "\nLoanBalancer Id: %s, Name: %s" % [lb['LoadBalancerId'], lb['LoadBalancerName']]
97
+ puts "==============================================================\n\n"
98
+ listeners = lb['Listeners']
99
+ background_servers = exec('slb', 'DescribeLoadBalancerAttribute', "--LoadBalancerId=#{lb['LoadBalancerId']}", **options)['BackendServers']['BackendServer'] || []
100
+
101
+ puts ' LoadBalancer Basic Information:'
102
+ puts([{
103
+ Id: lb['LoadBalancerId'],
104
+ Name: lb['LoadBalancerName'],
105
+ Address: lb['Address'],
106
+ Eip: lb['Eip'],
107
+ Listeners: listeners.size
108
+ }].table.to_s.gsub(/^/, ' '))
109
+ puts
110
+
111
+ if background_servers && background_servers.size > 0
112
+ puts ' Default Backend Servers:'
113
+ puts
114
+
115
+ ecss = @ecs.select { |e| background_servers.map{|ee| ee['ServerId']}.include?(e['InstanceId']) }
116
+
117
+ puts ecss.map { |row|
118
+ {
119
+ Id: row['InstanceId'],
120
+ Name: row['InstanceName'],
121
+ PrivateIP: row['PrivateIP'].join(','),
122
+ PublicIP: row['PublicIP'].join(','),
123
+ CPU: row['Cpu'],
124
+ RAM: "#{row['Memory'] / 1024.0} GB"
125
+ }
126
+ }.table.to_s.gsub(/^/, ' ')
127
+ puts
128
+ end
129
+
130
+ vserver_groups = exec('slb', 'DescribeVServerGroups', '--pager', "--LoadBalancerId=#{lb['LoadBalancerId']}", **options)["VServerGroups"]["VServerGroup"] || []
131
+ vserver_group_servers = vserver_groups.flat_map do |vg|
132
+ vg_attr = exec('slb', 'DescribeVServerGroupAttribute', "--VServerGroupId=#{vg['VServerGroupId']}", **options)["BackendServers"]["BackendServer"]
133
+ vg_attr.each_with_index.map do |attr, idx|
134
+ ecs = @ecs.find {|e| e['InstanceId'] == attr['ServerId'] }
135
+ {
136
+ VGroupId: (idx.zero? ? vg['VServerGroupId'] : ''),
137
+ VGroupName: (idx.zero? ? vg['VServerGroupName'] : ''),
138
+ Weight: attr['Weight'],
139
+ Port: attr['Port'],
140
+ Type: attr['Type'],
141
+ EcsId: attr['ServerId'],
142
+ EcsName: ecs['InstanceName'],
143
+ PrivateIP: ecs['PrivateIP'].join(','),
144
+ PublicIP: ecs['PublicIP'].join(','),
145
+ CPU: ecs['Cpu'],
146
+ RAM: "#{ecs['Memory'] / 1024.0} GB"
147
+ }
148
+ end + [nil]
149
+ end
150
+ puts ' VServer Groups:'
151
+ puts vserver_group_servers[0..-2].table.to_s.gsub(/^/, ' ')
152
+ puts
153
+
154
+ listeners.each do |listener|
155
+ listener_type = ['HTTP', 'HTTPS', 'TCP', 'TCPS', 'UDP'].find { |e| !listener["#{e}ListenerConfig"].empty? }
156
+ listener['ListenerType'] = listener_type
157
+ end
158
+
159
+ listeners_info = listeners.map do |listener|
160
+ {
161
+ Description: listener['Description'],
162
+ Port: listener['ListenerPort'],
163
+ Protocol: listener['ListenerProtocol'],
164
+ Status: listener['Status'],
165
+ HeathCheck: (listener["#{listener['ListenerType']}ListenerConfig"] || {}).dig("HealthCheck") || 'off',
166
+ BackendServerPort: listener['BackendServerPort'],
167
+ ForwardPort: listener.dig('HTTPListenerConfig', 'ForwardPort'),
168
+ VServerGroup: listener['VServerGroupId'],
169
+ AclStatus: listener['AclStatus'] || 'off',
170
+ AclType: listener['AclType'],
171
+ AclIds: (listener['AclIds'] || []).join(','),
172
+ }
173
+ end
174
+
175
+ puts ' Configured Listeners:'
176
+ puts listeners_info.table.to_s.gsub(/^/, ' ')
177
+ puts
178
+
179
+ listener_rules = listeners.flat_map do |listener|
180
+ listener_type = listener['ListenerType']
181
+ next [] unless listener_type
182
+ rules = exec('slb', "DescribeLoadBalancer#{listener_type}ListenerAttribute", "--LoadBalancerId=#{lb['LoadBalancerId']}", "--ListenerPort=#{listener['ListenerPort']}", **options).dig('Rules', 'Rule') || []
183
+ rules.map do |rule|
184
+ {'Listener' => listener['Description']}.merge(rule)
185
+ end
186
+ end
187
+
188
+ puts ' Listener Rules:'
189
+ puts listener_rules.table.to_s.gsub(/^/, ' ')
190
+ puts
191
+
192
+ if options['acl']
193
+ acl_ids = listeners.flat_map { |listener| listener['AclIds'] || [] }.uniq
194
+ unless acl_ids.empty?
195
+ alc_entries = acl_ids.flat_map do |acl_id|
196
+ attr = exec('slb', 'DescribeAccessControlListAttribute', "--AclId=#{acl_id}", **options)
197
+ (attr.dig('AclEntrys', 'AclEntry') || []).each_with_index.map do |e, idx|
198
+ {
199
+ AclId: (idx.zero? ? attr['AclId'] : ''),
200
+ AclName: (idx.zero? ? attr['AclName'] : ''),
201
+ AclEntryIP: e['AclEntryIP'],
202
+ AclEntryComment: e['AclEntryComment']
203
+ }
204
+ end + [nil]
205
+ end
206
+ puts ' Access Control Lists:'
207
+ puts alc_entries[0..-2].table.to_s.gsub(/^/, ' ')
208
+ puts
209
+ end
210
+
211
+ end
212
+
213
+ end
214
+ end
215
+
93
216
  def slb(*args, **options)
94
217
  raw_out = exec('slb', 'DescribeLoadBalancers', '--pager', **options)
95
- selected = raw_out['LoadBalancers']['LoadBalancer']
218
+ selected = raw_out['LoadBalancers']['LoadBalancer'] || []
219
+
220
+ eips = exec('vpc', 'DescribeEipAddresses', "--PageSize=#{selected.size}", **options)['EipAddresses']['EipAddress'].each_with_object({}) do |item, result|
221
+ result[item['InstanceId']] = item['IpAddress']
222
+ end
96
223
 
97
- listeners = exec('slb', 'DescribeLoadBalancerListeners', '--pager', 'path=Listeners', **options)['Listeners'].each_with_object({}) do |listener, result|
224
+ listeners = (exec('slb', 'DescribeLoadBalancerListeners', '--pager', 'path=Listeners', **options)['Listeners'] || []).each_with_object({}) do |listener, result|
98
225
  instance_id = listener['LoadBalancerId']
99
226
  result[instance_id] ||= []
100
227
  result[instance_id] << listener
@@ -110,7 +237,29 @@ module Aly
110
237
  end
111
238
  end
112
239
 
113
- if options['detail']
240
+ @eip ||= exec('vpc', 'DescribeEipAddresses', '--PageSize=100', **options)['EipAddresses']['EipAddress'] || []
241
+
242
+ eip_map = @eip.each_with_object({}) { |eip, h| h[eip['InstanceId']] = eip['IpAddress'] }
243
+ selected.each do |slb|
244
+ slb['Eip'] = eip_map[slb['LoadBalancerId']]
245
+ end
246
+
247
+ @ecs = exec('ecs', 'DescribeInstances', '--pager', **options)['Instances']['Instance'] || []
248
+ @ecs.each do |item|
249
+ item['PrivateIP'] = (item['NetworkInterfaces']['NetworkInterface'] || []).map { |ni| ni['PrimaryIpAddress'] }
250
+ item['PublicIP'] = []
251
+ if ip = item['EipAddress']['IpAddress']
252
+ item['PublicIP'] << ip
253
+ end
254
+ if ips = item['PublicIpAddress']['IpAddress']
255
+ item['PublicIP'] += ips
256
+ end
257
+ item['AllIPs'] = item['PrivateIP'] + item['PublicIP']
258
+ end
259
+
260
+ if options['full']
261
+ full_slb(selected, eips, **options)
262
+ elsif options['detail']
114
263
  selected.each do |row|
115
264
  described_load_balancer_attributes = exec('slb', 'DescribeLoadBalancerAttribute', "--LoadBalancerId=#{row['LoadBalancerId']}", **options)
116
265
  row['BackendServers'] = described_load_balancer_attributes['BackendServers']['BackendServer']
@@ -140,6 +289,7 @@ module Aly
140
289
  Id: row['LoadBalancerId'],
141
290
  Name: row['LoadBalancerName'],
142
291
  Address: row['Address'],
292
+ Eip: eips[row['LoadBalancerId']] || '',
143
293
  Listeners: listeners
144
294
  }
145
295
  end
@@ -148,7 +298,7 @@ module Aly
148
298
  end
149
299
 
150
300
  def slb_contains_host?(host)
151
- @slb.any? { |lb| lb['Address'] == host }
301
+ @slb.any? { |lb| lb['Address'] == host || lb['Eip'] == host }
152
302
  end
153
303
 
154
304
  def ecs_contains_host?(host)
@@ -156,82 +306,125 @@ module Aly
156
306
  end
157
307
 
158
308
  def show_slb(host, **options)
159
- @listeners ||= exec('slb', 'DescribeLoadBalancerListeners', '--pager', 'path=Listeners', **options)['Listeners']
160
- lb = @slb.find { |e| e['Address'] == host }
309
+ @listeners ||= exec('slb', 'DescribeLoadBalancerListeners', '--pager', 'path=Listeners', **options)['Listeners'] || []
310
+ lb = @slb.find { |e| e['Address'] == host || e['Eip'] == host }
161
311
  listeners = @listeners.select { |e| e['LoadBalancerId'] == lb['LoadBalancerId'] }
162
- background_servers = exec('slb', 'DescribeLoadBalancerAttribute', "--LoadBalancerId=#{lb['LoadBalancerId']}", **options)['BackendServers']['BackendServer']
163
312
 
164
- puts 'LoadBalancers:'
313
+ puts "\nLoanBalancer Id: %s, Name: %s" % [lb['LoadBalancerId'], lb['LoadBalancerName']]
314
+ puts "==============================================================\n\n"
315
+ background_servers = exec('slb', 'DescribeLoadBalancerAttribute', "--LoadBalancerId=#{lb['LoadBalancerId']}", **options)['BackendServers']['BackendServer'] || []
316
+
317
+ puts ' LoadBalancer Basic Information:'
165
318
  puts([{
166
- Id: lb['LoadBalancerId'],
167
- Name: lb['LoadBalancerName'],
168
- Address: lb['Address'],
169
- Listeners: listeners.size
170
- }].table.to_s)
319
+ Id: lb['LoadBalancerId'],
320
+ Name: lb['LoadBalancerName'],
321
+ Address: lb['Address'],
322
+ Eip: lb['Eip'],
323
+ Listeners: listeners.size
324
+ }].table.to_s.gsub(/^/, ' '))
171
325
  puts
172
326
 
173
327
  if background_servers && background_servers.size > 0
174
- puts 'Default Backend Servers:'
175
- puts background_servers.table.to_s
328
+ puts ' Default Backend Servers:'
329
+ puts
330
+
331
+ ecss = @ecs.select { |e| background_servers.map{|ee| ee['ServerId']}.include?(e['InstanceId']) }
332
+
333
+ puts ecss.map { |row|
334
+ {
335
+ Id: row['InstanceId'],
336
+ Name: row['InstanceName'],
337
+ PrivateIP: row['PrivateIP'].join(','),
338
+ PublicIP: row['PublicIP'].join(','),
339
+ CPU: row['Cpu'],
340
+ RAM: "#{row['Memory'] / 1024.0} GB"
341
+ }
342
+ }.table.to_s.gsub(/^/, ' ')
176
343
  puts
177
344
  end
178
345
 
346
+ vserver_groups = exec('slb', 'DescribeVServerGroups', '--pager', "--LoadBalancerId=#{lb['LoadBalancerId']}", **options)["VServerGroups"]["VServerGroup"] || []
347
+ vserver_group_servers = vserver_groups.flat_map do |vg|
348
+ vg_attr = exec('slb', 'DescribeVServerGroupAttribute', "--VServerGroupId=#{vg['VServerGroupId']}", **options)["BackendServers"]["BackendServer"]
349
+ vg_attr.each_with_index.map do |attr, idx|
350
+ ecs = @ecs.find {|e| e['InstanceId'] == attr['ServerId'] }
351
+ {
352
+ VGroupId: (idx.zero? ? vg['VServerGroupId'] : ''),
353
+ VGroupName: (idx.zero? ? vg['VServerGroupName'] : ''),
354
+ Weight: attr['Weight'],
355
+ Port: attr['Port'],
356
+ Type: attr['Type'],
357
+ EcsId: attr['ServerId'],
358
+ EcsName: ecs['InstanceName'],
359
+ PrivateIP: ecs['PrivateIP'].join(','),
360
+ PublicIP: ecs['PublicIP'].join(','),
361
+ CPU: ecs['Cpu'],
362
+ RAM: "#{ecs['Memory'] / 1024.0} GB"
363
+ }
364
+ end + [nil]
365
+ end
366
+ puts ' VServer Groups:'
367
+ puts vserver_group_servers[0..-2].table.to_s.gsub(/^/, ' ')
368
+ puts
369
+
370
+ listeners.each do |listener|
371
+ listener_type = ['HTTP', 'HTTPS', 'TCP', 'TCPS', 'UDP'].find { |e| !listener["#{e}ListenerConfig"].empty? }
372
+ listener['ListenerType'] = listener_type
373
+ end
374
+
179
375
  listeners_info = listeners.map do |listener|
180
376
  {
377
+ Description: listener['Description'],
181
378
  Port: listener['ListenerPort'],
182
379
  Protocol: listener['ListenerProtocol'],
183
380
  Status: listener['Status'],
381
+ HeathCheck: (listener["#{listener['ListenerType']}ListenerConfig"] || {}).dig("HealthCheck") || 'off',
184
382
  BackendServerPort: listener['BackendServerPort'],
185
383
  ForwardPort: listener.dig('HTTPListenerConfig', 'ForwardPort'),
186
- VServerGroup: listener['VServerGroupId']
384
+ VServerGroup: listener['VServerGroupId'],
385
+ AclStatus: listener['AclStatus'] || 'off',
386
+ AclType: listener['AclType'],
387
+ AclIds: (listener['AclIds'] || []).join(','),
187
388
  }
188
389
  end
189
390
 
190
- puts 'Configured Listeners:'
191
- puts listeners_info.table.to_s
391
+ puts ' Configured Listeners:'
392
+ puts listeners_info.table.to_s.gsub(/^/, ' ')
192
393
  puts
193
394
 
194
- listeners_info.each do |listener|
195
- if listener[:VServerGroup]
196
- vserver_group = exec('slb', 'DescribeVServerGroupAttribute', "--VServerGroupId=#{listener[:VServerGroup]}", **options)["BackendServers"]["BackendServer"]
197
- puts "VServerGroup #{listener[:VServerGroup]}:"
198
- puts(vserver_group.map { |e|
199
- {
200
- EcsInstanceId: e['ServerId'],
201
- Port: e['Port'],
202
- Weight: e['Weight'],
203
- Type: e['Type']
204
- }
205
- }.table.to_s)
206
- puts
395
+ listener_rules = listeners.flat_map do |listener|
396
+ listener_type = listener['ListenerType']
397
+ next [] unless listener_type
398
+ rules = exec('slb', "DescribeLoadBalancer#{listener_type}ListenerAttribute", "--LoadBalancerId=#{lb['LoadBalancerId']}", "--ListenerPort=#{listener['ListenerPort']}", **options).dig('Rules', 'Rule') || []
399
+ rules.map do |rule|
400
+ {'Listener' => listener['Description']}.merge(rule)
207
401
  end
208
402
  end
209
403
 
210
- ecs_ids = background_servers.map { |e| e['ServerId'] }
211
- ecs_ids += listeners_info.flat_map { |e|
212
- if e[:VServerGroup]
213
- exec('slb', 'DescribeVServerGroupAttribute', "--VServerGroupId=#{e[:VServerGroup]}", **options)["BackendServers"]["BackendServer"].map { |e| e['ServerId'] }
214
- else
215
- []
216
- end
217
- }
218
- ecs_ids.uniq!
219
-
220
- ecss = @ecs.select { |e| ecs_ids.include?(e['InstanceId']) }
221
-
222
- puts "Referenced ECS Instances:"
404
+ puts ' Listener Rules:'
405
+ puts listener_rules.table.to_s.gsub(/^/, ' ')
406
+ puts
223
407
 
224
- puts ecss.map { |row|
225
- {
226
- Id: row['InstanceId'],
227
- Name: row['InstanceName'],
228
- PrivateIP: row['PrivateIP'].join(','),
229
- PublicIP: row['PublicIP'].join(','),
230
- CPU: row['Cpu'],
231
- RAM: "#{row['Memory'] / 1024.0} GB"
232
- }
233
- }.table.to_s
408
+ if options['acl']
409
+ acl_ids = listeners.flat_map { |listener| listener['AclIds'] || [] }.uniq
410
+ unless acl_ids.empty?
411
+ alc_entries = acl_ids.flat_map do |acl_id|
412
+ attr = exec('slb', 'DescribeAccessControlListAttribute', "--AclId=#{acl_id}", **options)
413
+ (attr.dig('AclEntrys', 'AclEntry') || []).each_with_index.map do |e, idx|
414
+ {
415
+ AclId: (idx.zero? ? attr['AclId'] : ''),
416
+ AclName: (idx.zero? ? attr['AclName'] : ''),
417
+ AclEntryIP: e['AclEntryIP'],
418
+ AclEntryComment: e['AclEntryComment']
419
+ }
420
+ end + [nil]
421
+ end
422
+ puts ' Access Control Lists:'
423
+ puts alc_entries[0..-2].table.to_s.gsub(/^/, ' ')
424
+ puts
425
+ end
234
426
 
427
+ end
235
428
  end
236
429
 
237
430
  def show_ecs(host)
@@ -254,11 +447,17 @@ module Aly
254
447
  end
255
448
 
256
449
  def show(*args, **options)
257
- @slb ||= exec('slb', 'DescribeLoadBalancers', '--pager', **options)['LoadBalancers']['LoadBalancer']
450
+ @slb ||= exec('slb', 'DescribeLoadBalancers', '--pager', **options)['LoadBalancers']['LoadBalancer'] || []
451
+
452
+ @eip ||= exec('vpc', 'DescribeEipAddresses', '--PageSize=100', **options)['EipAddresses']['EipAddress'] || []
453
+
454
+ eip_map = @eip.each_with_object({}) { |eip, h| h[eip['InstanceId']] = eip['IpAddress'] }
455
+ @slb.each do |slb|
456
+ slb['Eip'] = eip_map[slb['LoadBalancerId']]
457
+ end
258
458
 
259
- @eip ||= exec('vpc', 'DescribeEipAddresses', '--PageSize=100', **options)['EipAddresses']['EipAddress']
260
459
  unless @ecs
261
- @ecs = exec('ecs', 'DescribeInstances', '--pager', **options)['Instances']['Instance']
460
+ @ecs = exec('ecs', 'DescribeInstances', '--pager', **options)['Instances']['Instance'] || []
262
461
  @ecs.each do |item|
263
462
  item['PrivateIP'] = (item['NetworkInterfaces']['NetworkInterface'] || []).map { |ni| ni['PrimaryIpAddress'] }
264
463
  item['PublicIP'] = []
@@ -282,7 +481,7 @@ module Aly
282
481
  elsif ecs_contains_host?(host)
283
482
  show_ecs(host)
284
483
  elsif eip_contains_host?(host)
285
- eip(host)
484
+ eip(host, **options)
286
485
  else
287
486
  puts "Not found: #{host}"
288
487
  end
data/lib/aly/cli.rb CHANGED
@@ -4,6 +4,8 @@ module Aly
4
4
  class CLI < ::Thor
5
5
  class_option :profile, type: :string, optional: true, aliases: ['-p'], desc: 'select profile'
6
6
  class_option :detail, type: :boolean, optional: true, default: false, aliases: ['-d'], desc: 'show detail infomation in JSON format'
7
+ class_option :full, type: :boolean, optional: true, default: false, aliases: ['-f'], desc: 'print tables with more infomation'
8
+ class_option :acl, type: :boolean, optional: true, default: false, aliases: ['-a'], desc: 'show ACL entries'
7
9
 
8
10
  desc 'ecs', 'get ECS information'
9
11
  def ecs(query = nil)
data/lib/aly/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aly
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Liu Xiang
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-10 00:00:00.000000000 Z
11
+ date: 2022-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor