nocoffee-kamal 2.3.0.1
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 +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"
|