kamal 2.3.0 → 2.5.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +24 -9
  3. data/lib/kamal/cli/alias/command.rb +1 -0
  4. data/lib/kamal/cli/app/boot.rb +2 -2
  5. data/lib/kamal/cli/app.rb +28 -8
  6. data/lib/kamal/cli/base.rb +16 -1
  7. data/lib/kamal/cli/build.rb +36 -14
  8. data/lib/kamal/cli/main.rb +4 -3
  9. data/lib/kamal/cli/proxy.rb +2 -4
  10. data/lib/kamal/cli/registry.rb +2 -0
  11. data/lib/kamal/cli/secrets.rb +9 -3
  12. data/lib/kamal/cli/templates/deploy.yml +6 -3
  13. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  14. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  15. data/lib/kamal/cli.rb +1 -0
  16. data/lib/kamal/commander.rb +16 -25
  17. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  18. data/lib/kamal/commands/accessory.rb +4 -4
  19. data/lib/kamal/commands/app/assets.rb +4 -4
  20. data/lib/kamal/commands/app/containers.rb +2 -2
  21. data/lib/kamal/commands/app/execution.rb +4 -2
  22. data/lib/kamal/commands/app/images.rb +1 -1
  23. data/lib/kamal/commands/app/logging.rb +14 -4
  24. data/lib/kamal/commands/app.rb +15 -7
  25. data/lib/kamal/commands/base.rb +25 -1
  26. data/lib/kamal/commands/builder/base.rb +17 -6
  27. data/lib/kamal/commands/builder/cloud.rb +22 -0
  28. data/lib/kamal/commands/builder.rb +6 -20
  29. data/lib/kamal/commands/registry.rb +9 -7
  30. data/lib/kamal/configuration/accessory.rb +41 -9
  31. data/lib/kamal/configuration/builder.rb +8 -0
  32. data/lib/kamal/configuration/docs/accessory.yml +26 -3
  33. data/lib/kamal/configuration/docs/alias.yml +2 -2
  34. data/lib/kamal/configuration/docs/builder.yml +9 -0
  35. data/lib/kamal/configuration/docs/proxy.yml +13 -10
  36. data/lib/kamal/configuration/docs/registry.yml +4 -0
  37. data/lib/kamal/configuration/registry.rb +6 -6
  38. data/lib/kamal/configuration/role.rb +6 -6
  39. data/lib/kamal/configuration/validator/role.rb +1 -1
  40. data/lib/kamal/configuration.rb +31 -14
  41. data/lib/kamal/docker.rb +30 -0
  42. data/lib/kamal/git.rb +10 -0
  43. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +50 -0
  44. data/lib/kamal/secrets/adapters/base.rb +13 -3
  45. data/lib/kamal/secrets/adapters/bitwarden.rb +2 -2
  46. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +72 -0
  47. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  48. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  49. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  50. data/lib/kamal/secrets/adapters/last_pass.rb +3 -2
  51. data/lib/kamal/secrets/adapters/one_password.rb +2 -2
  52. data/lib/kamal/secrets/adapters/test.rb +2 -2
  53. data/lib/kamal/secrets/adapters.rb +2 -0
  54. data/lib/kamal/secrets.rb +1 -1
  55. data/lib/kamal/version.rb +1 -1
  56. metadata +13 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d5e6961984a3361505ebf35dfc52920c49af92085dd99c923dbfa801c668a95
4
- data.tar.gz: adddf71abb26f58e5f7bb16c62553f0d3358499ac4c09f333df75b650cc37b54
3
+ metadata.gz: '01499d423cd415dea520fe78d0e5f17b0d67d3ef2d241e56abb84e2deaeb3f65'
4
+ data.tar.gz: 826b542e67f360b9d019d886a454fff796b73cda57b5e201edc2981e5059a1dd
5
5
  SHA512:
