Repository: basecamp/kamal (Ruby)

Issue:
Unterminated quoted string
When running `mrsk setup -v` I get the following error:

```
$ mrsk setup -v
Acquiring the deploy lock
  INFO [8ee79814] Running /usr/bin/env mkdir mrsk_lock && echo "TG9ja2VkIGJ5OiAgYXQgMjAyMy0wNS0wOFQxOToyMjozMFoKVmVyc2lvbjog
N2VmOGY5YjFjNWVkMjFmNTFhOThjMzFmNjMzMzViNjcyMGRmMTVhNApNZXNz
YWdlOiBBdXRvbWF0aWMgZGVwbG95IGxvY2s=
" > mrsk_lock/details on 135.181.255.197
 DEBUG [8ee79814] Command: /usr/bin/env mkdir mrsk_lock && echo "TG9ja2VkIGJ5OiAgYXQgMjAyMy0wNS0wOFQxOToyMjozMFoKVmVyc2lvbjog
N2VmOGY5YjFjNWVkMjFmNTFhOThjMzFmNjMzMzViNjcyMGRmMTVhNApNZXNz
YWdlOiBBdXRvbWF0aWMgZGVwbG95IGxvY2s=
" > mrsk_lock/details
Enter passphrase for /root/.ssh/id_ed25519:
  INFO [8ee79814] Finished in 4.151 seconds with exit status 0 (successful).
  INFO [e543e0bc] Running docker -v on 135.181.255.197
 DEBUG [e543e0bc] Command: docker -v
 DEBUG [e543e0bc] 	Docker version 23.0.6, build ef23cbc
  INFO [e543e0bc] Finished in 0.305 seconds with exit status 0 (successful).
Log into image registry...
  INFO [dc7559b2] Running docker login ghcr.io -u [REDACTED] -p [REDACTED] on localhost
 DEBUG [dc7559b2] Command: docker login ghcr.io -u [REDACTED] -p [REDACTED]
 DEBUG [dc7559b2] 	sh: syntax error: unterminated quoted string
  Finished all in 0.0 seconds
  Finished all in 0.3 seconds
Releasing the deploy lock
  INFO [e572763f] Running /usr/bin/env rm mrsk_lock/details && rm -r mrsk_lock on 135.181.255.197
 DEBUG [e572763f] Command: /usr/bin/env rm mrsk_lock/details && rm -r mrsk_lock
  INFO [e572763f] Finished in 0.164 seconds with exit status 0 (successful).
  ERROR (SSHKit::Command::Failed): docker exit status: 512
docker stdout: Nothing written
docker stderr: sh: syntax error: unterminated quoted string

/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/command.rb:97:in `exit_status='
/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/backends/local.rb:59:in `block in execute_command'
/usr/local/lib/ruby/3.2.0/open3.rb:228:in `popen_run'
/usr/local/lib/ruby/3.2.0/open3.rb:103:in `popen3'
/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/backends/local.rb:44:in `execute_command'
/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/backends/abstract.rb:148:in `block in create_command_and_execute'
<internal:kernel>:90:in `tap'
/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/backends/abstract.rb:148:in `create_command_and_execute'
/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/backends/abstract.rb:80:in `execute'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/registry.rb:4:in `block in login'
/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/backends/abstract.rb:31:in `instance_exec'
/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/backends/abstract.rb:31:in `run'
/usr/local/bundle/gems/sshkit-1.21.4/lib/sshkit/dsl.rb:10:in `run_locally'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/registry.rb:4:in `login'
/usr/local/bundle/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
/usr/local/bundle/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
/usr/local/bundle/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
/usr/local/bundle/gems/thor-1.2.1/lib/thor/invocation.rb:116:in `invoke'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/main.rb:21:in `block (2 levels) in deploy'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/base.rb:68:in `print_runtime'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/main.rb:19:in `block in deploy'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/base.rb:81:in `with_lock'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/main.rb:16:in `deploy'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/main.rb:8:in `block (2 levels) in setup'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/base.rb:68:in `print_runtime'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/main.rb:5:in `block in setup'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/base.rb:86:in `with_lock'
/usr/local/bundle/gems/mrsk-0.12.1/lib/mrsk/cli/main.rb:4:in `setup'
/usr/local/bundle/gems/thor-1.2.1/lib/thor/command.rb:27:in `run'
/usr/local/bundle/gems/thor-1.2.1/lib/thor/invocation.rb:127:in `invoke_command'
/usr/local/bundle/gems/thor-1.2.1/lib/thor.rb:392:in `dispatch'
/usr/local/bundle/gems/thor-1.2.1/lib/thor/base.rb:485:in `start'
/usr/local/bundle/gems/mrsk-0.12.1/bin/mrsk:9:in `<top (required)>'
/usr/local/bundle/bin/mrsk:25:in `load'
/usr/local/bundle/bin/mrsk:25:in `<main>'
```




