kamal-backup 0.3.0.beta5 → 0.3.0.beta6
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/kamal_backup/cli.rb +22 -10
- data/lib/kamal_backup/command.rb +181 -5
- data/lib/kamal_backup/kamal_bridge.rb +14 -6
- data/lib/kamal_backup/restic.rb +31 -12
- data/lib/kamal_backup/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68d3201dc26c940938479dc76d1c0e17b206676ae69610d736a69879a78de21c
|
|
4
|
+
data.tar.gz: dd58ee38da83d4652515b108869e5fe441a25d80697b7db3ea1c606240a44766
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37ca5f6463fe6eb76c2f6994a783e2180f3eecff24e834550743c1f9130e6ad3019ce1e296dc44ac523a36c065f1e05ac00aaaec7c7224254daea0c2d683d7a0
|
|
7
|
+
data.tar.gz: '0985cd5e2eb8a52db5dcc4265271eddde1513c8c8db80d0aec21ee3ca1be0bd42e2adcd60ffeb942aa7bb2d926cbad69f5d89051485ce64145f3f5c821abe3bb'
|
data/lib/kamal_backup/cli.rb
CHANGED
|
@@ -20,11 +20,17 @@ module KamalBackup
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def direct_app
|
|
23
|
-
@direct_app ||= App.new(
|
|
23
|
+
@direct_app ||= App.new(
|
|
24
|
+
config: Config.new(env: command_env),
|
|
25
|
+
redactor: redactor
|
|
26
|
+
)
|
|
24
27
|
end
|
|
25
28
|
|
|
26
|
-
def
|
|
27
|
-
@
|
|
29
|
+
def local_restore_app
|
|
30
|
+
@local_restore_app ||= App.new(
|
|
31
|
+
config: local_command_config,
|
|
32
|
+
redactor: redactor
|
|
33
|
+
)
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
def local_preferences
|
|
@@ -65,7 +71,9 @@ module KamalBackup
|
|
|
65
71
|
redactor: redactor,
|
|
66
72
|
config_file: options[:config_file],
|
|
67
73
|
destination: options[:destination],
|
|
68
|
-
env: command_env
|
|
74
|
+
env: command_env,
|
|
75
|
+
stdout: $stdout,
|
|
76
|
+
stderr: $stderr
|
|
69
77
|
)
|
|
70
78
|
end
|
|
71
79
|
|
|
@@ -94,9 +102,11 @@ module KamalBackup
|
|
|
94
102
|
|
|
95
103
|
result = bridge.execute_on_accessory(
|
|
96
104
|
accessory_name: accessory_name,
|
|
97
|
-
command: Shellwords.join(argv)
|
|
105
|
+
command: Shellwords.join(argv),
|
|
106
|
+
stream: true
|
|
98
107
|
)
|
|
99
|
-
print(result.stdout)
|
|
108
|
+
print(result.stdout) unless result.streamed
|
|
109
|
+
$stderr.print(result.stderr) if !result.streamed && !result.stderr.empty?
|
|
100
110
|
result
|
|
101
111
|
end
|
|
102
112
|
|
|
@@ -276,7 +286,7 @@ module KamalBackup
|
|
|
276
286
|
desc "local [SNAPSHOT]", "Restore the backup into the local database and Active Storage path"
|
|
277
287
|
def local(snapshot = "latest")
|
|
278
288
|
confirm!("Restore #{snapshot} into the local database and Active Storage path? This will overwrite local data.")
|
|
279
|
-
puts(JSON.pretty_generate(
|
|
289
|
+
puts(JSON.pretty_generate(local_restore_app.restore_to_local_machine(snapshot)))
|
|
280
290
|
end
|
|
281
291
|
|
|
282
292
|
method_option :"confirm-production-restore", type: :boolean, default: false, desc: "Confirm production restore without interactive prompts"
|
|
@@ -301,9 +311,9 @@ module KamalBackup
|
|
|
301
311
|
desc "local [SNAPSHOT]", "Run a restore drill on the local machine"
|
|
302
312
|
def local(snapshot = "latest")
|
|
303
313
|
confirm!("Run a local restore drill for #{snapshot}? This will overwrite local data.")
|
|
304
|
-
result =
|
|
314
|
+
result = local_restore_app.drill_on_local_machine(snapshot, check_command: options[:check])
|
|
305
315
|
puts(JSON.pretty_generate(result))
|
|
306
|
-
exit(1) if
|
|
316
|
+
exit(1) if local_restore_app.drill_failed?(result)
|
|
307
317
|
end
|
|
308
318
|
|
|
309
319
|
method_option :database, type: :string, desc: "Scratch database name for PostgreSQL or MySQL"
|
|
@@ -389,7 +399,9 @@ module KamalBackup
|
|
|
389
399
|
|
|
390
400
|
def self.start(argv = ARGV, env: ENV)
|
|
391
401
|
self.command_env = env
|
|
392
|
-
|
|
402
|
+
Command.with_output(CommandOutput.new(io: $stderr)) do
|
|
403
|
+
super(normalize_global_options(argv))
|
|
404
|
+
end
|
|
393
405
|
rescue Error => e
|
|
394
406
|
warn("kamal-backup: #{Redactor.new(env: env).redact_string(e.message)}")
|
|
395
407
|
exit(1)
|
data/lib/kamal_backup/command.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
require "open3"
|
|
2
|
+
require "securerandom"
|
|
3
|
+
require "shellwords"
|
|
2
4
|
require_relative "errors"
|
|
3
5
|
|
|
4
6
|
module KamalBackup
|
|
@@ -18,14 +20,109 @@ module KamalBackup
|
|
|
18
20
|
|
|
19
21
|
def display(redactor)
|
|
20
22
|
env_prefix = env.keys.sort.map { |key| "#{key}=#{redactor.redact_value(key, env[key])}" }
|
|
21
|
-
redactor.redact_string((env_prefix + argv).join(" "))
|
|
23
|
+
redactor.redact_string((env_prefix + [argv.shelljoin]).join(" "))
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
|
|
25
|
-
CommandResult = Struct.new(:stdout, :stderr, :status, keyword_init: true)
|
|
27
|
+
CommandResult = Struct.new(:stdout, :stderr, :status, :streamed, keyword_init: true)
|
|
28
|
+
|
|
29
|
+
class CommandOutput
|
|
30
|
+
def initialize(io: $stdout)
|
|
31
|
+
@io = io
|
|
32
|
+
@mutex = Mutex.new
|
|
33
|
+
@buffers = {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def info(message, redactor:)
|
|
37
|
+
write_line(" INFO #{redactor.redact_string(message)}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def command_start(spec, redactor:)
|
|
41
|
+
id = SecureRandom.hex(4)
|
|
42
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
43
|
+
display = spec.display(redactor)
|
|
44
|
+
|
|
45
|
+
write_line(" INFO [#{id}] Running #{display} locally")
|
|
46
|
+
write_line(" DEBUG [#{id}] Command: #{display}")
|
|
47
|
+
|
|
48
|
+
{ id: id, started_at: started_at, redactor: redactor }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def command_output(context, _stream, data, redactor:)
|
|
52
|
+
raw = data.to_s
|
|
53
|
+
return if raw.empty?
|
|
54
|
+
|
|
55
|
+
synchronize do
|
|
56
|
+
key = [context.fetch(:id), _stream]
|
|
57
|
+
@buffers[key] = "#{@buffers[key]}#{raw}"
|
|
58
|
+
flush_complete_output_lines(context, key, redactor: redactor)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def command_exit(context, status)
|
|
63
|
+
runtime = Process.clock_gettime(Process::CLOCK_MONOTONIC) - context.fetch(:started_at)
|
|
64
|
+
result = status.to_i.zero? ? "successful" : "failed"
|
|
65
|
+
|
|
66
|
+
synchronize do
|
|
67
|
+
flush_output_buffers(context)
|
|
68
|
+
@io.puts(" INFO [#{context.fetch(:id)}] Finished in #{format("%.3f seconds", runtime)} with exit status #{status} (#{result}).")
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
def write_line(message)
|
|
74
|
+
synchronize { @io.puts(message) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def synchronize(&block)
|
|
78
|
+
@mutex.synchronize(&block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def flush_complete_output_lines(context, key, redactor:)
|
|
82
|
+
buffer = @buffers.fetch(key)
|
|
83
|
+
output = +""
|
|
84
|
+
|
|
85
|
+
while (index = buffer.index("\n"))
|
|
86
|
+
output << buffer.slice!(0..index)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
@buffers[key] = buffer
|
|
90
|
+
write_output(context, output, redactor: redactor) unless output.empty?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def flush_output_buffers(context)
|
|
94
|
+
id = context.fetch(:id)
|
|
95
|
+
keys = @buffers.keys.select { |key_id, _stream| key_id == id }
|
|
96
|
+
|
|
97
|
+
keys.each do |key|
|
|
98
|
+
output = @buffers.delete(key)
|
|
99
|
+
next if output.to_s.empty?
|
|
100
|
+
|
|
101
|
+
write_output(context, output, redactor: context.fetch(:redactor))
|
|
102
|
+
@io.puts unless output.end_with?("\n")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def write_output(context, output, redactor:)
|
|
107
|
+
redactor.redact_string(output).each_line do |line|
|
|
108
|
+
@io.print(" DEBUG [#{context.fetch(:id)}] \t#{line}")
|
|
109
|
+
end
|
|
110
|
+
@io.flush if @io.respond_to?(:flush)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
26
113
|
|
|
27
114
|
class Command
|
|
28
115
|
class << self
|
|
116
|
+
attr_accessor :output
|
|
117
|
+
|
|
118
|
+
def with_output(output)
|
|
119
|
+
previous_output = self.output
|
|
120
|
+
self.output = output
|
|
121
|
+
yield
|
|
122
|
+
ensure
|
|
123
|
+
self.output = previous_output
|
|
124
|
+
end
|
|
125
|
+
|
|
29
126
|
def available?(name)
|
|
30
127
|
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |dir|
|
|
31
128
|
path = File.join(dir, name)
|
|
@@ -33,9 +130,23 @@ module KamalBackup
|
|
|
33
130
|
end
|
|
34
131
|
end
|
|
35
132
|
|
|
36
|
-
def capture(spec, input: nil, redactor:)
|
|
37
|
-
|
|
38
|
-
|
|
133
|
+
def capture(spec, input: nil, redactor:, log: true, log_output: true, tee_stdout: nil, tee_stderr: nil)
|
|
134
|
+
output = log ? self.output : nil
|
|
135
|
+
return capture_quietly(spec, input: input, redactor: redactor) unless output || tee_stdout || tee_stderr
|
|
136
|
+
|
|
137
|
+
context = output&.command_start(spec, redactor: redactor)
|
|
138
|
+
stdout, stderr, status = popen_capture(
|
|
139
|
+
spec,
|
|
140
|
+
input: input,
|
|
141
|
+
redactor: redactor,
|
|
142
|
+
output: output,
|
|
143
|
+
context: context,
|
|
144
|
+
log_output: log_output,
|
|
145
|
+
tee_stdout: tee_stdout,
|
|
146
|
+
tee_stderr: tee_stderr
|
|
147
|
+
)
|
|
148
|
+
output&.command_exit(context, status.exitstatus)
|
|
149
|
+
result = CommandResult.new(stdout: stdout, stderr: stderr, status: status.exitstatus, streamed: !!(tee_stdout || tee_stderr))
|
|
39
150
|
|
|
40
151
|
if status.success?
|
|
41
152
|
result
|
|
@@ -46,7 +157,72 @@ module KamalBackup
|
|
|
46
157
|
raise command_not_found(spec, e)
|
|
47
158
|
end
|
|
48
159
|
|
|
160
|
+
def collect_stream(io, command_output: self.output, context: nil, stream: :stdout, log_output: true, tee_io: nil, redactor: nil)
|
|
161
|
+
captured_output = +""
|
|
162
|
+
tee_buffer = +""
|
|
163
|
+
|
|
164
|
+
loop do
|
|
165
|
+
chunk = io.readpartial(16 * 1024)
|
|
166
|
+
captured_output << chunk
|
|
167
|
+
command_output&.command_output(context, stream, chunk, redactor: redactor) if log_output && context
|
|
168
|
+
tee_buffer = tee_stream(tee_io, redactor, tee_buffer, chunk) if tee_io
|
|
169
|
+
rescue EOFError
|
|
170
|
+
flush_tee_stream(tee_io, redactor, tee_buffer) if tee_io
|
|
171
|
+
break
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
captured_output
|
|
175
|
+
end
|
|
176
|
+
|
|
49
177
|
private
|
|
178
|
+
def tee_stream(io, redactor, buffer, chunk)
|
|
179
|
+
buffer << chunk
|
|
180
|
+
|
|
181
|
+
while (index = buffer.index("\n"))
|
|
182
|
+
io.print(redactor.redact_string(buffer.slice!(0..index)))
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
io.flush if io.respond_to?(:flush)
|
|
186
|
+
buffer
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def flush_tee_stream(io, redactor, buffer)
|
|
190
|
+
return if buffer.empty?
|
|
191
|
+
|
|
192
|
+
io.print(redactor.redact_string(buffer))
|
|
193
|
+
io.flush if io.respond_to?(:flush)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def capture_quietly(spec, input:, redactor:)
|
|
197
|
+
stdout, stderr, status = Open3.capture3(spec.env, *spec.argv, stdin_data: input)
|
|
198
|
+
result = CommandResult.new(stdout: stdout, stderr: stderr, status: status.exitstatus, streamed: false)
|
|
199
|
+
|
|
200
|
+
if status.success?
|
|
201
|
+
result
|
|
202
|
+
else
|
|
203
|
+
raise command_failure(spec, status.exitstatus, stdout, stderr, redactor)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def popen_capture(spec, input:, redactor:, output:, context:, log_output:, tee_stdout:, tee_stderr:)
|
|
208
|
+
Open3.popen3(spec.env, *spec.argv) do |stdin, stdout, stderr, wait_thread|
|
|
209
|
+
stdin_writer = Thread.new do
|
|
210
|
+
stdin.write(input) if input
|
|
211
|
+
ensure
|
|
212
|
+
stdin.close unless stdin.closed?
|
|
213
|
+
end
|
|
214
|
+
stdout_reader = Thread.new do
|
|
215
|
+
collect_stream(stdout, command_output: output, context: context, stream: :stdout, log_output: log_output, tee_io: tee_stdout, redactor: redactor)
|
|
216
|
+
end
|
|
217
|
+
stderr_reader = Thread.new do
|
|
218
|
+
collect_stream(stderr, command_output: output, context: context, stream: :stderr, log_output: log_output, tee_io: tee_stderr, redactor: redactor)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
stdin_writer.join
|
|
222
|
+
[stdout_reader.value, stderr_reader.value, wait_thread.value]
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
50
226
|
def command_failure(spec, status, stdout, stderr, redactor)
|
|
51
227
|
CommandError.new(
|
|
52
228
|
"command failed (#{status}): #{spec.display(redactor)}\n#{redactor.redact_string(stderr)}",
|
|
@@ -7,12 +7,14 @@ module KamalBackup
|
|
|
7
7
|
DEFAULT_CONFIG_FILE = "config/deploy.yml"
|
|
8
8
|
VERSION_LINE_PATTERN = /\A\d+(?:\.\d+)+(?:[-.][A-Za-z0-9]+)*\z/
|
|
9
9
|
|
|
10
|
-
def initialize(redactor:, config_file: nil, destination: nil, env: ENV, cwd: Dir.pwd)
|
|
10
|
+
def initialize(redactor:, config_file: nil, destination: nil, env: ENV, cwd: Dir.pwd, stdout: $stdout, stderr: $stderr)
|
|
11
11
|
@redactor = redactor
|
|
12
12
|
@config_file = config_file
|
|
13
13
|
@destination = destination
|
|
14
14
|
@env = env
|
|
15
15
|
@cwd = cwd
|
|
16
|
+
@stdout = stdout
|
|
17
|
+
@stderr = stderr
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
def accessory_name(preferred: nil)
|
|
@@ -50,8 +52,8 @@ module KamalBackup
|
|
|
50
52
|
accessory_secret_placeholders(accessory_name).merge(accessory_clear_env(accessory_name))
|
|
51
53
|
end
|
|
52
54
|
|
|
53
|
-
def execute_on_accessory(accessory_name:, command:)
|
|
54
|
-
capture_kamal(kamal_exec_argv(accessory_name, command))
|
|
55
|
+
def execute_on_accessory(accessory_name:, command:, stream: false)
|
|
56
|
+
capture_kamal(kamal_exec_argv(accessory_name, command), stream: stream)
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def remote_version(accessory_name:)
|
|
@@ -212,13 +214,19 @@ module KamalBackup
|
|
|
212
214
|
argv
|
|
213
215
|
end
|
|
214
216
|
|
|
215
|
-
def capture_kamal(argv)
|
|
217
|
+
def capture_kamal(argv, stream: false)
|
|
216
218
|
spec = CommandSpec.new(argv: argv)
|
|
219
|
+
options = {
|
|
220
|
+
redactor: @redactor,
|
|
221
|
+
log_output: false,
|
|
222
|
+
tee_stdout: stream ? @stdout : nil,
|
|
223
|
+
tee_stderr: stream ? @stderr : nil
|
|
224
|
+
}
|
|
217
225
|
|
|
218
226
|
if defined?(Bundler)
|
|
219
|
-
Bundler.with_unbundled_env { Command.capture(spec,
|
|
227
|
+
Bundler.with_unbundled_env { Command.capture(spec, **options) }
|
|
220
228
|
else
|
|
221
|
-
Command.capture(spec,
|
|
229
|
+
Command.capture(spec, **options)
|
|
222
230
|
end
|
|
223
231
|
end
|
|
224
232
|
|
data/lib/kamal_backup/restic.rb
CHANGED
|
@@ -16,7 +16,7 @@ module KamalBackup
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def ensure_repository
|
|
19
|
-
run(%w[snapshots --json])
|
|
19
|
+
run(%w[snapshots --json], log_output: false)
|
|
20
20
|
rescue CommandError => e
|
|
21
21
|
if config.restic_init_if_missing?
|
|
22
22
|
log("restic repository not ready, running restic init")
|
|
@@ -43,14 +43,17 @@ module KamalBackup
|
|
|
43
43
|
log("backing up file content as #{filename}")
|
|
44
44
|
|
|
45
45
|
File.open(path, "rb") do |file|
|
|
46
|
+
output = Command.output
|
|
47
|
+
context = output&.command_start(command, redactor: redactor)
|
|
46
48
|
Open3.popen3(command.env, *command.argv) do |stdin, stdout, stderr, wait_thread|
|
|
47
|
-
stdout_reader = Thread.new { stdout
|
|
48
|
-
stderr_reader = Thread.new { stderr
|
|
49
|
+
stdout_reader = Thread.new { Command.collect_stream(stdout, command_output: output, context: context, stream: :stdout, redactor: redactor) }
|
|
50
|
+
stderr_reader = Thread.new { Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr, redactor: redactor) }
|
|
49
51
|
IO.copy_stream(file, stdin)
|
|
50
52
|
stdin.close
|
|
51
53
|
out = stdout_reader.value
|
|
52
54
|
err = stderr_reader.value
|
|
53
55
|
status = wait_thread.value
|
|
56
|
+
output&.command_exit(context, status.exitstatus)
|
|
54
57
|
raise_command_error(command, status, out, err) unless status.success?
|
|
55
58
|
|
|
56
59
|
CommandResult.new(stdout: out, stderr: err, status: status.exitstatus)
|
|
@@ -99,7 +102,7 @@ module KamalBackup
|
|
|
99
102
|
end
|
|
100
103
|
|
|
101
104
|
def snapshots_json(tags: common_tags)
|
|
102
|
-
output = run(["snapshots", "--json"] + filter_tag_args(tags)).stdout
|
|
105
|
+
output = run(["snapshots", "--json"] + filter_tag_args(tags), log_output: false).stdout
|
|
103
106
|
snapshots = JSON.parse(output)
|
|
104
107
|
required_tags = tags.compact
|
|
105
108
|
snapshots.select do |snapshot|
|
|
@@ -116,7 +119,7 @@ module KamalBackup
|
|
|
116
119
|
end
|
|
117
120
|
|
|
118
121
|
def ls_json(snapshot)
|
|
119
|
-
output = run(["ls", "--json", snapshot]).stdout
|
|
122
|
+
output = run(["ls", "--json", snapshot], log_output: false).stdout
|
|
120
123
|
output.lines.filter_map do |line|
|
|
121
124
|
JSON.parse(line)
|
|
122
125
|
rescue JSON::ParserError
|
|
@@ -153,12 +156,15 @@ module KamalBackup
|
|
|
153
156
|
FileUtils.mkdir_p(File.dirname(target_path))
|
|
154
157
|
temp_path = "#{target_path}.kamal-backup-#{$$}.tmp"
|
|
155
158
|
|
|
159
|
+
output = Command.output
|
|
160
|
+
context = output&.command_start(command, redactor: redactor)
|
|
156
161
|
Open3.popen3(command.env, *command.argv) do |stdin, stdout, stderr, wait_thread|
|
|
157
162
|
stdin.close
|
|
158
|
-
stderr_reader = Thread.new { stderr
|
|
163
|
+
stderr_reader = Thread.new { Command.collect_stream(stderr, command_output: output, context: context, stream: :stderr, redactor: redactor) }
|
|
159
164
|
File.open(temp_path, "wb") { |file| IO.copy_stream(stdout, file) }
|
|
160
165
|
err = stderr_reader.value
|
|
161
166
|
status = wait_thread.value
|
|
167
|
+
output&.command_exit(context, status.exitstatus)
|
|
162
168
|
raise_command_error(command, status, "", err) unless status.success?
|
|
163
169
|
end
|
|
164
170
|
File.rename(temp_path, target_path)
|
|
@@ -176,8 +182,12 @@ module KamalBackup
|
|
|
176
182
|
run(["restore", snapshot, "--target", target])
|
|
177
183
|
end
|
|
178
184
|
|
|
179
|
-
def run(args)
|
|
180
|
-
Command.capture(
|
|
185
|
+
def run(args, log_output: true)
|
|
186
|
+
Command.capture(
|
|
187
|
+
CommandSpec.new(argv: ["restic"] + args, env: restic_env),
|
|
188
|
+
redactor: redactor,
|
|
189
|
+
log_output: log_output
|
|
190
|
+
)
|
|
181
191
|
end
|
|
182
192
|
|
|
183
193
|
def common_tags
|
|
@@ -240,13 +250,16 @@ module KamalBackup
|
|
|
240
250
|
end
|
|
241
251
|
|
|
242
252
|
def pipe_commands(producer, consumer, producer_label:, consumer_label:)
|
|
253
|
+
output = Command.output
|
|
254
|
+
producer_context = output&.command_start(producer, redactor: redactor)
|
|
243
255
|
Open3.popen3(producer.env, *producer.argv) do |producer_stdin, producer_stdout, producer_stderr, producer_wait|
|
|
244
256
|
producer_stdin.close
|
|
245
257
|
|
|
258
|
+
consumer_context = output&.command_start(consumer, redactor: redactor)
|
|
246
259
|
Open3.popen3(consumer.env, *consumer.argv) do |consumer_stdin, consumer_stdout, consumer_stderr, consumer_wait|
|
|
247
|
-
producer_err_reader = Thread.new { producer_stderr
|
|
248
|
-
consumer_out_reader = Thread.new { consumer_stdout
|
|
249
|
-
consumer_err_reader = Thread.new { consumer_stderr
|
|
260
|
+
producer_err_reader = Thread.new { Command.collect_stream(producer_stderr, command_output: output, context: producer_context, stream: :stderr, redactor: redactor) }
|
|
261
|
+
consumer_out_reader = Thread.new { Command.collect_stream(consumer_stdout, command_output: output, context: consumer_context, stream: :stdout, redactor: redactor) }
|
|
262
|
+
consumer_err_reader = Thread.new { Command.collect_stream(consumer_stderr, command_output: output, context: consumer_context, stream: :stderr, redactor: redactor) }
|
|
250
263
|
|
|
251
264
|
copy_error = nil
|
|
252
265
|
copy_thread = Thread.new do
|
|
@@ -260,6 +273,8 @@ module KamalBackup
|
|
|
260
273
|
copy_thread.join
|
|
261
274
|
producer_status = producer_wait.value
|
|
262
275
|
consumer_status = consumer_wait.value
|
|
276
|
+
output&.command_exit(producer_context, producer_status.exitstatus)
|
|
277
|
+
output&.command_exit(consumer_context, consumer_status.exitstatus)
|
|
263
278
|
|
|
264
279
|
producer_err = producer_err_reader.value
|
|
265
280
|
consumer_out = consumer_out_reader.value
|
|
@@ -302,7 +317,11 @@ module KamalBackup
|
|
|
302
317
|
end
|
|
303
318
|
|
|
304
319
|
def log(message)
|
|
305
|
-
|
|
320
|
+
if Command.output
|
|
321
|
+
Command.output.info(message, redactor: redactor)
|
|
322
|
+
else
|
|
323
|
+
$stdout.puts("[kamal-backup] #{redactor.redact_string(message)}")
|
|
324
|
+
end
|
|
306
325
|
end
|
|
307
326
|
end
|
|
308
327
|
end
|
data/lib/kamal_backup/version.rb
CHANGED