squared 0.6.20 → 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.
@@ -16,30 +16,31 @@ module Squared
16
16
  cgroup-parent=b iidfile=p label=q network=b no-cache-filter=b o|output=q platform=q
17
17
  q|quiet secret=qq shm-size=b ssh=qq t|tag=b target=b ulimit=q].freeze,
18
18
  bake: %w[print list=q set=q].freeze,
19
- shared: %w[check load no-cache pull push allow=qq call=b? f|file=p metadata-file=p progress=b provenance=b?
20
- sbom=b?].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
21
21
  }.freeze,
22
22
  compose: {
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,
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
@@ -77,10 +78,10 @@ module Squared
77
78
  VAL_DOCKER = {
78
79
  run: {
79
80
  common: %w[source src destination dst target readonly ro].freeze,
80
- bind: %w[bind-create-src bind-propagation].freeze,
81
+ bind: %w[bind-propagation].freeze,
81
82
  volume: %w[volume-subpath volume-nocopy volume-opt].freeze,
82
83
  tmpfs: %w[tmpfs-size tmpfs-mode].freeze,
83
- image: %w[image-subpath].freeze
84
+ image: %w[image-path].freeze
84
85
  }.freeze,
85
86
  ls: {
86
87
  compose: %w[Name Image Command Service RunningFor Status Ports CreatedAt ExitCode Health ID Labels
@@ -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,99 +436,94 @@ 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
489
500
  end
490
501
  end
491
502
  case k
492
- when 'readonly', 'ro', 'volume-nocopy', 'bind-create-src'
493
- args << k if %w[bind volume].include?(type)
503
+ when 'readonly', 'ro'
504
+ out << k
494
505
  next
495
- when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-subpath'
496
- raise_error ArgumentError, "no path value: #{k}", hint: flag unless v
497
- case k
498
- when 'source', 'src'
499
- if type == 'bind'
500
- v = basepath v
501
- elsif type == 'tmpfs'
502
- next
503
- end
504
- when 'destination', 'dst', 'target'
505
- puts log_message("path is not absolute: #{v}", subject: from, hint: k) unless v.start_with?('/')
506
- end
506
+ when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-path'
507
+ raise_error ArgumentError, "#{k}: no path value", hint: flag unless v
508
+ v = basepath v
507
509
  v = shell_quote(v, option: false, force: false) if q == ''
508
510
  end
509
- args << "#{k}=#{q + v + q}"
511
+ out << "#{k}=#{q}#{v}#{q}"
510
512
  elsif !silent?
511
- puts log_message('unrecognized option', subject: from, hint: k)
513
+ log_message('unrecognized option', subject: from, hint: k)
512
514
  end
513
515
  end
514
516
  raise_error TypeError, 'none specified', hint: flag unless type
515
- op << "--mount type=#{type},#{args.join(',')}"
517
+ cmd << "--mount type=#{type},#{args.join(',')}"
516
518
  end
517
519
  end
518
520
  append_command(flag, id || tagmain, op.extras)
519
- when :update, :inspect
521
+ when :update
520
522
  raise_error ArgumentError, 'missing container', hint: flag if op.empty?
521
523
  op.append(escape: true, strip: /^:/)
522
524
  when :commit
523
525
  latest = op.shift || tagmain
524
- op << id << latest
526
+ cmd << id << latest
525
527
  raise_error ArgumentError, "unrecognized args: #{op.join(', ')}", hint: flag unless op.empty?
526
528
  return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
527
529
 
@@ -532,7 +534,7 @@ module Squared
532
534
  opts = []
533
535
  append_option('platform', target: opts, equals: true)
534
536
  opts << case option('disable-content-trust', ignore: false)
535
- when 'false', '0'
537
+ when '0', 'false'
536
538
  '--disable-content-trust=false'
537
539
  else
538
540
  '--disable-content-trust'
@@ -542,7 +544,7 @@ module Squared
542
544
  else
543
545
  if op.empty?
544
546
  ps, status, no = filter_ps flag, from
545
- op << '--no-stream' if flag == :stats
547
+ cmd << '--no-stream' if flag == :stats
546
548
  list_image(flag, ps, no: no, hint: status, from: from) { |img| run(cmd.temp(img), from: from) }
547
549
  return
548
550
  end
@@ -551,12 +553,12 @@ module Squared
551
553
  run(from: from)
552
554
  end
553
555
 
554
- def image(flag, opts = [], sync: true, id: nil, registry: nil, filter: nil)
556
+ def image(flag, opts = [], sync: true, id: nil, registry: nil)
555
557
  cmd, opts = docker_session('image', flag, opts: opts)
556
558
  op = OptionPartition.new(opts, OPT_DOCKER[:image].fetch(flag, []), cmd, project: self)
557
- exception = self.exception
559
+ exception = exception?
558
560
  banner = true
559
- from = :"image:#{flag}"
561
+ from = symjoin 'image', flag
560
562
  case flag
561
563
  when :ls
562
564
  if opts.size == op.size
@@ -569,7 +571,7 @@ module Squared
569
571
  opts.delete(val)
570
572
  break
571
573
  end
572
- list_image(:run, filter: filter, from: from) do |val|
574
+ list_image(:run, from: from) do |val|
573
575
  container(:run, if name
574
576
  opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
575
577
  else
@@ -583,7 +585,7 @@ module Squared
583
585
  when :rm
584
586
  unless id
585
587
  if op.empty?
586
- 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) }
587
589
  else
588
590
  op.each { |val| run(cmd.temp(val), sync: sync, from: from) }
589
591
  end
@@ -595,51 +597,48 @@ module Squared
595
597
  banner = false
596
598
  end
597
599
  when :tag, :save
598
- found = false
599
- list_image(flag, filter: filter, from: from) do |val|
600
+ list_image(flag, from: from) do |val|
600
601
  op << val
601
- found = true
602
- if flag == :tag
603
- op << tagname("#{project}:#{op.first}")
604
- break
605
- end
602
+ next unless flag == :tag
603
+
604
+ op << tagname("#{project}:#{op.first}")
605
+ break
606
606
  end
607
- raise_error ArgumentError, 'target not specified', hint: flag unless found
608
- when :push
609
- id ||= option('tag', ignore: false) || tagmain
610
- registry ||= op.shift || option('registry') || @registry
611
- unless id && op.empty?
612
- if id
613
- raise_error ArgumentError, "unrecognized args: #{op.join(', ')}", hint: flag
614
- else
615
- raise_error 'no id/tag', hint: flag
616
- end
607
+ when :pull
608
+ if !id
609
+ id = tagmain
610
+ elsif !op.arg?('a', 'all-tags') && !id.include?(':')
611
+ id = "#{project}:#{id}"
612
+ end
613
+ unless registry
614
+ registry = op.shift
615
+ registry ||= option('registry') || @registry unless id.include?('/')
617
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
618
622
  raise_error ArgumentError, 'username/registry not specified', hint: flag unless registry
619
- registry.chomp!('/')
620
- uri = shell_quote "#{registry}/#{id}"
623
+ uri = shell_quote tagjoin(registry, id)
621
624
  op << uri
622
625
  img = docker_output 'image', 'tag', id, uri
623
626
  return unless confirm_command(img.to_s, cmd.to_s, target: id, as: registry, title: from)
624
627
 
625
- cmd = img
628
+ @session = img
626
629
  sync = false
627
- exception = true
630
+ exception ||= true
628
631
  banner = false
629
632
  end
630
- run(cmd, sync: sync, exception: exception, banner: banner, from: from).tap do |ret|
631
- success?(ret, flag == :tag || flag == :save)
632
- end
633
+ success?(run(sync: sync, exception: exception, banner: banner, from: from), flag == :tag || flag == :save)
633
634
  end
634
635
 
635
636
  def network(flag, opts = [], target: nil)
636
637
  cmd, opts = docker_session('network', flag, opts: opts)
637
638
  OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self)
638
639
  .clear
639
- from = :"network:#{flag}"
640
- list_image(flag, docker_output('ps -a'), from: from) do |img|
641
- success?(run(cmd.temp(target, img), from: from))
642
- 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)) }
643
642
  end
