kamal 2.3.0 → 2.7.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +42 -16
  3. data/lib/kamal/cli/alias/command.rb +1 -0
  4. data/lib/kamal/cli/app/{prepare_assets.rb → assets.rb} +1 -1
  5. data/lib/kamal/cli/app/boot.rb +3 -2
  6. data/lib/kamal/cli/app/error_pages.rb +33 -0
  7. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  8. data/lib/kamal/cli/app.rb +94 -29
  9. data/lib/kamal/cli/base.rb +29 -4
  10. data/lib/kamal/cli/build.rb +60 -18
  11. data/lib/kamal/cli/main.rb +8 -10
  12. data/lib/kamal/cli/proxy.rb +58 -25
  13. data/lib/kamal/cli/registry.rb +2 -0
  14. data/lib/kamal/cli/secrets.rb +9 -3
  15. data/lib/kamal/cli/server.rb +4 -2
  16. data/lib/kamal/cli/templates/deploy.yml +6 -3
  17. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  18. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
  19. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  20. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
  21. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +1 -1
  22. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +19 -6
  23. data/lib/kamal/cli.rb +1 -0
  24. data/lib/kamal/commander/specifics.rb +9 -1
  25. data/lib/kamal/commander.rb +18 -27
  26. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  27. data/lib/kamal/commands/accessory.rb +9 -9
  28. data/lib/kamal/commands/app/assets.rb +4 -4
  29. data/lib/kamal/commands/app/containers.rb +2 -2
  30. data/lib/kamal/commands/app/error_pages.rb +9 -0
  31. data/lib/kamal/commands/app/execution.rb +6 -4
  32. data/lib/kamal/commands/app/images.rb +1 -1
  33. data/lib/kamal/commands/app/logging.rb +14 -4
  34. data/lib/kamal/commands/app/proxy.rb +17 -1
  35. data/lib/kamal/commands/app.rb +19 -10
  36. data/lib/kamal/commands/auditor.rb +11 -5
  37. data/lib/kamal/commands/base.rb +37 -1
  38. data/lib/kamal/commands/builder/base.rb +20 -7
  39. data/lib/kamal/commands/builder/cloud.rb +22 -0
  40. data/lib/kamal/commands/builder/pack.rb +46 -0
  41. data/lib/kamal/commands/builder.rb +11 -19
  42. data/lib/kamal/commands/proxy.rb +55 -15
  43. data/lib/kamal/commands/registry.rb +9 -7
  44. data/lib/kamal/configuration/accessory.rb +66 -11
  45. data/lib/kamal/configuration/builder.rb +20 -0
  46. data/lib/kamal/configuration/docs/accessory.yml +32 -4
  47. data/lib/kamal/configuration/docs/alias.yml +2 -2
  48. data/lib/kamal/configuration/docs/builder.yml +22 -0
  49. data/lib/kamal/configuration/docs/configuration.yml +6 -0
  50. data/lib/kamal/configuration/docs/env.yml +31 -0
  51. data/lib/kamal/configuration/docs/proxy.yml +78 -15
  52. data/lib/kamal/configuration/docs/registry.yml +4 -0
  53. data/lib/kamal/configuration/env.rb +13 -4
  54. data/lib/kamal/configuration/proxy/boot.rb +129 -0
  55. data/lib/kamal/configuration/proxy.rb +67 -5
  56. data/lib/kamal/configuration/registry.rb +6 -6
  57. data/lib/kamal/configuration/role.rb +11 -9
  58. data/lib/kamal/configuration/servers.rb +8 -1
  59. data/lib/kamal/configuration/validator/accessory.rb +6 -2
  60. data/lib/kamal/configuration/validator/builder.rb +2 -0
  61. data/lib/kamal/configuration/validator/proxy.rb +10 -0
  62. data/lib/kamal/configuration/validator/role.rb +3 -1
  63. data/lib/kamal/configuration/validator/servers.rb +1 -1
  64. data/lib/kamal/configuration/validator.rb +21 -1
  65. data/lib/kamal/configuration.rb +36 -57
  66. data/lib/kamal/docker.rb +30 -0
  67. data/lib/kamal/git.rb +10 -0
  68. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
  69. data/lib/kamal/secrets/adapters/base.rb +13 -3
  70. data/lib/kamal/secrets/adapters/bitwarden.rb +2 -2
  71. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  72. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  73. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  74. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  75. data/lib/kamal/secrets/adapters/last_pass.rb +3 -2
  76. data/lib/kamal/secrets/adapters/one_password.rb +47 -13
  77. data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
  78. data/lib/kamal/secrets/adapters/test.rb +2 -2
  79. data/lib/kamal/secrets/adapters.rb +2 -0
  80. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +2 -1
  81. data/lib/kamal/secrets.rb +1 -1
  82. data/lib/kamal/version.rb +1 -1
  83. metadata +22 -10
