squared 0.8.4 → 0.9.0.pre1

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
23
+ common: %w[1 all-resources compatibility dry-run ansi=b env-file=p f|file=p parallel=n profile=b progress=b
24
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
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 p|publish=q pull=b u|user=b v|volume=qq 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
@@ -50,7 +50,7 @@ module Squared
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
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=b?].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,
@@ -81,16 +81,17 @@ module Squared
81
81
  connect: %w[alias=b driver-opt=q gw-priority=n ip=b ip6=q link=b link-local-ip=q].freeze,
82
82
  create: %w[attachable config-only gateway=q ingress internal ipv4 ipv6 aux-address=q config-from=b
83
83
  d|driver=b ip-range=q ipam-driver=b ipam-opt=q label=q o|opt=q scope=b subnet=q].freeze,
84
- disconnect: %w[f|force].freeze
84
+ disconnect: %w[f|force].freeze,
85
+ rm: %w[f|force].freeze
85
86
  }.freeze
86
87
  }.freeze
87
88
  VAL_DOCKER = {
88
89
  run: {
89
90
  common: %w[source src destination dst target readonly ro].freeze,
90
- bind: %w[bind-propagation].freeze,
91
+ bind: %w[bind-create-src bind-propagation].freeze,
91
92
  volume: %w[volume-subpath volume-nocopy volume-opt].freeze,
92
93
  tmpfs: %w[tmpfs-size tmpfs-mode].freeze,
93
- image: %w[image-path].freeze
94
+ image: %w[image-subpath].freeze
94
95
  }.freeze,
95
96
  ls: {
96
97
  compose: %w[Name Image Command Service RunningFor Status Ports CreatedAt ExitCode Health ID Labels
@@ -127,7 +128,8 @@ module Squared
127
128
  'image' => %i[ls rm pull push tag save inspect history].freeze,
128
129
  'container' => %i[attach commit create diff exec inspect kill logs pause restart rm run start stats stop top
129
130
  unpause update].freeze,
130
- 'network' => %i[connect disconnect create].freeze,
131
+ 'run' => %i[rmi rmd mount volume entrypoint].freeze,
132
+ 'network' => %i[connect disconnect create rm].freeze,
131
133
  'ls' => nil
132
134
  })
133
135
 
@@ -139,8 +141,7 @@ module Squared
139
141
  self.global = nil
140
142
  return unless dockerfile(kwargs[:file]).exist?
141
143
 
142
- @tag = env('TAG', strict: true,
143
- ignore: false) || kwargs[:tag] || tagname(@project, @version || 'latest')
144
+ @tag = env('TAG', strict: true, ignore: false) || kwargs[:tag] || tagname(@project, @version || 'latest')
144
145
  @context = kwargs[:context]
145
146
  @client = kwargs[:client]&.each_with_object([]) do |(key, val), out|
146
147
  val = case (key = key.to_s)
@@ -149,7 +150,7 @@ module Squared
149
150
  when 'tlscacert', 'tlscert', 'tlskey'
150
151
  basepath!(val, type: 'f')
151
152
  when 'context', 'host', 'log-level'
152
- quote_option(key, val)
153
+ quote_option key, val
153
154
  when 'debug', 'tls', 'tlsverify'
154
155
  out << key
155
156
  next
@@ -158,7 +159,7 @@ module Squared
158
159
  end
159
160
  @mounts = Array(kwargs[:mounts])
160
161
  @secrets = kwargs[:secrets]
161
- self.registry = tagjoin kwargs[:registry], kwargs[:username]
162
+ @registry = tagjoin kwargs[:registry], kwargs[:username]
162
163
  @oci = tagjoin kwargs[:oci], kwargs[:username]
163
164
  initialize_ref Docker.ref
164
165
  initialize_logger kwargs[:log]
@@ -303,13 +304,22 @@ module Squared
303
304
  end
304
305
  end
305
306
  else
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}"
307
+ single = %i[logs diff attach top].freeze
308
+ format_desc action, flag, "opts*,id/name#{if flag == :stats
309
+ '*'
310
+ elsif !single.include?(flag)
311
+ '+'
312
+ end}|:"
311
313
  task flag do |_, args|
