squared 0.4.3 → 0.4.4

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.
@@ -63,6 +63,10 @@ module Squared
63
63
  list: %w[a|all digests no-trunc f|filter=q format=q].freeze,
64
64
  push: %w[a|all-tags disable-content-trust=b? platform=b q|quiet].freeze,
65
65
  rm: %w[f|force no-prune].freeze
66
+ }.freeze,
67
+ network: {
68
+ connect: %w[alias=b driver-opt=q gw-priority=n ip=b ip6=b link=b link-local-ip=b].freeze,
69
+ disconnect: %w[f|force].freeze
66
70
  }.freeze
67
71
  }.freeze
68
72
  VAL_DOCKER = {
@@ -85,13 +89,14 @@ module Squared
85
89
  end
86
90
  end
87
91
 
88
- @@tasks[ref] = {
92
+ subtasks({
89
93
  'build' => %i[tag context bake].freeze,
90
94
  'compose' => %i[build run exec up].freeze,
91
95
  'image' => %i[list rm push].freeze,
92
96
  'container' => %i[run exec update commit inspect diff start stop restart pause unpause top stats kill
93
- rm].freeze
94
- }.freeze
97
+ rm].freeze,
98
+ 'network' => %i[connect disconnect].freeze
99
+ })
95
100
 
96
101
  attr_reader :context
97
102
  attr_accessor :tag
@@ -101,7 +106,7 @@ module Squared
101
106
  return unless dockerfile(file).exist?
102
107
 
103
108
  @context = context
104
- @tag = tag || "#{@project}:latest"
109
+ @tag = tag || tagname("#{@project}:#{@version || 'latest'}")
105
110
  @mounts = mounts
106
111
  @secrets = secrets
107
112
  @registry = [registry, kwargs[:username]].compact.join('/')
@@ -132,7 +137,7 @@ module Squared
132
137
  format_desc(action, flag, 'opts*', before: flag == :tag ? 'name' : 'dir')
133
138
  task flag, [flag] do |_, args|
134
139
  param = param_guard(action, flag, args: args, key: flag)
135
- buildx(:build, args.to_a.drop(1), "#{flag}": param)
140
+ buildx(:build, args.extras, "#{flag}": param)
136
141
  end
137
142
  when :bake
138
143
  format_desc action, flag, 'opts*,target*,context?'
@@ -152,7 +157,7 @@ module Squared
152
157
  format_desc action, flag, "service,command#{flag == :exec ? '' : '?'},args*,opts*"
153
158
  task flag, [:service] do |_, args|
154
159
  service = param_guard(action, flag, args: args, key: :service)
155
- composex(flag, args.to_a.drop(1), service: service)
160
+ composex(flag, args.extras, service: service)
156
161
  end
157
162
  end
158
163
  when 'container'
@@ -161,13 +166,13 @@ module Squared
161
166
  format_desc(action, flag, 'id/name,opts*', after: flag == :exec ? 'args+' : 'tag?')
162
167
  task flag, [:id] do |_, args|
163
168
  id = param_guard(action, flag, args: args, key: :id)
164
- container(flag, args.to_a.drop(1), id: id)
169
+ container(flag, args.extras, id: id)
165
170
  end
166
171
  when :run
167
172
  format_desc action, flag, 'image,opts*,args*'
168
173
  task flag, [:image] do |_, args|
169
174
  image = param_guard(action, flag, args: args, key: :image)
170
- container(flag, args.to_a.drop(1), id: image)
175
+ container(flag, args.extras, id: image)
171
176
  end
172
177
  else
173
178
  format_desc(action, flag, 'opts*', after: "id/name#{flag == :update ? '+' : '*'}")
@@ -181,7 +186,7 @@ module Squared
181
186
  format_desc action, flag, 'tag,registry/username?,opts*'
182
187
  task flag, [:tag] do |_, args|
183
188
  tag = param_guard(action, flag, args: args, key: :tag)
184
- image(flag, args.to_a.drop(1), id: tag)
189
+ image(flag, args.extras, id: tag)
185
190
  end
186
191
  else
187
192
  format_desc(action, flag, flag == :rm ? 'id*,opts*' : 'opts*,args*')
@@ -189,6 +194,12 @@ module Squared
189
194
  image flag, args.to_a
190
195
  end
191
196
  end
197
+ when 'network'
198
+ format_desc action, flag, 'target,opts*'
199
+ task flag, [:target] do |_, args|
200
+ target = param_guard(action, flag, args: args, key: :target)
201
+ network(flag, args.extras, target: target)
202
+ end
192
203
  end