6
- metadata.gz: fdbd4d88c6fe8001def4c53a9f3ee058e871e58ce99f6697a478cbbac48f646947aab415d08074668e2c41147f8fd15fa1cb76f01919d9411aa0e85be6767aba
7
- data.tar.gz: b1716d147e84b386f8bac27deb60f70fc6a7fdfad18fc376dca1391d400de388085b654d5ec8e8e9b3b83724e0a22599bc5626d07cd3812330389add2ed915f9
6
+ metadata.gz: bbc7aa2632e0e5810666cb6d93ce698461fd3290dd5813c11bb88dd9faa4d6f7f015140c24a928b588df34316b814aec6ce710cbb4a28aa19713cc3420c15ae3
7
+ data.tar.gz: 2c818511055983038c9cbc9674a6f9c122de98296e27bf47441b02f3f9474176416e07cf1928bfc5b333648e51bb9ff4d857249173df82127e2ed29a124a97a7
@@ -18,6 +18,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
18
18
  execute *accessory.ensure_env_directory
19
19
  upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
20
20
  execute *accessory.run
21
+
22
+ if accessory.running_proxy?
23
+ target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
24
+ execute *accessory.deploy(target: target)
25
+ end
21
26
  end
22
27
  end
23
28
  end
@@ -75,6 +80,10 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
75
80
  on(hosts) do
76
81
  execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
77
82
  execute *accessory.start
83
+ if accessory.running_proxy?
84
+ target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
85
+ execute *accessory.deploy(target: target)
86
+ end
78
87
  end
79
88
  end
80
89
  end
@@ -87,6 +96,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
87
96
  on(hosts) do
88
97
  execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
89
98
  execute *accessory.stop, raise_on_non_zero_exit: false
99
+
100
+ if accessory.running_proxy?
101
+ target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
102
+ execute *accessory.remove if target
103
+ end
90
104
  end
91
105
  end
92
106
  end
@@ -112,14 +126,15 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
112
126
  end
113
127
  end
114
128
 
115
- desc "exec [NAME] [CMD]", "Execute a custom command on servers (use --help to show options)"
129
+ desc "exec [NAME] [CMD...]", "Execute a custom command on servers within the accessory container (use --help to show options)"
116
130
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
117
131
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
118
- def exec(name, cmd)
132
+ def exec(name, *cmd)
133
+ cmd = Kamal::Utils.join_commands(cmd)
119
134
  with_accessory(name) do |accessory, hosts|
120
135
  case
121
136
  when options[:interactive] && options[:reuse]
122
- say "Launching interactive command with via SSH from existing container...", :magenta
137
+ say "Launching interactive command via SSH from existing container...", :magenta
123
138
  run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
124
139
 
125
140
  when options[:interactive]
@@ -128,16 +143,16 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
128
143
 
129
144
  when options[:reuse]
130
145
  say "Launching command from existing container...", :magenta
131
- on(hosts) do
146
+ on(hosts) do |host|
132
147
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
133
- capture_with_info(*accessory.execute_in_existing_container(cmd))
148
+ puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd))
134
149
  end
135
150
 
136
151
  else
137
152
  say "Launching command from new container...", :magenta
138
- on(hosts) do
153
+ on(hosts) do |host|
139
154
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
140
- capture_with_info(*accessory.execute_in_new_container(cmd))
155
+ puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
141
156
  end
142
157
  end
143
158
  end
@@ -147,7 +162,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
147
162
  option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
148
163
  option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
149
164
  option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
150
- option :grep_options, aliases: "-o", desc: "Additional options supplied to grep"
165
+ option :grep_options, desc: "Additional options supplied to grep"
151
166
  option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
152
167
  option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
153
168
  def logs(name)
@@ -277,7 +292,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
277
292
  def prepare(name)
278
293
  with_accessory(name) do |accessory, hosts|
279
294
  on(hosts) do
280
- execute *KAMAL.registry.login
295
+ execute *KAMAL.registry.login(registry_config: accessory.registry)
281
296
  execute *KAMAL.docker.create_network
282
297
  rescue SSHKit::Command::Failed => e
283
298
  raise unless e.message.include?("already exists")
@@ -1,6 +1,7 @@
1
1
  class Kamal::Cli::Alias::Command < Thor::DynamicCommand
2
2
  def run(instance, args = [])
3
3
  if (_alias = KAMAL.config.aliases[name])
4
+ KAMAL.reset
4
5
  Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
5
6
  else
6
7
  super
@@ -45,7 +45,7 @@ class Kamal::Cli::App::Boot
45
45
 
