diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 251fb508..ec7c019a 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -9,9 +9,6 @@ class Kamal::Cli::App < Kamal::Cli::Base # Assets are prepared in a separate step to ensure they are on all hosts before booting on(KAMAL.hosts) do - execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug - execute *KAMAL.app.tag_current_image_as_latest - KAMAL.roles_on(host).each do |role| Kamal::Cli::App::PrepareAssets.new(host, role, self).run end @@ -22,6 +19,11 @@ class Kamal::Cli::App < Kamal::Cli::Base Kamal::Cli::App::Boot.new(host, role, version, self).run end end + + on(KAMAL.hosts) do |host| + execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug + execute *KAMAL.app.tag_latest_image + end end end end @@ -138,7 +140,10 @@ class Kamal::Cli::App < Kamal::Cli::Base roles = KAMAL.roles_on(host) roles.each do |role| - cli.send(:stale_versions, host: host, role: role).each do |version| + versions = capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false).split("\n") + versions -= [ capture_with_info(*KAMAL.app(role: role).current_running_version, raise_on_non_zero_exit: false).strip ] + + versions.each do |version| if stop puts_by_host host, "Stopping stale container for role #{role} with version #{version}" execute *KAMAL.app(role: role).stop(version: version), raise_on_non_zero_exit: false @@ -274,18 +279,7 @@ class Kamal::Cli::App < Kamal::Cli::Base version.presence end - def stale_versions(host:, role:) - versions = nil - on(host) do - versions = \ - capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false) - .split("\n") - .drop(1) - end - versions - end - def version_or_latest - options[:version] || "latest" + options[:version] || KAMAL.config.latest_tag end end diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 77e0e0dd..15992640 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -49,7 +49,7 @@ class Kamal::Commands::App < Kamal::Commands::Base def current_running_container_id - docker :ps, "--quiet", *filter_args(statuses: ACTIVE_DOCKER_STATUSES), "--latest" + current_running_container(format: "--quiet") end def container_id_for_version(version, only_running: false) @@ -57,13 +57,15 @@ class Kamal::Commands::App < Kamal::Commands::Base end def current_running_version - list_versions("--latest", statuses: ACTIVE_DOCKER_STATUSES) + pipe \ + current_running_container(format: "--format '{{.Names}}'"), + extract_version_from_name end def list_versions(*docker_args, statuses: nil) pipe \ docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'), - %(while read line; do echo ${line##{role.container_prefix}-}; done) # Extract SHA from "service-role-dest-SHA" + extract_version_from_name end @@ -81,10 +83,33 @@ class Kamal::Commands::App < Kamal::Commands::Base [ role.container_prefix, version || config.version ].compact.join("-") end + def latest_image_id + docker :image, :ls, *argumentize("--filter", "reference=#{config.latest_image}"), "--format", "'{{.ID}}'" + end + + def current_running_container(format:) + pipe \ + shell(chain(latest_image_container(format: format), latest_container(format: format))), + [ :head, "-1" ] + end + + def latest_image_container(format:) + latest_container format: format, filters: [ "ancestor=$(#{latest_image_id.join(" ")})" ] + end + + def latest_container(format:, filters: nil) + docker :ps, "--latest", *format, *filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters) + end + def filter_args(statuses: nil) argumentize "--filter", filters(statuses: statuses) end + def extract_version_from_name + # Extract SHA from "service-role-dest-SHA" + %(while read line; do echo ${line##{role.container_prefix}-}; done) + end + def filters(statuses: nil) [ "label=service=#{config.service}" ].tap do |filters| filters << "label=destination=#{config.destination}" if config.destination diff --git a/lib/kamal/commands/app/assets.rb b/lib/kamal/commands/app/assets.rb index dba651b2..26ca6e0c 100644 --- a/lib/kamal/commands/app/assets.rb +++ b/lib/kamal/commands/app/assets.rb @@ -5,7 +5,7 @@ module Kamal::Commands::App::Assets combine \ make_directory(role.asset_extracted_path), [ *docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true" ], - docker(:run, "--name", asset_container, "--detach", "--rm", config.latest_image, "sleep 1000000"), + docker(:run, "--name", asset_container, "--detach", "--rm", config.absolute_image, "sleep 1000000"), docker(:cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_path), docker(:stop, "-t 1", asset_container), by: "&&" diff --git a/lib/kamal/commands/app/images.rb b/lib/kamal/commands/app/images.rb index 9b1ae0b8..e20e83e1 100644 --- a/lib/kamal/commands/app/images.rb +++ b/lib/kamal/commands/app/images.rb @@ -7,7 +7,7 @@ module Kamal::Commands::App::Images docker :image, :prune, "--all", "--force", *filter_args end - def tag_current_image_as_latest + def tag_latest_image docker :tag, config.absolute_image, config.latest_image end end diff --git a/lib/kamal/commands/base.rb b/lib/kamal/commands/base.rb index 27948434..8c289ae0 100644 --- a/lib/kamal/commands/base.rb +++ b/lib/kamal/commands/base.rb @@ -71,7 +71,7 @@ module Kamal::Commands end def shell(command) - [ :sh, "-c", "'#{command.flatten.join(" ").gsub("'", "'\\''")}'" ] + [ :sh, "-c", "'#{command.flatten.join(" ").gsub("'", "'\\\\''")}'" ] end def docker(*args) diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index a61e2e55..df95f170 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -128,7 +128,11 @@ class Kamal::Configuration end def latest_image - "#{repository}:latest" + "#{repository}:#{latest_tag}" + end + + def latest_tag + [ "latest", *destination ].join("-") end def service_with_version diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 416bd895..ab0eff51 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -23,7 +23,7 @@ class CliAppTest < CliTestCase .returns("running") # health check SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) - .with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--filter", "status=restarting", "--latest", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) + .with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) .returns("123") # old version SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) @@ -75,7 +75,7 @@ class CliAppTest < CliTestCase .returns("running") # health check SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) - .with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--filter", "status=restarting", "--latest", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) + .with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) .returns("123").twice # old version SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) @@ -100,14 +100,18 @@ class CliAppTest < CliTestCase test "stop" do run_command("stop").tap do |output| - assert_match "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker stop", output + assert_match "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker stop", output end end test "stale_containers" do SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) - .returns("12345678\n87654321") + .returns("12345678\n87654321\n") + + SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) + .with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) + .returns("12345678\n") run_command("stale_containers").tap do |output| assert_match /Detected stale container for role web with version 87654321/, output @@ -117,7 +121,11 @@ class CliAppTest < CliTestCase test "stop stale_containers" do SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) - .returns("12345678\n87654321") + .returns("12345678\n87654321\n") + + SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) + .with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) + .returns("12345678\n") run_command("stale_containers", "--stop").tap do |output| assert_match /Stopping stale container for role web with version 87654321/, output @@ -133,7 +141,7 @@ class CliAppTest < CliTestCase test "remove" do run_command("remove").tap do |output| - assert_match /#{Regexp.escape("docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker stop")}/, output + assert_match /#{Regexp.escape("sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker stop")}/, output assert_match /#{Regexp.escape("docker container prune --force --filter label=service=app")}/, output assert_match /#{Regexp.escape("docker image prune --all --force --filter label=service=app")}/, output end @@ -165,7 +173,7 @@ class CliAppTest < CliTestCase test "exec with reuse" do run_command("exec", "--reuse", "ruby -v").tap do |output| - assert_match "docker ps --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done", output # Get current version + assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version assert_match "docker exec app-web-999 ruby -v", output end end @@ -184,7 +192,7 @@ class CliAppTest < CliTestCase .with("ssh -t root@1.1.1.1 -p 22 'docker exec -it app-web-999 ruby -v'") run_command("exec", "-i", "--reuse", "ruby -v").tap do |output| assert_match "Get current version of running container...", output - assert_match "Running docker ps --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done on 1.1.1.1", output + assert_match "Running /usr/bin/env sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done on 1.1.1.1", output assert_match "Launching interactive command with version 999 via SSH from existing container on 1.1.1.1...", output end end @@ -203,28 +211,28 @@ class CliAppTest < CliTestCase test "logs" do SSHKit::Backend::Abstract.any_instance.stubs(:exec) - .with("ssh -t root@1.1.1.1 'docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest| xargs docker logs --timestamps --tail 10 2>&1'") + .with("ssh -t root@1.1.1.1 'sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1| xargs docker logs --timestamps --tail 10 2>&1'") - assert_match "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --tail 100 2>&1", run_command("logs") + assert_match "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --tail 100 2>&1", run_command("logs") end test "logs with follow" do SSHKit::Backend::Abstract.any_instance.stubs(:exec) - .with("ssh -t root@1.1.1.1 -p 22 'docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --tail 10 --follow 2>&1'") + .with("ssh -t root@1.1.1.1 -p 22 'sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --tail 10 --follow 2>&1'") - assert_match "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --tail 10 --follow 2>&1", run_command("logs", "--follow") + assert_match "sh -c 'docker ps --latest --quiet --filter label=service=app --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=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 "version" do run_command("version").tap do |output| - assert_match "docker ps --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done", output + assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output end end test "version through main" do stdouted { Kamal::Cli::Main.start([ "app", "version", "-c", "test/fixtures/deploy_with_accessories.yml", "--hosts", "1.1.1.1" ]) }.tap do |output| - assert_match "docker ps --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done", output + assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output end end diff --git a/test/cli/main_test.rb b/test/cli/main_test.rb index 094a8e56..20331478 100644 --- a/test/cli/main_test.rb +++ b/test/cli/main_test.rb @@ -250,7 +250,7 @@ class CliMainTest < CliTestCase .with(:docker, :container, :ls, "--all", "--filter", "name=^app-#{role}-123$", "--quiet") .returns("version-to-rollback\n").at_least_once SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) - .with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=#{role}", "--filter", "status=running", "--filter", "status=restarting", "--latest", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-#{role}-}; done", raise_on_non_zero_exit: false) + .with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=role=#{role} --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=role=#{role} --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-#{role}-}; done", raise_on_non_zero_exit: false) .returns("version-to-rollback\n").at_least_once SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :container, :ls, "--all", "--filter", "name=^app-#{role}-123$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'") @@ -286,7 +286,7 @@ class CliMainTest < CliTestCase .with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet", raise_on_non_zero_exit: false) .returns("").at_least_once SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) - .with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=role=web", "--filter", "status=running", "--filter", "status=restarting", "--latest", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) + .with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting'", "|", :head, "-1", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) .returns("").at_least_once SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) .with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet", "|", :xargs, :docker, :inspect, "--format", "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'") diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 0abed6c5..2d616335 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -95,14 +95,14 @@ class CommandsAppTest < ActiveSupport::TestCase test "stop" do assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker stop", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker stop", new_command.stop.join(" ") end test "stop with custom stop wait time" do @config[:stop_wait_time] = 30 assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker stop -t 30", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker stop -t 30", new_command.stop.join(" ") end @@ -128,45 +128,45 @@ class CommandsAppTest < ActiveSupport::TestCase test "logs" do assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs 2>&1", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs 2>&1", new_command.logs.join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --since 5m 2>&1", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --since 5m 2>&1", new_command.logs(since: "5m").join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --tail 100 2>&1", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --tail 100 2>&1", new_command.logs(lines: "100").join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --since 5m --tail 100 2>&1", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --since 5m --tail 100 2>&1", new_command.logs(since: "5m", lines: "100").join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs 2>&1 | grep 'my-id'", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs 2>&1 | grep 'my-id'", new_command.logs(grep: "my-id").join(" ") assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --since 5m 2>&1 | grep 'my-id'", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --since 5m 2>&1 | grep 'my-id'", new_command.logs(since: "5m", grep: "my-id").join(" ") end test "follow logs" do - assert_match \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --follow 2>&1", + assert_equal \ + "ssh -t root@app-1 -p 22 'sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --follow 2>&1'", new_command.follow_logs(host: "app-1") - assert_match \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --follow 2>&1 | grep \"Completed\"", + assert_equal \ + "ssh -t root@app-1 -p 22 'sh -c 'docker ps --latest --quiet --filter label=service=app --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=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_match \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --tail 123 --follow 2>&1", + assert_equal \ + "ssh -t root@app-1 -p 22 'sh -c 'docker ps --latest --quiet --filter label=service=app --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=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) - assert_match \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --tail 123 --follow 2>&1 | grep \"Completed\"", + assert_equal \ + "ssh -t root@app-1 -p 22 'sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | xargs docker logs --timestamps --tail 123 --follow 2>&1 | grep \"Completed\"'", new_command.follow_logs(host: "app-1", lines: 123, grep: "Completed") end @@ -254,14 +254,14 @@ class CommandsAppTest < ActiveSupport::TestCase test "current_running_container_id" do assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest", + "sh -c 'docker ps --latest --quiet --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1", new_command.current_running_container_id.join(" ") end test "current_running_container_id with destination" do @destination = "staging" assert_equal \ - "docker ps --quiet --filter label=service=app --filter label=destination=staging --filter label=role=web --filter status=running --filter status=restarting --latest", + "sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination=staging --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest-staging --format '\\''{{.ID}}'\\'') ; docker ps --latest --quiet --filter label=service=app --filter label=destination=staging --filter label=role=web --filter status=running --filter status=restarting' | head -1", new_command.current_running_container_id.join(" ") end @@ -273,7 +273,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "current_running_version" do assert_equal \ - "docker ps --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done", + "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", new_command.current_running_version.join(" ") end @@ -351,10 +351,17 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.remove_images.join(" ") end - test "tag_current_image_as_latest" do + test "tag_latest_image" do assert_equal \ "docker tag dhh/app:999 dhh/app:latest", - new_command.tag_current_image_as_latest.join(" ") + new_command.tag_latest_image.join(" ") + end + + test "tag_latest_image with destination" do + @destination = "staging" + assert_equal \ + "docker tag dhh/app:999 dhh/app:latest-staging", + new_command.tag_latest_image.join(" ") end test "make_env_directory" do @@ -383,7 +390,7 @@ class CommandsAppTest < ActiveSupport::TestCase assert_equal [ :mkdir, "-p", ".kamal/assets/extracted/app-web-999", "&&", :docker, :stop, "-t 1", "app-web-assets", "2> /dev/null", "|| true", "&&", - :docker, :run, "--name", "app-web-assets", "--detach", "--rm", "dhh/app:latest", "sleep 1000000", "&&", + :docker, :run, "--name", "app-web-assets", "--detach", "--rm", "dhh/app:999", "sleep 1000000", "&&", :docker, :cp, "-L", "app-web-assets:/public/assets/.", ".kamal/assets/extracted/app-web-999", "&&", :docker, :stop, "-t 1", "app-web-assets" ], new_command(asset_path: "/public/assets").extract_assets diff --git a/test/integration/docker-compose.yml b/test/integration/docker-compose.yml index da67f164..5b2c31dc 100644 --- a/test/integration/docker-compose.yml +++ b/test/integration/docker-compose.yml @@ -18,7 +18,7 @@ services: build: context: docker/deployer environment: - - TEST_ID=${TEST_ID} + - TEST_ID=${TEST_ID:-} volumes: - ../..:/kamal - shared:/shared