193
204
  end
194
205
  end
@@ -251,9 +262,10 @@ module Squared
251
262
  as_a(@secrets).each { |arg| ret << quote_option('secret', arg) }
252
263
  end
253
264
  if (val = option('tag', ignore: false))
254
- ret << quote_option('tag', val)
265
+ append_tag val
266
+ ret << basic_option('tag', tagname(val))
255
267
  elsif !session_arg?('t', 'tag')
256
- ret << quote_option('tag', tag)
268
+ append_tag tag
257
269
  end
258
270
  append_context
259
271
  when :bake, :compose
@@ -268,10 +280,11 @@ module Squared
268
280
  cmd, opts = docker_session('buildx', opts: opts)
269
281
  opts = option_sanitize(opts, OPT_DOCKER[:buildx][:common]).first
270
282
  cmd << flag
271
- out = option_sanitize(opts, OPT_DOCKER[:buildx][flag] + OPT_DOCKER[:buildx][:shared]).first
283
+ list = OPT_DOCKER[:buildx][flag == :bake ? :bake : :build] + OPT_DOCKER[:buildx][:shared]
284
+ out = option_sanitize(opts, list).first
272
285
  case flag
273
- when :build
274
- cmd.merge(as_a(tag).map { |val| quote_option('tag', val) })
286
+ when :build, :context
287
+ append_tag(tag || option('tag', ignore: false) || @tag)
275
288
  append_context context
276
289
  when :bake
277
290
  unless out.empty?
@@ -288,7 +301,7 @@ module Squared
288
301
  contextdir(context) if context
289
302
  end
290
303
  end
291
- option_clear out
304
+ option_clear(out, pass: false)
292
305
  run(from: :"buildx:#{flag}")
293
306
  end
294
307
 
@@ -297,7 +310,7 @@ module Squared
297
310
  opts = option_sanitize(opts, OPT_DOCKER[:compose][:common]).first
298
311
  append_file filetype unless session_arg?('f', 'file')
299
312
  cmd << flag
300
- out = option_sanitize(opts, OPT_DOCKER[:compose][flag] + OPT_DOCKER[:common]).first
313
+ out = option_sanitize(opts, OPT_DOCKER[:compose][flag]).first
301
314
  from = :"compose:#{flag}"
302
315
  case flag
303
316
  when :build, :up
@@ -312,7 +325,7 @@ module Squared
312
325
  cmd, opts = docker_session('container', flag, opts: opts)
313
326
  list = OPT_DOCKER[:container].fetch(flag, [])
314
327
  list += OPT_DOCKER[:container][:update] if flag == :run
315
- out = option_sanitize(opts, list, first: flag == :exec).first
328
+ out = option_sanitize(opts, list, args: flag == :run || flag == :exec).first
316
329
  from = :"container:#{flag}"
317
330
  case flag
318
331
  when :run, :exec
@@ -348,12 +361,12 @@ module Squared
348
361
  cmd << "--mount type=#{tmpfs ? 'tmpfs' : 'bind'},#{args.join(',')}"
349
362
  end
350
363
  end
351
- append_command(flag, id.to_s.empty? ? tag : id, out, target: cmd, from: from)
364
+ append_command(flag, id.to_s.empty? ? tagmain : id, out, from: from)
352
365
  when :update
353
366
  raise_error('missing container', hint: from) if out.empty?
354
367
  append_value(out, escape: true)
355
368
  when :commit
356
- latest = out.shift || tag
369
+ latest = out.shift || tagmain
357
370
  cmd << id << latest
358
371
  raise_error("unknown args: #{out.join(', ')}", hint: from) unless out.empty?
359
372
  return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
@@ -364,17 +377,16 @@ module Squared
364
377
 
365
378
  opts = []
366
379
  append_option('platform', target: opts, equals: true)
367
- case option('disable-content-trust', ignore: false)
368
- when 'true', '1'
369
- opts << 'disable-content-trust'
370
- when 'false', '0'
371
- opts << 'disable-content-trust=false'
372
- end
380
+ opts << case option('disable-content-trust', ignore: false)
381
+ when 'false', '0'
382
+ '--disable-content-trust=false'
383
+ else
384
+ '--disable-content-trust'
385
+ end
373
386
  opts << '--quiet' unless verbose
374
387
  return image(:push, opts, id: latest, registry: registry)
