Handle duplicate stack names across endpoints in console command

When a stack name exists on multiple endpoints, error with a list showing
each match with its endpoint ID and name. Pass --endpoint ID to disambiguate.
Single matches continue to work without any extra flags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
grzlusandClaude Sonnet 4.6 committed 2026-03-22 15:46:34 +01:00
1 parent ed5eca8924
commit e002cdf170
2 files changed
+35 -7

No files matched your search

+1
View File
@@ -31,6 +31,7 @@ module PortainerCli
portainer-cli create stack --endpoint ID --name NAME --file FILE
portainer-cli create container --endpoint ID --image IMAGE [--name NAME] [--port h:c] [--env K=V]
portainer-cli console <stack> [container] Open interactive shell (resolves endpoint automatically)
--endpoint ID Disambiguate when stack name exists on multiple endpoints
--shell SHELL Shell to use (default: /bin/sh)
--run CMD Run command instead of a bare shell
portainer-cli exec <endpoint-id> <container> Open interactive shell (manual endpoint)
+34 -7
View File
@@ -25,12 +25,13 @@ module PortainerCli
o.on('--shell SHELL', 'Shell to use (default: /bin/sh)') { |v| opts[:shell] = v }
o.on('--run CMD', 'Run command instead of a bare shell') { |v| opts[:run] = v }
o.on('--quiet', 'Suppress status output') { opts[:quiet] = true }
o.on('--endpoint ID', 'Endpoint ID to disambiguate stacks') { |v| opts[:endpoint] = v }
end.parse!(args)
stack_name = args.shift or error("Usage: portainer-cli console <stack> [container] [--run CMD] [--shell /bin/bash]")
stack_name = args.shift or error("Usage: portainer-cli console <stack> [container] [--endpoint ID] [--run CMD] [--shell /bin/bash]")
container_name = args.shift || stack_name
stack = resolve_stack(stack_name)
stack = resolve_stack(stack_name, opts[:endpoint])
endpoint_id = stack['EndpointId']
status("Stack: #{stack['Name']} Endpoint: #{endpoint_id}", opts[:quiet])
@@ -60,16 +61,42 @@ module PortainerCli
$stderr.puts dim(msg) unless quiet
end
def resolve_stack(name_or_id)
def resolve_stack(name_or_id, endpoint_id = nil)
stacks = @client.get('/api/stacks')
match = stacks.find do |s|
matches = stacks.select do |s|
s['Name'] == name_or_id || s['Id'].to_s == name_or_id
end
unless match
available = stacks.map { |s| " #{s['Id']} #{s['Name']}" }.join("\n")
if matches.empty?
available = stacks.map { |s| " #{s['Id']} #{s['Name']} (endpoint: #{s['EndpointId']})" }.join("\n")
error("Stack '#{name_or_id}' not found. Available stacks:\n#{available}")
end
match
if matches.size > 1
if endpoint_id
matches = matches.select { |s| s['EndpointId'].to_s == endpoint_id.to_s }
if matches.empty?
error("Stack '#{name_or_id}' not found on endpoint #{endpoint_id}. " \
"It exists on endpoint(s): #{matches.map { |s| s['EndpointId'] }.join(', ')}")
end
else
endpoint_names = endpoint_name_map
list = matches.map do |s|
ep_name = endpoint_names[s['EndpointId']] || s['EndpointId'].to_s
" #{s['Id']} #{s['Name']} (endpoint #{s['EndpointId']}: #{ep_name})"
end.join("\n")
error("Ambiguous stack '#{name_or_id}' exists on multiple endpoints. Use --endpoint ID:\n#{list}")
end
end
matches.first
end
def endpoint_name_map
@client.get('/api/endpoints').each_with_object({}) { |e, h| h[e['Id']] = e['Name'] }
rescue StandardError
{}
end
def resolve_container(endpoint_id, name_or_id, stack_name = nil, quiet = false)
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.