squared 0.3.5 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -2
- data/README.md +13 -2
- data/README.ruby.md +168 -88
- data/lib/squared/app.rb +1 -0
- data/lib/squared/common/base.rb +6 -1
- data/lib/squared/common/class.rb +1 -1
- data/lib/squared/common/format.rb +24 -12
- data/lib/squared/common/prompt.rb +2 -2
- data/lib/squared/common/shell.rb +52 -39
- data/lib/squared/common/utils.rb +54 -1
- data/lib/squared/config.rb +1 -1
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +16 -13
- data/lib/squared/workspace/project/base.rb +429 -123
- data/lib/squared/workspace/project/docker.rb +572 -0
- data/lib/squared/workspace/project/git.rb +405 -157
- data/lib/squared/workspace/project/node.rb +51 -51
- data/lib/squared/workspace/project/python.rb +115 -24
- data/lib/squared/workspace/project/ruby.rb +33 -34
- data/lib/squared/workspace/project.rb +7 -1
- data/lib/squared/workspace/repo.rb +9 -4
- data/lib/squared/workspace/series.rb +1 -1
- metadata +2 -1
@@ -0,0 +1,572 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Squared
|
4
|
+
module Workspace
|
5
|
+
module Project
|
6
|
+
class Docker < Base
|
7
|
+
include Prompt
|
8
|
+
|
9
|
+
COMPOSEFILE = %w[compose.yaml compose.yml docker-compose.yaml compose.yml docker-compose.yml].freeze
|
10
|
+
BAKEFILE = %w[docker-bake.json docker-bake.hcl docker-bake.override.json docker-bake.override.hcl].freeze
|
11
|
+
DIR_DOCKER = (COMPOSEFILE + BAKEFILE).freeze
|
12
|
+
OPT_DOCKER = {
|
13
|
+
common: %w[tls tlsverify config=p c|context=b D|debug H|host=q l|log-level=b tlscacert=p tlscert=p
|
14
|
+
tlskey=p].freeze,
|
15
|
+
buildx: {
|
16
|
+
common: %w[builder=b D|debug],
|
17
|
+
build: %w[load pull push add-host=q annotation=q attest=q build-arg=qq ent=q iidfile=p label=q a-file=p
|
18
|
+
network=b no-cache-filter=b o|output=q platform=b q|quiet secret=qq shm-size=b ssh=qq t|tag=b
|
19
|
+
target=b ulimit=q].freeze,
|
20
|
+
bake: %w[load print pull push list=q metadata-file=p set=q].freeze,
|
21
|
+
shared: %w[check no-cache allow=q call=b? f|file=p progress=b provenance=q sbom=q].freeze
|
22
|
+
}.freeze,
|
23
|
+
compose: {
|
24
|
+
common: %w[all-resources compatibility dry-run ansi|b env-file=p f|file=p parallel=b profile=b progress=b
|
25
|
+
project-directory=p p|project-name=e].freeze,
|
26
|
+
build: %w[no-cache pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b ssh=qq].freeze,
|
27
|
+
exec: %w[dry-run privileged d|detach e|env=qq index=i T|no-TTY=b? user=e w|workdir=q].freeze,
|
28
|
+
run: %w[build dry-run no-deps quiet-pull remove-orphans rm P|service-ports use-aliases cap-add=b cap-drop=b
|
29
|
+
d|detach entrypoint=q e|env=qq i|interactive=b? l|label=q name=b T|no-TTY=b? p|publish=e pull=b
|
30
|
+
u|user=e v|volume=q w|workdir=q].freeze,
|
31
|
+
up: %w[y abort-on-container-exit abort-on-container-failure always-recreate-deps attach-dependencies build
|
32
|
+
d|detach dry-run force-recreate menu no-build no-color no-deps no-log-prefix no-recreate no-start
|
33
|
+
quiet-pull remove-orphans V|renew-anon-volumes timestamps wait w|watch attach=b exit-code-from=b
|
34
|
+
no-attach=b pull=b scale=i t|timeout=i wait-timeout=i].freeze
|
35
|
+
}.freeze,
|
36
|
+
container: {
|
37
|
+
run: %w[d|detach init i|interactive no-healthcheck oom-kill-disable privileged P|publish-all q|quiet
|
38
|
+
read-only rm runtime t|tty add-host=q annotation=q a|attach=b blkio-weight-device=i cap-add=b
|
39
|
+
cap-drop=b cgroup-parent=b cgroupns=b cidfile=p detach-keys=q device=q device-cgroup-rule=q
|
40
|
+
device-read-bps=q device-read-iops=q device-write-bps=q device-write-iops=q
|
41
|
+
disable-content-trust=b? dns=e dns-option=e dns-search=e domainname=b entrypoint=q e|env=qq
|
42
|
+
env-file=p expose=e gpus=q group-add=b health-cmd=q health-interval=b health-retries=i
|
43
|
+
health-start-interval=b health-start-period=b health-timeout=b h|hostname=e io-maxbandwidth=b
|
44
|
+
io-maxiops=b ip=b ip6=e ipc=b isolation=b kernel-memory=b l|label=q label-file=p link=b
|
45
|
+
link-local-ip=b log-driver=b log-opt=q mac-address=e memory-swappiness=b mount=q name=b network=b
|
46
|
+
network-alias=b oom-score-adj=b pid=b platform=b p|publish=e pull=b restart=b runtime=b
|
47
|
+
security-opt=q shm-size=b sig-proxy=b? stop-signal=b stop-timeout=i storage-opt=q sysctl=q tmpfs=q
|
48
|
+
ulimit=q user=e userns=b uts=b v|volume=q volume-driver=b volumes-from=b w|workdir=q].freeze,
|
49
|
+
exec: %w[d|detach i|interactive privileged t|tty detach-keys=q e|env=qq env-file=p user=e
|
50
|
+
w|workdir=q].freeze,
|
51
|
+
update: %w[blkio-weight=i cpu-period=i cpu-quota=i cpu-rt-period=i cpu-rt-runtime=i c|cpu-shares=i cpus=f
|
52
|
+
cpuset-cpus=b cpuset-mems=b m|memory=b memory-reservation=b memory-swap=b pids-limit=b
|
53
|
+
restart=q].freeze,
|
54
|
+
commit: %w[a|author=q c|change=q m|message=q pause=b?].freeze,
|
55
|
+
inspect: %w[s|size f|format=q].freeze,
|
56
|
+
start: %w[a|attach i|interactive detach-keys=q].freeze,
|
57
|
+
stop: %w[s|signal=b t|time=i].freeze,
|
58
|
+
restart: %w[s|signal=b t|time=i].freeze,
|
59
|
+
kill: %w[s|signal=b].freeze,
|
60
|
+
stats: %w[no-trunc format|q].freeze
|
61
|
+
}.freeze,
|
62
|
+
image: {
|
63
|
+
list: %w[a|all digests no-trunc f|filter=q format=q].freeze,
|
64
|
+
push: %w[disable-content-trust=b? platform=b q|quiet].freeze,
|
65
|
+
rm: %w[f|force no-prune].freeze
|
66
|
+
}.freeze
|
67
|
+
}.freeze
|
68
|
+
private_constant :COMPOSEFILE, :BAKEFILE, :OPT_DOCKER
|
69
|
+
|
70
|
+
class << self
|
71
|
+
def tasks
|
72
|
+
[].freeze
|
73
|
+
end
|
74
|
+
|
75
|
+
def config?(val)
|
76
|
+
return false unless (val = as_path(val))
|
77
|
+
|
78
|
+
val.join('Dockerfile').exist? || DIR_DOCKER.any? { |file| val.join(file).exist? }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
@@tasks[ref] = {
|
83
|
+
'build' => %i[tag context bake].freeze,
|
84
|
+
'compose' => %i[build run exec up].freeze,
|
85
|
+
'image' => %i[list rm].freeze,
|
86
|
+
'container' => %i[run exec update commit inspect diff start stop restart pause unpause top stats kill
|
87
|
+
rm].freeze
|
88
|
+
}.freeze
|
89
|
+
|
90
|
+
attr_reader :context
|
91
|
+
attr_accessor :tag
|
92
|
+
|
93
|
+
def initialize(*, file: nil, context: nil, tag: nil, secrets: nil, registry: nil, **kwargs)
|
94
|
+
super
|
95
|
+
return unless dockerfile(file).exist?
|
96
|
+
|
97
|
+
@context = context
|
98
|
+
@tag = tag || "#{@project}:latest"
|
99
|
+
@secrets = secrets
|
100
|
+
@registry = registry
|
101
|
+
initialize_ref Docker.ref
|
102
|
+
initialize_logger(**kwargs)
|
103
|
+
initialize_env(**kwargs)
|
104
|
+
@output[4] = merge_opts(kwargs[:args], @output[4]) if kwargs[:args]
|
105
|
+
end
|
106
|
+
|
107
|
+
def ref
|
108
|
+
Docker.ref
|
109
|
+
end
|
110
|
+
|
111
|
+
def populate(*, **)
|
112
|
+
super
|
113
|
+
return unless ref?(Docker.ref)
|
114
|
+
|
115
|
+
namespace name do
|
116
|
+
@@tasks[Docker.ref].each do |action, flags|
|
117
|
+
next if @pass.include?(action)
|
118
|
+
|
119
|
+
namespace action do
|
120
|
+
flags.each do |flag|
|
121
|
+
case action
|
122
|
+
when 'build'
|
123
|
+
case flag
|
124
|
+
when :tag, :context
|
125
|
+
format_desc(action, flag, 'opts*', before: flag == :tag ? 'name' : 'dir')
|
126
|
+
task flag, [flag] do |_, args|
|
127
|
+
param = param_guard(action, flag, args: args, key: flag)
|
128
|
+
buildx(:build, args.to_a.drop(1), "#{flag}": param)
|
129
|
+
end
|
130
|
+
when :bake
|
131
|
+
format_desc action, flag, 'opts*,target*,context?'
|
132
|
+
task flag do |_, args|
|
133
|
+
args = param_guard(action, flag, args: args.to_a)
|
134
|
+
buildx flag, args
|
135
|
+
end
|
136
|
+
end
|
137
|
+
when 'compose'
|
138
|
+
case flag
|
139
|
+
when :build, :up
|
140
|
+
format_desc action, flag, 'opts*,service*'
|
141
|
+
task flag do |_, args|
|
142
|
+
composex flag, args.to_a
|
143
|
+
end
|
144
|
+
when :exec, :run
|
145
|
+
format_desc action, flag, "service,command#{flag == :exec ? '' : '?'},args*,opts*"
|
146
|
+
task flag, [:service] do |_, args|
|
147
|
+
service = param_guard(action, flag, args: args, key: :service)
|
148
|
+
composex(flag, args.to_a.drop(1), service: service)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
when 'container'
|
152
|
+
case flag
|
153
|
+
when :exec, :commit
|
154
|
+
format_desc(action, flag, 'id/name,opts*', after: flag == :exec ? 'args+' : 'tag?')
|
155
|
+
task flag, [:id] do |_, args|
|
156
|
+
id = param_guard(action, flag, args: args, key: :id)
|
157
|
+
container(flag, args.to_a.drop(1), id: id)
|
158
|
+
end
|
159
|
+
when :run
|
160
|
+
format_desc action, flag, 'image,opts*,args*'
|
161
|
+
task flag, [:image] do |_, args|
|
162
|
+
image = param_guard(action, flag, args: args, key: :image)
|
163
|
+
container(flag, args.to_a.drop(1), id: image)
|
164
|
+
end
|
165
|
+
else
|
166
|
+
format_desc(action, flag, 'opts*', after: "id/name#{flag == :update ? '+' : '*'}")
|
167
|
+
task flag do |_, args|
|
168
|
+
container flag, args.to_a
|
169
|
+
end
|
170
|
+
end
|
171
|
+
when 'image'
|
172
|
+
format_desc(action, flag, flag == :rm ? 'id*,opts*' : 'opts*,args*')
|
173
|
+
task flag do |_, args|
|
174
|
+
image flag, args.to_a
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def clean(*, sync: invoked_sync?('clean'), **)
|
184
|
+
return super unless @clean.nil?
|
185
|
+
|
186
|
+
image(:rm, sync: sync)
|
187
|
+
end
|
188
|
+
|
189
|
+
def compose(opts, flags = nil, script: false, args: nil, from: :build, **)
|
190
|
+
return opts if script == false
|
191
|
+
|
192
|
+
ret = docker_session
|
193
|
+
if from == :build
|
194
|
+
case (n = filetype)
|
195
|
+
when 1, 2
|
196
|
+
ret << 'buildx' << 'bake'
|
197
|
+
append_file n
|
198
|
+
from = :bake
|
199
|
+
when 3, 4
|
200
|
+
ret << 'compose' << 'build'
|
201
|
+
append_file n
|
202
|
+
from = :compose
|
203
|
+
else
|
204
|
+
ret << 'build'
|
205
|
+
end
|
206
|
+
else
|
207
|
+
ret << from
|
208
|
+
end
|
209
|
+
case opts
|
210
|
+
when String
|
211
|
+
ret << opts
|
212
|
+
when Hash
|
213
|
+
ret.merge(append_hash(opts, build: true))
|
214
|
+
when Enumerable
|
215
|
+
ret.merge(opts.to_a)
|
216
|
+
end
|
217
|
+
[args, flags].each_with_index do |target, index|
|
218
|
+
next unless target
|
219
|
+
|
220
|
+
target = append_any(target, target: []) unless target.is_a?(Array)
|
221
|
+
ret.merge(target.map { |arg| index == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
|
222
|
+
end
|
223
|
+
case from
|
224
|
+
when :build
|
225
|
+
case @secrets
|
226
|
+
when String
|
227
|
+
quote_option('secret', @secrets, double: true)
|
228
|
+
when Hash
|
229
|
+
append = lambda do |type|
|
230
|
+
as_a(@secrets[type]).each { |arg| ret << quote_option('secret', "type=#{type},#{arg}", double: true) }
|
231
|
+
end
|
232
|
+
append.(:file)
|
233
|
+
append.(:env)
|
234
|
+
else
|
235
|
+
as_a(@secrets).each { |arg| ret << quote_option('secret', arg) }
|
236
|
+
end
|
237
|
+
if (val = option('tag', ignore: false))
|
238
|
+
ret << quote_option('tag', val)
|
239
|
+
elsif !session_arg?('t', 'tag')
|
240
|
+
ret << quote_option('tag', tag)
|
241
|
+
end
|
242
|
+
append_context
|
243
|
+
when :bake, :compose
|
244
|
+
if (val = option(from == :bake ? 'target' : 'service', ignore: false))
|
245
|
+
ret.merge(split_escape(val).map { |s| shell_escape(s) })
|
246
|
+
end
|
247
|
+
end
|
248
|
+
ret
|
249
|
+
end
|
250
|
+
|
251
|
+
def buildx(flag, opts = [], tag: nil, context: nil)
|
252
|
+
cmd, opts = docker_session('buildx', opts: opts)
|
253
|
+
opts = option_sanitize(opts, OPT_DOCKER[:buildx][:common]).first
|
254
|
+
cmd << flag
|
255
|
+
out = option_sanitize(opts, OPT_DOCKER[:buildx][flag] + OPT_DOCKER[:buildx][:shared]).first
|
256
|
+
case flag
|
257
|
+
when :build
|
258
|
+
cmd.merge(as_a(tag).map { |val| quote_option('tag', val) })
|
259
|
+
append_context context
|
260
|
+
when :bake
|
261
|
+
unless out.empty?
|
262
|
+
args = out.dup
|
263
|
+
out.clear
|
264
|
+
if Dir.exist?(args.last)
|
265
|
+
if projectpath?(val = args.pop)
|
266
|
+
context = val
|
267
|
+
else
|
268
|
+
out << val
|
269
|
+
end
|
270
|
+
end
|
271
|
+
append_value(args, escape: true)
|
272
|
+
contextdir(context) if context
|
273
|
+
end
|
274
|
+
end
|
275
|
+
option_clear out
|
276
|
+
run(from: :"buildx:#{flag}")
|
277
|
+
end
|
278
|
+
|
279
|
+
def composex(flag, opts = [], service: nil)
|
280
|
+
cmd, opts = docker_session('compose', opts: opts)
|
281
|
+
opts = option_sanitize(opts, OPT_DOCKER[:compose][:common]).first
|
282
|
+
append_file filetype unless session_arg?('f', 'file')
|
283
|
+
cmd << flag
|
284
|
+
out = option_sanitize(opts, OPT_DOCKER[:compose][flag] + OPT_DOCKER[:common]).first
|
285
|
+
from = :"compose:#{flag}"
|
286
|
+
case flag
|
287
|
+
when :build, :up
|
288
|
+
append_value(out, escape: true)
|
289
|
+
when :exec, :run
|
290
|
+
append_command(flag, service, out, from: from)
|
291
|
+
end
|
292
|
+
run(from: from)
|
293
|
+
end
|
294
|
+
|
295
|
+
def container(flag, opts = [], id: nil)
|
296
|
+
cmd, opts = docker_session('container', flag, opts: opts)
|
297
|
+
list = OPT_DOCKER[:container].fetch(flag, [])
|
298
|
+
list += OPT_DOCKER[:container][:update] if flag == :run
|
299
|
+
out = option_sanitize(opts, list, first: flag == :exec).first
|
300
|
+
from = :"container:#{flag}"
|
301
|
+
case flag
|
302
|
+
when :exec, :run
|
303
|
+
append_command(flag, id.to_s.empty? ? tag : id, out, target: cmd, from: from)
|
304
|
+
when :update
|
305
|
+
raise_error('missing container', hint: from) if out.empty?
|
306
|
+
append_value(out, escape: true)
|
307
|
+
when :commit
|
308
|
+
latest = out.shift || tag
|
309
|
+
cmd << id << latest
|
310
|
+
raise_error("unknown args: #{out.join(', ')}", hint: from) unless out.empty?
|
311
|
+
return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
|
312
|
+
|
313
|
+
registry = option('registry') || @registry
|
314
|
+
run(from: from, exception: registry.nil? ? exception : true)
|
315
|
+
return unless registry
|
316
|
+
|
317
|
+
opts = []
|
318
|
+
append_option('platform', target: opts, equals: true)
|
319
|
+
case option('disable-content-trust', ignore: false)
|
320
|
+
when 'true', '1'
|
321
|
+
opts << 'disable-content-trust'
|
322
|
+
when 'false', '0'
|
323
|
+
opts << 'disable-content-trust=false'
|
324
|
+
end
|
325
|
+
opts << '--quiet' unless verbose
|
326
|
+
return image(:push, opts, id: latest, registry: registry)
|
327
|
+
else
|
328
|
+
if out.empty?
|
329
|
+
ps = docker_output 'ps', '-a'
|
330
|
+
status = []
|
331
|
+
no = true
|
332
|
+
case flag
|
333
|
+
when :inspect, :diff
|
334
|
+
no = false
|
335
|
+
when :start
|
336
|
+
status = %w[created exited]
|
337
|
+
no = false
|
338
|
+
when :stop, :pause
|
339
|
+
status = %w[running restarting]
|
340
|
+
when :restart
|
341
|
+
status = %w[running paused exited]
|
342
|
+
when :unpause
|
343
|
+
status << 'paused'
|
344
|
+
no = false
|
345
|
+
when :top, :stats
|
346
|
+
status << 'running'
|
347
|
+
cmd << '--no-stream' if flag == :stats
|
348
|
+
no = false
|
349
|
+
when :kill
|
350
|
+
status = %w[running restarting paused]
|
351
|
+
when :rm
|
352
|
+
status = %w[created exited dead]
|
353
|
+
end
|
354
|
+
ps.merge(status.map { |s| "--filter=\"status=#{s}\"" })
|
355
|
+
list_image(flag, ps, no: no, hint: "status: #{status.join(', ')}", from: from) do |id|
|
356
|
+
run(cmd.temp(id), from: from)
|
357
|
+
end
|
358
|
+
return
|
359
|
+
else
|
360
|
+
append_value(out, escape: true)
|
361
|
+
end
|
362
|
+
end
|
363
|
+
run(from: from)
|
364
|
+
end
|
365
|
+
|
366
|
+
def image(flag, opts = [], sync: true, id: nil, registry: nil)
|
367
|
+
cmd, opts = docker_session('image', flag == :exec ? :list : flag, opts: opts)
|
368
|
+
out = option_sanitize(opts, OPT_DOCKER[:image][flag]).first
|
369
|
+
from = :"image:#{flag}"
|
370
|
+
case flag
|
371
|
+
when :list
|
372
|
+
if opts.size == out.size
|
373
|
+
index = 0
|
374
|
+
name = nil
|
375
|
+
opts.each do |opt|
|
376
|
+
if (name = opt[/^name=["']?(.+)["']?$/, 1])
|
377
|
+
opts.delete(opt)
|
378
|
+
break
|
379
|
+
end
|
380
|
+
end
|
381
|
+
flag = :run if flag == :list
|
382
|
+
list_image(flag, cmd << '-a', from: from) do |val|
|
383
|
+
container(flag, if name
|
384
|
+
opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
|
385
|
+
else
|
386
|
+
opts
|
387
|
+
end, id: val)
|
388
|
+
index += 1
|
389
|
+
end
|
390
|
+
return
|
391
|
+
else
|
392
|
+
option_clear out
|
393
|
+
end
|
394
|
+
when :rm
|
395
|
+
if id
|
396
|
+
cmd << id
|
397
|
+
elsif !out.empty?
|
398
|
+
out.each { |val| run(cmd.temp(val), sync: sync, from: from) }
|
399
|
+
return
|
400
|
+
else
|
401
|
+
list_image(flag, docker_output('image', 'ls -a'), from: from) do |val|
|
402
|
+
image(:rm, opts, sync: sync, id: val)
|
403
|
+
end
|
404
|
+
return
|
405
|
+
end
|
406
|
+
when :push
|
407
|
+
id ||= tag
|
408
|
+
raise_error(id ? "unknown args: #{out.join(', ')}" : 'no id/tag given', hint: from) unless id && out.empty?
|
409
|
+
reg = (registry || @registry).chomp('/')
|
410
|
+
uri = shell_quote("#{reg}/#{id}")
|
411
|
+
cmd << uri
|
412
|
+
img = docker_output 'image', 'tag', id, uri
|
413
|
+
return unless confirm_command(img.to_s, cmd.to_s, target: id, as: reg, title: from)
|
414
|
+
|
415
|
+
run(img, exception: true, sync: false, banner: false)
|
416
|
+
end
|
417
|
+
run(sync: sync, from: from)
|
418
|
+
end
|
419
|
+
|
420
|
+
def build?
|
421
|
+
@output[0] != false && dockerfile.exist?
|
422
|
+
end
|
423
|
+
|
424
|
+
def clean?
|
425
|
+
super || dockerfile.exist?
|
426
|
+
end
|
427
|
+
|
428
|
+
def dockerfile(val = nil)
|
429
|
+
if val == 'Dockerfile'
|
430
|
+
@file = false
|
431
|
+
elsif val
|
432
|
+
@file = if val.is_a?(Array)
|
433
|
+
val = val.select { |file| basepath(file).exist? }
|
434
|
+
val.size > 1 ? val : val.first
|
435
|
+
else
|
436
|
+
val || DIR_DOCKER.find { |file| basepath(file).exist? }
|
437
|
+
end
|
438
|
+
@file ||= false
|
439
|
+
end
|
440
|
+
basepath((@file.is_a?(Array) ? @file.first : @file) || 'Dockerfile')
|
441
|
+
end
|
442
|
+
|
443
|
+
private
|
444
|
+
|
445
|
+
def docker_session(*cmd, opts: nil)
|
446
|
+
return session('docker', *cmd) unless opts
|
447
|
+
|
448
|
+
ret = session 'docker'
|
449
|
+
opts = option_sanitize(opts, OPT_DOCKER[:common]).first
|
450
|
+
[ret.merge(cmd), opts]
|
451
|
+
end
|
452
|
+
|
453
|
+
def docker_output(*cmd, **kwargs)
|
454
|
+
session('docker', *cmd, main: false, options: false, **kwargs)
|
455
|
+
end
|
456
|
+
|
457
|
+
def append_command(flag, val, list, target: @session, from: nil)
|
458
|
+
raise_error('no command args', hint: from) if flag == :exec && list.empty?
|
459
|
+
target << val << list.shift
|
460
|
+
target << shell_quote(list.join(' && '), double: true, option: false) unless list.empty?
|
461
|
+
end
|
462
|
+
|
463
|
+
def append_file(type, target: @session)
|
464
|
+
return unless type == 2 || type == 4 || @file.is_a?(Array)
|
465
|
+
|
466
|
+
target.merge(as_a(@file).map { |val| quote_option('file', basepath(val)) })
|
467
|
+
end
|
468
|
+
|
469
|
+
def append_context(ctx = nil, target: @session)
|
470
|
+
if @file.is_a?(String) && !session_arg?('f', 'file') && !bake?(dockerfile) && !compose?(dockerfile)
|
471
|
+
target << quote_option('file', dockerfile)
|
472
|
+
end
|
473
|
+
target << contextdir(ctx || context)
|
474
|
+
end
|
475
|
+
|
476
|
+
def list_image(flag, cmd, hint: nil, from: nil, no: true, &blk)
|
477
|
+
pwd_set do
|
478
|
+
found = false
|
479
|
+
IO.popen(session_done(cmd << '--format=json')).each_with_index do |line, index|
|
480
|
+
data = JSON.parse(line)
|
481
|
+
rt = [data['Repository'], data['Tag']].reject { |val| val == '<none>' }.join(':')
|
482
|
+
rt = nil if tag.empty?
|
483
|
+
aa = if data['Names']
|
484
|
+
as_a(data['Names']).join(', ')
|
485
|
+
elsif tag
|
486
|
+
dd = true
|
487
|
+
data['Repository']
|
488
|
+
else
|
489
|
+
data['ID']
|
490
|
+
end
|
491
|
+
bb = index.succ.to_s
|
492
|
+
cc = bb.size + 1
|
493
|
+
a = sub_style(data['Image'] || rt || aa, styles: theme[:inline])
|
494
|
+
b = "Execute #{sub_style(flag, styles: theme[:active])} on #{a} (#{data['ID']})"
|
495
|
+
c, d = no ? ['y/N', 'N'] : ['Y/n', 'Y']
|
496
|
+
e = time_format(time_offset(data['CreatedAt']), pass: ['ms'])
|
497
|
+
f = sub_style(ARG[:BORDER][0], styles: theme[:inline])
|
498
|
+
g = ' ' * (cc + 1)
|
499
|
+
h = "#{sub_style(bb.rjust(cc), styles: theme[:current])} #{f} "
|
500
|
+
puts unless index == 0
|
501
|
+
puts "#{h + sub_style(aa, styles: theme[:subject])} (created #{e} ago)"
|
502
|
+
%w[Tag Status Ports Size].each do |key|
|
503
|
+
next if (key == 'Tag' && !dd) || (key == 'Size' && data[key] == '0B')
|
504
|
+
|
505
|
+
puts "#{g + f} #{key}: #{as_a(data[key]).join(', ')}" unless data[key].to_s.empty?
|
506
|
+
end
|
507
|
+
w = 9 + flag.to_s.size + 4 + aa.size
|
508
|
+
puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
|
509
|
+
found = true
|
510
|
+
next unless confirm("#{h + b}? [#{c}] ", d, timeout: 60)
|
511
|
+
|
512
|
+
puts if @@print_order == 0
|
513
|
+
blk.call data['ID']
|
514
|
+
end
|
515
|
+
puts log_message(Logger::INFO, 'none detected', subject: "#{name}:#{from}", hint: hint) unless found
|
516
|
+
end
|
517
|
+
rescue StandardError => e
|
518
|
+
log.error e
|
519
|
+
ret = on(:error, from, e)
|
520
|
+
raise if exception && ret != true
|
521
|
+
|
522
|
+
warn log_message(Logger::WARN, e, pass: true) if warning?
|
523
|
+
end
|
524
|
+
|
525
|
+
def confirm_command(*args, title: nil, target: nil, as: nil)
|
526
|
+
return false unless title && target
|
527
|
+
|
528
|
+
puts unless @@print_order == 0
|
529
|
+
t = title.to_s.split(':')
|
530
|
+
emphasize(args, title: message(t.first.upcase, *t.drop(1)), border: borderstyle, sub: [
|
531
|
+
{ pat: /\A(\w+(?: => \w+)+)(.*)\z/, styles: theme[:header] },
|
532
|
+
{ pat: /\A(.+)\z/, styles: theme[:caution] }
|
533
|
+
])
|
534
|
+
@@print_order += 1
|
535
|
+
a = t.last.capitalize
|
536
|
+
b = sub_style(target, styles: theme[:subject])
|
537
|
+
c = as && sub_style(as, styles: theme[:inline])
|
538
|
+
confirm("#{a} #{b}#{c ? " as #{c}" : ''}? [y/N] ", 'N', timeout: 60)
|
539
|
+
end
|
540
|
+
|
541
|
+
def filetype(val = dockerfile)
|
542
|
+
case File.extname(val)
|
543
|
+
when '.hcl', '.json'
|
544
|
+
bake?(val) ? 1 : 2
|
545
|
+
when '.yml', '.yaml'
|
546
|
+
if compose?(val)
|
547
|
+
path.children.any? { |file| bake?(file) } ? 1 : 3
|
548
|
+
else
|
549
|
+
4
|
550
|
+
end
|
551
|
+
else
|
552
|
+
0
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
def contextdir(val = nil)
|
557
|
+
val && projectpath?(val) ? shell_quote(basepath(val)) : '.'
|
558
|
+
end
|
559
|
+
|
560
|
+
def compose?(file)
|
561
|
+
COMPOSEFILE.include?(File.basename(file))
|
562
|
+
end
|
563
|
+
|
564
|
+
def bake?(file)
|
565
|
+
BAKEFILE.include?(File.basename(file))
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
Application.implement Docker
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|