kamal 2.10.1 → 2.12.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/README.md +1 -1
- data/lib/kamal/cli/accessory.rb +48 -39
- data/lib/kamal/cli/alias/command.rb +2 -2
- data/lib/kamal/cli/app.rb +57 -48
- data/lib/kamal/cli/base.rb +118 -17
- data/lib/kamal/cli/build.rb +10 -7
- data/lib/kamal/cli/lock.rb +5 -16
- data/lib/kamal/cli/main.rb +59 -53
- data/lib/kamal/cli/proxy.rb +9 -9
- data/lib/kamal/cli/prune.rb +3 -3
- data/lib/kamal/cli/server.rb +34 -15
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +1 -1
- data/lib/kamal/cli/templates/secrets +4 -0
- data/lib/kamal/commander.rb +71 -17
- data/lib/kamal/commands/accessory.rb +3 -2
- data/lib/kamal/commands/app/logging.rb +1 -1
- data/lib/kamal/commands/app.rb +1 -1
- data/lib/kamal/commands/base.rb +15 -2
- data/lib/kamal/commands/builder/clone.rb +2 -1
- data/lib/kamal/commands/docker.rb +17 -1
- data/lib/kamal/commands/proxy.rb +1 -1
- data/lib/kamal/configuration/accessory.rb +13 -5
- data/lib/kamal/configuration/docs/alias.yml +3 -0
- data/lib/kamal/configuration/docs/configuration.yml +37 -2
- data/lib/kamal/configuration/docs/env.yml +6 -4
- data/lib/kamal/configuration/docs/output.yml +25 -0
- data/lib/kamal/configuration/docs/role.yml +1 -0
- data/lib/kamal/configuration/docs/ssh.yml +8 -0
- data/lib/kamal/configuration/output.rb +34 -0
- data/lib/kamal/configuration/proxy/run.rb +10 -1
- data/lib/kamal/configuration/role.rb +18 -6
- data/lib/kamal/configuration/ssh.rb +5 -1
- data/lib/kamal/configuration/validator.rb +29 -2
- data/lib/kamal/configuration.rb +41 -3
- data/lib/kamal/git.rb +1 -1
- data/lib/kamal/otel_shipper.rb +176 -0
- data/lib/kamal/output/base_logger.rb +29 -0
- data/lib/kamal/output/file_logger.rb +51 -0
- data/lib/kamal/output/formatter.rb +36 -0
- data/lib/kamal/output/otel_logger.rb +70 -0
- data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +10 -2
- data/lib/kamal/secrets/adapters/passbolt.rb +1 -1
- data/lib/kamal/secrets.rb +1 -1
- data/lib/kamal/sshkit_with_ext.rb +9 -4
- data/lib/kamal/version.rb +1 -1
- metadata +23 -2
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require "active_support/core_ext/numeric/time"
|
|
2
|
+
require "net/http"
|
|
3
|
+
require "json"
|
|
4
|
+
require "securerandom"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
class Kamal::OtelShipper
|
|
8
|
+
BATCH_SIZE = 100
|
|
9
|
+
FLUSH_INTERVAL = 5.seconds
|
|
10
|
+
|
|
11
|
+
OTEL_ATTRIBUTE_KEYS = {
|
|
12
|
+
service: "service.namespace",
|
|
13
|
+
version: "kamal.deploy_version",
|
|
14
|
+
performer: "kamal.performer",
|
|
15
|
+
destination: "deployment.environment.name"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
SEVERITIES = {
|
|
19
|
+
debug: { severityNumber: 5, severityText: "DEBUG" },
|
|
20
|
+
info: { severityNumber: 9, severityText: "INFO" },
|
|
21
|
+
warn: { severityNumber: 13, severityText: "WARN" },
|
|
22
|
+
error: { severityNumber: 17, severityText: "ERROR" },
|
|
23
|
+
fatal: { severityNumber: 21, severityText: "FATAL" }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
LOGGER_SEVERITIES = {
|
|
27
|
+
Logger::DEBUG => :debug,
|
|
28
|
+
Logger::INFO => :info,
|
|
29
|
+
Logger::WARN => :warn,
|
|
30
|
+
Logger::ERROR => :error,
|
|
31
|
+
Logger::FATAL => :fatal
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
attr_reader :run_id
|
|
35
|
+
|
|
36
|
+
def initialize(endpoint:, tags:)
|
|
37
|
+
@endpoint = URI("#{endpoint.chomp('/')}/v1/logs")
|
|
38
|
+
@run_id = SecureRandom.uuid
|
|
39
|
+
@resource_attributes = [
|
|
40
|
+
{ key: "service.name", value: { stringValue: "kamal" } },
|
|
41
|
+
{ key: "service.version", value: { stringValue: Kamal::VERSION } },
|
|
42
|
+
{ key: "kamal.run_id", value: { stringValue: @run_id } },
|
|
43
|
+
*tags.tags.map do |key, value|
|
|
44
|
+
otel_key = OTEL_ATTRIBUTE_KEYS.fetch(key, "kamal.#{key}")
|
|
45
|
+
{ key: otel_key, value: { stringValue: value.to_s } }
|
|
46
|
+
end
|
|
47
|
+
]
|
|
48
|
+
@buffer = Queue.new
|
|
49
|
+
@flush_mutex = Mutex.new
|
|
50
|
+
@running = true
|
|
51
|
+
@signal = Queue.new
|
|
52
|
+
@thread = start_flush_thread
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def <<(str)
|
|
56
|
+
append(str)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def append(str, host: nil, iostream: nil, severity: nil)
|
|
60
|
+
otel_severity = LOGGER_SEVERITIES.fetch(severity, :info)
|
|
61
|
+
extra = build_context_attributes(host: host, iostream: iostream)
|
|
62
|
+
str.to_s.each_line do |line|
|
|
63
|
+
enqueue build_record(line.chomp, severity: otel_severity, attributes: extra)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
self
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def event(name, severity: :info, **attributes)
|
|
70
|
+
attrs = attributes.map { |k, v| { key: k.to_s, value: typed_value(v) } }
|
|
71
|
+
enqueue build_record(name, severity: severity, event_name: name, attributes: attrs)
|
|
72
|
+
|
|
73
|
+
self
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def flush
|
|
77
|
+
@flush_mutex.synchronize do
|
|
78
|
+
lines = drain_buffer
|
|
79
|
+
ship(lines) if lines.any?
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def shutdown
|
|
84
|
+
@running = false
|
|
85
|
+
@signal << true
|
|
86
|
+
@thread&.join(FLUSH_INTERVAL + 1.second)
|
|
87
|
+
flush
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
def enqueue(record)
|
|
92
|
+
@buffer << record
|
|
93
|
+
@signal << true if @buffer.size >= BATCH_SIZE
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def start_flush_thread
|
|
97
|
+
Thread.new do
|
|
98
|
+
while @running
|
|
99
|
+
@signal.pop(timeout: FLUSH_INTERVAL)
|
|
100
|
+
flush
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def drain_buffer
|
|
106
|
+
records = []
|
|
107
|
+
records << @buffer.pop(true) until @buffer.empty?
|
|
108
|
+
records
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def ship(records)
|
|
112
|
+
with_connection do |http|
|
|
113
|
+
records.each_slice(BATCH_SIZE) do |batch|
|
|
114
|
+
ship_records(http, batch)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def build_record(body, severity: :info, event_name: nil, attributes: nil)
|
|
120
|
+
now = time_ns
|
|
121
|
+
{ timeUnixNano: now, observedTimeUnixNano: now, **SEVERITIES.fetch(severity),
|
|
122
|
+
body: { stringValue: body }, eventName: event_name, attributes: attributes }.compact
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def build_context_attributes(host:, iostream:)
|
|
126
|
+
attrs = []
|
|
127
|
+
attrs << { key: "server.address", value: { stringValue: host } } if host
|
|
128
|
+
attrs << { key: "log.iostream", value: { stringValue: iostream } } if iostream
|
|
129
|
+
attrs.presence
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def typed_value(v)
|
|
133
|
+
case v
|
|
134
|
+
when Integer then { intValue: v }
|
|
135
|
+
when Float then { doubleValue: v }
|
|
136
|
+
when Array then { arrayValue: { values: v.map { |e| typed_value(e) } } }
|
|
137
|
+
else { stringValue: v.to_s }
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def with_connection
|
|
142
|
+
http = Net::HTTP.new(@endpoint.host, @endpoint.port)
|
|
143
|
+
http.use_ssl = @endpoint.scheme == "https"
|
|
144
|
+
http.open_timeout = 2.seconds
|
|
145
|
+
http.read_timeout = 5.seconds
|
|
146
|
+
http.start { |conn| yield conn }
|
|
147
|
+
rescue StandardError => e
|
|
148
|
+
unless @ship_error_logged
|
|
149
|
+
@ship_error_logged = true
|
|
150
|
+
$stderr.puts "OTel log shipping failed: #{e.class}: #{e.message}"
|
|
151
|
+
$stderr.puts e.backtrace.join("\n") if ENV["VERBOSE"]
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def ship_records(http, records)
|
|
156
|
+
payload = {
|
|
157
|
+
resourceLogs: [ {
|
|
158
|
+
resource: { attributes: @resource_attributes },
|
|
159
|
+
scopeLogs: [ { scope: { name: "kamal", version: Kamal::VERSION }, logRecords: records } ]
|
|
160
|
+
} ]
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
req = Net::HTTP::Post.new(@endpoint.request_uri, "Content-Type" => "application/json")
|
|
164
|
+
req.body = JSON.generate(payload)
|
|
165
|
+
response = http.request(req)
|
|
166
|
+
|
|
167
|
+
unless response.is_a?(Net::HTTPSuccess) || @ship_error_logged
|
|
168
|
+
@ship_error_logged = true
|
|
169
|
+
$stderr.puts "OTel log shipping failed: HTTP #{response.code} #{response.message}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def time_ns
|
|
174
|
+
Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond).to_s
|
|
175
|
+
end
|
|
176
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require "logger"
|
|
2
|
+
|
|
3
|
+
class Kamal::Output::BaseLogger < ::Logger
|
|
4
|
+
def initialize
|
|
5
|
+
super(nil)
|
|
6
|
+
@subscription = ActiveSupport::Notifications.subscribe("modify.kamal", self)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def start(name, id, payload)
|
|
10
|
+
@started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
11
|
+
on_start(payload)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def finish(name, id, payload)
|
|
15
|
+
runtime = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @started_at).round(1)
|
|
16
|
+
on_finish(payload, runtime)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add(severity, message = nil, progname = nil, &block)
|
|
20
|
+
if msg = message || (block ? block.call : progname)
|
|
21
|
+
self << msg.to_s
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def close
|
|
26
|
+
ActiveSupport::Notifications.unsubscribe(@subscription)
|
|
27
|
+
on_close
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
class Kamal::Output::FileLogger < Kamal::Output::BaseLogger
|
|
2
|
+
attr_reader :path
|
|
3
|
+
|
|
4
|
+
def self.build(settings:, config:)
|
|
5
|
+
raise ArgumentError, "file path is required" unless settings["path"]
|
|
6
|
+
new(path: settings["path"])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initialize(path:)
|
|
10
|
+
@path = Pathname.new(path)
|
|
11
|
+
super()
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def <<(message)
|
|
15
|
+
@file&.print(message)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
def on_start(payload)
|
|
20
|
+
path.mkpath
|
|
21
|
+
@file_path = path.join(filename_for(payload))
|
|
22
|
+
@file = File.open(@file_path, "a")
|
|
23
|
+
@file.sync = true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def on_finish(payload, runtime)
|
|
27
|
+
if @file
|
|
28
|
+
if payload[:exception]
|
|
29
|
+
error_class, error_message = payload[:exception]
|
|
30
|
+
@file.puts "# FAILED: #{error_class}: #{error_message} (#{runtime}s)"
|
|
31
|
+
else
|
|
32
|
+
@file.puts "# Completed in #{runtime}s"
|
|
33
|
+
end
|
|
34
|
+
@file.close
|
|
35
|
+
@file = nil
|
|
36
|
+
puts "Logs written to #{@file_path}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def on_close
|
|
41
|
+
if @file
|
|
42
|
+
@file.close
|
|
43
|
+
@file = nil
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def filename_for(payload)
|
|
48
|
+
command = [ payload[:command], payload[:subcommand] ].compact.join("_")
|
|
49
|
+
[ Time.now.strftime("%Y-%m-%dT%H-%M-%S"), payload[:destination], command ].compact.join("_") + ".log"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
class Kamal::Output::Formatter < SSHKit::Formatter::Pretty
|
|
2
|
+
def initialize(output, logger)
|
|
3
|
+
@logger = logger
|
|
4
|
+
super(output)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def log_command_start(command)
|
|
8
|
+
with_command_context(command) { super }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def log_command_data(command, stream_type, stream_data)
|
|
12
|
+
with_command_context(command, iostream: stream_type.to_s) { super }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def log_command_exit(command)
|
|
16
|
+
with_command_context(command) { super }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
def write_message(verbosity, message, uuid = nil)
|
|
21
|
+
super
|
|
22
|
+
Thread.current[:kamal_severity] = verbosity
|
|
23
|
+
@logger << "#{format_message(verbosity, message, uuid)}\n" rescue nil
|
|
24
|
+
ensure
|
|
25
|
+
Thread.current[:kamal_severity] = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def with_command_context(command, iostream: nil)
|
|
29
|
+
Thread.current[:kamal_host] = command.host.to_s
|
|
30
|
+
Thread.current[:kamal_iostream] = iostream
|
|
31
|
+
yield
|
|
32
|
+
ensure
|
|
33
|
+
Thread.current[:kamal_host] = nil
|
|
34
|
+
Thread.current[:kamal_iostream] = nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
class Kamal::Output::OtelLogger < Kamal::Output::BaseLogger
|
|
2
|
+
def self.build(settings:, config:)
|
|
3
|
+
raise ArgumentError, "OTel endpoint is required" unless settings["endpoint"]
|
|
4
|
+
new(
|
|
5
|
+
endpoint: settings["endpoint"],
|
|
6
|
+
tags: Kamal::Tags.from_config(config).except(:service_version, :recorded_at),
|
|
7
|
+
service: config.service
|
|
8
|
+
)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(endpoint:, tags:, service: nil)
|
|
12
|
+
@endpoint = endpoint
|
|
13
|
+
@shipper = Kamal::OtelShipper.new(endpoint: endpoint, tags: tags)
|
|
14
|
+
@service = service
|
|
15
|
+
super()
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def <<(message)
|
|
19
|
+
host = Thread.current[:kamal_host]
|
|
20
|
+
iostream = Thread.current[:kamal_iostream]
|
|
21
|
+
severity = Thread.current[:kamal_severity]
|
|
22
|
+
@shipper.append(message, host: host, iostream: iostream, severity: severity)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
DEPLOY_COMMANDS = %w[ deploy redeploy rollback setup ].freeze
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
def on_start(payload)
|
|
29
|
+
@shipper.event("kamal.start",
|
|
30
|
+
"kamal.command": full_command(payload),
|
|
31
|
+
**deployment_attrs(payload))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def on_finish(payload, runtime)
|
|
35
|
+
if payload[:exception]
|
|
36
|
+
error_class, error_message = payload[:exception]
|
|
37
|
+
@shipper.event("kamal.failed", severity: :error,
|
|
38
|
+
"kamal.command": full_command(payload), "kamal.runtime": runtime,
|
|
39
|
+
"exception.type": error_class, "exception.message": error_message,
|
|
40
|
+
**deployment_attrs(payload, status: "failed"))
|
|
41
|
+
else
|
|
42
|
+
@shipper.event("kamal.complete",
|
|
43
|
+
"kamal.command": full_command(payload), "kamal.runtime": runtime,
|
|
44
|
+
**deployment_attrs(payload, status: "succeeded"))
|
|
45
|
+
end
|
|
46
|
+
puts "Logs sent to #{@endpoint}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def on_close
|
|
50
|
+
@shipper.shutdown
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def full_command(payload)
|
|
54
|
+
[ payload[:command], payload[:subcommand] ].compact.join(" ")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def deploy?(payload)
|
|
58
|
+
DEPLOY_COMMANDS.include?(payload[:command])
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def deployment_attrs(payload, status: nil)
|
|
62
|
+
if deploy?(payload)
|
|
63
|
+
attrs = { "deployment.id": @shipper.run_id, "deployment.name": "#{full_command(payload)} #{@service}" }
|
|
64
|
+
attrs[:"deployment.status"] = status if status
|
|
65
|
+
attrs
|
|
66
|
+
else
|
|
67
|
+
{}
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -14,8 +14,12 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba
|
|
|
14
14
|
secret_name = secret["Name"]
|
|
15
15
|
secret_string = JSON.parse(secret["SecretString"])
|
|
16
16
|
|
|
17
|
-
secret_string.
|
|
18
|
-
|
|
17
|
+
if secret_string.is_a?(Hash)
|
|
18
|
+
secret_string.each do |key, value|
|
|
19
|
+
results["#{secret_name}/#{key}"] = stringify_secret_value(value)
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
results["#{secret_name}"] = stringify_secret_value(secret_string)
|
|
19
23
|
end
|
|
20
24
|
rescue JSON::ParserError
|
|
21
25
|
results["#{secret_name}"] = secret["SecretString"]
|
|
@@ -40,6 +44,10 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba
|
|
|
40
44
|
end
|
|
41
45
|
end
|
|
42
46
|
|
|
47
|
+
def stringify_secret_value(value)
|
|
48
|
+
value.is_a?(String) ? value : JSON.dump(value)
|
|
49
|
+
end
|
|
50
|
+
|
|
43
51
|
def check_dependencies!
|
|
44
52
|
raise RuntimeError, "AWS CLI is not installed" unless cli_installed?
|
|
45
53
|
end
|
|
@@ -47,7 +47,7 @@ class Kamal::Secrets::Adapters::Passbolt < Kamal::Secrets::Adapters::Base
|
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
filter_condition = filter_conditions.any? ? "--filter '#{filter_conditions.join(" || ")}'" : ""
|
|
50
|
-
items = `passbolt list resources #{filter_condition} #{folders.map { |item| "--folder #{item["id"].to_s.shellescape}" }.join(" ")} --json`
|
|
50
|
+
items = `passbolt list resources #{filter_condition} #{folders.map { |item| "--folder #{item["id"].to_s.shellescape}" }.join(" ")} --column name --column password --json`
|
|
51
51
|
raise RuntimeError, "Could not read #{secrets} from Passbolt" unless $?.success?
|
|
52
52
|
items = JSON.parse(items)
|
|
53
53
|
found_names = items.map { |item| item["name"] }
|
data/lib/kamal/secrets.rb
CHANGED
|
@@ -3,7 +3,7 @@ require "dotenv"
|
|
|
3
3
|
class Kamal::Secrets
|
|
4
4
|
Kamal::Secrets::Dotenv::InlineCommandSubstitution.install!
|
|
5
5
|
|
|
6
|
-
def initialize(destination: nil, secrets_path:)
|
|
6
|
+
def initialize(destination: nil, secrets_path: ".kamal/secrets")
|
|
7
7
|
@destination = destination
|
|
8
8
|
@secrets_path = secrets_path
|
|
9
9
|
@mutex = Mutex.new
|
|
@@ -19,11 +19,16 @@ class SSHKit::Backend::Abstract
|
|
|
19
19
|
JSON.pretty_generate(JSON.parse(capture(*args, **kwargs)))
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def puts_by_host(host, output, type: "App", quiet: false)
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
def puts_by_host(host, output, type: "App", quiet: false, raw: false)
|
|
23
|
+
if raw
|
|
24
|
+
$stdout.binmode
|
|
25
|
+
$stdout.write(output)
|
|
26
|
+
else
|
|
27
|
+
unless quiet
|
|
28
|
+
puts "#{type} Host: #{host}"
|
|
29
|
+
end
|
|
30
|
+
puts "#{output}\n\n"
|
|
25
31
|
end
|
|
26
|
-
puts "#{output}\n\n"
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
# Our execution pattern is for the CLI execute args lists returned
|
data/lib/kamal/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kamal
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Heinemeier Hansson
|
|
@@ -175,6 +175,20 @@ dependencies:
|
|
|
175
175
|
- - ">="
|
|
176
176
|
- !ruby/object:Gem::Version
|
|
177
177
|
version: '0'
|
|
178
|
+
- !ruby/object:Gem::Dependency
|
|
179
|
+
name: minitest
|
|
180
|
+
requirement: !ruby/object:Gem::Requirement
|
|
181
|
+
requirements:
|
|
182
|
+
- - "<"
|
|
183
|
+
- !ruby/object:Gem::Version
|
|
184
|
+
version: '6'
|
|
185
|
+
type: :development
|
|
186
|
+
prerelease: false
|
|
187
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
188
|
+
requirements:
|
|
189
|
+
- - "<"
|
|
190
|
+
- !ruby/object:Gem::Version
|
|
191
|
+
version: '6'
|
|
178
192
|
- !ruby/object:Gem::Dependency
|
|
179
193
|
name: mocha
|
|
180
194
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -288,6 +302,7 @@ files:
|
|
|
288
302
|
- lib/kamal/configuration/docs/configuration.yml
|
|
289
303
|
- lib/kamal/configuration/docs/env.yml
|
|
290
304
|
- lib/kamal/configuration/docs/logging.yml
|
|
305
|
+
- lib/kamal/configuration/docs/output.yml
|
|
291
306
|
- lib/kamal/configuration/docs/proxy.yml
|
|
292
307
|
- lib/kamal/configuration/docs/registry.yml
|
|
293
308
|
- lib/kamal/configuration/docs/role.yml
|
|
@@ -297,6 +312,7 @@ files:
|
|
|
297
312
|
- lib/kamal/configuration/env.rb
|
|
298
313
|
- lib/kamal/configuration/env/tag.rb
|
|
299
314
|
- lib/kamal/configuration/logging.rb
|
|
315
|
+
- lib/kamal/configuration/output.rb
|
|
300
316
|
- lib/kamal/configuration/proxy.rb
|
|
301
317
|
- lib/kamal/configuration/proxy/boot.rb
|
|
302
318
|
- lib/kamal/configuration/proxy/run.rb
|
|
@@ -320,6 +336,11 @@ files:
|
|
|
320
336
|
- lib/kamal/docker.rb
|
|
321
337
|
- lib/kamal/env_file.rb
|
|
322
338
|
- lib/kamal/git.rb
|
|
339
|
+
- lib/kamal/otel_shipper.rb
|
|
340
|
+
- lib/kamal/output/base_logger.rb
|
|
341
|
+
- lib/kamal/output/file_logger.rb
|
|
342
|
+
- lib/kamal/output/formatter.rb
|
|
343
|
+
- lib/kamal/output/otel_logger.rb
|
|
323
344
|
- lib/kamal/secrets.rb
|
|
324
345
|
- lib/kamal/secrets/adapters.rb
|
|
325
346
|
- lib/kamal/secrets/adapters/aws_secrets_manager.rb
|
|
@@ -357,7 +378,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
357
378
|
- !ruby/object:Gem::Version
|
|
358
379
|
version: '0'
|
|
359
380
|
requirements: []
|
|
360
|
-
rubygems_version:
|
|
381
|
+
rubygems_version: 4.0.10
|
|
361
382
|
specification_version: 4
|
|
362
383
|
summary: Deploy web apps in containers to servers running Docker with zero downtime.
|
|
363
384
|
test_files: []
|