squared 0.8.2 → 0.8.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.
@@ -13,34 +13,35 @@ module Squared
13
13
  buildx: {
14
14
  common: %w[builder=b D|debug],
15
15
  build: %w[add-host=q annotation=q attest=q build-arg=qq build-context=qq cache-from=q cache-to=q
16
- cgroup-parent=b iidfile=p label=q network=b no-cache-filter=b o|output=q platform=q
17
- q|quiet secret=qq shm-size=b ssh=qq t|tag=b target=b ulimit=q].freeze,
18
- bake: %w[print list=q set=q].freeze,
19
- shared: %w[check load no-cache pull push allow=q call=b? f|file=p metadata-file=p progress=b provenance=q
20
- sbom=q].freeze
16
+ cgroup-parent=b iidfile=p label=q network=b no-cache-filter=b o|output=q platform=q q|quiet
17
+ resource=q secret=qq shm-size=b ssh=qq t|tag=b target=b ulimit=q].freeze,
18
+ bake: %w[print list=q set=qq var=qq].freeze,
19
+ shared: %w[check load no-cache pull push allow=q call=b? f|file=p metadata-file=p policy=q progress=b
20
+ provenance=q sbom=q].freeze
21
21
  }.freeze,
22
22
  compose: {
23
23
  common: %w[all-resources ansi=b compatibility dry-run env-file=p f|file=p parallel=n profile=b progress=b
24
- project-directory=p p|project-name=e].freeze,
24
+ project-directory=p p|project-name=b].freeze,
25
25
  build: %w[check no-cache print pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b
26
26
  provenance=q sbom=q ssh=qq].freeze,
27
27
  create: %w[build force-recreate no-build no-recreate quiet-pull remove-orphans y|yes pull=b scale=i].freeze,
28
- exec: %w[d|detach privileged e|env=qq index=i T|no-TTY=b? user=e w|workdir=q].freeze,
28
+ exec: %w[d|detach privileged e|env=qq index=i T|no-tty user=b w|workdir=q].freeze,
29
29
  run: %w[build d|detach no-deps q|quiet quiet-build quiet-pull remove-orphans rm P|service-ports use-aliases
30
30
  cap-add=b cap-drop=b entrypoint=q e|env=qq env-from-file=p i|interactive=b? l|label=q name=b
31
- T|no-TTY=b? p|publish=q pull=b u|user=e v|volume=q w|workdir=q].freeze,
31
+ T|no-tty p|publish=q pull=b u|user=b v|volume=qq w|workdir=q].freeze,
32
32
  up: %w[abort-on-container-exit abort-on-container-failure always-recreate-deps attach-dependencies build
33
33
  d|detach force-recreate menu no-build no-color no-deps no-log-prefix no-recreate no-start quiet-build
34
34
  quiet-pull remove-orphans V|renew-anon-volumes timestamps wait w|watch y|yes attach=b
35
35
  exit-code-from=b no-attach=b pull=b scale=i t|timeout=i wait-timeout=i].freeze,
36
36
  down: %w[remove-orphans v|volumes rmi=b t|timeout=i].freeze,
37
- publish: %w[app resolve-image-digests with-env y|yes oci-version=b].freeze
37
+ publish: %w[app resolve-image-digests with-env y|yes oci-version=b].freeze,
38
+ logs: %w[dry-run f|follow no-color no-log-prefix t|timestamps index=i since=q n|tail=b until=q].freeze
38
39
  }.freeze,
