squared 0.4.41 → 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,83 +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 network=b no-cache-filter=b o|output=q platform=q
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
24
- project-directory=p p|project-name=b].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=!? user=b 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=!? p|publish=q pull=b u|user=b v|volume=qq 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
22
+ common: %w[all-resources compatibility dry-run ansi|b env-file=p f|file=p parallel=b profile=b progress=b
23
+ project-directory=p p|project-name=e].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
40
- blkio-weight-device=i cap-add=b cap-drop=b cgroup-parent=b cgroupns=b cidfile=p device=q
41
- device-cgroup-rule=q 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 hostname=q io-maxbandwidth=b
45
- io-maxiops=b ip=b ip6=q ipc=b isolation=b kernel-memory=b l|label=q label-file=q link=b
46
- link-local-ip=q log-driver=b log-opt=q mac-address=q m|memory=b memory-reservation=b
47
- memory-swap=n memory-swappiness=n mount=qq name=b network=b network-alias=b oom-score-adj=b
48
- pid=b pids-limit=n platform=q p|publish=q pull=b restart=b runtime=b security-opt=q shm-size=b
49
- stop-signal=b stop-timeout=i storage-opt=q sysctl=q tmpfs=q ulimit=q u|user=b userns=b uts=b
50
- v|volume=qq volume-driver=b volumes-from=b w|workdir=q].freeze,
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
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,
51
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=b
56
- w|workdir=q].freeze,
57
- rm: %w[f|force l|link v|volumes].freeze,
58
54
  commit: %w[a|author=q c|change=q m|message=q pause=b?].freeze,
59
- inspect: %w[s|size f|format=q type=b].freeze,
55
+ inspect: %w[s|size f|format=q].freeze,
60
56
  start: %w[a|attach i|interactive detach-keys=q].freeze,
61
- stop: %w[s|signal=b t|time=i t|timeout=i].freeze,
62
- 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,
63
59
  kill: %w[s|signal=b].freeze,
64
- stats: %w[a|all no-stream no-trunc format|q].freeze
60
+ stats: %w[no-trunc format|q].freeze
65
61
  }.freeze,
66
62
  image: {
67
- list: %w[a|all q|quiet digests no-trunc tree f|filter=q format=q].freeze,
68
- push: %w[a|all-tags disable-content-trust=b? platform=q q|quiet].freeze,
69
- rm: %w[f|force no-prune platform=q].freeze,
70
- save: %w[o|output=p platform=q].freeze
63
+ list: %w[a|all digests no-trunc f|filter=q format=q].freeze,
64
+ push: %w[a|all-tags disable-content-trust=b? platform=b q|quiet].freeze,
65
+ rm: %w[f|force no-prune].freeze,
66
+ save: %w[o|output=p platform=b].freeze
71
67
  }.freeze,
72
68
  network: {
73
- 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,
74
70
  disconnect: %w[f|force].freeze
75
71
  }.freeze
76
72
  }.freeze
77
73
  VAL_DOCKER = {
78
74
  run: {
79
- common: %w[source src destination dst target readonly ro].freeze,
80
- bind: %w[bind-propagation].freeze,
81
- volume: %w[volume-subpath volume-nocopy volume-opt].freeze,
82
- tmpfs: %w[tmpfs-size tmpfs-mode].freeze,
83
- 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
84
77
  }.freeze
85
78
  }.freeze
86
79
  private_constant :COMPOSEFILE, :BAKEFILE, :OPT_DOCKER, :VAL_DOCKER
@@ -93,14 +86,13 @@ module Squared
93
86
  def config?(val)
94
87
  return false unless (val = as_path(val))
95
88
 
96
- DIR_DOCKER.any? { |file| val.join(file).exist? }
89
+ val.join('Dockerfile').exist? || DIR_DOCKER.any? { |file| val.join(file).exist? }
97
90
  end
98
91
  end
99
92
 
