kamal 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|