diff --git a/lib/kamal/cli/traefik.rb b/lib/kamal/cli/traefik.rb index e835b1d8..c67dc69e 100644 --- a/lib/kamal/cli/traefik.rb +++ b/lib/kamal/cli/traefik.rb @@ -20,7 +20,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base on(hosts) do execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug execute *KAMAL.registry.login - execute *KAMAL.traefik.stop + execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false execute *KAMAL.traefik.remove_container execute *KAMAL.traefik.run end @@ -44,7 +44,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base mutating do on(KAMAL.traefik_hosts) do execute *KAMAL.auditor.record("Stopped traefik"), verbosity: :debug - execute *KAMAL.traefik.stop + execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false end end end diff --git a/lib/kamal/commands/builder/base.rb b/lib/kamal/commands/builder/base.rb index f710807c..582adf9a 100644 --- a/lib/kamal/commands/builder/base.rb +++ b/lib/kamal/commands/builder/base.rb @@ -24,7 +24,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base def validate_image pipe \ docker(:inspect, "-f", "'{{ .Config.Labels.service }}'", config.absolute_image), - [:grep, "-x", config.service, "||", "(echo \"Image #{config.absolute_image} is missing the `service` label\" && exit 1)"] + [:grep, "-x", config.service, "||", "(echo \"Image #{config.absolute_image} is missing the 'service' label\" && exit 1)"] end diff --git a/lib/kamal/commands/registry.rb b/lib/kamal/commands/registry.rb index eb02acad..e13c90ac 100644 --- a/lib/kamal/commands/registry.rb +++ b/lib/kamal/commands/registry.rb @@ -2,7 +2,10 @@ class Kamal::Commands::Registry < Kamal::Commands::Base delegate :registry, to: :config def login - docker :login, registry["server"], "-u", sensitive(lookup("username")), "-p", sensitive(lookup("password")) + docker :login, + registry["server"], + "-u", sensitive(Kamal::Utils.escape_shell_value(lookup("username"))), + "-p", sensitive(Kamal::Utils.escape_shell_value(lookup("password"))) end def logout diff --git a/lib/kamal/configuration/boot.rb b/lib/kamal/configuration/boot.rb index f6daa1fa..e5cf5a6e 100644 --- a/lib/kamal/configuration/boot.rb +++ b/lib/kamal/configuration/boot.rb @@ -8,7 +8,7 @@ class Kamal::Configuration::Boot limit = @options["limit"] if limit.to_s.end_with?("%") - @host_count * limit.to_i / 100 + [@host_count * limit.to_i / 100, 1].max else limit end diff --git a/test/cli/build_test.rb b/test/cli/build_test.rb index cd71e279..082881c5 100644 --- a/test/cli/build_test.rb +++ b/test/cli/build_test.rb @@ -57,7 +57,7 @@ class CliBuildTest < CliTestCase run_command("pull").tap do |output| assert_match /docker image rm --force dhh\/app:999/, output assert_match /docker pull dhh\/app:999/, output - assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the `service` label\" && exit 1)", output + assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the 'service' label\" && exit 1)", output end end diff --git a/test/commander_test.rb b/test/commander_test.rb index 4e848a4b..598fc56b 100644 --- a/test/commander_test.rb +++ b/test/commander_test.rb @@ -109,6 +109,12 @@ class CommanderTest < ActiveSupport::TestCase assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy) end + test "percentage-based group strategy limit is at least 1" do + configure_with(:deploy_with_low_percentage_boot_strategy) + + assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy) + end + test "try to match the primary role from a list of specific roles" do configure_with(:deploy_primary_web_role_override) diff --git a/test/commands/builder_test.rb b/test/commands/builder_test.rb index 51bb837c..fe1b879c 100644 --- a/test/commands/builder_test.rb +++ b/test/commands/builder_test.rb @@ -120,7 +120,7 @@ class CommandsBuilderTest < ActiveSupport::TestCase end test "validate image" do - assert_equal "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:123 | grep -x app || (echo \"Image dhh/app:123 is missing the `service` label\" && exit 1)", new_builder_command.validate_image.join(" ") + assert_equal "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:123 | grep -x app || (echo \"Image dhh/app:123 is missing the 'service' label\" && exit 1)", new_builder_command.validate_image.join(" ") end private diff --git a/test/commands/registry_test.rb b/test/commands/registry_test.rb index 83b73a7b..c25d7585 100755 --- a/test/commands/registry_test.rb +++ b/test/commands/registry_test.rb @@ -15,7 +15,7 @@ class CommandsRegistryTest < ActiveSupport::TestCase test "registry login" do assert_equal \ - "docker login hub.docker.com -u dhh -p secret", + "docker login hub.docker.com -u \"dhh\" -p \"secret\"", @registry.login.join(" ") end @@ -24,7 +24,18 @@ class CommandsRegistryTest < ActiveSupport::TestCase @config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ] assert_equal \ - "docker login hub.docker.com -u dhh -p more-secret", + "docker login hub.docker.com -u \"dhh\" -p \"more-secret\"", + @registry.login.join(" ") + ensure + ENV.delete("KAMAL_REGISTRY_PASSWORD") + end + + test "registry login escape password" do + ENV["KAMAL_REGISTRY_PASSWORD"] = "more-secret'\"" + @config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ] + + assert_equal \ + "docker login hub.docker.com -u \"dhh\" -p \"more-secret'\\\"\"", @registry.login.join(" ") ensure ENV.delete("KAMAL_REGISTRY_PASSWORD") @@ -35,7 +46,7 @@ class CommandsRegistryTest < ActiveSupport::TestCase @config[:registry]["username"] = [ "KAMAL_REGISTRY_USERNAME" ] assert_equal \ - "docker login hub.docker.com -u also-secret -p secret", + "docker login hub.docker.com -u \"also-secret\" -p \"secret\"", @registry.login.join(" ") ensure ENV.delete("KAMAL_REGISTRY_USERNAME") diff --git a/test/fixtures/deploy_with_low_percentage_boot_strategy.yml b/test/fixtures/deploy_with_low_percentage_boot_strategy.yml new file mode 100644 index 00000000..9b6a3c64 --- /dev/null +++ b/test/fixtures/deploy_with_low_percentage_boot_strategy.yml @@ -0,0 +1,17 @@ +service: app +image: dhh/app +servers: + web: + - "1.1.1.1" + - "1.1.1.2" + workers: + - "1.1.1.3" + - "1.1.1.4" + +registry: + username: user + password: pw + +boot: + limit: 1% + wait: 2 diff --git a/test/fixtures/deploy_with_percentage_boot_strategy.yml b/test/fixtures/deploy_with_percentage_boot_strategy.yml index eb68a52f..9b6a3c64 100644 --- a/test/fixtures/deploy_with_percentage_boot_strategy.yml +++ b/test/fixtures/deploy_with_percentage_boot_strategy.yml @@ -13,5 +13,5 @@ registry: password: pw boot: - limit: 25% + limit: 1% wait: 2 diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index 856781cb..22f60860 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -60,6 +60,19 @@ class MainTest < IntegrationTest assert_equal({ "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999, "cord"=>"/tmp/kamal-cord", "log_lines" => 50, "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1" }, config[:healthcheck]) end + test "setup and remove" do + # Check remove completes when nothing has been setup yet + kamal :remove, "-y" + assert_no_images_or_containers + + kamal :envify + kamal :setup + assert_images_and_containers + + kamal :remove, "-y" + assert_no_images_or_containers + end + private def assert_local_env_file(contents) assert_equal contents, deployer_exec("cat .env", capture: true) @@ -84,4 +97,22 @@ class MainTest < IntegrationTest assert_equal "200", Net::HTTP.get_response(URI.parse("http://localhost:12345/versions/.hidden")).code end + + def vm1_image_ids + docker_compose("exec vm1 docker image ls -q", capture: true).strip.split("\n") + end + + def vm1_container_ids + docker_compose("exec vm1 docker ps -a -q", capture: true).strip.split("\n") + end + + def assert_no_images_or_containers + assert vm1_image_ids.empty? + assert vm1_container_ids.empty? + end + + def assert_images_and_containers + assert vm1_image_ids.any? + assert vm1_container_ids.any? + end end