46
46
  def start_new_version
47
47
  audit "Booted app version #{version}"
48
- hostname = "#{host.to_s[0...51].gsub(/\.+$/, '')}-#{SecureRandom.hex(6)}"
48
+ hostname = "#{host.to_s[0...51].chomp(".")}-#{SecureRandom.hex(6)}"
49
49
 
50
50
  execute *app.ensure_env_directory
51
51
  upload! role.secrets_io(host), role.secrets_path, mode: "0600"
@@ -91,7 +91,7 @@ class Kamal::Cli::App::Boot
91
91
  if barrier.close
92
92
  info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
93
93
  begin
94
- error capture_with_info(*app.logs(version: version))
94
+ error capture_with_info(*app.logs(container_id: app.container_id_for_version(version)))
95
95
  error capture_with_info(*app.container_health_log(version: version))
96
96
  rescue SSHKit::Command::Failed
97
97
  error "Could not fetch logs for #{version}"
data/lib/kamal/cli/app.rb CHANGED
@@ -16,10 +16,18 @@ class Kamal::Cli::App < Kamal::Cli::Base
16
16
  # Primary hosts and roles are returned first, so they can open the barrier
17
17
  barrier = Kamal::Cli::Healthcheck::Barrier.new
18
18
 
19
- on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
20
- KAMAL.roles_on(host).each do |role|
21
- Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
19
+ host_boot_groups.each do |hosts|
20
+ host_list = Array(hosts).join(",")
21
+ run_hook "pre-app-boot", hosts: host_list
22
+
23
+ on(hosts) do |host|
24
+ KAMAL.roles_on(host).each do |role|
25
+ Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
26
+ end
22
27
  end
28
+
29
+ run_hook "post-app-boot", hosts: host_list
30
+ sleep KAMAL.config.boot.wait if KAMAL.config.boot.wait
23
31
  end
24
32
 
25
33
  # Tag once the app booted on all hosts
@@ -94,9 +102,15 @@ class Kamal::Cli::App < Kamal::Cli::Base
94
102
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
95
103
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
96
104
  option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
105
+ option :detach, type: :boolean, default: false, desc: "Execute command in a detached container"
97
106
  def exec(*cmd)
107
+ if (incompatible_options = [ :interactive, :reuse ].select { |key| options[:detach] && options[key] }.presence)
108
+ raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}"
109
+ end
110
+
98
111
  cmd = Kamal::Utils.join_commands(cmd)
99
112
  env = options[:env]
113
+ detach = options[:detach]
100
114
  case
101
115
  when options[:interactive] && options[:reuse]
102
116
  say "Get current version of running container...", :magenta unless options[:version]
@@ -138,7 +152,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
138
152
 
139
153
  roles.each do |role|
140
154
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
141
- puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env))
155
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach))
142
156
  end
143
157
  end
144
158
  end
@@ -186,15 +200,17 @@ class Kamal::Cli::App < Kamal::Cli::Base
186
200
  option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
187
201
  option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
188
202
  option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
189
- option :grep_options, aliases: "-o", desc: "Additional options supplied to grep"
203
+ option :grep_options, desc: "Additional options supplied to grep"
190
204
  option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)"
191
205
  option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
206
+ option :container_id, desc: "Docker container ID to fetch logs"
192
207
  def logs
193
208
  # FIXME: Catch when app containers aren't running
194
209
 
195
210
  grep = options[:grep]
196
211
  grep_options = options[:grep_options]
197
212
  since = options[:since]
213
+ container_id = options[:container_id]
198
214
  timestamps = !options[:skip_timestamps]
199
215
 
200
216
  if options[:follow]
@@ -207,8 +223,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
207
223
  role = KAMAL.roles_on(KAMAL.primary_host).first
208
224
 
209
225
  app = KAMAL.app(role: role, host: host)
210
- info app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
211
- exec app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
226
+ info app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
227
+ exec app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
212
228
  end
213
229
  else
214
230
  lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
@@ -218,7 +234,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
218
234
 
219
235
  roles.each do |role|
220
236
  begin