@@ -5,15 +5,22 @@ 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 pre-connect hooks run before the build, they may needed for a remote builder
18
+ # or the pre-build hooks.
19
+ pre_connect_if_required
20
+
21
+ ensure_docker_installed
22
+ login_to_registry_locally
23
+
17
24
  run_hook "pre-build"
18
25
 
19
26
  uncommitted_changes = Kamal::Git.uncommitted_changes
@@ -49,7 +56,7 @@ class Kamal::Cli::Build < Kamal::Cli::Base
49
56
  end
50
57
 
51
58
  # Get the command here to ensure the Dir.chdir doesn't interfere with it
52
- push = KAMAL.builder.push
59
+ push = KAMAL.builder.push(cli.options[:output])
53
60
 
54
61
  KAMAL.with_verbosity(:debug) do
55
62
  Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
@@ -60,14 +67,16 @@ class Kamal::Cli::Build < Kamal::Cli::Base
60
67
 
61
68
  desc "pull", "Pull app image from registry onto servers"
62
69
  def pull
70
+ login_to_registry_remotely
71
+
63
72
  if (first_hosts = mirror_hosts).any?
64
73
  #  Pull on a single host per mirror first to seed them
65
74
  say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
66
75
  pull_on_hosts(first_hosts)
67
76
  say "Pulling image on remaining hosts...", :magenta
68
- pull_on_hosts(KAMAL.hosts - first_hosts)
77
+ pull_on_hosts(KAMAL.app_hosts - first_hosts)
69
78
  else
70
- pull_on_hosts(KAMAL.hosts)
79
+ pull_on_hosts(KAMAL.app_hosts)
71
80
  end
72
81
  end
73
82
 
@@ -108,21 +117,42 @@ class Kamal::Cli::Build < Kamal::Cli::Base
108
117
  end
109
118
  end
110
119
 
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"
120
+ desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
121
+ 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'."
122
+ def dev
123
+ cli = self
124
+
125
+ ensure_docker_installed
126
+
127
+ docker_included_files = Set.new(Kamal::Docker.included_files)
128
+ git_uncommitted_files = Set.new(Kamal::Git.uncommitted_files)
129
+ git_untracked_files = Set.new(Kamal::Git.untracked_files)
130
+
131
+ docker_uncommitted_files = docker_included_files & git_uncommitted_files
132
+ if docker_uncommitted_files.any?
133
+ say "WARNING: Files with uncommitted changes will be present in the dev container:", :yellow
134
+ docker_uncommitted_files.sort.each { |f| say " #{f}", :yellow }
135
+ say
136
+ end
137
+
138
+ docker_untracked_files = docker_included_files & git_untracked_files
139
+ if docker_untracked_files.any?
140
+ say "WARNING: Untracked files will be present in the dev container:", :yellow
141
+ docker_untracked_files.sort.each { |f| say " #{f}", :yellow }
142
+ say
143
+ end
120
144
 
121
- raise BuildError, build_error
145
+ with_env(KAMAL.config.builder.secrets) do
146
+ run_locally do
147
+ build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true)
148
+ KAMAL.with_verbosity(:debug) do
149
+ execute(*build)
122
150
  end
