kamal 2.10.1 → 2.11.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/alias/command.rb +2 -2
- data/lib/kamal/cli/base.rb +19 -6
- data/lib/kamal/cli/build.rb +1 -1
- data/lib/kamal/cli/server.rb +10 -0
- data/lib/kamal/commander.rb +17 -16
- data/lib/kamal/commands/accessory.rb +1 -0
- data/lib/kamal/commands/base.rb +15 -2
- data/lib/kamal/commands/docker.rb +17 -1
- data/lib/kamal/commands/proxy.rb +1 -1
- data/lib/kamal/configuration/docs/alias.yml +3 -0
- data/lib/kamal/configuration/docs/configuration.yml +20 -0
- data/lib/kamal/configuration/proxy/run.rb +1 -1
- data/lib/kamal/configuration/validator.rb +15 -0
- data/lib/kamal/configuration.rb +35 -2
- data/lib/kamal/version.rb +1 -1
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8f7525b3ed804be2810d0af353c254b8c7ebf4d5cf5916dbfa97d5bf4615d54
|
|
4
|
+
data.tar.gz: adfac172b3e4b45bc00092317bde87395fcedecf107b30c8500ab58097ef22d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 05150f9253685ce18dd910cf809a99e51449b7452536e6c3f39c8e37d5ce3db4806bd8e198ed3e29aa872523eb8937db5b58faee036619f8e5b43d1084eebffc
|
|
7
|
+
data.tar.gz: 47e77a8d347c44aff4326f9927a0933c0240b5bfdb90f1b623e85ecdad8ba48c0e3443d43cd062b265aa29e2dbd47d778499be39a079d3cb93d8856cb0388108
|
data/README.md
CHANGED
|
@@ -6,7 +6,7 @@ From bare metal to cloud VMs, deploy web apps anywhere with zero downtime. Kamal
|
|
|
6
6
|
|
|
7
7
|
## Contributing to the documentation
|
|
8
8
|
|
|
9
|
-
Please help us improve Kamal's documentation on
|
|
9
|
+
Please help us improve Kamal's documentation on [the basecamp/kamal-site repository](https://github.com/basecamp/kamal-site).
|
|
10
10
|
|
|
11
11
|
## License
|
|
12
12
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
class Kamal::Cli::Alias::Command < Thor::DynamicCommand
|
|
2
2
|
def run(instance, args = [])
|
|
3
|
-
if (
|
|
3
|
+
if (command = KAMAL.resolve_alias(name))
|
|
4
4
|
KAMAL.reset
|
|
5
|
-
Kamal::Cli::Main.start(Shellwords.split(
|
|
5
|
+
Kamal::Cli::Main.start(Shellwords.split(command) + ARGV[1..-1])
|
|
6
6
|
else
|
|
7
7
|
super
|
|
8
8
|
end
|
data/lib/kamal/cli/base.rb
CHANGED
|
@@ -5,6 +5,8 @@ module Kamal::Cli
|
|
|
5
5
|
class Base < Thor
|
|
6
6
|
include SSHKit::DSL
|
|
7
7
|
|
|
8
|
+
VERBOSITY = { verbose: :debug, quiet: :error }.freeze
|
|
9
|
+
|
|
8
10
|
def self.exit_on_failure?() true end
|
|
9
11
|
def self.dynamic_command_class() Kamal::Cli::Alias::Command end
|
|
10
12
|
|
|
@@ -43,11 +45,11 @@ module Kamal::Cli
|
|
|
43
45
|
KAMAL.tap do |commander|
|
|
44
46
|
if options[:verbose]
|
|
45
47
|
ENV["VERBOSE"] = "1" # For backtraces via cli/start
|
|
46
|
-
commander.verbosity = :
|
|
48
|
+
commander.verbosity = VERBOSITY[:verbose]
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
if options[:quiet]
|
|
50
|
-
commander.verbosity = :
|
|
52
|
+
commander.verbosity = VERBOSITY[:quiet]
|
|
51
53
|
end
|
|
52
54
|
|
|
53
55
|
commander.configure \
|
|
@@ -141,10 +143,21 @@ module Kamal::Cli
|
|
|
141
143
|
subcommand: subcommand
|
|
142
144
|
}.compact
|
|
143
145
|
|
|
144
|
-
|
|
146
|
+
hooks_output = KAMAL.config.hooks_output_for(hook)
|
|
147
|
+
|
|
148
|
+
# CLI flags override config: -q hides all, -v shows all
|
|
149
|
+
# Config setting :verbose forces output, :quiet forces silence
|
|
150
|
+
hook_verbosity = if KAMAL.verbosity == :info && hooks_output
|
|
151
|
+
VERBOSITY.fetch(hooks_output)
|
|
152
|
+
else
|
|
153
|
+
KAMAL.verbosity
|
|
154
|
+
end
|
|
155
|
+
|
|
145
156
|
with_env KAMAL.hook.env(**details, **extra_details) do
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
KAMAL.with_verbosity(hook_verbosity) do
|
|
158
|
+
run_locally do
|
|
159
|
+
execute *KAMAL.hook.run(hook)
|
|
160
|
+
end
|
|
148
161
|
end
|
|
149
162
|
rescue SSHKit::Command::Failed => e
|
|
150
163
|
raise HookError.new("Hook `#{hook}` failed:\n#{e.message}")
|
|
@@ -160,7 +173,7 @@ module Kamal::Cli
|
|
|
160
173
|
|
|
161
174
|
def pre_connect_if_required
|
|
162
175
|
if !KAMAL.connected?
|
|
163
|
-
run_hook "pre-connect"
|
|
176
|
+
run_hook "pre-connect", secrets: true unless options[:skip_hooks]
|
|
164
177
|
KAMAL.connected = true
|
|
165
178
|
end
|
|
166
179
|
end
|
data/lib/kamal/cli/build.rb
CHANGED
|
@@ -13,7 +13,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
|
13
13
|
def push
|
|
14
14
|
cli = self
|
|
15
15
|
|
|
16
|
-
# Ensure pre-connect hooks run before the build, they may needed for a remote builder
|
|
16
|
+
# Ensure pre-connect hooks run before the build, they may be needed for a remote builder
|
|
17
17
|
# or the pre-build hooks.
|
|
18
18
|
pre_connect_if_required
|
|
19
19
|
|
data/lib/kamal/cli/server.rb
CHANGED
|
@@ -35,6 +35,16 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|
|
35
35
|
if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
|
|
36
36
|
info "Missing Docker on #{host}. Installing…"
|
|
37
37
|
execute *KAMAL.docker.install
|
|
38
|
+
|
|
39
|
+
unless execute(*KAMAL.docker.root?, raise_on_non_zero_exit: false) ||
|
|
40
|
+
execute(*KAMAL.docker.in_docker_group?, raise_on_non_zero_exit: false)
|
|
41
|
+
execute *KAMAL.docker.add_to_docker_group
|
|
42
|
+
begin
|
|
43
|
+
execute *KAMAL.docker.refresh_session
|
|
44
|
+
rescue IOError
|
|
45
|
+
info "Session refreshed due to group change."
|
|
46
|
+
end
|
|
47
|
+
end
|
|
38
48
|
else
|
|
39
49
|
missing << host
|
|
40
50
|
end
|
data/lib/kamal/commander.rb
CHANGED
|
@@ -46,27 +46,19 @@ class Kamal::Commander
|
|
|
46
46
|
|
|
47
47
|
def specific_roles=(role_names)
|
|
48
48
|
@specifics = nil
|
|
49
|
-
if role_names.present?
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
raise ArgumentError, "No --roles match for #{role_names.join(',')}"
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
@specific_roles
|
|
49
|
+
@specific_roles = if role_names.present?
|
|
50
|
+
filtered = Kamal::Utils.filter_specific_items(role_names, config.roles)
|
|
51
|
+
raise ArgumentError, "No --roles match for #{role_names.join(',')}" if filtered.empty?
|
|
52
|
+
filtered
|
|
57
53
|
end
|
|
58
54
|
end
|
|
59
55
|
|
|
60
56
|
def specific_hosts=(hosts)
|
|
61
57
|
@specifics = nil
|
|
62
|
-
if hosts.present?
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
raise ArgumentError, "No --hosts match for #{hosts.join(',')}"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
@specific_hosts
|
|
58
|
+
@specific_hosts = if hosts.present?
|
|
59
|
+
filtered = Kamal::Utils.filter_specific_items(hosts, config.all_hosts)
|
|
60
|
+
raise ArgumentError, "No --hosts match for #{hosts.join(',')}" if filtered.empty?
|
|
61
|
+
filtered
|
|
70
62
|
end
|
|
71
63
|
end
|
|
72
64
|
|
|
@@ -129,6 +121,15 @@ class Kamal::Commander
|
|
|
129
121
|
config.aliases[name]
|
|
130
122
|
end
|
|
131
123
|
|
|
124
|
+
def resolve_alias(name)
|
|
125
|
+
if @config
|
|
126
|
+
@config.aliases[name]&.command
|
|
127
|
+
else
|
|
128
|
+
raw_config = Kamal::Configuration.load_raw_config(**@config_kwargs.to_h.slice(:config_file, :destination))
|
|
129
|
+
raw_config[:aliases]&.dig(name)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
132
133
|
def with_verbosity(level)
|
|
133
134
|
old_level = self.verbosity
|
|
134
135
|
|
data/lib/kamal/commands/base.rb
CHANGED
|
@@ -11,11 +11,11 @@ module Kamal::Commands
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def run_over_ssh(*command, host:)
|
|
14
|
-
"ssh#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
|
|
14
|
+
"ssh#{ssh_config_args}#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def container_id_for(container_name:, only_running: false)
|
|
18
|
-
docker :container, :ls, *("--all" unless only_running), "--filter", "name=^#{container_name}$", "--quiet"
|
|
18
|
+
docker :container, :ls, *("--all" unless only_running), "--filter", "'name=^#{container_name}$'", "--quiet"
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def make_directory_for(remote_file)
|
|
@@ -100,6 +100,19 @@ module Kamal::Commands
|
|
|
100
100
|
Kamal::Tags.from_config(config, **details)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
def ssh_config_args
|
|
104
|
+
case config.ssh.config
|
|
105
|
+
when Array
|
|
106
|
+
config.ssh.config.map { |file| " -F #{file}" }.join
|
|
107
|
+
when String
|
|
108
|
+
" -F #{config.ssh.config}"
|
|
109
|
+
when true
|
|
110
|
+
"" # Use default SSH config
|
|
111
|
+
when false
|
|
112
|
+
" -F /dev/null" # Ignore SSH config
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
103
116
|
def ssh_proxy_args
|
|
104
117
|
case config.ssh.proxy
|
|
105
118
|
when Net::SSH::Proxy::Jump
|
|
@@ -16,7 +16,23 @@ class Kamal::Commands::Docker < Kamal::Commands::Base
|
|
|
16
16
|
|
|
17
17
|
# Do we have superuser access to install Docker and start system services?
|
|
18
18
|
def superuser?
|
|
19
|
-
[ '[ "${EUID:-$(id -u)}" -eq 0 ] ||
|
|
19
|
+
[ '[ "${EUID:-$(id -u)}" -eq 0 ] || sudo -nl usermod >/dev/null' ]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def root?
|
|
23
|
+
[ '[ "${EUID:-$(id -u)}" -eq 0 ]' ]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def in_docker_group?
|
|
27
|
+
[ 'id -nG "${USER:-$(id -un)}" | grep -qw docker' ]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def add_to_docker_group
|
|
31
|
+
[ 'sudo -n usermod -aG docker "${USER:-$(id -un)}"' ]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def refresh_session
|
|
35
|
+
[ "kill -HUP $PPID" ]
|
|
20
36
|
end
|
|
21
37
|
|
|
22
38
|
def create_network
|
data/lib/kamal/commands/proxy.rb
CHANGED
|
@@ -85,6 +85,26 @@ asset_path: /path/to/assets
|
|
|
85
85
|
# See https://kamal-deploy.org/docs/hooks for more information:
|
|
86
86
|
hooks_path: /user_home/kamal/hooks
|
|
87
87
|
|
|
88
|
+
# Hook output
|
|
89
|
+
#
|
|
90
|
+
# Hook output visibility. Can be set globally or per-hook.
|
|
91
|
+
# CLI flags (`-v`, `-q`) override these settings.
|
|
92
|
+
#
|
|
93
|
+
# - `:quiet` - hook output is hidden
|
|
94
|
+
# - `:verbose` - hook output is shown
|
|
95
|
+
#
|
|
96
|
+
# With no setting, hook output follows CLI verbosity flags.
|
|
97
|
+
#
|
|
98
|
+
# Note: Failed hooks always show output in the error message regardless of setting.
|
|
99
|
+
#
|
|
100
|
+
# Global setting for all hooks:
|
|
101
|
+
hooks_output: :verbose
|
|
102
|
+
|
|
103
|
+
# Or per-hook settings:
|
|
104
|
+
hooks_output:
|
|
105
|
+
pre-deploy: :verbose
|
|
106
|
+
pre-build: :quiet
|
|
107
|
+
|
|
88
108
|
# Secrets path
|
|
89
109
|
#
|
|
90
110
|
# Path to secrets, defaults to `.kamal/secrets`.
|
|
@@ -29,6 +29,8 @@ class Kamal::Configuration::Validator
|
|
|
29
29
|
end
|
|
30
30
|
elsif key.to_s == "ssl"
|
|
31
31
|
validate_type! value, TrueClass, FalseClass, Hash
|
|
32
|
+
elsif key.to_s == "hooks_output"
|
|
33
|
+
validate_hooks_output!(value)
|
|
32
34
|
elsif key == "hosts"
|
|
33
35
|
validate_servers! value
|
|
34
36
|
elsif example_value.is_a?(Array)
|
|
@@ -161,6 +163,19 @@ class Kamal::Configuration::Validator
|
|
|
161
163
|
end
|
|
162
164
|
end
|
|
163
165
|
|
|
166
|
+
def validate_hooks_output!(value)
|
|
167
|
+
# hooks_output can be either a symbol/string (global) or a hash (per-hook)
|
|
168
|
+
if value.is_a?(Hash)
|
|
169
|
+
value.each do |hook, level|
|
|
170
|
+
with_context(hook) do
|
|
171
|
+
validate_type! level, String, Symbol
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
else
|
|
175
|
+
validate_type! value, String, Symbol
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
164
179
|
def validate_type!(value, *types)
|
|
165
180
|
type_error(*types) unless types.any? { |type| valid_type?(value, type) }
|
|
166
181
|
end
|
data/lib/kamal/configuration.rb
CHANGED
|
@@ -6,6 +6,8 @@ require "erb"
|
|
|
6
6
|
require "net/ssh/proxy/jump"
|
|
7
7
|
|
|
8
8
|
class Kamal::Configuration
|
|
9
|
+
HOOKS_OUTPUT_LEVELS = [ :quiet, :verbose ].freeze
|
|
10
|
+
|
|
9
11
|
delegate :service, :labels, :hooks_path, to: :raw_config, allow_nil: true
|
|
10
12
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
11
13
|
|
|
@@ -18,11 +20,15 @@ class Kamal::Configuration
|
|
|
18
20
|
def create_from(config_file:, destination: nil, version: nil)
|
|
19
21
|
ENV["KAMAL_DESTINATION"] = destination
|
|
20
22
|
|
|
21
|
-
raw_config =
|
|
23
|
+
raw_config = load_raw_config(config_file: config_file, destination: destination)
|
|
22
24
|
|
|
23
25
|
new raw_config, destination: destination, version: version
|
|
24
26
|
end
|
|
25
27
|
|
|
28
|
+
def load_raw_config(config_file:, destination: nil)
|
|
29
|
+
load_config_files(config_file, *destination_config_file(config_file, destination))
|
|
30
|
+
end
|
|
31
|
+
|
|
26
32
|
private
|
|
27
33
|
def load_config_files(*files)
|
|
28
34
|
files.inject({}) { |config, file| config.deep_merge! load_config_file(file) }
|
|
@@ -32,7 +38,9 @@ class Kamal::Configuration
|
|
|
32
38
|
if file.exist?
|
|
33
39
|
# Newer Psych doesn't load aliases by default
|
|
34
40
|
load_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load
|
|
35
|
-
|
|
41
|
+
template = File.read(file)
|
|
42
|
+
rendered = ERB.new(template, trim_mode: "-").result
|
|
43
|
+
YAML.send(load_method, rendered).symbolize_keys
|
|
36
44
|
else
|
|
37
45
|
raise "Configuration file not found in #{file}"
|
|
38
46
|
end
|
|
@@ -78,6 +86,7 @@ class Kamal::Configuration
|
|
|
78
86
|
ensure_unique_hosts_for_ssl_roles
|
|
79
87
|
ensure_local_registry_remote_builder_has_ssh_url
|
|
80
88
|
ensure_no_conflicting_proxy_runs
|
|
89
|
+
ensure_valid_hooks_output!
|
|
81
90
|
end
|
|
82
91
|
|
|
83
92
|
def version=(version)
|
|
@@ -279,6 +288,15 @@ class Kamal::Configuration
|
|
|
279
288
|
env_tags.detect { |t| t.name == name.to_s }
|
|
280
289
|
end
|
|
281
290
|
|
|
291
|
+
def hooks_output_for(hook)
|
|
292
|
+
case raw_config.hooks_output
|
|
293
|
+
when Symbol, String
|
|
294
|
+
raw_config.hooks_output.to_sym
|
|
295
|
+
when Hash
|
|
296
|
+
raw_config.hooks_output[hook]&.to_sym
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
282
300
|
def to_h
|
|
283
301
|
{
|
|
284
302
|
roles: role_names,
|
|
@@ -409,6 +427,21 @@ class Kamal::Configuration
|
|
|
409
427
|
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
|
|
410
428
|
end
|
|
411
429
|
|
|
430
|
+
def ensure_valid_hooks_output!
|
|
431
|
+
case raw_config.hooks_output
|
|
432
|
+
when Symbol, String
|
|
433
|
+
validate_hooks_output_level!(raw_config.hooks_output.to_sym)
|
|
434
|
+
when Hash
|
|
435
|
+
raw_config.hooks_output.each { |hook, level| validate_hooks_output_level!(level.to_sym, hook) }
|
|
436
|
+
end
|
|
437
|
+
end
|
|
438
|
+
|
|
439
|
+
def validate_hooks_output_level!(level, hook = nil)
|
|
440
|
+
return if HOOKS_OUTPUT_LEVELS.include?(level)
|
|
441
|
+
context = hook ? " for hook '#{hook}'" : ""
|
|
442
|
+
raise Kamal::ConfigurationError, "Invalid hooks_output '#{level}'#{context}, must be one of: #{HOOKS_OUTPUT_LEVELS.join(', ')}"
|
|
443
|
+
end
|
|
444
|
+
|
|
412
445
|
def git_version
|
|
413
446
|
@git_version ||=
|
|
414
447
|
if Kamal::Git.used?
|
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.11.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
|