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,106 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'net/http'
|
||||
require 'json'
|
||||
require 'uri'
|
||||
require 'openssl'
|
||||
|
||||
module PortainerCli
|
||||
class Client
|
||||
class ApiError < StandardError
|
||||
attr_reader :code
|
||||
def initialize(msg, code)
|
||||
super(msg)
|
||||
@code = code
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
@base = URI.parse(config.url.chomp('/'))
|
||||
end
|
||||
|
||||
def get(path, params = {})
|
||||
request(:get, path, params: params)
|
||||
end
|
||||
|
||||
def post(path, body = nil)
|
||||
request(:post, path, body: body)
|
||||
end
|
||||
|
||||
def delete(path)
|
||||
request(:delete, path)
|
||||
end
|
||||
|
||||
# Returns the raw websocket URL (ws:// or wss://) for exec sessions
|
||||
def websocket_url(path, params = {})
|
||||
uri = ws_uri(path)
|
||||
uri.query = URI.encode_www_form(params) unless params.empty?
|
||||
uri.to_s
|
||||
end
|
||||
|
||||
def auth_header
|
||||
@config.auth_header
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def request(method, path, params: {}, body: nil)
|
||||
uri = http_uri(path)
|
||||
uri.query = URI.encode_www_form(params) unless params.empty?
|
||||
|
||||
http = build_http(uri)
|
||||
req = build_request(method, uri, body)
|
||||
|
||||
resp = http.request(req)
|
||||
handle_response(resp)
|
||||
end
|
||||
|
||||
def build_http(uri)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
if uri.scheme == 'https'
|
||||
http.use_ssl = true
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
end
|
||||
http.open_timeout = 10
|
||||
http.read_timeout = 30
|
||||
http
|
||||
end
|
||||
|
||||
def build_request(method, uri, body)
|
||||
klass = {
|
||||
get: Net::HTTP::Get,
|
||||
post: Net::HTTP::Post,
|
||||
delete: Net::HTTP::Delete
|
||||
}.fetch(method)
|
||||
|
||||
req = klass.new(uri)
|
||||
req['Content-Type'] = 'application/json'
|
||||
req['Accept'] = 'application/json'
|
||||
@config.auth_header.each { |k, v| req[k] = v }
|
||||
|
||||
req.body = JSON.generate(body) if body
|
||||
req
|
||||
end
|
||||
|
||||
def handle_response(resp)
|
||||
code = resp.code.to_i
|
||||
raise ApiError.new("HTTP #{code}: #{resp.body}", code) if code >= 400
|
||||
|
||||
return nil if resp.body.nil? || resp.body.empty?
|
||||
|
||||
JSON.parse(resp.body)
|
||||
rescue JSON::ParserError
|
||||
resp.body
|
||||
end
|
||||
|
||||
def http_uri(path)
|
||||
URI.parse("#{@base}#{path}")
|
||||
end
|
||||
|
||||
def ws_uri(path)
|
||||
scheme = @base.scheme == 'https' ? 'wss' : 'ws'
|
||||
URI.parse("#{scheme}://#{@base.host}:#{@base.port}#{path}")
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user