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:
@@ -0,0 +1,116 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'base'
|
||||
require 'optparse'
|
||||
|
||||
module PortainerCli
|
||||
module Commands
|
||||
class Create
|
||||
include Base
|
||||
|
||||
def initialize(config, client)
|
||||
@config = config
|
||||
@client = client
|
||||
end
|
||||
|
||||
def run(args)
|
||||
require_config!(@config)
|
||||
sub = args.shift&.downcase
|
||||
|
||||
case sub
|
||||
when 'stack'
|
||||
create_stack(args)
|
||||
when 'container'
|
||||
create_container(args)
|
||||
else
|
||||
puts "Usage: portainer-cli create <stack|container> [options]"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# portainer-cli create stack --name mystack --endpoint 1 --file docker-compose.yml
|
||||
# portainer-cli create stack --name mystack --endpoint 1 --compose "version: '3'..."
|
||||
def create_stack(args)
|
||||
opts = { env: [] }
|
||||
OptionParser.new do |o|
|
||||
o.on('--name NAME', 'Stack name') { |v| opts[:name] = v }
|
||||
o.on('--endpoint ID', 'Endpoint ID') { |v| opts[:endpoint] = v }
|
||||
o.on('--file FILE', 'docker-compose.yml path') { |v| opts[:file] = v }
|
||||
o.on('--compose COMPOSE', 'Inline compose string') { |v| opts[:compose] = v }
|
||||
o.on('--env KEY=VAL', 'Environment variable') { |v| opts[:env] << v }
|
||||
end.parse!(args)
|
||||
|
||||
error("--name is required") unless opts[:name]
|
||||
error("--endpoint is required") unless opts[:endpoint]
|
||||
|
||||
compose_content = if opts[:file]
|
||||
error("File not found: #{opts[:file]}") unless File.exist?(opts[:file])
|
||||
File.read(opts[:file])
|
||||
elsif opts[:compose]
|
||||
opts[:compose]
|
||||
else
|
||||
error("Either --file or --compose is required")
|
||||
end
|
||||
|
||||
env_vars = opts[:env].map do |pair|
|
||||
k, v = pair.split('=', 2)
|
||||
{ 'name' => k, 'value' => v.to_s }
|
||||
end
|
||||
|
||||
body = {
|
||||
'name' => opts[:name],
|
||||
'stackFileContent' => compose_content,
|
||||
'env' => env_vars
|
||||
}
|
||||
|
||||
result = @client.post(
|
||||
"/api/stacks/create/standalone/string?endpointId=#{opts[:endpoint]}",
|
||||
body
|
||||
)
|
||||
puts green("Stack created: #{result['Name']} (ID: #{result['Id']})")
|
||||
end
|
||||
|
||||
# portainer-cli create container --endpoint 1 --name web --image nginx:alpine --port 8080:80 --env FOO=bar
|
||||
def create_container(args)
|
||||
opts = { ports: [], env: [] }
|
||||
OptionParser.new do |o|
|
||||
o.on('--endpoint ID', 'Endpoint ID') { |v| opts[:endpoint] = v }
|
||||
o.on('--name NAME', 'Container name') { |v| opts[:name] = v }
|
||||
o.on('--image IMAGE', 'Docker image') { |v| opts[:image] = v }
|
||||
o.on('--port HOST:CONT', 'Port mapping') { |v| opts[:ports] << v }
|
||||
o.on('--env KEY=VAL', 'Environment variable') { |v| opts[:env] << v }
|
||||
o.on('--cmd CMD', 'Command to run') { |v| opts[:cmd] = v }
|
||||
end.parse!(args)
|
||||
|
||||
error("--endpoint is required") unless opts[:endpoint]
|
||||
error("--image is required") unless opts[:image]
|
||||
|
||||
port_bindings = {}
|
||||
exposed_ports = {}
|
||||
opts[:ports].each do |mapping|
|
||||
host_port, cont_port = mapping.split(':', 2)
|
||||
cont_key = "#{cont_port}/tcp"
|
||||
exposed_ports[cont_key] = {}
|
||||
port_bindings[cont_key] = [{ 'HostPort' => host_port }]
|
||||
end
|
||||
|
||||
body = {
|
||||
'Image' => opts[:image],
|
||||
'Env' => opts[:env],
|
||||
'ExposedPorts' => exposed_ports,
|
||||
'HostConfig' => { 'PortBindings' => port_bindings }
|
||||
}
|
||||
body['Cmd'] = opts[:cmd].split(' ') 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)
|
||||
container_id = result['Id']
|
||||
|
||||
@client.post("/api/endpoints/#{opts[:endpoint]}/docker/containers/#{container_id}/start")
|
||||
puts green("Container started: #{opts[:name] || container_id[0, 12]}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user