312
- container flag, args.to_a
314
+ args = args.to_a
315
+ if choice!(args, first: false)
316
+ args.concat(Array(choice_command(:container, multiple: !single.include?(flag))))
317
+ if flag == :rm
318
+ stop = filter_ps(:stop, column: 'ID').select { |id| args.include?(id) }
319
+ container :stop, stop if !stop.empty? && confirm_basic('Stop?', stop.join(', '))
320
+ end
321
+ end
322
+ container flag, args
313
323
  end
314
324
  end
315
325
  when 'image'
@@ -358,14 +368,47 @@ module Squared
358
368
  end
359
369
  end
360
370
  end
371
+ when 'run'
372
+ format_desc action, flag, 'id|:,opts*'
373
+ task flag, [:id] do |_, args|
374
+ id = if args.id == ':'
375
+ choice_command(:image, multiple: false)
376
+ else
377
+ param_guard(action, flag, args: args, key: :id)
378
+ end
379
+ args = case flag
380
+ when :mount
381
+ mount = []
382
+ loop do
383
+ s = readline("Enter mount ##{mount.size.succ}", force: false)
384
+ break if s.empty?
385
+
386
+ mount << (s.include?('type=') ? s : "type=bind,#{s}")
387
+ end
388
+ mount.map { |val| quote_option('mount', val, double: true) }
389
+ when :volume
390
+ choice_command(:volume, multiple: true, series: true).map do |val|
391
+ basic_option 'volume', val
392
+ end
393
+ when :entrypoint
394
+ ["entrypoint=#{shell_quote(readline('Entrypoint', force: true), double: true)}"]
395
+ when :rmi
396
+ %w[rm i t]
397
+ when :rmd
398
+ %w[rm d]
399
+ end + args.extras
400
+ container(:run, args.uniq, id: id)
401
+ end
361
402
  when 'network'
362
- format_desc action, flag, 'target,opts*'
363
- task flag, [:target] do |_, args|
364
- target = flag == :create ? param_guard(action, flag, args: args, key: :target) : args.target
365
- if target
366
- network(flag, args.extras, target: target)
403
+ format_desc action, flag, 'name,opts*'
404
+ task flag, [:name] do |_, args|
405
+ name = flag == :create ? param_guard(action, flag, args: args, key: :name) : args.name
406
+ if name
407
+ network(flag, args.extras, name: name)
408
+ elsif flag == :rm
409
+ choice_command :'network:rm'
367
410
  else
368
- choice_command flag
411
+ choice_command(flag, id: choice_command(:container, force: false), series: true)
369
412
  end
370
413
  end
371
414
  end
@@ -497,7 +540,7 @@ module Squared
497
540
  if flag == :publish
498
541
  id ||= option('tag', ignore: false) || op.shift || tagmain
499
542
  registry ||= option('registry') || op.shift || @oci
500
- emptyargs op, flag
543
+ op.clear(strict: true)
501
544
  op << shell_quote(tagjoin(registry, id))
502
545
  return unless confirm_command(op.to_s, title: from, target: id)
503
546
  else
@@ -512,10 +555,10 @@ module Squared
512
555
  if multiple
513
556
  op.concat(service) if service
514
557
  op.append(delim: true, escape: true, strip: ':')
515
- else
516
- raise ArgumentError, message('no service was selected', hint: flag) unless service
517
-
558
+ elsif service
518
559
  append_command(flag, service, op.extras, prompt: '::')
560
+ else
561
+ raise ArgumentError, message('no service was selected', hint: flag)
519
562
  end
520
563
  end
521
564
  end
@@ -530,6 +573,7 @@ module Squared
530
573
  list += ct[:create] if rc
531
574
  list += ct[:update] if rc ||= flag == :create
532
575
  op = OptionPartition.new(opts, list, cmd, args: rc || flag == :exec, **dockerinit)
576
+ option('entrypoint') { |val| op.add_option('entrypoint', val, escape: false, double: true) } if flag == :run
533
577
  from = symjoin 'container', flag