100
93
  subtasks({
101
- 'build' => %i[tag context].freeze,
102
- 'compose' => %i[build run exec up down].freeze,
103
- 'bake' => %i[build check].freeze,
94
+ 'build' => %i[tag context bake].freeze,
95
+ 'compose' => %i[build run exec up].freeze,
104
96
  'image' => %i[list rm push tag save].freeze,
105
97
  'container' => %i[run create exec update commit inspect diff start stop restart pause unpause top stats kill
106
98
  rm].freeze,
@@ -115,10 +107,11 @@ module Squared
115
107
  return unless dockerfile(file).exist?
116
108
 
117
109
  @context = context
118
- self.tag = tag || tagname("#{@project}:#{@version || 'latest'}")
110
+ @tag = tag || tagname("#{@project}:#{@version || 'latest'}")
119
111
  @mounts = mounts
120
112
  @secrets = secrets
121
113
  @registry = tagjoin registry, kwargs[:username]
114
+ @file = nil
122
115
  initialize_ref Docker.ref
123
116
  initialize_logger(**kwargs)
124
117
  initialize_env(**kwargs)
@@ -131,11 +124,11 @@ module Squared
131
124
 
132
125
  def populate(*, **)
133
126
  super
134
- return unless ref?(Docker.ref) || @only
127
+ return unless ref?(Docker.ref)
135
128
 
136
129
  namespace name do
137
130
  Docker.subtasks do |action, flags|
138
- next if task_pass?(action)
131
+ next if @pass.include?(action)
139
132
 
140
133
  namespace action do
141
134
  flags.each do |flag|
@@ -148,39 +141,26 @@ module Squared
148
141
  param = param_guard(action, flag, args: args, key: flag)
149
142
  buildx(:build, args.extras, "#{flag}": param)
150
143
  end
151
- end
152
- when 'bake'
153
- break unless bake?
154
-
155
- case flag
156
- when :build
157
- format_desc action, flag, 'opts*,target*,context?|:'
144
+ when :bake
145
+ format_desc action, flag, ':?,opts*,target*,context?'
158
146
  task flag do |_, args|
159
147
  args = args.to_a
160
148
  if args.first == ':'
161
149
  choice_command :bake
162
150
  else
163
- buildx :bake, args
151
+ buildx flag, args
164
152
  end
165
153
  end
166
- when :check
167
- format_desc action, flag, 'target'
168
- task flag, [:target] do |_, args|
169
- target = param_guard(action, flag, args: args, key: :target)
170
- buildx :bake, ['allow=fs.read=*', 'call=check', target]
171
- end
172
154
  end
173
155
  when 'compose'
174
- break unless compose?
175
-
176
156
  case flag
177
- when :build, :up, :down
157
+ when :build, :up
178
158
  format_desc action, flag, 'opts*,service*'
179
159
  task flag do |_, args|
180
160
  compose! flag, args.to_a
181
161
  end
182
162
  when :exec, :run
183
- format_desc action, flag, "service,command#{flag == :exec ? '' : '?'}|:,args*,opts*"
163
+ format_desc action, flag, "service,command#{flag == :exec ? '' : '?'},args*,opts*"
184
164
  task flag, [:service] do |_, args|
185
165
  service = param_guard(action, flag, args: args, key: :service)
186
166
  compose!(flag, args.extras, service: service)
@@ -208,7 +188,7 @@ module Squared
208
188
  end
209
189
  end
210
190
  else
211
- format_desc action, flag, "opts*,id/name#{flag == :update || flag == :inspect ? '+' : '*'}"
191
+ format_desc(action, flag, 'opts*,id/name', after: flag == :update ? '+' : '*')
212
192
  task flag do |_, args|
213
193
  container flag, args.to_a
214
194
  end
@@ -218,15 +198,14 @@ module Squared
218
198
  when :push
219
199
  format_desc action, flag, 'tag,registry/username?,opts*'
220
200
  task flag, [:tag] do |_, args|
221
- tag = param_guard(action, flag, args: args, key: :tag)
222
- image(flag, args.extras, id: tag)
201
+ id = param_guard(action, flag, args: args, key: :tag)
202
+ image(flag, args.extras, id: id)
223
203
  end
224
204
  else
225
205
  format_desc(action, flag, case flag
226
206
  when :rm, :save then 'id*,opts*'
227
207
  when :tag then 'version?'
228
- else 'opts*,args*'
229
- end)
208
+ else 'opts*,args*' end)
230
209
  task flag do |_, args|
231
210
  args = args.to_a
232
211
  if args.empty? && flag != :list
@@ -253,44 +232,43 @@ module Squared
253
232
  end
254
233
 
255
234
  def clean(*, sync: invoked_sync?('clean'), **)
256
- if runnable?(@clean)
257
- super
258
- elsif sync || option('y', prefix: 'docker')
259
- image :rm
260
- end
235
+ return super unless @clean.nil?
236
+
237
+ image(:rm, sync: sync)
261
238
  end
262
239
 
263
240
  def compose(opts, flags = nil, script: false, args: nil, from: :run, **)
264
241
  return opts if script == false
265
242
 
243
+ ret = docker_session
266
244
  if from == :run
267
- if bake?(n = filetype)
268
- ret = docker_session '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 = docker_session 'compose build'
250
+ when 3, 4
251
+ ret << 'compose' << 'build'
273
252
  append_file n
274
253
  from = :compose
275
254
  else
276
- ret = docker_session 'build'
255
+ ret << 'build'
277
256
  end
278
257
  else
279
- ret = docker_session from
258
+ ret << from
280
259
  end
281
260
  case opts
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,11 +306,22 @@ 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
- op.append(escape: true, strip: /^:/, clear: true)
335
- op.push(context) if context
312
+ unless op.empty?
313
+ args = op.dup
314
+ op.extras.clear
315
+ if Dir.exist?(args.last)
316
+ if projectpath?(val = args.pop)
317
+ context = val
318
+ else
319
+ op.extras << val
320
+ end
321
+ end
322
+ op.append(args, escape: true)
323
+ contextdir context if context
324
+ end
336
325
  end
337
326
  op.clear(pass: false)
338
327
  run(from: :"buildx:#{flag}")
@@ -343,16 +332,16 @@ module Squared
343
332
  op = OptionPartition.new(opts, OPT_DOCKER[:compose][:common], cmd, project: self)
344
333
  append_file filetype unless op.arg?('f', 'file')
345
334
  op << flag
346
- op.parse(OPT_DOCKER[:compose].fetch(flag, []))
335
+ op.parse(OPT_DOCKER[:compose][flag])
336
+ from = :"compose:#{flag}"
347
337
  case flag
348
- when :build, :up, :down
349
- op.append(escape: true, strip: /^:/)
338
+ when :build, :up
339
+ op.append(escape: true)
350
340
  when :exec, :run
351
- append_command flag, service, op.extras
341
+ append_command(flag, service, op.extras, from: from)
352
342
  end
353
- run(from: :"compose:#{flag}")
343
+ run(from: from)
354
344
  end
355
- alias compose_ compose!
356
345
 
357
346
  def container(flag, opts = [], id: nil)
358
347
  cmd, opts = docker_session('container', flag, opts: opts)
@@ -365,54 +354,45 @@ module Squared
365
354
  case flag
366
355
  when :run, :create, :exec
367
356
  if rc && !op.arg?('mount')
368
- all = collect_hash VAL_DOCKER[:run]
369
- delim = Regexp.new(",\\s*(?=#{all.join('|')})")
370
- 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|
371
362
  args = []
372
- type = nil
363
+ tmpfs = true
373
364
  val.split(delim).each do |opt|
374
365
  k, v, q = split_option opt
375
- if k == 'type'
376
- case v
377
- when 'bind', 'volume', 'image', 'tmpfs'
378
- type = v
379
- else
380
- raise_error("unknown type: #{v}", hint: flag)
381
- end
382
- elsif all.include?(k)
383
- unless type
384
- VAL_DOCKER[:run].each_pair do |key, a|
385
- next unless a.include?(k)
366
+ next unless both.include?(k)
386
367
 
387
- type = key.to_s unless key == :common
388
- break
389
- end
390
- end
391
- case k
392
- when 'readonly', 'ro'
393
- args << k
394
- next
395
- when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-path'
396
- v = basepath v
397
- v = shell_quote(v, option: false, force: false) if q == ''
398
- end
399
- args << "#{k}=#{q + v + q}"
400
- elsif verbose
401
- 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
402
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'
382
+ end
383
+ args << "#{k}=#{q + v + q}"
403
384
  end
404
- raise_error('missing type', hint: flag) unless type
405
- cmd << "--mount type=#{type},#{args.join(',')}"
385
+ cmd << "--mount type=#{tmpfs ? 'tmpfs' : 'bind'},#{args.join(',')}"
406
386
  end
407
387
  end
408
- append_command(flag, id || tagmain, op.extras)
409
- when :update, :inspect
410
- raise_error('missing container', hint: flag) if op.empty?
411
- op.append(escape: true, strip: /^:/)
388
+ append_command(flag, id || tagmain, op.extras, from: from)
389
+ when :update
390
+ raise_error('missing container', hint: from) if op.empty?
391
+ op.append(escape: true)
412
392
  when :commit
413
393
  latest = op.shift || tagmain
414
394
  cmd << id << latest
415
- raise_error("unknown args: #{op.join(', ')}", hint: flag) unless op.empty?
395
+ raise_error("unknown args: #{op.join(', ')}", hint: from) unless op.empty?
416
396
  return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
417
397
 
418
398
  registry = option('registry') || @registry
@@ -455,13 +435,13 @@ module Squared
455
435
  when :rm
456
436
  status = %w[created exited dead]
457
437
  end
458
- 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}\"" })
459
439
  list_image(flag, ps, no: no, hint: "status: #{status.join(', ')}", from: from) do |img|
