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,16 @@
|
|
|
1
|
+
module Kamal::Commands::Accessory::Proxy
|
|
2
|
+
delegate :container_name, to: :"config.proxy_boot", prefix: :proxy
|
|
3
|
+
|
|
4
|
+
def deploy(target:)
|
|
5
|
+
proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def remove
|
|
9
|
+
proxy_exec :remove, service_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
def proxy_exec(*command)
|
|
14
|
+
docker :exec, proxy_container_name, "kamal-proxy", *command
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
class Kamal::Commands::Accessory < Kamal::Commands::Base
|
|
2
|
+
include Proxy
|
|
3
|
+
|
|
4
|
+
attr_reader :accessory_config
|
|
5
|
+
delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
|
|
6
|
+
:network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
|
|
7
|
+
:restart_policy, :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
|
|
8
|
+
to: :accessory_config
|
|
9
|
+
|
|
10
|
+
def initialize(config, name:)
|
|
11
|
+
super(config)
|
|
12
|
+
@accessory_config = config.accessory(name)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run(host: nil)
|
|
16
|
+
docker :run,
|
|
17
|
+
"--name", service_name,
|
|
18
|
+
"--detach",
|
|
19
|
+
"--restart", restart_policy,
|
|
20
|
+
*network_args,
|
|
21
|
+
*config.logging_args,
|
|
22
|
+
*publish_args,
|
|
23
|
+
*([ "--env", "KAMAL_HOST=\"#{host}\"" ] if host),
|
|
24
|
+
*env_args,
|
|
25
|
+
*volume_args,
|
|
26
|
+
*label_args,
|
|
27
|
+
*option_args,
|
|
28
|
+
image,
|
|
29
|
+
cmd
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def start
|
|
33
|
+
docker :container, :start, service_name
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def stop
|
|
37
|
+
docker :container, :stop, service_name
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def info(all: false, quiet: false)
|
|
41
|
+
docker :ps, *("-a" if all), *("-q" if quiet), *service_filter
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
|
45
|
+
pipe \
|
|
46
|
+
docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
|
47
|
+
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def follow_logs(timestamps: true, grep: nil, grep_options: nil)
|
|
51
|
+
run_over_ssh \
|
|
52
|
+
pipe \
|
|
53
|
+
docker(:logs, service_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
|
|
54
|
+
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def execute_in_existing_container(*command, interactive: false)
|
|
58
|
+
docker :exec,
|
|
59
|
+
(docker_interactive_args if interactive),
|
|
60
|
+
service_name,
|
|
61
|
+
*command
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def execute_in_new_container(*command, interactive: false)
|
|
65
|
+
docker :run,
|
|
66
|
+
(docker_interactive_args if interactive),
|
|
67
|
+
"--rm",
|
|
68
|
+
*network_args,
|
|
69
|
+
*env_args,
|
|
70
|
+
*volume_args,
|
|
71
|
+
*option_args,
|
|
72
|
+
image,
|
|
73
|
+
*command
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def execute_in_existing_container_over_ssh(*command)
|
|
77
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def execute_in_new_container_over_ssh(*command)
|
|
81
|
+
run_over_ssh execute_in_new_container(*command, interactive: true)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def run_over_ssh(command)
|
|
85
|
+
super command, host: hosts.first
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def ensure_local_file_present(local_file)
|
|
89
|
+
if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
|
|
90
|
+
raise "Missing file: #{local_file}"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def pull_image
|
|
95
|
+
docker :image, :pull, image
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def remove_service_directory
|
|
99
|
+
[ :rm, "-rf", service_name ]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def remove_container
|
|
103
|
+
docker :container, :prune, "--force", *service_filter
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def remove_image
|
|
107
|
+
docker :image, :rm, "--force", image
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def ensure_env_directory
|
|
111
|
+
make_directory env_directory
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
def service_filter
|
|
116
|
+
[ "--filter", "label=service=#{service_name}" ]
|
|
117
|
+
end
|
|
118
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module Kamal::Commands::App::Assets
|
|
2
|
+
def extract_assets
|
|
3
|
+
asset_container = "#{role.container_prefix}-assets"
|
|
4
|
+
|
|
5
|
+
combine \
|
|
6
|
+
make_directory(role.asset_extracted_directory),
|
|
7
|
+
[ *docker(:container, :rm, asset_container, "2> /dev/null"), "|| true" ],
|
|
8
|
+
docker(:container, :create, "--name", asset_container, config.absolute_image),
|
|
9
|
+
docker(:container, :cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_directory),
|
|
10
|
+
docker(:container, :rm, asset_container),
|
|
11
|
+
by: "&&"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def sync_asset_volumes(old_version: nil)
|
|
15
|
+
new_extracted_path, new_volume_path = role.asset_extracted_directory(config.version), role.asset_volume.host_path
|
|
16
|
+
if old_version.present?
|
|
17
|
+
old_extracted_path, old_volume_path = role.asset_extracted_directory(old_version), role.asset_volume(old_version).host_path
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
commands = [ make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path) ]
|
|
21
|
+
|
|
22
|
+
if old_version.present?
|
|
23
|
+
commands << copy_contents(new_extracted_path, old_volume_path, continue_on_error: true)
|
|
24
|
+
commands << copy_contents(old_extracted_path, new_volume_path, continue_on_error: true)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
chain *commands
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def clean_up_assets
|
|
31
|
+
chain \
|
|
32
|
+
find_and_remove_older_siblings(role.asset_extracted_directory),
|
|
33
|
+
find_and_remove_older_siblings(role.asset_volume_directory)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
def find_and_remove_older_siblings(path)
|
|
38
|
+
[
|
|
39
|
+
:find,
|
|
40
|
+
Pathname.new(path).dirname.to_s,
|
|
41
|
+
"-maxdepth 1",
|
|
42
|
+
"-name", "'#{role.name}-*'",
|
|
43
|
+
"!", "-name", Pathname.new(path).basename.to_s,
|
|
44
|
+
"-exec rm -rf \"{}\" +"
|
|
45
|
+
]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def copy_contents(source, destination, continue_on_error: false)
|
|
49
|
+
[ :cp, "-rnT", "#{source}", destination, *("|| true" if continue_on_error) ]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Kamal::Commands::App::Containers
|
|
2
|
+
DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
|
|
3
|
+
|
|
4
|
+
def list_containers
|
|
5
|
+
docker :container, :ls, "--all", *container_filter_args
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list_container_names
|
|
9
|
+
[ *list_containers, "--format", "'{{ .Names }}'" ]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def remove_container(version:)
|
|
13
|
+
pipe \
|
|
14
|
+
container_id_for(container_name: container_name(version)),
|
|
15
|
+
xargs(docker(:container, :rm))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def rename_container(version:, new_version:)
|
|
19
|
+
docker :rename, container_name(version), container_name(new_version)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def remove_containers
|
|
23
|
+
docker :container, :prune, "--force", *container_filter_args
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def container_health_log(version:)
|
|
27
|
+
pipe \
|
|
28
|
+
container_id_for(container_name: container_name(version)),
|
|
29
|
+
xargs(docker(:inspect, "--format", DOCKER_HEALTH_LOG_FORMAT))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module Kamal::Commands::App::ErrorPages
|
|
2
|
+
def create_error_pages_directory
|
|
3
|
+
make_directory(config.proxy_boot.error_pages_directory)
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def clean_up_error_pages
|
|
7
|
+
[ :find, config.proxy_boot.error_pages_directory, "-mindepth", "1", "-maxdepth", "1", "!", "-name", KAMAL.config.version, "-exec", "rm", "-rf", "{} +" ]
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Kamal::Commands::App::Execution
|
|
2
|
+
def execute_in_existing_container(*command, interactive: false, env:)
|
|
3
|
+
docker :exec,
|
|
4
|
+
(docker_interactive_args if interactive),
|
|
5
|
+
*argumentize("--env", env),
|
|
6
|
+
container_name,
|
|
7
|
+
*command
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def execute_in_new_container(*command, interactive: false, detach: false, env:)
|
|
11
|
+
docker :run,
|
|
12
|
+
(docker_interactive_args if interactive),
|
|
13
|
+
("--detach" if detach),
|
|
14
|
+
("--rm" unless detach),
|
|
15
|
+
"--name", container_name_for_exec,
|
|
16
|
+
"--network", "kamal",
|
|
17
|
+
*role&.env_args(host),
|
|
18
|
+
*argumentize("--env", env),
|
|
19
|
+
*role.logging_args,
|
|
20
|
+
*config.volume_args,
|
|
21
|
+
*role&.option_args,
|
|
22
|
+
config.absolute_image,
|
|
23
|
+
*command
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def execute_in_existing_container_over_ssh(*command, env:)
|
|
27
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true, env: env), host: host
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def execute_in_new_container_over_ssh(*command, env:)
|
|
31
|
+
run_over_ssh execute_in_new_container(*command, interactive: true, env: env), host: host
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
def container_name_for_exec
|
|
36
|
+
[ role.container_prefix, "exec", config.version, SecureRandom.hex(3) ].compact.join("-")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Kamal::Commands::App::Images
|
|
2
|
+
def list_images
|
|
3
|
+
docker :image, :ls, config.repository
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def remove_images
|
|
7
|
+
docker :image, :prune, "--all", "--force", *image_filter_args
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def tag_latest_image
|
|
11
|
+
docker :tag, config.absolute_image, config.latest_image
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Kamal::Commands::App::Logging
|
|
2
|
+
def logs(container_id: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
|
3
|
+
pipe \
|
|
4
|
+
container_id_command(container_id),
|
|
5
|
+
"xargs docker logs#{" --timestamps" if timestamps}#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
|
|
6
|
+
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def follow_logs(host:, container_id: nil, timestamps: true, lines: nil, grep: nil, grep_options: nil)
|
|
10
|
+
run_over_ssh \
|
|
11
|
+
pipe(
|
|
12
|
+
container_id_command(container_id),
|
|
13
|
+
"xargs docker logs#{" --timestamps" if timestamps}#{" --tail #{lines}" if lines} --follow 2>&1",
|
|
14
|
+
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
|
15
|
+
),
|
|
16
|
+
host: host
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def container_id_command(container_id)
|
|
22
|
+
case container_id
|
|
23
|
+
when Array then container_id
|
|
24
|
+
when String, Symbol then shell([ "echo #{container_id}" ])
|
|
25
|
+
else current_running_container_id
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Kamal::Commands::App::Proxy
|
|
2
|
+
delegate :container_name, to: :"config.proxy_boot", prefix: :proxy
|
|
3
|
+
|
|
4
|
+
def deploy(target:)
|
|
5
|
+
proxy_exec :deploy, role.container_prefix, *role.proxy.deploy_command_args(target: target)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def remove
|
|
9
|
+
proxy_exec :remove, role.container_prefix
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def live
|
|
13
|
+
proxy_exec :resume, role.container_prefix
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def maintenance(**options)
|
|
17
|
+
proxy_exec :stop, role.container_prefix, *role.proxy.stop_command_args(**options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def remove_proxy_app_directory
|
|
21
|
+
remove_directory config.proxy_boot.app_directory
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_ssl_directory
|
|
25
|
+
make_directory(File.join(config.proxy_boot.tls_directory, role.name))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
def proxy_exec(*command)
|
|
30
|
+
docker :exec, proxy_container_name, "kamal-proxy", *command
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
class Kamal::Commands::App < Kamal::Commands::Base
|
|
2
|
+
include Assets, Containers, ErrorPages, Execution, Images, Logging, Proxy
|
|
3
|
+
|
|
4
|
+
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
|
5
|
+
|
|
6
|
+
attr_reader :role, :host
|
|
7
|
+
|
|
8
|
+
delegate :container_name, to: :role
|
|
9
|
+
|
|
10
|
+
def initialize(config, role: nil, host: nil)
|
|
11
|
+
super(config)
|
|
12
|
+
@role = role
|
|
13
|
+
@host = host
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run(hostname: nil)
|
|
17
|
+
docker :run,
|
|
18
|
+
"--detach",
|
|
19
|
+
"--restart", role.restart_policy,
|
|
20
|
+
"--name", container_name,
|
|
21
|
+
"--network", "kamal",
|
|
22
|
+
*([ "--hostname", hostname ] if hostname),
|
|
23
|
+
"--env", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
|
24
|
+
"--env", "KAMAL_VERSION=\"#{config.version}\"",
|
|
25
|
+
"--env", "KAMAL_HOST=\"#{host}\"",
|
|
26
|
+
*([ "--env", "KAMAL_DESTINATION=\"#{config.destination}\"" ] if config.destination),
|
|
27
|
+
*role.env_args(host),
|
|
28
|
+
*role.logging_args,
|
|
29
|
+
*config.volume_args,
|
|
30
|
+
*role.asset_volume_args,
|
|
31
|
+
*role.label_args,
|
|
32
|
+
*role.option_args,
|
|
33
|
+
config.absolute_image,
|
|
34
|
+
role.cmd
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def start
|
|
38
|
+
docker :start, container_name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def status(version:)
|
|
42
|
+
pipe container_id_for_version(version), xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def stop(version: nil)
|
|
46
|
+
pipe \
|
|
47
|
+
version ? container_id_for_version(version) : current_running_container_id,
|
|
48
|
+
xargs(docker(:stop, *role.stop_args))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def info
|
|
52
|
+
docker :ps, *container_filter_args
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def current_running_container_id
|
|
57
|
+
current_running_container(format: "--quiet")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def container_id_for_version(version, only_running: false)
|
|
61
|
+
container_id_for(container_name: container_name(version), only_running: only_running)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def current_running_version
|
|
65
|
+
pipe \
|
|
66
|
+
current_running_container(format: "--format '{{.Names}}'"),
|
|
67
|
+
extract_version_from_name
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def list_versions(*docker_args, statuses: nil)
|
|
71
|
+
pipe \
|
|
72
|
+
docker(:ps, *container_filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
|
73
|
+
extract_version_from_name
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def ensure_env_directory
|
|
77
|
+
make_directory role.env_directory
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
def latest_image_id
|
|
82
|
+
docker :image, :ls, *argumentize("--filter", "reference=#{config.latest_image}"), "--format", "'{{.ID}}'"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def current_running_container(format:)
|
|
86
|
+
pipe \
|
|
87
|
+
shell(chain(latest_image_container(format: format), latest_container(format: format))),
|
|
88
|
+
[ :head, "-1" ]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def latest_image_container(format:)
|
|
92
|
+
latest_container format: format, filters: [ "ancestor=$(#{latest_image_id.join(" ")})" ]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def latest_container(format:, filters: nil)
|
|
96
|
+
docker :ps, "--latest", *format, *container_filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def container_filter_args(statuses: nil)
|
|
100
|
+
argumentize "--filter", container_filters(statuses: statuses)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def image_filter_args
|
|
104
|
+
argumentize "--filter", image_filters
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def extract_version_from_name
|
|
108
|
+
# Extract SHA from "service-role-dest-SHA"
|
|
109
|
+
%(while read line; do echo ${line##{role.container_prefix}-}; done)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def container_filters(statuses: nil)
|
|
113
|
+
[ "label=service=#{config.service}" ].tap do |filters|
|
|
114
|
+
filters << "label=destination=#{config.destination}"
|
|
115
|
+
filters << "label=role=#{role}" if role
|
|
116
|
+
statuses&.each do |status|
|
|
117
|
+
filters << "status=#{status}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def image_filters
|
|
123
|
+
[ "label=service=#{config.service}" ]
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class Kamal::Commands::Auditor < Kamal::Commands::Base
|
|
2
|
+
attr_reader :details
|
|
3
|
+
delegate :escape_shell_value, to: Kamal::Utils
|
|
4
|
+
|
|
5
|
+
def initialize(config, **details)
|
|
6
|
+
super(config)
|
|
7
|
+
@details = details
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Runs remotely
|
|
11
|
+
def record(line, **details)
|
|
12
|
+
combine \
|
|
13
|
+
make_run_directory,
|
|
14
|
+
append([ :echo, escape_shell_value(audit_line(line, **details)) ], audit_log_file)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def reveal
|
|
18
|
+
[ :tail, "-n", 50, audit_log_file ]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
def audit_log_file
|
|
23
|
+
file = [ config.service, config.destination, "audit.log" ].compact.join("-")
|
|
24
|
+
|
|
25
|
+
File.join(config.run_directory, file)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def audit_tags(**details)
|
|
29
|
+
tags(**self.details, **details)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def make_run_directory
|
|
33
|
+
[ :mkdir, "-p", config.run_directory ]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def audit_line(line, **details)
|
|
37
|
+
"#{audit_tags(**details).except(:version, :service_version, :service)} #{line}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
module Kamal::Commands
|
|
2
|
+
class Base
|
|
3
|
+
delegate :sensitive, :argumentize, to: Kamal::Utils
|
|
4
|
+
|
|
5
|
+
DOCKER_HEALTH_STATUS_FORMAT = "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'"
|
|
6
|
+
|
|
7
|
+
attr_accessor :config
|
|
8
|
+
|
|
9
|
+
def initialize(config)
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def run_over_ssh(*command, host:)
|
|
14
|
+
"ssh#{ssh_config_args}#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def container_id_for(container_name:, only_running: false)
|
|
18
|
+
docker :container, :ls, *("--all" unless only_running), "--filter", "'name=^#{container_name}$'", "--quiet"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def make_directory_for(remote_file)
|
|
22
|
+
make_directory Pathname.new(remote_file).dirname.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def make_directory(path)
|
|
26
|
+
[ :mkdir, "-p", path ]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def remove_directory(path)
|
|
30
|
+
[ :rm, "-r", path ]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def remove_file(path)
|
|
34
|
+
[ :rm, path ]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def ensure_docker_installed
|
|
38
|
+
combine \
|
|
39
|
+
ensure_local_docker_installed,
|
|
40
|
+
ensure_local_buildx_installed
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
def combine(*commands, by: "&&")
|
|
45
|
+
commands
|
|
46
|
+
.compact
|
|
47
|
+
.collect { |command| Array(command) + [ by ] }.flatten # Join commands
|
|
48
|
+
.tap { |commands| commands.pop } # Remove trailing combiner
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def chain(*commands)
|
|
52
|
+
combine *commands, by: ";"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def pipe(*commands)
|
|
56
|
+
combine *commands, by: "|"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def append(*commands)
|
|
60
|
+
combine *commands, by: ">>"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def write(*commands)
|
|
64
|
+
combine *commands, by: ">"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def any(*commands)
|
|
68
|
+
combine *commands, by: "||"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def substitute(*commands)
|
|
72
|
+
"\$\(#{commands.join(" ")}\)"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def xargs(command)
|
|
76
|
+
[ :xargs, command ].flatten
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def shell(command)
|
|
80
|
+
[ :sh, "-c", "'#{command.flatten.join(" ").gsub("'", "'\\\\''")}'" ]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def docker(*args)
|
|
84
|
+
args.compact.unshift :docker
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def pack(*args)
|
|
88
|
+
args.compact.unshift :pack
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def git(*args, path: nil)
|
|
92
|
+
[ :git, *([ "-C", path ] if path), *args.compact ]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def grep(*args)
|
|
96
|
+
args.compact.unshift :grep
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def tags(**details)
|
|
100
|
+
Kamal::Tags.from_config(config, **details)
|
|
101
|
+
end
|
|
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
|
+
|
|
116
|
+
def ssh_proxy_args
|
|
117
|
+
case config.ssh.proxy
|
|
118
|
+
when Net::SSH::Proxy::Jump
|
|
119
|
+
" -J #{config.ssh.proxy.jump_proxies}"
|
|
120
|
+
when Net::SSH::Proxy::Command
|
|
121
|
+
" -o ProxyCommand='#{config.ssh.proxy.command_line_template}'"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def ssh_keys_args
|
|
126
|
+
"#{ ssh_keys.join("") if ssh_keys}" + "#{" -o IdentitiesOnly=yes" if config.ssh&.keys_only}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def ssh_keys
|
|
130
|
+
config.ssh.keys&.map do |key|
|
|
131
|
+
" -i #{key}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def ensure_local_docker_installed
|
|
136
|
+
docker "--version"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def ensure_local_buildx_installed
|
|
140
|
+
docker :buildx, "version"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def docker_interactive_args
|
|
144
|
+
STDIN.isatty ? "-it" : "-i"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|