Discussion:
It looks like an issue with your docker credentials.  Do you have your password quoted in your `.env` file?
I don't, but I have a very long password that contains both single (`'`) and double (`"`) quotes.



Patch:
diff --git a/lib/kamal/commands/registry.rb b/lib/kamal/commands/registry.rb
index eb02acadf..e13c90acf 100644
--- a/lib/kamal/commands/registry.rb
+++ b/lib/kamal/commands/registry.rb
@@ -2,7 +2,10 @@ class Kamal::Commands::Registry < Kamal::Commands::Base
   delegate :registry, to: :config
 
   def login
-    docker :login, registry["server"], "-u", sensitive(lookup("username")), "-p", sensitive(lookup("password"))
+    docker :login,
+      registry["server"],
+      "-u", sensitive(Kamal::Utils.escape_shell_value(lookup("username"))),
+      "-p", sensitive(Kamal::Utils.escape_shell_value(lookup("password")))
   end
 
   def logout


Test patch:
diff --git a/test/commands/registry_test.rb b/test/commands/registry_test.rb
index 83b73a7ba..c25d75853 100755
--- a/test/commands/registry_test.rb
+++ b/test/commands/registry_test.rb
@@ -15,7 +15,7 @@ class CommandsRegistryTest < ActiveSupport::TestCase
 
   test "registry login" do
     assert_equal \
-      "docker login hub.docker.com -u dhh -p secret",
+      "docker login hub.docker.com -u \"dhh\" -p \"secret\"",
       @registry.login.join(" ")
   end
 
@@ -24,7 +24,18 @@ class CommandsRegistryTest < ActiveSupport::TestCase
     @config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ]
 
     assert_equal \
-      "docker login hub.docker.com -u dhh -p more-secret",
+      "docker login hub.docker.com -u \"dhh\" -p \"more-secret\"",
+      @registry.login.join(" ")
+  ensure
+    ENV.delete("KAMAL_REGISTRY_PASSWORD")
+  end
+
+  test "registry login escape password" do
+    ENV["KAMAL_REGISTRY_PASSWORD"] = "more-secret'\""
+    @config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ]
+
+    assert_equal \
+      "docker login hub.docker.com -u \"dhh\" -p \"more-secret'\\\"\"",
       @registry.login.join(" ")
   ensure
     ENV.delete("KAMAL_REGISTRY_PASSWORD")
@@ -35,7 +46,7 @@ class CommandsRegistryTest < ActiveSupport::TestCase
     @config[:registry]["username"] = [ "KAMAL_REGISTRY_USERNAME" ]
 
     assert_equal \
-      "docker login hub.docker.com -u also-secret -p secret",
+      "docker login hub.docker.com -u \"also-secret\" -p \"secret\"",
       @registry.login.join(" ")
   ensure
     ENV.delete("KAMAL_REGISTRY_USERNAME")