kamal 1.5.2 → 1.7.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/kamal/cli/accessory.rb +30 -24
- data/lib/kamal/cli/app/boot.rb +70 -18
- data/lib/kamal/cli/app/prepare_assets.rb +1 -1
- data/lib/kamal/cli/app.rb +60 -47
- data/lib/kamal/cli/base.rb +26 -28
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +64 -53
- data/lib/kamal/cli/env.rb +5 -5
- data/lib/kamal/cli/healthcheck/barrier.rb +31 -0
- data/lib/kamal/cli/healthcheck/error.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +6 -7
- data/lib/kamal/cli/main.rb +49 -44
- data/lib/kamal/cli/prune.rb +3 -3
- data/lib/kamal/cli/registry.rb +9 -10
- data/lib/kamal/cli/server.rb +39 -15
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +1 -1
- data/lib/kamal/cli/traefik.rb +13 -11
- data/lib/kamal/cli.rb +1 -1
- data/lib/kamal/commander.rb +6 -6
- data/lib/kamal/commands/accessory.rb +4 -4
- data/lib/kamal/commands/app/containers.rb +8 -0
- data/lib/kamal/commands/app/execution.rb +3 -3
- data/lib/kamal/commands/app/logging.rb +5 -5
- data/lib/kamal/commands/app.rb +6 -5
- data/lib/kamal/commands/base.rb +2 -3
- data/lib/kamal/commands/builder/base.rb +19 -12
- data/lib/kamal/commands/builder/clone.rb +28 -0
- data/lib/kamal/commands/builder/multiarch/remote.rb +10 -0
- data/lib/kamal/commands/builder/multiarch.rb +13 -9
- data/lib/kamal/commands/builder/native/cached.rb +14 -6
- data/lib/kamal/commands/builder/native/remote.rb +17 -9
- data/lib/kamal/commands/builder/native.rb +6 -7
- data/lib/kamal/commands/builder.rb +19 -11
- data/lib/kamal/commands/registry.rb +4 -13
- data/lib/kamal/commands/traefik.rb +8 -47
- data/lib/kamal/configuration/accessory.rb +30 -41
- data/lib/kamal/configuration/boot.rb +9 -4
- data/lib/kamal/configuration/builder.rb +61 -30
- data/lib/kamal/configuration/docs/accessory.yml +90 -0
- data/lib/kamal/configuration/docs/boot.yml +19 -0
- data/lib/kamal/configuration/docs/builder.yml +107 -0
- data/lib/kamal/configuration/docs/configuration.yml +157 -0
- data/lib/kamal/configuration/docs/env.yml +72 -0
- data/lib/kamal/configuration/docs/healthcheck.yml +59 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/registry.yml +49 -0
- data/lib/kamal/configuration/docs/role.yml +52 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +46 -0
- data/lib/kamal/configuration/docs/sshkit.yml +23 -0
- data/lib/kamal/configuration/docs/traefik.yml +62 -0
- data/lib/kamal/configuration/env/tag.rb +12 -0
- data/lib/kamal/configuration/env.rb +10 -14
- data/lib/kamal/configuration/healthcheck.rb +63 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/registry.rb +31 -0
- data/lib/kamal/configuration/role.rb +72 -61
- data/lib/kamal/configuration/servers.rb +18 -0
- data/lib/kamal/configuration/ssh.rb +11 -8
- data/lib/kamal/configuration/sshkit.rb +9 -7
- data/lib/kamal/configuration/traefik.rb +60 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +9 -0
- data/lib/kamal/configuration/validator/builder.rb +9 -0
- data/lib/kamal/configuration/validator/env.rb +54 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +11 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +140 -0
- data/lib/kamal/configuration.rb +50 -63
- data/lib/kamal/git.rb +4 -0
- data/lib/kamal/sshkit_with_ext.rb +36 -0
- data/lib/kamal/version.rb +1 -1
- data/lib/kamal.rb +2 -0
- metadata +64 -9
- data/lib/kamal/cli/healthcheck.rb +0 -21
- data/lib/kamal/commands/healthcheck.rb +0 -59
@@ -0,0 +1,61 @@
|
|
1
|
+
require "uri"
|
2
|
+
|
3
|
+
class Kamal::Cli::Build::Clone
|
4
|
+
attr_reader :sshkit
|
5
|
+
delegate :info, :error, :execute, :capture_with_info, to: :sshkit
|
6
|
+
|
7
|
+
def initialize(sshkit)
|
8
|
+
@sshkit = sshkit
|
9
|
+
end
|
10
|
+
|
11
|
+
def prepare
|
12
|
+
begin
|
13
|
+
clone_repo
|
14
|
+
rescue SSHKit::Command::Failed => e
|
15
|
+
if e.message =~ /already exists and is not an empty directory/
|
16
|
+
reset
|
17
|
+
else
|
18
|
+
raise Kamal::Cli::Build::BuildError, "Failed to clone repo: #{e.message}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
validate!
|
23
|
+
rescue Kamal::Cli::Build::BuildError => e
|
24
|
+
error "Error preparing clone: #{e.message}, deleting and retrying..."
|
25
|
+
|
26
|
+
FileUtils.rm_rf KAMAL.config.builder.clone_directory
|
27
|
+
clone_repo
|
28
|
+
validate!
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def clone_repo
|
33
|
+
info "Cloning repo into build directory `#{KAMAL.config.builder.build_directory}`..."
|
34
|
+
|
35
|
+
FileUtils.mkdir_p KAMAL.config.builder.clone_directory
|
36
|
+
execute *KAMAL.builder.clone
|
37
|
+
end
|
38
|
+
|
39
|
+
def reset
|
40
|
+
info "Resetting local clone as `#{KAMAL.config.builder.build_directory}` already exists..."
|
41
|
+
|
42
|
+
KAMAL.builder.clone_reset_steps.each { |step| execute *step }
|
43
|
+
rescue SSHKit::Command::Failed => e
|
44
|
+
raise Kamal::Cli::Build::BuildError, "Failed to clone repo: #{e.message}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def validate!
|
48
|
+
status = capture_with_info(*KAMAL.builder.clone_status).strip
|
49
|
+
|
50
|
+
unless status.empty?
|
51
|
+
raise Kamal::Cli::Build::BuildError, "Clone in #{KAMAL.config.builder.build_directory} is dirty, #{status}"
|
52
|
+
end
|
53
|
+
|
54
|
+
revision = capture_with_info(*KAMAL.builder.clone_revision).strip
|
55
|
+
if revision != Kamal::Git.revision
|
56
|
+
raise Kamal::Cli::Build::BuildError, "Clone in #{KAMAL.config.builder.build_directory} is not on the correct revision, expected `#{Kamal::Git.revision}` but got `#{revision}`"
|
57
|
+
end
|
58
|
+
rescue SSHKit::Command::Failed => e
|
59
|
+
raise Kamal::Cli::Build::BuildError, "Failed to validate clone: #{e.message}"
|
60
|
+
end
|
61
|
+
end
|
data/lib/kamal/cli/build.rb
CHANGED
@@ -5,74 +5,84 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
5
5
|
|
6
6
|
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
7
7
|
def deliver
|
8
|
-
|
9
|
-
|
10
|
-
pull
|
11
|
-
end
|
8
|
+
push
|
9
|
+
pull
|
12
10
|
end
|
13
11
|
|
14
12
|
desc "push", "Build and push app image to registry"
|
15
13
|
def push
|
16
|
-
|
17
|
-
|
14
|
+
cli = self
|
15
|
+
|
16
|
+
verify_local_dependencies
|
17
|
+
run_hook "pre-build"
|
18
18
|
|
19
|
-
|
20
|
-
run_hook "pre-build"
|
19
|
+
uncommitted_changes = Kamal::Git.uncommitted_changes
|
21
20
|
|
22
|
-
|
23
|
-
|
21
|
+
if KAMAL.config.builder.git_clone?
|
22
|
+
if uncommitted_changes.present?
|
23
|
+
say "Building from a local git clone, so ignoring these uncommitted changes:\n #{uncommitted_changes}", :yellow
|
24
24
|
end
|
25
25
|
|
26
26
|
run_locally do
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
27
|
+
Clone.new(self).prepare
|
28
|
+
end
|
29
|
+
elsif uncommitted_changes.present?
|
30
|
+
say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
34
|
+
push = KAMAL.builder.push
|
35
|
+
|
36
|
+
run_locally do
|
37
|
+
begin
|
38
|
+
context_hosts = capture_with_info(*KAMAL.builder.context_hosts).split("\n")
|
39
|
+
|
40
|
+
if context_hosts != KAMAL.builder.config_context_hosts
|
41
|
+
warn "Context hosts have changed, so re-creating builder, was: #{context_hosts.join(", ")}], now: #{KAMAL.builder.config_context_hosts.join(", ")}"
|
42
|
+
cli.remove
|
43
|
+
cli.create
|
41
44
|
end
|
45
|
+
rescue SSHKit::Command::Failed => e
|
46
|
+
warn "Missing compatible builder, so creating a new one first"
|
47
|
+
if e.message =~ /(context not found|no builder)/
|
48
|
+
cli.create
|
49
|
+
else
|
50
|
+
raise
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
KAMAL.with_verbosity(:debug) do
|
55
|
+
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
|
42
56
|
end
|
43
57
|
end
|
44
58
|
end
|
45
59
|
|
46
60
|
desc "pull", "Pull app image from registry onto servers"
|
47
61
|
def pull
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
execute *KAMAL.builder.validate_image
|
54
|
-
end
|
62
|
+
on(KAMAL.hosts) do
|
63
|
+
execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
|
64
|
+
execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
|
65
|
+
execute *KAMAL.builder.pull
|
66
|
+
execute *KAMAL.builder.validate_image
|
55
67
|
end
|
56
68
|
end
|
57
69
|
|
58
70
|
desc "create", "Create a build setup"
|
59
71
|
def create
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
end
|
72
|
+
if (remote_host = KAMAL.config.builder.remote_host)
|
73
|
+
connect_to_remote_host(remote_host)
|
74
|
+
end
|
64
75
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
end
|
76
|
+
run_locally do
|
77
|
+
begin
|
78
|
+
debug "Using builder: #{KAMAL.builder.name}"
|
79
|
+
execute *KAMAL.builder.create
|
80
|
+
rescue SSHKit::Command::Failed => e
|
81
|
+
if e.message =~ /stderr=(.*)/
|
82
|
+
error "Couldn't create remote builder: #{$1}"
|
83
|
+
false
|
84
|
+
else
|
85
|
+
raise
|
76
86
|
end
|
77
87
|
end
|
78
88
|
end
|
@@ -80,11 +90,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
80
90
|
|
81
91
|
desc "remove", "Remove build setup"
|
82
92
|
def remove
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
execute *KAMAL.builder.remove
|
87
|
-
end
|
93
|
+
run_locally do
|
94
|
+
debug "Using builder: #{KAMAL.builder.name}"
|
95
|
+
execute *KAMAL.builder.remove
|
88
96
|
end
|
89
97
|
end
|
90
98
|
|
@@ -114,8 +122,11 @@ class Kamal::Cli::Build < Kamal::Cli::Base
|
|
114
122
|
def connect_to_remote_host(remote_host)
|
115
123
|
remote_uri = URI.parse(remote_host)
|
116
124
|
if remote_uri.scheme == "ssh"
|
117
|
-
|
118
|
-
|
125
|
+
host = SSHKit::Host.new(
|
126
|
+
hostname: remote_uri.host,
|
127
|
+
ssh_options: { user: remote_uri.user, port: remote_uri.port }.compact
|
128
|
+
)
|
129
|
+
on(host, options) do
|
119
130
|
execute "true"
|
120
131
|
end
|
121
132
|
end
|
data/lib/kamal/cli/env.rb
CHANGED
@@ -3,13 +3,13 @@ require "tempfile"
|
|
3
3
|
class Kamal::Cli::Env < Kamal::Cli::Base
|
4
4
|
desc "push", "Push the env file to the remote hosts"
|
5
5
|
def push
|
6
|
-
|
6
|
+
with_lock do
|
7
7
|
on(KAMAL.hosts) do
|
8
8
|
execute *KAMAL.auditor.record("Pushed env files"), verbosity: :debug
|
9
9
|
|
10
10
|
KAMAL.roles_on(host).each do |role|
|
11
|
-
execute *KAMAL.app(role: role).make_env_directory
|
12
|
-
upload! role.env.secrets_io, role.env.secrets_file, mode: 400
|
11
|
+
execute *KAMAL.app(role: role, host: host).make_env_directory
|
12
|
+
upload! role.env(host).secrets_io, role.env(host).secrets_file, mode: 400
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
@@ -30,12 +30,12 @@ class Kamal::Cli::Env < Kamal::Cli::Base
|
|
30
30
|
|
31
31
|
desc "delete", "Delete the env file from the remote hosts"
|
32
32
|
def delete
|
33
|
-
|
33
|
+
with_lock do
|
34
34
|
on(KAMAL.hosts) do
|
35
35
|
execute *KAMAL.auditor.record("Deleted env files"), verbosity: :debug
|
36
36
|
|
37
37
|
KAMAL.roles_on(host).each do |role|
|
38
|
-
execute *KAMAL.app(role: role).remove_env_file
|
38
|
+
execute *KAMAL.app(role: role, host: host).remove_env_file
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Kamal::Cli::Healthcheck::Barrier
|
2
|
+
def initialize
|
3
|
+
@ivar = Concurrent::IVar.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def close
|
7
|
+
set(false)
|
8
|
+
end
|
9
|
+
|
10
|
+
def open
|
11
|
+
set(true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def wait
|
15
|
+
unless opened?
|
16
|
+
raise Kamal::Cli::Healthcheck::Error.new("Halted at barrier")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def opened?
|
22
|
+
@ivar.value
|
23
|
+
end
|
24
|
+
|
25
|
+
def set(value)
|
26
|
+
@ivar.set(value)
|
27
|
+
true
|
28
|
+
rescue Concurrent::MultipleAssignmentError
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
@@ -3,11 +3,10 @@ module Kamal::Cli::Healthcheck::Poller
|
|
3
3
|
|
4
4
|
TRAEFIK_UPDATE_DELAY = 5
|
5
5
|
|
6
|
-
class HealthcheckError < StandardError; end
|
7
6
|
|
8
7
|
def wait_for_healthy(pause_after_ready: false, &block)
|
9
8
|
attempt = 1
|
10
|
-
max_attempts = KAMAL.config.healthcheck
|
9
|
+
max_attempts = KAMAL.config.healthcheck.max_attempts
|
11
10
|
|
12
11
|
begin
|
13
12
|
case status = block.call
|
@@ -16,9 +15,9 @@ module Kamal::Cli::Healthcheck::Poller
|
|
16
15
|
when "running" # No health check configured
|
17
16
|
sleep KAMAL.config.readiness_delay if pause_after_ready
|
18
17
|
else
|
19
|
-
raise
|
18
|
+
raise Kamal::Cli::Healthcheck::Error, "container not ready (#{status})"
|
20
19
|
end
|
21
|
-
rescue
|
20
|
+
rescue Kamal::Cli::Healthcheck::Error => e
|
22
21
|
if attempt <= max_attempts
|
23
22
|
info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
|
24
23
|
sleep attempt
|
@@ -34,16 +33,16 @@ module Kamal::Cli::Healthcheck::Poller
|
|
34
33
|
|
35
34
|
def wait_for_unhealthy(pause_after_ready: false, &block)
|
36
35
|
attempt = 1
|
37
|
-
max_attempts = KAMAL.config.healthcheck
|
36
|
+
max_attempts = KAMAL.config.healthcheck.max_attempts
|
38
37
|
|
39
38
|
begin
|
40
39
|
case status = block.call
|
41
40
|
when "unhealthy"
|
42
41
|
sleep TRAEFIK_UPDATE_DELAY if pause_after_ready
|
43
42
|
else
|
44
|
-
raise
|
43
|
+
raise Kamal::Cli::Healthcheck::Error, "container not unhealthy (#{status})"
|
45
44
|
end
|
46
|
-
rescue
|
45
|
+
rescue Kamal::Cli::Healthcheck::Error => e
|
47
46
|
if attempt <= max_attempts
|
48
47
|
info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
|
49
48
|
sleep attempt
|
data/lib/kamal/cli/main.rb
CHANGED
@@ -3,14 +3,14 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
3
3
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
4
4
|
def setup
|
5
5
|
print_runtime do
|
6
|
-
|
6
|
+
with_lock do
|
7
7
|
invoke_options = deploy_options
|
8
8
|
|
9
9
|
say "Ensure Docker is installed...", :magenta
|
10
10
|
invoke "kamal:cli:server:bootstrap", [], invoke_options
|
11
11
|
|
12
|
-
say "
|
13
|
-
invoke "kamal:cli:
|
12
|
+
say "Evaluate and push env files...", :magenta
|
13
|
+
invoke "kamal:cli:main:envify", [], invoke_options
|
14
14
|
|
15
15
|
invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
|
16
16
|
deploy
|
@@ -22,30 +22,25 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
22
22
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
23
23
|
def deploy
|
24
24
|
runtime = print_runtime do
|
25
|
-
|
26
|
-
invoke_options = deploy_options
|
25
|
+
invoke_options = deploy_options
|
27
26
|
|
28
|
-
|
29
|
-
|
27
|
+
say "Log into image registry...", :magenta
|
28
|
+
invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
|
30
29
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
30
|
+
if options[:skip_push]
|
31
|
+
say "Pull app image...", :magenta
|
32
|
+
invoke "kamal:cli:build:pull", [], invoke_options
|
33
|
+
else
|
34
|
+
say "Build and push app image...", :magenta
|
35
|
+
invoke "kamal:cli:build:deliver", [], invoke_options
|
36
|
+
end
|
38
37
|
|
38
|
+
with_lock do
|
39
39
|
run_hook "pre-deploy"
|
40
40
|
|
41
41
|
say "Ensure Traefik is running...", :magenta
|
42
42
|
invoke "kamal:cli:traefik:boot", [], invoke_options
|
43
43
|
|
44
|
-
if KAMAL.config.role(KAMAL.config.primary_role).running_traefik?
|
45
|
-
say "Ensure app can pass healthcheck...", :magenta
|
46
|
-
invoke "kamal:cli:healthcheck:perform", [], invoke_options
|
47
|
-
end
|
48
|
-
|
49
44
|
say "Detect stale containers...", :magenta
|
50
45
|
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
51
46
|
|
@@ -63,22 +58,19 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
63
58
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
64
59
|
def redeploy
|
65
60
|
runtime = print_runtime do
|
66
|
-
|
67
|
-
invoke_options = deploy_options
|
61
|
+
invoke_options = deploy_options
|
68
62
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
63
|
+
if options[:skip_push]
|
64
|
+
say "Pull app image...", :magenta
|
65
|
+
invoke "kamal:cli:build:pull", [], invoke_options
|
66
|
+
else
|
67
|
+
say "Build and push app image...", :magenta
|
68
|
+
invoke "kamal:cli:build:deliver", [], invoke_options
|
69
|
+
end
|
76
70
|
|
71
|
+
with_lock do
|
77
72
|
run_hook "pre-deploy"
|
78
73
|
|
79
|
-
say "Ensure app can pass healthcheck...", :magenta
|
80
|
-
invoke "kamal:cli:healthcheck:perform", [], invoke_options
|
81
|
-
|
82
74
|
say "Detect stale containers...", :magenta
|
83
75
|
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
84
76
|
|
@@ -93,7 +85,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
93
85
|
def rollback(version)
|
94
86
|
rolled_back = false
|
95
87
|
runtime = print_runtime do
|
96
|
-
|
88
|
+
with_lock do
|
97
89
|
invoke_options = deploy_options
|
98
90
|
|
99
91
|
KAMAL.config.version = version
|
@@ -134,6 +126,18 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
134
126
|
end
|
135
127
|
end
|
136
128
|
|
129
|
+
desc "docs", "Show Kamal documentation for configuration setting"
|
130
|
+
def docs(section = nil)
|
131
|
+
case section
|
132
|
+
when NilClass
|
133
|
+
puts Kamal::Configuration.validation_doc
|
134
|
+
else
|
135
|
+
puts Kamal::Configuration.const_get(section.titlecase.to_sym).validation_doc
|
136
|
+
end
|
137
|
+
rescue NameError
|
138
|
+
puts "No documentation found for #{section}"
|
139
|
+
end
|
140
|
+
|
137
141
|
desc "init", "Create config stub in config/deploy.yml and env stub in .env"
|
138
142
|
option :bundle, type: :boolean, default: false, desc: "Add Kamal to the Gemfile and create a bin/kamal binstub"
|
139
143
|
def init
|
@@ -185,23 +189,27 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
185
189
|
env_path = ".env"
|
186
190
|
end
|
187
191
|
|
188
|
-
|
192
|
+
if Pathname.new(File.expand_path(env_template_path)).exist?
|
193
|
+
File.write(env_path, ERB.new(File.read(env_template_path), trim_mode: "-").result, perm: 0600)
|
189
194
|
|
190
|
-
|
191
|
-
|
192
|
-
|
195
|
+
unless options[:skip_push]
|
196
|
+
reload_envs
|
197
|
+
invoke "kamal:cli:env:push", options
|
198
|
+
end
|
199
|
+
else
|
200
|
+
puts "Skipping envify (no #{env_template_path} exist)"
|
193
201
|
end
|
194
202
|
end
|
195
203
|
|
196
204
|
desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
|
197
205
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
198
206
|
def remove
|
199
|
-
|
200
|
-
|
207
|
+
confirming "This will remove all containers and images. Are you sure?" do
|
208
|
+
with_lock do
|
201
209
|
invoke "kamal:cli:traefik:remove", [], options.without(:confirmed)
|
202
210
|
invoke "kamal:cli:app:remove", [], options.without(:confirmed)
|
203
211
|
invoke "kamal:cli:accessory:remove", [ "all" ], options
|
204
|
-
invoke "kamal:cli:registry:logout", [], options.without(:confirmed)
|
212
|
+
invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true)
|
205
213
|
end
|
206
214
|
end
|
207
215
|
end
|
@@ -223,9 +231,6 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
223
231
|
desc "env", "Manage environment files"
|
224
232
|
subcommand "env", Kamal::Cli::Env
|
225
233
|
|
226
|
-
desc "healthcheck", "Healthcheck application"
|
227
|
-
subcommand "healthcheck", Kamal::Cli::Healthcheck
|
228
|
-
|
229
234
|
desc "lock", "Manage the deploy lock"
|
230
235
|
subcommand "lock", Kamal::Cli::Lock
|
231
236
|
|
@@ -246,11 +251,11 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
246
251
|
begin
|
247
252
|
on(KAMAL.hosts) do
|
248
253
|
KAMAL.roles_on(host).each do |role|
|
249
|
-
container_id = capture_with_info(*KAMAL.app(role: role).container_id_for_version(version))
|
254
|
+
container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
|
250
255
|
raise "Container not found" unless container_id.present?
|
251
256
|
end
|
252
257
|
end
|
253
|
-
rescue SSHKit::Runner::ExecuteError => e
|
258
|
+
rescue SSHKit::Runner::ExecuteError, SSHKit::Runner::MultipleExecuteError => e
|
254
259
|
if e.message =~ /Container not found/
|
255
260
|
say "Error looking for container version #{version}: #{e.message}"
|
256
261
|
return false
|
data/lib/kamal/cli/prune.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
class Kamal::Cli::Prune < Kamal::Cli::Base
|
2
2
|
desc "all", "Prune unused images and stopped containers"
|
3
3
|
def all
|
4
|
-
|
4
|
+
with_lock do
|
5
5
|
containers
|
6
6
|
images
|
7
7
|
end
|
@@ -9,7 +9,7 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
|
|
9
9
|
|
10
10
|
desc "images", "Prune unused images"
|
11
11
|
def images
|
12
|
-
|
12
|
+
with_lock do
|
13
13
|
on(KAMAL.hosts) do
|
14
14
|
execute *KAMAL.auditor.record("Pruned images"), verbosity: :debug
|
15
15
|
execute *KAMAL.prune.dangling_images
|
@@ -24,7 +24,7 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
|
|
24
24
|
retain = options.fetch(:retain, KAMAL.config.retain_containers)
|
25
25
|
raise "retain must be at least 1" if retain < 1
|
26
26
|
|
27
|
-
|
27
|
+
with_lock do
|
28
28
|
on(KAMAL.hosts) do
|
29
29
|
execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
|
30
30
|
execute *KAMAL.prune.app_containers(retain: retain)
|
data/lib/kamal/cli/registry.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
class Kamal::Cli::Registry < Kamal::Cli::Base
|
2
2
|
desc "login", "Log in to registry locally and remotely"
|
3
|
+
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
4
|
+
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
3
5
|
def login
|
4
|
-
run_locally { execute *KAMAL.registry.login }
|
5
|
-
on(KAMAL.hosts) { execute *KAMAL.registry.login }
|
6
|
-
# FIXME: This rescue needed?
|
7
|
-
rescue ArgumentError => e
|
8
|
-
puts e.message
|
6
|
+
run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
|
7
|
+
on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
|
9
8
|
end
|
10
9
|
|
11
|
-
desc "logout", "Log out of registry remotely"
|
10
|
+
desc "logout", "Log out of registry locally and remotely"
|
11
|
+
option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
|
12
|
+
option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
|
12
13
|
def logout
|
13
|
-
|
14
|
-
|
15
|
-
rescue ArgumentError => e
|
16
|
-
puts e.message
|
14
|
+
run_locally { execute *KAMAL.registry.logout } unless options[:skip_local]
|
15
|
+
on(KAMAL.hosts) { execute *KAMAL.registry.logout } unless options[:skip_remote]
|
17
16
|
end
|
18
17
|
end
|
data/lib/kamal/cli/server.rb
CHANGED
@@ -1,25 +1,49 @@
|
|
1
1
|
class Kamal::Cli::Server < Kamal::Cli::Base
|
2
|
+
desc "exec", "Run a custom command on the server (use --help to show options)"
|
3
|
+
option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
|
4
|
+
def exec(cmd)
|
5
|
+
hosts = KAMAL.hosts | KAMAL.accessory_hosts
|
6
|
+
|
7
|
+
case
|
8
|
+
when options[:interactive]
|
9
|
+
host = KAMAL.primary_host
|
10
|
+
|
11
|
+
say "Running '#{cmd}' on #{host} interactively...", :magenta
|
12
|
+
|
13
|
+
run_locally { exec KAMAL.server.run_over_ssh(cmd, host: host) }
|
14
|
+
else
|
15
|
+
say "Running '#{cmd}' on #{hosts.join(', ')}...", :magenta
|
16
|
+
|
17
|
+
on(hosts) do |host|
|
18
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{host}"), verbosity: :debug
|
19
|
+
puts_by_host host, capture_with_info(cmd)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
2
24
|
desc "bootstrap", "Set up Docker to run Kamal apps"
|
3
25
|
def bootstrap
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
26
|
+
with_lock do
|
27
|
+
missing = []
|
28
|
+
|
29
|
+
on(KAMAL.hosts | KAMAL.accessory_hosts) do |host|
|
30
|
+
unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
|
31
|
+
if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
|
32
|
+
info "Missing Docker on #{host}. Installing…"
|
33
|
+
execute *KAMAL.docker.install
|
34
|
+
else
|
35
|
+
missing << host
|
36
|
+
end
|
13
37
|
end
|
38
|
+
|
39
|
+
execute(*KAMAL.server.ensure_run_directory)
|
14
40
|
end
|
15
41
|
|
16
|
-
|
17
|
-
|
42
|
+
if missing.any?
|
43
|
+
raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and either `wget` or `curl`. Install Docker manually: https://docs.docker.com/engine/install/"
|
44
|
+
end
|
18
45
|
|
19
|
-
|
20
|
-
raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and either `wget` or `curl`. Install Docker manually: https://docs.docker.com/engine/install/"
|
46
|
+
run_hook "docker-setup"
|
21
47
|
end
|
22
|
-
|
23
|
-
run_hook "docker-setup"
|
24
48
|
end
|
25
49
|
end
|