460
440
  run(cmd.temp(img), from: from)
461
441
  end
462
442
  return
463
443
  else
464
- op.append(escape: true, strip: /^:/)
444
+ op.append(escape: true)
465
445
  end
466
446
  end
467
447
  run(from: from)
@@ -512,17 +492,17 @@ module Squared
512
492
  list_image(flag, docker_output('image ls -a'), from: from) do |val|
513
493
  op << val
514
494
  if flag == :tag
515
- op << tagname("#{project}:#{op.first}")
495
+ op << tagname("#{@project}:#{op.extras.first}")
516
496
  break
517
497
  end
518
498
  end
519
499
  when :push
520
500
  id ||= option('tag', ignore: false) || tagmain
521
501
  registry ||= op.shift || option('registry') || @registry
522
- raise_error(id ? "unknown args: #{op.join(', ')}" : 'no id/tag given', hint: flag) unless id && op.empty?
523
- 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
524
504
  registry.chomp!('/')
525
- uri = shell_quote("#{registry}/#{id}")
505
+ uri = shell_quote "#{registry}/#{id}"
526
506
  op << uri
527
507
  img = docker_output 'image', 'tag', id, uri
528
508
  return unless confirm_command(img.to_s, cmd.to_s, target: id, as: registry, title: from)
@@ -538,7 +518,7 @@ module Squared
538
518
 
