squared 0.6.18 → 0.7.0

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.
@@ -20,26 +20,27 @@ module Squared
20
20
  sbom=q].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=b].freeze,
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,
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=!? user=b w|workdir=q].freeze,
28
+ exec: %w[d|detach privileged e|env=qq index=i T|no-TTY=b? user=e 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=!? p|publish=q pull=b u|user=b v|volume=qq w|workdir=q].freeze,
31
+ T|no-TTY=b? p|publish=q pull=b u|user=e v|volume=q 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
- down: %w[remove-orphans v|volumes rmi=b t|timeout=i].freeze
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
38
  }.freeze,
38
39
  container: {
39
40
  create: %w[init i|interactive no-healthcheck oom-kill-disable privileged P|publish-all q|quiet read-only
40
- rm t|tty=!? use-api-socket add-host=q annotation=q a|attach=b blkio-weight=i
41
- blkio-weight-device=i cap-add=b cap-drop=b cgroup-parent=b cgroupns=b cidfile=p device=q
42
- device-cgroup-rule=q device-read-bps=q device-read-iops=q device-write-bps=q device-write-iops=q
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
43
44
  dns=q dns-option=q dns-search=q domainname=b entrypoint=q e|env=qq env-file=p expose=q gpus=q
44
45
  group-add=b health-cmd=q health-interval=b health-retries=i health-start-interval=q
45
46
  health-start-period=q health-timeout=q hostname=q io-maxbandwidth=b io-maxiops=b ip=b ip6=q ipc=b
@@ -47,14 +48,13 @@ module Squared
47
48
  m|memory=b memory-reservation=b memory-swap=n memory-swappiness=n mount=qq name=b network=b
48
49
  network-alias=b oom-score-adj=b pid=b pids-limit=n platform=q p|publish=q pull=b restart=b
49
50
  runtime=b security-opt=q shm-size=b stop-signal=b stop-timeout=i storage-opt=q sysctl=q tmpfs=q
50
- ulimit=q u|user=b userns=b uts=b v|volume=qq volume-driver=b volumes-from=b w|workdir=q].freeze,
51
+ ulimit=q u|user=b userns=b uts=b v|volume=q volume-driver=b volumes-from=b w|workdir=q].freeze,
51
52
  run: %w[d|detach detach-keys=q sig-proxy=b?].freeze,
52
53
  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
53
54
  cpuset-cpus=b cpuset-mems=b m|memory=b memory-reservation=b memory-swap=n pids-limit=n
54
55
  restart=q].freeze,
55
- exec: %w[d|detach i|interactive privileged t|tty=!? detach-keys=q e|env=qq env-file=p u|user=b
56
+ exec: %w[d|detach i|interactive privileged t|tty detach-keys=q e|env=qq env-file=p u|user=e
56
57
  w|workdir=q].freeze,
57
- rm: %w[f|force l|link v|volumes].freeze,
58
58
  commit: %w[no-pause a|author=q c|change=q m|message=q pause=b?].freeze,
59
59
  inspect: %w[s|size f|format=q type=b].freeze,
60
60
  start: %w[a|attach i|interactive detach-keys=q].freeze,
@@ -65,6 +65,7 @@ module Squared
65
65
  }.freeze,