534
578
  case flag
535
579
  when :run, :create, :exec
@@ -557,23 +601,35 @@ module Squared
557
601
  end
558
602
  end
559
603
  case k
560
- when 'readonly', 'ro'
561
- out << k
604
+ when 'readonly', 'ro', 'volume-nocopy', 'bind-create-src'
605
+ out << k if %w[bind volume].include?(type)
562
606
  next
563
- when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-path'
607
+ when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-subpath'
564
608
  raise ArgumentError, message('no path value', k, hint: flag) unless v
565
609
 
566
- v = basepath v
610
+ case k
611
+ when 'source', 'src'
612
+ if type == 'bind'
613
+ v = basepath v
614
+ elsif type == 'tmpfs'
615
+ next
616
+ end
617
+ when 'destination', 'dst', 'target'
618
+ unless v.start_with?('/')
619
+ print_error(Logger::ERROR, "path is not absolute: #{v}", subject: from, hint: k)
620
+ next
621
+ end
622
+ end
567
623
  v = shell_quote(v, option: false, force: false) if q == ''
568
624
  end
569
625
  out << "#{k}=#{q}#{v}#{q}"
570
626
  elsif !silent?
571
- log_message('unrecognized option', subject: from, hint: k)
627
+ puts log_message('unrecognized option', subject: from, hint: k)
572
628
  end
573
629
  end
574
630
  raise TypeError, message('none specified', hint: flag) unless type
575
631
 
576
- cmd << "--mount type=#{type},#{args.join(',')}"
632
+ op << "--mount type=#{type},#{args.join(',')}"
577
633
  end
578
634
  end
579
635
  append_command(flag, id || tagmain, op.extras)
@@ -583,7 +639,7 @@ module Squared
583
639
  op.append(escape: true, strip: ':')
584
640
  when :commit
585
641
  latest = op.shift || tagmain
586
- cmd << id << latest
642
+ op << id << latest
587
643
  raise ArgumentError, message('unrecognized args', op.join(', '), hint: flag) unless op.empty?
588
644
  return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
589
645
 
@@ -604,7 +660,7 @@ module Squared
604
660
  else
605
661
  if op.empty?
606
662
  ps, status, no = filter_ps(flag, from: from)
607
- cmd << '--no-stream' if flag == :stats
663
+ op << '--no-stream' if flag == :stats
608
664
  list_image(flag, ps, no: no, hint: status, multiple: flag != :attach, from: from) do |img|
609
665
  run(cmd.temp(img), from: from)
610
666
  end
@@ -633,11 +689,12 @@ module Squared
633
689
  op = OptionPartition.new(opts, list, cmd, **dockerinit)
634
690
  exception = exception?
635
691
  banner = true
692
+ status = false
636
693
  from = symjoin 'image', flag
637
694
  case flag
638
695
  when :ls
639
696
  if opts.size == op.size
640
- index = 0
697
+ index = -1
641
698
  name = nil
642
699
  opts.reverse_each do |val|
643
700
  next unless (arg = OptionPartition.parse_arg!('name', val))
@@ -648,8 +705,9 @@ module Squared
648
705
  end
649
706
  list_image(:run, filter: filter, from: from) do |val|
650
707
  args = if name
708
+ index += 1
651
709
  opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
652
- elsif choice!(opts, only: true)
710
+ elsif choice!(opts, empty: true, only: true)
653
711
  OptionPartition.strip(readline('Enter arguments', force: false))
654
712
  else
655
713
  opts
@@ -659,7 +717,6 @@ module Squared
659
717
  break
660
718
  end
661
719
  container(:run, args, id: val)
662
- index += 1
663
720
  end
664
721
  return
665
722
  end
@@ -679,16 +736,15 @@ module Squared
679
736
  banner = false
680
737
  end
681
738
  when :tag, :save
682
- found = false
683
739
  list_image(flag, filter: filter, from: from) do |val|
684
740
  op << val
685
- found = true
741
+ status = true
686
742
  if flag == :tag
