From 3fa9cd5a41ee8468a02386b873836722cc468691 Mon Sep 17 00:00:00 2001 From: Federico Date: Fri, 20 Sep 2024 18:20:50 -0300 Subject: [PATCH 01/27] Make kamal use ssh keys from config when performing commands --- lib/kamal/commands/base.rb | 11 ++++++++++- test/commands/app_test.rb | 10 ++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/kamal/commands/base.rb b/lib/kamal/commands/base.rb index c0eac91c..8ef584ae 100644 --- a/lib/kamal/commands/base.rb +++ b/lib/kamal/commands/base.rb @@ -11,7 +11,7 @@ module Kamal::Commands end def run_over_ssh(*command, host:) - "ssh#{ssh_proxy_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'" + "ssh#{ssh_proxy_args}#{ssh_keys_args} -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ").gsub("'", "'\\\\''")}'" end def container_id_for(container_name:, only_running: false) @@ -94,5 +94,14 @@ module Kamal::Commands " -o ProxyCommand='#{config.ssh.proxy.command_line_template}'" end end + + def ssh_keys_args + args = "" + config.ssh.keys&.each do |key| + args << " -i #{key}" + end + args << " -o IdentitiesOnly=yes" if config.ssh&.keys_only + args + end end end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 1fb59e8a..aad31f8a 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -315,6 +315,16 @@ class CommandsAppTest < ActiveSupport::TestCase assert_equal "ssh -J root@2.2.2.2 -t app@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1") end + test "run over ssh with keys config" do + @config[:ssh] = { "keys" => [ "path_to_key.pem" ] } + assert_equal "ssh -i path_to_key.pem -t root@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1") + end + + test "run over ssh with keys config with keys_only" do + @config[:ssh] = { "keys" => [ "path_to_key.pem" ], "keys_only" => true } + assert_equal "ssh -i path_to_key.pem -o IdentitiesOnly=yes -t root@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1") + end + test "run over ssh with proxy_command" do @config[:ssh] = { "proxy_command" => "ssh -W %h:%p user@proxy-server" } assert_equal "ssh -o ProxyCommand='ssh -W %h:%p user@proxy-server' -t root@1.1.1.1 -p 22 'ls'", new_command.run_over_ssh("ls", host: "1.1.1.1") From dbe0c3a7f8269db005528b1afd18773e1321722f Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:32:01 +0200 Subject: [PATCH 02/27] Allow running detached app commands this is useful for long running rake tasks or scripts that can be run without having to keep open connection to the server. Example: ``` kamal app exec 'bin/rails db:backfill_task' --detach ``` --- lib/kamal/cli/app.rb | 4 +++- lib/kamal/commands/app/execution.rb | 3 ++- test/cli/app_test.rb | 6 ++++++ test/commands/app_test.rb | 6 ++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 74b7b4df..0217dbd1 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -94,9 +94,11 @@ class Kamal::Cli::App < Kamal::Cli::Base option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)" option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one" option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command" + option :detach, type: :boolean, default: false, desc: "Execute command in a detached container" def exec(*cmd) cmd = Kamal::Utils.join_commands(cmd) env = options[:env] + detach = options[:detach] case when options[:interactive] && options[:reuse] say "Get current version of running container...", :magenta unless options[:version] @@ -138,7 +140,7 @@ class Kamal::Cli::App < Kamal::Cli::Base roles.each do |role| execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug - puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env)) + puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach)) end end end diff --git a/lib/kamal/commands/app/execution.rb b/lib/kamal/commands/app/execution.rb index 4434c26a..d6fa04fd 100644 --- a/lib/kamal/commands/app/execution.rb +++ b/lib/kamal/commands/app/execution.rb @@ -7,9 +7,10 @@ module Kamal::Commands::App::Execution *command end - def execute_in_new_container(*command, interactive: false, env:) + def execute_in_new_container(*command, interactive: false, detach: false, env:) docker :run, ("-it" if interactive), + ("--detach" if detach), "--rm", "--network", "kamal", *role&.env_args(host), diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 5e76179c..63eade9b 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -273,6 +273,12 @@ class CliAppTest < CliTestCase end end + test "exec detach" do + run_command("exec", "--detach", "ruby -v").tap do |output| + assert_match "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v", output + end + end + test "exec with reuse" do run_command("exec", "--reuse", "ruby -v").tap do |output| assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 1fb59e8a..abcefed1 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -234,6 +234,12 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.execute_in_new_container("bin/rails", "db:setup", env: { "foo" => "bar" }).join(" ") end + test "execute in new detached container" do + assert_equal \ + "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails db:setup", + new_command.execute_in_new_container("bin/rails", "db:setup", detach: true, env: {}).join(" ") + end + test "execute in new container with tags" do @config[:servers] = [ { "1.1.1.1" => "tag1" } ] @config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } } From c662b8d578275f0796e5bfa05c5bcea94472ddca Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Wed, 23 Oct 2024 16:57:34 +0200 Subject: [PATCH 03/27] Make --detach incompatible with reuse or interactive --- lib/kamal/cli/app.rb | 4 ++++ test/cli/app_test.rb | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 0217dbd1..3f813014 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -96,6 +96,10 @@ class Kamal::Cli::App < Kamal::Cli::Base option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command" option :detach, type: :boolean, default: false, desc: "Execute command in a detached container" def exec(*cmd) + if (incompatible_options = [ :interactive, :reuse ].select { |key| options[:detach] && options[key] }.presence) + raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}" + end + cmd = Kamal::Utils.join_commands(cmd) env = options[:env] detach = options[:detach] diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 63eade9b..c5255c4a 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -279,6 +279,24 @@ class CliAppTest < CliTestCase end end + test "exec detach with reuse" do + assert_raises(ArgumentError, "Detach is not compatible with reuse") do + run_command("exec", "--detach", "--reuse", "ruby -v") + end + end + + test "exec detach with interactive" do + assert_raises(ArgumentError, "Detach is not compatible with interactive") do + run_command("exec", "--interactive", "--detach", "ruby -v") + end + end + + test "exec detach with interactive and reuse" do + assert_raises(ArgumentError, "Detach is not compatible with interactive or reuse") do + run_command("exec", "--interactive", "--detach", "--reuse", "ruby -v") + end + end + test "exec with reuse" do run_command("exec", "--reuse", "ruby -v").tap do |output| assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version From 1da882bb01b4077e6e105664328d14b757124e06 Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Wed, 23 Oct 2024 18:58:38 +0200 Subject: [PATCH 04/27] Enable logging on app exec new containers --- lib/kamal/commands/app/execution.rb | 1 + test/cli/app_test.rb | 8 ++++---- test/commands/app_test.rb | 24 ++++++++++++++++-------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/kamal/commands/app/execution.rb b/lib/kamal/commands/app/execution.rb index d6fa04fd..3d5ff756 100644 --- a/lib/kamal/commands/app/execution.rb +++ b/lib/kamal/commands/app/execution.rb @@ -15,6 +15,7 @@ module Kamal::Commands::App::Execution "--network", "kamal", *role&.env_args(host), *argumentize("--env", env), + *role.logging_args, *config.volume_args, *role&.option_args, config.absolute_image, diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index c5255c4a..e6a51894 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -263,19 +263,19 @@ class CliAppTest < CliTestCase test "exec" do run_command("exec", "ruby -v").tap do |output| - assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v", output + assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output end end test "exec separate arguments" do run_command("exec", "ruby", " -v").tap do |output| - assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v", output + assert_match "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output end end test "exec detach" do run_command("exec", "--detach", "ruby -v").tap do |output| - assert_match "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v", output + assert_match "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output end end @@ -306,7 +306,7 @@ class CliAppTest < CliTestCase test "exec interactive" do SSHKit::Backend::Abstract.any_instance.expects(:exec) - .with("ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v'") + .with("ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v'") run_command("exec", "-i", "ruby -v").tap do |output| assert_match "Get most recent version available as an image...", output assert_match "Launching interactive command with version latest via SSH from new container on 1.1.1.1...", output diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index abcefed1..f3db9568 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -224,19 +224,27 @@ class CommandsAppTest < ActiveSupport::TestCase test "execute in new container" do assert_equal \ - "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails db:setup", + "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup", + new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") + end + + test "execute in new container with logging" do + @config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } } + + assert_equal \ + "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") end test "execute in new container with env" do assert_equal \ - "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --env foo=\"bar\" dhh/app:999 bin/rails db:setup", + "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --env foo=\"bar\" --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", env: { "foo" => "bar" }).join(" ") end test "execute in new detached container" do assert_equal \ - "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails db:setup", + "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", detach: true, env: {}).join(" ") end @@ -245,14 +253,14 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } } assert_equal \ - "docker run --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails db:setup", + "docker run --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") end test "execute in new container with custom options" do @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } } assert_equal \ - "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup", + "docker run --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") end @@ -269,7 +277,7 @@ class CommandsAppTest < ActiveSupport::TestCase end test "execute in new container over ssh" do - assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails c}, + assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size="10m" dhh/app:999 bin/rails c}, new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) end @@ -277,13 +285,13 @@ class CommandsAppTest < ActiveSupport::TestCase @config[:servers] = [ { "1.1.1.1" => "tag1" } ] @config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } } - assert_equal "ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails c'", + assert_equal "ssh -t root@1.1.1.1 -p 22 'docker run -it --rm --network kamal --env ENV1=\"value1\" --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails c'", new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) end test "execute in new container with custom options over ssh" do @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } } - assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c}, + assert_match %r{docker run -it --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c}, new_command.execute_in_new_container_over_ssh("bin/rails", "c", env: {}) end From 183fe9e06e56a154b6e9f076df0190b2f72381bd Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Sat, 26 Oct 2024 13:53:55 +0200 Subject: [PATCH 05/27] Follow logs of a specific container --- lib/kamal/cli/app.rb | 6 ++++-- lib/kamal/commands/app/logging.rb | 4 ++-- test/cli/app_test.rb | 7 +++++++ test/commands/app_test.rb | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 3f813014..3ed9a3b2 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -195,12 +195,14 @@ class Kamal::Cli::App < Kamal::Cli::Base option :grep_options, aliases: "-o", desc: "Additional options supplied to grep" option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)" option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output" + option :container_id, desc: "Docker container ID to fetch logs" def logs # FIXME: Catch when app containers aren't running grep = options[:grep] grep_options = options[:grep_options] since = options[:since] + container_id = options[:container_id] timestamps = !options[:skip_timestamps] if options[:follow] @@ -213,8 +215,8 @@ class Kamal::Cli::App < Kamal::Cli::Base role = KAMAL.roles_on(KAMAL.primary_host).first app = KAMAL.app(role: role, host: host) - info app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options) - exec app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options) + info app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options, container_id: container_id) + exec app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options, container_id: container_id) end else lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set diff --git a/lib/kamal/commands/app/logging.rb b/lib/kamal/commands/app/logging.rb index ad66f370..4a45a533 100644 --- a/lib/kamal/commands/app/logging.rb +++ b/lib/kamal/commands/app/logging.rb @@ -6,10 +6,10 @@ module Kamal::Commands::App::Logging ("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep) end - def follow_logs(host:, timestamps: true, lines: nil, grep: nil, grep_options: nil) + def follow_logs(host:, timestamps: true, lines: nil, grep: nil, grep_options: nil, container_id: nil) run_over_ssh \ pipe( - current_running_container_id, + container_id ? "echo #{container_id}" : current_running_container_id, "xargs docker logs#{" --timestamps" if timestamps}#{" --tail #{lines}" if lines} --follow 2>&1", (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep) ), diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index e6a51894..63937ea2 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -353,6 +353,13 @@ class CliAppTest < CliTestCase assert_match "sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''\\'\\'''\\''{{.ID}}'\\''\\'\\'''\\'') ; docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow") end + test "logs with follow and container_id" do + SSHKit::Backend::Abstract.any_instance.stubs(:exec) + .with("ssh -t root@1.1.1.1 -p 22 'echo ID123 | xargs docker logs --timestamps --tail 10 --follow 2>&1'") + + assert_match "echo ID123 | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow", "--container_id", "ID123") + end + test "logs with follow and grep" do SSHKit::Backend::Abstract.any_instance.stubs(:exec) .with("ssh -t root@1.1.1.1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''\\'\\'''\\''{{.ID}}'\\''\\'\\'''\\'') ; docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --follow 2>&1 | grep \"hey\"'") diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index f3db9568..c9934629 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -208,6 +208,10 @@ class CommandsAppTest < ActiveSupport::TestCase "ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''\\'\\'''\\''{{.ID}}'\\''\\'\\'''\\'') ; docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --follow 2>&1 | grep \"Completed\"'", new_command.follow_logs(host: "app-1", grep: "Completed") + assert_equal \ + "ssh -t root@app-1 -p 22 'echo ID321 | xargs docker logs --timestamps --follow 2>&1'", + new_command.follow_logs(host: "app-1", container_id: "ID321") + assert_equal \ "ssh -t root@app-1 -p 22 'sh -c '\\''docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''\\'\\'''\\''{{.ID}}'\\''\\'\\'''\\'') ; docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting'\\'' | head -1 | xargs docker logs --timestamps --tail 123 --follow 2>&1'", new_command.follow_logs(host: "app-1", lines: 123) From 75b44cd328dac23013e2ee48e8847164a5291d9a Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Sat, 26 Oct 2024 14:40:35 +0200 Subject: [PATCH 06/27] Capture logs for specific container_id --- lib/kamal/cli/app.rb | 6 +++--- lib/kamal/cli/app/boot.rb | 2 +- lib/kamal/commands/app/execution.rb | 2 +- lib/kamal/commands/app/logging.rb | 6 +++--- test/cli/app_test.rb | 2 +- test/commands/app_test.rb | 8 +++++++- 6 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 3ed9a3b2..2378fa03 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -215,8 +215,8 @@ class Kamal::Cli::App < Kamal::Cli::Base role = KAMAL.roles_on(KAMAL.primary_host).first app = KAMAL.app(role: role, host: host) - info app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options, container_id: container_id) - exec app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options, container_id: container_id) + info app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options) + exec app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options) end else lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set @@ -226,7 +226,7 @@ class Kamal::Cli::App < Kamal::Cli::Base roles.each do |role| begin - 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)) + 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)) rescue SSHKit::Command::Failed puts_by_host host, "Nothing found" end diff --git a/lib/kamal/cli/app/boot.rb b/lib/kamal/cli/app/boot.rb index fd330c71..b98e0472 100644 --- a/lib/kamal/cli/app/boot.rb +++ b/lib/kamal/cli/app/boot.rb @@ -91,7 +91,7 @@ class Kamal::Cli::App::Boot if barrier.close info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles" begin - error capture_with_info(*app.logs(version: version)) + error capture_with_info(*app.logs(container_id: "$(#{app.container_id_for_version(version)})")) error capture_with_info(*app.container_health_log(version: version)) rescue SSHKit::Command::Failed error "Could not fetch logs for #{version}" diff --git a/lib/kamal/commands/app/execution.rb b/lib/kamal/commands/app/execution.rb index 3d5ff756..e1289fd8 100644 --- a/lib/kamal/commands/app/execution.rb +++ b/lib/kamal/commands/app/execution.rb @@ -11,7 +11,7 @@ module Kamal::Commands::App::Execution docker :run, ("-it" if interactive), ("--detach" if detach), - "--rm", + ("--rm" unless detach), "--network", "kamal", *role&.env_args(host), *argumentize("--env", env), diff --git a/lib/kamal/commands/app/logging.rb b/lib/kamal/commands/app/logging.rb index 4a45a533..56e0679c 100644 --- a/lib/kamal/commands/app/logging.rb +++ b/lib/kamal/commands/app/logging.rb @@ -1,12 +1,12 @@ module Kamal::Commands::App::Logging - def logs(version: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil) + def logs(container_id: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil) pipe \ - version ? container_id_for_version(version) : current_running_container_id, + container_id ? "echo #{container_id}" : current_running_container_id, "xargs docker logs#{" --timestamps" if timestamps}#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1", ("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep) end - def follow_logs(host:, timestamps: true, lines: nil, grep: nil, grep_options: nil, container_id: nil) + def follow_logs(host:, container_id: nil, timestamps: true, lines: nil, grep: nil, grep_options: nil) run_over_ssh \ pipe( container_id ? "echo #{container_id}" : current_running_container_id, diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 63937ea2..7c71d05e 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -275,7 +275,7 @@ class CliAppTest < CliTestCase test "exec detach" do run_command("exec", "--detach", "ruby -v").tap do |output| - assert_match "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output + assert_match "docker run --detach --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:latest ruby -v", output end end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index c9934629..c42f535a 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -157,6 +157,12 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.logs.join(" ") end + test "logs with container_id" do + assert_equal \ + "echo C137 | xargs docker logs --timestamps 2>&1", + new_command.logs(container_id: "C137").join(" ") + end + test "logs with since" do assert_equal \ "sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --since 5m 2>&1", @@ -248,7 +254,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "execute in new detached container" do assert_equal \ - "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup", + "docker run --detach --network kamal --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" dhh/app:999 bin/rails db:setup", new_command.execute_in_new_container("bin/rails", "db:setup", detach: true, env: {}).join(" ") end From ac90ee068fd36c31c5a7adf94c22a0f8153a2306 Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Sat, 26 Oct 2024 18:47:10 +0200 Subject: [PATCH 07/27] Prefer dasherized notation --- test/cli/app_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 7c71d05e..2e532730 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -357,7 +357,7 @@ class CliAppTest < CliTestCase SSHKit::Backend::Abstract.any_instance.stubs(:exec) .with("ssh -t root@1.1.1.1 -p 22 'echo ID123 | xargs docker logs --timestamps --tail 10 --follow 2>&1'") - assert_match "echo ID123 | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow", "--container_id", "ID123") + assert_match "echo ID123 | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow", "--container-id", "ID123") end test "logs with follow and grep" do From e31b98539cfe5813a7efd17a08559ecfcf5f1fe4 Mon Sep 17 00:00:00 2001 From: Matteo Giaccone Date: Fri, 22 Nov 2024 09:57:45 +0100 Subject: [PATCH 08/27] Avoid string mutation For Ruby 3.4 --- lib/kamal/commands/base.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/kamal/commands/base.rb b/lib/kamal/commands/base.rb index 8ef584ae..6d3f71ec 100644 --- a/lib/kamal/commands/base.rb +++ b/lib/kamal/commands/base.rb @@ -96,12 +96,13 @@ module Kamal::Commands end def ssh_keys_args - args = "" - config.ssh.keys&.each do |key| - args << " -i #{key}" + "#{ ssh_keys.join("") if ssh_keys}" + "#{" -o IdentitiesOnly=yes" if config.ssh&.keys_only}" + end + + def ssh_keys + config.ssh.keys&.map do |key| + " -i #{key}" end - args << " -o IdentitiesOnly=yes" if config.ssh&.keys_only - args end end end From 1cc5406b00048775dce4b75aabd94512ee821abb Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:37:58 +0100 Subject: [PATCH 09/27] Pipe app container id --- lib/kamal/cli/app/boot.rb | 2 +- lib/kamal/commands/app/logging.rb | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/kamal/cli/app/boot.rb b/lib/kamal/cli/app/boot.rb index b98e0472..41db39a6 100644 --- a/lib/kamal/cli/app/boot.rb +++ b/lib/kamal/cli/app/boot.rb @@ -91,7 +91,7 @@ class Kamal::Cli::App::Boot if barrier.close info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles" begin - error capture_with_info(*app.logs(container_id: "$(#{app.container_id_for_version(version)})")) + error capture_with_info(*app.logs(container_id: app.container_id_for_version(version))) error capture_with_info(*app.container_health_log(version: version)) rescue SSHKit::Command::Failed error "Could not fetch logs for #{version}" diff --git a/lib/kamal/commands/app/logging.rb b/lib/kamal/commands/app/logging.rb index 56e0679c..d5c8d5c7 100644 --- a/lib/kamal/commands/app/logging.rb +++ b/lib/kamal/commands/app/logging.rb @@ -1,7 +1,7 @@ module Kamal::Commands::App::Logging def logs(container_id: nil, timestamps: true, since: nil, lines: nil, grep: nil, grep_options: nil) pipe \ - container_id ? "echo #{container_id}" : current_running_container_id, + container_id_command(container_id), "xargs docker logs#{" --timestamps" if timestamps}#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1", ("grep '#{grep}'#{" #{grep_options}" if grep_options}" if grep) end @@ -9,10 +9,20 @@ module Kamal::Commands::App::Logging def follow_logs(host:, container_id: nil, timestamps: true, lines: nil, grep: nil, grep_options: nil) run_over_ssh \ pipe( - container_id ? "echo #{container_id}" : current_running_container_id, + container_id_command(container_id), "xargs docker logs#{" --timestamps" if timestamps}#{" --tail #{lines}" if lines} --follow 2>&1", (%(grep "#{grep}"#{" #{grep_options}" if grep_options}) if grep) ), host: host end + + private + + def container_id_command(container_id) + case container_id + when Array then container_id + when String, Symbol then "echo #{container_id}" + else current_running_container_id + end + end end From 8a7843cb35600195f587e171b1ddb5d56bfb3e84 Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Sat, 23 Nov 2024 21:43:34 +0100 Subject: [PATCH 10/27] Allow running the CI manually --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 839bc4e6..8ef7d0ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,7 @@ on: branches: - main pull_request: + workflow_dispatch: jobs: rubocop: name: RuboCop From a1596af8156ef19d7be0a62a1d6c4c6b0679cebf Mon Sep 17 00:00:00 2001 From: Axel Gustav Date: Tue, 26 Nov 2024 09:55:21 -0400 Subject: [PATCH 11/27] Bump proxy minimum version to 0.8.3 --- lib/kamal/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 021e5e49..23d13e99 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -14,7 +14,7 @@ class Kamal::Configuration include Validation - PROXY_MINIMUM_VERSION = "v0.8.2" + PROXY_MINIMUM_VERSION = "v0.8.3" PROXY_HTTP_PORT = 80 PROXY_HTTPS_PORT = 443 PROXY_LOG_MAX_SIZE = "10m" From 2386c903ca4450f98bc10419e0b354be185bbde9 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Mon, 2 Dec 2024 10:37:07 +0000 Subject: [PATCH 12/27] Update to proxy version 0.8.4 Release: https://github.com/basecamp/kamal-proxy/releases/tag/v0.8.4 - Silence late healthcheck requests --- lib/kamal/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 23d13e99..92d850e2 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -14,7 +14,7 @@ class Kamal::Configuration include Validation - PROXY_MINIMUM_VERSION = "v0.8.3" + PROXY_MINIMUM_VERSION = "v0.8.4" PROXY_HTTP_PORT = 80 PROXY_HTTPS_PORT = 443 PROXY_LOG_MAX_SIZE = "10m" From f5391d7fe4e0347275b59d5090b4467d6b15b0fd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:06:59 +0000 Subject: [PATCH 13/27] Bump rails-html-sanitizer in the bundler group across 1 directory Bumps the bundler group with 1 update in the / directory: [rails-html-sanitizer](https://github.com/rails/rails-html-sanitizer). Updates `rails-html-sanitizer` from 1.6.0 to 1.6.1 - [Release notes](https://github.com/rails/rails-html-sanitizer/releases) - [Changelog](https://github.com/rails/rails-html-sanitizer/blob/main/CHANGELOG.md) - [Commits](https://github.com/rails/rails-html-sanitizer/compare/v1.6.0...v1.6.1) --- updated-dependencies: - dependency-name: rails-html-sanitizer dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index c057d7f6..9af5e108 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -67,7 +67,7 @@ GEM reline (>= 0.4.2) json (2.7.2) language_server-protocol (3.17.0.3) - loofah (2.22.0) + loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) minitest (5.25.1) @@ -79,11 +79,11 @@ GEM net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) net-ssh (7.3.0) - nokogiri (1.16.7-arm64-darwin) + nokogiri (1.16.8-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-darwin) + nokogiri (1.16.8-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.7-x86_64-linux) + nokogiri (1.16.8-x86_64-linux) racc (~> 1.4) parallel (1.25.1) parser (3.3.4.0) @@ -104,9 +104,9 @@ GEM activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.1) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) railties (7.1.4.1) actionpack (= 7.1.4.1) activesupport (= 7.1.4.1) From 3468b45014392f1763bf7963ce482f4ab019c5be Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:46:48 -0800 Subject: [PATCH 14/27] Bump actionpack in the bundler group across 1 directory (#1283) Bumps the bundler group with 1 update in the / directory: [actionpack](https://github.com/rails/rails). Updates `actionpack` from 7.1.4.1 to 7.1.5.1 - [Release notes](https://github.com/rails/rails/releases) - [Changelog](https://github.com/rails/rails/blob/v8.0.0.1/actionpack/CHANGELOG.md) - [Commits](https://github.com/rails/rails/compare/v7.1.4.1...v7.1.5.1) --- updated-dependencies: - dependency-name: actionpack dependency-type: indirect dependency-group: bundler ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9af5e108..486b29ed 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,9 +16,9 @@ PATH GEM remote: https://rubygems.org/ specs: - actionpack (7.1.4.1) - actionview (= 7.1.4.1) - activesupport (= 7.1.4.1) + actionpack (7.1.5.1) + actionview (= 7.1.5.1) + activesupport (= 7.1.5.1) nokogiri (>= 1.8.5) racc rack (>= 2.2.4) @@ -26,27 +26,31 @@ GEM rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actionview (7.1.4.1) - activesupport (= 7.1.4.1) + actionview (7.1.5.1) + activesupport (= 7.1.5.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activesupport (7.1.4.1) + activesupport (7.1.5.1) base64 + benchmark (>= 0.3) bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) ast (2.4.2) base64 (0.2.0) bcrypt_pbkdf (1.1.1) bcrypt_pbkdf (1.1.1-arm64-darwin) bcrypt_pbkdf (1.1.1-x86_64-darwin) + benchmark (0.4.0) bigdecimal (3.1.8) builder (3.3.0) concurrent-ruby (1.3.4) @@ -67,23 +71,24 @@ GEM reline (>= 0.4.2) json (2.7.2) language_server-protocol (3.17.0.3) + logger (1.6.2) loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) - minitest (5.25.1) + minitest (5.25.4) mocha (2.4.5) ruby2_keywords (>= 0.0.5) - mutex_m (0.2.0) + mutex_m (0.3.0) net-scp (4.0.0) net-ssh (>= 2.6.5, < 8.0.0) net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) net-ssh (7.3.0) - nokogiri (1.16.8-arm64-darwin) + nokogiri (1.17.1-arm64-darwin) racc (~> 1.4) - nokogiri (1.16.8-x86_64-darwin) + nokogiri (1.17.1-x86_64-darwin) racc (~> 1.4) - nokogiri (1.16.8-x86_64-linux) + nokogiri (1.17.1-x86_64-linux) racc (~> 1.4) parallel (1.25.1) parser (3.3.4.0) @@ -107,9 +112,9 @@ GEM rails-html-sanitizer (1.6.1) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.1.4.1) - actionpack (= 7.1.4.1) - activesupport (= 7.1.4.1) + railties (7.1.5.1) + actionpack (= 7.1.5.1) + activesupport (= 7.1.5.1) irb rackup (>= 1.0.0) rake (>= 12.2) @@ -154,6 +159,7 @@ GEM rubocop-rails ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) + securerandom (0.4.0) sshkit (1.23.0) base64 net-scp (>= 1.1.2) From 407c8b834ec8c8e31dc24da88129597f643d069b Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Tue, 10 Dec 2024 15:57:30 -0800 Subject: [PATCH 15/27] Simplify hostname trimming. References #762. --- lib/kamal/cli/app/boot.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/kamal/cli/app/boot.rb b/lib/kamal/cli/app/boot.rb index 41db39a6..8ddcaac3 100644 --- a/lib/kamal/cli/app/boot.rb +++ b/lib/kamal/cli/app/boot.rb @@ -45,7 +45,7 @@ class Kamal::Cli::App::Boot def start_new_version audit "Booted app version #{version}" - hostname = "#{host.to_s[0...51].gsub(/\.+$/, '')}-#{SecureRandom.hex(6)}" + hostname = "#{host.to_s[0...51].chomp(".")}-#{SecureRandom.hex(6)}" execute *app.ensure_env_directory upload! role.secrets_io(host), role.secrets_path, mode: "0600" From 16fb3adacb70d600bfbe2136c76905960d978349 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Tue, 10 Dec 2024 16:08:56 -0800 Subject: [PATCH 16/27] No need for IO.read for basic file paths References 3cad095, e1d5182 --- lib/kamal/configuration.rb | 2 +- lib/kamal/configuration/accessory.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 92d850e2..52c807d7 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -37,7 +37,7 @@ class Kamal::Configuration if file.exist? # Newer Psych doesn't load aliases by default load_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load - YAML.send(load_method, ERB.new(IO.read(file)).result).symbolize_keys + YAML.send(load_method, ERB.new(File.read(file)).result).symbolize_keys else raise "Configuration file not found in #{file}" end diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index 2728607d..198e6321 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -142,7 +142,7 @@ class Kamal::Configuration::Accessory end def read_dynamic_file(local_file) - StringIO.new(ERB.new(IO.read(local_file)).result) + StringIO.new(ERB.new(File.read(local_file)).result) end def expand_remote_file(remote_file) From b2cf3f33a781f1ddfd14d89b019b36435d759e8e Mon Sep 17 00:00:00 2001 From: Nick Hammond Date: Thu, 12 Dec 2024 03:47:25 -0700 Subject: [PATCH 17/27] Remove the alias for grep_options, issues processing with thor --- lib/kamal/cli/accessory.rb | 2 +- lib/kamal/cli/app.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index 4de8832b..00999b2d 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -162,7 +162,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)" option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server" option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)" - option :grep_options, aliases: "-o", desc: "Additional options supplied to grep" + option :grep_options, desc: "Additional options supplied to grep" option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)" option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output" def logs(name) diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 2378fa03..fb665af9 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -192,7 +192,7 @@ class Kamal::Cli::App < Kamal::Cli::Base option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)" option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server" option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)" - option :grep_options, aliases: "-o", desc: "Additional options supplied to grep" + option :grep_options, desc: "Additional options supplied to grep" option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)" option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output" option :container_id, desc: "Docker container ID to fetch logs" From 55983c64313af9487be0d55501e2e94b9609e4d1 Mon Sep 17 00:00:00 2001 From: Nick Hammond Date: Thu, 12 Dec 2024 04:10:48 -0700 Subject: [PATCH 18/27] AWS secrets manager value can be a string --- .../secrets/adapters/aws_secrets_manager.rb | 2 + .../aws_secrets_manager_adapter_test.rb | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/lib/kamal/secrets/adapters/aws_secrets_manager.rb b/lib/kamal/secrets/adapters/aws_secrets_manager.rb index e23ea1f1..5db8fd7c 100644 --- a/lib/kamal/secrets/adapters/aws_secrets_manager.rb +++ b/lib/kamal/secrets/adapters/aws_secrets_manager.rb @@ -13,6 +13,8 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba secret_string.each do |key, value| results["#{secret_name}/#{key}"] = value end + rescue JSON::ParserError + results["#{secret_name}"] = secret["SecretString"] end end end diff --git a/test/secrets/aws_secrets_manager_adapter_test.rb b/test/secrets/aws_secrets_manager_adapter_test.rb index 42a0f48a..7ca947a6 100644 --- a/test/secrets/aws_secrets_manager_adapter_test.rb +++ b/test/secrets/aws_secrets_manager_adapter_test.rb @@ -44,6 +44,48 @@ class AwsSecretsManagerAdapterTest < SecretAdapterTestCase assert_equal expected_json, json end + test "fetch with string value" do + stub_ticks.with("aws --version 2> /dev/null") + stub_ticks + .with("aws secretsmanager batch-get-secret-value --secret-id-list secret secret2/KEY1 --profile default") + .returns(<<~JSON) + { + "SecretValues": [ + { + "ARN": "arn:aws:secretsmanager:us-east-1:aaaaaaaaaaaa:secret:secret", + "Name": "secret", + "VersionId": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv", + "SecretString": "a-string-secret", + "VersionStages": [ + "AWSCURRENT" + ], + "CreatedDate": "2024-01-01T00:00:00.000000" + }, + { + "ARN": "arn:aws:secretsmanager:us-east-1:aaaaaaaaaaaa:secret:secret2", + "Name": "secret2", + "VersionId": "vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv", + "SecretString": "{\\"KEY2\\":\\"VALUE2\\"}", + "VersionStages": [ + "AWSCURRENT" + ], + "CreatedDate": "2024-01-01T00:00:00.000000" + } + ], + "Errors": [] + } + JSON + + json = JSON.parse(shellunescape(run_command("fetch", "secret", "secret2/KEY1"))) + + expected_json = { + "secret"=>"a-string-secret", + "secret/KEY2"=>"VALUE2" + } + + assert_equal expected_json, json + end + test "fetch with secret names" do stub_ticks.with("aws --version 2> /dev/null") stub_ticks From 68e6f82b30b1b7b57f1e7c4ed6b1a5d6b8831439 Mon Sep 17 00:00:00 2001 From: Nick Hammond Date: Thu, 12 Dec 2024 04:17:03 -0700 Subject: [PATCH 19/27] Grab from secret2 for assertion --- test/secrets/aws_secrets_manager_adapter_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/secrets/aws_secrets_manager_adapter_test.rb b/test/secrets/aws_secrets_manager_adapter_test.rb index 7ca947a6..3fbfe3e9 100644 --- a/test/secrets/aws_secrets_manager_adapter_test.rb +++ b/test/secrets/aws_secrets_manager_adapter_test.rb @@ -80,7 +80,7 @@ class AwsSecretsManagerAdapterTest < SecretAdapterTestCase expected_json = { "secret"=>"a-string-secret", - "secret/KEY2"=>"VALUE2" + "secret2/KEY2"=>"VALUE2" } assert_equal expected_json, json From e4641773499192f7ba0b0b8c27706540a37fa78e Mon Sep 17 00:00:00 2001 From: Nick Hammond Date: Thu, 12 Dec 2024 04:58:53 -0700 Subject: [PATCH 20/27] Check for errors from AWS secrets manager --- .../secrets/adapters/aws_secrets_manager.rb | 14 ++++++++--- .../aws_secrets_manager_adapter_test.rb | 24 +++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/kamal/secrets/adapters/aws_secrets_manager.rb b/lib/kamal/secrets/adapters/aws_secrets_manager.rb index 5db8fd7c..e3f54687 100644 --- a/lib/kamal/secrets/adapters/aws_secrets_manager.rb +++ b/lib/kamal/secrets/adapters/aws_secrets_manager.rb @@ -6,7 +6,15 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba def fetch_secrets(secrets, account:, session:) {}.tap do |results| - JSON.parse(get_from_secrets_manager(secrets, account: account))["SecretValues"].each do |secret| + secrets = JSON.parse(get_from_secrets_manager(secrets, account: account)) + + if secrets["Errors"].present? + first_error = secrets["Errors"].first + + raise RuntimeError, "#{first_error['SecretId']}: #{first_error['Message']}" + end + + secrets["SecretValues"].each do |secret| secret_name = secret["Name"] secret_string = JSON.parse(secret["SecretString"]) @@ -20,8 +28,8 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba end def get_from_secrets_manager(secrets, account:) - `aws secretsmanager batch-get-secret-value --secret-id-list #{secrets.map(&:shellescape).join(" ")} --profile #{account.shellescape}`.tap do - raise RuntimeError, "Could not read #{secret} from AWS Secrets Manager" unless $?.success? + `aws secretsmanager batch-get-secret-value --secret-id-list #{secrets.map(&:shellescape).join(" ")} --profile #{account.shellescape}`.tap do |secrets| + raise RuntimeError, "Could not read #{secrets} from AWS Secrets Manager" unless $?.success? end end diff --git a/test/secrets/aws_secrets_manager_adapter_test.rb b/test/secrets/aws_secrets_manager_adapter_test.rb index 3fbfe3e9..5873731e 100644 --- a/test/secrets/aws_secrets_manager_adapter_test.rb +++ b/test/secrets/aws_secrets_manager_adapter_test.rb @@ -1,6 +1,30 @@ require "test_helper" class AwsSecretsManagerAdapterTest < SecretAdapterTestCase + test "fails when errors are present" do + stub_ticks.with("aws --version 2> /dev/null") + stub_ticks + .with("aws secretsmanager batch-get-secret-value --secret-id-list unknown-secret-id --profile default") + .returns(<<~JSON) + { + "SecretValues": [], + "Errors": [ + { + "SecretId": "unknown-secret-id", + "ErrorCode": "ResourceNotFoundException", + "Message": "Secrets Manager can't find the specified secret." + } + ] + } + JSON + + error = assert_raises RuntimeError do + JSON.parse(shellunescape(run_command("fetch", "unknown-secret-id"))) + end + + assert_equal "unknown-secret-id: Secrets Manager can't find the specified secret.", error.message + end + test "fetch" do stub_ticks.with("aws --version 2> /dev/null") stub_ticks From ba567e0474e77450e54267ac6237c7ae907bc19d Mon Sep 17 00:00:00 2001 From: Nick Hammond Date: Thu, 12 Dec 2024 05:09:12 -0700 Subject: [PATCH 21/27] Just map the secrets returned from AWS --- .../secrets/adapters/aws_secrets_manager.rb | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/kamal/secrets/adapters/aws_secrets_manager.rb b/lib/kamal/secrets/adapters/aws_secrets_manager.rb index e3f54687..c9314ca5 100644 --- a/lib/kamal/secrets/adapters/aws_secrets_manager.rb +++ b/lib/kamal/secrets/adapters/aws_secrets_manager.rb @@ -6,15 +6,7 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba def fetch_secrets(secrets, account:, session:) {}.tap do |results| - secrets = JSON.parse(get_from_secrets_manager(secrets, account: account)) - - if secrets["Errors"].present? - first_error = secrets["Errors"].first - - raise RuntimeError, "#{first_error['SecretId']}: #{first_error['Message']}" - end - - secrets["SecretValues"].each do |secret| + get_from_secrets_manager(secrets, account: account).each do |secret| secret_name = secret["Name"] secret_string = JSON.parse(secret["SecretString"]) @@ -30,6 +22,12 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba def get_from_secrets_manager(secrets, account:) `aws secretsmanager batch-get-secret-value --secret-id-list #{secrets.map(&:shellescape).join(" ")} --profile #{account.shellescape}`.tap do |secrets| raise RuntimeError, "Could not read #{secrets} from AWS Secrets Manager" unless $?.success? + + secrets = JSON.parse(secrets) + + return secrets["SecretValues"] unless secrets["Errors"].present? + + raise RuntimeError, secrets["Errors"].map { |error| "#{error['SecretId']}: #{error['Message']}" }.join(", ") end end From 84a874e63b39d42d4c82dc9411b5af9e4853f766 Mon Sep 17 00:00:00 2001 From: Nick Hammond Date: Thu, 12 Dec 2024 05:15:52 -0700 Subject: [PATCH 22/27] Update secrets manager spec to render multiple errors --- lib/kamal/secrets/adapters/aws_secrets_manager.rb | 2 +- test/secrets/aws_secrets_manager_adapter_test.rb | 13 +++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/kamal/secrets/adapters/aws_secrets_manager.rb b/lib/kamal/secrets/adapters/aws_secrets_manager.rb index c9314ca5..4bcac21d 100644 --- a/lib/kamal/secrets/adapters/aws_secrets_manager.rb +++ b/lib/kamal/secrets/adapters/aws_secrets_manager.rb @@ -27,7 +27,7 @@ class Kamal::Secrets::Adapters::AwsSecretsManager < Kamal::Secrets::Adapters::Ba return secrets["SecretValues"] unless secrets["Errors"].present? - raise RuntimeError, secrets["Errors"].map { |error| "#{error['SecretId']}: #{error['Message']}" }.join(", ") + raise RuntimeError, secrets["Errors"].map { |error| "#{error['SecretId']}: #{error['Message']}" }.join(" ") end end diff --git a/test/secrets/aws_secrets_manager_adapter_test.rb b/test/secrets/aws_secrets_manager_adapter_test.rb index 5873731e..60074778 100644 --- a/test/secrets/aws_secrets_manager_adapter_test.rb +++ b/test/secrets/aws_secrets_manager_adapter_test.rb @@ -4,13 +4,18 @@ class AwsSecretsManagerAdapterTest < SecretAdapterTestCase test "fails when errors are present" do stub_ticks.with("aws --version 2> /dev/null") stub_ticks - .with("aws secretsmanager batch-get-secret-value --secret-id-list unknown-secret-id --profile default") + .with("aws secretsmanager batch-get-secret-value --secret-id-list unknown1 unknown2 --profile default") .returns(<<~JSON) { "SecretValues": [], "Errors": [ { - "SecretId": "unknown-secret-id", + "SecretId": "unknown1", + "ErrorCode": "ResourceNotFoundException", + "Message": "Secrets Manager can't find the specified secret." + }, + { + "SecretId": "unknown2", "ErrorCode": "ResourceNotFoundException", "Message": "Secrets Manager can't find the specified secret." } @@ -19,10 +24,10 @@ class AwsSecretsManagerAdapterTest < SecretAdapterTestCase JSON error = assert_raises RuntimeError do - JSON.parse(shellunescape(run_command("fetch", "unknown-secret-id"))) + JSON.parse(shellunescape(run_command("fetch", "unknown1", "unknown2"))) end - assert_equal "unknown-secret-id: Secrets Manager can't find the specified secret.", error.message + assert_equal ["unknown1: Secrets Manager can't find the specified secret.", "unknown2: Secrets Manager can't find the specified secret."].join(" "), error.message end test "fetch" do From 725da6aa68ae632b1e651b7ae7fbdfaf5414a40d Mon Sep 17 00:00:00 2001 From: Nick Hammond Date: Thu, 12 Dec 2024 05:29:15 -0700 Subject: [PATCH 23/27] Rubocop, Rubocop --- test/secrets/aws_secrets_manager_adapter_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/secrets/aws_secrets_manager_adapter_test.rb b/test/secrets/aws_secrets_manager_adapter_test.rb index 60074778..7616342d 100644 --- a/test/secrets/aws_secrets_manager_adapter_test.rb +++ b/test/secrets/aws_secrets_manager_adapter_test.rb @@ -27,7 +27,7 @@ class AwsSecretsManagerAdapterTest < SecretAdapterTestCase JSON.parse(shellunescape(run_command("fetch", "unknown1", "unknown2"))) end - assert_equal ["unknown1: Secrets Manager can't find the specified secret.", "unknown2: Secrets Manager can't find the specified secret."].join(" "), error.message + assert_equal [ "unknown1: Secrets Manager can't find the specified secret.", "unknown2: Secrets Manager can't find the specified secret." ].join(" "), error.message end test "fetch" do From 3e4a1901733791287a8f32097bd18a1b05c40296 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Fri, 13 Dec 2024 10:27:23 +0000 Subject: [PATCH 24/27] Fix for Dotenv 3.1.5 In Dotenv 3.1.5, `Dotenv.parse` no longer returns values that are already in the environment. See https://github.com/bkeepers/dotenv/issues/518 We can get the values though by setting overwrite: true, which works with both 3.1.4 and 3.1.5. --- Gemfile.lock | 101 ++++++++++++++++++++++--------------------- lib/kamal/secrets.rb | 2 +- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 486b29ed..d3ed5a48 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -16,35 +16,35 @@ PATH GEM remote: https://rubygems.org/ specs: - actionpack (7.1.5.1) - actionview (= 7.1.5.1) - activesupport (= 7.1.5.1) + actionpack (8.0.0.1) + actionview (= 8.0.0.1) + activesupport (= 8.0.0.1) nokogiri (>= 1.8.5) - racc rack (>= 2.2.4) rack-session (>= 1.0.1) rack-test (>= 0.6.3) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - actionview (7.1.5.1) - activesupport (= 7.1.5.1) + useragent (~> 0.16) + actionview (8.0.0.1) + activesupport (= 8.0.0.1) builder (~> 3.1) erubi (~> 1.11) rails-dom-testing (~> 2.2) rails-html-sanitizer (~> 1.6) - activesupport (7.1.5.1) + activesupport (8.0.0.1) base64 benchmark (>= 0.3) bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) logger (>= 1.4.2) minitest (>= 5.1) - mutex_m securerandom (>= 0.3) - tzinfo (~> 2.0) + tzinfo (~> 2.0, >= 2.0.5) + uri (>= 0.13.1) ast (2.4.2) base64 (0.2.0) bcrypt_pbkdf (1.1.1) @@ -56,45 +56,47 @@ GEM concurrent-ruby (1.3.4) connection_pool (2.4.1) crass (1.0.6) + date (3.4.1) debug (1.9.2) irb (~> 1.10) reline (>= 0.3.8) - dotenv (3.1.2) + dotenv (3.1.5) drb (2.2.1) ed25519 (1.3.0) erubi (1.13.0) i18n (1.14.6) concurrent-ruby (~> 1.0) - io-console (0.7.2) - irb (1.14.0) + io-console (0.8.0) + irb (1.14.2) rdoc (>= 4.0.0) reline (>= 0.4.2) - json (2.7.2) + json (2.9.0) language_server-protocol (3.17.0.3) - logger (1.6.2) + logger (1.6.3) loofah (2.23.1) crass (~> 1.0.2) nokogiri (>= 1.12.0) minitest (5.25.4) - mocha (2.4.5) + mocha (2.7.1) ruby2_keywords (>= 0.0.5) - mutex_m (0.3.0) net-scp (4.0.0) net-ssh (>= 2.6.5, < 8.0.0) net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) net-ssh (7.3.0) - nokogiri (1.17.1-arm64-darwin) + nokogiri (1.17.2-arm64-darwin) racc (~> 1.4) - nokogiri (1.17.1-x86_64-darwin) + nokogiri (1.17.2-x86_64-darwin) racc (~> 1.4) - nokogiri (1.17.1-x86_64-linux) + nokogiri (1.17.2-x86_64-linux) racc (~> 1.4) - parallel (1.25.1) - parser (3.3.4.0) + ostruct (0.6.1) + parallel (1.26.3) + parser (3.3.6.0) ast (~> 2.4.1) racc - psych (5.1.2) + psych (5.2.1) + date stringio racc (1.8.1) rack (3.1.8) @@ -102,55 +104,52 @@ GEM rack (>= 3.0.0) rack-test (2.1.0) rack (>= 1.3) - rackup (2.1.0) + rackup (2.2.1) rack (>= 3) - webrick (~> 1.8) rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.1) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) - railties (7.1.5.1) - actionpack (= 7.1.5.1) - activesupport (= 7.1.5.1) - irb + railties (8.0.0.1) + actionpack (= 8.0.0.1) + activesupport (= 8.0.0.1) + irb (~> 1.13) rackup (>= 1.0.0) rake (>= 12.2) thor (~> 1.0, >= 1.2.2) zeitwerk (~> 2.6) rainbow (3.1.1) rake (13.2.1) - rdoc (6.7.0) + rdoc (6.8.1) psych (>= 4.0.0) - regexp_parser (2.9.2) - reline (0.5.9) + regexp_parser (2.9.3) + reline (0.5.12) io-console (~> 0.5) - rexml (3.3.9) - rubocop (1.65.1) + rubocop (1.69.2) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 2.4, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.31.1, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.36.2, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.0) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.36.2) parser (>= 3.3.1.0) - rubocop-minitest (0.35.1) + rubocop-minitest (0.36.0) rubocop (>= 1.61, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-performance (1.21.1) + rubocop-performance (1.23.0) rubocop (>= 1.48.1, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) - rubocop-rails (2.25.1) + rubocop-rails (2.27.0) activesupport (>= 4.2.0) rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) + rubocop (>= 1.52.0, < 2.0) rubocop-ast (>= 1.31.1, < 2.0) rubocop-rails-omakase (1.0.0) rubocop @@ -160,17 +159,21 @@ GEM ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) securerandom (0.4.0) - sshkit (1.23.0) + sshkit (1.23.2) base64 net-scp (>= 1.1.2) net-sftp (>= 2.1.2) net-ssh (>= 2.8.0) - stringio (3.1.1) - thor (1.3.1) + ostruct + stringio (3.1.2) + thor (1.3.2) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.5.0) - webrick (1.8.2) + unicode-display_width (3.1.2) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) + uri (1.0.2) + useragent (0.16.11) zeitwerk (2.7.1) PLATFORMS diff --git a/lib/kamal/secrets.rb b/lib/kamal/secrets.rb index 7a382f98..9d4b0ff7 100644 --- a/lib/kamal/secrets.rb +++ b/lib/kamal/secrets.rb @@ -32,7 +32,7 @@ class Kamal::Secrets private def secrets @secrets ||= secrets_files.inject({}) do |secrets, secrets_file| - secrets.merge!(::Dotenv.parse(secrets_file)) + secrets.merge!(::Dotenv.parse(secrets_file, overwrite: true)) end end From 77c202ebaf409fd0bce91b1047811b01a47d57d0 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Fri, 13 Dec 2024 12:20:05 +0000 Subject: [PATCH 25/27] Highlight ssl/forward_headers behaviour Pulled in from: https://github.com/basecamp/kamal-site/pull/141 --- lib/kamal/configuration/docs/proxy.yml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/kamal/configuration/docs/proxy.yml b/lib/kamal/configuration/docs/proxy.yml index 76ec3e41..9ed6a97f 100644 --- a/lib/kamal/configuration/docs/proxy.yml +++ b/lib/kamal/configuration/docs/proxy.yml @@ -46,9 +46,22 @@ proxy: # The host value must point to the server we are deploying to, and port 443 must be # open for the Let's Encrypt challenge to succeed. # + # If you set `ssl` to `true`, `kamal-proxy` will stop forwarding headers to your app, + # unless you explicitly set `forward_headers: true` + # # Defaults to `false`: ssl: true + # Forward headers + # + # Whether to forward the `X-Forwarded-For` and `X-Forwarded-Proto` headers. + # + # If you are behind a trusted proxy, you can set this to `true` to forward the headers. + # + # By default, kamal-proxy will not forward the headers if the `ssl` option is set to `true`, and + # will forward them if it is set to `false`. + forward_headers: true + # Response timeout # # How long to wait for requests to complete before timing out, defaults to 30 seconds: @@ -93,13 +106,3 @@ proxy: response_headers: - X-Request-ID - X-Request-Start - - # Forward headers - # - # Whether to forward the `X-Forwarded-For` and `X-Forwarded-Proto` headers. - # - # If you are behind a trusted proxy, you can set this to `true` to forward the headers. - # - # By default, kamal-proxy will not forward the headers if the `ssl` option is set to `true`, and - # will forward them if it is set to `false`. - forward_headers: true From ae7a4f3411fa8fa1d255e77711d9af8183ea1281 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Fri, 13 Dec 2024 12:27:22 +0000 Subject: [PATCH 26/27] Update yml files to match doc site changes --- lib/kamal/configuration/docs/accessory.yml | 6 +++--- lib/kamal/configuration/docs/alias.yml | 4 ++-- lib/kamal/configuration/docs/registry.yml | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/kamal/configuration/docs/accessory.yml b/lib/kamal/configuration/docs/accessory.yml index fab2989f..b82a476e 100644 --- a/lib/kamal/configuration/docs/accessory.yml +++ b/lib/kamal/configuration/docs/accessory.yml @@ -43,8 +43,8 @@ accessories: # Port mappings # - # See https://docs.docker.com/network/, and especially note the warning about the security - # implications of exposing ports publicly. + # See [https://docs.docker.com/network/](https://docs.docker.com/network/), and + # especially note the warning about the security implications of exposing ports publicly. port: "127.0.0.1:3306:3306" # Labels @@ -101,4 +101,4 @@ accessories: # Proxy # proxy: - ... \ No newline at end of file + ... diff --git a/lib/kamal/configuration/docs/alias.yml b/lib/kamal/configuration/docs/alias.yml index 32c37bad..6c46b695 100644 --- a/lib/kamal/configuration/docs/alias.yml +++ b/lib/kamal/configuration/docs/alias.yml @@ -5,12 +5,12 @@ # For example, for a Rails app, you might open a console with: # # ```shell -# kamal app exec -i -r console "rails console" +# kamal app exec -i --reuse "bin/rails console" # ``` # # By defining an alias, like this: aliases: - console: app exec -r console -i "rails console" + console: app exec -i --reuse "bin/rails console" # You can now open the console with: # # ```shell diff --git a/lib/kamal/configuration/docs/registry.yml b/lib/kamal/configuration/docs/registry.yml index 84f9c7c1..4411fd4d 100644 --- a/lib/kamal/configuration/docs/registry.yml +++ b/lib/kamal/configuration/docs/registry.yml @@ -2,6 +2,10 @@ # # The default registry is Docker Hub, but you can change it using `registry/server`. # +# By default, Docker Hub creates public repositories. To avoid making your images public, +# set up a private repository before deploying, or change the default repository privacy +# settings to private in your [Docker Hub settings](https://hub.docker.com/repository-settings/default-privacy). +# # A reference to a secret (in this case, `DOCKER_REGISTRY_TOKEN`) will look up the secret # in the local environment: registry: From 1547089da044159a934f1ef90006ae10e599d687 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Fri, 13 Dec 2024 12:38:26 +0000 Subject: [PATCH 27/27] Bump version for 2.4.0 --- Gemfile.lock | 2 +- lib/kamal/version.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index d3ed5a48..67963d62 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - kamal (2.3.0) + kamal (2.4.0) activesupport (>= 7.0) base64 (~> 0.2) bcrypt_pbkdf (~> 1.0) diff --git a/lib/kamal/version.rb b/lib/kamal/version.rb index 2f9a86b6..9f3a9126 100644 --- a/lib/kamal/version.rb +++ b/lib/kamal/version.rb @@ -1,3 +1,3 @@ module Kamal - VERSION = "2.3.0" + VERSION = "2.4.0" end