squared 0.8.3 → 0.8.5

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.
@@ -16,19 +16,19 @@ module Squared
16
16
  cgroup-parent=b iidfile=p label=q network=b no-cache-filter=b o|output=q platform=q q|quiet
17
17
  resource=q secret=qq shm-size=b ssh=qq t|tag=b target=b ulimit=q].freeze,
18
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
19
+ shared: %w[check load no-cache provenance pull push sbom allow=qq call=b? f|file=p metadata-file=p policy=q
20
+ progress=b].freeze
21
21
  }.freeze,
22
22
  compose: {
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,
23
+ common: %w[1 all-resources compatibility dry-run ansi=b env-file=p f|file=p parallel=n profile=b progress=b
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
- 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,
30
+ cap-add=b cap-drop=b entrypoint=q e|env=qq env-from-file=p i|interactive l|label=q name=b T|no-tty
31
+ 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
@@ -39,9 +39,9 @@ module Squared
39
39
  }.freeze,
40
40
  container: {
41
41
  create: %w[init i|interactive no-healthcheck oom-kill-disable privileged P|publish-all q|quiet read-only
42
- rm t|tty use-api-socket add-host=q annotation=q a|attach=b blkio-weight=i blkio-weight-device=i
43
- cap-add=b cap-drop=b cgroup-parent=b cgroupns=b cidfile=p device=q device-cgroup-rule=q
44
- 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
45
45
  dns=q dns-option=q dns-search=q domainname=b entrypoint=q e|env=qq env-file=p expose=q gpus=q
46
46
  group-add=b health-cmd=q health-interval=b health-retries=i health-start-interval=q
47
47
  health-start-period=q health-timeout=q hostname=q io-maxbandwidth=b io-maxiops=b ip=b ip6=q ipc=b
@@ -49,13 +49,14 @@ module Squared
49
49
  m|memory=b memory-reservation=b memory-swap=n memory-swappiness=n mount=qq name=b network=b
50
50
  network-alias=b oom-score-adj=b pid=b pids-limit=n platform=q p|publish=q pull=b restart=b
51
51
  runtime=b security-opt=q shm-size=b stop-signal=b stop-timeout=i storage-opt=q sysctl=q tmpfs=q
52
- ulimit=q u|user=b userns=b uts=b v|volume=q volume-driver=b volumes-from=b w|workdir=q].freeze,
53
- run: %w[d|detach detach-keys=q sig-proxy=b?].freeze,
52
+ ulimit=q u|user=b userns=b uts=b v|volume=qq volume-driver=b volumes-from=b w|workdir=q].freeze,
53
+ run: %w[d|detach detach-keys=q sig-proxy].freeze,
54
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
55
55
  cpuset-cpus=b cpuset-mems=b m|memory=b memory-reservation=b memory-swap=n pids-limit=n
56
56
  restart=q].freeze,
57
- 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
58
58
  w|workdir=q].freeze,
59
+ rm: %w[f|force l|link v|volumes].freeze,
59
60
  commit: %w[no-pause a|author=q c|change=q m|message=q].freeze,
60
61
  logs: %w[details f|follow t|timestamps since=q n|tail=b until=q].freeze,
61
62
  inspect: %w[s|size f|format=q].freeze,
@@ -64,12 +65,12 @@ module Squared
64
65
  restart: %w[s|signal=b t|timeout=i].freeze,
65
66
  kill: %w[s|signal=b].freeze,
66
67
  stats: %w[a|all no-stream no-trunc format|q].freeze,
67
- attach: %w[no-stdin detach-keys=q sig-proxy=!?].freeze
68
+ attach: %w[no-stdin detach-keys=q sig-proxy].freeze
68
69
  }.freeze,
69
70
  image: {
70
71
  common: %w[platform=q].freeze,
71
72
  inspect: %w[f|format].freeze,
72
- history: %w[f|format no-trunc q|quiet H|human=!?].freeze,
73
+ history: %w[f|format no-trunc q|quiet H|human].freeze,
73
74
  ls: %w[a|all digests no-trunc q|quiet tree f|filter=q format=q].freeze,
74
75
  pull: %w[a|all-tags q|quiet].freeze,
75
76
  push: %w[a|all-tags q|quiet].freeze,
@@ -80,16 +81,17 @@ module Squared
80
81
  connect: %w[alias=b driver-opt=q gw-priority=n ip=b ip6=q link=b link-local-ip=q].freeze,
81
82
  create: %w[attachable config-only gateway=q ingress internal ipv4 ipv6 aux-address=q config-from=b
82
83
  d|driver=b ip-range=q ipam-driver=b ipam-opt=q label=q o|opt=q scope=b subnet=q].freeze,
83
- disconnect: %w[f|force].freeze
84
+ disconnect: %w[f|force].freeze,
85
+ rm: %w[f|force].freeze
84
86
  }.freeze
