dash 4.0.8 → 4.1.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/lib/dash/build/progress_parser.rb +136 -0
- data/lib/dash/build/report.rb +104 -0
- data/lib/dash/build/step.rb +49 -0
- data/lib/dash/cli/app/boot.rb +20 -10
- data/lib/dash/cli/app.rb +2 -2
- data/lib/dash/cli/base.rb +117 -2
- data/lib/dash/cli/build.rb +61 -5
- data/lib/dash/cli/doctor/config_checks.rb +36 -1
- data/lib/dash/cli/doctor.rb +2 -1
- data/lib/dash/cli/main.rb +24 -7
- data/lib/dash/cli/prune.rb +5 -8
- data/lib/dash/cli/report.rb +97 -0
- data/lib/dash/cli/templates/sample_hooks/post-deploy.sample +5 -0
- data/lib/dash/commander.rb +10 -2
- data/lib/dash/commands/app.rb +18 -0
- data/lib/dash/commands/auditor.rb +10 -0
- data/lib/dash/commands/builder/base.rb +18 -0
- data/lib/dash/commands/builder.rb +1 -1
- data/lib/dash/configuration/docs/configuration.yml +6 -0
- data/lib/dash/configuration/docs/report.yml +39 -0
- data/lib/dash/configuration/report.rb +65 -0
- data/lib/dash/configuration.rb +2 -1
- data/lib/dash/dockerfile/analyzer.rb +66 -0
- data/lib/dash/dockerfile/context.rb +147 -0
- data/lib/dash/dockerfile/dockerignore.rb +29 -0
- data/lib/dash/dockerfile/document.rb +28 -0
- data/lib/dash/dockerfile/finding.rb +20 -0
- data/lib/dash/dockerfile/hadolint.rb +75 -0
- data/lib/dash/dockerfile/instruction.rb +58 -0
- data/lib/dash/dockerfile/parser.rb +199 -0
- data/lib/dash/dockerfile/rules/apt_hygiene.rb +35 -0
- data/lib/dash/dockerfile/rules/base.rb +44 -0
- data/lib/dash/dockerfile/rules/cache_busting_arg.rb +40 -0
- data/lib/dash/dockerfile/rules/cache_export_cost.rb +20 -0
- data/lib/dash/dockerfile/rules/context_size.rb +20 -0
- data/lib/dash/dockerfile/rules/copy_before_install.rb +34 -0
- data/lib/dash/dockerfile/rules/curl_pipe_shell.rb +16 -0
- data/lib/dash/dockerfile/rules/dockerignore_gaps.rb +36 -0
- data/lib/dash/dockerfile/rules/inline_env_blob.rb +23 -0
- data/lib/dash/dockerfile/rules/latest_base.rb +24 -0
- data/lib/dash/dockerfile/rules/missing_dockerignore.rb +11 -0
- data/lib/dash/dockerfile/rules/no_cache_mount.rb +26 -0
- data/lib/dash/dockerfile/rules/root_user.rb +12 -0
- data/lib/dash/dockerfile/rules/secret_in_build_arg.rb +30 -0
- data/lib/dash/dockerfile/rules/single_stage_build_deps.rb +19 -0
- data/lib/dash/dockerfile/rules/uncached_install.rb +20 -0
- data/lib/dash/dockerfile/stage.rb +65 -0
- data/lib/dash/otel_shipper.rb +5 -4
- data/lib/dash/output/otel_logger.rb +52 -0
- data/lib/dash/report/history.rb +94 -0
- data/lib/dash/report/trends.rb +129 -0
- data/lib/dash/report/writer.rb +142 -0
- data/lib/dash/report.rb +170 -0
- data/lib/dash/sshkit_with_ext.rb +62 -0
- data/lib/dash/timings.rb +163 -10
- data/lib/dash/utils.rb +7 -0
- data/lib/dash/version.rb +1 -1
- data/lib/dash.rb +4 -0
- metadata +36 -1
data/lib/dash/timings.rb
CHANGED
|
@@ -5,40 +5,193 @@
|
|
|
5
5
|
# Entries land in start order and carry a depth, so a parent phase (Boot) prints above
|
|
6
6
|
# the host entries it wraps even though the hosts finish first. Boot runs one thread per
|
|
7
7
|
# host, so every mutation is behind the mutex.
|
|
8
|
+
#
|
|
9
|
+
# Each entry also accounts for the commands issued while it was the current phase, which
|
|
10
|
+
# is what separates "the boot took 55s" from "the boot spent 41s of that in forty serial
|
|
11
|
+
# SSH round trips". Attribution is by thread-local: `phase` marks its entry as current for
|
|
12
|
+
# the duration of the block, and `lib/dash/sshkit_with_ext.rb` stamps every command and
|
|
13
|
+
# every SSH connect onto whatever entry is current on that thread. Nothing extra is
|
|
14
|
+
# executed — dash only measures the round trips it was already making.
|
|
8
15
|
class Dash::Timings
|
|
9
|
-
|
|
16
|
+
# The entry the current thread is inside. SSHKit's per-host threads inherit it from
|
|
17
|
+
# the thread that spawned them (see CompleteAll#execute and SSHKitDslRoles#on_roles),
|
|
18
|
+
# so a command issued on a boot thread lands on that host's row.
|
|
19
|
+
CURRENT_KEY = :dash_timing_entry
|
|
20
|
+
|
|
21
|
+
Entry = Struct.new(:name, :seconds, :detail, :depth, :parent, :commands, :command_seconds, :connect_seconds, :local)
|
|
22
|
+
|
|
23
|
+
# Read and written from lib/dash/sshkit_with_ext.rb, which hands the entry across the
|
|
24
|
+
# per-host and per-role threads SSHKit spawns. Class-level because the patches run
|
|
25
|
+
# without a commander in reach.
|
|
26
|
+
class << self
|
|
27
|
+
def current_entry
|
|
28
|
+
Thread.current[CURRENT_KEY]
|
|
29
|
+
end
|
|
10
30
|
|
|
11
|
-
|
|
12
|
-
|
|
31
|
+
def current_entry=(entry)
|
|
32
|
+
Thread.current[CURRENT_KEY] = entry
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Rebuilds a table from what #to_h exported, so `dash report` can print a saved
|
|
36
|
+
# deploy the way the deploy printed it. The entries come back parentless: their
|
|
37
|
+
# counters are already the subtree totals #to_h computed, and re-nesting them would
|
|
38
|
+
# roll those totals up a second time.
|
|
39
|
+
def from_h(phases)
|
|
40
|
+
new entries: Array(phases).map { |phase| entry_from(phase.transform_keys(&:to_sym)) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
def entry_from(phase)
|
|
45
|
+
Entry.new \
|
|
46
|
+
phase[:name], phase[:seconds].to_f, phase[:detail], phase[:depth].to_i, nil,
|
|
47
|
+
phase[:commands].to_i, phase[:command_seconds].to_f, phase[:connect_seconds].to_f,
|
|
48
|
+
phase[:local].nil? ? true : phase[:local]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def initialize(entries: [])
|
|
53
|
+
@entries = entries
|
|
13
54
|
@mutex = Mutex.new
|
|
14
55
|
end
|
|
15
56
|
|
|
16
57
|
# Times the block. The entry is yielded so the block can annotate it — a boot can say
|
|
17
58
|
# how long of its total was spent waiting for the container to become healthy.
|
|
18
59
|
def phase(name, depth: 0)
|
|
19
|
-
entry =
|
|
60
|
+
entry = new_entry(name, depth: depth)
|
|
20
61
|
@mutex.synchronize { @entries << entry }
|
|
21
62
|
started = clock
|
|
22
63
|
|
|
23
|
-
|
|
64
|
+
previous = self.class.current_entry
|
|
65
|
+
self.class.current_entry = entry
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
yield entry
|
|
69
|
+
ensure
|
|
70
|
+
self.class.current_entry = previous
|
|
71
|
+
end
|
|
24
72
|
ensure
|
|
25
73
|
entry.seconds = clock - started
|
|
26
74
|
end
|
|
27
75
|
|
|
76
|
+
# For time that was measured elsewhere — the gem load and config parse that happened
|
|
77
|
+
# before there was a Timings to record into.
|
|
78
|
+
def record(name, seconds, depth: 0, detail: nil)
|
|
79
|
+
new_entry(name, depth: depth).tap do |entry|
|
|
80
|
+
entry.seconds = seconds
|
|
81
|
+
entry.detail = detail
|
|
82
|
+
@mutex.synchronize { @entries << entry }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def current
|
|
87
|
+
self.class.current_entry
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def attribute_command(seconds, local:)
|
|
91
|
+
entry = current or return
|
|
92
|
+
|
|
93
|
+
@mutex.synchronize do
|
|
94
|
+
entry.commands += 1
|
|
95
|
+
entry.command_seconds += seconds
|
|
96
|
+
# A phase that issued even one remote command is reported as ssh: the local
|
|
97
|
+
# label is only honest when nothing left the machine.
|
|
98
|
+
entry.local &&= local
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def attribute_connect(seconds)
|
|
103
|
+
entry = current or return
|
|
104
|
+
|
|
105
|
+
@mutex.synchronize { entry.connect_seconds += seconds }
|
|
106
|
+
end
|
|
107
|
+
|
|
28
108
|
def any?
|
|
29
109
|
@mutex.synchronize { @entries.any? }
|
|
30
110
|
end
|
|
31
111
|
|
|
112
|
+
# Where an entry's row sits in #lines, so Dash::Report can splice its build rows in
|
|
113
|
+
# under the phase they belong to. Identity, not equality: two phases of the same name
|
|
114
|
+
# and duration are equal as Structs but are not the same row.
|
|
115
|
+
# The row at a position, so a consumer that saved an index (Dash::Report, reattaching
|
|
116
|
+
# its build rows to the phase they hung under) can find the entry again.
|
|
117
|
+
def entry_at(index)
|
|
118
|
+
@mutex.synchronize { @entries[index] } if index
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def index_of(entry)
|
|
122
|
+
@mutex.synchronize { @entries.index { |candidate| candidate.equal?(entry) } }
|
|
123
|
+
end
|
|
124
|
+
|
|
32
125
|
def lines
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
126
|
+
with_totals do |entry, totals|
|
|
127
|
+
line = format(" %s%-36s %6.1fs", " " * entry.depth, entry.name, entry.seconds.to_f)
|
|
128
|
+
line += format(" %3d %-5s %5.1fs", totals[:commands], totals[:local] ? "local" : "ssh", totals[:command_seconds]) if totals[:commands] > 0
|
|
129
|
+
entry.detail ? "#{line} (#{entry.detail})" : line
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# A top-level phase's wall time by name, for consumers that know a phase by what it is
|
|
134
|
+
# called rather than by where it sits — the post-deploy hook's build and boot runtimes.
|
|
135
|
+
# Depth-0 only: a per-host row inside Boot is named after the host, but a role could be
|
|
136
|
+
# named "Boot" and must not be mistaken for the phase.
|
|
137
|
+
def seconds_for(name)
|
|
138
|
+
@mutex.synchronize { @entries.find { |entry| entry.name == name && entry.depth.to_i.zero? }&.seconds }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# The command counts here are subtree totals, matching what the table prints — phases
|
|
142
|
+
# nest, so a consumer must not sum them across depths.
|
|
143
|
+
def to_h
|
|
144
|
+
with_totals do |entry, totals|
|
|
145
|
+
{
|
|
146
|
+
name: entry.name,
|
|
147
|
+
depth: entry.depth,
|
|
148
|
+
seconds: entry.seconds.to_f,
|
|
149
|
+
detail: entry.detail,
|
|
150
|
+
commands: totals[:commands],
|
|
151
|
+
command_seconds: totals[:command_seconds],
|
|
152
|
+
connect_seconds: totals[:connect_seconds],
|
|
153
|
+
local: totals[:local]
|
|
154
|
+
}
|
|
38
155
|
end
|
|
39
156
|
end
|
|
40
157
|
|
|
41
158
|
private
|
|
159
|
+
def new_entry(name, depth:)
|
|
160
|
+
Entry.new(name, nil, nil, depth, current, 0, 0.0, 0.0, true)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def with_totals
|
|
164
|
+
@mutex.synchronize do
|
|
165
|
+
totals = subtree_totals
|
|
166
|
+
@entries.map { |entry| yield entry, totals[entry.object_id] }
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# A parent phase issues few commands of its own — Boot opens one thread per host and
|
|
171
|
+
# then waits — so its own counters say nothing. Roll each entry's counters up through
|
|
172
|
+
# its ancestors at render time rather than at record time, so the host rows keep
|
|
173
|
+
# their own numbers while the parent shows what the phase cost in total.
|
|
174
|
+
def subtree_totals
|
|
175
|
+
totals = @entries.to_h { |entry| [ entry.object_id, { commands: 0, command_seconds: 0.0, connect_seconds: 0.0, local: true } ] }
|
|
176
|
+
|
|
177
|
+
@entries.each do |entry|
|
|
178
|
+
node = entry
|
|
179
|
+
|
|
180
|
+
while node
|
|
181
|
+
if (total = totals[node.object_id])
|
|
182
|
+
total[:commands] += entry.commands
|
|
183
|
+
total[:command_seconds] += entry.command_seconds
|
|
184
|
+
total[:connect_seconds] += entry.connect_seconds
|
|
185
|
+
total[:local] &&= entry.local if entry.commands > 0
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
node = node.parent
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
totals
|
|
193
|
+
end
|
|
194
|
+
|
|
42
195
|
def clock
|
|
43
196
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
44
197
|
end
|
data/lib/dash/utils.rb
CHANGED
|
@@ -134,6 +134,13 @@ module Dash::Utils
|
|
|
134
134
|
end
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
+
# buildx reports decimal units, so dash does too — an operator comparing a report row
|
|
138
|
+
# with what buildx printed should see the same number.
|
|
139
|
+
def human_bytes(bytes)
|
|
140
|
+
divisor, unit = [ [ 1_000_000_000, "GB" ], [ 1_000_000, "MB" ], [ 1_000, "kB" ] ].find { |size, _| bytes >= size }
|
|
141
|
+
divisor ? format("%.1f%s", bytes.to_f / divisor, unit) : "#{bytes}B"
|
|
142
|
+
end
|
|
143
|
+
|
|
137
144
|
def older_version?(version, other_version)
|
|
138
145
|
Gem::Version.new(version.delete_prefix("v")) < Gem::Version.new(other_version.delete_prefix("v"))
|
|
139
146
|
end
|
data/lib/dash/version.rb
CHANGED
data/lib/dash.rb
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
module Dash
|
|
2
2
|
class ConfigurationError < StandardError; end
|
|
3
|
+
|
|
4
|
+
# Stamped before the first require so the Startup row in the deploy report covers what
|
|
5
|
+
# it actually costs to get here: gem load, Zeitwerk, and everything bin/dash pulls in.
|
|
6
|
+
PROCESS_STARTED_AT = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
3
7
|
end
|
|
4
8
|
|
|
5
9
|
require "active_support"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 4.0
|
|
4
|
+
version: 4.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mikael Henriksson
|
|
@@ -177,6 +177,9 @@ files:
|
|
|
177
177
|
- README.md
|
|
178
178
|
- bin/dash
|
|
179
179
|
- lib/dash.rb
|
|
180
|
+
- lib/dash/build/progress_parser.rb
|
|
181
|
+
- lib/dash/build/report.rb
|
|
182
|
+
- lib/dash/build/step.rb
|
|
180
183
|
- lib/dash/cli.rb
|
|
181
184
|
- lib/dash/cli/accessory.rb
|
|
182
185
|
- lib/dash/cli/alias/command.rb
|
|
@@ -209,6 +212,7 @@ files:
|
|
|
209
212
|
- lib/dash/cli/proxy/reboot.rb
|
|
210
213
|
- lib/dash/cli/prune.rb
|
|
211
214
|
- lib/dash/cli/registry.rb
|
|
215
|
+
- lib/dash/cli/report.rb
|
|
212
216
|
- lib/dash/cli/secrets.rb
|
|
213
217
|
- lib/dash/cli/server.rb
|
|
214
218
|
- lib/dash/cli/templates/deploy.yml
|
|
@@ -273,6 +277,7 @@ files:
|
|
|
273
277
|
- lib/dash/configuration/docs/output.yml
|
|
274
278
|
- lib/dash/configuration/docs/proxy.yml
|
|
275
279
|
- lib/dash/configuration/docs/registry.yml
|
|
280
|
+
- lib/dash/configuration/docs/report.yml
|
|
276
281
|
- lib/dash/configuration/docs/role.yml
|
|
277
282
|
- lib/dash/configuration/docs/servers.yml
|
|
278
283
|
- lib/dash/configuration/docs/ssh.yml
|
|
@@ -287,6 +292,7 @@ files:
|
|
|
287
292
|
- lib/dash/configuration/proxy/boot.rb
|
|
288
293
|
- lib/dash/configuration/proxy/run.rb
|
|
289
294
|
- lib/dash/configuration/registry.rb
|
|
295
|
+
- lib/dash/configuration/report.rb
|
|
290
296
|
- lib/dash/configuration/role.rb
|
|
291
297
|
- lib/dash/configuration/role/healthcheck.rb
|
|
292
298
|
- lib/dash/configuration/servers.rb
|
|
@@ -305,6 +311,31 @@ files:
|
|
|
305
311
|
- lib/dash/configuration/validator/servers.rb
|
|
306
312
|
- lib/dash/configuration/volume.rb
|
|
307
313
|
- lib/dash/docker.rb
|
|
314
|
+
- lib/dash/dockerfile/analyzer.rb
|
|
315
|
+
- lib/dash/dockerfile/context.rb
|
|
316
|
+
- lib/dash/dockerfile/dockerignore.rb
|
|
317
|
+
- lib/dash/dockerfile/document.rb
|
|
318
|
+
- lib/dash/dockerfile/finding.rb
|
|
319
|
+
- lib/dash/dockerfile/hadolint.rb
|
|
320
|
+
- lib/dash/dockerfile/instruction.rb
|
|
321
|
+
- lib/dash/dockerfile/parser.rb
|
|
322
|
+
- lib/dash/dockerfile/rules/apt_hygiene.rb
|
|
323
|
+
- lib/dash/dockerfile/rules/base.rb
|
|
324
|
+
- lib/dash/dockerfile/rules/cache_busting_arg.rb
|
|
325
|
+
- lib/dash/dockerfile/rules/cache_export_cost.rb
|
|
326
|
+
- lib/dash/dockerfile/rules/context_size.rb
|
|
327
|
+
- lib/dash/dockerfile/rules/copy_before_install.rb
|
|
328
|
+
- lib/dash/dockerfile/rules/curl_pipe_shell.rb
|
|
329
|
+
- lib/dash/dockerfile/rules/dockerignore_gaps.rb
|
|
330
|
+
- lib/dash/dockerfile/rules/inline_env_blob.rb
|
|
331
|
+
- lib/dash/dockerfile/rules/latest_base.rb
|
|
332
|
+
- lib/dash/dockerfile/rules/missing_dockerignore.rb
|
|
333
|
+
- lib/dash/dockerfile/rules/no_cache_mount.rb
|
|
334
|
+
- lib/dash/dockerfile/rules/root_user.rb
|
|
335
|
+
- lib/dash/dockerfile/rules/secret_in_build_arg.rb
|
|
336
|
+
- lib/dash/dockerfile/rules/single_stage_build_deps.rb
|
|
337
|
+
- lib/dash/dockerfile/rules/uncached_install.rb
|
|
338
|
+
- lib/dash/dockerfile/stage.rb
|
|
308
339
|
- lib/dash/env_file.rb
|
|
309
340
|
- lib/dash/git.rb
|
|
310
341
|
- lib/dash/otel_shipper.rb
|
|
@@ -313,6 +344,10 @@ files:
|
|
|
313
344
|
- lib/dash/output/formatter.rb
|
|
314
345
|
- lib/dash/output/otel_logger.rb
|
|
315
346
|
- lib/dash/project_directory.rb
|
|
347
|
+
- lib/dash/report.rb
|
|
348
|
+
- lib/dash/report/history.rb
|
|
349
|
+
- lib/dash/report/trends.rb
|
|
350
|
+
- lib/dash/report/writer.rb
|
|
316
351
|
- lib/dash/secrets.rb
|
|
317
352
|
- lib/dash/secrets/adapters.rb
|
|
318
353
|
- lib/dash/secrets/adapters/aws_secrets_manager.rb
|