221
- puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
237
+ puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(container_id: container_id, timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
222
238
  rescue SSHKit::Command::Failed
223
239
  puts_by_host host, "Nothing found"
224
240
  end
@@ -332,4 +348,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
332
348
  yield
333
349
  end
334
350
  end
351
+
352
+ def host_boot_groups
353
+ KAMAL.config.boot.limit ? KAMAL.hosts.each_slice(KAMAL.config.boot.limit).to_a : [ KAMAL.hosts ]
354
+ end
335
355
  end
@@ -5,7 +5,7 @@ module Kamal::Cli
5
5
  class Base < Thor
6
6
  include SSHKit::DSL
7
7
 
8
- def self.exit_on_failure?() false end
8
+ def self.exit_on_failure?() true end
9
9
  def self.dynamic_command_class() Kamal::Cli::Alias::Command end
10
10
 
11
11
  class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
@@ -30,6 +30,7 @@ module Kamal::Cli
30
30
  else
31
31
  super
32
32
  end
33
+
33
34
  initialize_commander unless KAMAL.configured?
34
35
  end
35
36
 
@@ -194,5 +195,19 @@ module Kamal::Cli
194
195
  ENV.clear
195
196
  ENV.update(current_env)
196
197
  end
198
+
199
+ def ensure_docker_installed
200
+ run_locally do
201
+ begin
202
+ execute *KAMAL.builder.ensure_docker_installed
203
+ rescue SSHKit::Command::Failed => e
204
+ error = e.message =~ /command not found/ ?
205
+ "Docker is not installed locally" :
206
+ "Docker buildx plugin is not installed locally"
207
+
208
+ raise DependencyError, error
209
+ end
210
+ end
211
+ end
197
212
  end
198
213
  end
@@ -5,15 +5,16 @@ class Kamal::Cli::Build < Kamal::Cli::Base
5
5
 
6
6
  desc "deliver", "Build app and push app image to registry then pull image on servers"
7
7
  def deliver
8
- push
9
- pull
8
+ invoke :push
9
+ invoke :pull
10
10
  end
11
11
 
12
12
  desc "push", "Build and push app image to registry"
13
+ option :output, type: :string, default: "registry", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
13
14
  def push
14
15
  cli = self
15
16
 
16
- verify_local_dependencies
17
+ ensure_docker_installed
17
18
  run_hook "pre-build"
18
19
 
19
20
  uncommitted_changes = Kamal::Git.uncommitted_changes
@@ -49,7 +50,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
49
50
  end
50
51
 
51
52
  # Get the command here to ensure the Dir.chdir doesn't interfere with it
52
- push = KAMAL.builder.push
53
+ push = KAMAL.builder.push(cli.options[:output])
53
54
 
54
55
  KAMAL.with_verbosity(:debug) do
55
56
  Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
@@ -108,21 +109,42 @@ class Kamal::Cli::Build < Kamal::Cli::Base
108
109
  end
109
110
  end
110
111
 
111
- private
112
- def verify_local_dependencies
113
- run_locally do
114
- begin
115
- execute *KAMAL.builder.ensure_local_dependencies_installed
116
- rescue SSHKit::Command::Failed => e
117
- build_error = e.message =~ /command not found/ ?
118
- "Docker is not installed locally" :
119
- "Docker buildx plugin is not installed locally"
112
+ desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
113
+ option :output, type: :string, default: "docker", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
114
+ def dev
115
+ cli = self
116
+
117
+ ensure_docker_installed
118
+
119
+ docker_included_files = Set.new(Kamal::Docker.included_files)
120
+ git_uncommitted_files = Set.new(Kamal::Git.uncommitted_files)
121
+ git_untracked_files = Set.new(Kamal::Git.untracked_files)
122
+
123
+ docker_uncommitted_files = docker_included_files & git_uncommitted_files
124
+ if docker_uncommitted_files.any?
125
+ say "WARNING: Files with uncommitted changes will be present in the dev container:", :yellow
126
+ docker_uncommitted_files.sort.each { |f| say " #{f}", :yellow }
127
+ say
128
+ end
129
+
130
+ docker_untracked_files = docker_included_files & git_untracked_files
131
+ if docker_untracked_files.any?
132
+ say "WARNING: Untracked files will be present in the dev container:", :yellow
133
+ docker_untracked_files.sort.each { |f| say " #{f}", :yellow }
134
+ say
135
+ end
120
136
 
121
- raise BuildError, build_error
137
+ with_env(KAMAL.config.builder.secrets) do
138
+ run_locally do
139
+ build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true)
140
+ KAMAL.with_verbosity(:debug) do
141
+ execute(*build)
122
142
  end