85
87
  }.freeze
86
88
  VAL_DOCKER = {
87
89
  run: {
88
90
  common: %w[source src destination dst target readonly ro].freeze,
89
- bind: %w[bind-propagation].freeze,
91
+ bind: %w[bind-create-src bind-propagation].freeze,
90
92
  volume: %w[volume-subpath volume-nocopy volume-opt].freeze,
91
93
  tmpfs: %w[tmpfs-size tmpfs-mode].freeze,
92
- image: %w[image-path].freeze
94
+ image: %w[image-subpath].freeze
93
95
  }.freeze,
94
96
  ls: {
95
97
  compose: %w[Name Image Command Service RunningFor Status Ports CreatedAt ExitCode Health ID Labels
@@ -108,6 +110,10 @@ module Squared
108
110
  [].freeze
109
111
  end
110
112
 
113
+ def bannerargs
114
+ %i[context registry tag].freeze
115
+ end
116
+
111
117
  def config?(val)
112
118
  return false unless (val = as_path(val))
113
119
 
@@ -120,41 +126,37 @@ module Squared
120
126
  'compose' => %i[build create publish run exec up down logs service].freeze,
121
127
  'bake' => %i[build compose check].freeze,
122
128
  'image' => %i[ls rm pull push tag save inspect history].freeze,
123
- 'container' => %i[attach commit create diff exec logs top inspect kill pause restart rm run start stats stop
129
+ 'container' => %i[attach commit create diff exec inspect kill logs pause restart rm run start stats stop top
124
130
  unpause update].freeze,
125
- 'network' => %i[connect disconnect create].freeze,
131
+ 'network' => %i[connect disconnect create rm].freeze,
126
132
  'ls' => nil
127
133
  })
128
134
 
129
135
  attr_reader :context
130
- attr_accessor :tag
136
+ attr_accessor :registry, :tag
131
137
 
132
- def initialize(*, client: nil, mounts: [], **kwargs)
138
+ def initialize(*, **kwargs)
133
139
  super
134
140
  self.global = nil
135
141
  return unless dockerfile(kwargs[:file]).exist?
136
142
 
137
- @tag = kwargs[:tag] || tagname("#{@project}:#{@version || 'latest'}")
143
+ @tag = env('TAG', strict: true, ignore: false) || kwargs[:tag] || tagname(@project, @version || 'latest')
138
144
  @context = kwargs[:context]
139
- @client = if client
140
- opts = []
141
- client.each do |key, val|
142
- val = case (key = key.to_s)
143
- when 'config'
144
- basepath!(val, type: 'd')
145
- when 'tlscacert', 'tlscert', 'tlskey'
146
- basepath!(val, type: 'f')
147
- when 'context', 'host', 'log-level'
148
- quote_option(key, val)
149
- when 'debug', 'tls', 'tlsverify'
150
- opts << key
151
- next
152
- end
153
- opts << quote_option(key, val) if val
154
- end
155
- opts unless opts.empty?
156
- end
157
- @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])
158
160
  @secrets = kwargs[:secrets]
159
161
  @registry = tagjoin kwargs[:registry], kwargs[:username]
160
162
  @oci = tagjoin kwargs[:oci], kwargs[:username]
@@ -229,7 +231,7 @@ module Squared
229
231
  format_desc action, flag, 'opts*,target*,context?|:'
230
232
  task flag do |_, args|
231
233
  args = args.to_a
232
- if args.first == ':'
234
+ if choice!(args)
233
235
  choice_command :bake
234
236
  else
235
237
  buildx(:bake, args, from: (:'buildx:bake' if flag == :compose))
@@ -253,14 +255,14 @@ module Squared
253
255
  compose_(flag, args.extras, service: service)
