squared 0.4.24 → 0.5.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.
@@ -4,67 +4,65 @@ module Squared
4
4
  module Workspace
5
5
  module Project
6
6
  class Docker < Base
7
- COMPOSEFILE = %w[compose.yaml compose.yml docker-compose.yaml docker-compose.yml].freeze
7
+ COMPOSEFILE = %w[compose.yaml compose.yml docker-compose.yaml compose.yml docker-compose.yml].freeze
8
8
  BAKEFILE = %w[docker-bake.json docker-bake.hcl docker-bake.override.json docker-bake.override.hcl].freeze
9
- DIR_DOCKER = (COMPOSEFILE + BAKEFILE + ['Dockerfile']).freeze
9
+ DIR_DOCKER = (COMPOSEFILE + BAKEFILE).freeze
10
10
  OPT_DOCKER = {
11
11
  common: %w[tls tlsverify config=p c|context=b D|debug H|host=q l|log-level=b tlscacert=p tlscert=p
12
12
  tlskey=p].freeze,
13
13
  buildx: {
14
14
  common: %w[builder=b D|debug],
15
- build: %w[add-host=q annotation=q attest=q build-arg=qq build-context=qq cache-from=q cache-to=q
16
- cgroup-parent=b ent=q iidfile=p label=q a-file=p network=b no-cache-filter=b o|output=q platform=b
17
- q|quiet secret=qq shm-size=b ssh=qq t|tag=b target=b ulimit=q].freeze,
18
- bake: %w[print list=q set=q].freeze,
19
- shared: %w[check load no-cache pull push allow=q call=b? f|file=p metadata-file=p progress=b provenance=q
20
- sbom=q].freeze
15
+ build: %w[load pull push add-host=q annotation=q attest=q build-arg=qq ent=q iidfile=p label=q a-file=p
16
+ network=b no-cache-filter=b o|output=q platform=b q|quiet secret=qq shm-size=b ssh=qq t|tag=b
17
+ target=b ulimit=q].freeze,
18
+ bake: %w[load print pull push list=q metadata-file=p set=q].freeze,
19
+ shared: %w[check no-cache allow=q call=b? f|file=p progress=b provenance=q sbom=q].freeze
21
20
  }.freeze,
22
21
  compose: {
23
- common: %w[all-resources compatibility dry-run ansi|b env-file=p f|file=p parallel=n profile=b progress=b
22
+ common: %w[all-resources compatibility dry-run ansi|b env-file=p f|file=p parallel=b profile=b progress=b
24
23
  project-directory=p p|project-name=e].freeze,
25
- build: %w[check no-cache print pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b
26
- provenance=q sbom=q ssh=qq].freeze,
27
- exec: %w[d|detach privileged e|env=qq index=i T|no-TTY=b? user=e w|workdir=q].freeze,
28
- run: %w[build d|detach no-deps q|quiet quiet-build quiet-pull remove-orphans rm P|service-ports use-aliases
29
- cap-add=b cap-drop=b q e|env=qq env-from-file=p i|interactive=b? l|label=q name=b T|no-TTY=b?
30
- p|publish=e pull=b u|user=e v|volume=q w|workdir=q].freeze,
31
- up: %w[abort-on-container-exit abort-on-container-failure always-recreate-deps attach-dependencies build
32
- d|detach force-recreate menu no-build no-color no-deps no-log-prefix no-recreate no-start quiet-build
33
- quiet-pull remove-orphans V|renew-anon-volumes timestamps wait w|watch y|yes attach=b
34
- exit-code-from=b no-attach=b pull=b scale=i t|timeout=i wait-timeout=i].freeze,
35
- down: %w[remove-orphans v|volumes rmi=b t|timeout=i].freeze
24
+ build: %w[no-cache pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b ssh=qq].freeze,
25
+ exec: %w[dry-run privileged d|detach e|env=qq index=i T|no-TTY=b? user=e w|workdir=q].freeze,
26
+ run: %w[build dry-run no-deps quiet-pull remove-orphans rm P|service-ports use-aliases cap-add=b cap-drop=b
27
+ d|detach entrypoint=q e|env=qq i|interactive=b? l|label=q name=b T|no-TTY=b? p|publish=e pull=b
28
+ u|user=e v|volume=q w|workdir=q].freeze,
29
+ up: %w[y abort-on-container-exit abort-on-container-failure always-recreate-deps attach-dependencies build
30
+ d|detach dry-run force-recreate menu no-build no-color no-deps no-log-prefix no-recreate no-start
31
+ quiet-pull remove-orphans V|renew-anon-volumes timestamps wait w|watch attach=b exit-code-from=b
32
+ no-attach=b pull=b scale=i t|timeout=i wait-timeout=i].freeze
36
33
  }.freeze,
37
34
  container: {
38
35
  create: %w[init i|interactive no-healthcheck oom-kill-disable privileged P|publish-all q|quiet read-only
39
- rm runtime t|tty use-api-socket io-maxbandwidth=b io-maxiops=b add-host=q annotation=q a|attach=b
40
- blkio-weight=i blkio-weight-device=i cap-add=b cap-drop=b cgroup-parent=b cgroupns=b cidfile=p
41
- device=q device-cgroup-rule=q device-read-bps=q device-read-iops=q device-write-bps=q
36
+ rm runtime t|tty use-api-socket io-maxbandwidth=b io-maxiops=b add-host=q annotation=q
37
+ a|attach=b blkio-weight=i blkio-weight-device=i cap-add=b cap-drop=b cgroup-parent=b cgroupns=b
38
+ cidfile=p device=q device-cgroup-rule=q device-read-bps=q device-read-iops=q device-write-bps=q
42
39
  device-write-iops=q disable-content-trust=b? dns=e dns-option=e dns-search=e domainname=b
43
- entrypoint=q e|env=qq env-file=p expose=e gpus=q group-add=b health-cmd=q health-interval=b ip6=e
44
- ipc=b isolation=b kernel-memory=b l|label=q label-file=p link=b link-local-ip=b log-driver=b
45
- log-opt=q mac-address=e m|memory=b memory-reservation=b memory-swap=n memory-swappiness=n
46
- mount=qq name=b network=b network-alias=b oom-score-adj=b pid=b pids-limit=n platform=b
47
- p|publish=e pull=b restart=b runtime=b security-opt=q shm-size=b stop-signal=b stop-timeout=i
48
- storage-opt=q sysctl=q tmpfs=q ulimit=q u|user=b userns=b uts=b v|volume=q volume-driver=b
49
- volumes-from=b w|workdir=q].freeze,
40
+ entrypoint=q e|env=qq env-file=p expose=e gpus=q group-add=b health-cmd=q health-interval=b
41
+ health-retries=i health-start-interval=b health-start-period=b health-timeout=b h|hostname=e ip=b
42
+ ip6=e ipc=b isolation=b kernel-memory=b l|label=q label-file=p link=b link-local-ip=b
43
+ log-driver=b log-opt=q mac-address=e m|memory=b memory-reservation=b memory-swap=n
44
+ memory-swappiness=n mount=qq name=b network=b network-alias=b oom-score-adj=b pid=b pids-limit=n
45
+ platform=b p|publish=e pull=b restart=b runtime=b security-opt=q shm-size=b stop-signal=b
46
+ stop-timeout=i storage-opt=q sysctl=q tmpfs=q ulimit=q u|user=b userns=b uts=b v|volume=q
47
+ volume-driver=b volumes-from=b w|workdir=q].freeze,
50
48
  run: %w[d|detach detach-keys=q sig-proxy=b?].freeze,
49
+ exec: %w[d|detach i|interactive privileged t|tty detach-keys=q e|env=qq env-file=p user=e
50
+ w|workdir=q].freeze,
51
51
  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
52
52
  cpuset-cpus=b cpuset-mems=b m|memory=b memory-reservation=b memory-swap=b pids-limit=n
53
53
  restart=q].freeze,
54
- exec: %w[d|detach i|interactive privileged t|tty detach-keys=q e|env=qq env-file=p user=e
55
- w|workdir=q].freeze,
56
54
  commit: %w[a|author=q c|change=q m|message=q pause=b?].freeze,
57
55
  inspect: %w[s|size f|format=q].freeze,
58
56
  start: %w[a|attach i|interactive detach-keys=q].freeze,
59
- stop: %w[s|signal=b t|time=i t|timeout=i].freeze,
60
- restart: %w[s|signal=b t|time=i t|timeout=i].freeze,
57
+ stop: %w[s|signal=b t|time=i].freeze,
58
+ restart: %w[s|signal=b t|time=i].freeze,
61
59
  kill: %w[s|signal=b].freeze,
62
- stats: %w[a|all no-stream no-trunc format|q].freeze
60
+ stats: %w[no-trunc format|q].freeze
63
61
  }.freeze,