123
143
  end
124
144
  end
145
+ end
125
146
 
147
+ private
126
148
  def connect_to_remote_host(remote_host)
127
149
  remote_uri = URI.parse(remote_host)
128
150
  if remote_uri.scheme == "ssh"
@@ -9,15 +9,14 @@ class Kamal::Cli::Main < Kamal::Cli::Base
9
9
  say "Ensure Docker is installed...", :magenta
10
10
  invoke "kamal:cli:server:bootstrap", [], invoke_options
11
11
 
12
- invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
13
- deploy
12
+ deploy(boot_accessories: true)
14
13
  end
15
14
  end
16
15
  end
17
16
 
18
17
  desc "deploy", "Deploy app to servers"
19
18
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
20
- def deploy
19
+ def deploy(boot_accessories: false)
21
20
  runtime = print_runtime do
22
21
  invoke_options = deploy_options
23
22
 
@@ -38,6 +37,8 @@ class Kamal::Cli::Main < Kamal::Cli::Base
38
37
  say "Ensure kamal-proxy is running...", :magenta
39
38
  invoke "kamal:cli:proxy:boot", [], invoke_options
40
39
 
40
+ invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
41
+
41
42
  say "Detect stale containers...", :magenta
42
43
  invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
43
44
 
@@ -23,6 +23,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
23
23
 
24
24
  desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
25
25
  option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
26
+ option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
26
27
  option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host"
27
28
  option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host"
28
29
  option :log_max_size, type: :string, default: Kamal::Configuration::PROXY_LOG_MAX_SIZE, desc: "Max size of proxy logs"
@@ -31,7 +32,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
31
32
  case subcommand
32
33
  when "set"
33
34
  boot_options = [
34
- *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port]) if options[:publish]),
35
+ *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]),
35
36
  *(KAMAL.config.proxy_logging_args(options[:log_max_size])),
36
37
  *options[:docker_options].map { |option| "--#{option}" }
37
38
  ]
@@ -67,9 +68,6 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
67
68
  execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
68
69
  execute *KAMAL.registry.login
69
70
 
70
- "Stopping and removing Traefik on #{host}, if running..."
71
- execute *KAMAL.proxy.cleanup_traefik
72
-
73
71
  "Stopping and removing kamal-proxy on #{host}, if running..."
74
72
  execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
75
73
  execute *KAMAL.proxy.remove_container
@@ -3,6 +3,8 @@ class Kamal::Cli::Registry < Kamal::Cli::Base
3
3
  option :skip_local, aliases: "-L", type: :boolean, default: false, desc: "Skip local login"
4
4
  option :skip_remote, aliases: "-R", type: :boolean, default: false, desc: "Skip remote login"
5
5
  def login
6
+ ensure_docker_installed
7
+
6
8
  run_locally { execute *KAMAL.registry.login } unless options[:skip_local]
7
9
  on(KAMAL.hosts) { execute *KAMAL.registry.login } unless options[:skip_remote]
8
10
  end
@@ -1,11 +1,17 @@
1
1
  class Kamal::Cli::Secrets < Kamal::Cli::Base
2
2
  desc "fetch [SECRETS...]", "Fetch secrets from a vault"
3
3
  option :adapter, type: :string, aliases: "-a", required: true, desc: "Which vault adapter to use"
4
- option :account, type: :string, required: true, desc: "The account identifier or username"
4
+ option :account, type: :string, required: false, desc: "The account identifier or username"
5
5
  option :from, type: :string, required: false, desc: "A vault or folder to fetch the secrets from"
6
6
  option :inline, type: :boolean, required: false, hidden: true
7
7
  def fetch(*secrets)