66
66
  image: {
67
67
  ls: %w[a|all digests no-trunc q|quiet tree f|filter=q format=q].freeze,
68
+ pull: %w[a|all-tags platform=q q|quiet].freeze,
68
69
  push: %w[a|all-tags platform=q q|quiet].freeze,
69
70
  rm: %w[f|force no-prune platform=q].freeze,
70
71
  save: %w[o|output=p platform=q].freeze
@@ -108,9 +109,9 @@ module Squared
108
109
 
109
110
  subtasks({
110
111
  'build' => %i[tag context].freeze,
111
- 'compose' => %i[build create run exec up down service].freeze,
112
- 'bake' => %i[build check].freeze,
113
- 'image' => %i[ls rm push tag save].freeze,
112
+ 'compose' => %i[build create publish run exec up down service].freeze,
113
+ 'bake' => %i[build compose check].freeze,
114
+ 'image' => %i[ls rm pull push tag save].freeze,
114
115
  'container' => %i[run create exec update commit inspect diff start stop restart pause unpause top stats kill
115
116
  rm].freeze,
116
117
  'network' => %i[connect disconnect].freeze,
@@ -120,17 +121,19 @@ module Squared
120
121
  attr_reader :context
121
122
  attr_accessor :tag
122
123
 
123
- def initialize(*, file: nil, context: nil, tag: nil, secrets: nil, mounts: [], registry: nil, **kwargs)
124
+ def initialize(*, mounts: [], **kwargs)
124
125
  super
125
- return unless dockerfile(file).exist?
126
+ self.global = nil
127
+ return unless dockerfile(kwargs[:file]).exist?
126
128
 
127
- @context = context
128
- self.tag = tag || tagname("#{@project}:#{@version || 'latest'}")
129
+ self.tag = kwargs[:tag] || tagname("#{@project}:#{@version || 'latest'}")
130
+ @context = kwargs[:context]
129
131
  @mounts = mounts
130
- @secrets = secrets
131
- @registry = tagjoin registry, kwargs[:username]
132
+ @secrets = kwargs[:secrets]
133
+ @registry = tagjoin kwargs[:registry], kwargs[:username]
134
+ @oci = tagjoin kwargs[:oci], kwargs[:username]
132
135
  initialize_ref Docker.ref
133
- initialize_logger(**kwargs)
136
+ initialize_logger kwargs[:log]
134
137
  initialize_env(**kwargs)
135
138
  @output[4] = merge_opts(kwargs[:args], @output[4]) if kwargs[:args]
136
139
  end
@@ -164,25 +167,21 @@ module Squared
164
167
  end
165
168
  cmd << '-a' if has_value!(args, 'a', 'all') && command != 'network'
166
169
  data = VAL_DOCKER[:ls][command.to_sym]
167
- cols = if has_value!(args, 's', 'standard')
168
- data.first(data.index('CreatedAt'))
169
- else
170
- [].tap do |out|
171
- args.each do |val|
172
- if val =~ /^(\d+)$/
173
- out << data[$1.to_i.pred]
174
- elsif val =~ /^(\d+)(-|\.{2,3})(\d+)$/
175
- j = $1.to_i.pred
176
- k = $3.to_i - ($2 == '..' ? 2 : 1)
177
- out.concat(data[j..k]) if k > j
178
- end
179
- end
180
- next unless out.empty?
181
-
182
- out.replace(choice_index('Select a column', data, multiple: true, attempts: 1))
183
- end
184
- end
185
- cmd << quote_option('format', "table #{cols.map! { |val| "{{.#{val}}}" }.join("\t")}")
170
+ if has_value!(args, 's', 'standard')
171
+ cols = data.first(data.index('CreatedAt'))
172
+ else
173
+ cols = args.each_with_object([]) do |val, out|
174
+ if val =~ /^(\d+)$/
175
+ out << data[$1.to_i.pred]
176
+ elsif val =~ /^(\d+)(-|\.{2,3})(\d+)$/
177
+ j = $1.to_i.pred
178
+ k = $3.to_i - ($2 == '..' ? 2 : 1)
179
+ out.concat(data[j..k]) if k > j
180
+ end
181
+ end
182
+ cols = choice_index('Select a column', data, multiple: true, attempts: 1) if cols.empty?
183
+ end
184
+ cmd << quote_option('format', "table #{cols.map { |val| "{{.#{val}}}" }.join("\t")}")
186
185
  run(cmd, banner: false, from: :ls)
187
186
  end
188
187
  end
@@ -200,14 +199,14 @@ module Squared
200
199
  break unless bake?
201
200
 
202
201
  case flag
203
- when :build
204
- format_desc action, flag, 'opts*,target*,context|:'
202
+ when :build, :compose
203
+ format_desc action, flag, 'opts*,target*,context?|:'
205
204
  task flag do |_, args|
206
205
  args = args.to_a
207
206
  if args.first == ':'
208
207
  choice_command :bake
209
208
  else
210
- buildx :bake, args
209
+ buildx(:bake, args, from: (:'buildx:bake' if flag == :compose))
211
210
  end
212
211
  end
213
212
  when :check
@@ -240,6 +239,13 @@ module Squared
240
239
  compose!(flag, [command], service: service.empty? || service)
241
240
  end
242
241
  end
242
+ when :publish
243
+ next unless @oci
244
+
245
+ format_desc action, flag, 'tag?,repository?,opts*'
246
+ task flag, [:tag] do |_, args|
247
+ compose! flag, args.to_a
248
+ end
243
249
  else
244
250
  format_desc action, flag, 'opts*,service*|:'
245
251
  task flag do |_, args|
@@ -268,7 +274,7 @@ module Squared
268
274
  end
269
275
  end
270
276
  else
271
- format_desc action, flag, "opts*,id/name#{flag == :update || flag == :inspect ? '+' : '*'}"
277
+ format_desc action, flag, "opts*,id/name#{flag == :update ? '+' : '*'}"
272
278
  task flag do |_, args|
273
279
  container flag, args.to_a
274
280
  end
@@ -276,6 +282,13 @@ module Squared
276
282
  when 'image'
277
283
  case flag
278
284
  when :push
285
+ next unless @registry
286
+
287
+ format_desc action, flag, 'tag?,registry/username?,opts*'
288
+ task flag, [:tag] do |_, args|
289
+ image flag, args.to_a
290
+ end
291
+ when :pull
279
292
  format_desc action, flag, 'tag,registry/username?,opts*'
280
293
  task flag, [:tag] do |_, args|
281
294
  id = param_guard(action, flag, args: args, key: :tag)
@@ -283,18 +296,14 @@ module Squared
283
296
  end
284
297
  else
285
298
  format_desc(action, flag, case flag
286
- when :rm, :save then 'id,opts*'
287
- when :tag then 'version'
299
+ when :rm, :save then 'id*,opts*'
300
+ when :tag then 'version?'
288
301
  else 'opts*,args*'
289
- end, before: 'pattern?')
302
+ end)
290
303
  task flag do |_, args|
291
304
  args = args.to_a
292
- n = args.size
293
- if (n > 1 || (flag == :ls && n > 0)) && OptionPartition.pattern?(args.first)
294
- filter = args.shift
295
- end
296
305
  if !args.empty? || flag == :ls
297
- image(flag, args, filter: filter)
306
+ image flag, args
298
307
  else
299
308
  choice_command flag
300
309
  end
@@ -320,8 +329,8 @@ module Squared
320
329
  def clean(*, sync: invoked_sync?('clean'), **)
321
330
  if runnable?(@clean)
322
331
  super
323
- elsif sync || option('y', prefix: 'docker')
324
- image :rm
332
+ else
333
+ image(:rm, sync: sync)
325
334
  end
326
335
  end
327
336
 
@@ -354,7 +363,7 @@ module Squared
354
363
  [args, flags].each_with_index do |item, i|
355
364
  next unless item && (data = append_any(item, target: []))
356
365
 
357
- ret.merge(data.map! { |arg| i == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
366
+ ret.merge(data.map { |arg| i == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
358
367
  end
359
368
  case from
360
369
  when :run
@@ -362,13 +371,13 @@ module Squared
362
371
  when String
363
372
  ret << quote_option('secret', @secrets, double: true)
364
373
  when Hash
365
- append = lambda do |type|
366
- Array(@secrets[type]).each { |arg| ret << quote_option('secret', "type=#{type},#{arg}", double: true) }
374
+ append = lambda do |key|
375
+ ret.merge(Array(@secrets[key]).map { |arg| quote_option('secret', "type=#{key},#{arg}", double: true) })
367
376
  end
368
377
  append.call(:file)
369
378
  append.call(:env)
370
379
  else
371
- Array(@secrets).each { |arg| ret << quote_option('secret', arg) }
380
+ ret.merge(Array(@secrets).map { |arg| quote_option('secret', arg) })
372
381
  end
373
382
  if (val = option('tag', ignore: false))
374
383
  append_tag val
@@ -382,40 +391,38 @@ module Squared
382
391
  ret
383
392
  end
384
393
 
385
- def buildx(flag, opts = [], tag: nil, context: nil)
394
+ def buildx(flag, opts = [], tag: nil, context: nil, from: nil)
386
395
  cmd, opts = docker_session('buildx', opts: opts)
387
- op = OPT_DOCKER[:buildx].yield_self do |data|
388
- OptionPartition.new(opts, data[:common], cmd, project: self)
389
- .append(flag, quote: false)
390
- .parse(data[flag == :bake ? :bake : :build] + data[:shared])
391
- end
396
+ data = OPT_DOCKER[:buildx]
397
+ op = OptionPartition.new(opts, data[:common], cmd, project: self)
398
+ op.append(flag, quote: false)
399
+ .parse(data[flag == :bake ? :bake : :build] + data[:shared])
392
400
  case flag
393
401
  when :build, :context
394
402
  append_tag(tag || option('tag', ignore: false) || self.tag)
395
403
  append_context context
396
404
  when :bake
397
- if op.first == '-'
398
- op.shift
399
- op << @output[4] if @output[4]
400
- end
405
+ append_file(0, index: 3) unless from || op.arg?('f', 'file') || !anypath?(*COMPOSEFILE)
401
406
  unless op.empty?
402
- if !context && op.size > 1 && basepath(op.last).directory?
403
- pat = /\btarget\s+"#{Regexp.escape(op.last)}"/
404
- context = op.pop if op.values_of('f', 'file').none? { |f| basepath!(f)&.read&.match?(pat) }
405
- end
406
- if context
407
- context = basepath context
408
- op.each { |name| op.add_option('set', "#{name}.context=#{context}", escape: false) }
407
+ args = op.dup
408
+ op.reset
409
+ if Dir.exist?(args.last)
410
+ if projectpath?(val = args.pop)
411
+ context = val
412
+ else
413
+ op.push(val)
414
+ end
409
415
  end
410
- op.append(escape: true, strip: /^:/, clear: true)
416
+ op.append(args, escape: true, strip: /^:/)
417
+ contextdir context if context
411
418
  end
412
419
  end
413
420
  op.clear(pass: false)
414
- run(from: :"buildx:#{flag}")
421
+ run(from: from || symjoin('buildx', flag))
415
422
  end
416
423
 
417
- def compose!(flag, opts = [], service: nil, multiple: false)
418
- from = :"compose:#{flag}"
424
+ def compose!(flag, opts = [], id: nil, service: nil, multiple: false)
425
+ from = symjoin 'compose', flag
419
426
  if flag == :service
420
427
  command = opts.first
421
428
  if service == true
@@ -429,60 +436,64 @@ module Squared
429
436
  else
430
437
  cmd, opts = docker_session('compose', opts: opts)
431
438
  op = OptionPartition.new(opts, OPT_DOCKER[:compose][:common], cmd, project: self)
432
- append_file filetype unless op.arg?('f', 'file')
439
+ append_file(filetype, force: flag == :publish) unless op.arg?('f', 'file')
433
440
  op << flag
434
441
  op.parse(OPT_DOCKER[:compose].fetch(flag, []))
435
- if op.remove(':') || service == ':'
436
- keys = Set.new
437
- read_composefile('services', target: op.values_of('f', 'file')) { |data| keys.merge(data.keys) }
438
- service = unless keys.empty?
439
- choice_index('Add services', keys, multiple: multiple, force: !multiple,
440
- attempts: multiple ? 1 : 3)
441
- end
442
- end
443
- if multiple
444
- op.concat(service) if service
445
- op.append(delim: true, escape: true, strip: /^:/)
442
+ if flag == :publish
443
+ id ||= option('tag', ignore: false) || op.shift || tagmain
444
+ registry ||= option('registry') || op.shift || @oci
445
+ emptyargs op, flag
446
+ op << shell_quote(tagjoin(registry, id))
447
+ return unless confirm_command(op.to_s, title: from, target: id)
446
448
  else
447
- raise_error ArgumentError, 'no service was selected', hint: flag unless service
448
- append_command(flag, service, op.extras, prompt: '::')
449
+ if op.remove(':') || service == ':'
450
+ keys = Set.new
451
+ read_composefile('services', target: op.values_of('f', 'file')) { |data| keys.merge(data.keys) }
452
+ service = unless keys.empty?
453
+ choice_index('Add services', keys, multiple: multiple, force: !multiple,
454
+ attempts: multiple ? 1 : 3)
455
+ end
456
+ end
457
+ if multiple
458
+ op.concat(service) if service
459
+ op.append(delim: true, escape: true, strip: /^:/)
460
+ else
461
+ raise_error ArgumentError, 'no service was selected', hint: flag unless service
462
+ append_command(flag, service, op.extras, prompt: '::')
463
+ end
449
464
  end
450
465
  end
451
466
  run(from: from)
452
467
  end
453
- alias compose_ compose!
454
468
 
455
469
  def container(flag, opts = [], id: nil)
456
470
  cmd, opts = docker_session('container', flag, opts: opts)
457
- rc = flag == :run || flag == :create
458
- op = OPT_DOCKER[:container].yield_self do |data|
459
- list = data.fetch(flag, [])
460
- list += data[:create] if flag == :run
461
- list += data[:update] if rc
462
- OptionPartition.new(opts, list, cmd, project: self, args: rc || flag == :exec)
463
- end
464
- from = :"container:#{flag}"
471
+ data = OPT_DOCKER[:container]
472
+ list = data.fetch(flag, [])
473
+ list += data[:create] if (rc = flag == :run)
474
+ list += data[:update] if rc ||= flag == :create
475
+ op = OptionPartition.new(opts, list, cmd, project: self, args: rc || flag == :exec)
476
+ from = symjoin 'container', flag
465
477
  case flag
466
478
  when :run, :create, :exec
467
479
  if rc && !op.arg?('mount')
468
480
  all = collect_hash VAL_DOCKER[:run]
469
481
  delim = Regexp.new(",\\s*(?=#{all.join('|')})")
470
482
  Array(@mounts).each do |val|
471
- args = []
472
483
  type = nil
473
- val.split(delim).each do |opt|
474
- k, v, q = split_option opt
484
+ args = val.split(delim).each_with_object([]) do |opt, out|
485
+ k, v, q = OptionPartition.parse_option(opt)
475
486
  if k == 'type'
476
487
  case v
477
488
  when 'bind', 'volume', 'image', 'tmpfs'
478
489
  type = v
479
490
  else
480
- raise_error TypeError, "unknown: #{v}", hint: flag
491
+ raise_error TypeError, "unknown: #{v || "''"}", hint: flag
481
492
  end
482
493
  elsif all.include?(k)
483
494
  unless type
484
- VAL_DOCKER[:run].each_pair do |key, a|
485
- next unless a.include?(k)
495
+ VAL_DOCKER[:run].each_pair do |key, items|
496
+ next unless items.include?(k)
486
497
 
487
498
  type = key.to_s unless key == :common
488
499
  break
@@ -490,13 +501,14 @@ module Squared
490
501
  end
491
502
  case k
492
503
  when 'readonly', 'ro'
493
- args << k
504
+ out << k
494
505
  next
495
506
  when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-path'
507
+ raise_error ArgumentError, "#{k}: no path value", hint: flag unless v
496
508
  v = basepath v
497
509
  v = shell_quote(v, option: false, force: false) if q == ''
498
510
  end
499
- args << "#{k}=#{q + v + q}"
511
+ out << "#{k}=#{q}#{v}#{q}"
500
512
  elsif !silent?
501
513
  log_message('unrecognized option', subject: from, hint: k)
502
514
  end
@@ -506,7 +518,7 @@ module Squared
506
518
  end
507
519
  end
508
520
  append_command(flag, id || tagmain, op.extras)
509
- when :update, :inspect
521
+ when :update
510
522
  raise_error ArgumentError, 'missing container', hint: flag if op.empty?
511
523
  op.append(escape: true, strip: /^:/)
512
524
  when :commit
@@ -522,7 +534,7 @@ module Squared
522
534
  opts = []
523
535
  append_option('platform', target: opts, equals: true)
524
536
  opts << case option('disable-content-trust', ignore: false)
525
- when 'false', '0'
537
+ when '0', 'false'
526
538
  '--disable-content-trust=false'
527
539
  else
528
540
  '--disable-content-trust'
@@ -541,12 +553,12 @@ module Squared
541
553
  run(from: from)
542
554
  end
543
555
 
544
- def image(flag, opts = [], sync: true, id: nil, registry: nil, filter: nil)
556
+ def image(flag, opts = [], sync: true, id: nil, registry: nil)
545
557
  cmd, opts = docker_session('image', flag, opts: opts)
546
558
  op = OptionPartition.new(opts, OPT_DOCKER[:image].fetch(flag, []), cmd, project: self)
547
- exception = self.exception
559
+ exception = exception?
548
560
  banner = true
549
- from = :"image:#{flag}"
561
+ from = symjoin 'image', flag
550
562
  case flag
551
563
  when :ls
552
564
  if opts.size == op.size
@@ -559,7 +571,7 @@ module Squared
559
571
  opts.delete(val)
560
572
  break
561
573
  end
562
- list_image(:run, filter: filter, from: from) do |val|
574
+ list_image(:run, from: from) do |val|
563
575
  container(:run, if name
564
576
  opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
565
577
  else
@@ -573,7 +585,7 @@ module Squared
573
585
  when :rm
574
586
  unless id
575
587
  if op.empty?
576
- list_image(:rm, filter: filter, from: from) { |val| image(:rm, opts, sync: sync, id: val) }
588
+ list_image(:rm, from: from) { |val| image(:rm, opts, sync: sync, id: val) }
577
589
  else
578
590
  op.each { |val| run(cmd.temp(val), sync: sync, from: from) }
579
591
  end
@@ -585,51 +597,48 @@ module Squared
585
597
  banner = false
586
598
  end
587
599
  when :tag, :save
588
- found = false
589
- list_image(flag, filter: filter, from: from) do |val|
600
+ list_image(flag, from: from) do |val|
590
601
  op << val
591
- found = true
592
- if flag == :tag
593
- op << tagname("#{project}:#{op.first}")
594
- break
595
- end
602
+ next unless flag == :tag
603
+
604
+ op << tagname("#{project}:#{op.first}")
605
+ break
596
606
  end
597
- raise_error ArgumentError, 'target not specified', hint: flag unless found
598
- when :push
599
- id ||= option('tag', ignore: false) || tagmain
600
- registry ||= op.shift || option('registry') || @registry
601
- unless id && op.empty?
602
- if id
603
- raise_error ArgumentError, "unrecognized args: #{op.join(', ')}", hint: flag
604
- else
605
- raise_error 'no id/tag', hint: flag
606
- end
607
+ when :pull
608
+ if !id
609
+ id = tagmain
610
+ elsif !op.arg?('a', 'all-tags') && !id.include?(':')
611
+ id = "#{project}:#{id}"
607
612
  end
613
+ unless registry
614
+ registry = op.shift
615
+ registry ||= option('registry') || @registry unless id.include?('/')
616
+ end
617
+ cmd << shell_quote(tagjoin(registry, id))
618
+ when :push
619
+ id ||= option('tag', ignore: false) || op.shift || tagmain
620
+ registry ||= option('registry') || op.shift || @registry
621
+ emptyargs op, flag
608
622
  raise_error ArgumentError, 'username/registry not specified', hint: flag unless registry
609
- registry.chomp!('/')
610
- uri = shell_quote "#{registry}/#{id}"
623
+ uri = shell_quote tagjoin(registry, id)
611
624
  op << uri
612
625
  img = docker_output 'image', 'tag', id, uri
613
626
  return unless confirm_command(img.to_s, cmd.to_s, target: id, as: registry, title: from)
614
627
 
615
- cmd = img
628
+ @session = img
616
629
  sync = false
617
- exception = true
630
+ exception ||= true
618
631
  banner = false
619
632
  end
620
- run(cmd, sync: sync, exception: exception, banner: banner, from: from).tap do |ret|
621
- success?(ret, flag == :tag || flag == :save)
622
- end
633
+ success?(run(sync: sync, exception: exception, banner: banner, from: from), flag == :tag || flag == :save)
623
634
  end
624
635
 
625
636
  def network(flag, opts = [], target: nil)
626
637
  cmd, opts = docker_session('network', flag, opts: opts)
627
638
  OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self)
628
639
  .clear
629
- from = :"network:#{flag}"
630
- list_image(flag, docker_output('ps -a'), from: from) do |img|
631
- success?(run(cmd.temp(target, img), from: from))
632
- end
640
+ from = symjoin 'network', flag
641
+ list_image(flag, docker_output('ps -a'), from: from) { |id| success?(run(cmd.temp(target, id), from: from)) }
633
642
  end
634
643
 
635
644
  def build?
@@ -678,7 +687,7 @@ module Squared
678
687
  elsif (data = doc.dig(*keys))
679
688
  yield data
680
689
  end
681
- rescue StandardError => e
690
+ rescue => e
682
691
  log.debug e
683
692
  end
684
693
  end
@@ -695,11 +704,11 @@ module Squared
695
704
  end
696
705
 
697
706
  def append_command(flag, val, list, target: @session, prompt: ':')
698
- if list.delete(prompt)
699
- list << readline('Enter command [args]', force: flag == :exec)
700
- else
701
- env('DOCKER_ARGS') { |args| list << args }
702
- end
707
+ list << if list.delete(prompt)
708
+ readline('Enter command [args]', force: flag == :exec)
709
+ else
710
+ env('DOCKER_ARGS')
711
+ end
703
712
  case flag
704
713
  when :run
705
714
  unless session_arg?('name', target: target)
@@ -708,28 +717,14 @@ module Squared
708
717
  when :exec
709
718
  raise_error ArgumentError, 'nothing to execute', hint: flag if list.empty?
710
719
  end
711
- target << val
712
- case list.first
713
- when '--'
714
- target.merge(list.drop(1))
715
- when /\A(--|&&)(\s|\z)/
716
- target.merge(list)
717
- else
718
- target << list.shift
719
- unless list.empty?
720
- if list.first.start_with?('-')
721
- target.merge(list)
722
- else
723
- target << list.join(' && ')
724
- end
725
- end
726
- end
720
+ target << val << list.shift
721
+ target << list.join(' && ') unless list.empty?
727
722
  end
728
723
 
729
- def append_file(type, target: @session, index: 2)
730
- return if !@file || (ENV['COMPOSE_FILE'] && compose?(type))
724
+ def append_file(type, target: @session, index: 2, force: false)
725
+ return unless @file && !(ENV['COMPOSE_FILE'] && compose?(type))
731
726
 
732
- unless @file.is_a?(Array)
727
+ unless @file.is_a?(Array) || force
733
728
  case type
734
729
  when 2, 4
735
730
  return
@@ -737,14 +732,7 @@ module Squared
737
732
  return unless COMPOSEFILE.select { |val| basepath!(val) }.size > 1
738
733
  end
739
734
  end
740
- files = Array(@file).map { |val| quote_option('file', basepath(val)) }
741
- if target.is_a?(Set)
742
- opts = target.to_a.insert(index, *files)
743
- target.clear
744
- .merge(opts)
745
- else
746
- target.insert(index, *files)
747
- end
735
+ target.insert(index, *Array(@file).map { |val| quote_option('file', basepath(val)) })
748
736
  end
749
737
 
750
738
  def append_context(ctx = nil, target: @session)
@@ -758,7 +746,7 @@ module Squared
758
746
  ver = option('version', target: target, ignore: false)
759
747
  case val
760
748
  when String
761
- split_escape val
749
+ val.split(',')
762
750
  else
763
751
  Array(val)
764
752
  end.each do |s|
@@ -797,20 +785,12 @@ module Squared
797
785
  [cmd, status, no]
798
786
  end
799
787
 
800
- def list_image(flag, cmd = docker_output('image ls -a'), filter: nil, hint: nil, no: true, from: nil)
788
+ def list_image(flag, cmd = docker_output('image ls -a'), hint: nil, no: true, from: nil)
801
789
  pwd_set(from: from) do
802
790
  index = 1
803
791
  all = option('all', prefix: 'docker')
804
792
  y = from == :'image:rm' && option('y', prefix: 'docker')
805
- filter = env('DOCKER_FILTER', filter).to_s
806
- pat = if OptionPartition.pattern?(filter)
807
- Regexp.new(filter)
808
- elsif filter.match?(/[:_-]$/)
809
- /\b#{Regexp.escape(filter)}/
810
- else
811
- filter = filter.empty? ? '(?:[:_-]|$)' : "[:_-]#{filter}"
812
- /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})#{filter}/
813
- end
793
+ pat = /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})\b/
814
794
  IO.popen(cmd.temp('--format=json')).each do |line|
815
795
  data = JSON.parse(line)
816
796
  id = data['ID']
@@ -859,7 +839,7 @@ module Squared
859
839
  end
860
840
  list_empty(hint: hint || from) if index == 1 && !y
861
841
  end
862
- rescue StandardError => e
842
+ rescue => e
863
843
  on_error e, from
864
844
  end
865
845
 
@@ -893,11 +873,11 @@ module Squared
893
873
  when :service
894
874
  ['Choose a service',
895
875
  'compose ps -a ' \
896
- '--format="table {{.Service}}\t{{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Status}}\t{{.Ports}}"']
876
+ "--format='table {{.Service}}\t{{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Status}}\t{{.Ports}}'"]
897
877
  else
898
878
  ['Choose an image',
899
879
  'images -a ' \
900
- '--format="table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.CreatedSince}}\t{{.Size}}"']
880
+ "--format='table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.CreatedSince}}\t{{.Size}}'"]
901
881
  end
902
882
  lines = `#{docker_output(cmd)}`.lines
903
883
  if lines.size <= 1
@@ -931,18 +911,18 @@ module Squared
931
911
  when :tag
932
912
  args = tagjoin @registry, tag
933
913
  when :save
934
- opts = "#{opts}.tar" unless opts.end_with?('.tar')
914
+ opts = opts.sub_ext('.tar')
935
915
  cmd << quote_option('output', File.expand_path(opts))
936
916
  if args
937
- cmd << basic_option('platform', args)
917
+ cmd << quote_option('platform', args)
938
918
  args = nil
939
919
  end
940
920
  else
941
921
  cmd << opts << '--'
942
922
  end
943
- cmd.merge(Array(out).map! { |val| val.split(/\s+/, 2).first })
923
+ cmd.merge(Array(out).map { |val| val.split(/\s+/, 2).first })
944
924
  cmd << args
945
- success?(run(cmd, { 'NO_COLOR' => 'true' }), ctx.start_with?(/network|tag|save/))
925
+ success?(run(cmd), ctx.start_with?('network', 'tag', 'save'))
946
926
  end
947
927
 
948
928
  def filetype(val = dockerfile)
@@ -965,11 +945,12 @@ module Squared
965
945
  end
966
946
 
967
947
  def tagjoin(*args, char: '/')
968
- args.join(char) unless (args = args.compact).empty?
948
+ args.compact!
949
+ args.map { |val| val.chomp(char) }.join(char) unless args.empty?
969
950
  end
970
951
 
971
952
  def tagname(val)
972
- val = val.split(':').map! { |s| charname(s.sub(/^\W+/, '')) }
953
+ val = val.split(':').map { |s| charname(s.sub(/^\W+/, '')) }
973
954
  ret = val.join(':')
974
955
  ret = val.first if val.size > 1 && ret.size > 128
975
956
  ret[0..127]
@@ -986,6 +967,14 @@ module Squared
986
967
  def tagmain
987
968
  tag.is_a?(Array) ? tag.first : tag
988
969
  end
970
+
971
+ def emptyargs(list, hint = nil)
972
+ raise_error ArgumentError, "unrecognized args: #{list.join(', ')}", hint: hint unless list.empty?
973
+ end
974
+
975
+ def anypath?(*args)
976
+ args.any? { |val| basepath!(val) }
977
+ end
989
978
  end
990
979
 
991
980
  Application.implement Docker