Initial implementation of portainer-cli

Ruby CLI tool for interacting with the Portainer API. Supports listing
endpoints/stacks/containers/volumes/networks, creating stacks and containers,
and opening an interactive exec session in a container via WebSocket.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 12:58:33 +01:00
commit d3cb4fdebd
14 changed files with 815 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
# frozen_string_literal: true
require_relative 'base'
module PortainerCli
module Commands
class List
include Base
SUBCOMMANDS = %w[endpoints stacks containers volumes networks].freeze
def initialize(config, client)
@config = config
@client = client
end
def run(args)
require_config!(@config)
sub = args.shift&.downcase
case sub
when 'endpoints', 'envs', 'env'
list_endpoints
when 'stacks', 'stack'
list_stacks
when 'containers', 'container', 'ps'
endpoint_id = resolve_endpoint_id(args)
list_containers(endpoint_id)
when 'volumes', 'volume'
endpoint_id = resolve_endpoint_id(args)
list_volumes(endpoint_id)
when 'networks', 'network'
endpoint_id = resolve_endpoint_id(args)
list_networks(endpoint_id)
else
puts "Usage: portainer-cli list <#{SUBCOMMANDS.join('|')}> [endpoint-id]"
exit 1
end
end
private
def list_endpoints
data = @client.get('/api/endpoints')
rows = data.map do |e|
[e['Id'], e['Name'], e['URL'] || e['PublicURL'] || '-', endpoint_status(e['Status'])]
end
print_table(%w[ID Name URL Status], rows)
end
def list_stacks
data = @client.get('/api/stacks')
rows = data.map do |s|
[s['Id'], s['Name'], s['EndpointId'], stack_status(s['Status'])]
end
print_table(%w[ID Name Endpoint Status], rows)
end
def list_containers(endpoint_id)
data = @client.get("/api/endpoints/#{endpoint_id}/docker/containers/json", all: 1)
rows = data.map do |c|
name = Array(c['Names']).first&.delete_prefix('/') || '-'
image = c['Image']
status = c['Status']
id = c['Id'][0, 12]
[id, name, image, status]
end
print_table(%w[ID Name Image Status], rows)
end
def list_volumes(endpoint_id)
data = @client.get("/api/endpoints/#{endpoint_id}/docker/volumes")
vols = data.is_a?(Hash) ? (data['Volumes'] || []) : data
rows = vols.map { |v| [v['Name'], v['Driver'], v['Mountpoint']] }
print_table(%w[Name Driver Mountpoint], rows)
end
def list_networks(endpoint_id)
data = @client.get("/api/endpoints/#{endpoint_id}/docker/networks")
rows = data.map { |n| [n['Id'][0, 12], n['Name'], n['Driver'], n['Scope']] }
print_table(%w[ID Name Driver Scope], rows)
end
def resolve_endpoint_id(args)
id = args.shift
unless id
puts "Endpoint ID required. Run 'portainer-cli list endpoints' to see IDs."
exit 1
end
id
end
def endpoint_status(code)
case code
when 1 then green('active')
when 2 then red('inactive')
else dim(code.to_s)
end
end
def stack_status(code)
case code
when 1 then green('active')
when 2 then yellow('inactive')
else dim(code.to_s)
end
end
end
end
end