539
519
  def network(flag, opts = [], target: nil)
540
520
  cmd, opts = docker_session('network', flag, opts: opts)
541
- op = OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self)
521
+ op = OptionPartition.new(opts, OPT_DOCKER[:network][flag], cmd, project: self)
542
522
  op.clear
543
523
  from = :"network:#{flag}"
544
524
  list_image(flag, docker_output('ps -a'), from: from) do |img|
@@ -554,28 +534,17 @@ module Squared
554
534
  super || dockerfile.exist?
555
535
  end
556
536
 
557
- def compose?(file = dockerfile)
558
- return file == 3 || file == 4 if file.is_a?(Numeric)
559
-
560
- COMPOSEFILE.include?(File.basename(file))
561
- end
562
-
563
- def bake?(file = dockerfile)
564
- return file == 1 || file == 2 if file.is_a?(Numeric)
565
-
566
- BAKEFILE.include?(File.basename(file))
567
- end
568
-
569
537
  def dockerfile(val = nil)
570
- if val
538
+ if val == 'Dockerfile'
539
+ @file = false
540
+ elsif val
571
541
  @file = if val.is_a?(Array)
572
542
  val = val.select { |file| basepath(file).exist? }
573
543
  val.size > 1 ? val : val.first
574
- elsif val == true
575
- DIR_DOCKER.find { |file| basepath(file).exist? }
576
- elsif val != 'Dockerfile'
577
- val
544
+ else
545
+ val || DIR_DOCKER.find { |file| basepath(file).exist? }
578
546
  end
547
+ @file ||= false
579
548
  end
580
549
  basepath((@file.is_a?(Array) ? @file.first : @file) || 'Dockerfile')
581
550
  end
@@ -594,56 +563,35 @@ module Squared
594
563
  session('docker', *cmd, main: false, options: false, **kwargs)
595
564
  end
596
565
 
597
- def append_command(flag, val, list, target: @session)
598
- if list.delete(':')
599
- list << readline('Enter command [args]', force: true)
600
- elsif (args = env('DOCKER_ARGS'))
566
+ def append_command(flag, val, list, target: @session, from: nil)
567
+ if (args = env('DOCKER_ARGS'))
601
568
  list << args
602
569
  end
603
570
  case flag
604
571
  when :run
605
572
  unless session_arg?('name', target: target)
606
- 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))
607
579
  end
608
580
  when :exec
609
- raise_error('no command args', hint: flag) if list.empty?
610
- end
611
- target << val
612
- case list.first
613
- when '--'
614
- target.merge(list.drop(1))
615
- when /\A(--|&&)(\s|\z)/
616
- target.merge(list)
617
- else
618
- target << list.shift
619
- unless list.empty?
620
- if list.first.start_with?('-')
621
- target.merge(list)
622
- else
623
- target << list.join(' && ')
624
- end
625
- end
581
+ raise_error('no command args', hint: from) if list.empty?
626
582
  end
583
+ target << val << list.shift
584
+ target << list.join(' && ') unless list.empty?
627
585
  end
628
586
 
629
- def append_file(type, target: @session, index: 2)
630
- 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)
631
589
 