254
256
  end
255
257
  when :service
256
- 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
257
259
  format_desc(action, flag, cmds, arg: nil, after: 'name+|:')
258
260
  task flag, [:command] do |_, args|
259
261
  cmd = param_guard(action, flag, args: args, key: :command)
260
262
  raise ArgumentError, message('unrecognized command', hint: cmd) unless cmds.include?(cmd)
261
263
 
262
264
  service = args.extras
263
- if service.first == ':'
265
+ if choice!(service)
264
266
  choice_command flag, cmd
265
267
  else
266
268
  compose_(flag, [cmd], service: service.empty? || service)
@@ -313,11 +315,13 @@ module Squared
313
315
  when 'image'
314
316
  case flag
315
317
  when :push
316
- next unless @registry
317
-
318
- format_desc action, flag, 'tag?,registry/username?,opts*'
318
+ format_desc action, flag, 'tag?,registry/username?|:,opts*'
319
319
  task flag do |_, args|
320
- 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)
321
325
  end
322
326
  when :pull
323
327
  format_desc action, flag, 'tag,registry/username?,opts*'
@@ -355,11 +359,15 @@ module Squared
355
359
  end
356
360
  end
357
361
  when 'network'
358
- format_desc action, flag, 'target,opts*'
359
- task flag, [:target] do |_, args|
360
- target = flag == :create ? param_guard(action, flag, args: args, key: :target) : args.target
361
- if target
362
- network(flag, args.extras, target: target)
362
+ format_desc action, flag, 'name,opts*'
363
+ task flag, [:name] do |_, args|
364
+ name = if flag == :create || flag == :rm
365
+ param_guard(action, flag, args: args, key: :name)
366
+ else
367
+ args.name
368
+ end
369
+ if name
370
+ network(flag, args.extras, name: name)
363
371
  else
364
372
  choice_command flag
365
373
  end
@@ -423,11 +431,7 @@ module Squared
423
431
  else
424
432
  ret.merge(Array(@secrets).map { |arg| quote_option('secret', arg) })
425
433
  end
426
- if (val = option('tag', ignore: false))
427
- append_tag val
428
- elsif !session_arg?('t', 'tag')
429
- append_tag tag
430
- end
434
+ append_tag option('tag', ignore: false) or session_arg?('t', 'tag') ? nil : tag
431
435
  append_context
432
436
  when :bake, :compose
433
437
  option(from == :bake ? 'target' : 'service', ignore: false) { |val| ret.merge(split_escape(val).quote!) }
@@ -436,15 +440,15 @@ module Squared
436
440
  end
437
441
 
438
442
  def buildx(flag, opts = [], tag: nil, context: nil, from: nil)
