squared 0.5.12 → 0.6.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 +94 -0
- data/README.md +75 -34
- data/lib/squared/common/base.rb +1 -22
- data/lib/squared/common/format.rb +32 -24
- data/lib/squared/common/prompt.rb +55 -32
- data/lib/squared/common/shell.rb +43 -10
- data/lib/squared/common/system.rb +69 -36
- data/lib/squared/common/utils.rb +29 -6
- data/lib/squared/config.rb +17 -21
- data/lib/squared/version.rb +1 -1
- data/lib/squared/workspace/application.rb +80 -81
- data/lib/squared/workspace/project/base.rb +493 -329
- data/lib/squared/workspace/project/docker.rb +375 -273
- data/lib/squared/workspace/project/git.rb +326 -310
- data/lib/squared/workspace/project/node.rb +485 -254
- data/lib/squared/workspace/project/python.rb +328 -199
- data/lib/squared/workspace/project/ruby.rb +643 -342
- data/lib/squared/workspace/project/support/class.rb +166 -68
- data/lib/squared/workspace/repo.rb +38 -34
- data/lib/squared/workspace/series.rb +6 -6
- data/lib/squared/workspace/support/base.rb +3 -24
- data/lib/squared/workspace/support/variables.rb +48 -0
- data/lib/squared/workspace/support.rb +1 -1
- data/lib/squared/workspace.rb +1 -1
- metadata +2 -2
- data/lib/squared/workspace/support/data.rb +0 -11
|
@@ -20,10 +20,11 @@ module Squared
|
|
|
20
20
|
sbom=q].freeze
|
|
21
21
|
}.freeze,
|
|
22
22
|
compose: {
|
|
23
|
-
common: %w[all-resources compatibility dry-run
|
|
23
|
+
common: %w[all-resources ansi|b compatibility dry-run env-file=p f|file=p parallel=n profile=b progress=b
|
|
24
24
|
project-directory=p p|project-name=e].freeze,
|
|
25
25
|
build: %w[check no-cache print pull push with-dependencies q|quiet build-arg=qq builder=b m|memory=b
|
|
26
26
|
provenance=q sbom=q ssh=qq].freeze,
|
|
27
|
+
create: %w[build force-recreate no-build no-recreate quiet-pull remove-orphans y|yes pull=b scale=i].freeze,
|
|
27
28
|
exec: %w[d|detach privileged e|env=qq index=i T|no-TTY=b? user=e w|workdir=q].freeze,
|
|
28
29
|
run: %w[build d|detach no-deps q|quiet quiet-build quiet-pull remove-orphans rm P|service-ports use-aliases
|
|
29
30
|
cap-add=b cap-drop=b entrypoint=q e|env=qq env-from-file=p i|interactive=b? l|label=q name=b
|
|
@@ -57,13 +58,13 @@ module Squared
|
|
|
57
58
|
commit: %w[a|author=q c|change=q m|message=q pause=b?].freeze,
|
|
58
59
|
inspect: %w[s|size f|format=q type=b].freeze,
|
|
59
60
|
start: %w[a|attach i|interactive detach-keys=q].freeze,
|
|
60
|
-
stop: %w[s|signal=b t|
|
|
61
|
-
restart: %w[s|signal=b t|
|
|
61
|
+
stop: %w[s|signal=b t|timeout=i].freeze,
|
|
62
|
+
restart: %w[s|signal=b t|timeout=i].freeze,
|
|
62
63
|
kill: %w[s|signal=b].freeze,
|
|
63
64
|
stats: %w[a|all no-stream no-trunc format|q].freeze
|
|
64
65
|
}.freeze,
|
|
65
66
|
image: {
|
|
66
|
-
|
|
67
|
+
ls: %w[a|all digests no-trunc q|quiet tree f|filter=q format=q].freeze,
|
|
67
68
|
push: %w[a|all-tags disable-content-trust=b? platform=b q|quiet].freeze,
|
|
68
69
|
rm: %w[f|force no-prune platform=b].freeze,
|
|
69
70
|
save: %w[o|output=p platform=b].freeze
|
|
@@ -80,6 +81,15 @@ module Squared
|
|
|
80
81
|
volume: %w[volume-subpath volume-nocopy volume-opt].freeze,
|
|
81
82
|
tmpfs: %w[tmpfs-size tmpfs-mode].freeze,
|
|
82
83
|
image: %w[image-path].freeze
|
|
84
|
+
}.freeze,
|
|
85
|
+
ls: {
|
|
86
|
+
compose: %w[Name Image Command Service RunningFor Status Ports CreatedAt ExitCode Health ID Labels
|
|
87
|
+
LocalVolumes Mounts Names Networks Project Publishers Size State].freeze,
|
|
88
|
+
container: %w[ID Image Command RunningFor Status Ports Names CreatedAt Labels LocalVolumes Mounts Networks
|
|
89
|
+
Platform Size State].freeze,
|
|
90
|
+
image: %w[Repository Tag ID Containers CreatedSince Size CreatedAt Digest SharedSize UniqueSize
|
|
91
|
+
VirtualSize].freeze,
|
|
92
|
+
network: %w[ID Name Driver Scope CreatedAt IPv4 IPv6 Internal Labels].freeze
|
|
83
93
|
}.freeze
|
|
84
94
|
}.freeze
|
|
85
95
|
private_constant :COMPOSEFILE, :BAKEFILE, :OPT_DOCKER, :VAL_DOCKER
|
|
@@ -98,12 +108,13 @@ module Squared
|
|
|
98
108
|
|
|
99
109
|
subtasks({
|
|
100
110
|
'build' => %i[tag context].freeze,
|
|
101
|
-
'compose' => %i[build run exec up down].freeze,
|
|
111
|
+
'compose' => %i[build create run exec up down service].freeze,
|
|
102
112
|
'bake' => %i[build check].freeze,
|
|
103
|
-
'image' => %i[
|
|
113
|
+
'image' => %i[ls rm push tag save].freeze,
|
|
104
114
|
'container' => %i[run create exec update commit inspect diff start stop restart pause unpause top stats kill
|
|
105
115
|
rm].freeze,
|
|
106
|
-
'network' => %i[connect disconnect].freeze
|
|
116
|
+
'network' => %i[connect disconnect].freeze,
|
|
117
|
+
'ls' => nil
|
|
107
118
|
})
|
|
108
119
|
|
|
109
120
|
attr_reader :context
|
|
@@ -136,114 +147,168 @@ module Squared
|
|
|
136
147
|
Docker.subtasks do |action, flags|
|
|
137
148
|
next if task_pass?(action)
|
|
138
149
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
150
|
+
if flags.nil?
|
|
151
|
+
case action
|
|
152
|
+
when 'ls'
|
|
153
|
+
format_desc(action, nil, VAL_DOCKER[:ls].keys, after: 'a/ll?,s/tandard?,range*', arg: nil)
|
|
154
|
+
task action, [:command] do |_, args|
|
|
155
|
+
command = param_guard(action, 'command', args: args, key: :command)
|
|
156
|
+
args = args.extras
|
|
157
|
+
a = args.delete('a') || args.delete('all')
|
|
158
|
+
ls = case command
|
|
159
|
+
when 'network'
|
|
160
|
+
a = nil
|
|
161
|
+
'ls'
|
|
162
|
+
when 'image', 'container'
|
|
163
|
+
'ls'
|
|
164
|
+
when 'compose'
|
|
165
|
+
'ps'
|
|
166
|
+
else
|
|
167
|
+
raise_error ArgumentError, 'unrecognized command', hint: command
|
|
168
|
+
end
|
|
169
|
+
data = VAL_DOCKER[:ls][command.to_sym]
|
|
170
|
+
if args.delete('s') || args.delete('standard')
|
|
171
|
+
cols = data.first(data.index('CreatedAt'))
|
|
172
|
+
else
|
|
173
|
+
cols = []
|
|
174
|
+
args.each do |val|
|
|
175
|
+
if val =~ /^(\d+)$/
|
|
176
|
+
cols << data[$1.to_i.pred]
|
|
177
|
+
elsif val =~ /^(\d+)(-|\.{2,3})(\d+)$/
|
|
178
|
+
j = $1.to_i.pred
|
|
179
|
+
k = $3.to_i - ($2 == '..' ? 2 : 1)
|
|
180
|
+
cols.concat(data[j..k]) if k > j
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
if cols.empty?
|
|
184
|
+
cols = choice_index('Select a column', data, multiple: true, force: true, attempts: 1)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
cmd = docker_output(command, ls, a && '-a')
|
|
188
|
+
cmd << quote_option('format', "table #{cols.map! { |val| "{{.#{val}}}" }.join("\t")}")
|
|
189
|
+
run(cmd, banner: false, from: :ls)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
else
|
|
193
|
+
namespace action do
|
|
194
|
+
flags.each do |flag|
|
|
195
|
+
case action
|
|
196
|
+
when 'build'
|
|
145
197
|
format_desc(action, flag, 'opts*', before: flag == :tag ? 'name' : 'dir')
|
|
146
198
|
task flag, [flag] do |_, args|
|
|
147
199
|
param = param_guard(action, flag, args: args, key: flag)
|
|
148
200
|
buildx(:build, args.extras, "#{flag}": param)
|
|
149
201
|
end
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
202
|
+
when 'bake'
|
|
203
|
+
break unless bake?
|
|
204
|
+
|
|
205
|
+
case flag
|
|
206
|
+
when :build
|
|
207
|
+
format_desc action, flag, 'opts*,target*,context?|:'
|
|
208
|
+
task flag do |_, args|
|
|
209
|
+
args = args.to_a
|
|
210
|
+
if args.first == ':'
|
|
211
|
+
choice_command :bake
|
|
212
|
+
else
|
|
213
|
+
buildx :bake, args
|
|
214
|
+
end
|
|
163
215
|
end
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
buildx :bake, ['allow=fs.read=*', 'call=check', target]
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
when 'compose'
|
|
173
|
-
break unless compose?
|
|
174
|
-
|
|
175
|
-
case flag
|
|
176
|
-
when :build, :up, :down
|
|
177
|
-
format_desc action, flag, 'opts*,service*|:'
|
|
178
|
-
task flag do |_, args|
|
|
179
|
-
compose! flag, args.to_a
|
|
180
|
-
end
|
|
181
|
-
when :exec, :run
|
|
182
|
-
format_desc action, flag, "service|:,command#{flag == :exec ? '' : '?'}|::,args*,opts*"
|
|
183
|
-
task flag, [:service] do |_, args|
|
|
184
|
-
service = param_guard(action, flag, args: args, key: :service)
|
|
185
|
-
compose!(flag, args.extras, service: service)
|
|
186
|
-
end
|
|
187
|
-
end
|
|
188
|
-
when 'container'
|
|
189
|
-
case flag
|
|
190
|
-
when :exec, :commit
|
|
191
|
-
format_desc(action, flag, flag == :exec ? 'id/name,opts*,args+|:' : 'id/name,tag?,opts*')
|
|
192
|
-
task flag, [:id] do |_, args|
|
|
193
|
-
if flag == :exec && !args.id
|
|
194
|
-
choice_command flag
|
|
195
|
-
else
|
|
196
|
-
id = param_guard(action, flag, args: args, key: :id)
|
|
197
|
-
container(flag, args.extras, id: id)
|
|
216
|
+
when :check
|
|
217
|
+
format_desc action, flag, 'target'
|
|
218
|
+
task flag, [:target] do |_, args|
|
|
219
|
+
target = param_guard(action, flag, args: args, key: :target)
|
|
220
|
+
buildx :bake, ['allow=fs.read=*', 'call=check', target]
|
|
198
221
|
end
|
|
199
222
|
end
|
|
200
|
-
when
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
223
|
+
when 'compose'
|
|
224
|
+
break unless compose?
|
|
225
|
+
|
|
226
|
+
case flag
|
|
227
|
+
when :exec, :run
|
|
228
|
+
format_desc action, flag, "service|:,command#{flag == :exec ? '' : '?'}|::,args*,opts*"
|
|
229
|
+
task flag, [:service] do |_, args|
|
|
230
|
+
service = param_guard(action, flag, args: args, key: :service)
|
|
231
|
+
compose!(flag, args.extras, service: service)
|
|
232
|
+
end
|
|
233
|
+
when :service
|
|
234
|
+
cmds = %w[down kill pause restart rm start stop top unpause watch].freeze
|
|
235
|
+
format_desc(action, flag, cmds, arg: nil, after: 'name+|:')
|
|
236
|
+
task flag, [:command] do |_, args|
|
|
237
|
+
command = param_guard(action, flag, args: args, key: :command)
|
|
238
|
+
raise_error ArgumentError, 'unrecognized command', hint: command unless cmds.include?(command)
|
|
239
|
+
service = args.extras
|
|
240
|
+
if service.first == ':'
|
|
241
|
+
choice_command flag, command
|
|
242
|
+
else
|
|
243
|
+
compose!(flag, [command], service: service.empty? || service)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
else
|
|
247
|
+
format_desc action, flag, 'opts*,service*|:'
|
|
248
|
+
task flag do |_, args|
|
|
249
|
+
compose!(flag, args.to_a, multiple: true)
|
|
207
250
|
end
|
|
208
251
|
end
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
252
|
+
when 'container'
|
|
253
|
+
case flag
|
|
254
|
+
when :exec, :commit
|
|
255
|
+
format_desc(action, flag, flag == :exec ? 'id/name,opts*,args+|:' : 'id/name,tag?,opts*')
|
|
256
|
+
task flag, [:id] do |_, args|
|
|
257
|
+
if flag == :exec && !args.id
|
|
258
|
+
choice_command flag
|
|
259
|
+
else
|
|
260
|
+
id = param_guard(action, flag, args: args, key: :id)
|
|
261
|
+
container(flag, args.extras, id: id)
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
when :run, :create
|
|
265
|
+
format_desc action, flag, 'image,opts*,args*|:'
|
|
266
|
+
task flag, [:image] do |_, args|
|
|
267
|
+
if args.image
|
|
268
|
+
container(flag, args.extras, id: args.image)
|
|
269
|
+
else
|
|
270
|
+
choice_command flag
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
else
|
|
274
|
+
format_desc action, flag, "opts*,id/name#{flag == :update ? '+' : '*'}"
|
|
275
|
+
task flag do |_, args|
|
|
276
|
+
container flag, args.to_a
|
|
277
|
+
end
|
|
213
278
|
end
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
279
|
+
when 'image'
|
|
280
|
+
case flag
|
|
281
|
+
when :push
|
|
282
|
+
format_desc action, flag, 'tag,registry/username?,opts*'
|
|
283
|
+
task flag, [:tag] do |_, args|
|
|
284
|
+
id = param_guard(action, flag, args: args, key: :tag)
|
|
285
|
+
image(flag, args.extras, id: id)
|
|
286
|
+
end
|
|
287
|
+
else
|
|
288
|
+
format_desc(action, flag, case flag
|
|
289
|
+
when :rm, :save then 'id*,opts*'
|
|
290
|
+
when :tag then 'version?'
|
|
291
|
+
else 'opts*,args*'
|
|
292
|
+
end)
|
|
293
|
+
task flag do |_, args|
|
|
294
|
+
args = args.to_a
|
|
295
|
+
if !args.empty? || flag == :ls
|
|
296
|
+
image flag, args
|
|
297
|
+
else
|
|
298
|
+
choice_command flag
|
|
299
|
+
end
|
|
300
|
+
end
|
|
222
301
|
end
|
|
223
|
-
|
|
224
|
-
format_desc
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
end)
|
|
229
|
-
task flag do |_, args|
|
|
230
|
-
args = args.to_a
|
|
231
|
-
if args.empty? && flag != :list
|
|
232
|
-
choice_command flag
|
|
302
|
+
when 'network'
|
|
303
|
+
format_desc action, flag, 'target,opts*'
|
|
304
|
+
task flag, [:target] do |_, args|
|
|
305
|
+
if args.target
|
|
306
|
+
network(flag, args.extras, target: args.target)
|
|
233
307
|
else
|
|
234
|
-
|
|
308
|
+
choice_command flag
|
|
235
309
|
end
|
|
236
310
|
end
|
|
237
311
|
end
|
|
238
|
-
when 'network'
|
|
239
|
-
format_desc action, flag, 'target,opts*'
|
|
240
|
-
task flag, [:target] do |_, args|
|
|
241
|
-
if args.target
|
|
242
|
-
network(flag, args.extras, target: args.target)
|
|
243
|
-
else
|
|
244
|
-
choice_command flag
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
312
|
end
|
|
248
313
|
end
|
|
249
314
|
end
|
|
@@ -260,7 +325,7 @@ module Squared
|
|
|
260
325
|
end
|
|
261
326
|
|
|
262
327
|
def compose(opts, flags = nil, script: false, args: nil, from: :run, **)
|
|
263
|
-
return opts
|
|
328
|
+
return opts unless script
|
|
264
329
|
|
|
265
330
|
ret = docker_session
|
|
266
331
|
if from == :run
|
|
@@ -286,10 +351,10 @@ module Squared
|
|
|
286
351
|
when Enumerable
|
|
287
352
|
ret.merge(opts.to_a)
|
|
288
353
|
end
|
|
289
|
-
[args, flags].each_with_index do |
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
354
|
+
[args, flags].each_with_index do |item, i|
|
|
355
|
+
next unless item && (data = append_any(item, target: []))
|
|
356
|
+
|
|
357
|
+
ret.merge(data.map! { |arg| i == 0 ? fill_option(arg) : quote_option('build-arg', arg) })
|
|
293
358
|
end
|
|
294
359
|
case from
|
|
295
360
|
when :run
|
|
@@ -312,8 +377,8 @@ module Squared
|
|
|
312
377
|
end
|
|
313
378
|
append_context
|
|
314
379
|
when :bake, :compose
|
|
315
|
-
option(from == :bake ? 'target' : 'service', ignore: false) do |
|
|
316
|
-
ret.merge(split_escape(
|
|
380
|
+
option(from == :bake ? 'target' : 'service', ignore: false) do |val|
|
|
381
|
+
ret.merge(split_escape(val).map! { |s| shell_quote(s) })
|
|
317
382
|
end
|
|
318
383
|
end
|
|
319
384
|
ret
|
|
@@ -321,9 +386,11 @@ module Squared
|
|
|
321
386
|
|
|
322
387
|
def buildx(flag, opts = [], tag: nil, context: nil)
|
|
323
388
|
cmd, opts = docker_session('buildx', opts: opts)
|
|
324
|
-
op =
|
|
325
|
-
|
|
326
|
-
|
|
389
|
+
op = OPT_DOCKER[:buildx].yield_self do |data|
|
|
390
|
+
OptionPartition.new(opts, data[:common], cmd, project: self)
|
|
391
|
+
.append(flag, quote: false)
|
|
392
|
+
.parse(data[flag == :bake ? :bake : :build] + data[:shared])
|
|
393
|
+
end
|
|
327
394
|
case flag
|
|
328
395
|
when :build, :context
|
|
329
396
|
append_tag(tag || option('tag', ignore: false) || self.tag)
|
|
@@ -347,46 +414,56 @@ module Squared
|
|
|
347
414
|
run(from: :"buildx:#{flag}")
|
|
348
415
|
end
|
|
349
416
|
|
|
350
|
-
def compose!(flag, opts = [], service: nil)
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
read_composefile('services', target: op.values_of('f', 'file')) { |data| keys.merge(data.keys) }
|
|
363
|
-
service = unless keys.empty?
|
|
364
|
-
choice_index('Add services', keys, multiple: multiple, force: !multiple,
|
|
365
|
-
attempts: multiple ? 1 : 5)
|
|
366
|
-
end
|
|
367
|
-
end
|
|
368
|
-
if multiple
|
|
369
|
-
op.concat(service) if service
|
|
370
|
-
op.append(delim: true, escape: true, strip: /^:/)
|
|
417
|
+
def compose!(flag, opts = [], service: nil, multiple: false)
|
|
418
|
+
from = :"compose:#{flag}"
|
|
419
|
+
if flag == :service
|
|
420
|
+
command = opts.first
|
|
421
|
+
if service == true
|
|
422
|
+
cmd, status = filter_ps command, from
|
|
423
|
+
lines = IO.popen(cmd.temp('--services')).map(&:strip).reject(&:empty?)
|
|
424
|
+
return list_empty(hint: status) if lines.empty?
|
|
425
|
+
|
|
426
|
+
service = choice_index('Choose a service', lines, multiple: true, force: true, attempts: 1)
|
|
427
|
+
end
|
|
428
|
+
docker_session('compose', command, '--', *service)
|
|
371
429
|
else
|
|
372
|
-
|
|
373
|
-
|
|
430
|
+
cmd, opts = docker_session('compose', opts: opts)
|
|
431
|
+
op = OptionPartition.new(opts, OPT_DOCKER[:compose][:common], cmd, project: self)
|
|
432
|
+
append_file filetype unless op.arg?('f', 'file')
|
|
433
|
+
op << flag
|
|
434
|
+
op.parse(OPT_DOCKER[:compose].fetch(flag, []))
|
|
435
|
+
if op.remove(':') || service == ':'
|
|
436
|
+
keys = Set.new
|
|
437
|
+
read_composefile('services', target: op.values_of('f', 'file')) { |data| keys.merge(data.keys) }
|
|
438
|
+
service = unless keys.empty?
|
|
439
|
+
choice_index('Add services', keys, multiple: multiple, force: !multiple,
|
|
440
|
+
attempts: multiple ? 1 : 3)
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
if multiple
|
|
444
|
+
op.concat(service) if service
|
|
445
|
+
op.append(delim: true, escape: true, strip: /^:/)
|
|
446
|
+
else
|
|
447
|
+
raise_error ArgumentError, 'no service was selected', hint: flag unless service
|
|
448
|
+
append_command(flag, service, op.extras, prompt: '::')
|
|
449
|
+
end
|
|
374
450
|
end
|
|
375
|
-
run(from:
|
|
451
|
+
run(from: from)
|
|
376
452
|
end
|
|
377
453
|
|
|
378
454
|
def container(flag, opts = [], id: nil)
|
|
379
455
|
cmd, opts = docker_session('container', flag, opts: opts)
|
|
380
456
|
rc = flag == :run || flag == :create
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
457
|
+
op = OPT_DOCKER[:container].yield_self do |data|
|
|
458
|
+
list = data.fetch(flag, [])
|
|
459
|
+
list += data[:create] if flag == :run
|
|
460
|
+
list += data[:update] if rc
|
|
461
|
+
OptionPartition.new(opts, list, cmd, project: self, args: rc || flag == :exec)
|
|
462
|
+
end
|
|
385
463
|
from = :"container:#{flag}"
|
|
386
464
|
case flag
|
|
387
465
|
when :run, :create, :exec
|
|
388
466
|
if rc && !op.arg?('mount')
|
|
389
|
-
run = VAL_DOCKER[:run]
|
|
390
467
|
all = collect_hash VAL_DOCKER[:run]
|
|
391
468
|
delim = Regexp.new(",\\s*(?=#{all.join('|')})")
|
|
392
469
|
Array(@mounts).each do |val|
|
|
@@ -399,11 +476,11 @@ module Squared
|
|
|
399
476
|
when 'bind', 'volume', 'image', 'tmpfs'
|
|
400
477
|
type = v
|
|
401
478
|
else
|
|
402
|
-
raise_error
|
|
479
|
+
raise_error TypeError, "unknown: #{v}", hint: flag
|
|
403
480
|
end
|
|
404
481
|
elsif all.include?(k)
|
|
405
482
|
unless type
|
|
406
|
-
run.each_pair do |key, val|
|
|
483
|
+
VAL_DOCKER[:run].each_pair do |key, val|
|
|
407
484
|
next unless val.include?(k)
|
|
408
485
|
|
|
409
486
|
type = key.to_s unless key == :common
|
|
@@ -423,18 +500,18 @@ module Squared
|
|
|
423
500
|
log_message(Logger::INFO, 'unrecognized option', subject: from, hint: k)
|
|
424
501
|
end
|
|
425
502
|
end
|
|
426
|
-
raise_error
|
|
503
|
+
raise_error TypeError, 'none specified', hint: flag unless type
|
|
427
504
|
cmd << "--mount type=#{type},#{args.join(',')}"
|
|
428
505
|
end
|
|
429
506
|
end
|
|
430
507
|
append_command(flag, id || tagmain, op.extras)
|
|
431
508
|
when :update
|
|
432
|
-
raise_error
|
|
509
|
+
raise_error ArgumentError, 'missing container', hint: flag if op.empty?
|
|
433
510
|
op.append(escape: true, strip: /^:/)
|
|
434
511
|
when :commit
|
|
435
512
|
latest = op.shift || tagmain
|
|
436
513
|
cmd << id << latest
|
|
437
|
-
raise_error
|
|
514
|
+
raise_error ArgumentError, "unrecognized args: #{op.join(', ')}", hint: flag unless op.empty?
|
|
438
515
|
return unless confirm_command(cmd.to_s, title: from, target: id, as: latest)
|
|
439
516
|
|
|
440
517
|
registry = option('registry') || @registry
|
|
@@ -453,38 +530,12 @@ module Squared
|
|
|
453
530
|
return image(:push, opts, id: latest, registry: registry)
|
|
454
531
|
else
|
|
455
532
|
if op.empty?
|
|
456
|
-
status =
|
|
457
|
-
no
|
|
458
|
-
|
|
459
|
-
when :inspect, :diff
|
|
460
|
-
no = false
|
|
461
|
-
when :start
|
|
462
|
-
status = %w[created exited]
|
|
463
|
-
no = false
|
|
464
|
-
when :stop, :pause
|
|
465
|
-
status = %w[running restarting]
|
|
466
|
-
when :restart
|
|
467
|
-
status = %w[running paused exited]
|
|
468
|
-
when :unpause
|
|
469
|
-
status << 'paused'
|
|
470
|
-
no = false
|
|
471
|
-
when :top, :stats
|
|
472
|
-
status << 'running'
|
|
473
|
-
cmd << '--no-stream' if flag == :stats
|
|
474
|
-
no = false
|
|
475
|
-
when :kill
|
|
476
|
-
status = %w[running restarting paused]
|
|
477
|
-
when :rm
|
|
478
|
-
status = %w[created exited dead]
|
|
479
|
-
end
|
|
480
|
-
ps = docker_output('ps -a', *status.map { |s| quote_option('filter', "status=#{s}") })
|
|
481
|
-
list_image(flag, ps, no: no, hint: "status: #{status.join(', ')}", from: from) do |img|
|
|
482
|
-
run(cmd.temp(img), from: from)
|
|
483
|
-
end
|
|
533
|
+
ps, status, no = filter_ps flag, from
|
|
534
|
+
cmd << '--no-stream' if flag == :stats
|
|
535
|
+
list_image(flag, ps, no: no, hint: status, from: from) { |img| run(cmd.temp(img), from: from) }
|
|
484
536
|
return
|
|
485
|
-
else
|
|
486
|
-
op.append(escape: true, strip: /^:/)
|
|
487
537
|
end
|
|
538
|
+
op.append(escape: true, strip: /^:/)
|
|
488
539
|
end
|
|
489
540
|
run(from: from)
|
|
490
541
|
end
|
|
@@ -492,16 +543,21 @@ module Squared
|
|
|
492
543
|
def image(flag, opts = [], sync: true, id: nil, registry: nil)
|
|
493
544
|
cmd, opts = docker_session('image', flag, opts: opts)
|
|
494
545
|
op = OptionPartition.new(opts, OPT_DOCKER[:image].fetch(flag, []), cmd, project: self)
|
|
495
|
-
exception =
|
|
546
|
+
exception = self.exception
|
|
496
547
|
banner = true
|
|
497
548
|
from = :"image:#{flag}"
|
|
498
549
|
case flag
|
|
499
|
-
when :
|
|
550
|
+
when :ls
|
|
500
551
|
if opts.size == op.size
|
|
501
552
|
index = 0
|
|
502
553
|
name = nil
|
|
503
|
-
opts.reverse_each
|
|
504
|
-
|
|
554
|
+
opts.reverse_each do |opt|
|
|
555
|
+
if (name = opt[/^name=["']?(.+?)["']?$/, 1])
|
|
556
|
+
opts.delete(opt)
|
|
557
|
+
break
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
list_image(:run, from: from) do |val|
|
|
505
561
|
container(:run, if name
|
|
506
562
|
opts.dup << "name=#{index == 0 ? name : "#{name}-#{index}"}"
|
|
507
563
|
else
|
|
@@ -510,19 +566,12 @@ module Squared
|
|
|
510
566
|
index += 1
|
|
511
567
|
end
|
|
512
568
|
return
|
|
513
|
-
else
|
|
514
|
-
op.clear
|
|
515
569
|
end
|
|
570
|
+
op.clear
|
|
516
571
|
when :rm
|
|
517
|
-
|
|
518
|
-
op << id
|
|
519
|
-
if option('y')
|
|
520
|
-
exception = false
|
|
521
|
-
banner = false
|
|
522
|
-
end
|
|
523
|
-
else
|
|
572
|
+
unless id
|
|
524
573
|
if op.empty?
|
|
525
|
-
list_image(:rm,
|
|
574
|
+
list_image(:rm, from: from) do |val|
|
|
526
575
|
image(:rm, opts, sync: sync, id: val)
|
|
527
576
|
end
|
|
528
577
|
else
|
|
@@ -530,8 +579,13 @@ module Squared
|
|
|
530
579
|
end
|
|
531
580
|
return
|
|
532
581
|
end
|
|
582
|
+
op << id
|
|
583
|
+
if option('y')
|
|
584
|
+
exception = false
|
|
585
|
+
banner = false
|
|
586
|
+
end
|
|
533
587
|
when :tag, :save
|
|
534
|
-
list_image(flag,
|
|
588
|
+
list_image(flag, from: from) do |val|
|
|
535
589
|
op << val
|
|
536
590
|
if flag == :tag
|
|
537
591
|
op << tagname("#{project}:#{op.first}")
|
|
@@ -541,8 +595,14 @@ module Squared
|
|
|
541
595
|
when :push
|
|
542
596
|
id ||= option('tag', ignore: false) || tagmain
|
|
543
597
|
registry ||= op.shift || option('registry') || @registry
|
|
544
|
-
|
|
545
|
-
|
|
598
|
+
unless id && op.empty?
|
|
599
|
+
if id
|
|
600
|
+
raise_error ArgumentError, "unrecognized args: #{op.join(', ')}", hint: flag
|
|
601
|
+
else
|
|
602
|
+
raise_error 'no id/tag', hint: flag
|
|
603
|
+
end
|
|
604
|
+
end
|
|
605
|
+
raise_error ArgumentError, 'username/registry not specified', hint: flag unless registry
|
|
546
606
|
registry.chomp!('/')
|
|
547
607
|
uri = shell_quote "#{registry}/#{id}"
|
|
548
608
|
op << uri
|
|
@@ -554,17 +614,18 @@ module Squared
|
|
|
554
614
|
exception = true
|
|
555
615
|
banner = false
|
|
556
616
|
end
|
|
557
|
-
|
|
558
|
-
|
|
617
|
+
run(cmd, sync: sync, exception: exception, banner: banner, from: from).tap do |ret|
|
|
618
|
+
success?(ret, flag == :tag || flag == :save)
|
|
619
|
+
end
|
|
559
620
|
end
|
|
560
621
|
|
|
561
622
|
def network(flag, opts = [], target: nil)
|
|
562
623
|
cmd, opts = docker_session('network', flag, opts: opts)
|
|
563
|
-
|
|
564
|
-
|
|
624
|
+
OptionPartition.new(opts, OPT_DOCKER[:network].fetch(flag, []), cmd, project: self)
|
|
625
|
+
.clear
|
|
565
626
|
from = :"network:#{flag}"
|
|
566
627
|
list_image(flag, docker_output('ps -a'), from: from) do |img|
|
|
567
|
-
|
|
628
|
+
success?(run(cmd.temp(target, img), from: from))
|
|
568
629
|
end
|
|
569
630
|
end
|
|
570
631
|
|
|
@@ -591,10 +652,10 @@ module Squared
|
|
|
591
652
|
def dockerfile(val = nil)
|
|
592
653
|
if val
|
|
593
654
|
@file = if val.is_a?(Array)
|
|
594
|
-
val = val.select { |file|
|
|
655
|
+
val = val.select { |file| exist?(file) }
|
|
595
656
|
val.size > 1 ? val : val.first
|
|
596
657
|
elsif val == true
|
|
597
|
-
DIR_DOCKER.find { |file|
|
|
658
|
+
DIR_DOCKER.find { |file| exist?(file) }
|
|
598
659
|
elsif val != 'Dockerfile'
|
|
599
660
|
val
|
|
600
661
|
end
|
|
@@ -623,8 +684,7 @@ module Squared
|
|
|
623
684
|
return session('docker', *cmd) unless opts
|
|
624
685
|
|
|
625
686
|
op = OptionPartition.new(opts, OPT_DOCKER[:common], project: self)
|
|
626
|
-
|
|
627
|
-
[ret, op.extras]
|
|
687
|
+
[session('docker', *op.to_a, *cmd), op.extras]
|
|
628
688
|
end
|
|
629
689
|
|
|
630
690
|
def docker_output(*cmd, **kwargs)
|
|
@@ -634,8 +694,8 @@ module Squared
|
|
|
634
694
|
def append_command(flag, val, list, target: @session, prompt: ':')
|
|
635
695
|
if list.delete(prompt)
|
|
636
696
|
list << readline('Enter command [args]', force: flag == :exec)
|
|
637
|
-
|
|
638
|
-
list << args
|
|
697
|
+
else
|
|
698
|
+
env('DOCKER_ARGS') { |args| list << args }
|
|
639
699
|
end
|
|
640
700
|
case flag
|
|
641
701
|
when :run
|
|
@@ -643,7 +703,7 @@ module Squared
|
|
|
643
703
|
target << basic_option('name', dnsname("#{name}_%s" % rand_s(6)))
|
|
644
704
|
end
|
|
645
705
|
when :exec
|
|
646
|
-
raise_error
|
|
706
|
+
raise_error ArgumentError, 'nothing to execute', hint: flag if list.empty?
|
|
647
707
|
end
|
|
648
708
|
target << val << list.shift
|
|
649
709
|
target << list.join(' && ') unless list.empty?
|
|
@@ -695,17 +755,45 @@ module Squared
|
|
|
695
755
|
end
|
|
696
756
|
end
|
|
697
757
|
|
|
698
|
-
def
|
|
758
|
+
def filter_ps(flag, from = :'container:ps')
|
|
759
|
+
no = false
|
|
760
|
+
status = case flag.to_sym
|
|
761
|
+
when :start
|
|
762
|
+
%w[created exited]
|
|
763
|
+
when :stop, :pause
|
|
764
|
+
no = true
|
|
765
|
+
%w[running restarting]
|
|
766
|
+
when :restart
|
|
767
|
+
no = true
|
|
768
|
+
%w[running paused exited]
|
|
769
|
+
when :unpause
|
|
770
|
+
%w[paused]
|
|
771
|
+
when :top, :stats, :watch
|
|
772
|
+
%w[running]
|
|
773
|
+
when :kill
|
|
774
|
+
no = true
|
|
775
|
+
%w[running paused restarting]
|
|
776
|
+
when :rm
|
|
777
|
+
no = true
|
|
778
|
+
%w[created exited dead]
|
|
779
|
+
else
|
|
780
|
+
[]
|
|
781
|
+
end
|
|
782
|
+
cmd = docker_output("#{from.to_s.split(':').first} ps -a",
|
|
783
|
+
*status.map { |s| quote_option('filter', "status=#{s}") })
|
|
784
|
+
[cmd, status, no]
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
def list_image(flag, cmd = docker_output('image ls -a'), hint: nil, from: nil, no: true)
|
|
699
788
|
pwd_set do
|
|
700
|
-
|
|
701
|
-
index = 0
|
|
789
|
+
index = 1
|
|
702
790
|
all = option('all', prefix: 'docker')
|
|
703
791
|
y = from == :'image:rm' && option('y', prefix: 'docker')
|
|
704
792
|
pat = /\b(?:#{dnsname(name)}|#{tagname(project)}|#{tagmain.split(':', 2).first})\b/
|
|
705
|
-
IO.popen(
|
|
793
|
+
IO.popen(cmd.temp('--format=json')).each do |line|
|
|
706
794
|
data = JSON.parse(line)
|
|
707
795
|
id = data['ID']
|
|
708
|
-
rt = [data['Repository'], data['Tag']].reject { |val| val == '<none>' }.join(':')
|
|
796
|
+
rt = [data['Repository'], data['Tag']].reject { |val| val.to_s.empty? || val == '<none>' }.join(':')
|
|
709
797
|
rt = nil if rt.empty?
|
|
710
798
|
aa = data['Names'] || (if rt && data['Repository']
|
|
711
799
|
dd = true
|
|
@@ -717,16 +805,16 @@ module Squared
|
|
|
717
805
|
next unless all || ee.match?(pat) || aa.match?(pat)
|
|
718
806
|
|
|
719
807
|
unless y
|
|
720
|
-
bb = index.
|
|
721
|
-
cc = bb.size
|
|
808
|
+
bb = index.to_s
|
|
809
|
+
cc = bb.size.succ
|
|
722
810
|
a = sub_style(ee, styles: theme[:inline])
|
|
723
|
-
b = "Execute #{sub_style(flag, styles: theme[:active])} on #{a
|
|
811
|
+
b = "Execute #{sub_style(flag, styles: theme[:active])} on #{a.subhint(ee == id ? nil : id)}"
|
|
724
812
|
e = time_format(time_since(data['CreatedAt']), pass: ['ms'])
|
|
725
813
|
f = sub_style(ARG[:BORDER][0], styles: theme[:inline])
|
|
726
|
-
g = ' ' *
|
|
814
|
+
g = ' ' * cc.succ
|
|
727
815
|
h = "#{sub_style(bb.rjust(cc), styles: theme[:current])} #{f} "
|
|
728
|
-
puts unless index ==
|
|
729
|
-
puts
|
|
816
|
+
puts unless index == 1
|
|
817
|
+
puts (h + sub_style(aa, styles: theme[:subject])).subhint("created #{e} ago")
|
|
730
818
|
cols = %w[Tag Status Ports]
|
|
731
819
|
cols << case flag
|
|
732
820
|
when :connect, :disconnect
|
|
@@ -741,89 +829,104 @@ module Squared
|
|
|
741
829
|
end
|
|
742
830
|
w = 9 + flag.to_s.size + 4 + ee.size
|
|
743
831
|
puts g + sub_style(ARG[:BORDER][6] + (ARG[:BORDER][1] * w), styles: theme[:inline])
|
|
744
|
-
found = true
|
|
745
832
|
index += 1
|
|
746
|
-
next unless confirm("#{h + b}?", no ? 'N' : 'Y'
|
|
833
|
+
next unless confirm("#{h + b}?", no ? 'N' : 'Y')
|
|
747
834
|
|
|
748
835
|
puts if printfirst?
|
|
749
836
|
end
|
|
750
837
|
yield id
|
|
751
838
|
end
|
|
752
|
-
|
|
839
|
+
list_empty(hint: hint || from) if index == 1 && !y
|
|
753
840
|
end
|
|
754
841
|
rescue StandardError => e
|
|
755
842
|
on_error e, from
|
|
756
843
|
end
|
|
757
844
|
|
|
845
|
+
def list_empty(subject: name, hint: nil, **kwargs)
|
|
846
|
+
hint = "status: #{hint.join(', ')}" if hint.is_a?(Array)
|
|
847
|
+
puts log_message(Logger::INFO, 'none detected', subject: subject, hint: hint, **kwargs)
|
|
848
|
+
end
|
|
849
|
+
|
|
758
850
|
def confirm_command(*args, title: nil, target: nil, as: nil)
|
|
759
851
|
return false unless title && target
|
|
760
852
|
|
|
761
853
|
puts unless printfirst?
|
|
762
854
|
t = title.to_s.split(':')
|
|
763
855
|
emphasize(args, title: message(t.first.upcase, *t.drop(1)), border: borderstyle, sub: [
|
|
764
|
-
|
|
765
|
-
|
|
856
|
+
opt_style(theme[:header], /\A(\w+(?: => \w+)+)(.*)\z/),
|
|
857
|
+
opt_style(theme[:caution], /\A(.+)\z/)
|
|
766
858
|
])
|
|
767
859
|
printsucc
|
|
768
860
|
a = t.last.capitalize
|
|
769
861
|
b = sub_style(target, styles: theme[:subject])
|
|
770
862
|
c = as && sub_style(as, styles: theme[:inline])
|
|
771
|
-
confirm
|
|
772
|
-
end
|
|
773
|
-
|
|
774
|
-
def choice_command(flag)
|
|
775
|
-
msg, cmd
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
863
|
+
confirm "#{a} #{b}#{c ? " as #{c}" : ''}?", 'N'
|
|
864
|
+
end
|
|
865
|
+
|
|
866
|
+
def choice_command(flag, *action)
|
|
867
|
+
msg, cmd = case flag
|
|
868
|
+
when :exec
|
|
869
|
+
['Choose a container', 'ps -a']
|
|
870
|
+
when :bake
|
|
871
|
+
['Choose a target', 'buildx bake --list=type=targets']
|
|
872
|
+
when :connect, :disconnect
|
|
873
|
+
['Choose a network', 'network ls']
|
|
874
|
+
when :service
|
|
875
|
+
['Choose a service',
|
|
876
|
+
'compose ps -a ' \
|
|
877
|
+
"--format='table {{.Service}}\t{{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Status}}\t{{.Ports}}'"]
|
|
878
|
+
else
|
|
879
|
+
['Choose an image',
|
|
880
|
+
'images -a ' \
|
|
881
|
+
"--format='table {{.ID}}\t{{.Repository}}\t{{.Tag}}\t{{.CreatedSince}}\t{{.Size}}'"]
|
|
882
|
+
end
|
|
785
883
|
lines = `#{docker_output(cmd)}`.lines
|
|
786
884
|
header = lines.shift
|
|
787
885
|
if lines.empty?
|
|
788
|
-
puts log_message(Logger::INFO, 'none found', subject: name,
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
886
|
+
puts log_message(Logger::INFO, 'none found', subject: name,
|
|
887
|
+
hint: "docker #{cmd.split(' ', 3)[0...2].join(' ')}")
|
|
888
|
+
return
|
|
889
|
+
end
|
|
890
|
+
puts " # #{header}"
|
|
891
|
+
multiple = false
|
|
892
|
+
parse = ->(val) { val.split(/\s+/)[0] }
|
|
893
|
+
ctx = flag.to_s
|
|
894
|
+
case flag
|
|
895
|
+
when :run, :exec
|
|
896
|
+
values = [['Options', flag == :run], ['Arguments', flag == :exec]]
|
|
897
|
+
when :rm, :bake
|
|
898
|
+
values = ['Options']
|
|
899
|
+
multiple = true
|
|
900
|
+
ctx = flag == :rm ? 'image rm' : "buildx bake -f #{shell_quote(dockerfile)}"
|
|
901
|
+
when :save
|
|
902
|
+
values = [['Output', true], 'Platform']
|
|
903
|
+
multiple = true
|
|
904
|
+
when :service
|
|
905
|
+
values = []
|
|
906
|
+
multiple = true
|
|
907
|
+
ctx = 'compose'
|
|
908
|
+
when :connect, :disconnect
|
|
909
|
+
values = ['Options', ['Container', true]]
|
|
910
|
+
ctx = "network #{flag}"
|
|
911
|
+
end
|
|
912
|
+
out, opts, args = choice_index(msg, lines, multiple: multiple, values: values)
|
|
913
|
+
cmd = docker_output(ctx, *action)
|
|
914
|
+
case flag
|
|
915
|
+
when :tag
|
|
916
|
+
args = tagjoin @registry, tag
|
|
917
|
+
when :save
|
|
918
|
+
opts = "#{opts}.tar" unless opts.end_with?('.tar')
|
|
919
|
+
cmd << quote_option('output', File.expand_path(opts))
|
|
920
|
+
if args
|
|
921
|
+
cmd << basic_option('platform', args)
|
|
922
|
+
args = nil
|
|
822
923
|
end
|
|
823
|
-
|
|
824
|
-
cmd <<
|
|
825
|
-
print_success if success?(run(cmd), ctx.start_with?(/(?:network|tag|save)/))
|
|
924
|
+
else
|
|
925
|
+
cmd << opts << '--'
|
|
826
926
|
end
|
|
927
|
+
cmd.merge(Array(out).map! { |val| parse.call(val) })
|
|
928
|
+
cmd << args
|
|
929
|
+
success?(run(cmd), ctx.start_with?(/(?:network|tag|save)/))
|
|
827
930
|
end
|
|
828
931
|
|
|
829
932
|
def filetype(val = dockerfile)
|
|
@@ -852,10 +955,9 @@ module Squared
|
|
|
852
955
|
|
|
853
956
|
def tagname(val)
|
|
854
957
|
val = val.split(':').map! { |s| charname(s.sub(/^\W+/, '')) }
|
|
855
|
-
val.join(':')
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
end
|
|
958
|
+
ret = val.join(':')
|
|
959
|
+
ret = val.first if val.size > 1 && ret.size > 128
|
|
960
|
+
ret[0..127]
|
|
859
961
|
end
|
|
860
962
|
|
|
861
963
|
def dnsname(val)
|