From 89994c8b20647d405e174f74a2c1d64d6c27219c Mon Sep 17 00:00:00 2001 From: Nick Hammond Date: Fri, 24 May 2024 08:59:33 -0700 Subject: [PATCH] Add grep's context option to show lines before and after a match --- lib/kamal/cli/accessory.rb | 8 +++++--- lib/kamal/cli/app.rb | 9 ++++++--- lib/kamal/cli/traefik.rb | 8 +++++--- lib/kamal/commands/accessory.rb | 8 ++++---- lib/kamal/commands/app/logging.rb | 8 ++++---- lib/kamal/commands/traefik.rb | 8 ++++---- test/cli/accessory_test.rb | 29 +++++++++++++++++++++++++++++ test/cli/app_test.rb | 18 ++++++++++++++++++ test/cli/traefik_test.rb | 14 ++++++++++++++ test/commands/accessory_test.rb | 4 ++++ test/commands/app_test.rb | 22 ++++++++++++++++++++++ test/commands/traefik_test.rb | 6 ++++++ 12 files changed, 121 insertions(+), 21 deletions(-) diff --git a/lib/kamal/cli/accessory.rb b/lib/kamal/cli/accessory.rb index e1d31b77..928af920 100644 --- a/lib/kamal/cli/accessory.rb +++ b/lib/kamal/cli/accessory.rb @@ -150,22 +150,24 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base 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 :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)" + option :context, aliases: "-C", desc: "Show number of lines leading and trailing a grep match (use with --grep)" def logs(name) with_accessory(name) do |accessory, hosts| grep = options[:grep] + context = options[:context] if options[:follow] run_locally do info "Following logs on #{hosts}..." - info accessory.follow_logs(grep: grep) - exec accessory.follow_logs(grep: grep) + info accessory.follow_logs(grep: grep, context: context) + exec accessory.follow_logs(grep: grep, context: context) end else since = options[:since] lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set on(hosts) do - puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep)) + puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep, context: context)) end end end diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 809607a9..685b9944 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -167,11 +167,14 @@ class Kamal::Cli::App < Kamal::Cli::Base 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 :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)" + option :context, aliases: "-C", desc: "Show number of lines leading and trailing a grep match (use with --grep)" def logs # FIXME: Catch when app containers aren't running grep = options[:grep] + context = options[:context] since = options[:since] + if options[:follow] lines = options[:lines].presence || ((since || grep) ? nil : 10) # Default to 10 lines if since or grep isn't set @@ -182,8 +185,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, lines: lines, grep: grep) - exec app.follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep) + info app.follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep, context: context) + exec app.follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep, context: context) end else lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set @@ -193,7 +196,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(since: since, lines: lines, grep: grep)) + puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(since: since, lines: lines, grep: grep, context: context)) rescue SSHKit::Command::Failed puts_by_host host, "Nothing found" end diff --git a/lib/kamal/cli/traefik.rb b/lib/kamal/cli/traefik.rb index d192ee1a..0cd4023f 100644 --- a/lib/kamal/cli/traefik.rb +++ b/lib/kamal/cli/traefik.rb @@ -70,21 +70,23 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base 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 :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)" + option :context, aliases: "-C", desc: "Show number of lines leading and trailing a grep match (use with --grep)" def logs grep = options[:grep] + context = options[:context] if options[:follow] run_locally do info "Following logs on #{KAMAL.primary_host}..." - info KAMAL.traefik.follow_logs(host: KAMAL.primary_host, grep: grep) - exec KAMAL.traefik.follow_logs(host: KAMAL.primary_host, grep: grep) + info KAMAL.traefik.follow_logs(host: KAMAL.primary_host, grep: grep, context: context) + exec KAMAL.traefik.follow_logs(host: KAMAL.primary_host, grep: grep, context: context) end else since = options[:since] lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set on(KAMAL.traefik_hosts) do |host| - puts_by_host host, capture(*KAMAL.traefik.logs(since: since, lines: lines, grep: grep)), type: "Traefik" + puts_by_host host, capture(*KAMAL.traefik.logs(since: since, lines: lines, grep: grep, context: context)), type: "Traefik" end end end diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 661ab7ee..b475be6c 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -36,17 +36,17 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base end - def logs(since: nil, lines: nil, grep: nil) + def logs(since: nil, lines: nil, grep: nil, context: nil) pipe \ docker(:logs, service_name, (" --since #{since}" if since), (" --tail #{lines}" if lines), "--timestamps", "2>&1"), - ("grep '#{grep}'" if grep) + ("grep '#{grep}'#{" -C #{context}" if context}" if grep) end - def follow_logs(grep: nil) + def follow_logs(grep: nil, context: nil) run_over_ssh \ pipe \ docker(:logs, service_name, "--timestamps", "--tail", "10", "--follow", "2>&1"), - (%(grep "#{grep}") if grep) + (%(grep "#{grep}"#{" -C #{context}" if context}) if grep) end diff --git a/lib/kamal/commands/app/logging.rb b/lib/kamal/commands/app/logging.rb index 8acb49e9..becc88c6 100644 --- a/lib/kamal/commands/app/logging.rb +++ b/lib/kamal/commands/app/logging.rb @@ -1,17 +1,17 @@ module Kamal::Commands::App::Logging - def logs(version: nil, since: nil, lines: nil, grep: nil) + def logs(version: nil, since: nil, lines: nil, grep: nil, context: nil) pipe \ version ? container_id_for_version(version) : current_running_container_id, "xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1", - ("grep '#{grep}'" if grep) + ("grep '#{grep}'#{" -C #{context}" if context}" if grep) end - def follow_logs(host:, lines: nil, grep: nil) + def follow_logs(host:, lines: nil, grep: nil, context: nil) run_over_ssh \ pipe( current_running_container_id, "xargs docker logs --timestamps#{" --tail #{lines}" if lines} --follow 2>&1", - (%(grep "#{grep}") if grep) + (%(grep "#{grep}"#{" -C #{context}" if context}) if grep) ), host: host end diff --git a/lib/kamal/commands/traefik.rb b/lib/kamal/commands/traefik.rb index 569c2c2c..3b0322ba 100644 --- a/lib/kamal/commands/traefik.rb +++ b/lib/kamal/commands/traefik.rb @@ -46,16 +46,16 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base docker :ps, "--filter", "name=^traefik$" end - def logs(since: nil, lines: nil, grep: nil) + def logs(since: nil, lines: nil, grep: nil, context: nil) pipe \ docker(:logs, "traefik", (" --since #{since}" if since), (" --tail #{lines}" if lines), "--timestamps", "2>&1"), - ("grep '#{grep}'" if grep) + ("grep '#{grep}'#{" -C #{context}" if context}" if grep) end - def follow_logs(host:, grep: nil) + def follow_logs(host:, grep: nil, context: nil) run_over_ssh pipe( docker(:logs, "traefik", "--timestamps", "--tail", "10", "--follow", "2>&1"), - (%(grep "#{grep}") if grep) + (%(grep "#{grep}"#{" -C #{context}" if context}) if grep) ).join(" "), host: host end diff --git a/test/cli/accessory_test.rb b/test/cli/accessory_test.rb index cb52ee2e..35e55850 100644 --- a/test/cli/accessory_test.rb +++ b/test/cli/accessory_test.rb @@ -114,6 +114,21 @@ class CliAccessoryTest < CliTestCase .with("ssh -t root@1.1.1.3 'docker logs app-mysql --timestamps --tail 10 2>&1'") assert_match "docker logs app-mysql --tail 100 --timestamps 2>&1", run_command("logs", "mysql") + + end + + test "logs with grep" do + SSHKit::Backend::Abstract.any_instance.stubs(:exec) + .with("ssh -t root@1.1.1.3 'docker logs app-mysql --timestamps 2>&1 | grep \'hey\''") + + assert_match "docker logs app-mysql --timestamps 2>&1 | grep 'hey'", run_command("logs", "mysql", "--grep", "hey") + end + + test "logs with grep and context" do + SSHKit::Backend::Abstract.any_instance.stubs(:exec) + .with("ssh -t root@1.1.1.3 'docker logs app-mysql --timestamps 2>&1 | grep \'hey\' -C 2'") + + assert_match "docker logs app-mysql --timestamps 2>&1 | grep 'hey' -C 2", run_command("logs", "mysql", "--grep", "hey", "--context", "2") end test "logs with follow" do @@ -123,6 +138,20 @@ class CliAccessoryTest < CliTestCase assert_match "docker logs app-mysql --timestamps --tail 10 --follow 2>&1", run_command("logs", "mysql", "--follow") end + test "logs with follow and grep" do + SSHKit::Backend::Abstract.any_instance.stubs(:exec) + .with("ssh -t root@1.1.1.3 -p 22 'docker logs app-mysql --timestamps --tail 10 --follow 2>&1 | grep \"hey\"'") + + assert_match "docker logs app-mysql --timestamps --tail 10 --follow 2>&1 | grep \"hey\"", run_command("logs", "mysql", "--follow", "--grep", "hey") + end + + test "logs with follow, grep, and context" do + SSHKit::Backend::Abstract.any_instance.stubs(:exec) + .with("ssh -t root@1.1.1.3 -p 22 'docker logs app-mysql --timestamps --tail 10 --follow 2>&1 | grep \"hey\" -C 2'") + + assert_match "docker logs app-mysql --timestamps --tail 10 --follow 2>&1 | grep \"hey\" -C 2", run_command("logs", "mysql", "--follow", "--grep", "hey", "--context", "2") + end + test "remove with confirmation" do Kamal::Cli::Accessory.any_instance.expects(:stop).with("mysql") Kamal::Cli::Accessory.any_instance.expects(:remove_container).with("mysql") diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 643f8d85..044eadc3 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -290,6 +290,10 @@ class CliAppTest < CliTestCase .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 "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") + + 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 2>&1 | grep 'hey'", run_command("logs", "--grep", "hey") + + 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 2>&1 | grep 'hey' -C 2", run_command("logs", "--grep", "hey", "--context", "2") end test "logs with follow" do @@ -299,6 +303,20 @@ class CliAppTest < CliTestCase 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 "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=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 \"hey\"'") + + 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 --follow 2>&1 | grep \"hey\"", run_command("logs", "--follow", "--grep", "hey") + end + + test "logs with follow, grep and context" 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=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 \"hey\" -C 2'") + + 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 --follow 2>&1 | grep \"hey\" -C 2", run_command("logs", "--follow", "--grep", "hey", "--context", "2") + end + test "version" do run_command("version").tap do |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 diff --git a/test/cli/traefik_test.rb b/test/cli/traefik_test.rb index d83529cd..73e7ad5d 100644 --- a/test/cli/traefik_test.rb +++ b/test/cli/traefik_test.rb @@ -69,6 +69,20 @@ class CliTraefikTest < CliTestCase assert_match "docker logs traefik --timestamps --tail 10 --follow", run_command("logs", "--follow") 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 'docker logs traefik --timestamps --tail 10 --follow 2>&1 | grep \"hey\"'") + + assert_match "docker logs traefik --timestamps --tail 10 --follow 2>&1 | grep \"hey\"", run_command("logs", "--follow", "--grep", "hey") + end + + test "logs with follow, grep, and context" do + SSHKit::Backend::Abstract.any_instance.stubs(:exec) + .with("ssh -t root@1.1.1.1 -p 22 'docker logs traefik --timestamps --tail 10 --follow 2>&1 | grep \"hey\" -C 2'") + + assert_match "docker logs traefik --timestamps --tail 10 --follow 2>&1 | grep \"hey\" -C 2", run_command("logs", "--follow", "--grep", "hey", "--context", "2") + end + test "remove" do Kamal::Cli::Traefik.any_instance.expects(:stop) Kamal::Cli::Traefik.any_instance.expects(:remove_container) diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index da8c3057..c8b33fa3 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -125,6 +125,10 @@ class CommandsAccessoryTest < ActiveSupport::TestCase assert_equal \ "docker logs app-mysql --since 5m --tail 100 --timestamps 2>&1 | grep 'thing'", new_command(:mysql).logs(since: "5m", lines: 100, grep: "thing").join(" ") + + assert_equal \ + "docker logs app-mysql --since 5m --tail 100 --timestamps 2>&1 | grep 'thing' -C 2", + new_command(:mysql).logs(since: "5m", lines: 100, grep: "thing", context: 2).join(" ") end test "follow logs" do diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 2f9dc57f..226dd166 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -139,23 +139,45 @@ class CommandsAppTest < ActiveSupport::TestCase assert_equal \ "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(" ") + end + test "logs with since" do assert_equal \ "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(" ") + end + test "logs with lines" do assert_equal \ "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(" ") + end + test "logs with since and lines" do assert_equal \ "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(" ") + end + test "logs with grep" do assert_equal \ "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(" ") + end + test "logs with grep and context" do + assert_equal \ + "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' -C 2", + new_command.logs(grep: "my-id", context: 2).join(" ") + end + + test "logs with since, grep and context" do + assert_equal \ + "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' -C 2", + new_command.logs(since: "5m", grep: "my-id", context: 2).join(" ") + end + + test "logs with since and grep" do assert_equal \ "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(" ") diff --git a/test/commands/traefik_test.rb b/test/commands/traefik_test.rb index 157670a3..ececa5fe 100644 --- a/test/commands/traefik_test.rb +++ b/test/commands/traefik_test.rb @@ -153,6 +153,12 @@ class CommandsTraefikTest < ActiveSupport::TestCase new_command.logs(grep: "hello!").join(" ") end + test "traefik logs with grep hello! and context" do + assert_equal \ + "docker logs traefik --timestamps 2>&1 | grep 'hello!' -C 2", + new_command.logs(grep: "hello!", context: 2).join(" ") + end + test "traefik remove container" do assert_equal \ "docker container prune --force --filter label=org.opencontainers.image.title=Traefik",