squared 0.4.42 → 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=qq call=b? f|file=p metadata-file=p progress=b provenance=b?
20
- sbom=b?].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[1 all-resources compatibility dry-run ansi=b env-file=p f|file=p parallel=n profile=b progress=b
24
- project-directory=p p|project-name=b].freeze,
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-create-src 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-subpath].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,64 +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', 'volume-nocopy', 'bind-create-src'
393
- args << k if %w[bind volume].include?(type)
394
- next
395
- when 'source', 'src', 'destination', 'dst', 'target', 'volume-subpath', 'image-subpath'
396
- raise_error ArgumentError, "no path value: #{k}", hint: flag unless v
397
- case k
398
- when 'source', 'src'
399
- if type == 'bind'
400
- v = basepath v
401
- elsif type == 'tmpfs'
402
- next
403
- end
404
- when 'destination', 'dst', 'target'
405
- puts log_message("path is not absolute: #{v}", subject: from, hint: k) unless v.start_with?('/')
406
- end
407
- v = shell_quote(v, option: false, force: false) if q == ''
408
- end
409
- args << "#{k}=#{q + v + q}"
410
- elsif verbose
411
- puts 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
- op << "--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)
419
- when :update, :inspect
420
- raise_error('missing container', hint: flag) if op.empty?
421
- 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)
422
392
  when :commit
423
393
  latest = op.shift || tagmain
424
- op << id << latest
425
- raise_error("unknown args: #{op.join(', ')}", hint: flag) unless op.empty?
394
+ cmd << id << latest
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,56 +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?
620
- end
621
- target << val
622
- case list.first
623
- when '--'
624
- target.merge(list.drop(1))
625
- when /\A(--|&&)(\s|\z)/
626
- target.merge(list)
627
- else
628
- target << list.shift
629
- unless list.empty?
630
- if list.first.start_with?('-')
631
- target.merge(list)
632
- else
633
- target << list.join(' && ')
634
- end
635
- end
581
+ raise_error('no command args', hint: from) if list.empty?
636
582
  end
583
+ target << val << list.shift
584
+ target << list.join(' && ') unless list.empty?
637
585
  end
638
586
 
639
- def append_file(type, target: @session, index: 2)
640
- 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)
641
589
 
642
- unless @file.is_a?(Array)
643
- case type
644
- when 2, 4
645
- return
646
- when 3
647
- return unless COMPOSEFILE.select { |val| basepath(val).exist? }.size > 1
648
- end
649
- end
650
- files = Array(@file).map { |val| quote_option('file', basepath(val)) }
590
+ files = as_a(@file).map { |val| quote_option('file', path + val) }
651
591
  if target.is_a?(Set)
652
- opts = target.to_a.insert(index, *files)
653
- target.clear
654
- .merge(opts)
592
+ target.merge(files)
655
593
  else
656
- target.insert(index, *files)
594
+ target.concat(files)
657
595
  end
658
596
  end
659
597
 
@@ -665,28 +603,30 @@ module Squared
665
603
  end
666
604
 
667
605
  def append_tag(val, target: @session)
668
- ver = option('version', target: target, ignore: false)
669
- list = case val
670
- when String
671
- split_escape val
672
- when Array
673
- val
674
- else
675
- []
676
- end
677
- list.each do |s|
678
- s = "#{s.sub(/:latest$/, '')}:#{ver}" if ver && (!s.include?(':') || s.end_with?(':latest'))
679
- 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
680
620
  end
681
621
  end
682
622
 
683
- def list_image(flag, cmd, hint: nil, no: true, from: nil)
684
- pwd_set(from: from) do
623
+ def list_image(flag, cmd, hint: nil, from: nil, no: true)
624
+ pwd_set do
685
625
  found = false
686
626
  index = 0
687
627
  all = option('all', prefix: 'docker')
688
628
  y = from == :'image:rm' && option('y', prefix: 'docker')
689
- pat = /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})\b/
629
+ pat = /^(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})(?:[_.,:-]|$)/
690
630
  IO.popen(session_done(cmd << '--format=json')).each do |line|
691
631
  data = JSON.parse(line)
692
632
  id = data['ID']
@@ -723,7 +663,7 @@ module Squared
723
663
  cols.each do |key|
724
664
  next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
725
665
 
726
- 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?
727
667
  end
728
668
  w = 9 + flag.to_s.size + 4 + ee.size
729
669
  puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
@@ -735,10 +675,14 @@ module Squared
735
675
  end
736
676
  yield id
737
677
  end
738
- 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
739
679
  end
740
680
  rescue StandardError => e
741
- 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?
742
686
  end
743
687
 
744
688
  def confirm_command(*args, title: nil, target: nil, as: nil)
@@ -795,7 +739,7 @@ module Squared
795
739
  cmd = docker_output ctx
796
740
  case flag
797
741
  when :tag
798
- args = tagjoin @registry, tag
742
+ args = tagjoin @registry, @tag
799
743
  when :save
800
744
  opts = "#{opts}.tar" unless opts.end_with?('.tar')
801
745
  cmd << quote_option('output', File.expand_path(opts))
@@ -806,9 +750,13 @@ module Squared
806
750
  else
807
751
  cmd << opts << '--'
808
752
  end
809
- 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)
810
758
  cmd << args
811
- 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)/)
812
760
  end
813
761
  end
814
762
 
@@ -818,7 +766,7 @@ module Squared
818
766
  bake?(val) ? 1 : 2
819
767
  when '.yml', '.yaml'
820
768
  if compose?(val)
821
- @only&.include?('compose') || path.children.none? { |file| bake?(file) } ? 3 : 1
769
+ path.children.any? { |file| bake?(file) } ? 1 : 3
822
770
  else
823
771
  4
824
772
  end
@@ -828,7 +776,7 @@ module Squared
828
776
  end
829
777
 
830
778
  def contextdir(val = nil)
831
- val && projectpath?(val) ? shell_quote(basepath(val)) : '.'
779
+ val && projectpath?(val) ? shell_quote(path + val) : '.'
832
780
  end
833
781
 
834
782
  def tagjoin(*args, char: '/')
@@ -838,9 +786,10 @@ module Squared
838
786
 
839
787
  def tagname(val)
840
788
  val = val.split(':').map! { |s| charname(s.sub(/^\W+/, '')) }
841
- ret = val.join(':')
842
- ret = val.first if val.size > 1 && ret.size > 128
843
- 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
844
793
  end
845
794
 
846
795
  def dnsname(val)
@@ -854,6 +803,14 @@ module Squared
854
803
  def tagmain
855
804
  tag.is_a?(Array) ? tag.first : tag
856
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
857
814
  end
858
815
 
859
816
  Application.implement Docker