443
+ bx = OPT_DOCKER[:buildx]
439
444
  if flag == :bake && context&.match?(%r{^["']?(https?|git)://}i)
440
- shared = data[:shared].dup.push('f|file=q')
445
+ shared = bx[:shared].dup.push('f|file=q')
441
446
  shared.delete('f|file=p')
442
447
  end
443
448
  cmd, opts = docker_session('buildx', opts: opts)
444
- data = OPT_DOCKER[:buildx]
445
- op = OptionPartition.new(opts, data[:common], cmd, project: self, strict: strict?)
449
+ op = OptionPartition.new(opts, bx[:common], cmd, **dockerinit)
446
450
  op.append(flag, quote: false)
447
- .parse(data[flag == :bake ? :bake : :build] + (shared || data[:shared]))
451
+ .parse(bx[flag == :bake ? :bake : :build] + (shared || bx[:shared]))
448
452
  case flag
449
453
  when :build, :context
450
454
  append_tag tag || option('tag', ignore: false) || self.tag
@@ -455,6 +459,10 @@ module Squared
455
459
  end
456
460
  when :bake
457
461
  append_file(0, index: 3) unless from || op.arg?('f', 'file') || !anypath?(*COMPOSEFILE)
462
+ if op.first == '-'
463
+ op.shift
464
+ op << @output[4] if @output[4]
465
+ end
458
466
  unless op.empty?
459
467
  if !context && op.size > 1 && exist?(op.last, type: 'd')
460
468
  pat = /\btarget\s+"#{Regexp.escape(op.last)}"/
@@ -476,7 +484,7 @@ module Squared
476
484
  if flag == :service
477
485
  command = opts.first
478
486
  if service == true
479
- cmd, status = filter_ps command, from
487
+ cmd, status = filter_ps(command, from: from)
480
488
  lines = IO.popen(cmd.temp('--services')).map(&:strip).reject(&:empty?)
481
489
  return list_empty(hint: status) if lines.empty?
482
490
 
@@ -485,10 +493,11 @@ module Squared
485
493
  docker_session('compose', command, '--', *service)
486
494
  else
487
495
  cmd, opts = docker_session('compose', opts: opts)
488
- op = OptionPartition.new(opts, OPT_DOCKER[:compose][:common], cmd, project: self, strict: strict?)
496
+ cp = OPT_DOCKER[:compose]
497
+ op = OptionPartition.new(opts, cp[:common], cmd, **dockerinit)
489
498
  append_file(filetype, force: flag == :publish) unless op.arg?('f', 'file')
490
499
  op << flag
491
- op.parse(OPT_DOCKER[:compose].fetch(flag, []))
500
+ op.parse(cp.fetch(flag, []))
492
501
  if flag == :publish
493
502
  id ||= option('tag', ignore: false) || op.shift || tagmain
494
503
  registry ||= option('registry') || op.shift || @oci
@@ -519,18 +528,20 @@ module Squared
519
528
 
520
529
  def container(flag, opts = [], id: nil)
521
530
  cmd, opts = docker_session('container', flag, opts: opts)
522
- data = OPT_DOCKER[:container]
523
- list = data.fetch(flag, [])
524
- list += data[:create] if (rc = flag == :run)
525
- list += data[:update] if rc ||= flag == :create
526
- op = OptionPartition.new(opts, list, cmd, project: self, strict: strict?, args: rc || flag == :exec)
531
+ rc = flag == :run
532
+ ct = OPT_DOCKER[:container]
533
+ list = ct.fetch(flag, [])
534
+ list += ct[:create] if rc
535
+ list += ct[:update] if rc ||= flag == :create
536
+ op = OptionPartition.new(opts, list, cmd, args: rc || flag == :exec, **dockerinit)
537
+ option('entrypoint') { |val| op.add_option('entrypoint', val, escape: false, double: true) } if flag == :run
527
538
  from = symjoin 'container', flag
528
539
  case flag
529
540
  when :run, :create, :exec
530
541
  if rc && !op.arg?('mount')
531
542
  all = collect_hash VAL_DOCKER[:run]
532
543
  delim = Regexp.new(",\\s*(?=#{all.join('|')})")
533
- Array(@mounts).each do |val|
544
+ @mounts.each do |val|
534
545
  type = nil
535
546
  args = val.split(delim).each_with_object([]) do |opt, out|
536
547
  k, v, q = OptionPartition.parse_option(opt)
@@ -551,23 +562,35 @@ module Squared
551
562
  end
552
563
  end
553
564
  case k
554
- when 'readonly', 'ro'
555
- out << k
565
+ when 'readonly', 'ro', 'volume-nocopy', 'bind-create-src'
566
+ out << k if %w[bind volume].include?(type)
556
567
  next
557
- when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-path'
568
+ when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-subpath'
558
569
  raise ArgumentError, message('no path value', k, hint: flag) unless v
559
570
 
560
- v = basepath v
571
+ case k
572
+ when 'source', 'src'
573
+ if type == 'bind'
574
+ v = basepath v
575
+ elsif type == 'tmpfs'
576
+ next
577
+ end
578
+ when 'destination', 'dst', 'target'
579
+ unless v.start_with?('/')
580
+ print_error(Logger::ERROR, "path is not absolute: #{v}", subject: from, hint: k)
581
+ next
582
+ end
583
+ end
561
584
  v = shell_quote(v, option: false, force: false) if q == ''
562
585
  end
563
586
  out << "#{k}=#{q}#{v}#{q}"
564
587
  elsif !silent?
565
- log_message('unrecognized option', subject: from, hint: k)
588
+ puts log_message('unrecognized option', subject: from, hint: k)
566
589
  end
567
590
  end
568
591
  raise TypeError, message('none specified', hint: flag) unless type
569
592
 
570
- cmd << "--mount type=#{type},#{args.join(',')}"
593
+ op << "--mount type=#{type},#{args.join(',')}"
571
594
  end
572
595
  end
573
596
  append_command(flag, id || tagmain, op.extras)
@@ -577,11 +600,11 @@ module Squared
577
600
  op.append(escape: true, strip: ':')
578
601
  when :commit
579
602
  latest = op.shift || tagmain
580
- cmd << id << latest
603
+ op << id << latest
581
604
  raise ArgumentError, message('unrecognized args', op.join(', '), hint: flag) unless op.empty?
582
605
  return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
583
606
 
584
- registry = option('registry') || @registry
607
+ registry = option('registry') || self.registry
585
608
  run(from: from, exception: registry.nil? ? exception? : true)
586
609
  return unless registry
587
610
 
@@ -597,8 +620,8 @@ module Squared
597
620
  return image(:push, opts, id: latest, registry: registry)
598
621
  else
599
622
  if op.empty?
600
- ps, status, no = filter_ps flag, from
601
- cmd << '--no-stream' if flag == :stats
623
+ ps, status, no = filter_ps(flag, from: from)
624
+ op << '--no-stream' if flag == :stats
602
625
  list_image(flag, ps, no: no, hint: status, multiple: flag != :attach, from: from) do |img|
603
626
  run(cmd.temp(img), from: from)
604
627
  end
@@ -621,16 +644,18 @@ module Squared
621
644
 
622
645
  def image(flag, opts = [], sync: true, id: nil, registry: nil, filter: nil)
623
646
  cmd, opts = docker_session('image', flag, opts: opts)
624
- list = OPT_DOCKER[:image].fetch(flag, [])
625
- list += OPT_DOCKER[:image][:common] unless flag == :ls || flag == :tag
626
- op = OptionPartition.new(opts, list, cmd, project: self, strict: strict?)
647
+ im = OPT_DOCKER[:image]
648
+ list = im.fetch(flag, [])
649
+ list += im[:common] unless flag == :ls || flag == :tag
650
+ op = OptionPartition.new(opts, list, cmd, **dockerinit)
627
651
  exception = exception?
628
652
  banner = true
653
+ status = false
629
654
  from = symjoin 'image', flag
630
655
  case flag
631
656
  when :ls
632
657
  if opts.size == op.size
633
- index = 0
658
+ index = -1
634
659
  name = nil
635
660
  opts.reverse_each do |val|
636
661
  next unless (arg = OptionPartition.parse_arg!('name', val))
@@ -640,12 +665,19 @@ module Squared
640
665
  break
641
666
  end
642
667
  list_image(:run, filter: filter, from: from) do |val|
643
- container(:run, if name
644
- opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
645
- else
646
- opts
647
- end, id: val)
648
- index += 1
668
+ args = if name
669
+ index += 1
670
+ opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
671
+ elsif choice!(opts, only: true)
672
+ OptionPartition.strip(readline('Enter arguments', force: false))
673
+ else
674
+ opts
675
+ end
676
+ if args.none?(/^d(etach)?$/)
677
+ Thread.new(args.dup, val) { |a, id| container(:run, a, id: id) }.join
678
+ break
679
+ end
680
+ container(:run, args, id: val)
649
681
  end
650
682
  return
651
683
  end
@@ -665,41 +697,40 @@ module Squared
665
697
  banner = false
666
698
  end
667
699
  when :tag, :save
668
- found = false
669
700
  list_image(flag, filter: filter, from: from) do |val|
670
701
  op << val
671
- found = true
702
+ status = true
672
703
  if flag == :tag
673
- op << tagname("#{project}:#{op.first}")
704
+ op << tagname(project, op.first)
674
705
  break
675
706
  end
676
707
  end
677
- raise ArgumentError, message('target not specified', hint: flag) unless found
708
+ raise ArgumentError, message('target not specified', hint: flag) unless status
678
709
  when :pull
679
710
  if !id
680
711
  id = tagmain
681
712
  elsif !op.arg?('a', 'all-tags') && !id.include?(':')
682
- id = "#{project}:#{id}"
713
+ id = tagname project, id
683
714
  end
684
715
  unless registry
685
716
  registry = op.shift
686
- registry ||= option('registry') || @registry unless id.include?('/')
717
+ registry ||= option('registry') || self.registry unless id.include?('/')
687
718
  end
688
- cmd << shell_quote(tagjoin(registry, id))
719
+ op << shell_quote(tagjoin(registry, id))
689
720
  when :push
690
721
  id ||= option('tag', ignore: false) || op.shift || tagmain
691
- registry ||= option('registry') || op.shift || @registry
722
+ registry ||= option('registry') || op.shift || self.registry
692
723
  emptyargs op, flag
693
724
  raise ArgumentError, message('username/registry not specified', hint: flag) unless registry
694
725
 
695
726
  uri = shell_quote tagjoin(registry, id)
696
727
  op << uri
697
- img = docker_output 'image', 'tag', id, uri
728
+ img = docker_output 'image tag', id, uri
698
729
  return unless confirm_command(img.to_s, cmd.to_s, target: id, as: registry, title: from)
699
730
 
700
731
  @session = img
701
732
  sync = false
702
- exception ||= true
733
+ exception = true
703
734
  banner = false
704
735
  when :inspect, :history
705
736
  raise ArgumentError, message('missing image', hint: flag) if op.empty?
@@ -711,16 +742,17 @@ module Squared
711
742
  op.append(escape: true, strip: ':')
712
743
  end
713
744
  end
714
- success?(run(sync: sync, exception: exception, banner: banner, from: from), flag == :tag || flag == :save)
745
+ success?(run(sync: sync, exception: exception, banner: banner, from: from), status)
715
746
  end
716
747
 
717
- def network(flag, opts = [], target: nil)
748
+ def network(flag, opts = [], target: nil, name: target)
718
749
  cmd, opts = docker_session('network', flag, opts: opts)
719
- op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self, strict: strict?)
750
+ op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, **dockerinit)
720
751
  .clear
721
752
  from = symjoin 'network', flag
722
- if flag == :create
723
- op.add_quote(target)
753
+ case flag
754
+ when :create, :rm
755
+ op.add_quote(name)
724
756
  run(from: from)
725
757
  else
726
758
  list_image(flag, docker_output('ps -a'), from: from) do |id|
@@ -738,13 +770,13 @@ module Squared
738
770
  end
739
771
 
740
772
  def compose?(file = dockerfile)
741
- return file == 3 || file == 4 if file.is_a?(Numeric)
773
+ return file.between?(3, 4) if file.is_a?(Numeric)
742
774
 
743
775
  COMPOSEFILE.include?(File.basename(file))
744
776
  end
745
777
 
746
778
  def bake?(file = dockerfile)
747
- return file == 1 || file == 2 if file.is_a?(Numeric)
779
+ return file.between?(1, 2) if file.is_a?(Numeric)
748
780
 
749
781
  BAKEFILE.include?(File.basename(file))
750
782
  end
@@ -756,18 +788,18 @@ module Squared
756
788
  val.size > 1 ? val : val.first
757
789
  elsif val == true
758
790
  DIR_DOCKER.find { |file| exist?(file) }
759
- elsif val != 'Dockerfile'
791
+ elsif val != DIR_DOCKER.last
760
792
  val
761
793
  end
762
794
  end
763
- basepath((@file.is_a?(Array) ? @file.first : @file) || 'Dockerfile')
795
+ basepath((@file.is_a?(Array) ? @file.first : @file) || DIR_DOCKER.last)
764
796
  end
765
797
 
766
798
  private
767
799
 
768
800
  def read_composefile(*keys, target: nil)
769
801
  require 'yaml'
770
- target = ENV['COMPOSE_FILE']&.split(workspace.windows? ? ';' : ':') unless target && !target.empty?
802
+ target = ENV['COMPOSE_FILE']&.split(windows? ? ';' : ':') unless target && !target.empty?
771
803
  Array(target || dockerfile).each do |val|
772
804
  doc = YAML.load_file(basepath(val))
773
805
  if keys.empty?
@@ -776,7 +808,7 @@ module Squared
776
808
  yield data
777
809
  end
778
810
  rescue => e
779
- log.debug e
811
+ print_error(e, subject: name, hint: 'compose')
780
812
  end
781
813
  end
782
814
 
@@ -784,7 +816,7 @@ module Squared
784
816
  opts = OptionPartition.union(Array(opts), @client) if @client
785
817
  return session('docker', *cmd) unless opts
786
818
 
787
- op = OptionPartition.new(opts, OPT_DOCKER[:common], project: self, strict: strict?)
819
+ op = OptionPartition.new(opts, OPT_DOCKER[:common], **dockerinit)
788
820
  [session('docker', *op.to_a, *cmd), op.extras]
789
821
  end
790
822
 
@@ -792,12 +824,12 @@ module Squared
792
824
  session('docker', *cmd, main: false, options: false, **kwargs)
793
825
  end
794
826
 
795
- def append_command(flag, val, list, target: @session, prompt: ':')
796
- list << if list.delete(prompt)
797
- readline('Enter command [args]', force: flag == :exec)
798
- else
799
- env('DOCKER_ARGS')
800
- end
827
+ def append_command(flag, id, list, target: @session, prompt: ':')
828
+ if list.delete(prompt)
829
+ list << readline('Enter command [args]', force: flag == :exec)
830
+ elsif (val = env('DOCKER_ARGS'))
831
+ list << val
832
+ end
801
833
  case flag
802
834
  when :run
803
835
  unless session_arg?('name', target: target)
@@ -806,8 +838,22 @@ module Squared
806
838
  when :exec
807
839
  raise ArgumentError, message('nothing to execute', hint: flag) if list.empty?
808
840
  end
809
- target << val << list.shift
810
- target << list.join(' && ') unless list.empty?
841
+ target << id
842
+ case list.first
843
+ when '--'
844
+ target.merge(list.drop(1))
845
+ when /\A(--|&&)(\s|\z)/
846
+ target.merge(list)
847
+ else
848
+ target << list.shift
849
+ unless list.empty?
850
+ if list.first.start_with?('-')
851
+ target.merge(list)
852
+ else
853
+ target << list.join(' && ')
854
+ end
855
+ end
856
+ end
811
857
  end
812
858
 
813
859
  def append_file(type, target: @session, index: 2, force: false)
@@ -839,13 +885,13 @@ module Squared
839
885
  else
840
886
  Array(val)
841
887
  end.each do |s|
842
- s = "#{s}:#{ver}" if ver && (!s.include?(':') || s.delete_suffix!(':latest'))
888
+ s = tagname s, ver if ver && (!s.include?(':') || s.delete_suffix!(':latest'))
843
889
  target << basic_option('tag', tagname(s))
844
890
  end
845
891
  target
846
892
  end
847
893
 
848
- def filter_ps(flag, from = :'container:ps')
894
+ def filter_ps(flag, meth = nil, column: nil, from: meth || :'container:ps')
849
895
  no = false
850
896
  status = case flag.to_sym
851
897
  when :start
@@ -871,6 +917,8 @@ module Squared
871
917
  end
872
918
  cmd = docker_output("#{from.to_s.split(':').first} ps -a",
873
919
  *status.map { |s| quote_option('filter', "status=#{s}") })
920
+ return IO.popen(cmd.temp("--format=\"{{.#{column}}}\"")).map(&:chomp) if column
921
+
874
922
  [cmd, status, no]
875
923
  end
876
924
 
@@ -879,15 +927,15 @@ module Squared
879
927
  pwd_set(from: from) do
880
928
  index = 1
881
929
  all = option('all', prefix: 'docker')
882
- y = from == :'image:rm' && option('y', prefix: 'docker')
930
+ y = from.to_s.end_with?(':rm') && option('y', prefix: 'docker')
883
931
  filter = env('DOCKER_FILTER', filter).to_s
884
932
  pat = if OptionPartition.pattern?(filter)
885
933
  Regexp.new(filter)
886
934
  elsif filter.match?(/[:_-]$/)
887
935
  /\b#{Regexp.escape(filter)}/
888
936
  else
889
- filter = filter.empty? ? '([:_-]|$)' : "[:_-]#{filter}"
890
- /\b(#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})#{filter}/
937
+ names = [dnsname(name), subdomainname(name), tagname(project), tagmain.split(':').first]
938
+ /\b(#{names.join('|')})#{filter.empty? ? '([:_-]|$)' : "[:_-]#{filter}"}/
891
939
  end
892
940
  IO.popen(cmd.temp('--format=json')).each do |line|
893
941
  data = JSON.parse(line)
@@ -915,17 +963,17 @@ module Squared
915
963
  puts unless index == 1
916
964
  puts (h + sub_style(aa, theme[:subject])).subhint("created #{e} ago")
917
965
  cols = %w[Tag Status Ports]
918
- cols << case flag
919
- when :connect, :disconnect
920
- 'Networks'
921
- else
922
- 'Size'
923
- end
924
- cols.each do |key|
925
- next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
926
-
927
- puts "#{g + f} #{key}: #{Array(data[key]).join(', ')}" unless data[key].to_s.empty?
928
- end
966
+ .push(case flag
967
+ when :connect, :disconnect
968
+ 'Networks'
969
+ else
970
+ 'Size'
971
+ end)
972
+ .reject do |key|
973
+ (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B') || data[key].to_s.empty?
974
+ end
975
+ pad = cols.map(&:size).max
976
+ cols.each { |key| puts "#{g + f} #{key.rjust(pad)}: #{Array(data[key]).join(', ')}" }
929
977
  w = 9 + flag.to_s.size + 4 + ee.size
930
978
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), theme[:inline])
931
979
  index += 1
@@ -1008,7 +1056,7 @@ module Squared
1008
1056
  cmd = docker_output(ctx, *action)
1009
1057
  case flag
1010
1058
  when :tag
1011
- args = tagjoin @registry, tag
1059
+ args = tagjoin registry, tag
1012
1060
  when :save
1013
1061
  opts = opts.sub_ext('.tar')
1014
1062
  cmd << quote_option('output', File.expand_path(opts))
@@ -1020,8 +1068,7 @@ module Squared
1020
1068
  cmd << opts << '--'
1021
1069
  end
1022
1070
  cmd.merge(Array(out).map { |val| val.split(/\s+/, 2).first })
1023
- cmd << args
1024
- success?(run(cmd), ctx.start_with?('network', 'tag', 'save'))
1071
+ success?(run(cmd << args, { 'NO_COLOR' => 'true' }), ctx.start_with?('network', 'tag', 'save'))
1025
1072
  end
1026
1073
 
1027
1074
  def filetype(val = dockerfile)
@@ -1048,15 +1095,32 @@ module Squared
1048
1095
  args.map { |val| val.chomp(char) }.join(char) unless args.empty?
1049
1096
  end
1050
1097
 
1051
- def tagname(val)
1052
- val = val.split(':').map { |s| charname(s.sub(/^\W+/, '')) }
1098
+ def tagname(*val, max: 128)
1099
+ val = val.compact
1100
+ .join(':')
1101
+ .split(':')
1102
+ .map { |s| subdomainname(s) }
1053
1103
  ret = val.join(':')
1054
- ret = val.first if val.size > 1 && ret.size > 128
1055
- ret[0..127]
1104
+ return ret unless ret.size > max
1105
+
1106
+ while val.size > 1
1107
+ val.pop
1108
+ s = val.join(':')
1109
+ return s unless s.size > max
1110
+ end
1111
+ ret[0...max]
1056
1112
  end
1057
1113
 
1058
- def dnsname(val)
1059
- charname(val[/^[^a-z\d]*(.*?)[^a-z\d]*$/i, 1].gsub(/-{2,}/, '-'))[0..62].downcase
1114
+ def dnsname(val, max: 63, char: nil)
1115
+ ret = val.sub(/^[^a-z]+/i, '')
1116
+ .sub(/[^a-z\d]+$/i, '')
1117
+ .gsub(/[^a-z\d#{char}]+/i, '-')
1118
+ .gsub(/-{2,}/, '-')[0...max]
1119
+ char ? ret : ret.downcase
1120
+ end
1121
+
1122
+ def subdomainname(val)
1123
+ dnsname(val, max: 128, char: '.').gsub(/\.{2,}/, '.')
1060
1124
  end
1061
1125
 
1062
1126
  def charname(val)
@@ -1067,6 +1131,10 @@ module Squared
1067
1131
  tag.is_a?(Array) ? tag.first : tag
1068
1132
  end
1069
1133
 
1134
+ def dockerinit
1135
+ { project: self, strict: strict?, explicit: '=', bool: %w[1 0 true false] }
1136
+ end
1137
+
1070
1138
  def emptyargs(list, hint = nil)
1071
1139
  raise ArgumentError, message('unrecognized args', list.join(', '), hint: hint) unless list.empty?
1072
1140
  end