687
743
  op << tagname(project, op.first)
688
744
  break
689
745
  end
690
746
  end
691
- raise ArgumentError, message('target not specified', hint: flag) unless found
747
+ raise ArgumentError, message('target not specified', hint: flag) unless status
692
748
  when :pull
693
749
  if !id
694
750
  id = tagmain
@@ -699,13 +755,13 @@ module Squared
699
755
  registry = op.shift
700
756
  registry ||= option('registry') || self.registry unless id.include?('/')
701
757
  end
702
- cmd << shell_quote(tagjoin(registry, id))
758
+ op << shell_quote(tagjoin(registry, id))
703
759
  when :push
704
760
  id ||= option('tag', ignore: false) || op.shift || tagmain
705
761
  registry ||= option('registry') || op.shift || self.registry
706
- emptyargs op, flag
707
762
  raise ArgumentError, message('username/registry not specified', hint: flag) unless registry
708
763
 
764
+ op.clear(strict: true)
709
765
  uri = shell_quote tagjoin(registry, id)
710
766
  op << uri
711
767
  img = docker_output 'image tag', id, uri
@@ -725,16 +781,17 @@ module Squared
725
781
  op.append(escape: true, strip: ':')
726
782
  end
727
783
  end
728
- success?(run(sync: sync, exception: exception, banner: banner, from: from), flag == :tag || flag == :save)
784
+ success?(run(sync: sync, exception: exception, banner: banner, from: from), status)
729
785
  end
730
786
 
731
- def network(flag, opts = [], target: nil)
787
+ def network(flag, opts = [], name: nil)
732
788
  cmd, opts = docker_session('network', flag, opts: opts)
733
789
  op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, **dockerinit)
734
790
  .clear
735
791
  from = symjoin 'network', flag
736
- if flag == :create
737
- op.add_quote(target)
792
+ case flag
793
+ when :create, :rm
794
+ op.add_quote(name)
738
795
  run(from: from)
739
796
  else
740
797
  list_image(flag, docker_output('ps -a'), from: from) do |id|
@@ -752,13 +809,13 @@ module Squared
752
809
  end
753
810
 
754
811
  def compose?(file = dockerfile)
755
- return file == 3 || file == 4 if file.is_a?(Numeric)
812
+ return file.between?(3, 4) if file.is_a?(Numeric)
756
813
 
757
814
  COMPOSEFILE.include?(File.basename(file))
758
815
  end
759
816
 
760
817
  def bake?(file = dockerfile)
761
- return file == 1 || file == 2 if file.is_a?(Numeric)
818
+ return file.between?(1, 2) if file.is_a?(Numeric)
762
819
 
763
820
  BAKEFILE.include?(File.basename(file))
764
821
  end
@@ -770,11 +827,11 @@ module Squared
770
827
  val.size > 1 ? val : val.first
771
828
  elsif val == true
772
829
  DIR_DOCKER.find { |file| exist?(file) }
773
- elsif val != 'Dockerfile'
830
+ elsif val != DIR_DOCKER.last
774
831
  val
775
832
  end
776
833
  end
777
- basepath((@file.is_a?(Array) ? @file.first : @file) || 'Dockerfile')
834
+ basepath((@file.is_a?(Array) ? @file.first : @file) || DIR_DOCKER.last)
778
835
  end
779
836
 
780
837
  private
@@ -790,7 +847,7 @@ module Squared
790
847
  yield data
791
848
  end
792
849
  rescue => e
793
- log.debug e
850
+ print_error(e, subject: name, hint: 'compose')
794
851
  end
795
852
  end
796
853
 
@@ -873,7 +930,7 @@ module Squared
873
930
  target
874
931
  end
875
932
 
876
- def filter_ps(flag, meth = nil, from: meth || :'container:ps')
933
+ def filter_ps(flag, column: nil, from: :'container:ps')
877
934
  no = false
878
935
  status = case flag.to_sym
879
936
  when :start
@@ -899,6 +956,8 @@ module Squared
899
956
  end