375
388
  else
376
389
  if out.empty?
377
- ps = docker_output 'ps', '-a'
378
390
  status = []
379
391
  no = true
380
392
  case flag
@@ -399,7 +411,7 @@ module Squared
399
411
  when :rm
400
412
  status = %w[created exited dead]
401
413
  end
402
- ps.merge(status.map { |s| "--filter=\"status=#{s}\"" })
414
+ ps = docker_output('ps -a', *status.map { |s| "--filter=\"status=#{s}\"" })
403
415
  list_image(flag, ps, no: no, hint: "status: #{status.join(', ')}", from: from) do |img|
404
416
  run(cmd.temp(img), from: from)
405
417
  end
@@ -412,7 +424,7 @@ module Squared
412
424
  end
413
425
 
414
426
  def image(flag, opts = [], sync: true, id: nil, registry: nil)
415
- cmd, opts = docker_session('image', flag == :exec ? :list : flag, opts: opts)
427
+ cmd, opts = docker_session('image', flag, opts: opts)
416
428
  out = option_sanitize(opts, OPT_DOCKER[:image][flag]).first
417
429
  from = :"image:#{flag}"
418
430
  case flag
@@ -420,13 +432,8 @@ module Squared
420
432
  if opts.size == out.size
421
433
  index = 0
422
434
  name = nil