123
151
  end
124
152
  end
153
+ end
125
154
 
155
+ private
126
156
  def connect_to_remote_host(remote_host)
127
157
  remote_uri = URI.parse(remote_host)
128
158
  if remote_uri.scheme == "ssh"
@@ -137,9 +167,9 @@ class Kamal::Cli::Build < Kamal::Cli::Base
137
167
  end
138
168
 
139
169
  def mirror_hosts
140
- if KAMAL.hosts.many?
170
+ if KAMAL.app_hosts.many?
141
171
  mirror_hosts = Concurrent::Hash.new
142
- on(KAMAL.hosts) do |host|
172
+ on(KAMAL.app_hosts) do |host|
143
173
  first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
144
174
  mirror_hosts[first_mirror] ||= host.to_s if first_mirror
145
175
  rescue SSHKit::Command::Failed => e
@@ -159,4 +189,16 @@ class Kamal::Cli::Build < Kamal::Cli::Base
159
189
  execute *KAMAL.builder.validate_image
160
190
  end
161
191
  end
192
+
193
+ def login_to_registry_locally
194
+ run_locally do
195
+ execute *KAMAL.registry.login
196
+ end
197
+ end
198
+
199
+ def login_to_registry_remotely
200
+ on(KAMAL.app_hosts) do
201
+ execute *KAMAL.registry.login
202
+ end
203
+ end
162
204
  end
@@ -9,21 +9,17 @@ 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
 
24
- say "Log into image registry...", :magenta
25
- invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
26
-
27
23
  if options[:skip_push]
28
24
  say "Pull app image...", :magenta
29
25
  invoke "kamal:cli:build:pull", [], invoke_options
@@ -38,6 +34,8 @@ class Kamal::Cli::Main < Kamal::Cli::Base
38
34
  say "Ensure kamal-proxy is running...", :magenta
39
35
  invoke "kamal:cli:proxy:boot", [], invoke_options
40
36
 
37
+ invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options if boot_accessories
38
+
41
39
  say "Detect stale containers...", :magenta
42
40
  invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
43
41
 
@@ -51,7 +49,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
51
49
  run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
52
50
  end
53
51
 
54
- desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy, pruning, and registry login"
52
+ desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy and pruning"
55
53
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
56
54
  def redeploy
57
55
  runtime = print_runtime do
@@ -196,10 +194,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
196
194
  confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
197
195
  with_lock do
198
196
  if options[:rolling]
199
- (KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
197
+ KAMAL.hosts.each do |host|
200
198
  KAMAL.with_specific_hosts(host) do
201
199
  say "Upgrading #{host}...", :magenta
202
- if KAMAL.hosts.include?(host)
200
+ if KAMAL.app_hosts.include?(host)
203
201
  invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
204
202
  reset_invocation(Kamal::Cli::Proxy)
205
203
  end
@@ -255,7 +253,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
255
253
  private
256
254
  def container_available?(version)
257
255
  begin
258
- on(KAMAL.hosts) do
256
+ on(KAMAL.app_hosts) do
259
257
  KAMAL.roles_on(host).each do |role|
260
258
  container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
261
259
  raise "Container not found" unless container_id.present?
@@ -13,9 +13,10 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
13
13
 
14
14
  version = capture_with_info(*KAMAL.proxy.version).strip.presence
15
15
 
16
- if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION)
17
- raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}"
16
+ if version && Kamal::Utils.older_version?(version, Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION)
17
+ raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::Proxy::Boot::MINIMUM_VERSION}"
18
18
  end
19
+ execute *KAMAL.proxy.ensure_apps_config_directory
19
20
  execute *KAMAL.proxy.start_or_run
20
21
  end
21
22
  end
@@ -23,30 +24,76 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
23
24
 
24
25
  desc "boot_config <set|get|reset>", "Manage kamal-proxy boot configuration"
25
26
  option :publish, type: :boolean, default: true, desc: "Publish the proxy ports on the host"