900
957
  cmd = docker_output("#{from.to_s.split(':').first} ps -a",
901
958
  *status.map { |s| quote_option('filter', "status=#{s}") })
959
+ return IO.popen(cmd.temp("--format=\"{{.#{column}}}\"")).map(&:chomp) if column
960
+
902
961
  [cmd, status, no]
903
962
  end
904
963
 
@@ -907,15 +966,15 @@ module Squared
907
966
  pwd_set(from: from) do
908
967
  index = 1
909
968
  all = option('all', prefix: 'docker')
910
- y = from.to_s.end_with?(':rm') && option('y', prefix: 'docker')
969
+ y = from == :'image:rm' && option('y', prefix: 'docker')
911
970
  filter = env('DOCKER_FILTER', filter).to_s
912
971
  pat = if OptionPartition.pattern?(filter)
913
972
  Regexp.new(filter)
914
973
  elsif filter.match?(/[:_-]$/)
915
974
  /\b#{Regexp.escape(filter)}/
916
975
  else
917
- filter = filter.empty? ? '([:_-]|$)' : "[:_-]#{filter}"
918
- /\b(#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})#{filter}/
976
+ names = [dnsname(name), subdomainname(name), tagname(project), tagmain.split(':').first]
977
+ /\b(#{names.join('|')})#{filter.empty? ? '([:_-]|$)' : "[:_-]#{filter}"}/
919
978
  end
920
979
  IO.popen(cmd.temp('--format=json')).each do |line|
921
980
  data = JSON.parse(line)
@@ -989,18 +1048,22 @@ module Squared
989
1048
  confirm "#{s} #{sub_style(target, theme[:subject])}#{" as #{sub_style(as, theme[:inline])}" if as}?", 'N'
990
1049
  end
991
1050
 
992
- def choice_command(flag, *action)
1051
+ def choice_command(flag, *action, id: nil, multiple: nil, series: false, force: true)
993
1052
  msg, cmd = case flag
994
- when :exec
1053
+ when :exec, :container
995
1054
  ['Choose a container', 'ps -a']
996
1055
  when :bake
997
1056
  ['Choose a target', 'buildx bake --list=type=targets']
998
- when :connect, :disconnect
1057
+ when :connect, :disconnect, :'network:rm'
999
1058
  ['Choose a network', 'network ls']
1000
1059
  when :service
1001
1060
  ['Choose a service',
1002
1061
  'compose ps -a ' \
1003
1062
  '--format="table {{.Service}}\t{{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Status}}\t{{.Ports}}"']
1063
+ when :volume
1064
+ ['Choose a volume',
1065
+ 'volume ls ' \
1066
+ '--format="table {{.Name}}\t{{.Driver}}\t{{.Mountpoint}}"']
1004
1067
  else
1005
1068
  ['Choose an image',
1006
1069
  'images -a ' \
@@ -1011,28 +1074,40 @@ module Squared
1011
1074
  puts log_message('none found', subject: name, hint: "docker #{cmd.split(' ', 3)[0, 2].join(' ')}")
1012
1075
  return
1013
1076
  end
1014
- puts " # #{lines.shift}"
1015
- multiple = false
1077
+ parse = lambda do |list|
1078
+ ret = Array(list).map { |val| val.split(/\s+/, 2).first }.reject(&:empty?)
1079
+ return ret.first if multiple == false
1080
+
1081
+ ret unless ret.empty?
1082
+ end
1083
+ puts "#{"\n" if id && !printfirst?} # #{lines.shift}"
1016
1084
  ctx = flag.to_s
1017
1085
  case flag
1018
1086
  when :run, :exec
1019
1087
  values = [['Options', flag == :run], ['Arguments', flag == :exec]]
1020
1088
  when :rm, :bake
1021
1089
  values = ['Options']
1022
- multiple = true
1090
+ multiple = true if multiple.nil?
1023
1091
  ctx = flag == :rm ? 'image rm' : "buildx bake -f #{shell_quote(dockerfile)}"
1024
1092
  when :save
1025
1093
  values = [['Output', true], 'Platform']
1026
- multiple = true
1094
+ multiple = true if multiple.nil?
1027
1095
  when :service
1028
- values = []
1029
- multiple = true
1096
+ values = ['Options']
1097
+ multiple = true if multiple.nil?
1030
1098
  ctx = 'compose'
1031
1099
  when :connect, :disconnect
1032
- values = ['Options', ['Container', true]]
1100
+ values = id ? ['Options'] : ['Options', ['Container', true]]
1033
1101
  ctx = "network #{flag}"
1102
+ when :'network:rm'
1103
+ values = ['Options']
1104
+ multiple = true if multiple.nil?
1105
+ ctx = 'network rm'
1106
+ when :container, :image, :volume
1107
+ return parse.call(choice_index(msg, lines, multiple: multiple, series: series, force: force,
1108
+ attempts: force ? 3 : 1))
1034
1109
  end
1035
- out, opts, args = choice_index(msg, lines, multiple: multiple, values: values)
1110
+ out, opts, args = choice_index(msg, lines, values: values, multiple: multiple, series: series)
1036
1111
  cmd = docker_output(ctx, *action)
1037
1112
  case flag
1038
1113
  when :tag
@@ -1047,8 +1122,10 @@ module Squared
1047
1122
  else
1048
1123
  cmd << opts << '--'
1049
1124
  end
1050
- cmd.merge(Array(out).map { |val| val.split(/\s+/, 2).first })
1051
- success?(run(cmd << args, { 'NO_COLOR' => 'true' }), ctx.start_with?('network', 'tag', 'save'))
1125
+ cmd.merge(parse.call(out))
1126
+ cmd << args
1127
+ cmd.merge(Array(id)) if id
1128
+ success?(run(cmd, { 'NO_COLOR' => 'true' }), ctx.start_with?('network', 'tag', 'save'))
1052
1129
  end
1053
1130
 
1054
1131
  def filetype(val = dockerfile)
@@ -1079,7 +1156,7 @@ module Squared
1079
1156
  val = val.compact
1080
1157
  .join(':')
1081
1158
  .split(':')
1082
- .map { |s| charname(s.sub(/^\W+/, '')) }
1159
+ .map { |s| subdomainname(s) }
1083
1160
  ret = val.join(':')
1084
1161
  return ret unless ret.size > max
1085
1162
 
@@ -1088,15 +1165,19 @@ module Squared
1088
1165
  s = val.join(':')
1089
1166
  return s unless s.size > max
1090
1167
  end
1091
- ret[0...max]
1168
+ ret[...max]
1092
1169
  end
1093
1170
 
1094
- def dnsname(val)
1095
- charname(val[/^[^a-z\d]*(.*?)[^a-z\d]*$/i, 1].gsub(/-{2,}/, '-'))[0..62].downcase
1171
+ def dnsname(val, max: 63, char: nil)
1172
+ ret = val.sub(/^[^a-z]+/i, '')
1173
+ .sub(/[^a-z\d]+$/i, '')
1174
+ .gsub(/[^a-z\d#{char}]+/i, '-')
1175
+ .gsub(/-{2,}/, '-')[...max]
1176
+ char ? ret : ret.downcase
1096
1177
  end
1097
1178
 
1098
- def charname(val)
1099
- val.gsub(/[^\w.-]+/, '_')
1179
+ def subdomainname(val)
1180
+ dnsname(val, max: 128, char: '.').gsub(/\.{2,}/, '.')
1100
1181
  end
1101
1182
 
1102
1183
  def tagmain
@@ -1104,11 +1185,7 @@ module Squared
1104
1185
  end
1105
1186
 
1106
1187
  def dockerinit
1107
- { project: self, strict: strict?, explicit: '=', bool: %w[true false 1 0] }
1108
- end
1109
-
1110
- def emptyargs(list, hint = nil)
1111
- raise ArgumentError, message('unrecognized args', list.join(', '), hint: hint) unless list.empty?
1188
+ { project: self, strict: strict?, explicit: '=', bool: %w[1 0 true false] }
1112
1189
  end
1113
1190
 
1114
1191
  def anypath?(*args)