64
62
  image: {
65
- list: %w[a|all q|quiet digests no-trunc tree f|filter=q format=q].freeze,
63
+ list: %w[a|all digests no-trunc f|filter=q format=q].freeze,
66
64
  push: %w[a|all-tags disable-content-trust=b? platform=b q|quiet].freeze,
67
- rm: %w[f|force no-prune platform=b].freeze,
65
+ rm: %w[f|force no-prune].freeze,
68
66
  save: %w[o|output=p platform=b].freeze
69
67
  }.freeze,
70
68
  network: {
@@ -74,11 +72,8 @@ module Squared
74
72
  }.freeze
75
73
  VAL_DOCKER = {
76
74
  run: {
77
- common: %w[source src destination dst target readonly ro].freeze,
78
- bind: %w[bind-propagation].freeze,
79
- volume: %w[volume-subpath volume-nocopy volume-opt].freeze,
80
- tmpfs: %w[tmpfs-size tmpfs-mode].freeze,
81
- image: %w[image-path].freeze
75
+ bind: %w[type source src destination dst target readonly ro bind-propagation].freeze,
76
+ tmpfs: %w[type destination dst target tmpfs-size tmpfs-mode].freeze
82
77
  }.freeze
83
78
  }.freeze
84
79
  private_constant :COMPOSEFILE, :BAKEFILE, :OPT_DOCKER, :VAL_DOCKER
