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:
1 parent
ed5eca8924
commit
e002cdf170
2 files changed
+38
-10
No files matched your search
@@ -22,15 +22,16 @@ module PortainerCli
|
||||
def run(args)
|
||||
opts = { shell: '/bin/sh' }
|
||||
OptionParser.new do |o|
|
||||
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('--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)
|
||||
|
||||