import axios, { AxiosInstance, AxiosError } from 'axios';
import { config, Config } from './config.js';
export interface PortainerContainer {
Id: string;
Names: string[];
Image: string;
State: string;
Status: string;
Ports?: any[];
}
export interface PortainerImage {
Id: string;
RepoTags?: string[];
Size: number;
Created: number;
}
export interface PortainerNetwork {
Id: string;
Name: string;
Driver: string;
Scope: string;
}
export interface PortainerEndpoint {
Id: number;
Name: string;
URL: string;
Type: number;
}
export interface PortainerVolume {
Name: string;
Driver: string;
Mountpoint: string;
}
export class PortainerClient {
private client: AxiosInstance;
private endpointId: number;
constructor(customConfig?: Partial<Config>) {
const cfg = customConfig ? { ...config, ...customConfig } : config;
if (!cfg.portainerUrl || !cfg.portainerApiKey) {
throw new Error(
'Portainer not configured. Use the "set_portainer_config" tool to set the URL and API key:\n\n' +
'Example: set_portainer_config(url="https://portainer.onlitec.com.br", api_key="ptr_your_key")'
);
}
this.endpointId = cfg.portainerEndpointId;
this.client = axios.create({
baseURL: cfg.portainerUrl,
headers: {
'X-API-Key': cfg.portainerApiKey,
},
});
}
private handleError(error: unknown): never {
if (axios.isAxiosError(error)) {
const axiosError = error as AxiosError;
if (axiosError.response) {
// If it's a 404 for a specific endpoint, it might still return valuable info for other calls,
// but for listEndpoints it shouldn't happen unless the base URL is wrong.
throw new Error(
`Portainer API error: ${axiosError.response.status} - ${JSON.stringify(axiosError.response.data)}`
);
} else if (axiosError.request) {
throw new Error('No response from Portainer API. Check if Portainer is running.');
}
}
throw new Error(`Unexpected error: ${error}`);
}
// System operations
async listEndpoints(): Promise<PortainerEndpoint[]> {
try {
const response = await this.client.get('/api/endpoints');
return response.data;
} catch (error) {
this.handleError(error);
}
}
// Container operations
async listContainers(all = false): Promise<PortainerContainer[]> {
try {
const response = await this.client.get(
`/api/endpoints/${this.endpointId}/docker/containers/json`,
{ params: { all } }
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async getContainer(id: string): Promise<any> {
try {
const response = await this.client.get(
`/api/endpoints/${this.endpointId}/docker/containers/${id}/json`
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async startContainer(id: string): Promise<void> {
try {
await this.client.post(
`/api/endpoints/${this.endpointId}/docker/containers/${id}/start`
);
} catch (error) {
this.handleError(error);
}
}
async stopContainer(id: string, timeout = 10): Promise<void> {
try {
await this.client.post(
`/api/endpoints/${this.endpointId}/docker/containers/${id}/stop`,
null,
{ params: { t: timeout } }
);
} catch (error) {
this.handleError(error);
}
}
async restartContainer(id: string, timeout = 10): Promise<void> {
try {
await this.client.post(
`/api/endpoints/${this.endpointId}/docker/containers/${id}/restart`,
null,
{ params: { t: timeout } }
);
} catch (error) {
this.handleError(error);
}
}
async removeContainer(id: string, force = false, volumes = false): Promise<void> {
try {
await this.client.delete(
`/api/endpoints/${this.endpointId}/docker/containers/${id}`,
{ params: { force, v: volumes } }
);
} catch (error) {
this.handleError(error);
}
}
async createContainer(config: any): Promise<any> {
try {
const response = await this.client.post(
`/api/endpoints/${this.endpointId}/docker/containers/create`,
config
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
// Image operations
async listImages(all = false): Promise<PortainerImage[]> {
try {
const response = await this.client.get(
`/api/endpoints/${this.endpointId}/docker/images/json`,
{ params: { all } }
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async getImage(id: string): Promise<any> {
try {
const response = await this.client.get(
`/api/endpoints/${this.endpointId}/docker/images/${id}/json`
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async pullImage(image: string): Promise<any> {
try {
const response = await this.client.post(
`/api/endpoints/${this.endpointId}/docker/images/create`,
null,
{ params: { fromImage: image } }
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async removeImage(id: string, force = false): Promise<void> {
try {
await this.client.delete(
`/api/endpoints/${this.endpointId}/docker/images/${id}`,
{ params: { force } }
);
} catch (error) {
this.handleError(error);
}
}
// Network operations
async listNetworks(): Promise<PortainerNetwork[]> {
try {
const response = await this.client.get(
`/api/endpoints/${this.endpointId}/docker/networks`
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async createNetwork(networkConfig: any): Promise<any> {
try {
const response = await this.client.post(
`/api/endpoints/${this.endpointId}/docker/networks/create`,
networkConfig
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async removeNetwork(id: string): Promise<void> {
try {
await this.client.delete(
`/api/endpoints/${this.endpointId}/docker/networks/${id}`
);
} catch (error) {
this.handleError(error);
}
}
// Volume operations
async listVolumes(): Promise<any> {
try {
const response = await this.client.get(
`/api/endpoints/${this.endpointId}/docker/volumes`
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async createVolume(volumeConfig: any): Promise<any> {
try {
const response = await this.client.post(
`/api/endpoints/${this.endpointId}/docker/volumes/create`,
volumeConfig
);
return response.data;
} catch (error) {
this.handleError(error);
}
}
async removeVolume(name: string, force = false): Promise<void> {
try {
await this.client.delete(
`/api/endpoints/${this.endpointId}/docker/volumes/${name}`,
{ params: { force } }
);
} catch (error) {
this.handleError(error);
}
}
}