kamal 2.3.0 → 2.5.0

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