26
- option :http_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTP_PORT, desc: "HTTP port to publish on the host"
27
- option :https_port, type: :numeric, default: Kamal::Configuration::PROXY_HTTPS_PORT, desc: "HTTPS port to publish on the host"
28
- option :log_max_size, type: :string, default: Kamal::Configuration::PROXY_LOG_MAX_SIZE, desc: "Max size of proxy logs"
27
+ option :publish_host_ip, type: :string, repeatable: true, default: nil, desc: "Host IP address to bind HTTP/HTTPS traffic to. Defaults to all interfaces"
28
+ option :http_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTP_PORT, desc: "HTTP port to publish on the host"
29
+ option :https_port, type: :numeric, default: Kamal::Configuration::Proxy::Boot::DEFAULT_HTTPS_PORT, desc: "HTTPS port to publish on the host"
30
+ option :log_max_size, type: :string, default: Kamal::Configuration::Proxy::Boot::DEFAULT_LOG_MAX_SIZE, desc: "Max size of proxy logs"
31
+ option :registry, type: :string, default: nil, desc: "Registry to use for the proxy image"
32
+ option :repository, type: :string, default: nil, desc: "Repository for the proxy image"
33
+ option :image_version, type: :string, default: nil, desc: "Version of the proxy to run"
34
+ option :metrics_port, type: :numeric, default: nil, desc: "Port to report prometheus metrics on"
35
+ option :debug, type: :boolean, default: false, desc: "Whether to run the proxy in debug mode"
29
36
  option :docker_options, type: :array, default: [], desc: "Docker options to pass to the proxy container", banner: "option=value option2=value2"
30
37
  def boot_config(subcommand)
38
+ proxy_boot_config = KAMAL.config.proxy_boot
39
+
31
40
  case subcommand
32
41
  when "set"
33
42
  boot_options = [
34
- *(KAMAL.config.proxy_publish_args(options[:http_port], options[:https_port]) if options[:publish]),
35
- *(KAMAL.config.proxy_logging_args(options[:log_max_size])),
43
+ *(proxy_boot_config.publish_args(options[:http_port], options[:https_port], options[:publish_host_ip]) if options[:publish]),
44
+ *(proxy_boot_config.logging_args(options[:log_max_size])),
45
+ *("--expose=#{options[:metrics_port]}" if options[:metrics_port]),
36
46
  *options[:docker_options].map { |option| "--#{option}" }
37
47
  ]
38
48
 
49
+ image = [
50
+ options[:registry].presence,
51
+ options[:repository].presence || proxy_boot_config.repository_name,
52
+ proxy_boot_config.image_name
53
+ ].compact.join("/")
54
+
55
+ image_version = options[:image_version]
56
+
57
+ run_command_options = { debug: options[:debug] || nil, "metrics-port": options[:metrics_port] }.compact
58
+ run_command = "kamal-proxy run #{Kamal::Utils.optionize(run_command_options).join(" ")}" if run_command_options.any?
59
+
39
60
  on(KAMAL.proxy_hosts) do |host|
40
61
  execute(*KAMAL.proxy.ensure_proxy_directory)
41
- upload! StringIO.new(boot_options.join(" ")), KAMAL.config.proxy_options_file
62
+ if boot_options != proxy_boot_config.default_boot_options
63
+ upload! StringIO.new(boot_options.join(" ")), proxy_boot_config.options_file
64
+ else
65
+ execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
66
+ end
67
+
68
+ if image != proxy_boot_config.image_default
69
+ upload! StringIO.new(image), proxy_boot_config.image_file
70
+ else
71
+ execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
72
+ end
73
+
74
+ if image_version
75
+ upload! StringIO.new(image_version), proxy_boot_config.image_version_file
76
+ else
77
+ execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
78
+ end
79
+
80
+ if run_command
81
+ upload! StringIO.new(run_command), proxy_boot_config.run_command_file
82
+ else
83
+ execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
84
+ end
42
85
  end
43
86
  when "get"
87
+
44
88
  on(KAMAL.proxy_hosts) do |host|