8
- results = adapter(options[:adapter]).fetch(secrets, **options.slice(:account, :from).symbolize_keys)
8
+ adapter = initialize_adapter(options[:adapter])
9
+
10
+ if adapter.requires_account? && options[:account].blank?
11
+ return puts "No value provided for required options '--account'"
12
+ end
13
+
14
+ results = adapter.fetch(secrets, **options.slice(:account, :from).symbolize_keys)
9
15
 
10
16
  return_or_puts JSON.dump(results).shellescape, inline: options[:inline]
11
17
  end
@@ -29,7 +35,7 @@ class Kamal::Cli::Secrets < Kamal::Cli::Base
29
35
  end
30
36
 
31
37
  private
32
- def adapter(adapter)
38
+ def initialize_adapter(adapter)
33
39
  Kamal::Secrets::Adapters.lookup(adapter)
34
40
  end
35
41
 
@@ -16,8 +16,8 @@ servers:
16
16
  # Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server.
17
17
  # Remove this section when using multiple web servers and ensure you terminate SSL at your load balancer.
18
18
  #
19
- # Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
20
- proxy:
19
+ # Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
20
+ proxy:
21
21
  ssl: true
22
22
  host: app.example.com
23
23
  # Proxy connects to your container on port 80 by default.
@@ -36,6 +36,9 @@ registry:
36
36
  # Configure builder setup.
37
37
  builder:
38
38
  arch: amd64
39
+ # Pass in additional build args needed for your Dockerfile.
40
+ # args:
41
+ # RUBY_VERSION: <%= ENV["RBENV_VERSION"] || ENV["rvm_ruby_string"] || "#{RUBY_ENGINE}-#{RUBY_ENGINE_VERSION}" %>
39
42
 
40
43
  # Inject ENV variables into containers (secrets come from .kamal/secrets).
41
44
  #
@@ -46,7 +49,7 @@ builder:
46
49
  # - RAILS_MASTER_KEY
47
50
 
48
51
  # Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
49
- # "bin/kamal logs -r job" will tail logs from the first server in the job section.
52
+ # "bin/kamal app logs -r job" will tail logs from the first server in the job section.
50
53
  #
51
54
  # aliases:
52
55
  # shell: app exec --interactive --reuse "bash"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Booted app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."
data/lib/kamal/cli.rb CHANGED
@@ -2,6 +2,7 @@ module Kamal::Cli
2
2
  class BootError < StandardError; end
3
3
  class HookError < StandardError; end
4
4
  class LockError < StandardError; end
5
+ class DependencyError < StandardError; end
5
6
  end
6
7
 
7
8
  # SSHKit uses instance eval, so we need a global const for ergonomics
@@ -4,13 +4,20 @@ require "active_support/core_ext/object/blank"
4
4
 
5
5
  class Kamal::Commander
6
6
  attr_accessor :verbosity, :holding_lock, :connected
7
+ attr_reader :specific_roles, :specific_hosts
7
8
  delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :proxy_hosts, :accessory_hosts, to: :specifics
8
9
 
9
10
  def initialize
11
+ reset
12
+ end
13
+
14
+ def reset
10
15
  self.verbosity = :info
11
16
  self.holding_lock = false
12
17
  self.connected = false
13
- @specifics = nil
18
+ @specifics = @specific_roles = @specific_hosts = nil
19
+ @config = @config_kwargs = nil
20
+ @commands = {}
14
21
  end
15
22
 
16
23
  def config
@@ -28,8 +35,6 @@ class Kamal::Commander
28
35
  @config || @config_kwargs
29
36
  end
30
37
 
31
- attr_reader :specific_roles, :specific_hosts
32
-
33
38
  def specific_primary!
34
39
  @specifics = nil
35
40
  if specific_roles.present?
@@ -76,11 +81,6 @@ class Kamal::Commander
76
81
  config.accessories&.collect(&:name) || []
77
82
  end
78
83
 
79
- def accessories_on(host)
80
- config.accessories.select { |accessory| accessory.hosts.include?(host.to_s) }.map(&:name)
81
- end
82
-
83
-
84
84
  def app(role: nil, host: nil)
85
85
  Kamal::Commands::App.new(config, role: role, host: host)
86
86
  end
@@ -94,42 +94,41 @@ class Kamal::Commander
94
94
  end
95
95
 
96
96
  def builder
