nocoffee-kamal 2.3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/kamal +18 -0
  5. data/lib/kamal/cli/accessory.rb +287 -0
  6. data/lib/kamal/cli/alias/command.rb +9 -0
  7. data/lib/kamal/cli/app/boot.rb +125 -0
  8. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  9. data/lib/kamal/cli/app.rb +335 -0
  10. data/lib/kamal/cli/base.rb +198 -0
  11. data/lib/kamal/cli/build/clone.rb +61 -0
  12. data/lib/kamal/cli/build.rb +162 -0
  13. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  14. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  15. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  16. data/lib/kamal/cli/lock.rb +45 -0
  17. data/lib/kamal/cli/main.rb +279 -0
  18. data/lib/kamal/cli/proxy.rb +257 -0
  19. data/lib/kamal/cli/prune.rb +34 -0
  20. data/lib/kamal/cli/registry.rb +17 -0
  21. data/lib/kamal/cli/secrets.rb +43 -0
  22. data/lib/kamal/cli/server.rb +48 -0
  23. data/lib/kamal/cli/templates/deploy.yml +98 -0
  24. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  25. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  26. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  28. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  29. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
  30. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  31. data/lib/kamal/cli/templates/secrets +17 -0
  32. data/lib/kamal/cli.rb +8 -0
  33. data/lib/kamal/commander/specifics.rb +54 -0
  34. data/lib/kamal/commander.rb +176 -0
  35. data/lib/kamal/commands/accessory.rb +113 -0
  36. data/lib/kamal/commands/app/assets.rb +51 -0
  37. data/lib/kamal/commands/app/containers.rb +31 -0
  38. data/lib/kamal/commands/app/execution.rb +30 -0
  39. data/lib/kamal/commands/app/images.rb +13 -0
  40. data/lib/kamal/commands/app/logging.rb +18 -0
  41. data/lib/kamal/commands/app/proxy.rb +16 -0
  42. data/lib/kamal/commands/app.rb +115 -0
  43. data/lib/kamal/commands/auditor.rb +33 -0
  44. data/lib/kamal/commands/base.rb +98 -0
  45. data/lib/kamal/commands/builder/base.rb +111 -0
  46. data/lib/kamal/commands/builder/clone.rb +31 -0
  47. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  48. data/lib/kamal/commands/builder/local.rb +14 -0
  49. data/lib/kamal/commands/builder/remote.rb +63 -0
  50. data/lib/kamal/commands/builder.rb +56 -0
  51. data/lib/kamal/commands/docker.rb +34 -0
  52. data/lib/kamal/commands/hook.rb +20 -0
  53. data/lib/kamal/commands/lock.rb +70 -0
  54. data/lib/kamal/commands/proxy.rb +87 -0
  55. data/lib/kamal/commands/prune.rb +38 -0
  56. data/lib/kamal/commands/registry.rb +14 -0
  57. data/lib/kamal/commands/server.rb +15 -0
  58. data/lib/kamal/commands.rb +2 -0
  59. data/lib/kamal/configuration/accessory.rb +186 -0
  60. data/lib/kamal/configuration/alias.rb +15 -0
  61. data/lib/kamal/configuration/boot.rb +25 -0
  62. data/lib/kamal/configuration/builder.rb +191 -0
  63. data/lib/kamal/configuration/docs/accessory.yml +100 -0
  64. data/lib/kamal/configuration/docs/alias.yml +26 -0
  65. data/lib/kamal/configuration/docs/boot.yml +19 -0
  66. data/lib/kamal/configuration/docs/builder.yml +110 -0
  67. data/lib/kamal/configuration/docs/configuration.yml +178 -0
  68. data/lib/kamal/configuration/docs/env.yml +85 -0
  69. data/lib/kamal/configuration/docs/logging.yml +21 -0
  70. data/lib/kamal/configuration/docs/proxy.yml +110 -0
  71. data/lib/kamal/configuration/docs/registry.yml +52 -0
  72. data/lib/kamal/configuration/docs/role.yml +53 -0
  73. data/lib/kamal/configuration/docs/servers.yml +27 -0
  74. data/lib/kamal/configuration/docs/ssh.yml +70 -0
  75. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  76. data/lib/kamal/configuration/env/tag.rb +13 -0
  77. data/lib/kamal/configuration/env.rb +29 -0
  78. data/lib/kamal/configuration/logging.rb +33 -0
  79. data/lib/kamal/configuration/proxy.rb +63 -0
  80. data/lib/kamal/configuration/registry.rb +32 -0
  81. data/lib/kamal/configuration/role.rb +220 -0
  82. data/lib/kamal/configuration/servers.rb +18 -0
  83. data/lib/kamal/configuration/ssh.rb +57 -0
  84. data/lib/kamal/configuration/sshkit.rb +22 -0
  85. data/lib/kamal/configuration/validation.rb +27 -0
  86. data/lib/kamal/configuration/validator/accessory.rb +9 -0
  87. data/lib/kamal/configuration/validator/alias.rb +15 -0
  88. data/lib/kamal/configuration/validator/builder.rb +13 -0
  89. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  90. data/lib/kamal/configuration/validator/env.rb +54 -0
  91. data/lib/kamal/configuration/validator/proxy.rb +15 -0
  92. data/lib/kamal/configuration/validator/registry.rb +25 -0
  93. data/lib/kamal/configuration/validator/role.rb +11 -0
  94. data/lib/kamal/configuration/validator/servers.rb +7 -0
  95. data/lib/kamal/configuration/validator.rb +171 -0
  96. data/lib/kamal/configuration/volume.rb +22 -0
  97. data/lib/kamal/configuration.rb +393 -0
  98. data/lib/kamal/env_file.rb +44 -0
  99. data/lib/kamal/git.rb +27 -0
  100. data/lib/kamal/secrets/adapters/base.rb +23 -0
  101. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  102. data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
  103. data/lib/kamal/secrets/adapters/one_password.rb +70 -0
  104. data/lib/kamal/secrets/adapters/test.rb +14 -0
  105. data/lib/kamal/secrets/adapters.rb +14 -0
  106. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  107. data/lib/kamal/secrets.rb +42 -0
  108. data/lib/kamal/sshkit_with_ext.rb +142 -0
  109. data/lib/kamal/tags.rb +40 -0
  110. data/lib/kamal/utils/sensitive.rb +20 -0
  111. data/lib/kamal/utils.rb +110 -0
  112. data/lib/kamal/version.rb +3 -0
  113. data/lib/kamal.rb +14 -0
  114. metadata +349 -0
