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