dash 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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/bin/dash +18 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +342 -0
- data/lib/kamal/cli/alias/command.rb +10 -0
- data/lib/kamal/cli/app/assets.rb +24 -0
- data/lib/kamal/cli/app/boot.rb +126 -0
- data/lib/kamal/cli/app/error_pages.rb +33 -0
- data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
- data/lib/kamal/cli/app.rb +368 -0
- data/lib/kamal/cli/base.rb +324 -0
- data/lib/kamal/cli/build/clone.rb +59 -0
- data/lib/kamal/cli/build/port_forwarding.rb +66 -0
- data/lib/kamal/cli/build.rb +242 -0
- data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
- data/lib/kamal/cli/healthcheck/error.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +42 -0
- data/lib/kamal/cli/lock.rb +34 -0
- data/lib/kamal/cli/main.rb +299 -0
- data/lib/kamal/cli/proxy.rb +419 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +49 -0
- data/lib/kamal/cli/secrets.rb +50 -0
- data/lib/kamal/cli/server.rb +70 -0
- data/lib/kamal/cli/templates/deploy.yml +102 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +22 -0
- data/lib/kamal/cli.rb +9 -0
- data/lib/kamal/commander/specifics.rb +62 -0
- data/lib/kamal/commander.rb +230 -0
- data/lib/kamal/commands/accessory/proxy.rb +16 -0
- data/lib/kamal/commands/accessory.rb +118 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/error_pages.rb +9 -0
- data/lib/kamal/commands/app/execution.rb +38 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +28 -0
- data/lib/kamal/commands/app/proxy.rb +32 -0
- data/lib/kamal/commands/app.rb +125 -0
- data/lib/kamal/commands/auditor.rb +39 -0
- data/lib/kamal/commands/base.rb +147 -0
- data/lib/kamal/commands/builder/base.rb +143 -0
- data/lib/kamal/commands/builder/clone.rb +32 -0
- data/lib/kamal/commands/builder/cloud.rb +22 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +20 -0
- data/lib/kamal/commands/builder/pack.rb +46 -0
- data/lib/kamal/commands/builder/remote.rb +75 -0
- data/lib/kamal/commands/builder.rb +54 -0
- data/lib/kamal/commands/docker.rb +50 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/loadbalancer.rb +130 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +150 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +38 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +280 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +29 -0
- data/lib/kamal/configuration/builder.rb +218 -0
- data/lib/kamal/configuration/docs/accessory.yml +160 -0
- data/lib/kamal/configuration/docs/alias.yml +29 -0
- data/lib/kamal/configuration/docs/boot.yml +21 -0
- data/lib/kamal/configuration/docs/builder.yml +132 -0
- data/lib/kamal/configuration/docs/configuration.yml +228 -0
- data/lib/kamal/configuration/docs/env.yml +118 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/output.yml +25 -0
- data/lib/kamal/configuration/docs/proxy.yml +207 -0
- data/lib/kamal/configuration/docs/registry.yml +64 -0
- data/lib/kamal/configuration/docs/role.yml +54 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +81 -0
- data/lib/kamal/configuration/docs/sshkit.yml +31 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +42 -0
- data/lib/kamal/configuration/loadbalancer.rb +34 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/output.rb +34 -0
- data/lib/kamal/configuration/proxy/boot.rb +124 -0
- data/lib/kamal/configuration/proxy/run.rb +152 -0
- data/lib/kamal/configuration/proxy.rb +156 -0
- data/lib/kamal/configuration/registry.rb +40 -0
- data/lib/kamal/configuration/role.rb +247 -0
- data/lib/kamal/configuration/servers.rb +25 -0
- data/lib/kamal/configuration/ssh.rb +76 -0
- data/lib/kamal/configuration/sshkit.rb +26 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +13 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +15 -0
- data/lib/kamal/configuration/validator/configuration.rb +6 -0
- data/lib/kamal/configuration/validator/env.rb +54 -0
- data/lib/kamal/configuration/validator/proxy.rb +47 -0
- data/lib/kamal/configuration/validator/registry.rb +27 -0
- data/lib/kamal/configuration/validator/role.rb +13 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +251 -0
- data/lib/kamal/configuration/volume.rb +29 -0
- data/lib/kamal/configuration.rb +465 -0
- data/lib/kamal/docker.rb +30 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +37 -0
- 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 +59 -0
- data/lib/kamal/secrets/adapters/base.rb +33 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
- data/lib/kamal/secrets/adapters/doppler.rb +57 -0
- data/lib/kamal/secrets/adapters/enpass.rb +71 -0
- data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
- data/lib/kamal/secrets/adapters/one_password.rb +104 -0
- data/lib/kamal/secrets/adapters/passbolt.rb +129 -0
- data/lib/kamal/secrets/adapters/test.rb +16 -0
- data/lib/kamal/secrets/adapters.rb +16 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +47 -0
- data/lib/kamal/secrets.rb +53 -0
- data/lib/kamal/sshkit_with_ext.rb +273 -0
- data/lib/kamal/tags.rb +40 -0
- data/lib/kamal/utils/sensitive.rb +20 -0
- data/lib/kamal/utils.rb +110 -0
- data/lib/kamal/version.rb +3 -0
- data/lib/kamal.rb +15 -0
- metadata +388 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class Kamal::Cli::Build::Clone
|
|
2
|
+
attr_reader :sshkit
|
|
3
|
+
delegate :info, :error, :execute, :capture_with_info, to: :sshkit
|
|
4
|
+
|
|
5
|
+
def initialize(sshkit)
|
|
6
|
+
@sshkit = sshkit
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def prepare
|
|
10
|
+
begin
|
|
11
|
+
clone_repo
|
|
12
|
+
rescue SSHKit::Command::Failed => e
|
|
13
|
+
if e.message =~ /already exists and is not an empty directory/
|
|
14
|
+
reset
|
|
15
|
+
else
|
|
16
|
+
raise Kamal::Cli::Build::BuildError, "Failed to clone repo: #{e.message}"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
validate!
|
|
21
|
+
rescue Kamal::Cli::Build::BuildError => e
|
|
22
|
+
error "Error preparing clone: #{e.message}, deleting and retrying..."
|
|
23
|
+
|
|
24
|
+
FileUtils.rm_rf KAMAL.config.builder.clone_directory
|
|
25
|
+
clone_repo
|
|
26
|
+
validate!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
def clone_repo
|
|
31
|
+
info "Cloning repo into build directory `#{KAMAL.config.builder.build_directory}`..."
|
|
32
|
+
|
|
33
|
+
FileUtils.mkdir_p KAMAL.config.builder.clone_directory
|
|
34
|
+
execute *KAMAL.builder.clone
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def reset
|
|
38
|
+
info "Resetting local clone as `#{KAMAL.config.builder.build_directory}` already exists..."
|
|
39
|
+
|
|
40
|
+
KAMAL.builder.clone_reset_steps.each { |step| execute *step }
|
|
41
|
+
rescue SSHKit::Command::Failed => e
|
|
42
|
+
raise Kamal::Cli::Build::BuildError, "Failed to clone repo: #{e.message}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validate!
|
|
46
|
+
status = capture_with_info(*KAMAL.builder.clone_status).strip
|
|
47
|
+
|
|
48
|
+
unless status.empty?
|
|
49
|
+
raise Kamal::Cli::Build::BuildError, "Clone in #{KAMAL.config.builder.build_directory} is dirty, #{status}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
revision = capture_with_info(*KAMAL.builder.clone_revision).strip
|
|
53
|
+
if revision != Kamal::Git.revision
|
|
54
|
+
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}`"
|
|
55
|
+
end
|
|
56
|
+
rescue SSHKit::Command::Failed => e
|
|
57
|
+
raise Kamal::Cli::Build::BuildError, "Failed to validate clone: #{e.message}"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
require "concurrent/atomic/count_down_latch"
|
|
2
|
+
|
|
3
|
+
class Kamal::Cli::Build::PortForwarding
|
|
4
|
+
attr_reader :hosts, :port, :ssh_options
|
|
5
|
+
|
|
6
|
+
def initialize(hosts, port, **ssh_options)
|
|
7
|
+
@hosts = hosts
|
|
8
|
+
@port = port
|
|
9
|
+
@ssh_options = ssh_options
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def forward
|
|
13
|
+
@done = false
|
|
14
|
+
forward_ports
|
|
15
|
+
|
|
16
|
+
yield
|
|
17
|
+
ensure
|
|
18
|
+
stop
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
def stop
|
|
23
|
+
@done = true
|
|
24
|
+
@threads.to_a.each(&:join)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def forward_ports
|
|
28
|
+
ready = Concurrent::CountDownLatch.new(hosts.size)
|
|
29
|
+
|
|
30
|
+
@threads = hosts.map do |host|
|
|
31
|
+
Thread.new do
|
|
32
|
+
begin
|
|
33
|
+
Net::SSH.start(host, ssh_options[:user], **ssh_options.except(:user)) do |ssh|
|
|
34
|
+
ssh.forward.remote(port, "localhost", port, "127.0.0.1") do |remote_port, bind_address|
|
|
35
|
+
if remote_port == :error
|
|
36
|
+
raise "Failed to establish port forward on #{host}"
|
|
37
|
+
else
|
|
38
|
+
ready.count_down
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
ssh.loop(0.1) do
|
|
43
|
+
if @done
|
|
44
|
+
ssh.forward.cancel_remote(port, "127.0.0.1")
|
|
45
|
+
break
|
|
46
|
+
else
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
rescue Exception => e
|
|
52
|
+
error "Error setting up port forwarding to #{host}: #{e.class}: #{e.message}"
|
|
53
|
+
error e.backtrace.join("\n")
|
|
54
|
+
|
|
55
|
+
raise
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
raise "Timed out waiting for port forwarding to be established" unless ready.wait(30)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def error(message)
|
|
64
|
+
SSHKit.config.output.error(message)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
class Kamal::Cli::Build < Kamal::Cli::Base
|
|
2
|
+
class BuildError < StandardError; end
|
|
3
|
+
|
|
4
|
+
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
|
5
|
+
def deliver
|
|
6
|
+
invoke :push
|
|
7
|
+
invoke :pull
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
desc "push", "Build and push app image to registry"
|
|
11
|
+
option :output, type: :string, default: "registry", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
|
|
12
|
+
option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
|
|
13
|
+
def push
|
|
14
|
+
cli = self
|
|
15
|
+
|
|
16
|
+
# Ensure pre-connect hooks run before the build, they may be needed for a remote builder
|
|
17
|
+
# or the pre-build hooks.
|
|
18
|
+
pre_connect_if_required
|
|
19
|
+
|
|
20
|
+
ensure_docker_installed
|
|
21
|
+
setup_local_registry if KAMAL.registry.local?
|
|
22
|
+
login_to_registry_locally unless KAMAL.registry.local?
|
|
23
|
+
|
|
24
|
+
run_hook "pre-build"
|
|
25
|
+
|
|
26
|
+
uncommitted_changes = Kamal::Git.uncommitted_changes
|
|
27
|
+
|
|
28
|
+
if KAMAL.config.builder.git_clone?
|
|
29
|
+
if uncommitted_changes.present?
|
|
30
|
+
say "Building from a local git clone, so ignoring these uncommitted changes:\n #{uncommitted_changes}", :yellow
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
run_locally do
|
|
34
|
+
Clone.new(self).prepare
|
|
35
|
+
end
|
|
36
|
+
elsif uncommitted_changes.present?
|
|
37
|
+
say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
forward_local_registry_port_for_remote_builder do
|
|
41
|
+
with_env(KAMAL.config.builder.secrets) do
|
|
42
|
+
run_locally do
|
|
43
|
+
begin
|
|
44
|
+
execute *KAMAL.builder.inspect_builder
|
|
45
|
+
rescue SSHKit::Command::Failed => e
|
|
46
|
+
if e.message =~ /(context not found|no builder|no compatible builder|does not exist)/
|
|
47
|
+
warn "Missing compatible builder, so creating a new one first"
|
|
48
|
+
begin
|
|
49
|
+
cli.remove
|
|
50
|
+
rescue SSHKit::Command::Failed
|
|
51
|
+
raise unless e.message =~ /(context not found|no builder|does not exist)/
|
|
52
|
+
end
|
|
53
|
+
cli.create
|
|
54
|
+
else
|
|
55
|
+
raise
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Get the command here to ensure the Dir.chdir doesn't interfere with it
|
|
60
|
+
push = KAMAL.builder.push(cli.options[:output], no_cache: cli.options[:no_cache])
|
|
61
|
+
|
|
62
|
+
KAMAL.with_verbosity(:debug) do
|
|
63
|
+
Dir.chdir(KAMAL.config.builder.build_directory) { execute *push, env: KAMAL.builder.push_env }
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
desc "pull", "Pull app image from registry onto servers"
|
|
71
|
+
def pull
|
|
72
|
+
login_to_registry_remotely unless KAMAL.registry.local?
|
|
73
|
+
|
|
74
|
+
forward_local_registry_port(KAMAL.hosts, **KAMAL.config.ssh.options) do
|
|
75
|
+
if (first_hosts = mirror_hosts).any?
|
|
76
|
+
# Pull on a single host per mirror first to seed them
|
|
77
|
+
say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
|
|
78
|
+
pull_on_hosts(first_hosts)
|
|
79
|
+
say "Pulling image on remaining hosts...", :magenta
|
|
80
|
+
pull_on_hosts(KAMAL.app_hosts - first_hosts)
|
|
81
|
+
else
|
|
82
|
+
pull_on_hosts(KAMAL.app_hosts)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
desc "create", "Create a build setup"
|
|
88
|
+
def create
|
|
89
|
+
if (remote_host = KAMAL.config.builder.remote)
|
|
90
|
+
connect_to_remote_host(remote_host)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
run_locally do
|
|
94
|
+
begin
|
|
95
|
+
debug "Using builder: #{KAMAL.builder.name}"
|
|
96
|
+
execute *KAMAL.builder.create
|
|
97
|
+
rescue SSHKit::Command::Failed => e
|
|
98
|
+
if e.message =~ /stderr=(.*)/
|
|
99
|
+
error "Couldn't create remote builder: #{$1}"
|
|
100
|
+
false
|
|
101
|
+
else
|
|
102
|
+
raise
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
desc "remove", "Remove build setup"
|
|
109
|
+
def remove
|
|
110
|
+
run_locally do
|
|
111
|
+
debug "Using builder: #{KAMAL.builder.name}"
|
|
112
|
+
execute *KAMAL.builder.remove
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
desc "details", "Show build setup"
|
|
117
|
+
def details
|
|
118
|
+
run_locally do
|
|
119
|
+
puts "Builder: #{KAMAL.builder.name}"
|
|
120
|
+
puts capture(*KAMAL.builder.info)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
|
|
125
|
+
option :output, type: :string, default: "docker", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
|
|
126
|
+
option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
|
|
127
|
+
def dev
|
|
128
|
+
cli = self
|
|
129
|
+
|
|
130
|
+
ensure_docker_installed
|
|
131
|
+
|
|
132
|
+
docker_included_files = Set.new(Kamal::Docker.included_files)
|
|
133
|
+
git_uncommitted_files = Set.new(Kamal::Git.uncommitted_files)
|
|
134
|
+
git_untracked_files = Set.new(Kamal::Git.untracked_files)
|
|
135
|
+
|
|
136
|
+
docker_uncommitted_files = docker_included_files & git_uncommitted_files
|
|
137
|
+
if docker_uncommitted_files.any?
|
|
138
|
+
say "WARNING: Files with uncommitted changes will be present in the dev container:", :yellow
|
|
139
|
+
docker_uncommitted_files.sort.each { |f| say " #{f}", :yellow }
|
|
140
|
+
say
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
docker_untracked_files = docker_included_files & git_untracked_files
|
|
144
|
+
if docker_untracked_files.any?
|
|
145
|
+
say "WARNING: Untracked files will be present in the dev container:", :yellow
|
|
146
|
+
docker_untracked_files.sort.each { |f| say " #{f}", :yellow }
|
|
147
|
+
say
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
with_env(KAMAL.config.builder.secrets) do
|
|
151
|
+
run_locally do
|
|
152
|
+
build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true, no_cache: cli.options[:no_cache])
|
|
153
|
+
KAMAL.with_verbosity(:debug) do
|
|
154
|
+
execute(*build)
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
def connect_to_remote_host(remote_host)
|
|
162
|
+
remote_uri = URI.parse(remote_host)
|
|
163
|
+
if remote_uri.scheme == "ssh"
|
|
164
|
+
host = SSHKit::Host.new(
|
|
165
|
+
hostname: remote_uri.host,
|
|
166
|
+
ssh_options: { user: remote_uri.user, port: remote_uri.port }.compact
|
|
167
|
+
)
|
|
168
|
+
on(host, options) do
|
|
169
|
+
execute "true"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def mirror_hosts
|
|
175
|
+
if KAMAL.app_hosts.many?
|
|
176
|
+
mirror_hosts = Concurrent::Hash.new
|
|
177
|
+
on(KAMAL.app_hosts) do |host|
|
|
178
|
+
first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
|
|
179
|
+
mirror_hosts[first_mirror] ||= host.to_s if first_mirror
|
|
180
|
+
rescue SSHKit::Command::Failed => e
|
|
181
|
+
raise unless e.message =~ /error calling index: reflect: slice index out of range/
|
|
182
|
+
end
|
|
183
|
+
mirror_hosts.values
|
|
184
|
+
else
|
|
185
|
+
[]
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def pull_on_hosts(hosts)
|
|
190
|
+
on(hosts) do
|
|
191
|
+
execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
|
|
192
|
+
execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
|
|
193
|
+
execute *KAMAL.builder.pull
|
|
194
|
+
execute *KAMAL.builder.validate_image
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def setup_local_registry
|
|
199
|
+
run_locally do
|
|
200
|
+
execute *KAMAL.registry.setup
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def login_to_registry_locally
|
|
205
|
+
run_locally do
|
|
206
|
+
execute *KAMAL.registry.login
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def login_to_registry_remotely
|
|
211
|
+
on(KAMAL.app_hosts) do
|
|
212
|
+
execute *KAMAL.registry.login
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def forward_local_registry_port_for_remote_builder(&block)
|
|
217
|
+
if KAMAL.builder.remote?
|
|
218
|
+
remote_uri = URI(KAMAL.config.builder.remote)
|
|
219
|
+
forward_local_registry_port([ remote_uri.host ], **remote_builder_ssh_options(remote_uri), &block)
|
|
220
|
+
else
|
|
221
|
+
yield
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def forward_local_registry_port(hosts, **ssh_options, &block)
|
|
226
|
+
if KAMAL.config.registry.local?
|
|
227
|
+
say "Setting up local registry port forwarding to #{hosts.join(', ')}..."
|
|
228
|
+
PortForwarding.new(hosts, KAMAL.config.registry.local_port, **ssh_options).forward(&block)
|
|
229
|
+
else
|
|
230
|
+
yield
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def remote_builder_ssh_options(remote_uri)
|
|
235
|
+
{ user: remote_uri.user,
|
|
236
|
+
port: remote_uri.port,
|
|
237
|
+
keepalive: KAMAL.config.ssh.options[:keepalive],
|
|
238
|
+
keepalive_interval: KAMAL.config.ssh.options[:keepalive_interval],
|
|
239
|
+
logger: KAMAL.config.ssh.options[:logger]
|
|
240
|
+
}.compact
|
|
241
|
+
end
|
|
242
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require "concurrent/ivar"
|
|
2
|
+
|
|
3
|
+
class Kamal::Cli::Healthcheck::Barrier
|
|
4
|
+
def initialize
|
|
5
|
+
@ivar = Concurrent::IVar.new
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def close
|
|
9
|
+
set(false)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def open
|
|
13
|
+
set(true)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def wait
|
|
17
|
+
unless opened?
|
|
18
|
+
raise Kamal::Cli::Healthcheck::Error.new("Halted at barrier")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
def opened?
|
|
24
|
+
@ivar.value
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def set(value)
|
|
28
|
+
@ivar.set(value)
|
|
29
|
+
true
|
|
30
|
+
rescue Concurrent::MultipleAssignmentError
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Kamal::Cli::Healthcheck::Poller
|
|
2
|
+
extend self
|
|
3
|
+
|
|
4
|
+
def wait_for_healthy(&block)
|
|
5
|
+
attempt = 1
|
|
6
|
+
timeout_at = Time.now + KAMAL.config.deploy_timeout
|
|
7
|
+
readiness_delay = KAMAL.config.readiness_delay
|
|
8
|
+
|
|
9
|
+
begin
|
|
10
|
+
status = block.call
|
|
11
|
+
|
|
12
|
+
if status == "running"
|
|
13
|
+
# Wait for the readiness delay and confirm it is still running
|
|
14
|
+
if readiness_delay > 0
|
|
15
|
+
info "Container is running, waiting for readiness delay of #{readiness_delay} seconds"
|
|
16
|
+
sleep readiness_delay
|
|
17
|
+
status = block.call
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
unless %w[ running healthy ].include?(status)
|
|
22
|
+
raise Kamal::Cli::Healthcheck::Error, "container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})"
|
|
23
|
+
end
|
|
24
|
+
rescue Kamal::Cli::Healthcheck::Error => e
|
|
25
|
+
time_left = timeout_at - Time.now
|
|
26
|
+
if time_left > 0
|
|
27
|
+
sleep [ attempt, time_left ].min
|
|
28
|
+
attempt += 1
|
|
29
|
+
retry
|
|
30
|
+
else
|
|
31
|
+
raise
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
info "Container is healthy!"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def info(message)
|
|
40
|
+
SSHKit.config.output.info(message)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class Kamal::Cli::Lock < Kamal::Cli::Base
|
|
2
|
+
desc "status", "Report lock status"
|
|
3
|
+
def status
|
|
4
|
+
handle_missing_lock do
|
|
5
|
+
puts capture_lock_status
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
desc "acquire", "Acquire the deploy lock"
|
|
10
|
+
option :message, aliases: "-m", type: :string, desc: "A lock message", required: true
|
|
11
|
+
def acquire
|
|
12
|
+
ensure_run_directory
|
|
13
|
+
|
|
14
|
+
raise_if_locked do
|
|
15
|
+
execute_lock_acquire(options[:message])
|
|
16
|
+
say "Acquired the deploy lock"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "release", "Release the deploy lock"
|
|
21
|
+
def release
|
|
22
|
+
handle_missing_lock do
|
|
23
|
+
execute_lock_release
|
|
24
|
+
say "Released the deploy lock"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
def handle_missing_lock
|
|
30
|
+
yield
|
|
31
|
+
rescue LockMissingError
|
|
32
|
+
say "There is no deploy lock"
|
|
33
|
+
end
|
|
34
|
+
end
|