@@ -0,0 +1,162 @@
1
+ require "uri"
2
+
3
+ class Kamal::Cli::Build < Kamal::Cli::Base
4
+ class BuildError < StandardError; end
5
+
6
+ desc "deliver", "Build app and push app image to registry then pull image on servers"
7
+ def deliver
8
+ push
9
+ pull
10
+ end
11
+
12
+ desc "push", "Build and push app image to registry"
13
+ def push
14
+ cli = self
15
+
16
+ verify_local_dependencies
17
+ run_hook "pre-build"
18
+
19
+ uncommitted_changes = Kamal::Git.uncommitted_changes
20
+
21
+ if KAMAL.config.builder.git_clone?
22
+ if uncommitted_changes.present?
23
+ say "Building from a local git clone, so ignoring these uncommitted changes:\n #{uncommitted_changes}", :yellow
24
+ end
25
+
26
+ run_locally do
27
+ Clone.new(self).prepare
28
+ end
29
+ elsif uncommitted_changes.present?
30
+ say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
31
+ end
32
+
33
+ with_env(KAMAL.config.builder.secrets) do
34
+ run_locally do
35
+ begin
36
+ execute *KAMAL.builder.inspect_builder
37
+ rescue SSHKit::Command::Failed => e
38
+ if e.message =~ /(context not found|no builder|no compatible builder|does not exist)/
39
+ warn "Missing compatible builder, so creating a new one first"
40
+ begin
41
+ cli.remove
42
+ rescue SSHKit::Command::Failed
43
+ raise unless e.message =~ /(context not found|no builder|does not exist)/
44
+ end
45
+ cli.create
46
+ else
47
+ raise
48
+ end
49
+ end
50
+
51
+ # Get the command here to ensure the Dir.chdir doesn't interfere with it
52
+ push = KAMAL.builder.push
53
+
54
+ KAMAL.with_verbosity(:debug) do
55
+ Dir.chdir(KAMAL.config.builder.build_directory) { execute *push }
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ desc "pull", "Pull app image from registry onto servers"
62
+ def pull
63
+ if (first_hosts = mirror_hosts).any?
64
+ #  Pull on a single host per mirror first to seed them
65
+ say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
66
+ pull_on_hosts(first_hosts)
67
+ say "Pulling image on remaining hosts...", :magenta
68
+ pull_on_hosts(KAMAL.hosts - first_hosts)
69
+ else
70
+ pull_on_hosts(KAMAL.hosts)
71
+ end
72
+ end
73
+
74
+ desc "create", "Create a build setup"
75
+ def create
76
+ if (remote_host = KAMAL.config.builder.remote)
77
+ connect_to_remote_host(remote_host)
78
+ end
79
+
80
+ run_locally do
81
+ begin
82
+ debug "Using builder: #{KAMAL.builder.name}"
83
+ execute *KAMAL.builder.create
84
+ rescue SSHKit::Command::Failed => e
85
+ if e.message =~ /stderr=(.*)/
86
+ error "Couldn't create remote builder: #{$1}"
87
+ false
88
+ else
89
+ raise
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ desc "remove", "Remove build setup"
96
+ def remove
97
+ run_locally do
98
+ debug "Using builder: #{KAMAL.builder.name}"
99
+ execute *KAMAL.builder.remove
100
+ end
101
+ end
102
+
103
+ desc "details", "Show build setup"
104
+ def details
105
+ run_locally do
106
+ puts "Builder: #{KAMAL.builder.name}"
107
+ puts capture(*KAMAL.builder.info)
108
+ end
109
+ end
110
+
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
+
121
+ raise BuildError, build_error
122
+ end
123
+ end
124
+ end
125
+
126
+ def connect_to_remote_host(remote_host)
127
+ remote_uri = URI.parse(remote_host)
128
+ if remote_uri.scheme == "ssh"
129
+ host = SSHKit::Host.new(
130
+ hostname: remote_uri.host,
131
+ ssh_options: { user: remote_uri.user, port: remote_uri.port }.compact
132
+ )
133
+ on(host, options) do
134
+ execute "true"
135
+ end
136
+ end
137
+ end
138
+
139
+ def mirror_hosts
140
+ if KAMAL.hosts.many?
141
+ mirror_hosts = Concurrent::Hash.new
142
+ on(KAMAL.hosts) do |host|
143
+ first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
144
+ mirror_hosts[first_mirror] ||= host.to_s if first_mirror
145
+ rescue SSHKit::Command::Failed => e
146
+ raise unless e.message =~ /error calling index: reflect: slice index out of range/
147
+ end
148
+ mirror_hosts.values
149
+ else
150
+ []
151
+ end
152
+ end
153
+
154
+ def pull_on_hosts(hosts)
155
+ on(hosts) do
156
+ execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
157
+ execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
158
+ execute *KAMAL.builder.pull
159
+ execute *KAMAL.builder.validate_image
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,33 @@
1
+ require "concurrent/ivar"
2
+
3
+ class Kamal::Cli::Healthcheck::Barrier
4
+ def initialize
5
+ @ivar = Concurrent::IVar.new
6
+ end
7
+
8
+ def close
9
+ set(false)
10
+ end
11
+
12
+ def open
13
+ set(true)
14
+ end
15
+
16
+ def wait
17
+ unless opened?
18
+ raise Kamal::Cli::Healthcheck::Error.new("Halted at barrier")
19
+ end
20
+ end
21
+
22
+ private
23
+ def opened?
24
+ @ivar.value
25
+ end
26
+
27
+ def set(value)
28
+ @ivar.set(value)
29
+ true
30
+ rescue Concurrent::MultipleAssignmentError
31
+ false
32
+ end
33
+ end
@@ -0,0 +1,2 @@
1
+ class Kamal::Cli::Healthcheck::Error < StandardError
2
+ end
@@ -0,0 +1,42 @@
1
+ module Kamal::Cli::Healthcheck::Poller
2
+ extend self
3
+
4
+ def wait_for_healthy(role, &block)
5
+ attempt = 1
6
+ timeout_at = Time.now + KAMAL.config.deploy_timeout
7
+ readiness_delay = KAMAL.config.readiness_delay
8
+
9
+ begin
10
+ status = block.call
11
+
12
+ if status == "running"
13
+ # Wait for the readiness delay and confirm it is still running
14
+ if readiness_delay > 0
15
+ info "Container is running, waiting for readiness delay of #{readiness_delay} seconds"
16
+ sleep readiness_delay
17
+ status = block.call
18
+ end
19
+ end
20
+
21
+ unless %w[ running healthy ].include?(status)
22
+ raise Kamal::Cli::Healthcheck::Error, "container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})"
23
+ end
24
+ rescue Kamal::Cli::Healthcheck::Error => e
25
+ time_left = timeout_at - Time.now
26
+ if time_left > 0
27
+ sleep [ attempt, time_left ].min
28
+ attempt += 1
29
+ retry
30
+ else
31
+ raise
32
+ end
33
+ end
34
+
35
+ info "Container is healthy!"
36
+ end
37
+
38
+ private
39
+ def info(message)
40
+ SSHKit.config.output.info(message)
41
+ end
42
+ end
@@ -0,0 +1,45 @@
1
+ class Kamal::Cli::Lock < Kamal::Cli::Base
2
+ desc "status", "Report lock status"
3
+ def status
4
+ handle_missing_lock do
5
+ on(KAMAL.primary_host) do
6
+ puts capture_with_debug(*KAMAL.lock.status)
7
+ end
8
+ end
9
+ end
10
+
11
+ desc "acquire", "Acquire the deploy lock"
12
+ option :message, aliases: "-m", type: :string, desc: "A lock message", required: true
13
+ def acquire
14
+ message = options[:message]
15
+ ensure_run_directory
16
+
17
+ raise_if_locked do
18
+ on(KAMAL.primary_host) do
19
+ execute *KAMAL.lock.acquire(message, KAMAL.config.version), verbosity: :debug
20
+ end
21
+ say "Acquired the deploy lock"
22
+ end
23
+ end
24
+
25
+ desc "release", "Release the deploy lock"
26
+ def release
27
+ handle_missing_lock do
28
+ on(KAMAL.primary_host) do
29
+ execute *KAMAL.lock.release, verbosity: :debug
30
+ end
31
+ say "Released the deploy lock"
32
+ end
33
+ end
34
+
35
+ private
36
+ def handle_missing_lock
37
+ yield
38
+ rescue SSHKit::Runner::ExecuteError => e
39
+ if e.message =~ /No such file or directory/
40
+ say "There is no deploy lock"
41
+ else
42
+ raise
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,279 @@
1
+ class Kamal::Cli::Main < Kamal::Cli::Base
2
+ desc "setup", "Setup all accessories, push the env, and deploy app to servers"
3
+ option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
4
+ def setup
5
+ print_runtime do
6
+ with_lock do
7
+ invoke_options = deploy_options
8
+
9
+ say "Ensure Docker is installed...", :magenta
10
+ invoke "kamal:cli:server:bootstrap", [], invoke_options
11
+
12
+ invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
13
+ deploy
14
+ end
15
+ end
16
+ end
17
+
18
+ desc "deploy", "Deploy app to servers"
19
+ option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
20
+ def deploy
21
+ runtime = print_runtime do
22
+ invoke_options = deploy_options
23
+
24
+ say "Log into image registry...", :magenta
25
+ invoke "kamal:cli:registry:login", [], invoke_options.merge(skip_local: options[:skip_push])
26
+
27
+ if options[:skip_push]
28
+ say "Pull app image...", :magenta
29
+ invoke "kamal:cli:build:pull", [], invoke_options
30
+ else
31
+ say "Build and push app image...", :magenta
32
+ invoke "kamal:cli:build:deliver", [], invoke_options
33
+ end
34
+
35
+ with_lock do
36
+ run_hook "pre-deploy", secrets: true
37
+
38
+ say "Ensure kamal-proxy is running...", :magenta
39
+ invoke "kamal:cli:proxy:boot", [], invoke_options
40
+
41
+ say "Detect stale containers...", :magenta
42
+ invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
43
+
44
+ invoke "kamal:cli:app:boot", [], invoke_options
45
+
46
+ say "Prune old containers and images...", :magenta
47
+ invoke "kamal:cli:prune:all", [], invoke_options
48
+ end
49
+ end
50
+
51
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
52
+ end
53
+
54
+ desc "redeploy", "Deploy app to servers without bootstrapping servers, starting kamal-proxy, pruning, and registry login"
55
+ option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
56
+ def redeploy
57
+ runtime = print_runtime do
58
+ invoke_options = deploy_options
59
+
60
+ if options[:skip_push]
61
+ say "Pull app image...", :magenta
62
+ invoke "kamal:cli:build:pull", [], invoke_options
63
+ else
64
+ say "Build and push app image...", :magenta
65
+ invoke "kamal:cli:build:deliver", [], invoke_options
66
+ end
67
+
68
+ with_lock do
69
+ run_hook "pre-deploy", secrets: true
70
+
71
+ say "Detect stale containers...", :magenta
72
+ invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
73
+
74
+ invoke "kamal:cli:app:boot", [], invoke_options
75
+ end
76
+ end
77
+
78
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s
79
+ end
80
+
81
+ desc "rollback [VERSION]", "Rollback app to VERSION"
82
+ def rollback(version)
83
+ rolled_back = false
84
+ runtime = print_runtime do
85
+ with_lock do
86
+ invoke_options = deploy_options
87
+
88
+ KAMAL.config.version = version
89
+ old_version = nil
90
+
91
+ if container_available?(version)
92
+ run_hook "pre-deploy", secrets: true
93
+
94
+ invoke "kamal:cli:app:boot", [], invoke_options.merge(version: version)
95
+ rolled_back = true
96
+ else
97
+ say "The app version '#{version}' is not available as a container (use 'kamal app containers' for available versions)", :red
98
+ end
99
+ end
100
+ end
101
+
102
+ run_hook "post-deploy", secrets: true, runtime: runtime.round.to_s if rolled_back
103
+ end
104
+
105
+ desc "details", "Show details about all containers"
106
+ def details
107
+ invoke "kamal:cli:proxy:details"
108
+ invoke "kamal:cli:app:details"
109
+ invoke "kamal:cli:accessory:details", [ "all" ]
110
+ end
111
+
112
+ desc "audit", "Show audit log from servers"
113
+ def audit
114
+ on(KAMAL.hosts) do |host|
115
+ puts_by_host host, capture_with_info(*KAMAL.auditor.reveal)
116
+ end
117
+ end
118
+
119
+ desc "config", "Show combined config (including secrets!)"
120
+ def config
121
+ run_locally do
122
+ puts Kamal::Utils.redacted(KAMAL.config.to_h).to_yaml
123
+ end
124
+ end
125
+
126
+ desc "docs [SECTION]", "Show Kamal configuration documentation"
127
+ def docs(section = nil)
128
+ case section
129
+ when NilClass
130
+ puts Kamal::Configuration.validation_doc
131
+ else
132
+ puts Kamal::Configuration.const_get(section.titlecase.to_sym).validation_doc
133
+ end
134
+ rescue NameError
135
+ puts "No documentation found for #{section}"
136
+ end
137
+
138
+ desc "init", "Create config stub in config/deploy.yml and secrets stub in .kamal"
139
+ option :bundle, type: :boolean, default: false, desc: "Add Kamal to the Gemfile and create a bin/kamal binstub"
140
+ def init
141
+ require "fileutils"
142
+
143
+ if (deploy_file = Pathname.new(File.expand_path("config/deploy.yml"))).exist?
144
+ puts "Config file already exists in config/deploy.yml (remove first to create a new one)"
145
+ else
146
+ FileUtils.mkdir_p deploy_file.dirname
147
+ FileUtils.cp_r Pathname.new(File.expand_path("templates/deploy.yml", __dir__)), deploy_file
148
+ puts "Created configuration file in config/deploy.yml"
149
+ end
150
+
151
+ unless (secrets_file = Pathname.new(File.expand_path(".kamal/secrets"))).exist?
152
+ FileUtils.mkdir_p secrets_file.dirname
153
+ FileUtils.cp_r Pathname.new(File.expand_path("templates/secrets", __dir__)), secrets_file
154
+ puts "Created .kamal/secrets file"
155
+ end
156
+
157
+ unless (hooks_dir = Pathname.new(File.expand_path(".kamal/hooks"))).exist?
158
+ hooks_dir.mkpath
159
+ Pathname.new(File.expand_path("templates/sample_hooks", __dir__)).each_child do |sample_hook|
160
+ FileUtils.cp sample_hook, hooks_dir, preserve: true
161
+ end
162
+ puts "Created sample hooks in .kamal/hooks"
163
+ end
164
+
165
+ if options[:bundle]
166
+ if (binstub = Pathname.new(File.expand_path("bin/kamal"))).exist?
167
+ puts "Binstub already exists in bin/kamal (remove first to create a new one)"
168
+ else
169
+ puts "Adding Kamal to Gemfile and bundle..."
170
+ run_locally do
171
+ execute :bundle, :add, :kamal
172
+ execute :bundle, :binstubs, :kamal
173
+ end
174
+ puts "Created binstub file in bin/kamal"
175
+ end
176
+ end
177
+ end
178
+
179
+ desc "remove", "Remove kamal-proxy, app, accessories, and registry session from servers"
180
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
181
+ def remove
182
+ confirming "This will remove all containers and images. Are you sure?" do
183
+ with_lock do
184
+ invoke "kamal:cli:app:remove", [], options.without(:confirmed)
185
+ invoke "kamal:cli:proxy:remove", [], options.without(:confirmed)
186
+ invoke "kamal:cli:accessory:remove", [ "all" ], options
187
+ invoke "kamal:cli:registry:logout", [], options.without(:confirmed).merge(skip_local: true)
188
+ end
189
+ end
190
+ end
191
+
192
+ desc "upgrade", "Upgrade from Kamal 1.x to 2.0"
193
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
194
+ option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
195
+ def upgrade
196
+ confirming "This will replace Traefik with kamal-proxy and restart all accessories" do
197
+ with_lock do
198
+ if options[:rolling]
199
+ (KAMAL.hosts | KAMAL.accessory_hosts).each do |host|
200
+ KAMAL.with_specific_hosts(host) do
201
+ say "Upgrading #{host}...", :magenta
202
+ if KAMAL.hosts.include?(host)
203
+ invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true, rolling: false)
204
+ reset_invocation(Kamal::Cli::Proxy)
205
+ end
206
+ if KAMAL.accessory_hosts.include?(host)
207
+ invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true, rolling: false)
208
+ reset_invocation(Kamal::Cli::Accessory)
209
+ end
210
+ say "Upgraded #{host}", :magenta
211
+ end
212
+ end
213
+ else
214
+ say "Upgrading all hosts...", :magenta
215
+ invoke "kamal:cli:proxy:upgrade", [], options.merge(confirmed: true)
216
+ invoke "kamal:cli:accessory:upgrade", [ "all" ], options.merge(confirmed: true)
217
+ say "Upgraded all hosts", :magenta
218
+ end
219
+ end
220
+ end
221
+ end
222
+
223
+ desc "version", "Show Kamal version"
224
+ def version
225
+ puts Kamal::VERSION
226
+ end
227
+
228
+ desc "accessory", "Manage accessories (db/redis/search)"
229
+ subcommand "accessory", Kamal::Cli::Accessory
230
+
231
+ desc "app", "Manage application"
232
+ subcommand "app", Kamal::Cli::App
233
+
234
+ desc "build", "Build application image"
235
+ subcommand "build", Kamal::Cli::Build
236
+
237
+ desc "lock", "Manage the deploy lock"
238
+ subcommand "lock", Kamal::Cli::Lock
239
+
240
+ desc "proxy", "Manage kamal-proxy"
241
+ subcommand "proxy", Kamal::Cli::Proxy
242
+
243
+ desc "prune", "Prune old application images and containers"
244
+ subcommand "prune", Kamal::Cli::Prune
245
+
246
+ desc "registry", "Login and -out of the image registry"
247
+ subcommand "registry", Kamal::Cli::Registry
248
+
249
+ desc "secrets", "Helpers for extracting secrets"
250
+ subcommand "secrets", Kamal::Cli::Secrets
251
+
252
+ desc "server", "Bootstrap servers with curl and Docker"
253
+ subcommand "server", Kamal::Cli::Server
254
+
255
+ private
256
+ def container_available?(version)
257
+ begin
258
+ on(KAMAL.hosts) do
259
+ KAMAL.roles_on(host).each do |role|
260
+ container_id = capture_with_info(*KAMAL.app(role: role, host: host).container_id_for_version(version))
261
+ raise "Container not found" unless container_id.present?
262
+ end
263
+ end
264
+ rescue SSHKit::Runner::ExecuteError, SSHKit::Runner::MultipleExecuteError => e
265
+ if e.message =~ /Container not found/
266
+ say "Error looking for container version #{version}: #{e.message}"
267
+ return false
268
+ else
269
+ raise
270
+ end
271
+ end
272
+
273
+ true
274
+ end
275
+
276
+ def deploy_options
277
+ { "version" => KAMAL.config.version }.merge(options.without("skip_push"))
278
+ end
279
+ end