squared 0.4.30 → 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)
@@ -375,54 +354,45 @@ module Squared
375
354
  case flag
376
355
  when :run, :create, :exec
377
356
  if rc && !op.arg?('mount')
378
- all = collect_hash VAL_DOCKER[:run]
379
- delim = Regexp.new(",\\s*(?=#{all.join('|')})")
380
- Array(@mounts).each do |val|
357
+ run = VAL_DOCKER[:run]
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
- VAL_DOCKER[:run].each_pair do |key, a|
395
- next unless a.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,42 +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.select { |val| basepath(val).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
640
- .merge(opts)
592
+ target.merge(files)
641
593
  else
642
- target.insert(index, *files)
594
+ target.concat(files)
643
595
  end
644
596
  end
645
597
 
@@ -651,18 +603,20 @@ module Squared
651
603
  end
652
604
 
653
605
  def append_tag(val, target: @session)
654
- ver = option('version', target: target, ignore: false)
655
- list = case val
656
- when String
657
- split_escape val
658
- when Array
659
- val
660
- else
661
- []
662
- end
663
- list.each do |s|
664
- s = "#{s.sub(/:latest$/, '')}:#{ver}" if ver && (!s.include?(':') || s.end_with?(':latest'))
665
- 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
666
620
  end
667
621
  end
668
622
 
@@ -672,7 +626,7 @@ module Squared
672
626
  index = 0
673
627
  all = option('all', prefix: 'docker')
674
628
  y = from == :'image:rm' && option('y', prefix: 'docker')
675
- pat = /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})\b/
629
+ pat = /^(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})(?:[_.,:-]|$)/
676
630
  IO.popen(session_done(cmd << '--format=json')).each do |line|
677
631
  data = JSON.parse(line)
678
632
  id = data['ID']
@@ -709,7 +663,7 @@ module Squared
709
663
  cols.each do |key|
710
664
  next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
711
665
 
712
- 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?
713
667
  end
714
668
  w = 9 + flag.to_s.size + 4 + ee.size
715
669
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
@@ -721,10 +675,14 @@ module Squared
721
675
  end
722
676
  yield id
723
677
  end
724
- 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
725
679
  end
726
680
  rescue StandardError => e
727
- 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?
728
686
  end
729
687
 
730
688
  def confirm_command(*args, title: nil, target: nil, as: nil)
@@ -781,7 +739,7 @@ module Squared
781
739
  cmd = docker_output ctx
782
740
  case flag
783
741
  when :tag
784
- args = tagjoin @registry, tag
742
+ args = tagjoin @registry, @tag
785
743
  when :save
786
744
  opts = "#{opts}.tar" unless opts.end_with?('.tar')
787
745
  cmd << quote_option('output', File.expand_path(opts))
@@ -792,9 +750,13 @@ module Squared
792
750
  else
793
751
  cmd << opts << '--'
794
752
  end
795
- 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)
796
758
  cmd << args
797
- 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)/)
798
760
  end
799
761
  end
800
762
 
@@ -804,7 +766,7 @@ module Squared
804
766
  bake?(val) ? 1 : 2
805
767
  when '.yml', '.yaml'
806
768
  if compose?(val)
807
- @only&.include?('compose') || path.children.none? { |file| bake?(file) } ? 3 : 1
769
+ path.children.any? { |file| bake?(file) } ? 1 : 3
808
770
  else
809
771
  4
810
772
  end
@@ -814,7 +776,7 @@ module Squared
814
776
  end
815
777
 
816
778
  def contextdir(val = nil)
817
- val && projectpath?(val) ? shell_quote(basepath(val)) : '.'
779
+ val && projectpath?(val) ? shell_quote(path + val) : '.'
818
780
  end
819
781
 
820
782
  def tagjoin(*args, char: '/')
@@ -824,9 +786,10 @@ module Squared
824
786
 
825
787
  def tagname(val)
826
788
  val = val.split(':').map! { |s| charname(s.sub(/^\W+/, '')) }
827
- ret = val.join(':')
828
- ret = val.first if val.size > 1 && ret.size > 128
829
- 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
830
793
  end
831
794
 
832
795
  def dnsname(val)
@@ -840,6 +803,14 @@ module Squared
840
803
  def tagmain
841
804
  tag.is_a?(Array) ? tag.first : tag
842
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
843
814
  end
844
815
 
845
816
  Application.implement Docker