Fix 5 pre-release bugs

- Help text showed wrong config path (~/.portainer-cli vs ~/.config/portainer-cli)
- --port without colon (e.g. --port 8080) crashed with nil/tcp key; now errors clearly
- --cmd used naive split(' ') breaking quoted args; switched to Shellwords.split
- --env without '=' (e.g. --env FOO) silently passed empty value; now errors clearly
- SSL/connection errors surfaced as raw exception; now caught with actionable message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 15:35:28 +01:00
parent f263088d15
commit ed5eca8924
3 changed files with 18 additions and 4 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ module PortainerCli
portainer-cli exec <endpoint-id> <container> Open interactive shell (manual endpoint)
--shell SHELL Shell to use (default: /bin/sh)
Config file: ~/.portainer-cli/config.yml
Config file: ~/.config/portainer-cli/config.yml
HELP
def self.run(argv)
+8 -2
View File
@@ -2,6 +2,7 @@
require_relative 'base'
require 'optparse'
require 'shellwords'
module PortainerCli
module Commands
@@ -55,6 +56,7 @@ module PortainerCli
end
env_vars = opts[:env].map do |pair|
error("Invalid --env '#{pair}': expected KEY=VALUE") unless pair.include?('=')
k, v = pair.split('=', 2)
{ 'name' => k, 'value' => v.to_s }
end
@@ -90,7 +92,11 @@ module PortainerCli
port_bindings = {}
exposed_ports = {}
opts[:ports].each do |mapping|
host_port, cont_port = mapping.split(':', 2)
parts = mapping.split(':', 2)
error("Invalid --port '#{mapping}': expected HOST:CONTAINER (e.g. 8080:80)") unless parts.size == 2
host_port, cont_port = parts
error("Invalid --port '#{mapping}': missing host port") if host_port.empty?
error("Invalid --port '#{mapping}': missing container port") if cont_port.empty?
cont_key = "#{cont_port}/tcp"
exposed_ports[cont_key] = {}
port_bindings[cont_key] = [{ 'HostPort' => host_port }]
@@ -102,7 +108,7 @@ module PortainerCli
'ExposedPorts' => exposed_ports,
'HostConfig' => { 'PortBindings' => port_bindings }
}
body['Cmd'] = opts[:cmd].split(' ') if opts[:cmd]
body['Cmd'] = Shellwords.split(opts[:cmd]) if opts[:cmd]
query = opts[:name] ? "?name=#{URI.encode_www_form_component(opts[:name])}" : ''
result = @client.post("/api/endpoints/#{opts[:endpoint]}/docker/containers/create#{query}", body)
+8
View File
@@ -40,11 +40,19 @@ module PortainerCli
ssl = OpenSSL::SSL::SSLSocket.new(raw, ctx)
ssl.hostname = @url.host
ssl.sync_close = true
begin
ssl.connect
rescue OpenSSL::SSL::SSLError => e
raw.close rescue nil
hint = @ssl_verify ? " (try: portainer-cli configure → answer 'n' to SSL verification)" : ""
raise "SSL connection failed: #{e.message}#{hint}"
end
ssl
else
raw
end
rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
raise "Cannot connect to #{@url.host}:#{@url.port}#{e.message}"
end
def perform_handshake(socket)