39
40
  container: {
40
41
  create: %w[init i|interactive no-healthcheck oom-kill-disable privileged P|publish-all q|quiet read-only
41
- rm t|tty use-api-socket add-host=q annotation=q a|attach=b blkio-weight=i blkio-weight-device=i
42
- cap-add=b cap-drop=b cgroup-parent=b cgroupns=b cidfile=p device=q device-cgroup-rule=q
43
- device-read-bps=q device-read-iops=q device-write-bps=q device-write-iops=q
42
+ rm t|tty use-api-socket add-host=q annotation=q a|attach=b blkio-weight=i
43
+ blkio-weight-device=i cap-add=b cap-drop=b cgroup-parent=b cgroupns=b cidfile=p device=q
44
+ device-cgroup-rule=q device-read-bps=q device-read-iops=q device-write-bps=q device-write-iops=q
44
45
  dns=q dns-option=q dns-search=q domainname=b entrypoint=q e|env=qq env-file=p expose=q gpus=q
45
46
  group-add=b health-cmd=q health-interval=b health-retries=i health-start-interval=q
46
47
  health-start-period=q health-timeout=q hostname=q io-maxbandwidth=b io-maxiops=b ip=b ip6=q ipc=b
@@ -48,26 +49,28 @@ module Squared
48
49
  m|memory=b memory-reservation=b memory-swap=n memory-swappiness=n mount=qq name=b network=b
49
50
  network-alias=b oom-score-adj=b pid=b pids-limit=n platform=q p|publish=q pull=b restart=b
50
51
  runtime=b security-opt=q shm-size=b stop-signal=b stop-timeout=i storage-opt=q sysctl=q tmpfs=q
51
- ulimit=q u|user=b userns=b uts=b v|volume=q volume-driver=b volumes-from=b w|workdir=q].freeze,
52
+ ulimit=q u|user=b userns=b uts=b v|volume=qq volume-driver=b volumes-from=b w|workdir=q].freeze,
52
53
  run: %w[d|detach detach-keys=q sig-proxy=b?].freeze,
53
54
  update: %w[blkio-weight=i cpu-period=i cpu-quota=i cpu-rt-period=i cpu-rt-runtime=i c|cpu-shares=i cpus=f
54
55
  cpuset-cpus=b cpuset-mems=b m|memory=b memory-reservation=b memory-swap=n pids-limit=n
55
56
  restart=q].freeze,
56
- exec: %w[d|detach i|interactive privileged t|tty detach-keys=q e|env=qq env-file=p u|user=e
57
+ exec: %w[d|detach i|interactive privileged t|tty detach-keys=q e|env=qq env-file=p u|user=b
57
58
  w|workdir=q].freeze,
58
- commit: %w[no-pause a|author=q c|change=q m|message=q pause=b?].freeze,
59
+ rm: %w[f|force l|link v|volumes].freeze,
60
+ commit: %w[no-pause a|author=q c|change=q m|message=q].freeze,
61
+ logs: %w[details f|follow t|timestamps since=q n|tail=b until=q].freeze,
59
62
  inspect: %w[s|size f|format=q].freeze,
60
63
  start: %w[a|attach i|interactive detach-keys=q].freeze,
61
64
  stop: %w[s|signal=b t|timeout=i].freeze,
62
65
  restart: %w[s|signal=b t|timeout=i].freeze,
63
66
  kill: %w[s|signal=b].freeze,
64
67
  stats: %w[a|all no-stream no-trunc format|q].freeze,
65
- attach: %w[no-stdin detach-keys=q sig-proxy=!?].freeze
68
+ attach: %w[no-stdin detach-keys=q sig-proxy].freeze
66
69
  }.freeze,