45
- puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.get_boot_options)}"
89
+ puts "Host #{host}: #{capture_with_info(*KAMAL.proxy.boot_config)}"
46
90
  end
47
91
  when "reset"
48
92
  on(KAMAL.proxy_hosts) do |host|
49
- execute *KAMAL.proxy.reset_boot_options
93
+ execute *KAMAL.proxy.reset_boot_options, raise_on_non_zero_exit: false
94
+ execute *KAMAL.proxy.reset_image, raise_on_non_zero_exit: false
95
+ execute *KAMAL.proxy.reset_image_version, raise_on_non_zero_exit: false
96
+ execute *KAMAL.proxy.reset_run_command, raise_on_non_zero_exit: false
50
97
  end
51
98
  else
52
99
  raise ArgumentError, "Unknown boot_config subcommand #{subcommand}"
@@ -67,26 +114,12 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base
67
114
  execute *KAMAL.auditor.record("Rebooted proxy"), verbosity: :debug
68
115
  execute *KAMAL.registry.login
69
116
 
70
- "Stopping and removing Traefik on #{host}, if running..."
71
- execute *KAMAL.proxy.cleanup_traefik
72
-
73
117
  "Stopping and removing kamal-proxy on #{host}, if running..."
74
118
  execute *KAMAL.proxy.stop, raise_on_non_zero_exit: false
75
119
  execute *KAMAL.proxy.remove_container
120
+ execute *KAMAL.proxy.ensure_apps_config_directory
76
121
 
77
122
  execute *KAMAL.proxy.run
78
-
79
- KAMAL.roles_on(host).select(&:running_proxy?).each do |role|
80
- app = KAMAL.app(role: role, host: host)
81
-
82
- version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
83
- endpoint = capture_with_info(*app.container_id_for_version(version)).strip
84
-
85
- if endpoint.present?
86
- info "Deploying #{endpoint} for role `#{role}` on #{host}..."
87
- execute *app.deploy(target: endpoint)
88
- end
89
- end
90
123
  end
91
124
  run_hook "post-proxy-reboot", hosts: host_list
92
125
  end
@@ -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 unless options[:skip_local]
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
 
@@ -2,8 +2,10 @@ class Kamal::Cli::Server < Kamal::Cli::Base
2
2
  desc "exec", "Run a custom command on the server (use --help to show options)"
3
3
  option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
4
4
  def exec(*cmd)
5
+ pre_connect_if_required
6
+
5
7
  cmd = Kamal::Utils.join_commands(cmd)
6
- hosts = KAMAL.hosts | KAMAL.accessory_hosts
8
+ hosts = KAMAL.hosts
7
9
 
8
10
  case
9
11
  when options[:interactive]
@@ -27,7 +29,7 @@ class Kamal::Cli::Server < Kamal::Cli::Base
27
29
  with_lock do
28
30
  missing = []
29
31
 
30
- on(KAMAL.hosts | KAMAL.accessory_hosts) do |host|
32
+ on(KAMAL.hosts) do |host|
31
33
  unless execute(*KAMAL.docker.installed?, raise_on_non_zero_exit: false)
32
34
  if execute(*KAMAL.docker.superuser?, raise_on_non_zero_exit: false)
33
35
  info "Missing Docker on #{host}. Installing…"
@@ -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..."
@@ -7,7 +7,7 @@
7
7
  # KAMAL_PERFORMER
8
8
  # KAMAL_VERSION
9
9
  # KAMAL_HOSTS
10
- # KAMAL_ROLE (if set)
10
+ # KAMAL_ROLES (if set)
11
11
  # KAMAL_DESTINATION (if set)
12
12
  # KAMAL_RUNTIME
13
13
 
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Booting app version $KAMAL_VERSION on $KAMAL_HOSTS..."
@@ -13,7 +13,7 @@
13
13
  # KAMAL_PERFORMER
14
14
  # KAMAL_VERSION
15
15
  # KAMAL_HOSTS