644
643
 
645
644
  def build?
@@ -688,7 +687,7 @@ module Squared
688
687
  elsif (data = doc.dig(*keys))
689
688
  yield data
690
689
  end
691
- rescue StandardError => e
690
+ rescue => e
692
691
  log.debug e
693
692
  end
694
693
  end
@@ -705,11 +704,11 @@ module Squared
705
704
  end
706
705
 
707
706
  def append_command(flag, val, list, target: @session, prompt: ':')
708
- if list.delete(prompt)
709
- list << readline('Enter command [args]', force: flag == :exec)
710
- else
711
- env('DOCKER_ARGS') { |args| list << args }
712
- end
707
+ list << if list.delete(prompt)
708
+ readline('Enter command [args]', force: flag == :exec)
709
+ else
710
+ env('DOCKER_ARGS')
711
+ end
713
712
  case flag
714
713
  when :run
715
714
  unless session_arg?('name', target: target)
@@ -718,28 +717,14 @@ module Squared
718
717
  when :exec
719
718
  raise_error ArgumentError, 'nothing to execute', hint: flag if list.empty?
720
719
  end
721
- target << val
722
- case list.first
723
- when '--'
724
- target.merge(list.drop(1))
725
- when /\A(--|&&)(\s|\z)/
726
- target.merge(list)
727
- else
728
- target << list.shift
729
- unless list.empty?
730
- if list.first.start_with?('-')
731
- target.merge(list)
732
- else
733
- target << list.join(' && ')
734
- end
735
- end
736
- end
720
+ target << val << list.shift
721
+ target << list.join(' && ') unless list.empty?
737
722
  end
738
723
 