67
70
  image: {
68
71
  common: %w[platform=q].freeze,
69
72
  inspect: %w[f|format].freeze,
70
- history: %w[f|format no-trunc q|quiet H|human=!?].freeze,
73
+ history: %w[f|format no-trunc q|quiet H|human].freeze,
71
74
  ls: %w[a|all digests no-trunc q|quiet tree f|filter=q format=q].freeze,
72
75
  pull: %w[a|all-tags q|quiet].freeze,
73
76
  push: %w[a|all-tags q|quiet].freeze,
@@ -106,6 +109,10 @@ module Squared
106
109
  [].freeze
107
110
  end
108
111
 
112
+ def bannerargs
113
+ %i[context registry tag].freeze
114
+ end
115
+
109
116
  def config?(val)
110
117
  return false unless (val = as_path(val))
111
118
 
@@ -115,46 +122,43 @@ module Squared
115
122
 
116
123
  subtasks({
117
124
  'build' => %i[tag context].freeze,
118
- 'compose' => %i[build create publish run exec up down service].freeze,
125
+ 'compose' => %i[build create publish run exec up down logs service].freeze,
119
126
  'bake' => %i[build compose check].freeze,
120
127
  'image' => %i[ls rm pull push tag save inspect history].freeze,
121
- 'container' => %i[attach commit create diff exec inspect kill pause restart rm run start stats stop top
128
+ 'container' => %i[attach commit create diff exec inspect kill logs pause restart rm run start stats stop top
122
129
  unpause update].freeze,
123
130
  'network' => %i[connect disconnect create].freeze,
124
131
  'ls' => nil
125
132
  })
126
133
 
127
134
  attr_reader :context
128
- attr_accessor :tag
135
+ attr_accessor :registry, :tag
129
136
 
130
- def initialize(*, client: nil, mounts: [], **kwargs)
137
+ def initialize(*, **kwargs)
131
138
  super
132
139
  self.global = nil
133
140
  return unless dockerfile(kwargs[:file]).exist?
134
141
 
135
- @tag = kwargs[:tag] || tagname("#{@project}:#{@version || 'latest'}")
142
+ @tag = env('TAG', strict: true,
143
+ ignore: false) || kwargs[:tag] || tagname(@project, @version || 'latest')
136
144
  @context = kwargs[:context]
137
- @client = if client
138
- opts = []
139
- client.each do |key, val|
140
- val = case (key = key.to_s)
141
- when 'config'
142
- basepath!(val, type: 'd')
143
- when 'tlscacert', 'tlscert', 'tlskey'
144
- basepath!(val, type: 'f')
145
- when 'context', 'host', 'log-level'
146
- quote_option(key, val)
147
- when 'debug', 'tls', 'tlsverify'
148
- opts << key
149
- next
150
- end
151
- opts << quote_option(key, val) if val
152
- end
153
- opts unless opts.empty?
154
- end
155
- @mounts = mounts
145
+ @client = kwargs[:client]&.each_with_object([]) do |(key, val), out|
146
+ val = case (key = key.to_s)
147
+ when 'config'
148
+ basepath!(val, type: 'd')
149
+ when 'tlscacert', 'tlscert', 'tlskey'
150
+ basepath!(val, type: 'f')
151
+ when 'context', 'host', 'log-level'
152
+ quote_option(key, val)
153
+ when 'debug', 'tls', 'tlsverify'
154
+ out << key
155
+ next
156
+ end
157
+ out << quote_option(key, val) if val
158
+ end
159
+ @mounts = Array(kwargs[:mounts])
156
160
  @secrets = kwargs[:secrets]
157
- @registry = tagjoin kwargs[:registry], kwargs[:username]
161
+ self.registry = tagjoin kwargs[:registry], kwargs[:username]
158
162
  @oci = tagjoin kwargs[:oci], kwargs[:username]
159
163
  initialize_ref Docker.ref
160
164
  initialize_logger kwargs[:log]
@@ -227,7 +231,7 @@ module Squared
227
231
  format_desc action, flag, 'opts*,target*,context?|:'
228
232
  task flag do |_, args|
229
233
  args = args.to_a
230
- if args.first == ':'
234
+ if choice!(args)
231
235
  choice_command :bake
232
236
  else
233
237
  buildx(:bake, args, from: (:'buildx:bake' if flag == :compose))
@@ -251,14 +255,14 @@ module Squared
251
255
  compose_(flag, args.extras, service: service)
252
256
  end
253
257
  when :service
254
- cmds = %w[down kill pause restart rm start stop top unpause watch].freeze
258
+ cmds = %w[down kill logs pause restart rm start stop top unpause watch].freeze
255
259
  format_desc(action, flag, cmds, arg: nil, after: 'name+|:')
256
260
  task flag, [:command] do |_, args|
257
261
  cmd = param_guard(action, flag, args: args, key: :command)
258
262
  raise ArgumentError, message('unrecognized command', hint: cmd) unless cmds.include?(cmd)
259
263
 
260
264
  service = args.extras
261
- if service.first == ':'
265
+ if choice!(service)
262
266
  choice_command flag, cmd
263
267
  else
264
268
  compose_(flag, [cmd], service: service.empty? || service)
@@ -299,7 +303,11 @@ module Squared
299
303
  end
300
304
  end
301
305
  else
302
- format_desc action, flag, "opts*,id/name#{flag == :update || flag == :inspect ? '+' : '*'}"
306
+ format_desc action, flag, "opts*,id/name#{case flag
307
+ when :stats then '*'
308
+ when :logs, :diff, :attach, :top then nil
309
+ else '+'
310
+ end}"
303
311
  task flag do |_, args|
304
312
  container flag, args.to_a
305
313
  end
@@ -307,11 +315,13 @@ module Squared
307
315
  when 'image'
308
316
  case flag
309
317
  when :push
310
- next unless @registry
311
-
312
- format_desc action, flag, 'tag?,registry/username?,opts*'
318
+ format_desc action, flag, 'tag?,registry/username?|:,opts*'
313
319
  task flag do |_, args|
314
- image flag, args.to_a
320
+ args = args.to_a
321
+ if choice!(args, first: false) || !self.registry
322
+ registry = readline('Enter registry/username', force: true)
323
+ end
324
+ image(flag, args, id: id, registry: registry)
315
325
  end
316
326
  when :pull
317
327
  format_desc action, flag, 'tag,registry/username?,opts*'
@@ -411,19 +421,13 @@ module Squared
411
421
  when String
412
422
  ret << quote_option('secret', @secrets, double: true)
413
423
  when Hash
414
- append = lambda do |key|
424
+ %i[file env].each do |key|
415
425
  ret.merge(Array(@secrets[key]).map { |arg| quote_option('secret', "type=#{key},#{arg}", double: true) })
416
426
  end
417
- append.call(:file)
418
- append.call(:env)
419
427
  else
420
428
  ret.merge(Array(@secrets).map { |arg| quote_option('secret', arg) })
421
429
  end
422
- if (val = option('tag', ignore: false))
423
- append_tag val
424
- elsif !session_arg?('t', 'tag')
425
- append_tag tag
426
- end
430
+ append_tag option('tag', ignore: false) or session_arg?('t', 'tag') ? nil : tag
427
431
  append_context
428
432
  when :bake, :compose
429
433
  option(from == :bake ? 'target' : 'service', ignore: false) { |val| ret.merge(split_escape(val).quote!) }
@@ -432,18 +436,18 @@ module Squared
432
436
  end
433
437
 
434
438
  def buildx(flag, opts = [], tag: nil, context: nil, from: nil)
439
+ bx = OPT_DOCKER[:buildx]
435
440
  if flag == :bake && context&.match?(%r{^["']?(https?|git)://}i)
436
- shared = data[:shared].dup.push('f|file=q')
441
+ shared = bx[:shared].dup.push('f|file=q')
437
442
  shared.delete('f|file=p')
438
443
  end
439
444
  cmd, opts = docker_session('buildx', opts: opts)
440
- data = OPT_DOCKER[:buildx]
441
- op = OptionPartition.new(opts, data[:common], cmd, project: self, strict: strict?)
445
+ op = OptionPartition.new(opts, bx[:common], cmd, **dockerinit)
442
446
  op.append(flag, quote: false)
443
- .parse(data[flag == :bake ? :bake : :build] + (shared || data[:shared]))
447
+ .parse(bx[flag == :bake ? :bake : :build] + (shared || bx[:shared]))
444
448
  case flag
445
449
  when :build, :context
446
- append_tag(tag || option('tag', ignore: false) || self.tag)
450
+ append_tag tag || option('tag', ignore: false) || self.tag
447
451
  if shared
448
452
  op.add_quote(context, preserve: false)
449
453
  else
@@ -451,6 +455,10 @@ module Squared
451
455
  end
452
456
  when :bake
453
457
  append_file(0, index: 3) unless from || op.arg?('f', 'file') || !anypath?(*COMPOSEFILE)
458
+ if op.first == '-'
459
+ op.shift
460
+ op << @output[4] if @output[4]
461
+ end
454
462
  unless op.empty?
455
463
  if !context && op.size > 1 && exist?(op.last, type: 'd')
456
464
  pat = /\btarget\s+"#{Regexp.escape(op.last)}"/
@@ -460,7 +468,7 @@ module Squared
460
468
  context = basepath context
461
469
  op.each { |name| op.add_option('set', "#{name}.context=#{context}", escape: false) }
462
470
  end
463
- op.append(escape: true, strip: /^:/, clear: true)
471
+ op.append(escape: true, strip: ':', clear: true)
464
472
  end
465
473
  end
466
474
  op.clear(pass: false)
@@ -472,7 +480,7 @@ module Squared
472
480
  if flag == :service
473
481
  command = opts.first
474
482
  if service == true
475
- cmd, status = filter_ps command, from
483
+ cmd, status = filter_ps(command, from: from)
476
484
  lines = IO.popen(cmd.temp('--services')).map(&:strip).reject(&:empty?)
477
485
  return list_empty(hint: status) if lines.empty?
478
486
 
@@ -481,10 +489,11 @@ module Squared
481
489
  docker_session('compose', command, '--', *service)
482
490
  else
483
491
  cmd, opts = docker_session('compose', opts: opts)
484
- op = OptionPartition.new(opts, OPT_DOCKER[:compose][:common], cmd, project: self, strict: strict?)
492
+ cp = OPT_DOCKER[:compose]
493
+ op = OptionPartition.new(opts, cp[:common], cmd, **dockerinit)
485
494
  append_file(filetype, force: flag == :publish) unless op.arg?('f', 'file')
486
495
  op << flag
487
- op.parse(OPT_DOCKER[:compose].fetch(flag, []))
496
+ op.parse(cp.fetch(flag, []))
488
497
  if flag == :publish
489
498
  id ||= option('tag', ignore: false) || op.shift || tagmain
490
499
  registry ||= option('registry') || op.shift || @oci
@@ -502,7 +511,7 @@ module Squared
502
511
  end
503
512
  if multiple
504
513
  op.concat(service) if service
505
- op.append(delim: true, escape: true, strip: /^:/)
514
+ op.append(delim: true, escape: true, strip: ':')
506
515
  else
507
516
  raise ArgumentError, message('no service was selected', hint: flag) unless service
508
517
 
@@ -515,18 +524,19 @@ module Squared
515
524
 
516
525
  def container(flag, opts = [], id: nil)
517
526
  cmd, opts = docker_session('container', flag, opts: opts)
518
- data = OPT_DOCKER[:container]
519
- list = data.fetch(flag, [])
520
- list += data[:create] if (rc = flag == :run)
521
- list += data[:update] if rc ||= flag == :create
522
- op = OptionPartition.new(opts, list, cmd, project: self, strict: strict?, args: rc || flag == :exec)
527
+ rc = flag == :run
528
+ ct = OPT_DOCKER[:container]
529
+ list = ct.fetch(flag, [])
530
+ list += ct[:create] if rc
531
+ list += ct[:update] if rc ||= flag == :create
532
+ op = OptionPartition.new(opts, list, cmd, args: rc || flag == :exec, **dockerinit)
523
533
  from = symjoin 'container', flag
524
534
  case flag
525
535
  when :run, :create, :exec
526
536
  if rc && !op.arg?('mount')
527
537
  all = collect_hash VAL_DOCKER[:run]
528
538
  delim = Regexp.new(",\\s*(?=#{all.join('|')})")
529
- Array(@mounts).each do |val|
539
+ @mounts.each do |val|
530
540
  type = nil
531
541
  args = val.split(delim).each_with_object([]) do |opt, out|
532
542
  k, v, q = OptionPartition.parse_option(opt)
@@ -570,14 +580,14 @@ module Squared
570
580
  when :update, :inspect
571
581
  raise ArgumentError, message('missing container', hint: flag) if op.empty?
572
582
 
573
- op.append(escape: true, strip: /^:/)
583
+ op.append(escape: true, strip: ':')
574
584
  when :commit
575
585
  latest = op.shift || tagmain
576
586
  cmd << id << latest
577
587
  raise ArgumentError, message('unrecognized args', op.join(', '), hint: flag) unless op.empty?
578
588
  return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
579
589
 
580
- registry = option('registry') || @registry
590
+ registry = option('registry') || self.registry
581
591
  run(from: from, exception: registry.nil? ? exception? : true)
582
592
  return unless registry
583
593
 
@@ -593,23 +603,34 @@ module Squared
593
603
  return image(:push, opts, id: latest, registry: registry)
594
604
  else
595
605
  if op.empty?
596
- ps, status, no = filter_ps flag, from
606
+ ps, status, no = filter_ps(flag, from: from)
597
607
  cmd << '--no-stream' if flag == :stats
598
608
  list_image(flag, ps, no: no, hint: status, multiple: flag != :attach, from: from) do |img|
599
609
  run(cmd.temp(img), from: from)
600
610
  end
601
611
  return
602
612
  end
603
- op.append(escape: true, strip: /^:/)
613
+ case flag
614
+ when :logs, :diff, :attach, :top
615
+ op.add_first(escape: true, strip: ':')
616
+ if flag == :top
617
+ op.append(delim: true)
618
+ else
619
+ op.clear
620
+ end
621
+ else
622
+ op.append(escape: true, strip: ':')
623
+ end
604
624
  end
605
625
  run(from: from)
606
626
  end
607
627
 
608
628
  def image(flag, opts = [], sync: true, id: nil, registry: nil, filter: nil)
609
629
  cmd, opts = docker_session('image', flag, opts: opts)
610
- list = OPT_DOCKER[:image].fetch(flag, [])
611
- list += OPT_DOCKER[:image][:common] unless flag == :ls || flag == :tag
612
- op = OptionPartition.new(opts, list, cmd, project: self, strict: strict?)
630
+ im = OPT_DOCKER[:image]
631
+ list = im.fetch(flag, [])
632
+ list += im[:common] unless flag == :ls || flag == :tag
633
+ op = OptionPartition.new(opts, list, cmd, **dockerinit)
613
634
  exception = exception?
614
635
  banner = true
615
636
  from = symjoin 'image', flag
@@ -626,11 +647,18 @@ module Squared
626
647
  break
627
648
  end
628
649
  list_image(:run, filter: filter, from: from) do |val|
629
- container(:run, if name
630
- opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
631
- else
632
- opts
633
- end, id: val)
650
+ args = if name
651
+ opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
652
+ elsif choice!(opts, only: true)
653
+ OptionPartition.strip(readline('Enter arguments', force: false))
654
+ else
655
+ opts
656
+ end
657
+ if args.none?(/^d(etach)?$/)
658
+ Thread.new(args.dup, val) { |a, id| container(:run, a, id: id) }.join
659
+ break
660
+ end
661
+ container(:run, args, id: val)
634
662
  index += 1
635
663
  end
636
664
  return
@@ -656,7 +684,7 @@ module Squared
656
684
  op << val
657
685
  found = true
658
686
  if flag == :tag
659
- op << tagname("#{project}:#{op.first}")
687
+ op << tagname(project, op.first)
660
688
  break
661
689
  end
662
690
  end
@@ -665,36 +693,36 @@ module Squared
665
693
  if !id
666
694
  id = tagmain
667
695
  elsif !op.arg?('a', 'all-tags') && !id.include?(':')
668
- id = "#{project}:#{id}"
696
+ id = tagname project, id
669
697
  end
670
698
  unless registry
671
699
  registry = op.shift
672
- registry ||= option('registry') || @registry unless id.include?('/')
700
+ registry ||= option('registry') || self.registry unless id.include?('/')
673
701
  end
674
702
  cmd << shell_quote(tagjoin(registry, id))
675
703
  when :push
676
704
  id ||= option('tag', ignore: false) || op.shift || tagmain
677
- registry ||= option('registry') || op.shift || @registry
705
+ registry ||= option('registry') || op.shift || self.registry
678
706
  emptyargs op, flag
679
707
  raise ArgumentError, message('username/registry not specified', hint: flag) unless registry
680
708
 
681
709
  uri = shell_quote tagjoin(registry, id)
682
710
  op << uri
683
- img = docker_output 'image', 'tag', id, uri
711
+ img = docker_output 'image tag', id, uri
684
712
  return unless confirm_command(img.to_s, cmd.to_s, target: id, as: registry, title: from)
685
713
 
686
714
  @session = img
687
715
  sync = false
688
- exception ||= true
716
+ exception = true
689
717
  banner = false
690
718
  when :inspect, :history
691
719
  raise ArgumentError, message('missing image', hint: flag) if op.empty?
692
720
 
693
721
  if flag == :history
694
- op.add_first(escape: true, strip: /^:/)
722
+ op.add_first(escape: true, strip: ':')
695
723
  .clear
696
724
  else
697
- op.append(escape: true, strip: /^:/)
725
+ op.append(escape: true, strip: ':')
698
726
  end
699
727
  end
700
728
  success?(run(sync: sync, exception: exception, banner: banner, from: from), flag == :tag || flag == :save)
@@ -702,7 +730,7 @@ module Squared
702
730
 
703
731
  def network(flag, opts = [], target: nil)
704
732
  cmd, opts = docker_session('network', flag, opts: opts)
705
- op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self, strict: strict?)
733
+ op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, **dockerinit)
706
734
  .clear
707
735
  from = symjoin 'network', flag
708
736
  if flag == :create
@@ -753,7 +781,7 @@ module Squared
753
781
 
754
782
  def read_composefile(*keys, target: nil)
755
783
  require 'yaml'
756
- target = ENV['COMPOSE_FILE']&.split(workspace.windows? ? ';' : ':') unless target && !target.empty?
784
+ target = ENV['COMPOSE_FILE']&.split(windows? ? ';' : ':') unless target && !target.empty?
757
785
  Array(target || dockerfile).each do |val|
758
786
  doc = YAML.load_file(basepath(val))
759
787
  if keys.empty?
@@ -770,7 +798,7 @@ module Squared
770
798
  opts = OptionPartition.union(Array(opts), @client) if @client
771
799
  return session('docker', *cmd) unless opts
772
800
 
773
- op = OptionPartition.new(opts, OPT_DOCKER[:common], project: self, strict: strict?)
801
+ op = OptionPartition.new(opts, OPT_DOCKER[:common], **dockerinit)
774
802
  [session('docker', *op.to_a, *cmd), op.extras]
775
803
  end
776
804
 
@@ -778,12 +806,12 @@ module Squared
778
806
  session('docker', *cmd, main: false, options: false, **kwargs)
779
807
  end
780
808
 
781
- def append_command(flag, val, list, target: @session, prompt: ':')
782
- list << if list.delete(prompt)
783
- readline('Enter command [args]', force: flag == :exec)
784
- else
785
- env('DOCKER_ARGS')
786
- end
809
+ def append_command(flag, id, list, target: @session, prompt: ':')
810
+ if list.delete(prompt)
811
+ list << readline('Enter command [args]', force: flag == :exec)
812
+ elsif (val = env('DOCKER_ARGS'))
813
+ list << val
814
+ end
787
815
  case flag
788
816
  when :run
789
817
  unless session_arg?('name', target: target)
@@ -792,8 +820,22 @@ module Squared
792
820
  when :exec
793
821
  raise ArgumentError, message('nothing to execute', hint: flag) if list.empty?
794
822
  end
795
- target << val << list.shift
796
- target << list.join(' && ') unless list.empty?
823
+ target << id
824
+ case list.first
825
+ when '--'
826
+ target.merge(list.drop(1))
827
+ when /\A(--|&&)(\s|\z)/
828
+ target.merge(list)
829
+ else
830
+ target << list.shift
831
+ unless list.empty?
832
+ if list.first.start_with?('-')
833
+ target.merge(list)
834
+ else
835
+ target << list.join(' && ')
836
+ end
837
+ end
838
+ end
797
839
  end
798
840
 
799
841
  def append_file(type, target: @session, index: 2, force: false)
@@ -825,13 +867,13 @@ module Squared
825
867
  else
826
868
  Array(val)
827
869
  end.each do |s|
828
- s = "#{s}:#{ver}" if ver && (!s.include?(':') || s.delete_suffix!(':latest'))
870
+ s = tagname s, ver if ver && (!s.include?(':') || s.delete_suffix!(':latest'))
829
871
  target << basic_option('tag', tagname(s))
830
872
  end
831
873
  target
832
874
  end
833
875
 
834
- def filter_ps(flag, from = :'container:ps')
876
+ def filter_ps(flag, meth = nil, from: meth || :'container:ps')
835
877
  no = false
836
878
  status = case flag.to_sym
837
879
  when :start
@@ -865,7 +907,7 @@ module Squared
865
907
  pwd_set(from: from) do
866
908
  index = 1
867
909
  all = option('all', prefix: 'docker')
868
- y = from == :'image:rm' && option('y', prefix: 'docker')
910
+ y = from.to_s.end_with?(':rm') && option('y', prefix: 'docker')
869
911
  filter = env('DOCKER_FILTER', filter).to_s
870
912
  pat = if OptionPartition.pattern?(filter)
871
913
  Regexp.new(filter)
@@ -901,17 +943,17 @@ module Squared
901
943
  puts unless index == 1
902
944
  puts (h + sub_style(aa, theme[:subject])).subhint("created #{e} ago")
903
945
  cols = %w[Tag Status Ports]
904
- cols << case flag
905
- when :connect, :disconnect
906
- 'Networks'
907
- else
908
- 'Size'
909
- end
910
- cols.each do |key|
911
- next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
912
-
913
- puts "#{g + f} #{key}: #{Array(data[key]).join(', ')}" unless data[key].to_s.empty?
914
- end
946
+ .push(case flag
947
+ when :connect, :disconnect
948
+ 'Networks'
949
+ else
950
+ 'Size'
951
+ end)
952
+ .reject do |key|
953
+ (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B') || data[key].to_s.empty?
954
+ end
955
+ pad = cols.map(&:size).max
956
+ cols.each { |key| puts "#{g + f} #{key.rjust(pad)}: #{Array(data[key]).join(', ')}" }
915
957
  w = 9 + flag.to_s.size + 4 + ee.size
916
958
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), theme[:inline])
917
959
  index += 1
@@ -994,7 +1036,7 @@ module Squared
994
1036
  cmd = docker_output(ctx, *action)
995
1037
  case flag
996
1038
  when :tag
997
- args = tagjoin @registry, tag
1039
+ args = tagjoin registry, tag
998
1040
  when :save
999
1041
  opts = opts.sub_ext('.tar')
1000
1042
  cmd << quote_option('output', File.expand_path(opts))
@@ -1006,8 +1048,7 @@ module Squared
1006
1048
  cmd << opts << '--'
1007
1049
  end
1008
1050
  cmd.merge(Array(out).map { |val| val.split(/\s+/, 2).first })
1009
- cmd << args
1010
- success?(run(cmd), ctx.start_with?('network', 'tag', 'save'))
1051
+ success?(run(cmd << args, { 'NO_COLOR' => 'true' }), ctx.start_with?('network', 'tag', 'save'))
1011
1052
  end
1012
1053
 
1013
1054
  def filetype(val = dockerfile)
@@ -1034,11 +1075,20 @@ module Squared
1034
1075
  args.map { |val| val.chomp(char) }.join(char) unless args.empty?
1035
1076
  end
1036
1077
 
1037
- def tagname(val)
1038
- val = val.split(':').map { |s| charname(s.sub(/^\W+/, '')) }
1078
+ def tagname(*val, max: 128)
1079
+ val = val.compact
1080
+ .join(':')
1081
+ .split(':')
1082
+ .map { |s| charname(s.sub(/^\W+/, '')) }
1039
1083
  ret = val.join(':')
1040
- ret = val.first if val.size > 1 && ret.size > 128
1041
- ret[0..127]
1084
+ return ret unless ret.size > max
1085
+
1086
+ while val.size > 1
1087
+ val.pop
1088
+ s = val.join(':')
1089
+ return s unless s.size > max
1090
+ end
1091
+ ret[0...max]
1042
1092
  end
1043
1093
 
1044
1094
  def dnsname(val)
@@ -1053,6 +1103,10 @@ module Squared
1053
1103
  tag.is_a?(Array) ? tag.first : tag
1054
1104
  end
1055
1105
 
1106
+ def dockerinit
1107
+ { project: self, strict: strict?, explicit: '=', bool: %w[true false 1 0] }
1108
+ end
1109
+
1056
1110
  def emptyargs(list, hint = nil)
1057
1111
  raise ArgumentError, message('unrecognized args', list.join(', '), hint: hint) unless list.empty?
1058
1112
  end