kamal 0.16.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 +1021 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +239 -0
- data/lib/kamal/cli/app.rb +296 -0
- data/lib/kamal/cli/base.rb +171 -0
- data/lib/kamal/cli/build.rb +106 -0
- data/lib/kamal/cli/healthcheck.rb +20 -0
- data/lib/kamal/cli/lock.rb +37 -0
- data/lib/kamal/cli/main.rb +249 -0
- data/lib/kamal/cli/prune.rb +30 -0
- data/lib/kamal/cli/registry.rb +18 -0
- data/lib/kamal/cli/server.rb +21 -0
- data/lib/kamal/cli/templates/deploy.yml +74 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -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 +109 -0
- data/lib/kamal/cli/templates/template.env +2 -0
- data/lib/kamal/cli/traefik.rb +111 -0
- data/lib/kamal/cli.rb +7 -0
- data/lib/kamal/commander.rb +154 -0
- data/lib/kamal/commands/accessory.rb +113 -0
- data/lib/kamal/commands/app.rb +175 -0
- data/lib/kamal/commands/auditor.rb +28 -0
- data/lib/kamal/commands/base.rb +65 -0
- data/lib/kamal/commands/builder/base.rb +60 -0
- data/lib/kamal/commands/builder/multiarch/remote.rb +51 -0
- data/lib/kamal/commands/builder/multiarch.rb +29 -0
- data/lib/kamal/commands/builder/native/cached.rb +16 -0
- data/lib/kamal/commands/builder/native/remote.rb +59 -0
- data/lib/kamal/commands/builder/native.rb +20 -0
- data/lib/kamal/commands/builder.rb +62 -0
- data/lib/kamal/commands/docker.rb +21 -0
- data/lib/kamal/commands/healthcheck.rb +57 -0
- data/lib/kamal/commands/hook.rb +14 -0
- data/lib/kamal/commands/lock.rb +63 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +20 -0
- data/lib/kamal/commands/traefik.rb +104 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +169 -0
- data/lib/kamal/configuration/boot.rb +20 -0
- data/lib/kamal/configuration/builder.rb +114 -0
- data/lib/kamal/configuration/role.rb +155 -0
- data/lib/kamal/configuration/ssh.rb +38 -0
- data/lib/kamal/configuration/sshkit.rb +20 -0
- data/lib/kamal/configuration.rb +251 -0
- data/lib/kamal/sshkit_with_ext.rb +104 -0
- data/lib/kamal/tags.rb +39 -0
- data/lib/kamal/utils/healthcheck_poller.rb +39 -0
- data/lib/kamal/utils/sensitive.rb +19 -0
- data/lib/kamal/utils.rb +100 -0
- data/lib/kamal/version.rb +3 -0
- data/lib/kamal.rb +10 -0
- metadata +266 -0
@@ -0,0 +1,175 @@
|
|
1
|
+
class Kamal::Commands::App < Kamal::Commands::Base
|
2
|
+
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
3
|
+
|
4
|
+
attr_reader :role
|
5
|
+
|
6
|
+
def initialize(config, role: nil)
|
7
|
+
super(config)
|
8
|
+
@role = role
|
9
|
+
end
|
10
|
+
|
11
|
+
def start_or_run(hostname: nil)
|
12
|
+
combine start, run(hostname: hostname), by: "||"
|
13
|
+
end
|
14
|
+
|
15
|
+
def run(hostname: nil)
|
16
|
+
role = config.role(self.role)
|
17
|
+
|
18
|
+
docker :run,
|
19
|
+
"--detach",
|
20
|
+
"--restart unless-stopped",
|
21
|
+
"--name", container_name,
|
22
|
+
*(["--hostname", hostname] if hostname),
|
23
|
+
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
24
|
+
*role.env_args,
|
25
|
+
*role.health_check_args,
|
26
|
+
*config.logging_args,
|
27
|
+
*config.volume_args,
|
28
|
+
*role.label_args,
|
29
|
+
*role.option_args,
|
30
|
+
config.absolute_image,
|
31
|
+
role.cmd
|
32
|
+
end
|
33
|
+
|
34
|
+
def start
|
35
|
+
docker :start, container_name
|
36
|
+
end
|
37
|
+
|
38
|
+
def status(version:)
|
39
|
+
pipe container_id_for_version(version), xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
|
40
|
+
end
|
41
|
+
|
42
|
+
def stop(version: nil)
|
43
|
+
pipe \
|
44
|
+
version ? container_id_for_version(version) : current_running_container_id,
|
45
|
+
xargs(config.stop_wait_time ? docker(:stop, "-t", config.stop_wait_time) : docker(:stop))
|
46
|
+
end
|
47
|
+
|
48
|
+
def info
|
49
|
+
docker :ps, *filter_args
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def logs(since: nil, lines: nil, grep: nil)
|
54
|
+
pipe \
|
55
|
+
current_running_container_id,
|
56
|
+
"xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
|
57
|
+
("grep '#{grep}'" if grep)
|
58
|
+
end
|
59
|
+
|
60
|
+
def follow_logs(host:, grep: nil)
|
61
|
+
run_over_ssh \
|
62
|
+
pipe(
|
63
|
+
current_running_container_id,
|
64
|
+
"xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
65
|
+
(%(grep "#{grep}") if grep)
|
66
|
+
),
|
67
|
+
host: host
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def execute_in_existing_container(*command, interactive: false)
|
72
|
+
docker :exec,
|
73
|
+
("-it" if interactive),
|
74
|
+
container_name,
|
75
|
+
*command
|
76
|
+
end
|
77
|
+
|
78
|
+
def execute_in_new_container(*command, interactive: false)
|
79
|
+
role = config.role(self.role)
|
80
|
+
|
81
|
+
docker :run,
|
82
|
+
("-it" if interactive),
|
83
|
+
"--rm",
|
84
|
+
*config.env_args,
|
85
|
+
*config.volume_args,
|
86
|
+
*role&.option_args,
|
87
|
+
config.absolute_image,
|
88
|
+
*command
|
89
|
+
end
|
90
|
+
|
91
|
+
def execute_in_existing_container_over_ssh(*command, host:)
|
92
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true), host: host
|
93
|
+
end
|
94
|
+
|
95
|
+
def execute_in_new_container_over_ssh(*command, host:)
|
96
|
+
run_over_ssh execute_in_new_container(*command, interactive: true), host: host
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def current_running_container_id
|
101
|
+
docker :ps, "--quiet", *filter_args(statuses: ACTIVE_DOCKER_STATUSES), "--latest"
|
102
|
+
end
|
103
|
+
|
104
|
+
def container_id_for_version(version, only_running: false)
|
105
|
+
container_id_for(container_name: container_name(version), only_running: only_running)
|
106
|
+
end
|
107
|
+
|
108
|
+
def current_running_version
|
109
|
+
list_versions("--latest", statuses: ACTIVE_DOCKER_STATUSES)
|
110
|
+
end
|
111
|
+
|
112
|
+
def list_versions(*docker_args, statuses: nil)
|
113
|
+
pipe \
|
114
|
+
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
115
|
+
%(while read line; do echo ${line##{service_role_dest}-}; done) # Extract SHA from "service-role-dest-SHA"
|
116
|
+
end
|
117
|
+
|
118
|
+
def list_containers
|
119
|
+
docker :container, :ls, "--all", *filter_args
|
120
|
+
end
|
121
|
+
|
122
|
+
def list_container_names
|
123
|
+
[ *list_containers, "--format", "'{{ .Names }}'" ]
|
124
|
+
end
|
125
|
+
|
126
|
+
def remove_container(version:)
|
127
|
+
pipe \
|
128
|
+
container_id_for(container_name: container_name(version)),
|
129
|
+
xargs(docker(:container, :rm))
|
130
|
+
end
|
131
|
+
|
132
|
+
def rename_container(version:, new_version:)
|
133
|
+
docker :rename, container_name(version), container_name(new_version)
|
134
|
+
end
|
135
|
+
|
136
|
+
def remove_containers
|
137
|
+
docker :container, :prune, "--force", *filter_args
|
138
|
+
end
|
139
|
+
|
140
|
+
def list_images
|
141
|
+
docker :image, :ls, config.repository
|
142
|
+
end
|
143
|
+
|
144
|
+
def remove_images
|
145
|
+
docker :image, :prune, "--all", "--force", *filter_args
|
146
|
+
end
|
147
|
+
|
148
|
+
def tag_current_as_latest
|
149
|
+
docker :tag, config.absolute_image, config.latest_image
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
private
|
154
|
+
def container_name(version = nil)
|
155
|
+
[ config.service, role, config.destination, version || config.version ].compact.join("-")
|
156
|
+
end
|
157
|
+
|
158
|
+
def filter_args(statuses: nil)
|
159
|
+
argumentize "--filter", filters(statuses: statuses)
|
160
|
+
end
|
161
|
+
|
162
|
+
def service_role_dest
|
163
|
+
[config.service, role, config.destination].compact.join("-")
|
164
|
+
end
|
165
|
+
|
166
|
+
def filters(statuses: nil)
|
167
|
+
[ "label=service=#{config.service}" ].tap do |filters|
|
168
|
+
filters << "label=destination=#{config.destination}" if config.destination
|
169
|
+
filters << "label=role=#{role}" if role
|
170
|
+
statuses&.each do |status|
|
171
|
+
filters << "status=#{status}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class Kamal::Commands::Auditor < Kamal::Commands::Base
|
2
|
+
attr_reader :details
|
3
|
+
|
4
|
+
def initialize(config, **details)
|
5
|
+
super(config)
|
6
|
+
@details = details
|
7
|
+
end
|
8
|
+
|
9
|
+
# Runs remotely
|
10
|
+
def record(line, **details)
|
11
|
+
append \
|
12
|
+
[ :echo, audit_tags(**details).except(:version, :service_version).to_s, line ],
|
13
|
+
audit_log_file
|
14
|
+
end
|
15
|
+
|
16
|
+
def reveal
|
17
|
+
[ :tail, "-n", 50, audit_log_file ]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
def audit_log_file
|
22
|
+
[ "kamal", config.service, config.destination, "audit.log" ].compact.join("-")
|
23
|
+
end
|
24
|
+
|
25
|
+
def audit_tags(**details)
|
26
|
+
tags(**self.details, **details)
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,65 @@
|
|
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
|
+
DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
|
7
|
+
|
8
|
+
attr_accessor :config
|
9
|
+
|
10
|
+
def initialize(config)
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def run_over_ssh(*command, host:)
|
15
|
+
"ssh".tap do |cmd|
|
16
|
+
if config.ssh.proxy && config.ssh.proxy.is_a?(Net::SSH::Proxy::Jump)
|
17
|
+
cmd << " -J #{config.ssh.proxy.jump_proxies}"
|
18
|
+
elsif config.ssh.proxy && config.ssh.proxy.is_a?(Net::SSH::Proxy::Command)
|
19
|
+
cmd << " -o ProxyCommand='#{config.ssh.proxy.command_line_template}'"
|
20
|
+
end
|
21
|
+
cmd << " -t #{config.ssh.user}@#{host} '#{command.join(" ")}'"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def container_id_for(container_name:, only_running: false)
|
26
|
+
docker :container, :ls, *("--all" unless only_running), "--filter", "name=^#{container_name}$", "--quiet"
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def combine(*commands, by: "&&")
|
31
|
+
commands
|
32
|
+
.compact
|
33
|
+
.collect { |command| Array(command) + [ by ] }.flatten # Join commands
|
34
|
+
.tap { |commands| commands.pop } # Remove trailing combiner
|
35
|
+
end
|
36
|
+
|
37
|
+
def chain(*commands)
|
38
|
+
combine *commands, by: ";"
|
39
|
+
end
|
40
|
+
|
41
|
+
def pipe(*commands)
|
42
|
+
combine *commands, by: "|"
|
43
|
+
end
|
44
|
+
|
45
|
+
def append(*commands)
|
46
|
+
combine *commands, by: ">>"
|
47
|
+
end
|
48
|
+
|
49
|
+
def write(*commands)
|
50
|
+
combine *commands, by: ">"
|
51
|
+
end
|
52
|
+
|
53
|
+
def xargs(command)
|
54
|
+
[ :xargs, command ].flatten
|
55
|
+
end
|
56
|
+
|
57
|
+
def docker(*args)
|
58
|
+
args.compact.unshift :docker
|
59
|
+
end
|
60
|
+
|
61
|
+
def tags(**details)
|
62
|
+
Kamal::Tags.from_config(config, **details)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
3
|
+
class BuilderError < StandardError; end
|
4
|
+
|
5
|
+
delegate :argumentize, to: Kamal::Utils
|
6
|
+
delegate :args, :secrets, :dockerfile, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, to: :builder_config
|
7
|
+
|
8
|
+
def clean
|
9
|
+
docker :image, :rm, "--force", config.absolute_image
|
10
|
+
end
|
11
|
+
|
12
|
+
def pull
|
13
|
+
docker :pull, config.absolute_image
|
14
|
+
end
|
15
|
+
|
16
|
+
def build_options
|
17
|
+
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile ]
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_context
|
21
|
+
config.builder.context
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
private
|
26
|
+
def build_tags
|
27
|
+
[ "-t", config.absolute_image, "-t", config.latest_image ]
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_cache
|
31
|
+
if cache_to && cache_from
|
32
|
+
["--cache-to", cache_to,
|
33
|
+
"--cache-from", cache_from]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_labels
|
38
|
+
argumentize "--label", { service: config.service }
|
39
|
+
end
|
40
|
+
|
41
|
+
def build_args
|
42
|
+
argumentize "--build-arg", args, sensitive: true
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_secrets
|
46
|
+
argumentize "--secret", secrets.collect { |secret| [ "id", secret ] }
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_dockerfile
|
50
|
+
if Pathname.new(File.expand_path(dockerfile)).exist?
|
51
|
+
argumentize "--file", dockerfile
|
52
|
+
else
|
53
|
+
raise BuilderError, "Missing #{dockerfile}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def builder_config
|
58
|
+
config.builder
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Kamal::Commands::Builder::Multiarch::Remote < Kamal::Commands::Builder::Multiarch
|
2
|
+
def create
|
3
|
+
combine \
|
4
|
+
create_contexts,
|
5
|
+
create_local_buildx,
|
6
|
+
append_remote_buildx
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove
|
10
|
+
combine \
|
11
|
+
remove_contexts,
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def builder_name
|
17
|
+
super + "-remote"
|
18
|
+
end
|
19
|
+
|
20
|
+
def builder_name_with_arch(arch)
|
21
|
+
"#{builder_name}-#{arch}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_local_buildx
|
25
|
+
docker :buildx, :create, "--name", builder_name, builder_name_with_arch(local_arch), "--platform", "linux/#{local_arch}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def append_remote_buildx
|
29
|
+
docker :buildx, :create, "--append", "--name", builder_name, builder_name_with_arch(remote_arch), "--platform", "linux/#{remote_arch}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_contexts
|
33
|
+
combine \
|
34
|
+
create_context(local_arch, local_host),
|
35
|
+
create_context(remote_arch, remote_host)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_context(arch, host)
|
39
|
+
docker :context, :create, builder_name_with_arch(arch), "--description", "'#{builder_name} #{arch} native host'", "--docker", "'host=#{host}'"
|
40
|
+
end
|
41
|
+
|
42
|
+
def remove_contexts
|
43
|
+
combine \
|
44
|
+
remove_context(local_arch),
|
45
|
+
remove_context(remote_arch)
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_context(arch)
|
49
|
+
docker :context, :rm, builder_name_with_arch(arch)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Kamal::Commands::Builder::Multiarch < Kamal::Commands::Builder::Base
|
2
|
+
def create
|
3
|
+
docker :buildx, :create, "--use", "--name", builder_name
|
4
|
+
end
|
5
|
+
|
6
|
+
def remove
|
7
|
+
docker :buildx, :rm, builder_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def push
|
11
|
+
docker :buildx, :build,
|
12
|
+
"--push",
|
13
|
+
"--platform", "linux/amd64,linux/arm64",
|
14
|
+
"--builder", builder_name,
|
15
|
+
*build_options,
|
16
|
+
build_context
|
17
|
+
end
|
18
|
+
|
19
|
+
def info
|
20
|
+
combine \
|
21
|
+
docker(:context, :ls),
|
22
|
+
docker(:buildx, :ls)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def builder_name
|
27
|
+
"kamal-#{config.service}-multiarch"
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Kamal::Commands::Builder::Native::Cached < Kamal::Commands::Builder::Native
|
2
|
+
def create
|
3
|
+
docker :buildx, :create, "--use", "--driver=docker-container"
|
4
|
+
end
|
5
|
+
|
6
|
+
def remove
|
7
|
+
docker :buildx, :rm, builder_name
|
8
|
+
end
|
9
|
+
|
10
|
+
def push
|
11
|
+
docker :buildx, :build,
|
12
|
+
"--push",
|
13
|
+
*build_options,
|
14
|
+
build_context
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class Kamal::Commands::Builder::Native::Remote < Kamal::Commands::Builder::Native
|
2
|
+
def create
|
3
|
+
chain \
|
4
|
+
create_context,
|
5
|
+
create_buildx
|
6
|
+
end
|
7
|
+
|
8
|
+
def remove
|
9
|
+
chain \
|
10
|
+
remove_context,
|
11
|
+
remove_buildx
|
12
|
+
end
|
13
|
+
|
14
|
+
def push
|
15
|
+
docker :buildx, :build,
|
16
|
+
"--push",
|
17
|
+
"--platform", platform,
|
18
|
+
"--builder", builder_name,
|
19
|
+
*build_options,
|
20
|
+
build_context
|
21
|
+
end
|
22
|
+
|
23
|
+
def info
|
24
|
+
chain \
|
25
|
+
docker(:context, :ls),
|
26
|
+
docker(:buildx, :ls)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
def builder_name
|
32
|
+
"kamal-#{config.service}-native-remote"
|
33
|
+
end
|
34
|
+
|
35
|
+
def builder_name_with_arch
|
36
|
+
"#{builder_name}-#{remote_arch}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def platform
|
40
|
+
"linux/#{remote_arch}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_context
|
44
|
+
docker :context, :create,
|
45
|
+
builder_name_with_arch, "--description", "'#{builder_name} #{remote_arch} native host'", "--docker", "'host=#{remote_host}'"
|
46
|
+
end
|
47
|
+
|
48
|
+
def remove_context
|
49
|
+
docker :context, :rm, builder_name_with_arch
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_buildx
|
53
|
+
docker :buildx, :create, "--name", builder_name, builder_name_with_arch, "--platform", platform
|
54
|
+
end
|
55
|
+
|
56
|
+
def remove_buildx
|
57
|
+
docker :buildx, :rm, builder_name
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Kamal::Commands::Builder::Native < Kamal::Commands::Builder::Base
|
2
|
+
def create
|
3
|
+
# No-op on native without cache
|
4
|
+
end
|
5
|
+
|
6
|
+
def remove
|
7
|
+
# No-op on native without cache
|
8
|
+
end
|
9
|
+
|
10
|
+
def push
|
11
|
+
combine \
|
12
|
+
docker(:build, *build_options, build_context),
|
13
|
+
docker(:push, config.absolute_image),
|
14
|
+
docker(:push, config.latest_image)
|
15
|
+
end
|
16
|
+
|
17
|
+
def info
|
18
|
+
# No-op on native
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Kamal::Commands::Builder < Kamal::Commands::Base
|
2
|
+
delegate :create, :remove, :push, :clean, :pull, :info, to: :target
|
3
|
+
|
4
|
+
def name
|
5
|
+
target.class.to_s.remove("Kamal::Commands::Builder::").underscore.inquiry
|
6
|
+
end
|
7
|
+
|
8
|
+
def target
|
9
|
+
case
|
10
|
+
when !config.builder.multiarch? && !config.builder.cached?
|
11
|
+
native
|
12
|
+
when !config.builder.multiarch? && config.builder.cached?
|
13
|
+
native_cached
|
14
|
+
when config.builder.local? && config.builder.remote?
|
15
|
+
multiarch_remote
|
16
|
+
when config.builder.remote?
|
17
|
+
native_remote
|
18
|
+
else
|
19
|
+
multiarch
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def native
|
24
|
+
@native ||= Kamal::Commands::Builder::Native.new(config)
|
25
|
+
end
|
26
|
+
|
27
|
+
def native_cached
|
28
|
+
@native ||= Kamal::Commands::Builder::Native::Cached.new(config)
|
29
|
+
end
|
30
|
+
|
31
|
+
def native_remote
|
32
|
+
@native ||= Kamal::Commands::Builder::Native::Remote.new(config)
|
33
|
+
end
|
34
|
+
|
35
|
+
def multiarch
|
36
|
+
@multiarch ||= Kamal::Commands::Builder::Multiarch.new(config)
|
37
|
+
end
|
38
|
+
|
39
|
+
def multiarch_remote
|
40
|
+
@multiarch_remote ||= Kamal::Commands::Builder::Multiarch::Remote.new(config)
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def ensure_local_dependencies_installed
|
45
|
+
if name.native?
|
46
|
+
ensure_local_docker_installed
|
47
|
+
else
|
48
|
+
combine \
|
49
|
+
ensure_local_docker_installed,
|
50
|
+
ensure_local_buildx_installed
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def ensure_local_docker_installed
|
56
|
+
docker "--version"
|
57
|
+
end
|
58
|
+
|
59
|
+
def ensure_local_buildx_installed
|
60
|
+
docker :buildx, "version"
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Kamal::Commands::Docker < Kamal::Commands::Base
|
2
|
+
# Install Docker using the https://github.com/docker/docker-install convenience script.
|
3
|
+
def install
|
4
|
+
pipe [ :curl, "-fsSL", "https://get.docker.com" ], :sh
|
5
|
+
end
|
6
|
+
|
7
|
+
# Checks the Docker client version. Fails if Docker is not installed.
|
8
|
+
def installed?
|
9
|
+
docker "-v"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Checks the Docker server version. Fails if Docker is not running.
|
13
|
+
def running?
|
14
|
+
docker :version
|
15
|
+
end
|
16
|
+
|
17
|
+
# Do we have superuser access to install Docker and start system services?
|
18
|
+
def superuser?
|
19
|
+
[ '[ "${EUID:-$(id -u)}" -eq 0 ]' ]
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Kamal::Commands::Healthcheck < Kamal::Commands::Base
|
2
|
+
EXPOSED_PORT = 3999
|
3
|
+
|
4
|
+
def run
|
5
|
+
web = config.role(:web)
|
6
|
+
|
7
|
+
docker :run,
|
8
|
+
"--detach",
|
9
|
+
"--name", container_name_with_version,
|
10
|
+
"--publish", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
|
11
|
+
"--label", "service=#{container_name}",
|
12
|
+
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
13
|
+
*web.env_args,
|
14
|
+
*web.health_check_args,
|
15
|
+
*config.volume_args,
|
16
|
+
*web.option_args,
|
17
|
+
config.absolute_image,
|
18
|
+
web.cmd
|
19
|
+
end
|
20
|
+
|
21
|
+
def status
|
22
|
+
pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
|
23
|
+
end
|
24
|
+
|
25
|
+
def container_health_log
|
26
|
+
pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_LOG_FORMAT))
|
27
|
+
end
|
28
|
+
|
29
|
+
def logs
|
30
|
+
pipe container_id, xargs(docker(:logs, "--tail", 50, "2>&1"))
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
pipe container_id, xargs(docker(:stop))
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove
|
38
|
+
pipe container_id, xargs(docker(:container, :rm))
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def container_name
|
43
|
+
[ "healthcheck", config.service, config.destination ].compact.join("-")
|
44
|
+
end
|
45
|
+
|
46
|
+
def container_name_with_version
|
47
|
+
"#{container_name}-#{config.version}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def container_id
|
51
|
+
container_id_for(container_name: container_name_with_version)
|
52
|
+
end
|
53
|
+
|
54
|
+
def health_url
|
55
|
+
"http://localhost:#{EXPOSED_PORT}#{config.healthcheck["path"]}"
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Kamal::Commands::Hook < Kamal::Commands::Base
|
2
|
+
def run(hook, **details)
|
3
|
+
[ hook_file(hook), env: tags(**details).env ]
|
4
|
+
end
|
5
|
+
|
6
|
+
def hook_exists?(hook)
|
7
|
+
Pathname.new(hook_file(hook)).exist?
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def hook_file(hook)
|
12
|
+
"#{config.hooks_path}/#{hook}"
|
13
|
+
end
|
14
|
+
end
|