16
- # KAMAL_ROLE (if set)
16
+ # KAMAL_ROLES (if set)
17
17
  # KAMAL_DESTINATION (if set)
18
18
 
19
19
  if [ -n "$(git status --porcelain)" ]; then
@@ -9,7 +9,7 @@
9
9
  # KAMAL_PERFORMER
10
10
  # KAMAL_VERSION
11
11
  # KAMAL_HOSTS
12
- # KAMAL_ROLE (if set)
12
+ # KAMAL_ROLES (if set)
13
13
  # KAMAL_DESTINATION (if set)
14
14
  # KAMAL_RUNTIME
15
15
 
@@ -13,7 +13,7 @@
13
13
  # KAMAL_HOSTS
14
14
  # KAMAL_COMMAND
15
15
  # KAMAL_SUBCOMMAND
16
- # KAMAL_ROLE (if set)
16
+ # KAMAL_ROLES (if set)
17
17
  # KAMAL_DESTINATION (if set)
18
18
 
19
19
  # Only check the build status for production deployments
@@ -43,7 +43,7 @@ class GithubStatusChecks
43
43
  attr_reader :remote_url, :git_sha, :github_client, :combined_status
44
44
 
45
45
  def initialize
46
- @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
46
+ @remote_url = github_repo_from_remote_url
47
47
  @git_sha = `git rev-parse HEAD`.strip
48
48
  @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
49
49
  refresh!
@@ -77,16 +77,29 @@ class GithubStatusChecks
77
77
  "Build not started..."
78
78
  end
79
79
  end
80
+
81
+ private
82
+ def github_repo_from_remote_url
83
+ url = `git config --get remote.origin.url`.strip.delete_suffix(".git")
84
+ if url.start_with?("https://github.com/")
85
+ url.delete_prefix("https://github.com/")
86
+ elsif url.start_with?("git@github.com:")
87
+ url.delete_prefix("git@github.com:")
88
+ else
89
+ url
90
+ end
91
+ end
80
92
  end
81
93
 
82
94
 
83
95
  $stdout.sync = true
84
96
 
85
- puts "Checking build status..."
86
- attempts = 0
87
- checks = GithubStatusChecks.new
88
-
89
97
  begin
98
+ puts "Checking build status..."
99
+
100
+ attempts = 0
101
+ checks = GithubStatusChecks.new
102
+
90
103
  loop do
91
104
  case checks.state
92
105
  when "success"
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
@@ -11,13 +11,17 @@ class Kamal::Commander::Specifics
11
11
  @primary_role = primary_or_first_role(roles_on(primary_host))
12
12
 
13
13
  stable_sort!(roles) { |role| role == primary_role ? 0 : 1 }
14
- stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 }
14
+ sort_primary_role_hosts_first!(hosts)
15
15
  end
16
16
 
17
17
  def roles_on(host)
18
18
  roles.select { |role| role.hosts.include?(host.to_s) }
19
19
  end
20
20
 
21
+ def app_hosts
22
+ @app_hosts ||= sort_primary_role_hosts_first!(config.app_hosts & specified_hosts)
23
+ end
24
+
21
25
  def proxy_hosts
22
26
  config.proxy_hosts & specified_hosts
23
27
  end
@@ -51,4 +55,8 @@ class Kamal::Commander::Specifics
51
55
  specified_hosts
52
56
  end
53
57
  end
58
+
59
+ def sort_primary_role_hosts_first!(hosts)
60
+ stable_sort!(hosts) { |host| roles_on(host).any? { |role| role == primary_role } ? 0 : 1 }
61
+ end
54
62
  end
@@ -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
- delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :proxy_hosts, :accessory_hosts, to: :specifics
7
+ attr_reader :specific_roles, :specific_hosts
8
+ delegate :hosts, :roles, :primary_host, :primary_role, :roles_on, :app_hosts, :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
- self.holding_lock = false
16
+ self.holding_lock = ENV["KAMAL_LOCK"] == "true"
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 :container_name, to: :"config.proxy_boot", prefix: :proxy
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