From 13328687d122a3dd28fb5f9aec5a5e82b6a26335 Mon Sep 17 00:00:00 2001 From: Kohki Makimoto Date: Wed, 25 Sep 2024 21:35:39 +0900 Subject: [PATCH 01/27] support the "provenance" option in the "builder" config --- lib/kamal/commands/builder/base.rb | 8 ++++++-- lib/kamal/configuration/builder.rb | 4 ++++ lib/kamal/configuration/docs/builder.yml | 6 ++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/kamal/commands/builder/base.rb b/lib/kamal/commands/builder/base.rb index 636fe4f4..d551520b 100644 --- a/lib/kamal/commands/builder/base.rb +++ b/lib/kamal/commands/builder/base.rb @@ -6,7 +6,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base delegate :argumentize, to: Kamal::Utils delegate \ :args, :secrets, :dockerfile, :target, :arches, :local_arches, :remote_arches, :remote, - :cache_from, :cache_to, :ssh, :driver, :docker_driver?, + :cache_from, :cache_to, :ssh, :provenance, :driver, :docker_driver?, to: :builder_config def clean @@ -37,7 +37,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base end def build_options - [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh ] + [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_target, *build_ssh, *builder_provenance ] end def build_context @@ -97,6 +97,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base argumentize "--ssh", ssh if ssh.present? end + def builder_provenance + argumentize "--provenance", provenance unless provenance.nil? + end + def builder_config config.builder end diff --git a/lib/kamal/configuration/builder.rb b/lib/kamal/configuration/builder.rb index a395e228..d2f244db 100644 --- a/lib/kamal/configuration/builder.rb +++ b/lib/kamal/configuration/builder.rb @@ -111,6 +111,10 @@ class Kamal::Configuration::Builder builder_config["ssh"] end + def provenance + builder_config["provenance"] + end + def git_clone? Kamal::Git.used? && builder_config["context"].nil? end diff --git a/lib/kamal/configuration/docs/builder.yml b/lib/kamal/configuration/docs/builder.yml index cdde194f..2aefd006 100644 --- a/lib/kamal/configuration/docs/builder.yml +++ b/lib/kamal/configuration/docs/builder.yml @@ -104,3 +104,9 @@ builder: # # The build driver to use, defaults to `docker-container` driver: docker + + # Provenance + # + # It is used to configure provenance attestations for the build result. + # The value can also be a boolean to enable or disable provenance attestations. + provenance: mode=max From c17bdba61ca1d32a8603e6d9cc1f736c848fc2ed Mon Sep 17 00:00:00 2001 From: Kohki Makimoto Date: Wed, 25 Sep 2024 23:24:37 +0900 Subject: [PATCH 02/27] add tests --- test/commands/builder_test.rb | 7 +++++++ test/configuration/builder_test.rb | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/test/commands/builder_test.rb b/test/commands/builder_test.rb index e5daddfd..4e5e8046 100644 --- a/test/commands/builder_test.rb +++ b/test/commands/builder_test.rb @@ -144,6 +144,13 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder.push.join(" ") end + test "push with provenance" do + builder = new_builder_command(builder: { "provenance" => "mode=max" }) + assert_equal \ + "docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance mode=max .", + builder.push.join(" ") + end + test "mirror count" do command = new_builder_command assert_equal "docker info --format '{{index .RegistryConfig.Mirrors 0}}'", command.first_mirror.join(" ") diff --git a/test/configuration/builder_test.rb b/test/configuration/builder_test.rb index 53740ca8..545273b6 100644 --- a/test/configuration/builder_test.rb +++ b/test/configuration/builder_test.rb @@ -134,6 +134,16 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase assert_equal "default=$SSH_AUTH_SOCK", config.builder.ssh end + test "provenance" do + assert_nil config.builder.provenance + end + + test "setting provenance" do + @deploy[:builder]["provenance"] = "mode=max" + + assert_equal "mode=max", config.builder.provenance + end + test "local disabled but no remote set" do @deploy[:builder]["local"] = false From 92d82dd1a78061b8992ea949ca81c722ef959aef Mon Sep 17 00:00:00 2001 From: Kohki Makimoto Date: Thu, 26 Sep 2024 05:50:51 +0900 Subject: [PATCH 03/27] test: If the provenance is false, output "--provenance false". --- test/commands/builder_test.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/commands/builder_test.rb b/test/commands/builder_test.rb index 4e5e8046..be75d6fa 100644 --- a/test/commands/builder_test.rb +++ b/test/commands/builder_test.rb @@ -151,6 +151,13 @@ class CommandsBuilderTest < ActiveSupport::TestCase builder.push.join(" ") end + test "push with provenance false" do + builder = new_builder_command(builder: { "provenance" => false }) + assert_equal \ + "docker buildx build --push --platform linux/amd64 --builder kamal-local-docker-container -t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --provenance false .", + builder.push.join(" ") + end + test "mirror count" do command = new_builder_command assert_equal "docker info --format '{{index .RegistryConfig.Mirrors 0}}'", command.first_mirror.join(" ") From 256933f6f31d630367374ac5dd75b0201f5419ac Mon Sep 17 00:00:00 2001 From: Adam Tanner Date: Wed, 18 Sep 2024 14:18:56 -0400 Subject: [PATCH 04/27] builder/cache/options: fix order of build args when using registry --- lib/kamal/configuration/builder.rb | 2 +- test/configuration/builder_test.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/kamal/configuration/builder.rb b/lib/kamal/configuration/builder.rb index a395e228..b4a01e9b 100644 --- a/lib/kamal/configuration/builder.rb +++ b/lib/kamal/configuration/builder.rb @@ -166,7 +166,7 @@ class Kamal::Configuration::Builder end def cache_to_config_for_registry - [ "type=registry", builder_config["cache"]&.fetch("options", nil), "ref=#{cache_image_ref}" ].compact.join(",") + [ "type=registry", "ref=#{cache_image_ref}", builder_config["cache"]&.fetch("options", nil) ].compact.join(",") end def repo_basename diff --git a/test/configuration/builder_test.rb b/test/configuration/builder_test.rb index 53740ca8..12580c56 100644 --- a/test/configuration/builder_test.rb +++ b/test/configuration/builder_test.rb @@ -64,7 +64,7 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase @deploy[:builder] = { "arch" => "amd64", "cache" => { "type" => "registry", "options" => "mode=max,image-manifest=true,oci-mediatypes=true" } } assert_equal "type=registry,ref=dhh/app-build-cache", config.builder.cache_from - assert_equal "type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=dhh/app-build-cache", config.builder.cache_to + assert_equal "type=registry,ref=dhh/app-build-cache,mode=max,image-manifest=true,oci-mediatypes=true", config.builder.cache_to end test "setting registry cache when using a custom registry" do @@ -72,14 +72,14 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase @deploy[:builder] = { "arch" => "amd64", "cache" => { "type" => "registry", "options" => "mode=max,image-manifest=true,oci-mediatypes=true" } } assert_equal "type=registry,ref=registry.example.com/dhh/app-build-cache", config.builder.cache_from - assert_equal "type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=registry.example.com/dhh/app-build-cache", config.builder.cache_to + assert_equal "type=registry,ref=registry.example.com/dhh/app-build-cache,mode=max,image-manifest=true,oci-mediatypes=true", config.builder.cache_to end test "setting registry cache with image" do @deploy[:builder] = { "arch" => "amd64", "cache" => { "type" => "registry", "image" => "kamal", "options" => "mode=max" } } assert_equal "type=registry,ref=kamal", config.builder.cache_from - assert_equal "type=registry,mode=max,ref=kamal", config.builder.cache_to + assert_equal "type=registry,ref=kamal,mode=max", config.builder.cache_to end test "args" do From ccf32c2c1f6068058a51a84fd8aa93dfc4b720bf Mon Sep 17 00:00:00 2001 From: junket Date: Thu, 3 Oct 2024 09:31:30 -0400 Subject: [PATCH 05/27] Pass false values in env vars to docker --- lib/kamal/utils.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/kamal/utils.rb b/lib/kamal/utils.rb index ab8dd50e..58daaa91 100644 --- a/lib/kamal/utils.rb +++ b/lib/kamal/utils.rb @@ -12,6 +12,8 @@ module Kamal::Utils attr = "#{key}=#{escape_shell_value(value)}" attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive [ argument, attr ] + elsif value == false + [ argument, "#{key}=false" ] else [ argument, key ] end From 6d1d7a4c82be128beeff825834a849fa900b0b76 Mon Sep 17 00:00:00 2001 From: junket <789854+junket@users.noreply.github.com> Date: Thu, 3 Oct 2024 10:05:54 -0400 Subject: [PATCH 06/27] Updates argumentize test for false values --- test/utils_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/utils_test.rb b/test/utils_test.rb index e292e980..a8fa0797 100644 --- a/test/utils_test.rb +++ b/test/utils_test.rb @@ -2,8 +2,8 @@ require "test_helper" class UtilsTest < ActiveSupport::TestCase test "argumentize" do - assert_equal [ "--label", "foo=\"\\`bar\\`\"", "--label", "baz=\"qux\"", "--label", :quux ], \ - Kamal::Utils.argumentize("--label", { foo: "`bar`", baz: "qux", quux: nil }) + assert_equal [ "--label", "foo=\"\\`bar\\`\"", "--label", "baz=\"qux\"", "--label", :quux, "--label", "quuz=false" ], \ + Kamal::Utils.argumentize("--label", { foo: "`bar`", baz: "qux", quux: nil, quuz: false }) end test "argumentize with redacted" do From f04cae529abfdf5b4a1930b804eced4a84db34ed Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Wed, 9 Oct 2024 09:53:17 +0400 Subject: [PATCH 07/27] Added network configuration option to application, proxy and accessory sections --- lib/kamal/commands/accessory.rb | 6 +++--- lib/kamal/commands/app.rb | 2 +- lib/kamal/commands/app/execution.rb | 2 +- lib/kamal/configuration.rb | 9 +++++++++ lib/kamal/configuration/accessory.rb | 10 ++++++++++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/kamal/commands/accessory.rb b/lib/kamal/commands/accessory.rb index 0c1b9009..9abb6dfc 100644 --- a/lib/kamal/commands/accessory.rb +++ b/lib/kamal/commands/accessory.rb @@ -1,7 +1,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base attr_reader :accessory_config delegate :service_name, :image, :hosts, :port, :files, :directories, :cmd, - :publish_args, :env_args, :volume_args, :label_args, :option_args, + :network_args, :publish_args, :env_args, :volume_args, :label_args, :option_args, :secrets_io, :secrets_path, :env_directory, to: :accessory_config @@ -15,7 +15,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base "--name", service_name, "--detach", "--restart", "unless-stopped", - "--network", "kamal", + *network_args, *config.logging_args, *publish_args, *env_args, @@ -64,7 +64,7 @@ class Kamal::Commands::Accessory < Kamal::Commands::Base docker :run, ("-it" if interactive), "--rm", - "--network", "kamal", + *network_args, *env_args, *volume_args, image, diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 6d8f44c6..56a5656b 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -18,7 +18,7 @@ class Kamal::Commands::App < Kamal::Commands::Base "--detach", "--restart unless-stopped", "--name", container_name, - "--network", "kamal", + *config.network_args, *([ "--hostname", hostname ] if hostname), "-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"", "-e", "KAMAL_VERSION=\"#{config.version}\"", diff --git a/lib/kamal/commands/app/execution.rb b/lib/kamal/commands/app/execution.rb index 4434c26a..5770f7aa 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), "--rm", - "--network", "kamal", + *config.network_args, *role&.env_args(host), *argumentize("--env", env), *config.volume_args, diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 09f1ed89..b26c30c9 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -18,6 +18,7 @@ class Kamal::Configuration PROXY_HTTP_PORT = 80 PROXY_HTTPS_PORT = 443 PROXY_LOG_MAX_SIZE = "10m" + NETWORK = "kamal" class << self def create_from(config_file:, destination: nil, version: nil) @@ -193,6 +194,10 @@ class Kamal::Configuration logging.args end + def network_args + argumentize "--network", network + end + def readiness_delay raw_config.readiness_delay || 7 @@ -297,6 +302,10 @@ class Kamal::Configuration end private + def network + raw_config["network"] || NETWORK + end + # Will raise ArgumentError if any required config keys are missing def ensure_destination_if_required if require_destination? && destination.nil? diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index 804a1502..7aed80ea 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -1,6 +1,8 @@ class Kamal::Configuration::Accessory include Kamal::Configuration::Validation + NETWORK = "kamal" + delegate :argumentize, :optionize, to: Kamal::Utils attr_reader :name, :accessory_config, :env @@ -38,6 +40,10 @@ class Kamal::Configuration::Accessory end end + def network_args + argumentize "--network", network + end + def publish_args argumentize "--publish", port if port end @@ -173,4 +179,8 @@ class Kamal::Configuration::Accessory accessory_config["roles"].flat_map { |role| config.role(role).hosts } end end + + def network + accessory_config["network"] || NETWORK + end end From c917dd82cf93c123c2e6afe78651bfe5df9bfd08 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Tue, 1 Oct 2024 14:40:34 +0400 Subject: [PATCH 08/27] Added network_args to proxy configuration --- lib/kamal/commands/proxy.rb | 2 +- lib/kamal/configuration/proxy.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/kamal/commands/proxy.rb b/lib/kamal/commands/proxy.rb index acff3dbd..3399531f 100644 --- a/lib/kamal/commands/proxy.rb +++ b/lib/kamal/commands/proxy.rb @@ -4,7 +4,7 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base def run docker :run, "--name", container_name, - "--network", "kamal", + *config.network_args, "--detach", "--restart", "unless-stopped", "--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", diff --git a/lib/kamal/configuration/proxy.rb b/lib/kamal/configuration/proxy.rb index 6232c3e0..99c7aa1f 100644 --- a/lib/kamal/configuration/proxy.rb +++ b/lib/kamal/configuration/proxy.rb @@ -3,6 +3,7 @@ class Kamal::Configuration::Proxy DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ] CONTAINER_NAME = "kamal-proxy" + NETWORK = "kamal" delegate :argumentize, :optionize, to: Kamal::Utils @@ -51,6 +52,10 @@ class Kamal::Configuration::Proxy optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "=" end + def network_args + argumentize "--network", network + end + def merge(other) self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config) end @@ -59,4 +64,8 @@ class Kamal::Configuration::Proxy def seconds_duration(value) value ? "#{value}s" : nil end + + def network + proxy_config["network"] || NETWORK + end end From b6a10df56aaff503edf5acb9389138ea39f60efe Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Tue, 1 Oct 2024 20:24:28 +0400 Subject: [PATCH 09/27] Added tests for network configuration option --- lib/kamal/configuration/docs/accessory.yml | 8 ++++++++ lib/kamal/configuration/docs/configuration.yml | 7 +++++++ lib/kamal/configuration/docs/proxy.yml | 7 +++++++ test/configuration/accessory_test.rb | 9 +++++++++ test/configuration/proxy_test.rb | 9 +++++++++ test/configuration_test.rb | 9 +++++++++ 6 files changed, 49 insertions(+) diff --git a/lib/kamal/configuration/docs/accessory.yml b/lib/kamal/configuration/docs/accessory.yml index 86d49b77..e77bf754 100644 --- a/lib/kamal/configuration/docs/accessory.yml +++ b/lib/kamal/configuration/docs/accessory.yml @@ -90,3 +90,11 @@ accessories: # They are not created or copied before mounting: volumes: - /path/to/mysql-logs:/var/log/mysql + + # Network + # + # The network the accessory will be attached to. + # + # Defaults to kamal: + network: custom + diff --git a/lib/kamal/configuration/docs/configuration.yml b/lib/kamal/configuration/docs/configuration.yml index 6b059346..b59394a3 100644 --- a/lib/kamal/configuration/docs/configuration.yml +++ b/lib/kamal/configuration/docs/configuration.yml @@ -176,3 +176,10 @@ logging: # Alias configuration, see kamal docs alias: aliases: ... + +# Network +# +# The network the application will be attached to. +# +# Defaults to kamal: +network: custom diff --git a/lib/kamal/configuration/docs/proxy.yml b/lib/kamal/configuration/docs/proxy.yml index 76ec3e41..0af14826 100644 --- a/lib/kamal/configuration/docs/proxy.yml +++ b/lib/kamal/configuration/docs/proxy.yml @@ -103,3 +103,10 @@ proxy: # 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 + + # Network + # + # The network the proxy container will be attached to. + # + # Defaults to kamal: + network: custom diff --git a/test/configuration/accessory_test.rb b/test/configuration/accessory_test.rb index 2615dab6..f5220902 100644 --- a/test/configuration/accessory_test.rb +++ b/test/configuration/accessory_test.rb @@ -152,4 +152,13 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase test "options" do assert_equal [ "--cpus", "\"4\"", "--memory", "\"2GB\"" ], @config.accessory(:redis).option_args end + + test "network_args default" do + assert_equal [ "--network", "kamal" ], @config.accessory(:mysql).network_args + end + + test "network_args with configured options" do + @deploy[:accessories]["mysql"]["network"] = "database" + assert_equal [ "--network", "database" ], @config.accessory(:mysql).network_args + end end diff --git a/test/configuration/proxy_test.rb b/test/configuration/proxy_test.rb index 588e5a35..562c229f 100644 --- a/test/configuration/proxy_test.rb +++ b/test/configuration/proxy_test.rb @@ -38,6 +38,15 @@ class ConfigurationProxyTest < ActiveSupport::TestCase assert_not config.proxy.ssl? end + test "network_args defaults" do + assert_equal [ "--network", "kamal" ], config.proxy.network_args + end + + test "network_args with configured options" do + @deploy[:proxy] = { "network" => "example" } + assert_equal [ "--network", "example" ], config.proxy.network_args + end + private def config Kamal::Configuration.new(@deploy) diff --git a/test/configuration_test.rb b/test/configuration_test.rb index c1aaa697..da53ddb6 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -217,6 +217,15 @@ class ConfigurationTest < ActiveSupport::TestCase assert_equal [ "--log-driver", "\"local\"", "--log-opt", "max-size=\"100m\"", "--log-opt", "max-file=\"5\"" ], config.logging_args end + test "network_args default" do + assert_equal [ "--network", "kamal" ], @config.network_args + end + + test "network_args with configured options" do + config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!(network: "custom") }) + assert_equal [ "--network", "custom" ], config.network_args + end + test "erb evaluation of yml config" do config = Kamal::Configuration.create_from config_file: Pathname.new(File.expand_path("fixtures/deploy.erb.yml", __dir__)) assert_equal "my-user", config.registry.username From 08dacd27458368c7228a5ba99945690d4c37233b Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Tue, 1 Oct 2024 20:41:36 +0400 Subject: [PATCH 10/27] Added command tests --- test/commands/accessory_test.rb | 8 ++++++++ test/commands/app_test.rb | 8 ++++++++ test/commands/proxy_test.rb | 8 ++++++++ 3 files changed, 24 insertions(+) diff --git a/test/commands/accessory_test.rb b/test/commands/accessory_test.rb index f3d71ffd..1befd9e6 100644 --- a/test/commands/accessory_test.rb +++ b/test/commands/accessory_test.rb @@ -71,6 +71,14 @@ class CommandsAccessoryTest < ActiveSupport::TestCase new_command(:busybox).run.join(" ") end + test "run in custom network" do + @config[:accessories]["mysql"]["network"] = "custom" + + assert_equal \ + "docker run --name app-mysql --detach --restart unless-stopped --network custom --log-opt max-size=\"10m\" --publish 3306:3306 --env MYSQL_ROOT_HOST=\"%\" --env-file .kamal/apps/app/env/accessories/mysql.env --label service=\"app-mysql\" private.registry/mysql:8.0", + new_command(:mysql).run.join(" ") + end + test "start" do assert_equal \ "docker container start app-mysql", diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 0e5cad79..403e90d4 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -64,6 +64,14 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.run.join(" ") end + test "run in custom network" do + @config[:network] = "custom" + + assert_equal \ + "docker run --detach --restart unless-stopped --name app-web-999 --network custom -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination dhh/app:999", + new_command.run.join(" ") + end + test "start" do assert_equal \ "docker start app-web-999", diff --git a/test/commands/proxy_test.rb b/test/commands/proxy_test.rb index b7cc9f3d..80f420d2 100644 --- a/test/commands/proxy_test.rb +++ b/test/commands/proxy_test.rb @@ -27,6 +27,14 @@ class CommandsProxyTest < ActiveSupport::TestCase new_command.run.join(" ") end + test "run in custom network" do + @config[:network] = "custom" + + assert_equal \ + "docker run --name kamal-proxy --network custom --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") basecamp/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}", + new_command.run.join(" ") + end + test "proxy start" do assert_equal \ "docker container start kamal-proxy", From da2a543cbcfa995265cb5583ee9e5802a58c5109 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Wed, 9 Oct 2024 10:00:49 +0400 Subject: [PATCH 11/27] Reverted network arguments everywhere except to accessory config --- lib/kamal/commands/app.rb | 2 +- lib/kamal/commands/app/execution.rb | 2 +- lib/kamal/commands/proxy.rb | 2 +- lib/kamal/configuration.rb | 9 --------- lib/kamal/configuration/docs/configuration.yml | 7 ------- lib/kamal/configuration/docs/proxy.yml | 7 ------- lib/kamal/configuration/proxy.rb | 9 --------- test/commands/app_test.rb | 8 -------- test/commands/proxy_test.rb | 8 -------- test/configuration/proxy_test.rb | 9 --------- test/configuration_test.rb | 9 --------- 11 files changed, 3 insertions(+), 69 deletions(-) diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 56a5656b..6d8f44c6 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -18,7 +18,7 @@ class Kamal::Commands::App < Kamal::Commands::Base "--detach", "--restart unless-stopped", "--name", container_name, - *config.network_args, + "--network", "kamal", *([ "--hostname", hostname ] if hostname), "-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"", "-e", "KAMAL_VERSION=\"#{config.version}\"", diff --git a/lib/kamal/commands/app/execution.rb b/lib/kamal/commands/app/execution.rb index 5770f7aa..4434c26a 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), "--rm", - *config.network_args, + "--network", "kamal", *role&.env_args(host), *argumentize("--env", env), *config.volume_args, diff --git a/lib/kamal/commands/proxy.rb b/lib/kamal/commands/proxy.rb index 3399531f..acff3dbd 100644 --- a/lib/kamal/commands/proxy.rb +++ b/lib/kamal/commands/proxy.rb @@ -4,7 +4,7 @@ class Kamal::Commands::Proxy < Kamal::Commands::Base def run docker :run, "--name", container_name, - *config.network_args, + "--network", "kamal", "--detach", "--restart", "unless-stopped", "--volume", "kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy", diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index b26c30c9..09f1ed89 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -18,7 +18,6 @@ class Kamal::Configuration PROXY_HTTP_PORT = 80 PROXY_HTTPS_PORT = 443 PROXY_LOG_MAX_SIZE = "10m" - NETWORK = "kamal" class << self def create_from(config_file:, destination: nil, version: nil) @@ -194,10 +193,6 @@ class Kamal::Configuration logging.args end - def network_args - argumentize "--network", network - end - def readiness_delay raw_config.readiness_delay || 7 @@ -302,10 +297,6 @@ class Kamal::Configuration end private - def network - raw_config["network"] || NETWORK - end - # Will raise ArgumentError if any required config keys are missing def ensure_destination_if_required if require_destination? && destination.nil? diff --git a/lib/kamal/configuration/docs/configuration.yml b/lib/kamal/configuration/docs/configuration.yml index b59394a3..6b059346 100644 --- a/lib/kamal/configuration/docs/configuration.yml +++ b/lib/kamal/configuration/docs/configuration.yml @@ -176,10 +176,3 @@ logging: # Alias configuration, see kamal docs alias: aliases: ... - -# Network -# -# The network the application will be attached to. -# -# Defaults to kamal: -network: custom diff --git a/lib/kamal/configuration/docs/proxy.yml b/lib/kamal/configuration/docs/proxy.yml index 0af14826..76ec3e41 100644 --- a/lib/kamal/configuration/docs/proxy.yml +++ b/lib/kamal/configuration/docs/proxy.yml @@ -103,10 +103,3 @@ proxy: # 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 - - # Network - # - # The network the proxy container will be attached to. - # - # Defaults to kamal: - network: custom diff --git a/lib/kamal/configuration/proxy.rb b/lib/kamal/configuration/proxy.rb index 99c7aa1f..6232c3e0 100644 --- a/lib/kamal/configuration/proxy.rb +++ b/lib/kamal/configuration/proxy.rb @@ -3,7 +3,6 @@ class Kamal::Configuration::Proxy DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ] CONTAINER_NAME = "kamal-proxy" - NETWORK = "kamal" delegate :argumentize, :optionize, to: Kamal::Utils @@ -52,10 +51,6 @@ class Kamal::Configuration::Proxy optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "=" end - def network_args - argumentize "--network", network - end - def merge(other) self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config) end @@ -64,8 +59,4 @@ class Kamal::Configuration::Proxy def seconds_duration(value) value ? "#{value}s" : nil end - - def network - proxy_config["network"] || NETWORK - end end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 403e90d4..0e5cad79 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -64,14 +64,6 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.run.join(" ") end - test "run in custom network" do - @config[:network] = "custom" - - assert_equal \ - "docker run --detach --restart unless-stopped --name app-web-999 --network custom -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/apps/app/env/roles/web.env --log-opt max-size=\"10m\" --label service=\"app\" --label role=\"web\" --label destination dhh/app:999", - new_command.run.join(" ") - end - test "start" do assert_equal \ "docker start app-web-999", diff --git a/test/commands/proxy_test.rb b/test/commands/proxy_test.rb index 80f420d2..b7cc9f3d 100644 --- a/test/commands/proxy_test.rb +++ b/test/commands/proxy_test.rb @@ -27,14 +27,6 @@ class CommandsProxyTest < ActiveSupport::TestCase new_command.run.join(" ") end - test "run in custom network" do - @config[:network] = "custom" - - assert_equal \ - "docker run --name kamal-proxy --network custom --detach --restart unless-stopped --volume kamal-proxy-config:/home/kamal-proxy/.config/kamal-proxy $(cat .kamal/proxy/options || echo \"--publish 80:80 --publish 443:443\") basecamp/kamal-proxy:#{Kamal::Configuration::PROXY_MINIMUM_VERSION}", - new_command.run.join(" ") - end - test "proxy start" do assert_equal \ "docker container start kamal-proxy", diff --git a/test/configuration/proxy_test.rb b/test/configuration/proxy_test.rb index 562c229f..588e5a35 100644 --- a/test/configuration/proxy_test.rb +++ b/test/configuration/proxy_test.rb @@ -38,15 +38,6 @@ class ConfigurationProxyTest < ActiveSupport::TestCase assert_not config.proxy.ssl? end - test "network_args defaults" do - assert_equal [ "--network", "kamal" ], config.proxy.network_args - end - - test "network_args with configured options" do - @deploy[:proxy] = { "network" => "example" } - assert_equal [ "--network", "example" ], config.proxy.network_args - end - private def config Kamal::Configuration.new(@deploy) diff --git a/test/configuration_test.rb b/test/configuration_test.rb index da53ddb6..c1aaa697 100644 --- a/test/configuration_test.rb +++ b/test/configuration_test.rb @@ -217,15 +217,6 @@ class ConfigurationTest < ActiveSupport::TestCase assert_equal [ "--log-driver", "\"local\"", "--log-opt", "max-size=\"100m\"", "--log-opt", "max-file=\"5\"" ], config.logging_args end - test "network_args default" do - assert_equal [ "--network", "kamal" ], @config.network_args - end - - test "network_args with configured options" do - config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!(network: "custom") }) - assert_equal [ "--network", "custom" ], config.network_args - end - test "erb evaluation of yml config" do config = Kamal::Configuration.create_from config_file: Pathname.new(File.expand_path("fixtures/deploy.erb.yml", __dir__)) assert_equal "my-user", config.registry.username From 69b13ebc6a63ac00aaf126eedefa92344ff6b2a5 Mon Sep 17 00:00:00 2001 From: Igor Alexandrov Date: Wed, 2 Oct 2024 19:27:08 +0400 Subject: [PATCH 12/27] Renamed NETWORK to DEFAULT_NETWORK --- lib/kamal/configuration/accessory.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kamal/configuration/accessory.rb b/lib/kamal/configuration/accessory.rb index 7aed80ea..3258c9d0 100644 --- a/lib/kamal/configuration/accessory.rb +++ b/lib/kamal/configuration/accessory.rb @@ -1,7 +1,7 @@ class Kamal::Configuration::Accessory include Kamal::Configuration::Validation - NETWORK = "kamal" + DEFAULT_NETWORK = "kamal" delegate :argumentize, :optionize, to: Kamal::Utils @@ -181,6 +181,6 @@ class Kamal::Configuration::Accessory end def network - accessory_config["network"] || NETWORK + accessory_config["network"] || DEFAULT_NETWORK end end From 844e3acf50a9767611153866da07e9d000eb3376 Mon Sep 17 00:00:00 2001 From: Alan Oliveira Date: Tue, 15 Oct 2024 14:24:09 +0900 Subject: [PATCH 13/27] prevent escape '#' when generating env_file string --- lib/kamal/env_file.rb | 4 +++- test/env_file_test.rb | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/kamal/env_file.rb b/lib/kamal/env_file.rb index 6a4a80e3..36c251be 100644 --- a/lib/kamal/env_file.rb +++ b/lib/kamal/env_file.rb @@ -37,6 +37,8 @@ class Kamal::EnvFile def escape_docker_env_file_ascii_value(value) # Doublequotes are treated literally in docker env files # so remove leading and trailing ones and unescape any others - value.to_s.dump[1..-2].gsub(/\\"/, "\"") + value.to_s.dump[1..-2] + .gsub(/\\"/, "\"") + .gsub(/\\#/, "#") end end diff --git a/test/env_file_test.rb b/test/env_file_test.rb index c6b9e66e..26344934 100644 --- a/test/env_file_test.rb +++ b/test/env_file_test.rb @@ -11,6 +11,16 @@ class EnvFileTest < ActiveSupport::TestCase Kamal::EnvFile.new(env).to_s end + test "to_s won't escape '#'" do + env = { + "foo" => "\#$foo", + "bar" => "\#{bar}" + } + + assert_equal "foo=\#$foo\nbar=\#{bar}\n", \ + Kamal::EnvFile.new(env).to_s + end + test "to_str won't escape chinese characters" do env = { "foo" => '你好 means hello, "欢迎" means welcome, that\'s simple! 😃 {smile}' From 0f3786781ba6674defb56bc2a72bec2a70030b55 Mon Sep 17 00:00:00 2001 From: Jonas Pardeyke Date: Tue, 15 Oct 2024 22:47:08 +0200 Subject: [PATCH 14/27] added `kamal proxy reboot` to raised error --- lib/kamal/cli/proxy.rb | 2 +- test/cli/proxy_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index 2ce7c2ac..7d226605 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -14,7 +14,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base version = capture_with_info(*KAMAL.proxy.version).strip.presence if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION) - raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}" + raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}. Run `kamal proxy reboot`." end execute *KAMAL.proxy.start_or_run end diff --git a/test/cli/proxy_test.rb b/test/cli/proxy_test.rb index c62589fe..97287ed0 100644 --- a/test/cli/proxy_test.rb +++ b/test/cli/proxy_test.rb @@ -22,7 +22,7 @@ class CliProxyTest < CliTestCase end end - assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}" + assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}. Run `kamal proxy reboot`." ensure Thread.report_on_exception = false end From 8cec17dd058dbf446e67d7b85807a78c0dfc8309 Mon Sep 17 00:00:00 2001 From: Kyle Rippey Date: Tue, 15 Oct 2024 23:20:18 -0700 Subject: [PATCH 15/27] Made secret adapters raise a meaningful error if the required CLI is not installed --- lib/kamal/secrets/adapters/base.rb | 5 +++++ lib/kamal/secrets/adapters/bitwarden.rb | 9 +++++++++ lib/kamal/secrets/adapters/last_pass.rb | 9 +++++++++ lib/kamal/secrets/adapters/one_password.rb | 9 +++++++++ lib/kamal/secrets/adapters/test.rb | 4 ++++ test/secrets/bitwarden_adapter_test.rb | 23 ++++++++++++++++++++++ test/secrets/last_pass_adapter_test.rb | 13 ++++++++++++ test/secrets/one_password_adapter_test.rb | 15 ++++++++++++++ 8 files changed, 87 insertions(+) diff --git a/lib/kamal/secrets/adapters/base.rb b/lib/kamal/secrets/adapters/base.rb index 97b2a458..579414af 100644 --- a/lib/kamal/secrets/adapters/base.rb +++ b/lib/kamal/secrets/adapters/base.rb @@ -2,6 +2,7 @@ class Kamal::Secrets::Adapters::Base delegate :optionize, to: Kamal::Utils def fetch(secrets, account:, from: nil) + check_dependencies! session = login(account) full_secrets = secrets.map { |secret| [ from, secret ].compact.join("/") } fetch_secrets(full_secrets, account: account, session: session) @@ -15,4 +16,8 @@ class Kamal::Secrets::Adapters::Base def fetch_secrets(...) raise NotImplementedError end + + def check_dependencies! + raise NotImplementedError + end end diff --git a/lib/kamal/secrets/adapters/bitwarden.rb b/lib/kamal/secrets/adapters/bitwarden.rb index e84a0d93..7ea4f6f7 100644 --- a/lib/kamal/secrets/adapters/bitwarden.rb +++ b/lib/kamal/secrets/adapters/bitwarden.rb @@ -63,4 +63,13 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base result = `#{full_command}`.strip raw ? result : JSON.parse(result) end + + def check_dependencies! + raise RuntimeError, "Bitwarden CLI is not installed" unless cli_installed? + end + + def cli_installed? + `bw --version 2> /dev/null` + $?.success? + end end diff --git a/lib/kamal/secrets/adapters/last_pass.rb b/lib/kamal/secrets/adapters/last_pass.rb index 390e84ed..2f95148b 100644 --- a/lib/kamal/secrets/adapters/last_pass.rb +++ b/lib/kamal/secrets/adapters/last_pass.rb @@ -27,4 +27,13 @@ class Kamal::Secrets::Adapters::LastPass < Kamal::Secrets::Adapters::Base end end end + + def check_dependencies! + raise RuntimeError, "LastPass CLI is not installed" unless cli_installed? + end + + def cli_installed? + `lpass --version 2> /dev/null` + $?.success? + end end diff --git a/lib/kamal/secrets/adapters/one_password.rb b/lib/kamal/secrets/adapters/one_password.rb index c7e9b28d..c7f9d5ab 100644 --- a/lib/kamal/secrets/adapters/one_password.rb +++ b/lib/kamal/secrets/adapters/one_password.rb @@ -58,4 +58,13 @@ class Kamal::Secrets::Adapters::OnePassword < Kamal::Secrets::Adapters::Base raise RuntimeError, "Could not read #{fields.join(", ")} from #{item} in the #{vault} 1Password vault" unless $?.success? end end + + def check_dependencies! + raise RuntimeError, "1Password CLI is not installed" unless cli_installed? + end + + def cli_installed? + `op --version 2> /dev/null` + $?.success? + end end diff --git a/lib/kamal/secrets/adapters/test.rb b/lib/kamal/secrets/adapters/test.rb index fc0903d9..82577a76 100644 --- a/lib/kamal/secrets/adapters/test.rb +++ b/lib/kamal/secrets/adapters/test.rb @@ -7,4 +7,8 @@ class Kamal::Secrets::Adapters::Test < Kamal::Secrets::Adapters::Base def fetch_secrets(secrets, account:, session:) secrets.to_h { |secret| [ secret, secret.reverse ] } end + + def check_dependencies! + # no op + end end diff --git a/test/secrets/bitwarden_adapter_test.rb b/test/secrets/bitwarden_adapter_test.rb index e2a3ac37..2bc871d3 100644 --- a/test/secrets/bitwarden_adapter_test.rb +++ b/test/secrets/bitwarden_adapter_test.rb @@ -2,6 +2,8 @@ require "test_helper" class BitwardenAdapterTest < SecretAdapterTestCase test "fetch" do + stub_ticks.with("bw --version 2> /dev/null") + stub_unlocked stub_ticks.with("bw sync").returns("") stub_mypassword @@ -14,6 +16,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase end test "fetch with no login" do + stub_ticks.with("bw --version 2> /dev/null") + stub_unlocked stub_ticks.with("bw sync").returns("") stub_noteitem @@ -25,6 +29,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase end test "fetch with from" do + stub_ticks.with("bw --version 2> /dev/null") + stub_unlocked stub_ticks.with("bw sync").returns("") stub_myitem @@ -39,6 +45,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase end test "fetch with multiple items" do + stub_ticks.with("bw --version 2> /dev/null") + stub_unlocked stub_ticks.with("bw sync").returns("") @@ -80,6 +88,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase end test "fetch unauthenticated" do + stub_ticks.with("bw --version 2> /dev/null") + stub_ticks .with("bw status") .returns( @@ -101,6 +111,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase end test "fetch locked" do + stub_ticks.with("bw --version 2> /dev/null") + stub_ticks .with("bw status") .returns( @@ -126,6 +138,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase end test "fetch locked with session" do + stub_ticks.with("bw --version 2> /dev/null") + stub_ticks .with("bw status") .returns( @@ -150,6 +164,15 @@ class BitwardenAdapterTest < SecretAdapterTestCase assert_equal expected_json, json end + test "fetch without CLI installed" do + stub_ticks_with("bw --version 2> /dev/null", succeed: false) + + error = assert_raises RuntimeError do + JSON.parse(shellunescape(run_command("fetch", "mynote"))) + end + assert_equal "Bitwarden CLI is not installed", error.message + end + private def run_command(*command) stdouted do diff --git a/test/secrets/last_pass_adapter_test.rb b/test/secrets/last_pass_adapter_test.rb index 3801d486..ca1f346c 100644 --- a/test/secrets/last_pass_adapter_test.rb +++ b/test/secrets/last_pass_adapter_test.rb @@ -6,6 +6,7 @@ class LastPassAdapterTest < SecretAdapterTestCase end test "fetch" do + stub_ticks.with("lpass --version 2> /dev/null") stub_ticks.with("lpass status --color never").returns("Logged in as email@example.com.") stub_ticks @@ -63,6 +64,7 @@ class LastPassAdapterTest < SecretAdapterTestCase end test "fetch with from" do + stub_ticks.with("lpass --version 2> /dev/null") stub_ticks.with("lpass status --color never").returns("Logged in as email@example.com.") stub_ticks @@ -107,6 +109,8 @@ class LastPassAdapterTest < SecretAdapterTestCase end test "fetch with signin" do + stub_ticks.with("lpass --version 2> /dev/null") + stub_ticks_with("lpass status --color never", succeed: false).returns("Not logged in.") stub_ticks_with("lpass login email@example.com", succeed: true).returns("") stub_ticks.with("lpass show SECRET1 --json").returns(single_item_json) @@ -120,6 +124,15 @@ class LastPassAdapterTest < SecretAdapterTestCase assert_equal expected_json, json end + test "fetch without CLI installed" do + stub_ticks_with("lpass --version 2> /dev/null", succeed: false) + + error = assert_raises RuntimeError do + JSON.parse(shellunescape(run_command("fetch", "SECRET1", "FOLDER1/FSECRET1", "FOLDER1/FSECRET2"))) + end + assert_equal "LastPass CLI is not installed", error.message + end + private def run_command(*command) stdouted do diff --git a/test/secrets/one_password_adapter_test.rb b/test/secrets/one_password_adapter_test.rb index 59ad511d..36fab7c3 100644 --- a/test/secrets/one_password_adapter_test.rb +++ b/test/secrets/one_password_adapter_test.rb @@ -2,6 +2,7 @@ require "test_helper" class SecretsOnePasswordAdapterTest < SecretAdapterTestCase test "fetch" do + stub_ticks.with("op --version 2> /dev/null") stub_ticks.with("op account get --account myaccount 2> /dev/null") stub_ticks @@ -56,6 +57,7 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase end test "fetch with multiple items" do + stub_ticks.with("op --version 2> /dev/null") stub_ticks.with("op account get --account myaccount 2> /dev/null") stub_ticks @@ -115,6 +117,8 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase end test "fetch with signin, no session" do + stub_ticks.with("op --version 2> /dev/null") + stub_ticks_with("op account get --account myaccount 2> /dev/null", succeed: false) stub_ticks_with("op signin --account \"myaccount\" --force --raw", succeed: true).returns("") @@ -132,6 +136,8 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase end test "fetch with signin and session" do + stub_ticks.with("op --version 2> /dev/null") + stub_ticks_with("op account get --account myaccount 2> /dev/null", succeed: false) stub_ticks_with("op signin --account \"myaccount\" --force --raw", succeed: true).returns("1234567890") @@ -148,6 +154,15 @@ class SecretsOnePasswordAdapterTest < SecretAdapterTestCase assert_equal expected_json, json end + test "fetch without CLI installed" do + stub_ticks_with("op --version 2> /dev/null", succeed: false) + + error = assert_raises RuntimeError do + JSON.parse(shellunescape(run_command("fetch", "--from", "op://myvault/myitem", "section/SECRET1", "section/SECRET2", "section2/SECRET3"))) + end + assert_equal "1Password CLI is not installed", error.message + end + private def run_command(*command) stdouted do From e362b0106a931c0a22eab4647b68d9dcb4c79ec0 Mon Sep 17 00:00:00 2001 From: Jonas Pardeyke Date: Wed, 16 Oct 2024 09:08:30 +0200 Subject: [PATCH 16/27] changed text --- lib/kamal/cli/proxy.rb | 2 +- test/cli/proxy_test.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index 7d226605..d0e9ba2b 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -14,7 +14,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base version = capture_with_info(*KAMAL.proxy.version).strip.presence if version && Kamal::Utils.older_version?(version, Kamal::Configuration::PROXY_MINIMUM_VERSION) - raise "kamal-proxy version #{version} is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}. Run `kamal proxy reboot`." + raise "kamal-proxy version #{version} is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}" end execute *KAMAL.proxy.start_or_run end diff --git a/test/cli/proxy_test.rb b/test/cli/proxy_test.rb index 97287ed0..9fe8f56c 100644 --- a/test/cli/proxy_test.rb +++ b/test/cli/proxy_test.rb @@ -22,7 +22,7 @@ class CliProxyTest < CliTestCase end end - assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, please reboot to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}. Run `kamal proxy reboot`." + assert_includes exception.message, "kamal-proxy version v0.0.1 is too old, run `kamal proxy reboot` in order to update to at least #{Kamal::Configuration::PROXY_MINIMUM_VERSION}" ensure Thread.report_on_exception = false end From c320343bb23dee027e7591b7829cb203e67ea850 Mon Sep 17 00:00:00 2001 From: Aidan Haran Date: Sat, 19 Oct 2024 18:18:34 +0100 Subject: [PATCH 17/27] Updated secrets error message if secrets files do not exist --- lib/kamal/secrets.rb | 17 +++++++++++------ test/secrets_test.rb | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/kamal/secrets.rb b/lib/kamal/secrets.rb index c7d4cc03..7a382f98 100644 --- a/lib/kamal/secrets.rb +++ b/lib/kamal/secrets.rb @@ -1,13 +1,10 @@ require "dotenv" class Kamal::Secrets - attr_reader :secrets_files - Kamal::Secrets::Dotenv::InlineCommandSubstitution.install! def initialize(destination: nil) - @secrets_files = \ - [ ".kamal/secrets-common", ".kamal/secrets#{(".#{destination}" if destination)}" ].select { |f| File.exist?(f) } + @destination = destination @mutex = Mutex.new end @@ -17,10 +14,10 @@ class Kamal::Secrets secrets.fetch(key) end rescue KeyError - if secrets_files + if secrets_files.present? raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_files.join(", ")}" else - raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files provided" + raise Kamal::ConfigurationError, "Secret '#{key}' not found, no secret files (#{secrets_filenames.join(", ")}) provided" end end @@ -28,10 +25,18 @@ class Kamal::Secrets secrets end + def secrets_files + @secrets_files ||= secrets_filenames.select { |f| File.exist?(f) } + end + private def secrets @secrets ||= secrets_files.inject({}) do |secrets, secrets_file| secrets.merge!(::Dotenv.parse(secrets_file)) end end + + def secrets_filenames + [ ".kamal/secrets-common", ".kamal/secrets#{(".#{@destination}" if @destination)}" ] + end end diff --git a/test/secrets_test.rb b/test/secrets_test.rb index bb77a196..aca9cebe 100644 --- a/test/secrets_test.rb +++ b/test/secrets_test.rb @@ -31,4 +31,18 @@ class SecretsTest < ActiveSupport::TestCase assert_equal "JKL", Kamal::Secrets.new(destination: "nodest")["SECRET2"] end end + + test "no secrets files" do + with_test_secrets do + error = assert_raises(Kamal::ConfigurationError) do + Kamal::Secrets.new["SECRET"] + end + assert_equal "Secret 'SECRET' not found, no secret files (.kamal/secrets-common, .kamal/secrets) provided", error.message + + error = assert_raises(Kamal::ConfigurationError) do + Kamal::Secrets.new(destination: "dest")["SECRET"] + end + assert_equal "Secret 'SECRET' not found, no secret files (.kamal/secrets-common, .kamal/secrets.dest) provided", error.message + end + end end From 3ee45d7b30eef1479e14e8ec0499ca643c737392 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Tue, 22 Oct 2024 12:38:07 +0100 Subject: [PATCH 18/27] Require zeitwerk 2.6.12 We were requiring Zeitwerk 2.5+, but calling eager_load_namespace which was added in 2.6.2. Fixes: https://github.com/basecamp/kamal/issues/1109 --- Gemfile.lock | 4 ++-- kamal.gemspec | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8b497389..1f825a11 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -11,7 +11,7 @@ PATH net-ssh (~> 7.0) sshkit (>= 1.23.0, < 2.0) thor (~> 1.3) - zeitwerk (~> 2.5) + zeitwerk (>= 2.6.18, < 3.0) GEM remote: https://rubygems.org/ @@ -167,7 +167,7 @@ GEM concurrent-ruby (~> 1.0) unicode-display_width (2.5.0) webrick (1.8.2) - zeitwerk (2.6.17) + zeitwerk (2.7.1) PLATFORMS arm64-darwin diff --git a/kamal.gemspec b/kamal.gemspec index 0dfab60b..c1a8dbeb 100644 --- a/kamal.gemspec +++ b/kamal.gemspec @@ -16,7 +16,7 @@ Gem::Specification.new do |spec| spec.add_dependency "net-ssh", "~> 7.0" spec.add_dependency "thor", "~> 1.3" spec.add_dependency "dotenv", "~> 3.1" - spec.add_dependency "zeitwerk", "~> 2.5" + spec.add_dependency "zeitwerk", ">= 2.6.18", "< 3.0" spec.add_dependency "ed25519", "~> 1.2" spec.add_dependency "bcrypt_pbkdf", "~> 1.0" spec.add_dependency "concurrent-ruby", "~> 1.2" From a3f58307283223b9a32726ff92a504eed3067843 Mon Sep 17 00:00:00 2001 From: Alan Oliveira Date: Wed, 23 Oct 2024 08:06:24 +0900 Subject: [PATCH 19/27] improve test legibility --- test/env_file_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/env_file_test.rb b/test/env_file_test.rb index 26344934..34d31ad6 100644 --- a/test/env_file_test.rb +++ b/test/env_file_test.rb @@ -13,8 +13,8 @@ class EnvFileTest < ActiveSupport::TestCase test "to_s won't escape '#'" do env = { - "foo" => "\#$foo", - "bar" => "\#{bar}" + "foo" => '#$foo', + "bar" => '#{bar}' } assert_equal "foo=\#$foo\nbar=\#{bar}\n", \ From 7142534e77d244dc3fb98acb5f74f0544082fbf2 Mon Sep 17 00:00:00 2001 From: Jan Sterba Date: Sat, 5 Oct 2024 16:05:28 +0200 Subject: [PATCH 20/27] [bitwarden] ability to fetch all fields from an item Sometimes a projects has a lot of secrets (more than 10). And its cumbersome to write $(kama secrets fetch ...) with a lot of field names. I want to be able to just fetch all the fields from a given item and then just use these with $(kamal extract NAME) --- lib/kamal/secrets/adapters/bitwarden.rb | 22 +++++++---- test/secrets/bitwarden_adapter_test.rb | 52 ++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/lib/kamal/secrets/adapters/bitwarden.rb b/lib/kamal/secrets/adapters/bitwarden.rb index 7ea4f6f7..5dfb72db 100644 --- a/lib/kamal/secrets/adapters/bitwarden.rb +++ b/lib/kamal/secrets/adapters/bitwarden.rb @@ -25,18 +25,15 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base {}.tap do |results| items_fields(secrets).each do |item, fields| item_json = run_command("get item #{item.shellescape}", session: session, raw: true) - raise RuntimeError, "Could not read #{secret} from Bitwarden" unless $?.success? + raise RuntimeError, "Could not read #{item} from Bitwarden" unless $?.success? item_json = JSON.parse(item_json) - if fields.any? - fields.each do |field| - item_field = item_json["fields"].find { |f| f["name"] == field } - raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field - value = item_field["value"] - results["#{item}/#{field}"] = value - end + results.merge! fetch_secrets_from_fields(fields, item, item_json) elsif item_json.dig("login", "password") results[item] = item_json.dig("login", "password") + elsif item_json["fields"]&.any? + fields = item_json["fields"].pluck("name") + results.merge! fetch_secrets_from_fields(fields, item, item_json) else raise RuntimeError, "Item #{item} is not a login type item and no fields were specified" end @@ -44,6 +41,15 @@ class Kamal::Secrets::Adapters::Bitwarden < Kamal::Secrets::Adapters::Base end end + def fetch_secrets_from_fields(fields, item, item_json) + fields.to_h do |field| + item_field = item_json["fields"].find { |f| f["name"] == field } + raise RuntimeError, "Could not find field #{field} in item #{item} in Bitwarden" unless item_field + value = item_field["value"] + [ "#{item}/#{field}", value ] + end + end + def items_fields(secrets) {}.tap do |items| secrets.each do |secret| diff --git a/test/secrets/bitwarden_adapter_test.rb b/test/secrets/bitwarden_adapter_test.rb index 2bc871d3..ad280791 100644 --- a/test/secrets/bitwarden_adapter_test.rb +++ b/test/secrets/bitwarden_adapter_test.rb @@ -44,6 +44,23 @@ class BitwardenAdapterTest < SecretAdapterTestCase assert_equal expected_json, json end + test "fetch all with from" do + stub_ticks.with("bw --version 2> /dev/null") + + stub_unlocked + stub_ticks.with("bw sync").returns("") + stub_noteitem_with_fields + + json = JSON.parse(shellunescape(run_command("fetch", "mynotefields"))) + + expected_json = { + "mynotefields/field1"=>"secret1", "mynotefields/field2"=>"blam", "mynotefields/field3"=>"fewgrwjgk", + "mynotefields/field4"=>"auto" + } + + assert_equal expected_json, json + end + test "fetch with multiple items" do stub_ticks.with("bw --version 2> /dev/null") @@ -237,7 +254,37 @@ class BitwardenAdapterTest < SecretAdapterTestCase "collectionIds":[] } JSON - end + end + + def stub_noteitem_with_fields(session: nil) + stub_ticks + .with("#{"BW_SESSION=#{session} " if session}bw get item mynotefields") + .returns(<<~JSON) + { + "passwordHistory":null, + "revisionDate":"2024-09-28T09:07:27.461Z", + "creationDate":"2024-09-28T09:07:00.740Z", + "deletedDate":null, + "object":"item", + "id":"aaaaaaaa-cccc-eeee-0000-222222222222", + "organizationId":null, + "folderId":null, + "type":2, + "reprompt":0, + "name":"noteitem", + "notes":"NOTES", + "favorite":false, + "fields":[ + {"name":"field1","value":"secret1","type":1,"linkedId":null}, + {"name":"field2","value":"blam","type":1,"linkedId":null}, + {"name":"field3","value":"fewgrwjgk","type":1,"linkedId":null}, + {"name":"field4","value":"auto","type":1,"linkedId":null} + ], + "secureNote":{"type":0}, + "collectionIds":[] + } + JSON + end def stub_myitem stub_ticks @@ -260,7 +307,8 @@ class BitwardenAdapterTest < SecretAdapterTestCase "fields":[ {"name":"field1","value":"secret1","type":1,"linkedId":null}, {"name":"field2","value":"blam","type":1,"linkedId":null}, - {"name":"field3","value":"fewgrwjgk","type":1,"linkedId":null} + {"name":"field3","value":"fewgrwjgk","type":1,"linkedId":null}, + {"name":"field4","value":"auto","type":1,"linkedId":null} ], "login":{"fido2Credentials":[],"uris":[],"username":null,"password":null,"totp":null,"passwordRevisionDate":null},"collectionIds":[] } From 8d0f4903ae87c07ca7595eefea97600ab2fee465 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Wed, 23 Oct 2024 14:56:53 +0100 Subject: [PATCH 21/27] Ensure using at least net-ssh 7.3.0 This has support for aes(128|256)gcm ciphers and some fixes for Ruby 3.3. --- Gemfile.lock | 4 ++-- kamal.gemspec | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1f825a11..3e2f89a4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -8,7 +8,7 @@ PATH concurrent-ruby (~> 1.2) dotenv (~> 3.1) ed25519 (~> 1.2) - net-ssh (~> 7.0) + net-ssh (~> 7.3) sshkit (>= 1.23.0, < 2.0) thor (~> 1.3) zeitwerk (>= 2.6.18, < 3.0) @@ -78,7 +78,7 @@ GEM net-ssh (>= 2.6.5, < 8.0.0) net-sftp (4.0.0) net-ssh (>= 5.0.0, < 8.0.0) - net-ssh (7.2.3) + net-ssh (7.3.0) nokogiri (1.16.7-arm64-darwin) racc (~> 1.4) nokogiri (1.16.7-x86_64-darwin) diff --git a/kamal.gemspec b/kamal.gemspec index c1a8dbeb..7218f865 100644 --- a/kamal.gemspec +++ b/kamal.gemspec @@ -13,7 +13,7 @@ Gem::Specification.new do |spec| spec.add_dependency "activesupport", ">= 7.0" spec.add_dependency "sshkit", ">= 1.23.0", "< 2.0" - spec.add_dependency "net-ssh", "~> 7.0" + spec.add_dependency "net-ssh", "~> 7.3" spec.add_dependency "thor", "~> 1.3" spec.add_dependency "dotenv", "~> 3.1" spec.add_dependency "zeitwerk", ">= 2.6.18", "< 3.0" From 9a8a45015b881aed34e7b35197b1249487afe413 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Wed, 23 Oct 2024 15:05:20 +0100 Subject: [PATCH 22/27] Allow log max size to not be set The max-size log opt is not valid for all logging drivers, such as syslog. Allow the option to be removed from the boot config with: ``` kamal proxy boot_config set --log-max-size= or kamal proxy boot_config set --log-max-size="" ``` --- lib/kamal/cli/proxy.rb | 1 + lib/kamal/configuration.rb | 2 +- test/cli/proxy_test.rb | 9 +++++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index d0e9ba2b..d985e73e 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -37,6 +37,7 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base ] on(KAMAL.proxy_hosts) do |host| + p boot_options execute(*KAMAL.proxy.ensure_proxy_directory) upload! StringIO.new(boot_options.join(" ")), KAMAL.config.proxy_options_file end diff --git a/lib/kamal/configuration.rb b/lib/kamal/configuration.rb index 86602fe1..eba09657 100644 --- a/lib/kamal/configuration.rb +++ b/lib/kamal/configuration.rb @@ -254,7 +254,7 @@ class Kamal::Configuration end def proxy_logging_args(max_size) - argumentize "--log-opt", "max-size=#{max_size}" + argumentize "--log-opt", "max-size=#{max_size}" if max_size.present? end def proxy_options_default diff --git a/test/cli/proxy_test.rb b/test/cli/proxy_test.rb index 9fe8f56c..0a890451 100644 --- a/test/cli/proxy_test.rb +++ b/test/cli/proxy_test.rb @@ -263,6 +263,15 @@ class CliProxyTest < CliTestCase end end + test "boot_config set no log max size" do + run_command("boot_config", "set", "--log-max-size=").tap do |output| + %w[ 1.1.1.1 1.1.1.2 ].each do |host| + assert_match "Running /usr/bin/env mkdir -p .kamal/proxy on #{host}", output + assert_match "Uploading \"--publish 80:80 --publish 443:443\" to .kamal/proxy/options on #{host}", output + end + end + end + test "boot_config set custom ports" do run_command("boot_config", "set", "--http-port", "8080", "--https-port", "8443").tap do |output| %w[ 1.1.1.1 1.1.1.2 ].each do |host| From 1980a79e737f83c0c73a0c3099ca36291bdc3b5c Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Fri, 25 Oct 2024 08:10:25 +0100 Subject: [PATCH 23/27] Update lib/kamal/cli/proxy.rb Co-authored-by: Sijawusz Pur Rahnama --- lib/kamal/cli/proxy.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/kamal/cli/proxy.rb b/lib/kamal/cli/proxy.rb index d985e73e..d0e9ba2b 100644 --- a/lib/kamal/cli/proxy.rb +++ b/lib/kamal/cli/proxy.rb @@ -37,7 +37,6 @@ class Kamal::Cli::Proxy < Kamal::Cli::Base ] on(KAMAL.proxy_hosts) do |host| - p boot_options execute(*KAMAL.proxy.ensure_proxy_directory) upload! StringIO.new(boot_options.join(" ")), KAMAL.config.proxy_options_file end From cd4e183213d94317c7af5d595540cc711a17c98e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 01:39:47 -0700 Subject: [PATCH 24/27] Bump rexml from 3.3.6 to 3.3.9 in the bundler group across 1 directory (#1173) Bumps the bundler group with 1 update in the / directory: [rexml](https://github.com/ruby/rexml). Updates `rexml` from 3.3.6 to 3.3.9 - [Release notes](https://github.com/ruby/rexml/releases) - [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md) - [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9) --- updated-dependencies: - dependency-name: rexml 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 | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 3e2f89a4..562f59b8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -122,8 +122,7 @@ GEM regexp_parser (2.9.2) reline (0.5.9) io-console (~> 0.5) - rexml (3.3.6) - strscan + rexml (3.3.9) rubocop (1.65.1) json (~> 2.3) language_server-protocol (>= 3.17.0) @@ -161,7 +160,6 @@ GEM net-sftp (>= 2.1.2) net-ssh (>= 2.8.0) stringio (3.1.1) - strscan (3.1.0) thor (1.3.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) From 2c14f48300d0028ca000a2d1b6942412f5b08ced Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Wed, 30 Oct 2024 08:06:52 +0000 Subject: [PATCH 25/27] Bump proxy minimum version to 0.8.2 Detect event-stream content type properly See: https://github.com/basecamp/kamal-proxy/releases/tag/v0.8.2 --- 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 86602fe1..1eb9d5d6 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.1" + PROXY_MINIMUM_VERSION = "v0.8.2" PROXY_HTTP_PORT = 80 PROXY_HTTPS_PORT = 443 PROXY_LOG_MAX_SIZE = "10m" From 685312c9f802bb7eeac4a6493487e5937cdf4980 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Thu, 31 Oct 2024 09:14:29 +0000 Subject: [PATCH 26/27] Bump version for 2.3.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 562f59b8..c057d7f6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - kamal (2.2.2) + kamal (2.3.0) activesupport (>= 7.0) base64 (~> 0.2) bcrypt_pbkdf (~> 1.0) diff --git a/lib/kamal/version.rb b/lib/kamal/version.rb index 3f5ef1fc..2f9a86b6 100644 --- a/lib/kamal/version.rb +++ b/lib/kamal/version.rb @@ -1,3 +1,3 @@ module Kamal - VERSION = "2.2.2" + VERSION = "2.3.0" end From e9ba92386ca54da22d105d8bf08c3b17b882f946 Mon Sep 17 00:00:00 2001 From: Donal McBreen Date: Mon, 4 Nov 2024 09:17:12 +0000 Subject: [PATCH 27/27] Filter correctly for empty destinations An empty destination should only filter container with empty destination, not pick up all containers. Fixes: https://github.com/basecamp/kamal/issues/1184 --- lib/kamal/commands/app.rb | 22 ++++++++---- lib/kamal/commands/app/containers.rb | 4 +-- lib/kamal/commands/app/images.rb | 2 +- test/cli/app_test.rb | 48 ++++++++++++------------- test/cli/main_test.rb | 4 +-- test/commands/app_test.rb | 52 ++++++++++++++-------------- 6 files changed, 70 insertions(+), 62 deletions(-) diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 6d8f44c6..6c4df0e4 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -47,7 +47,7 @@ class Kamal::Commands::App < Kamal::Commands::Base end def info - docker :ps, *filter_args + docker :ps, *container_filter_args end @@ -67,7 +67,7 @@ class Kamal::Commands::App < Kamal::Commands::Base def list_versions(*docker_args, statuses: nil) pipe \ - docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'), + docker(:ps, *container_filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'), extract_version_from_name end @@ -91,11 +91,15 @@ class Kamal::Commands::App < Kamal::Commands::Base end def latest_container(format:, filters: nil) - docker :ps, "--latest", *format, *filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters) + docker :ps, "--latest", *format, *container_filter_args(statuses: ACTIVE_DOCKER_STATUSES), argumentize("--filter", filters) end - def filter_args(statuses: nil) - argumentize "--filter", filters(statuses: statuses) + def container_filter_args(statuses: nil) + argumentize "--filter", container_filters(statuses: statuses) + end + + def image_filter_args + argumentize "--filter", image_filters end def extract_version_from_name @@ -103,13 +107,17 @@ class Kamal::Commands::App < Kamal::Commands::Base %(while read line; do echo ${line##{role.container_prefix}-}; done) end - def filters(statuses: nil) + def container_filters(statuses: nil) [ "label=service=#{config.service}" ].tap do |filters| - filters << "label=destination=#{config.destination}" if config.destination + filters << "label=destination=#{config.destination}" filters << "label=role=#{role}" if role statuses&.each do |status| filters << "status=#{status}" end end end + + def image_filters + [ "label=service=#{config.service}" ] + end end diff --git a/lib/kamal/commands/app/containers.rb b/lib/kamal/commands/app/containers.rb index 0bab388b..a83d83ca 100644 --- a/lib/kamal/commands/app/containers.rb +++ b/lib/kamal/commands/app/containers.rb @@ -2,7 +2,7 @@ module Kamal::Commands::App::Containers DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'" def list_containers - docker :container, :ls, "--all", *filter_args + docker :container, :ls, "--all", *container_filter_args end def list_container_names @@ -20,7 +20,7 @@ module Kamal::Commands::App::Containers end def remove_containers - docker :container, :prune, "--force", *filter_args + docker :container, :prune, "--force", *container_filter_args end def container_health_log(version:) diff --git a/lib/kamal/commands/app/images.rb b/lib/kamal/commands/app/images.rb index e20e83e1..db596a31 100644 --- a/lib/kamal/commands/app/images.rb +++ b/lib/kamal/commands/app/images.rb @@ -4,7 +4,7 @@ module Kamal::Commands::App::Images end def remove_images - docker :image, :prune, "--all", "--force", *filter_args + docker :image, :prune, "--all", "--force", *image_filter_args end def tag_latest_image diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 32b37456..5e76179c 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -19,7 +19,7 @@ class CliAppTest < CliTestCase .returns("12345678") # running version 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) + .with(: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", raise_on_non_zero_exit: false) .returns("123") # old version SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) @@ -63,7 +63,7 @@ class CliAppTest < CliTestCase .returns("12345678") # running version 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) + .with(: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", raise_on_non_zero_exit: false) .returns("123").twice # old version SSHKit::Backend::Abstract.any_instance.expects(:capture_with_info) @@ -92,7 +92,7 @@ class CliAppTest < CliTestCase .returns("12345678") # running version 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) + .with(: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", raise_on_non_zero_exit: false) .returns("123") # old version run_command("boot", config: :with_env_tags).tap do |output| @@ -196,17 +196,17 @@ class CliAppTest < CliTestCase test "stop" do run_command("stop").tap do |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 + 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 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) + .with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=destination=", "--filter", "label=role=web", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) .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) + .with(: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", raise_on_non_zero_exit: false) .returns("12345678\n") run_command("stale_containers").tap do |output| @@ -216,11 +216,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) + .with(:docker, :ps, "--filter", "label=service=app", "--filter", "label=destination=", "--filter", "label=role=web", "--format", "\"{{.Names}}\"", "|", "while read line; do echo ${line#app-web-}; done", raise_on_non_zero_exit: false) .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) + .with(: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", raise_on_non_zero_exit: false) .returns("12345678\n") run_command("stale_containers", "--stop").tap do |output| @@ -231,13 +231,13 @@ class CliAppTest < CliTestCase test "details" do run_command("details").tap do |output| - assert_match "docker ps --filter label=service=app --filter label=role=web", output + assert_match "docker ps --filter label=service=app --filter label=destination= --filter label=role=web", output end end test "remove" do run_command("remove").tap do |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("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 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 @@ -275,7 +275,7 @@ class CliAppTest < CliTestCase 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=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 "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 assert_match "docker exec app-web-999 ruby -v", output end end @@ -294,7 +294,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 /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 "Running /usr/bin/env 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 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 @@ -313,46 +313,46 @@ class CliAppTest < CliTestCase test "logs" do SSHKit::Backend::Abstract.any_instance.stubs(:exec) - .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'") + .with("ssh -t root@1.1.1.1 '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 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 --timestamps --tail 100 2>&1", run_command("logs") + 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 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 --timestamps 2>&1 | grep 'hey'", run_command("logs", "--grep", "hey") + 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 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 --timestamps 2>&1 | grep 'hey' -C 2", run_command("logs", "--grep", "hey", "--grep-options", "-C 2") + 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 2>&1 | grep 'hey' -C 2", run_command("logs", "--grep", "hey", "--grep-options", "-C 2") end test "logs with follow" 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 --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=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'") - 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") + 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 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\"'") + .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\"'") - 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") + 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 --follow 2>&1 | grep \"hey\"", run_command("logs", "--follow", "--grep", "hey") end test "logs with follow, grep and grep options" 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'") + .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\" -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", "--grep-options", "-C 2") + 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 --follow 2>&1 | grep \"hey\" -C 2", run_command("logs", "--follow", "--grep", "hey", "--grep-options", "-C 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 + 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 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 "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 + 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 end end diff --git a/test/cli/main_test.rb b/test/cli/main_test.rb index 4b111bad..cd0efe1f 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(: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) + .with(:sh, "-c", "'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --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=destination= --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 end @@ -280,7 +280,7 @@ class CliMainTest < CliTestCase .with(:docker, :container, :ls, "--all", "--filter", "name=^app-web-123$", "--quiet") .returns("123").at_least_once 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) + .with(: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", raise_on_non_zero_exit: false) .returns("").at_least_once run_command("rollback", "123").tap do |output| diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 0e5cad79..1fb59e8a 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -79,18 +79,18 @@ class CommandsAppTest < ActiveSupport::TestCase test "stop" 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 stop", + "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 stop", new_command.stop.join(" ") end test "stop with custom drain timeout" do @config[:drain_timeout] = 20 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 stop", + "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 stop", new_command.stop.join(" ") assert_equal \ - "sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=role=workers --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=workers --filter status=running --filter status=restarting' | head -1 | xargs docker stop -t 20", + "sh -c 'docker ps --latest --quiet --filter label=service=app --filter label=destination= --filter label=role=workers --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=workers --filter status=running --filter status=restarting' | head -1 | xargs docker stop -t 20", new_command(role: "workers").stop.join(" ") end @@ -102,7 +102,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "info" do assert_equal \ - "docker ps --filter label=service=app --filter label=role=web", + "docker ps --filter label=service=app --filter label=destination= --filter label=role=web", new_command.info.join(" ") end @@ -153,71 +153,71 @@ class CommandsAppTest < ActiveSupport::TestCase test "logs" 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 --timestamps 2>&1", + "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 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 --timestamps --since 5m 2>&1", + "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", 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 --timestamps --tail 100 2>&1", + "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 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 --timestamps --since 5m --tail 100 2>&1", + "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 --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 --timestamps 2>&1 | grep 'my-id'", + "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 2>&1 | grep 'my-id'", new_command.logs(grep: "my-id").join(" ") end test "logs with grep and grep options" 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 --timestamps 2>&1 | grep 'my-id' -C 2", + "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 2>&1 | grep 'my-id' -C 2", new_command.logs(grep: "my-id", grep_options: "-C 2").join(" ") end test "logs with since, grep and grep options" 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 --timestamps --since 5m 2>&1 | grep 'my-id' -C 2", + "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 | grep 'my-id' -C 2", new_command.logs(since: "5m", grep: "my-id", grep_options: "-C 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 --timestamps --since 5m 2>&1 | grep 'my-id'", + "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 | grep 'my-id'", new_command.logs(since: "5m", grep: "my-id").join(" ") end test "follow logs" do 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'", + "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'", new_command.follow_logs(host: "app-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 | grep \"Completed\"'", + "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 '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'", + "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) 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\"'", + "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 | grep \"Completed\"'", new_command.follow_logs(host: "app-1", lines: 123, 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 --tail 123 --follow 2>&1 | grep \"Completed\"'", + "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 --tail 123 --follow 2>&1 | grep \"Completed\"'", new_command.follow_logs(host: "app-1", timestamps: false, lines: 123, grep: "Completed") end @@ -322,7 +322,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "current_running_container_id" 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", + "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", new_command.current_running_container_id.join(" ") end @@ -341,23 +341,23 @@ class CommandsAppTest < ActiveSupport::TestCase test "current_running_version" do assert_equal \ - "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", + "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", new_command.current_running_version.join(" ") end test "list_versions" do assert_equal \ - "docker ps --filter label=service=app --filter label=role=web --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done", + "docker ps --filter label=service=app --filter label=destination= --filter label=role=web --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done", new_command.list_versions.join(" ") 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", + "docker ps --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --latest --format \"{{.Names}}\" | while read line; do echo ${line#app-web-}; done", new_command.list_versions("--latest", statuses: [ :running, :restarting ]).join(" ") end test "list_containers" do assert_equal \ - "docker container ls --all --filter label=service=app --filter label=role=web", + "docker container ls --all --filter label=service=app --filter label=destination= --filter label=role=web", new_command.list_containers.join(" ") end @@ -370,7 +370,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "list_container_names" do assert_equal \ - "docker container ls --all --filter label=service=app --filter label=role=web --format '{{ .Names }}'", + "docker container ls --all --filter label=service=app --filter label=destination= --filter label=role=web --format '{{ .Names }}'", new_command.list_container_names.join(" ") end @@ -389,7 +389,7 @@ class CommandsAppTest < ActiveSupport::TestCase test "remove_containers" do assert_equal \ - "docker container prune --force --filter label=service=app --filter label=role=web", + "docker container prune --force --filter label=service=app --filter label=destination= --filter label=role=web", new_command.remove_containers.join(" ") end @@ -408,14 +408,14 @@ class CommandsAppTest < ActiveSupport::TestCase test "remove_images" do assert_equal \ - "docker image prune --all --force --filter label=service=app --filter label=role=web", + "docker image prune --all --force --filter label=service=app", new_command.remove_images.join(" ") end test "remove_images with destination" do @destination = "staging" assert_equal \ - "docker image prune --all --force --filter label=service=app --filter label=destination=staging --filter label=role=web", + "docker image prune --all --force --filter label=service=app", new_command.remove_images.join(" ") end