nocoffee-kamal 2.3.0.1
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/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +287 -0
- data/lib/kamal/cli/alias/command.rb +9 -0
- data/lib/kamal/cli/app/boot.rb +125 -0
- data/lib/kamal/cli/app/prepare_assets.rb +24 -0
- data/lib/kamal/cli/app.rb +335 -0
- data/lib/kamal/cli/base.rb +198 -0
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +162 -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 +45 -0
- data/lib/kamal/cli/main.rb +279 -0
- data/lib/kamal/cli/proxy.rb +257 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +17 -0
- data/lib/kamal/cli/secrets.rb +43 -0
- data/lib/kamal/cli/server.rb +48 -0
- data/lib/kamal/cli/templates/deploy.yml +98 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.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-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/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +17 -0
- data/lib/kamal/cli.rb +8 -0
- data/lib/kamal/commander/specifics.rb +54 -0
- data/lib/kamal/commander.rb +176 -0
- data/lib/kamal/commands/accessory.rb +113 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/execution.rb +30 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +18 -0
- data/lib/kamal/commands/app/proxy.rb +16 -0
- data/lib/kamal/commands/app.rb +115 -0
- data/lib/kamal/commands/auditor.rb +33 -0
- data/lib/kamal/commands/base.rb +98 -0
- data/lib/kamal/commands/builder/base.rb +111 -0
- data/lib/kamal/commands/builder/clone.rb +31 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +14 -0
- data/lib/kamal/commands/builder/remote.rb +63 -0
- data/lib/kamal/commands/builder.rb +56 -0
- data/lib/kamal/commands/docker.rb +34 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +87 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +14 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +186 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +25 -0
- data/lib/kamal/configuration/builder.rb +191 -0
- data/lib/kamal/configuration/docs/accessory.yml +100 -0
- data/lib/kamal/configuration/docs/alias.yml +26 -0
- data/lib/kamal/configuration/docs/boot.yml +19 -0
- data/lib/kamal/configuration/docs/builder.yml +110 -0
- data/lib/kamal/configuration/docs/configuration.yml +178 -0
- data/lib/kamal/configuration/docs/env.yml +85 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/proxy.yml +110 -0
- data/lib/kamal/configuration/docs/registry.yml +52 -0
- data/lib/kamal/configuration/docs/role.yml +53 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +70 -0
- data/lib/kamal/configuration/docs/sshkit.yml +23 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +29 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/proxy.rb +63 -0
- data/lib/kamal/configuration/registry.rb +32 -0
- data/lib/kamal/configuration/role.rb +220 -0
- data/lib/kamal/configuration/servers.rb +18 -0
- data/lib/kamal/configuration/ssh.rb +57 -0
- data/lib/kamal/configuration/sshkit.rb +22 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +9 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +13 -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 +15 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +11 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +171 -0
- data/lib/kamal/configuration/volume.rb +22 -0
- data/lib/kamal/configuration.rb +393 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +27 -0
- data/lib/kamal/secrets/adapters/base.rb +23 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
- data/lib/kamal/secrets/adapters/one_password.rb +70 -0
- data/lib/kamal/secrets/adapters/test.rb +14 -0
- data/lib/kamal/secrets/adapters.rb +14 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
- data/lib/kamal/secrets.rb +42 -0
- data/lib/kamal/sshkit_with_ext.rb +142 -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 +14 -0
- metadata +349 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
class Kamal::Commands::Proxy < Kamal::Commands::Base
|
|
2
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
3
|
+
|
|
4
|
+
def run
|
|
5
|
+
docker :run,
|
|
6
|
+
"--name", container_name,
|
|
7
|
+
"--network", "kamal",
|
|
8
|
+
"--detach",
|
|
9
|
+
"--restart", "unless-stopped",
|
|
10
|
+
"--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy",
|
|
11
|
+
"\$\(#{get_boot_options.join(" ")}\)",
|
|
12
|
+
config.proxy_image
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def start
|
|
16
|
+
docker :container, :start, container_name
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def stop(name: container_name)
|
|
20
|
+
docker :container, :stop, name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def start_or_run
|
|
24
|
+
combine start, run, by: "||"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def info
|
|
28
|
+
docker :ps, "--filter", "name=^#{container_name}$"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def version
|
|
32
|
+
pipe \
|
|
33
|
+
docker(:inspect, container_name, "--format '{{.Config.Image}}'"),
|
|
34
|
+
[ :cut, "-d:", "-f2" ]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
|
|
38
|
+
pipe \
|
|
39
|
+
docker(:logs, container_name, ("--since #{since}" if since), ("--tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
|
|
40
|
+
("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def follow_logs(host:, timestamps: true, grep: nil, grep_options: nil)
|
|
44
|
+
run_over_ssh pipe(
|
|
45
|
+
docker(:logs, container_name, ("--timestamps" if timestamps), "--tail", "10", "--follow", "2>&1"),
|
|
46
|
+
(%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
|
|
47
|
+
).join(" "), host: host
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def remove_container
|
|
51
|
+
docker :container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=kamal-proxy"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def remove_image
|
|
55
|
+
docker :image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=kamal-proxy"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def cleanup_traefik
|
|
59
|
+
chain \
|
|
60
|
+
docker(:container, :stop, "traefik"),
|
|
61
|
+
combine(
|
|
62
|
+
docker(:container, :prune, "--force", "--filter", "label=org.opencontainers.image.title=Traefik"),
|
|
63
|
+
docker(:image, :prune, "--all", "--force", "--filter", "label=org.opencontainers.image.title=Traefik")
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def ensure_proxy_directory
|
|
68
|
+
make_directory config.proxy_directory
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def remove_proxy_directory
|
|
72
|
+
remove_directory config.proxy_directory
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def get_boot_options
|
|
76
|
+
combine [ :cat, config.proxy_options_file ], [ :echo, "\"#{config.proxy_options_default.join(" ")}\"" ], by: "||"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def reset_boot_options
|
|
80
|
+
remove_file config.proxy_options_file
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
def container_name
|
|
85
|
+
config.proxy_container_name
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
require "active_support/duration"
|
|
2
|
+
require "active_support/core_ext/numeric/time"
|
|
3
|
+
|
|
4
|
+
class Kamal::Commands::Prune < Kamal::Commands::Base
|
|
5
|
+
def dangling_images
|
|
6
|
+
docker :image, :prune, "--force", "--filter", "label=service=#{config.service}"
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def tagged_images
|
|
10
|
+
pipe \
|
|
11
|
+
docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
|
|
12
|
+
grep("-v -w \"#{active_image_list}\""),
|
|
13
|
+
"while read image tag; do docker rmi $tag; done"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def app_containers(retain:)
|
|
17
|
+
pipe \
|
|
18
|
+
docker(:ps, "-q", "-a", *service_filter, *stopped_containers_filters),
|
|
19
|
+
"tail -n +#{retain + 1}",
|
|
20
|
+
"while read container_id; do docker rm $container_id; done"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def stopped_containers_filters
|
|
25
|
+
[ "created", "exited", "dead" ].flat_map { |status| [ "--filter", "status=#{status}" ] }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def active_image_list
|
|
29
|
+
# Pull the images that are used by any containers
|
|
30
|
+
# Append repo:latest - to avoid deleting the latest tag
|
|
31
|
+
# Append repo:<none> - to avoid deleting dangling images that are in use. Unused dangling images are deleted separately
|
|
32
|
+
"$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=#{config.service} | tr -d '\\n')#{config.latest_image}\\|#{config.repository}:<none>"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def service_filter
|
|
36
|
+
[ "--filter", "label=service=#{config.service}" ]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Kamal::Commands::Registry < Kamal::Commands::Base
|
|
2
|
+
delegate :registry, to: :config
|
|
3
|
+
|
|
4
|
+
def login
|
|
5
|
+
docker :login,
|
|
6
|
+
registry.server,
|
|
7
|
+
"-u", sensitive(Kamal::Utils.escape_shell_value(registry.username)),
|
|
8
|
+
"-p", sensitive(Kamal::Utils.escape_shell_value(registry.password))
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def logout
|
|
12
|
+
docker :logout, registry.server
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Kamal::Commands::Server < Kamal::Commands::Base
|
|
2
|
+
def ensure_run_directory
|
|
3
|
+
make_directory config.run_directory
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def remove_app_directory
|
|
7
|
+
remove_directory config.app_directory
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def app_directory_count
|
|
11
|
+
pipe \
|
|
12
|
+
[ :ls, config.apps_directory ],
|
|
13
|
+
[ :wc, "-l" ]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
class Kamal::Configuration::Accessory
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
DEFAULT_NETWORK = "kamal"
|
|
5
|
+
|
|
6
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
7
|
+
|
|
8
|
+
attr_reader :name, :accessory_config, :env
|
|
9
|
+
|
|
10
|
+
def initialize(name, config:)
|
|
11
|
+
@name, @config, @accessory_config = name.inquiry, config, config.raw_config["accessories"][name]
|
|
12
|
+
|
|
13
|
+
validate! \
|
|
14
|
+
accessory_config,
|
|
15
|
+
example: validation_yml["accessories"]["mysql"],
|
|
16
|
+
context: "accessories/#{name}",
|
|
17
|
+
with: Kamal::Configuration::Validator::Accessory
|
|
18
|
+
|
|
19
|
+
@env = Kamal::Configuration::Env.new \
|
|
20
|
+
config: accessory_config.fetch("env", {}),
|
|
21
|
+
secrets: config.secrets,
|
|
22
|
+
context: "accessories/#{name}/env"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def service_name
|
|
26
|
+
accessory_config["service"] || "#{config.service}-#{name}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def image
|
|
30
|
+
accessory_config["image"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def hosts
|
|
34
|
+
hosts_from_host || hosts_from_hosts || hosts_from_roles
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def port
|
|
38
|
+
if port = accessory_config["port"]&.to_s
|
|
39
|
+
port.include?(":") ? port : "#{port}:#{port}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def network_args
|
|
44
|
+
argumentize "--network", network
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def publish_args
|
|
48
|
+
argumentize "--publish", port if port
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def labels
|
|
52
|
+
default_labels.merge(accessory_config["labels"] || {})
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def label_args
|
|
56
|
+
argumentize "--label", labels
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def env_args
|
|
60
|
+
[ *env.clear_args, *argumentize("--env-file", secrets_path) ]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def env_directory
|
|
64
|
+
File.join(config.env_directory, "accessories")
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def secrets_io
|
|
68
|
+
env.secrets_io
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def secrets_path
|
|
72
|
+
File.join(config.env_directory, "accessories", "#{name}.env")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def files
|
|
76
|
+
accessory_config["files"]&.to_h do |local_to_remote_mapping|
|
|
77
|
+
local_file, remote_file = local_to_remote_mapping.split(":")
|
|
78
|
+
[ expand_local_file(local_file), expand_remote_file(remote_file) ]
|
|
79
|
+
end || {}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def directories
|
|
83
|
+
accessory_config["directories"]&.to_h do |host_to_container_mapping|
|
|
84
|
+
host_path, container_path = host_to_container_mapping.split(":")
|
|
85
|
+
[ expand_host_path(host_path), container_path ]
|
|
86
|
+
end || {}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def volumes
|
|
90
|
+
specific_volumes + remote_files_as_volumes + remote_directories_as_volumes
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def volume_args
|
|
94
|
+
argumentize "--volume", volumes
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def option_args
|
|
98
|
+
if args = accessory_config["options"]
|
|
99
|
+
optionize args
|
|
100
|
+
else
|
|
101
|
+
[]
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def cmd
|
|
106
|
+
accessory_config["cmd"]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
private
|
|
110
|
+
attr_accessor :config
|
|
111
|
+
|
|
112
|
+
def default_labels
|
|
113
|
+
{ "service" => service_name }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def expand_local_file(local_file)
|
|
117
|
+
if local_file.end_with?("erb")
|
|
118
|
+
with_clear_env_loaded { read_dynamic_file(local_file) }
|
|
119
|
+
else
|
|
120
|
+
Pathname.new(File.expand_path(local_file)).to_s
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def with_clear_env_loaded
|
|
125
|
+
env.clear.each { |k, v| ENV[k] = v }
|
|
126
|
+
yield
|
|
127
|
+
ensure
|
|
128
|
+
env.clear.each { |k, v| ENV.delete(k) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def read_dynamic_file(local_file)
|
|
132
|
+
StringIO.new(ERB.new(IO.read(local_file)).result)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def expand_remote_file(remote_file)
|
|
136
|
+
service_name + remote_file
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def specific_volumes
|
|
140
|
+
accessory_config["volumes"] || []
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def remote_files_as_volumes
|
|
144
|
+
accessory_config["files"]&.collect do |local_to_remote_mapping|
|
|
145
|
+
_, remote_file = local_to_remote_mapping.split(":")
|
|
146
|
+
"#{service_data_directory + remote_file}:#{remote_file}"
|
|
147
|
+
end || []
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def remote_directories_as_volumes
|
|
151
|
+
accessory_config["directories"]&.collect do |host_to_container_mapping|
|
|
152
|
+
host_path, container_path = host_to_container_mapping.split(":")
|
|
153
|
+
[ expand_host_path(host_path), container_path ].join(":")
|
|
154
|
+
end || []
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def expand_host_path(host_path)
|
|
158
|
+
absolute_path?(host_path) ? host_path : File.join(service_data_directory, host_path)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def absolute_path?(path)
|
|
162
|
+
Pathname.new(path).absolute?
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def service_data_directory
|
|
166
|
+
"$PWD/#{service_name}"
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def hosts_from_host
|
|
170
|
+
[ accessory_config["host"] ] if accessory_config.key?("host")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def hosts_from_hosts
|
|
174
|
+
accessory_config["hosts"] if accessory_config.key?("hosts")
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def hosts_from_roles
|
|
178
|
+
if accessory_config.key?("roles")
|
|
179
|
+
accessory_config["roles"].flat_map { |role| config.role(role).hosts }
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def network
|
|
184
|
+
accessory_config["network"] || DEFAULT_NETWORK
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Kamal::Configuration::Alias
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
attr_reader :name, :command
|
|
5
|
+
|
|
6
|
+
def initialize(name, config:)
|
|
7
|
+
@name, @command = name.inquiry, config.raw_config["aliases"][name]
|
|
8
|
+
|
|
9
|
+
validate! \
|
|
10
|
+
command,
|
|
11
|
+
example: validation_yml["aliases"]["uname"],
|
|
12
|
+
context: "aliases/#{name}",
|
|
13
|
+
with: Kamal::Configuration::Validator::Alias
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
class Kamal::Configuration::Boot
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
attr_reader :boot_config, :host_count
|
|
5
|
+
|
|
6
|
+
def initialize(config:)
|
|
7
|
+
@boot_config = config.raw_config.boot || {}
|
|
8
|
+
@host_count = config.all_hosts.count
|
|
9
|
+
validate! boot_config
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def limit
|
|
13
|
+
limit = boot_config["limit"]
|
|
14
|
+
|
|
15
|
+
if limit.to_s.end_with?("%")
|
|
16
|
+
[ host_count * limit.to_i / 100, 1 ].max
|
|
17
|
+
else
|
|
18
|
+
limit
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def wait
|
|
23
|
+
boot_config["wait"]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
class Kamal::Configuration::Builder
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
attr_reader :config, :builder_config
|
|
5
|
+
delegate :image, :service, to: :config
|
|
6
|
+
delegate :server, to: :"config.registry"
|
|
7
|
+
|
|
8
|
+
def initialize(config:)
|
|
9
|
+
@config = config
|
|
10
|
+
@builder_config = config.raw_config.builder || {}
|
|
11
|
+
@image = config.image
|
|
12
|
+
@server = config.registry.server
|
|
13
|
+
@service = config.service
|
|
14
|
+
|
|
15
|
+
validate! builder_config, with: Kamal::Configuration::Validator::Builder
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def to_h
|
|
19
|
+
builder_config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def remote
|
|
23
|
+
builder_config["remote"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def arches
|
|
27
|
+
Array(builder_config.fetch("arch", default_arch))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def local_arches
|
|
31
|
+
@local_arches ||= if local_disabled?
|
|
32
|
+
[]
|
|
33
|
+
elsif remote
|
|
34
|
+
arches & [ Kamal::Utils.docker_arch ]
|
|
35
|
+
else
|
|
36
|
+
arches
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def remote_arches
|
|
41
|
+
@remote_arches ||= if remote
|
|
42
|
+
arches - local_arches
|
|
43
|
+
else
|
|
44
|
+
[]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def remote?
|
|
49
|
+
remote_arches.any?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def local?
|
|
53
|
+
!local_disabled? && (arches.empty? || local_arches.any?)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def cached?
|
|
57
|
+
!!builder_config["cache"]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def args
|
|
61
|
+
builder_config["args"] || {}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def secrets
|
|
65
|
+
(builder_config["secrets"] || []).to_h { |key| [ key, config.secrets[key] ] }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def dockerfile
|
|
69
|
+
builder_config["dockerfile"] || "Dockerfile"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def target
|
|
73
|
+
builder_config["target"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def context
|
|
77
|
+
builder_config["context"] || "."
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def driver
|
|
81
|
+
builder_config.fetch("driver", "docker-container")
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def local_disabled?
|
|
85
|
+
builder_config["local"] == false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cache_from
|
|
89
|
+
if cached?
|
|
90
|
+
case builder_config["cache"]["type"]
|
|
91
|
+
when "gha"
|
|
92
|
+
cache_from_config_for_gha
|
|
93
|
+
when "registry"
|
|
94
|
+
cache_from_config_for_registry
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def cache_to
|
|
100
|
+
if cached?
|
|
101
|
+
case builder_config["cache"]["type"]
|
|
102
|
+
when "gha"
|
|
103
|
+
cache_to_config_for_gha
|
|
104
|
+
when "registry"
|
|
105
|
+
cache_to_config_for_registry
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def ssh
|
|
111
|
+
builder_config["ssh"]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def provenance
|
|
115
|
+
builder_config["provenance"]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def git_clone?
|
|
119
|
+
Kamal::Git.used? && builder_config["context"].nil?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def clone_directory
|
|
123
|
+
@clone_directory ||= File.join Dir.tmpdir, "kamal-clones", [ service, pwd_sha ].compact.join("-")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_directory
|
|
127
|
+
@build_directory ||=
|
|
128
|
+
if git_clone?
|
|
129
|
+
File.join clone_directory, repo_basename, repo_relative_pwd
|
|
130
|
+
else
|
|
131
|
+
"."
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def docker_driver?
|
|
136
|
+
driver == "docker"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
private
|
|
140
|
+
def valid?
|
|
141
|
+
if docker_driver?
|
|
142
|
+
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support remote builders" if remote
|
|
143
|
+
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support caching" if cached?
|
|
144
|
+
raise ArgumentError, "Invalid builder configuration: the `docker` driver does not not support multiple arches" if arches.many?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
if @options["cache"] && @options["cache"]["type"]
|
|
148
|
+
raise ArgumentError, "Invalid cache type: #{@options["cache"]["type"]}" unless [ "gha", "registry" ].include?(@options["cache"]["type"])
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def cache_image
|
|
153
|
+
builder_config["cache"]&.fetch("image", nil) || "#{image}-build-cache"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def cache_image_ref
|
|
157
|
+
[ server, cache_image ].compact.join("/")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def cache_from_config_for_gha
|
|
161
|
+
"type=gha"
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def cache_from_config_for_registry
|
|
165
|
+
[ "type=registry", "ref=#{cache_image_ref}" ].compact.join(",")
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def cache_to_config_for_gha
|
|
169
|
+
[ "type=gha", builder_config["cache"]&.fetch("options", nil) ].compact.join(",")
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def cache_to_config_for_registry
|
|
173
|
+
[ "type=registry", "ref=#{cache_image_ref}", builder_config["cache"]&.fetch("options", nil) ].compact.join(",")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def repo_basename
|
|
177
|
+
File.basename(Kamal::Git.root)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def repo_relative_pwd
|
|
181
|
+
Dir.pwd.delete_prefix(Kamal::Git.root)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def pwd_sha
|
|
185
|
+
Digest::SHA256.hexdigest(Dir.pwd)[0..12]
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def default_arch
|
|
189
|
+
docker_driver? ? [] : [ "amd64", "arm64" ]
|
|
190
|
+
end
|
|
191
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Accessories
|
|
2
|
+
#
|
|
3
|
+
# Accessories can be booted on a single host, a list of hosts, or on specific roles.
|
|
4
|
+
# The hosts do not need to be defined in the Kamal servers configuration.
|
|
5
|
+
#
|
|
6
|
+
# Accessories are managed separately from the main service — they are not updated
|
|
7
|
+
# when you deploy, and they do not have zero-downtime deployments.
|
|
8
|
+
#
|
|
9
|
+
# Run `kamal accessory boot <accessory>` to boot an accessory.
|
|
10
|
+
# See `kamal accessory --help` for more information.
|
|
11
|
+
|
|
12
|
+
# Configuring accessories
|
|
13
|
+
#
|
|
14
|
+
# First, define the accessory in the `accessories`:
|
|
15
|
+
accessories:
|
|
16
|
+
mysql:
|
|
17
|
+
|
|
18
|
+
# Service name
|
|
19
|
+
#
|
|
20
|
+
# This is used in the service label and defaults to `<service>-<accessory>`,
|
|
21
|
+
# where `<service>` is the main service name from the root configuration:
|
|
22
|
+
service: mysql
|
|
23
|
+
|
|
24
|
+
# Image
|
|
25
|
+
#
|
|
26
|
+
# The Docker image to use, prefix it with a registry if not using Docker Hub:
|
|
27
|
+
image: mysql:8.0
|
|
28
|
+
|
|
29
|
+
# Accessory hosts
|
|
30
|
+
#
|
|
31
|
+
# Specify one of `host`, `hosts`, or `roles`:
|
|
32
|
+
host: mysql-db1
|
|
33
|
+
hosts:
|
|
34
|
+
- mysql-db1
|
|
35
|
+
- mysql-db2
|
|
36
|
+
roles:
|
|
37
|
+
- mysql
|
|
38
|
+
|
|
39
|
+
# Custom command
|
|
40
|
+
#
|
|
41
|
+
# You can set a custom command to run in the container if you do not want to use the default:
|
|
42
|
+
cmd: "bin/mysqld"
|
|
43
|
+
|
|
44
|
+
# Port mappings
|
|
45
|
+
#
|
|
46
|
+
# See https://docs.docker.com/network/, and especially note the warning about the security
|
|
47
|
+
# implications of exposing ports publicly.
|
|
48
|
+
port: "127.0.0.1:3306:3306"
|
|
49
|
+
|
|
50
|
+
# Labels
|
|
51
|
+
labels:
|
|
52
|
+
app: myapp
|
|
53
|
+
|
|
54
|
+
# Options
|
|
55
|
+
#
|
|
56
|
+
# These are passed to the Docker run command in the form `--<name> <value>`:
|
|
57
|
+
options:
|
|
58
|
+
restart: always
|
|
59
|
+
cpus: 2
|
|
60
|
+
|
|
61
|
+
# Environment variables
|
|
62
|
+
#
|
|
63
|
+
# See kamal docs env for more information:
|
|
64
|
+
env:
|
|
65
|
+
...
|
|
66
|
+
|
|
67
|
+
# Copying files
|
|
68
|
+
#
|
|
69
|
+
# You can specify files to mount into the container.
|
|
70
|
+
# The format is `local:remote`, where `local` is the path to the file on the local machine
|
|
71
|
+
# and `remote` is the path to the file in the container.
|
|
72
|
+
#
|
|
73
|
+
# They will be uploaded from the local repo to the host and then mounted.
|
|
74
|
+
#
|
|
75
|
+
# ERB files will be evaluated before being copied.
|
|
76
|
+
files:
|
|
77
|
+
- config/my.cnf.erb:/etc/mysql/my.cnf
|
|
78
|
+
- config/myoptions.cnf:/etc/mysql/myoptions.cnf
|
|
79
|
+
|
|
80
|
+
# Directories
|
|
81
|
+
#
|
|
82
|
+
# You can specify directories to mount into the container. They will be created on the host
|
|
83
|
+
# before being mounted:
|
|
84
|
+
directories:
|
|
85
|
+
- mysql-logs:/var/log/mysql
|
|
86
|
+
|
|
87
|
+
# Volumes
|
|
88
|
+
#
|
|
89
|
+
# Any other volumes to mount, in addition to the files and directories.
|
|
90
|
+
# They are not created or copied before mounting:
|
|
91
|
+
volumes:
|
|
92
|
+
- /path/to/mysql-logs:/var/log/mysql
|
|
93
|
+
|
|
94
|
+
# Network
|
|
95
|
+
#
|
|
96
|
+
# The network the accessory will be attached to.
|
|
97
|
+
#
|
|
98
|
+
# Defaults to kamal:
|
|
99
|
+
network: custom
|
|
100
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Aliases
|
|
2
|
+
#
|
|
3
|
+
# Aliases are shortcuts for Kamal commands.
|
|
4
|
+
#
|
|
5
|
+
# For example, for a Rails app, you might open a console with:
|
|
6
|
+
#
|
|
7
|
+
# ```shell
|
|
8
|
+
# kamal app exec -i -r console "rails console"
|
|
9
|
+
# ```
|
|
10
|
+
#
|
|
11
|
+
# By defining an alias, like this:
|
|
12
|
+
aliases:
|
|
13
|
+
console: app exec -r console -i "rails console"
|
|
14
|
+
# You can now open the console with:
|
|
15
|
+
#
|
|
16
|
+
# ```shell
|
|
17
|
+
# kamal console
|
|
18
|
+
# ```
|
|
19
|
+
|
|
20
|
+
# Configuring aliases
|
|
21
|
+
#
|
|
22
|
+
# Aliases are defined in the root config under the alias key.
|
|
23
|
+
#
|
|
24
|
+
# Each alias is named and can only contain lowercase letters, numbers, dashes, and underscores:
|
|
25
|
+
aliases:
|
|
26
|
+
uname: app exec -p -q -r web "uname -a"
|