@@ -91,14 +86,13 @@ module Squared
91
86
  def config?(val)
92
87
  return false unless (val = as_path(val))
93
88
 
94
- DIR_DOCKER.any? { |file| val.join(file).exist? }
89
+ val.join('Dockerfile').exist? || DIR_DOCKER.any? { |file| val.join(file).exist? }
95
90
  end
96
91
  end
97
92
 
98
93
  subtasks({
99
- 'build' => %i[tag context].freeze,
100
- 'compose' => %i[build run exec up down].freeze,
101
- 'bake' => %i[build check].freeze,
94
+ 'build' => %i[tag context bake].freeze,
95
+ 'compose' => %i[build run exec up].freeze,
102
96
  'image' => %i[list rm push tag save].freeze,
103
97
  'container' => %i[run create exec update commit inspect diff start stop restart pause unpause top stats kill
104
98
  rm].freeze,
@@ -113,10 +107,11 @@ module Squared
113
107
  return unless dockerfile(file).exist?
114
108
 
115
109
  @context = context
116
- self.tag = tag || tagname("#{@project}:#{@version || 'latest'}")
110
+ @tag = tag || tagname("#{@project}:#{@version || 'latest'}")
117
111
  @mounts = mounts
118
112
  @secrets = secrets
119
113
  @registry = tagjoin registry, kwargs[:username]
114
+ @file = nil
120
115
  initialize_ref Docker.ref
121
116
  initialize_logger(**kwargs)
122
117
  initialize_env(**kwargs)
@@ -129,11 +124,11 @@ module Squared
129
124
 
130
125
  def populate(*, **)
131
126
  super
132
- return unless ref?(Docker.ref) || @only
127
+ return unless ref?(Docker.ref)
133
128
 
134
129
  namespace name do
135
130
  Docker.subtasks do |action, flags|
136
- next if task_pass?(action)
131
+ next if @pass.include?(action)
137
132
 
138
133
  namespace action do
139
134
  flags.each do |flag|
@@ -146,39 +141,26 @@ module Squared
146
141
  param = param_guard(action, flag, args: args, key: flag)
147
142
  buildx(:build, args.extras, "#{flag}": param)
148
143
  end
149
- end
150
- when 'bake'
151
- break unless bake?
152
-
153
- case flag
154
- when :build
155
- format_desc action, flag, 'opts*,target*,context?|:'
144
+ when :bake
145
+ format_desc action, flag, ':?,opts*,target*,context?'
156
146
  task flag do |_, args|
157
147
  args = args.to_a
158
148
  if args.first == ':'
159
149
  choice_command :bake
160
150
  else
161
- buildx :bake, args
151
+ buildx flag, args
162
152
  end
163
153
  end
164
- when :check
165
- format_desc action, flag, 'target'
166
- task flag, [:target] do |_, args|
167
- target = param_guard(action, flag, args: args, key: :target)
168
- buildx :bake, ['allow=fs.read=*', 'call=check', target]
169
- end
170
154
  end
171
155
  when 'compose'
172
- break unless compose?
173
-
174
156
  case flag
175
- when :build, :up, :down
157
+ when :build, :up
176
158
  format_desc action, flag, 'opts*,service*'
177
159
  task flag do |_, args|
178
160
  compose! flag, args.to_a
179
161
  end
180
162
  when :exec, :run
181
- format_desc action, flag, "service,command#{flag == :exec ? '' : '?'}|:,args*,opts*"
163
+ format_desc action, flag, "service,command#{flag == :exec ? '' : '?'},args*,opts*"
182
164
  task flag, [:service] do |_, args|
183
165
  service = param_guard(action, flag, args: args, key: :service)
184
166
  compose!(flag, args.extras, service: service)
@@ -206,7 +188,7 @@ module Squared
206
188
  end
207
189
  end
208
190
  else
209
- format_desc action, flag, "opts*,id/name#{flag == :update ? '+' : '*'}"
191
+ format_desc(action, flag, 'opts*,id/name', after: flag == :update ? '+' : '*')
210
192
  task flag do |_, args|
211
193
  container flag, args.to_a
212
194
  end
@@ -216,15 +198,14 @@ module Squared
216
198
  when :push
217
199
  format_desc action, flag, 'tag,registry/username?,opts*'
218
200
  task flag, [:tag] do |_, args|
219
- tag = param_guard(action, flag, args: args, key: :tag)
220
- image(flag, args.extras, id: tag)
201
+ id = param_guard(action, flag, args: args, key: :tag)
202
+ image(flag, args.extras, id: id)
221
203
  end
222
204
  else
223
205
  format_desc(action, flag, case flag
224
206
  when :rm, :save then 'id*,opts*'
225
207
  when :tag then 'version?'
226
- else 'opts*,args*'
227
- end)
208
+ else 'opts*,args*' end)
228
209
  task flag do |_, args|
229
210
  args = args.to_a
230
211
  if args.empty? && flag != :list
@@ -251,11 +232,9 @@ module Squared
251
232
  end
252
233
 
253
234
  def clean(*, sync: invoked_sync?('clean'), **)
254
- if runnable?(@clean)
255
- super
256
- else
257
- image(:rm, sync: sync)
258
- end
235
+ return super unless @clean.nil?
236
+
237
+ image(:rm, sync: sync)
259
238
  end
260
239
 
261
240
  def compose(opts, flags = nil, script: false, args: nil, from: :run, **)
@@ -263,12 +242,13 @@ module Squared
263
242
 
264
243
  ret = docker_session
265
244
  if from == :run
266
- if bake?(n = filetype)
267
- ret << 'buildx bake'
245
+ case (n = filetype)
246
+ when 1, 2
247
+ ret << 'buildx' << 'bake'
268
248
  append_file n
269
249
  from = :bake
270
- elsif compose?(n)
271
- ret << 'compose build'
250
+ when 3, 4
251
+ ret << 'compose' << 'build'
272
252
  append_file n
273
253
  from = :compose
274
254
  else
@@ -281,15 +261,14 @@ module Squared
281
261
  when String
282
262
  ret << opts
283
263
  when Hash
284
- ret.merge(append_hash(opts, target: [], build: true))
264
+ ret.merge(append_hash(opts, build: true))
285
265
  when Enumerable
286
266
  ret.merge(opts.to_a)
287
267
  end
268
+
288
269
  [args, flags].each_with_index do |target, index|
289
- if target.is_a?(String)
290
- ret << target
291
- elsif (target = append_any(target, target: []))
292
- ret.merge(target.map { |arg| index == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
270
+ if (data = append_any(target, target: []))
271
+ ret.merge(data.map { |arg| index == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
293
272
  end
294
273
  end
295
274
  case from
@@ -299,12 +278,12 @@ module Squared
299
278
  ret << quote_option('secret', @secrets, double: true)
300
279
  when Hash
301
280
  append = lambda do |type|
302
- Array(@secrets[type]).each { |arg| ret << quote_option('secret', "type=#{type},#{arg}", double: true) }
281
+ as_a(@secrets[type]).each { |arg| ret << quote_option('secret', "type=#{type},#{arg}", double: true) }
303
282
  end
304
283
  append.call(:file)
305
284
  append.call(:env)
306
285
  else
307
- Array(@secrets).each { |arg| ret << quote_option('secret', arg) }
286
+ as_a(@secrets).each { |arg| ret << quote_option('secret', arg) }
308
287
  end
309
288
  if (val = option('tag', ignore: false))
310
289
  append_tag val
@@ -313,8 +292,8 @@ module Squared
313
292
  end
314
293
  append_context
315
294
  when :bake, :compose
316
- if (val = option(from == :bake ? 'target' : 'service', ignore: false))
317
- ret.merge(split_escape(val).map! { |s| shell_quote(s) })
295
+ option(from == :bake ? 'target' : 'service', ignore: false) do |a|
296
+ ret.merge(split_escape(a).map! { |b| shell_escape(b) })
318
297
  end
319
298
  end
320
299
  ret
@@ -327,20 +306,20 @@ module Squared
327
306
  op.parse(OPT_DOCKER[:buildx][flag == :bake ? :bake : :build] + OPT_DOCKER[:buildx][:shared])
328
307
  case flag
329
308
  when :build, :context
330
- append_tag(tag || option('tag', ignore: false) || self.tag)
309
+ append_tag(tag || option('tag', ignore: false) || @tag)
331
310
  append_context context
332
311
  when :bake
333
312
  unless op.empty?
334
313
  args = op.dup
335
- op.reset
314
+ op.extras.clear
336
315
  if Dir.exist?(args.last)
337
316
  if projectpath?(val = args.pop)
338
317
  context = val
339
318
  else
340
- op.push(val)
319
+ op.extras << val
341
320
  end
342
321
  end
343
- op.append(args, escape: true, strip: /^:/)
322
+ op.append(args, escape: true)
344
323
  contextdir context if context
345
324
  end
346
325
  end
@@ -353,14 +332,15 @@ module Squared
353
332
  op = OptionPartition.new(opts, OPT_DOCKER[:compose][:common], cmd, project: self)
354
333
  append_file filetype unless op.arg?('f', 'file')
355
334
  op << flag
356
- op.parse(OPT_DOCKER[:compose].fetch(flag, []))
335
+ op.parse(OPT_DOCKER[:compose][flag])
336
+ from = :"compose:#{flag}"
357
337
  case flag
358
- when :build, :up, :down
359
- op.append(escape: true, strip: /^:/)
338
+ when :build, :up
339
+ op.append(escape: true)
360
340
  when :exec, :run
361
- append_command flag, service, op.extras
341
+ append_command(flag, service, op.extras, from: from)
362
342
  end
363
- run(from: :"compose:#{flag}")
343
+ run(from: from)
364
344
  end
365
345
 
366
346
  def container(flag, opts = [], id: nil)
@@ -375,54 +355,44 @@ module Squared
375
355
  when :run, :create, :exec
376
356
  if rc && !op.arg?('mount')
377
357
  run = VAL_DOCKER[:run]
378
- all = collect_hash VAL_DOCKER[:run]
379
- delim = Regexp.new(",\\s*(?=#{all.join('|')})")
380
- Array(@mounts).each do |val|
358
+ both = run[:bind] + run[:tmpfs]
359
+ diff = run[:bind].reject { |val| run[:tmpfs].include?(val) }
360
+ delim = Regexp.new(",\\s*(?=#{both.join('|')})")
361
+ as_a(@mounts).each do |val|
381
362
  args = []
382
- type = nil
363
+ tmpfs = true
383
364
  val.split(delim).each do |opt|
384
365
  k, v, q = split_option opt
385
- if k == 'type'
386
- case v
387
- when 'bind', 'volume', 'image', 'tmpfs'
388
- type = v
389
- else
390
- raise_error("unknown type: #{v}", hint: flag)
391
- end
392
- elsif all.include?(k)
393
- unless type
394
- run.each_pair do |key, val|
395
- next unless val.include?(k)
366
+ next unless both.include?(k)
396
367
 
397
- type = key.to_s unless key == :common
398
- break
399
- end
400
- end
401
- case k
402
- when 'readonly', 'ro'
403
- args << k
404
- next
405
- when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-path'
406
- v = basepath v
407
- v = shell_quote(v, option: false, force: false) if q == ''
408
- end
409
- args << "#{k}=#{q + v + q}"
410
- elsif verbose
411
- log_message(Logger::INFO, 'unrecognized option', subject: from, hint: k)
368
+ if k == 'type'
369
+ tmpfs = false if v == 'bind'
370
+ next
371
+ elsif diff.include?(k)
372
+ tmpfs = false
373
+ end
374
+ case k
375
+ when 'readonly', 'ro'
376
+ args << k
377
+ next
378
+ when 'source', 'src', 'destination', 'dst', 'target'
379
+ v = path + v
380
+ v = shell_quote(v, option: false, force: false) if q == ''
381
+ tmpfs = false if k[0] == 's'
412
382
  end
383
+ args << "#{k}=#{q + v + q}"
413
384
  end
414
- raise_error('missing type', hint: flag) unless type
415
- cmd << "--mount type=#{type},#{args.join(',')}"
385
+ cmd << "--mount type=#{tmpfs ? 'tmpfs' : 'bind'},#{args.join(',')}"
416
386
  end
417
387
  end
418
- append_command(flag, id || tagmain, op.extras)
388
+ append_command(flag, id || tagmain, op.extras, from: from)
419
389
  when :update
420
- raise_error('missing container', hint: flag) if op.empty?
421
- op.append(escape: true, strip: /^:/)
390
+ raise_error('missing container', hint: from) if op.empty?
391
+ op.append(escape: true)
422
392
  when :commit
423
393
  latest = op.shift || tagmain
424
394
  cmd << id << latest
425
- raise_error("unknown args: #{op.join(', ')}", hint: flag) unless op.empty?
395
+ raise_error("unknown args: #{op.join(', ')}", hint: from) unless op.empty?
426
396
  return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
427
397
 
428
398
  registry = option('registry') || @registry
@@ -465,13 +435,13 @@ module Squared
465
435
  when :rm
466
436
  status = %w[created exited dead]
467
437
  end
468
- ps = docker_output('ps -a', *status.map { |s| quote_option('filter', "status=#{s}") })
438
+ ps = docker_output('ps -a', *status.map { |s| "--filter=\"status=#{s}\"" })
469
439
  list_image(flag, ps, no: no, hint: "status: #{status.join(', ')}", from: from) do |img|
470
440
  run(cmd.temp(img), from: from)
471
441
  end
472
442
  return
473
443
  else
474
- op.append(escape: true, strip: /^:/)
444
+ op.append(escape: true)
475
445
  end
476
446
  end
477
447
  run(from: from)
@@ -522,17 +492,17 @@ module Squared
522
492
  list_image(flag, docker_output('image ls -a'), from: from) do |val|
523
493
  op << val
524
494
  if flag == :tag
525
- op << tagname("#{project}:#{op.first}")
495
+ op << tagname("#{@project}:#{op.extras.first}")
526
496
  break
527
497
  end
528
498
  end
529
499
  when :push
530
500
  id ||= option('tag', ignore: false) || tagmain
531
501
  registry ||= op.shift || option('registry') || @registry
532
- raise_error(id ? "unknown args: #{op.join(', ')}" : 'no id/tag given', hint: flag) unless id && op.empty?
533
- raise_error('username/registry not provided', hint: flag) unless registry
502
+ raise_error(id ? "unknown args: #{op.join(', ')}" : 'no id/tag given', hint: from) unless id && op.empty?
503
+ raise_error('username/registry not provided', hint: from) unless registry
534
504
  registry.chomp!('/')
535
- uri = shell_quote("#{registry}/#{id}")
505
+ uri = shell_quote "#{registry}/#{id}"
536
506
  op << uri
537
507
  img = docker_output 'image', 'tag', id, uri
538
508
  return unless confirm_command(img.to_s, cmd.to_s, target: id, as: registry, title: from)
@@ -548,7 +518,7 @@ module Squared
548
518
 
549
519
  def network(flag, opts = [], target: nil)
550
520
  cmd, opts = docker_session('network', flag, opts: opts)
551
- op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self)
521
+ op = OptionPartition.new(opts, OPT_DOCKER[:network][flag], cmd, project: self)
552
522
  op.clear
553
523
  from = :"network:#{flag}"
554
524
  list_image(flag, docker_output('ps -a'), from: from) do |img|
@@ -564,28 +534,17 @@ module Squared
564
534
  super || dockerfile.exist?
565
535
  end
566
536
 
567
- def compose?(file = dockerfile)
568
- return file == 3 || file == 4 if file.is_a?(Numeric)
569
-
570
- COMPOSEFILE.include?(File.basename(file))
571
- end
572
-
573
- def bake?(file = dockerfile)
574
- return file == 1 || file == 2 if file.is_a?(Numeric)
575
-
576
- BAKEFILE.include?(File.basename(file))
577
- end
578
-
579
537
  def dockerfile(val = nil)
580
- if val
538
+ if val == 'Dockerfile'
539
+ @file = false
540
+ elsif val
581
541
  @file = if val.is_a?(Array)
582
542
  val = val.select { |file| basepath(file).exist? }
583
543
  val.size > 1 ? val : val.first
584
- elsif val == true
585
- DIR_DOCKER.find { |file| basepath(file).exist? }
586
- elsif val != 'Dockerfile'
587
- val
544
+ else
545
+ val || DIR_DOCKER.find { |file| basepath(file).exist? }
588
546
  end
547
+ @file ||= false
589
548
  end
590
549
  basepath((@file.is_a?(Array) ? @file.first : @file) || 'Dockerfile')
591
550
  end
@@ -604,41 +563,35 @@ module Squared
604
563
  session('docker', *cmd, main: false, options: false, **kwargs)
605
564
  end
606
565
 
607
- def append_command(flag, val, list, target: @session)
608
- if list.delete(':')
609
- list << readline('Enter command [args]', force: true)
610
- elsif (args = env('DOCKER_ARGS'))
566
+ def append_command(flag, val, list, target: @session, from: nil)
567
+ if (args = env('DOCKER_ARGS'))
611
568
  list << args
612
569
  end
613
570
  case flag
614
571
  when :run
615
572
  unless session_arg?('name', target: target)
616
- target << basic_option('name', dnsname("#{name}_%s" % rand_s(6)))
573
+ target << basic_option('name', dnsname("#{name}_%s" % if RUBY_VERSION >= '3.1'
574
+ require 'random/formatter'
575
+ Random.new.alphanumeric(6)
576
+ else
577
+ (0...6).map { rand(97..122).chr }.join
578
+ end))
617
579
  end
618
580
  when :exec
619
- raise_error('no command args', hint: flag) if list.empty?
581
+ raise_error('no command args', hint: from) if list.empty?
620
582
  end
621
583
  target << val << list.shift
622
584
  target << list.join(' && ') unless list.empty?
623
585
  end
624
586
 
625
- def append_file(type, target: @session, index: 2)
626
- return if !@file || (ENV['COMPOSE_FILE'] && compose?(type))
587
+ def append_file(type, target: @session)
588
+ return unless type == 2 || type == 4 || @file.is_a?(Array)
627
589
 
628
- unless @file.is_a?(Array)
629
- case type
630
- when 2, 4
631
- return
632
- when 3
633
- return unless COMPOSEFILE.map { |val| basepath(val) }.select(&:exist?).size > 1
634
- end
635
- end
636
- files = Array(@file).map { |val| quote_option('file', basepath(val)) }
590
+ files = as_a(@file).map { |val| quote_option('file', path + val) }
637
591
  if target.is_a?(Set)
638
- opts = target.to_a.insert(index, *files)
639
- target.clear.merge(opts)
592
+ target.merge(files)
640
593
  else
641
- target.insert(index, *files)
594
+ target.concat(files)
642
595
  end
643
596
  end
644
597
 
@@ -650,18 +603,20 @@ module Squared
650
603
  end
651
604
 
652
605
  def append_tag(val, target: @session)
653
- ver = option('version', target: target, ignore: false)
654
- list = case val
655
- when String
656
- split_escape val
657
- when Array
658
- val
659
- else
660
- []
661
- end
662
- list.each do |s|
663
- s = "#{s.sub(/:latest$/, '')}:#{ver}" if ver && (!s.include?(':') || s.end_with?(':latest'))
664
- target << basic_option('tag', tagname(s))
606
+ case val
607
+ when String
608
+ val.split(',')
609
+ when Array
610
+ val
611
+ else
612
+ []
613
+ end.yield_self do |list|
614
+ ver = option('version', target: target, ignore: false)
615
+ list.each do |s|
616
+ s = "#{s}:#{ver}" if ver && (!s.include?(':') || s.delete_suffix!(':latest'))
617
+ target << basic_option('tag', tagname(s))
618
+ end
619
+ target
665
620
  end
666
621
  end
667
622
 
@@ -671,7 +626,7 @@ module Squared
671
626
  index = 0
672
627
  all = option('all', prefix: 'docker')
673
628
  y = from == :'image:rm' && option('y', prefix: 'docker')
674
- pat = /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})\b/
629
+ pat = /^(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})(?:[_.,:-]|$)/
675
630
  IO.popen(session_done(cmd << '--format=json')).each do |line|
676
631
  data = JSON.parse(line)
677
632
  id = data['ID']
@@ -708,7 +663,7 @@ module Squared
708
663
  cols.each do |key|
709
664
  next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
710
665
 
711
- puts "#{g + f} #{key}: #{Array(data[key]).join(', ')}" unless data[key].to_s.empty?
666
+ puts "#{g + f} #{key}: #{as_a(data[key]).join(', ')}" unless data[key].to_s.empty?
712
667
  end
713
668
  w = 9 + flag.to_s.size + 4 + ee.size
714
669
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
@@ -720,10 +675,14 @@ module Squared
720
675
  end
721
676
  yield id
722
677
  end
723
- puts log_message(Logger::INFO, 'none detected', subject: name, hint: hint || from) if !found && !y
678
+ puts log_message(Logger::INFO, 'none detected', subject: "#{name}:#{from}", hint: hint) if found || y
724
679
  end
725
680
  rescue StandardError => e
726
- on_error e, from
681
+ log.error e
682
+ ret = on(:error, from, e)
683
+ raise if exception && ret != true
684
+
685
+ warn log_message(Logger::WARN, e, pass: true) if warning?
727
686
  end
728
687
 
729
688
  def confirm_command(*args, title: nil, target: nil, as: nil)
@@ -780,7 +739,7 @@ module Squared
780
739
  cmd = docker_output ctx
781
740
  case flag
782
741
  when :tag
783
- args = tagjoin @registry, tag
742
+ args = tagjoin @registry, @tag
784
743
  when :save
785
744
  opts = "#{opts}.tar" unless opts.end_with?('.tar')
786
745
  cmd << quote_option('output', File.expand_path(opts))
@@ -791,9 +750,13 @@ module Squared
791
750
  else
792
751
  cmd << opts << '--'
793
752
  end
794
- cmd.merge(Array(out).map! { |val| parse.call(val) })
753
+ cmd.merge(if out.is_a?(Array)
754
+ out.map! { |val| parse.call(val) }
755
+ else
756
+ [parse.call(out)]
757
+ end)
795
758
  cmd << args
796
- print_success if success?(run(cmd)) && ctx.match?(/\A(?:network|tag|save)/)
759
+ print_success if success?(run(cmd)) && ctx.start_with?(/(?:network|tag|save)/)
797
760
  end
798
761
  end
799
762
 
@@ -803,7 +766,7 @@ module Squared
803
766
  bake?(val) ? 1 : 2
804
767
  when '.yml', '.yaml'
805
768
  if compose?(val)
806
- @only&.include?('compose') || path.children.none? { |file| bake?(file) } ? 3 : 1
769
+ path.children.any? { |file| bake?(file) } ? 1 : 3
807
770
  else
808
771
  4
809
772
  end
@@ -813,7 +776,7 @@ module Squared
813
776
  end
814
777
 
815
778
  def contextdir(val = nil)
816
- val && projectpath?(val) ? shell_quote(basepath(val)) : '.'
779
+ val && projectpath?(val) ? shell_quote(path + val) : '.'
817
780
  end
818
781
 
819
782
  def tagjoin(*args, char: '/')
@@ -823,9 +786,10 @@ module Squared
823
786
 
824
787
  def tagname(val)
825
788
  val = val.split(':').map! { |s| charname(s.sub(/^\W+/, '')) }
826
- ret = val.join(':')
827
- ret = val.first if val.size > 1 && ret.size > 128
828
- ret[0..127]
789
+ val.join(':').yield_self do |s|
790
+ s = val.first if val.size > 1 && s.size > 128
791
+ s[0..127]
792
+ end
829
793
  end
830
794
 
831
795
  def dnsname(val)
@@ -839,6 +803,14 @@ module Squared
839
803
  def tagmain
840
804
  tag.is_a?(Array) ? tag.first : tag
841
805
  end
806
+
807
+ def compose?(file = dockerfile)
808
+ COMPOSEFILE.include?(File.basename(file))
809
+ end
810
+
811
+ def bake?(file = dockerfile)
812
+ BAKEFILE.include?(File.basename(file))
813
+ end
842
814
  end
843
815
 
844
816
  Application.implement Docker