97
- @builder ||= Kamal::Commands::Builder.new(config)
97
+ @commands[:builder] ||= Kamal::Commands::Builder.new(config)
98
98
  end
99
99
 
100
100
  def docker
101
- @docker ||= Kamal::Commands::Docker.new(config)
101
+ @commands[:docker] ||= Kamal::Commands::Docker.new(config)
102
102
  end
103
103
 
104
104
  def hook
105
- @hook ||= Kamal::Commands::Hook.new(config)
105
+ @commands[:hook] ||= Kamal::Commands::Hook.new(config)
106
106
  end
107
107
 
108
108
  def lock
109
- @lock ||= Kamal::Commands::Lock.new(config)
109
+ @commands[:lock] ||= Kamal::Commands::Lock.new(config)
110
110
  end
111
111
 
112
112
  def proxy
113
- @proxy ||= Kamal::Commands::Proxy.new(config)
113
+ @commands[:proxy] ||= Kamal::Commands::Proxy.new(config)
114
114
  end
115
115
 
116
116
  def prune
117
- @prune ||= Kamal::Commands::Prune.new(config)
117
+ @commands[:prune] ||= Kamal::Commands::Prune.new(config)
118
118
  end
119
119
 
120
120
  def registry
121
- @registry ||= Kamal::Commands::Registry.new(config)
121
+ @commands[:registry] ||= Kamal::Commands::Registry.new(config)
122
122
  end
123
123
 
124
124
  def server
125
- @server ||= Kamal::Commands::Server.new(config)
125
+ @commands[:server] ||= Kamal::Commands::Server.new(config)
126
126
  end
127
127
 
128
128
  def alias(name)
129
129
  config.aliases[name]
130
130
  end
131
131
 
132
-
133
132
  def with_verbosity(level)
134
133
  old_level = self.verbosity
135
134
 
@@ -142,14 +141,6 @@ class Kamal::Commander
142
141
  SSHKit.config.output_verbosity = old_level
143
142
  end
144
143
 
145
- def boot_strategy
146
- if config.boot.limit.present?
147
- { in: :groups, limit: config.boot.limit, wait: config.boot.wait }
148
- else
149
- {}
150
- end
151
- end
152
-
153
144
  def holding_lock?
154
145
  self.holding_lock
155
146
  end
@@ -0,0 +1,16 @@
1
+ module Kamal::Commands::Accessory::Proxy
2
+ delegate :proxy_container_name, to: :config
3
+
4
+ def deploy(target:)
5
+ proxy_exec :deploy, service_name, *proxy.deploy_command_args(target: target)
6
+ end
7
+
8
+ def remove
9
+ proxy_exec :remove, service_name
10
+ end
11
+
12
+ private
13
+ def proxy_exec(*command)
14
+ docker :exec, proxy_container_name, "kamal-proxy", *command
15
+ end
16
+ end
@@ -1,9 +1,12 @@
1
1
  class Kamal::Commands::Accessory < Kamal::Commands::Base
2
+ include Proxy
3
+
2
4
  attr_reader :accessory_config
3
5
  delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd,
4
6
  :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args,
5
- :secrets_io, :secrets_path, :env_directory,
7
+ :secrets_io, :secrets_path, :env_directory, :proxy, :running_proxy?, :registry,
6
8
  to: :accessory_config
9
+ delegate :proxy_container_name, to: :config
7
10
 
8
11
  def initialize(config, name:)
9
12
  super(config)
@@ -38,7 +41,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
38
41
  docker :ps, *service_filter
39
42
  end
40
43
 
41
-
42
44
  def logs(timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil)
43
45
  pipe \
44
46
  docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), ("--timestamps" if timestamps), "2>&1"),
@@ -52,7 +54,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
52
54
  (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep)
53
55
  end
54
56
 
55
-
56
57
  def execute_in_existing_container(*command, interactive: false)
57
58
  docker :exec,
58
59
  ("-it" if interactive),
@@ -83,7 +84,6 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base
83
84
  super command, host: hosts.first
84
85
  end
85
86
 
86
-
87
87
  def ensure_local_file_present(local_file)
88
88
  if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
89
89
  raise "Missing file: #{local_file}"