632
- unless @file.is_a?(Array)
633
- case type
634
- when 2, 4
635
- return
636
- when 3
637
- return unless COMPOSEFILE.select { |val| basepath(val).exist? }.size > 1
638
- end
639
- end
640
- files = Array(@file).map { |val| quote_option('file', basepath(val)) }
590
+ files = as_a(@file).map { |val| quote_option('file', path + val) }
641
591
  if target.is_a?(Set)
642
- opts = target.to_a.insert(index, *files)
643
- target.clear
644
- .merge(opts)
592
+ target.merge(files)
645
593
  else
646
- target.insert(index, *files)
594
+ target.concat(files)
647
595
  end
648
596
  end
649
597
 
@@ -655,28 +603,30 @@ module Squared
655
603
  end
656
604
 
657
605
  def append_tag(val, target: @session)
658
- ver = option('version', target: target, ignore: false)
659
- list = case val
660
- when String
661
- split_escape val
662
- when Array
663
- val
664
- else
665
- []
666
- end
667
- list.each do |s|
668
- s = "#{s.sub(/:latest$/, '')}:#{ver}" if ver && (!s.include?(':') || s.end_with?(':latest'))
669
- 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
670
620
  end
671
621
  end
672
622
 
673
- def list_image(flag, cmd, hint: nil, no: true, from: nil)
674
- pwd_set(from: from) do
623
+ def list_image(flag, cmd, hint: nil, from: nil, no: true)
624
+ pwd_set do
675
625
  found = false
676
626
  index = 0
677
627
  all = option('all', prefix: 'docker')
678
628
  y = from == :'image:rm' && option('y', prefix: 'docker')
679
- pat = /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})\b/
629
+ pat = /^(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})(?:[_.,:-]|$)/
680
630
  IO.popen(session_done(cmd << '--format=json')).each do |line|
681
631
  data = JSON.parse(line)
682
632
  id = data['ID']
@@ -713,7 +663,7 @@ module Squared
713
663
  cols.each do |key|
714
664
  next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
715
665
 
716
- 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?
717
667
  end
718
668
  w = 9 + flag.to_s.size + 4 + ee.size
719
669
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
@@ -725,10 +675,14 @@ module Squared
725
675
  end
726
676
  yield id
727
677
  end
728
- 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
729
679
  end
730
680
  rescue StandardError => e
731
- 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?
732
686
  end
733
687
 
734
688
  def confirm_command(*args, title: nil, target: nil, as: nil)
@@ -785,7 +739,7 @@ module Squared
785
739
  cmd = docker_output ctx
786
740
  case flag
787
741
  when :tag
788
- args = tagjoin @registry, tag
742
+ args = tagjoin @registry, @tag
789
743
  when :save
790
744
  opts = "#{opts}.tar" unless opts.end_with?('.tar')
791
745
  cmd << quote_option('output', File.expand_path(opts))
@@ -796,9 +750,13 @@ module Squared
796
750
  else
797
751
  cmd << opts << '--'
798
752
  end
799
- 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)
800
758
  cmd << args
801
- print_success if success?(run(cmd, { 'NO_COLOR' => 'true' })) && ctx.match?(/\A(?:network|tag|save)/)
759
+ print_success if success?(run(cmd)) && ctx.start_with?(/(?:network|tag|save)/)
802
760
  end
803
761
  end
804
762
 
@@ -808,7 +766,7 @@ module Squared
808
766
  bake?(val) ? 1 : 2
809
767
  when '.yml', '.yaml'
810
768
  if compose?(val)
811
- @only&.include?('compose') || path.children.none? { |file| bake?(file) } ? 3 : 1
769
+ path.children.any? { |file| bake?(file) } ? 1 : 3
812
770
  else
813
771
  4
814
772
  end
@@ -818,7 +776,7 @@ module Squared
818
776
  end
819
777
 
820
778
  def contextdir(val = nil)
821
- val && projectpath?(val) ? shell_quote(basepath(val)) : '.'
779
+ val && projectpath?(val) ? shell_quote(path + val) : '.'
822
780
  end
823
781
 
824
782
  def tagjoin(*args, char: '/')
@@ -828,9 +786,10 @@ module Squared
828
786
 
829
787
  def tagname(val)
830
788
  val = val.split(':').map! { |s| charname(s.sub(/^\W+/, '')) }
831
- ret = val.join(':')
832
- ret = val.first if val.size > 1 && ret.size > 128
833
- 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
834
793
  end
835
794
 
836
795
  def dnsname(val)
@@ -844,6 +803,14 @@ module Squared
844
803
  def tagmain
845
804
  tag.is_a?(Array) ? tag.first : tag
846
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
847
814
  end
848
815
 
849
816
  Application.implement Docker