423
- opts.each do |opt|
424
- if (name = opt[/^name=["']?(.+)["']?$/, 1])
425
- opts.delete(opt)
426
- break
427
- end
428
- end
429
- list_image(flag, cmd << '-a', from: from) do |val|
435
+ opts.reverse_each { |opt| break opts.delete(opt) if (name = opt[/^name=["']?(.+?)["']?$/, 1]) }
436
+ list_image(:run, cmd << '-a', from: from) do |val|
430
437
  container(:run, if name
431
438
  opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
432
439
  else
@@ -451,8 +458,8 @@ module Squared
451
458
  return
452
459
  end
453
460
  when :push
454
- id ||= tag
455
- registry ||= out.shift || @registry
461
+ id ||= option('tag', ignore: false) || tagmain
462
+ registry ||= out.shift || option('registry') || @registry
456
463
  raise_error(id ? "unknown args: #{out.join(', ')}" : 'no id/tag given', hint: from) unless id && out.empty?
457
464
  raise_error('username/registry not provided', hint: from) unless registry
458
465
  registry.chomp!('/')
@@ -466,6 +473,15 @@ module Squared
466
473
  run(sync: sync, from: from)
467
474
  end
468
475
 
476
+ def network(flag, opts = [], target: nil)
477
+ cmd, opts = docker_session('network', flag, opts: opts)
478
+ option_sanitize(opts, OPT_DOCKER[:network][flag]).first
479
+ from = :"network:#{flag}"
480
+ list_image(flag, docker_output('ps -a'), from: from) do |img|
481
+ puts 'Success' if run(cmd.temp(target, img), from: from) == true && stdout? && banner?
482
+ end
483
+ end
484
+
469
485
  def build?
470
486
  @output[0] != false && dockerfile.exist?
471
487
  end
@@ -492,14 +508,12 @@ module Squared
492
508
  private
493
509
 
494
510
  def docker_session(*cmd, opts: nil)
495
- if opts
496
- out = []
497
- opts = option_sanitize(opts, OPT_DOCKER[:common], target: out).first
498
- ret = session('docker', *out, *cmd)
499
- [ret, opts]
500
- else
501
- session('docker', *cmd)
502
- end
511
+ return session('docker', *cmd) unless opts
512
+
513
+ out = []
514
+ opts = option_sanitize(opts, OPT_DOCKER[:common], target: out).first
515
+ ret = session('docker', *out, *cmd)
516
+ [ret, opts]
503
517
  end
504
518
 
505
519
  def docker_output(*cmd, **kwargs)
@@ -507,7 +521,15 @@ module Squared
507
521
  end
508
522
 
509
523
  def append_command(flag, val, list, target: @session, from: nil)
510
- raise_error('no command args', hint: from) if flag == :exec && list.empty?
524
+ case flag
525
+ when :run
526
+ unless session_arg?('name')
527
+ require 'random/formatter'
528
+ target << basic_option('name', dnsname("#{name}_#{Random.new.alphanumeric(6)}"))
529
+ end
530
+ when :exec
531
+ raise_error('no command args', hint: from) if list.empty?
532
+ end
511
533
  target << val << list.shift
512
534
  target << shell_quote(list.join(' && '), double: true, option: false) unless list.empty?
513
535
  end
@@ -525,25 +547,43 @@ module Squared
525
547
  target << contextdir(ctx || context)
526
548
  end
527
549
 
550
+ def append_tag(val, target: @session)
551
+ ver = option('version', ignore: false)
552
+ list = case val
553
+ when String
554
+ val.split(',')
555
+ when Array
556
+ val
557
+ else
558
+ []
559
+ end
560
+ list.each do |s|
561
+ s = "#{s.sub(/:latest$/, '')}:#{ver}" if ver && (!s.include?(':') || s.end_with?(':latest'))
562
+ target << basic_option('tag', tagname(s))
563
+ end
564
+ end
565
+
528
566
  def list_image(flag, cmd, hint: nil, from: nil, no: true, &blk)
529
567
  pwd_set do
530
568
  found = false
531
- IO.popen(session_done(cmd << '--format=json')).each_with_index do |line, index|
569
+ index = 0
570
+ pat = /^(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':').first})(?:[_.,:-]|$)/
571
+ IO.popen(session_done(cmd << '--format=json')).each do |line|
532
572
  data = JSON.parse(line)
533
573
  id = data['ID']
534
574
  rt = [data['Repository'], data['Tag']].reject { |val| val == '<none>' }.join(':')
535
575
  rt = nil if rt.empty?
536
- aa = if data['Names']
537
- as_a(data['Names']).join(', ')
538
- elsif rt
539
- dd = true
540
- data['Repository']
541
- else
542
- id
543
- end
576
+ aa = data['Names'] || (if rt && data['Repository']
577
+ dd = true
578
+ data['Repository']
579
+ else
580
+ id
581
+ end)
582
+ ee = data['Image'] || rt || aa
583
+ next unless ee.match?(pat) || aa.match?(pat)
584
+
544
585
  bb = index.succ.to_s
545
586
  cc = bb.size + 1
546
- ee = data['Image'] || rt || aa
547
587
  a = sub_style(ee, styles: theme[:inline])
548
588
  b = "Execute #{sub_style(flag, styles: theme[:active])} on #{a}#{ee == id ? '' : " (#{id})"}"
549
589
  c, d = no ? ['y/N', 'N'] : ['Y/n', 'Y']
@@ -553,14 +593,22 @@ module Squared
553
593
  h = "#{sub_style(bb.rjust(cc), styles: theme[:current])} #{f} "
554
594
  puts unless index == 0
555
595
  puts "#{h + sub_style(aa, styles: theme[:subject])} (created #{e} ago)"
556
- %w[Tag Status Ports Size].each do |key|
596
+ cols = %w[Tag Status Ports]
597
+ cols << case flag
598
+ when :connect, :disconnect
599
+ 'Networks'
600
+ else
601
+ 'Size'
602
+ end
603
+ cols.each do |key|
557
604
  next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
558
605
 
559
606
  puts "#{g + f} #{key}: #{as_a(data[key]).join(', ')}" unless data[key].to_s.empty?
560
607
  end
561
- w = 9 + flag.to_s.size + 4 + aa.size
608
+ w = 9 + flag.to_s.size + 4 + ee.size
562
609
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
563
610
  found = true
611
+ index += 1
564
612
  next unless confirm("#{h + b}? [#{c}] ", d, timeout: 60)
565
613
 
566
614
  puts if @@print_order == 0
@@ -611,6 +659,25 @@ module Squared
611
659
  val && projectpath?(val) ? shell_quote(basepath(val)) : '.'
612
660
  end
613
661
 
662
+ def tagname(val)
663
+ val = val.split(':').map { |s| charname(s.sub(/^\W+/, '')) }
664
+ ret = val.join(':')
665
+ ret = val.first if val.size > 1 && ret.size > 128
666
+ ret[0..127]
667
+ end
668
+
669
+ def dnsname(val)
670
+ charname(val[/^[^a-z\d]*(.*?)[^a-z\d]*$/i, 1].gsub(/-{2,}/, '-'))[0..62].downcase
671
+ end
672
+
673
+ def charname(val)
674
+ val.gsub(/[^\w.-]+/, '_')
675
+ end
676
+
677
+ def tagmain
678
+ tag.is_a?(Array) ? tag.first : tag
679
+ end
680
+
614
681
  def compose?(file)
615
682
  COMPOSEFILE.include?(File.basename(file))
616
683
  end