739
- def append_file(type, target: @session, index: 2)
740
- 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))
741
726
 
742
- unless @file.is_a?(Array)
727
+ unless @file.is_a?(Array) || force
743
728
  case type
744
729
  when 2, 4
745
730
  return
@@ -747,14 +732,7 @@ module Squared
747
732
  return unless COMPOSEFILE.select { |val| basepath!(val) }.size > 1
748
733
  end
749
734
  end
750
- files = Array(@file).map { |val| quote_option('file', basepath(val)) }
751
- if target.is_a?(Set)
752
- opts = target.to_a.insert(index, *files)
753
- target.clear
754
- .merge(opts)
755
- else
756
- target.insert(index, *files)
757
- end
735
+ target.insert(index, *Array(@file).map { |val| quote_option('file', basepath(val)) })
758
736
  end
759
737
 
760
738
  def append_context(ctx = nil, target: @session)
@@ -768,7 +746,7 @@ module Squared
768
746
  ver = option('version', target: target, ignore: false)
769
747
  case val
770
748
  when String
771
- split_escape val
749
+ val.split(',')
772
750
  else
773
751
  Array(val)
774
752
  end.each do |s|
@@ -807,20 +785,12 @@ module Squared
807
785
  [cmd, status, no]
808
786
  end
809
787
 
810
- 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)
811
789
  pwd_set(from: from) do
812
790
  index = 1
813
791
  all = option('all', prefix: 'docker')
814
792
  y = from == :'image:rm' && option('y', prefix: 'docker')
815
- filter = env('DOCKER_FILTER', filter).to_s
816
- pat = if OptionPartition.pattern?(filter)
817
- Regexp.new(filter)
818
- elsif filter.match?(/[:_-]$/)
819
- /\b#{Regexp.escape(filter)}/
820
- else
821
- filter = filter.empty? ? '(?:[:_-]|$)' : "[:_-]#{filter}"
822
- /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})#{filter}/
823
- end
793
+ pat = /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})\b/
824
794
  IO.popen(cmd.temp('--format=json')).each do |line|
825
795
  data = JSON.parse(line)
826
796
  id = data['ID']
@@ -869,7 +839,7 @@ module Squared
869
839
  end
870
840
  list_empty(hint: hint || from) if index == 1 && !y
871
841
  end
872
- rescue StandardError => e
842
+ rescue => e
873
843
  on_error e, from
874
844
  end
875
845
 
@@ -903,11 +873,11 @@ module Squared
903
873
  when :service
904
874
  ['Choose a service',
905
875
  'compose ps -a ' \
906
- '--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}}'"]
907
877
  else
908
878
  ['Choose an image',
909
879
  'images -a ' \
910
- '--format="table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.CreatedSince}}\t{{.Size}}"']
880
+ "--format='table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.CreatedSince}}\t{{.Size}}'"]
911
881
  end
912
882
  lines = `#{docker_output(cmd)}`.lines
913
883
  if lines.size <= 1
@@ -941,18 +911,18 @@ module Squared
941
911
  when :tag
942
912
  args = tagjoin @registry, tag
943
913
  when :save
944
- opts = "#{opts}.tar" unless opts.end_with?('.tar')
914
+ opts = opts.sub_ext('.tar')
945
915
  cmd << quote_option('output', File.expand_path(opts))
946
916
  if args
947
- cmd << basic_option('platform', args)
917
+ cmd << quote_option('platform', args)
948
918
  args = nil
949
919
  end
950
920
  else
951
921
  cmd << opts << '--'
952
922
  end
953
- cmd.merge(Array(out).map! { |val| val.split(/\s+/, 2).first })
923
+ cmd.merge(Array(out).map { |val| val.split(/\s+/, 2).first })
954
924
  cmd << args
955
- success?(run(cmd, { 'NO_COLOR' => 'true' }), ctx.start_with?(/network|tag|save/))
925
+ success?(run(cmd), ctx.start_with?('network', 'tag', 'save'))
956
926
  end
957
927
 
958
928
  def filetype(val = dockerfile)
@@ -975,11 +945,12 @@ module Squared
975
945
  end
976
946
 
977
947
  def tagjoin(*args, char: '/')
978
- args.join(char) unless (args = args.compact).empty?
948
+ args.compact!
949
+ args.map { |val| val.chomp(char) }.join(char) unless args.empty?
979
950
  end
980
951
 
981
952
  def tagname(val)
982
- val = val.split(':').map! { |s| charname(s.sub(/^\W+/, '')) }
953
+ val = val.split(':').map { |s| charname(s.sub(/^\W+/, '')) }
983
954
  ret = val.join(':')
984
955
  ret = val.first if val.size > 1 && ret.size > 128
985
956
  ret[0..127]
@@ -996,6 +967,14 @@ module Squared
996
967
  def tagmain
997
968
  tag.is_a?(Array) ? tag.first : tag
998
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
999
978
  end
1000
979
 
1001
980
  Application.implement Docker