feat: v3.7.0 — Coolify MCP plugin (30 tools)
New plugin: Coolify deployment management (applications, deployments, servers). 10 plugins, 596 tools, 686 tests passing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
5
plugins/coolify/__init__.py
Normal file
5
plugins/coolify/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Coolify Plugin — AI-driven deployment management for Coolify instances.
|
||||
|
||||
F.17: Phase 1 MVP — Applications, Deployments, Servers (~30 tools)
|
||||
"""
|
||||
269
plugins/coolify/client.py
Normal file
269
plugins/coolify/client.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
Coolify REST API Client
|
||||
|
||||
Handles all HTTP communication with Coolify REST API.
|
||||
Separates API communication from business logic.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
|
||||
|
||||
class CoolifyClient:
|
||||
"""
|
||||
Coolify REST API client for HTTP communication.
|
||||
|
||||
Handles Bearer token authentication, request formatting,
|
||||
and error handling for all Coolify API v1 endpoints.
|
||||
"""
|
||||
|
||||
def __init__(self, site_url: str, token: str):
|
||||
"""
|
||||
Initialize Coolify API client.
|
||||
|
||||
Args:
|
||||
site_url: Coolify instance URL (e.g., https://coolify.example.com)
|
||||
token: API token for Bearer authentication
|
||||
"""
|
||||
self.site_url = site_url.rstrip("/")
|
||||
self.api_base = f"{self.site_url}/api/v1"
|
||||
self.token = token
|
||||
self.logger = logging.getLogger(f"CoolifyClient.{site_url}")
|
||||
|
||||
def _get_headers(self) -> dict[str, str]:
|
||||
"""Get request headers with Bearer authentication."""
|
||||
return {
|
||||
"Authorization": f"Bearer {self.token}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
}
|
||||
|
||||
async def request(
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
params: dict | None = None,
|
||||
json_data: dict | None = None,
|
||||
) -> Any:
|
||||
"""
|
||||
Make authenticated request to Coolify REST API.
|
||||
|
||||
Args:
|
||||
method: HTTP method (GET, POST, PATCH, DELETE)
|
||||
endpoint: API endpoint (without base URL)
|
||||
params: Query parameters
|
||||
json_data: JSON body data
|
||||
|
||||
Returns:
|
||||
API response (dict, list, or None)
|
||||
|
||||
Raises:
|
||||
Exception: On API errors with status code and message
|
||||
"""
|
||||
url = f"{self.api_base}/{endpoint.lstrip('/')}"
|
||||
|
||||
if params:
|
||||
params = {k: v for k, v in params.items() if v is not None}
|
||||
|
||||
if json_data:
|
||||
json_data = {k: v for k, v in json_data.items() if v is not None}
|
||||
|
||||
self.logger.debug(f"{method} {url}")
|
||||
|
||||
async with (
|
||||
aiohttp.ClientSession() as session,
|
||||
session.request(
|
||||
method=method,
|
||||
url=url,
|
||||
params=params,
|
||||
json=json_data,
|
||||
headers=self._get_headers(),
|
||||
) as response,
|
||||
):
|
||||
self.logger.debug(f"Response status: {response.status}")
|
||||
|
||||
if response.status == 204:
|
||||
return {"success": True, "message": "Operation completed successfully"}
|
||||
|
||||
try:
|
||||
response_data = await response.json()
|
||||
except Exception:
|
||||
response_text = await response.text()
|
||||
if response.status >= 400:
|
||||
raise Exception(
|
||||
f"Coolify API error (status {response.status}): {response_text}"
|
||||
)
|
||||
return {"success": True, "message": response_text}
|
||||
|
||||
if response.status >= 400:
|
||||
error_msg = response_data.get("message", "Unknown error")
|
||||
raise Exception(f"Coolify API error (status {response.status}): {error_msg}")
|
||||
|
||||
return response_data
|
||||
|
||||
# --- Applications ---
|
||||
|
||||
async def list_applications(self, tag: str | None = None) -> list[dict]:
|
||||
"""List all applications."""
|
||||
params = {"tag": tag} if tag else {}
|
||||
return await self.request("GET", "applications", params=params)
|
||||
|
||||
async def get_application(self, uuid: str) -> dict:
|
||||
"""Get application by UUID."""
|
||||
return await self.request("GET", f"applications/{uuid}")
|
||||
|
||||
async def create_application_public(self, data: dict) -> dict:
|
||||
"""Create application from public repository."""
|
||||
return await self.request("POST", "applications/public", json_data=data)
|
||||
|
||||
async def create_application_dockerfile(self, data: dict) -> dict:
|
||||
"""Create application from Dockerfile."""
|
||||
return await self.request("POST", "applications/dockerfile", json_data=data)
|
||||
|
||||
async def create_application_docker_image(self, data: dict) -> dict:
|
||||
"""Create application from Docker image."""
|
||||
return await self.request("POST", "applications/dockerimage", json_data=data)
|
||||
|
||||
async def create_application_docker_compose(self, data: dict) -> dict:
|
||||
"""Create application from Docker Compose."""
|
||||
return await self.request("POST", "applications/dockercompose", json_data=data)
|
||||
|
||||
async def update_application(self, uuid: str, data: dict) -> dict:
|
||||
"""Update application by UUID."""
|
||||
return await self.request("PATCH", f"applications/{uuid}", json_data=data)
|
||||
|
||||
async def delete_application(
|
||||
self,
|
||||
uuid: str,
|
||||
delete_configurations: bool = True,
|
||||
delete_volumes: bool = True,
|
||||
docker_cleanup: bool = True,
|
||||
delete_connected_networks: bool = True,
|
||||
) -> dict:
|
||||
"""Delete application by UUID."""
|
||||
params = {
|
||||
"delete_configurations": str(delete_configurations).lower(),
|
||||
"delete_volumes": str(delete_volumes).lower(),
|
||||
"docker_cleanup": str(docker_cleanup).lower(),
|
||||
"delete_connected_networks": str(delete_connected_networks).lower(),
|
||||
}
|
||||
return await self.request("DELETE", f"applications/{uuid}", params=params)
|
||||
|
||||
async def start_application(
|
||||
self, uuid: str, force: bool = False, instant_deploy: bool = False
|
||||
) -> dict:
|
||||
"""Deploy/start application."""
|
||||
params = {}
|
||||
if force:
|
||||
params["force"] = "true"
|
||||
if instant_deploy:
|
||||
params["instant_deploy"] = "true"
|
||||
return await self.request("GET", f"applications/{uuid}/start", params=params)
|
||||
|
||||
async def stop_application(self, uuid: str, docker_cleanup: bool = True) -> dict:
|
||||
"""Stop application."""
|
||||
params = {"docker_cleanup": str(docker_cleanup).lower()}
|
||||
return await self.request("GET", f"applications/{uuid}/stop", params=params)
|
||||
|
||||
async def restart_application(self, uuid: str) -> dict:
|
||||
"""Restart application."""
|
||||
return await self.request("GET", f"applications/{uuid}/restart")
|
||||
|
||||
async def get_application_logs(self, uuid: str, lines: int = 100) -> dict:
|
||||
"""Get application logs."""
|
||||
return await self.request("GET", f"applications/{uuid}/logs", params={"lines": lines})
|
||||
|
||||
# --- Application Environment Variables ---
|
||||
|
||||
async def list_application_envs(self, uuid: str) -> list[dict]:
|
||||
"""List application environment variables."""
|
||||
return await self.request("GET", f"applications/{uuid}/envs")
|
||||
|
||||
async def create_application_env(self, uuid: str, data: dict) -> dict:
|
||||
"""Create application environment variable."""
|
||||
return await self.request("POST", f"applications/{uuid}/envs", json_data=data)
|
||||
|
||||
async def update_application_env(self, uuid: str, data: dict) -> dict:
|
||||
"""Update application environment variable."""
|
||||
return await self.request("PATCH", f"applications/{uuid}/envs", json_data=data)
|
||||
|
||||
async def update_application_envs_bulk(self, uuid: str, data: list[dict]) -> dict:
|
||||
"""Bulk update application environment variables."""
|
||||
return await self.request(
|
||||
"PATCH", f"applications/{uuid}/envs/bulk", json_data={"data": data}
|
||||
)
|
||||
|
||||
async def delete_application_env(self, uuid: str, env_uuid: str) -> dict:
|
||||
"""Delete application environment variable."""
|
||||
return await self.request("DELETE", f"applications/{uuid}/envs/{env_uuid}")
|
||||
|
||||
# --- Deployments ---
|
||||
|
||||
async def list_deployments(self) -> list[dict]:
|
||||
"""List running deployments."""
|
||||
return await self.request("GET", "deployments")
|
||||
|
||||
async def get_deployment(self, uuid: str) -> dict:
|
||||
"""Get deployment by UUID."""
|
||||
return await self.request("GET", f"deployments/{uuid}")
|
||||
|
||||
async def cancel_deployment(self, uuid: str) -> dict:
|
||||
"""Cancel a deployment."""
|
||||
return await self.request("POST", f"deployments/{uuid}/cancel")
|
||||
|
||||
async def deploy(
|
||||
self,
|
||||
tag: str | None = None,
|
||||
uuid: str | None = None,
|
||||
force: bool = False,
|
||||
) -> dict:
|
||||
"""Deploy by tag or UUID."""
|
||||
params = {}
|
||||
if tag:
|
||||
params["tag"] = tag
|
||||
if uuid:
|
||||
params["uuid"] = uuid
|
||||
if force:
|
||||
params["force"] = "true"
|
||||
return await self.request("GET", "deploy", params=params)
|
||||
|
||||
async def list_app_deployments(self, uuid: str, skip: int = 0, take: int = 10) -> list[dict]:
|
||||
"""List deployments for a specific application."""
|
||||
params = {"skip": skip, "take": take}
|
||||
return await self.request("GET", f"deployments/applications/{uuid}", params=params)
|
||||
|
||||
# --- Servers ---
|
||||
|
||||
async def list_servers(self) -> list[dict]:
|
||||
"""List all servers."""
|
||||
return await self.request("GET", "servers")
|
||||
|
||||
async def get_server(self, uuid: str) -> dict:
|
||||
"""Get server by UUID."""
|
||||
return await self.request("GET", f"servers/{uuid}")
|
||||
|
||||
async def create_server(self, data: dict) -> dict:
|
||||
"""Create a new server."""
|
||||
return await self.request("POST", "servers", json_data=data)
|
||||
|
||||
async def update_server(self, uuid: str, data: dict) -> dict:
|
||||
"""Update server by UUID."""
|
||||
return await self.request("PATCH", f"servers/{uuid}", json_data=data)
|
||||
|
||||
async def delete_server(self, uuid: str) -> dict:
|
||||
"""Delete server by UUID."""
|
||||
return await self.request("DELETE", f"servers/{uuid}")
|
||||
|
||||
async def get_server_resources(self, uuid: str) -> list[dict]:
|
||||
"""Get resources on a server."""
|
||||
return await self.request("GET", f"servers/{uuid}/resources")
|
||||
|
||||
async def get_server_domains(self, uuid: str) -> list[dict]:
|
||||
"""Get domains configured on a server."""
|
||||
return await self.request("GET", f"servers/{uuid}/domains")
|
||||
|
||||
async def validate_server(self, uuid: str) -> dict:
|
||||
"""Validate server connectivity and configuration."""
|
||||
return await self.request("GET", f"servers/{uuid}/validate")
|
||||
13
plugins/coolify/handlers/__init__.py
Normal file
13
plugins/coolify/handlers/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""
|
||||
Coolify Plugin Handlers
|
||||
|
||||
All tool handlers for Coolify operations.
|
||||
"""
|
||||
|
||||
from . import applications, deployments, servers
|
||||
|
||||
__all__ = [
|
||||
"applications",
|
||||
"deployments",
|
||||
"servers",
|
||||
]
|
||||
860
plugins/coolify/handlers/applications.py
Normal file
860
plugins/coolify/handlers/applications.py
Normal file
@@ -0,0 +1,860 @@
|
||||
"""Application Handler — manages Coolify applications, lifecycle, and env vars."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.coolify.client import CoolifyClient
|
||||
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator."""
|
||||
return [
|
||||
{
|
||||
"name": "list_applications",
|
||||
"method_name": "list_applications",
|
||||
"description": "List all Coolify applications. Optionally filter by tag name.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Filter by tag name",
|
||||
},
|
||||
},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_application",
|
||||
"method_name": "get_application",
|
||||
"description": "Get details of a specific Coolify application by UUID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_application_public",
|
||||
"method_name": "create_application_public",
|
||||
"description": (
|
||||
"Create a Coolify application from a public Git repository. "
|
||||
"Requires project_uuid, server_uuid, environment, git_repository, "
|
||||
"git_branch, build_pack, and ports_exposes."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_uuid": {
|
||||
"type": "string",
|
||||
"description": "Project UUID",
|
||||
},
|
||||
"server_uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
},
|
||||
"environment_name": {
|
||||
"type": "string",
|
||||
"description": "Environment name (e.g., 'production')",
|
||||
},
|
||||
"git_repository": {
|
||||
"type": "string",
|
||||
"description": "Git repository URL",
|
||||
},
|
||||
"git_branch": {
|
||||
"type": "string",
|
||||
"description": "Git branch name",
|
||||
},
|
||||
"build_pack": {
|
||||
"type": "string",
|
||||
"description": "Build pack type",
|
||||
"enum": ["nixpacks", "static", "dockerfile", "dockercompose"],
|
||||
},
|
||||
"ports_exposes": {
|
||||
"type": "string",
|
||||
"description": "Exposed ports (e.g., '3000')",
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application name",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application description",
|
||||
},
|
||||
"domains": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Comma-separated domain URLs",
|
||||
},
|
||||
"instant_deploy": {
|
||||
"type": "boolean",
|
||||
"description": "Deploy immediately after creation",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"project_uuid",
|
||||
"server_uuid",
|
||||
"environment_name",
|
||||
"git_repository",
|
||||
"git_branch",
|
||||
"build_pack",
|
||||
"ports_exposes",
|
||||
],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "create_application_dockerfile",
|
||||
"method_name": "create_application_dockerfile",
|
||||
"description": (
|
||||
"Create a Coolify application from a Dockerfile (without git). "
|
||||
"Requires project_uuid, server_uuid, environment, and dockerfile content."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_uuid": {
|
||||
"type": "string",
|
||||
"description": "Project UUID",
|
||||
},
|
||||
"server_uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
},
|
||||
"environment_name": {
|
||||
"type": "string",
|
||||
"description": "Environment name",
|
||||
},
|
||||
"dockerfile": {
|
||||
"type": "string",
|
||||
"description": "Dockerfile content",
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application name",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application description",
|
||||
},
|
||||
"domains": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Comma-separated domain URLs",
|
||||
},
|
||||
"ports_exposes": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Exposed ports",
|
||||
},
|
||||
"instant_deploy": {
|
||||
"type": "boolean",
|
||||
"description": "Deploy immediately",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"project_uuid",
|
||||
"server_uuid",
|
||||
"environment_name",
|
||||
"dockerfile",
|
||||
],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "create_application_docker_image",
|
||||
"method_name": "create_application_docker_image",
|
||||
"description": (
|
||||
"Create a Coolify application from a Docker image. "
|
||||
"Requires project_uuid, server_uuid, environment, "
|
||||
"docker_registry_image_name, and ports_exposes."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_uuid": {
|
||||
"type": "string",
|
||||
"description": "Project UUID",
|
||||
},
|
||||
"server_uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
},
|
||||
"environment_name": {
|
||||
"type": "string",
|
||||
"description": "Environment name",
|
||||
},
|
||||
"docker_registry_image_name": {
|
||||
"type": "string",
|
||||
"description": "Docker image name (e.g., 'nginx')",
|
||||
},
|
||||
"ports_exposes": {
|
||||
"type": "string",
|
||||
"description": "Exposed ports",
|
||||
},
|
||||
"docker_registry_image_tag": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Docker image tag (default: latest)",
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application name",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application description",
|
||||
},
|
||||
"domains": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Comma-separated domain URLs",
|
||||
},
|
||||
"instant_deploy": {
|
||||
"type": "boolean",
|
||||
"description": "Deploy immediately",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"project_uuid",
|
||||
"server_uuid",
|
||||
"environment_name",
|
||||
"docker_registry_image_name",
|
||||
"ports_exposes",
|
||||
],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "create_application_compose",
|
||||
"method_name": "create_application_compose",
|
||||
"description": (
|
||||
"Create a Coolify application from Docker Compose. "
|
||||
"Note: Deprecated by Coolify — prefer using services instead."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_uuid": {
|
||||
"type": "string",
|
||||
"description": "Project UUID",
|
||||
},
|
||||
"server_uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
},
|
||||
"environment_name": {
|
||||
"type": "string",
|
||||
"description": "Environment name",
|
||||
},
|
||||
"docker_compose_raw": {
|
||||
"type": "string",
|
||||
"description": "Raw Docker Compose YAML content",
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application name",
|
||||
},
|
||||
"instant_deploy": {
|
||||
"type": "boolean",
|
||||
"description": "Deploy immediately",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": [
|
||||
"project_uuid",
|
||||
"server_uuid",
|
||||
"environment_name",
|
||||
"docker_compose_raw",
|
||||
],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_application",
|
||||
"method_name": "update_application",
|
||||
"description": (
|
||||
"Update a Coolify application settings. Supports name, description, "
|
||||
"domains, build settings, health checks, resource limits, and more."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application name",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Application description",
|
||||
},
|
||||
"domains": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Comma-separated domain URLs",
|
||||
},
|
||||
"git_repository": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Git repository URL",
|
||||
},
|
||||
"git_branch": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Git branch",
|
||||
},
|
||||
"build_pack": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"nixpacks",
|
||||
"static",
|
||||
"dockerfile",
|
||||
"dockercompose",
|
||||
],
|
||||
},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Build pack type",
|
||||
},
|
||||
"install_command": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Install command",
|
||||
},
|
||||
"build_command": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Build command",
|
||||
},
|
||||
"start_command": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Start command",
|
||||
},
|
||||
"ports_exposes": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Exposed ports",
|
||||
},
|
||||
"is_auto_deploy_enabled": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Enable auto-deploy on push",
|
||||
},
|
||||
"instant_deploy": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Deploy instantly",
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_application",
|
||||
"method_name": "delete_application",
|
||||
"description": (
|
||||
"Delete a Coolify application permanently. "
|
||||
"Optionally clean up configs, volumes, and networks."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"delete_configurations": {
|
||||
"type": "boolean",
|
||||
"description": "Delete configurations",
|
||||
"default": True,
|
||||
},
|
||||
"delete_volumes": {
|
||||
"type": "boolean",
|
||||
"description": "Delete volumes",
|
||||
"default": True,
|
||||
},
|
||||
"docker_cleanup": {
|
||||
"type": "boolean",
|
||||
"description": "Run Docker cleanup",
|
||||
"default": True,
|
||||
},
|
||||
"delete_connected_networks": {
|
||||
"type": "boolean",
|
||||
"description": "Delete connected networks",
|
||||
"default": True,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "start_application",
|
||||
"method_name": "start_application",
|
||||
"description": "Deploy/start a Coolify application. Triggers a new deployment.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"force": {
|
||||
"type": "boolean",
|
||||
"description": "Force rebuild without cache",
|
||||
"default": False,
|
||||
},
|
||||
"instant_deploy": {
|
||||
"type": "boolean",
|
||||
"description": "Skip deployment queue",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "stop_application",
|
||||
"method_name": "stop_application",
|
||||
"description": "Stop a running Coolify application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"docker_cleanup": {
|
||||
"type": "boolean",
|
||||
"description": "Prune networks and volumes",
|
||||
"default": True,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "restart_application",
|
||||
"method_name": "restart_application",
|
||||
"description": "Restart a Coolify application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "get_application_logs",
|
||||
"method_name": "get_application_logs",
|
||||
"description": "Get logs for a Coolify application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"lines": {
|
||||
"type": "integer",
|
||||
"description": "Number of log lines to retrieve",
|
||||
"default": 100,
|
||||
"minimum": 1,
|
||||
"maximum": 10000,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "list_application_envs",
|
||||
"method_name": "list_application_envs",
|
||||
"description": "List environment variables for a Coolify application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_application_env",
|
||||
"method_name": "create_application_env",
|
||||
"description": "Create an environment variable for a Coolify application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Environment variable key",
|
||||
"minLength": 1,
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "Environment variable value",
|
||||
},
|
||||
"is_preview": {
|
||||
"type": "boolean",
|
||||
"description": "Preview deployment only",
|
||||
"default": False,
|
||||
},
|
||||
"is_literal": {
|
||||
"type": "boolean",
|
||||
"description": "Treat as literal (no variable interpolation)",
|
||||
"default": False,
|
||||
},
|
||||
"is_multiline": {
|
||||
"type": "boolean",
|
||||
"description": "Allow multiline value",
|
||||
"default": False,
|
||||
},
|
||||
"is_shown_once": {
|
||||
"type": "boolean",
|
||||
"description": "Show value only once",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
"required": ["uuid", "key", "value"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_application_env",
|
||||
"method_name": "update_application_env",
|
||||
"description": "Update an environment variable for a Coolify application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "Environment variable key",
|
||||
"minLength": 1,
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "New value",
|
||||
},
|
||||
"is_preview": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Preview deployment only",
|
||||
},
|
||||
"is_literal": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Treat as literal",
|
||||
},
|
||||
"is_multiline": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Allow multiline",
|
||||
},
|
||||
"is_shown_once": {
|
||||
"anyOf": [{"type": "boolean"}, {"type": "null"}],
|
||||
"description": "Show only once",
|
||||
},
|
||||
},
|
||||
"required": ["uuid", "key", "value"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "update_application_envs_bulk",
|
||||
"method_name": "update_application_envs_bulk",
|
||||
"description": "Bulk update environment variables for a Coolify application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"description": "Array of env var objects with key, value, and flags",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"key": {"type": "string"},
|
||||
"value": {"type": "string"},
|
||||
"is_preview": {"type": "boolean"},
|
||||
"is_literal": {"type": "boolean"},
|
||||
"is_multiline": {"type": "boolean"},
|
||||
"is_shown_once": {"type": "boolean"},
|
||||
},
|
||||
"required": ["key", "value"],
|
||||
},
|
||||
},
|
||||
},
|
||||
"required": ["uuid", "data"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_application_env",
|
||||
"method_name": "delete_application_env",
|
||||
"description": "Delete an environment variable from a Coolify application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"env_uuid": {
|
||||
"type": "string",
|
||||
"description": "Environment variable UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid", "env_uuid"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# --- Handler Functions ---
|
||||
|
||||
|
||||
async def list_applications(client: CoolifyClient, tag: str | None = None) -> str:
|
||||
"""List all applications."""
|
||||
apps = await client.list_applications(tag=tag)
|
||||
result = {"success": True, "count": len(apps), "applications": apps}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def get_application(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Get application details."""
|
||||
app = await client.get_application(uuid)
|
||||
result = {"success": True, "application": app}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def create_application_public(client: CoolifyClient, **kwargs) -> str:
|
||||
"""Create application from public repository."""
|
||||
app = await client.create_application_public(kwargs)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": "Application created successfully",
|
||||
"application": app,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def create_application_dockerfile(client: CoolifyClient, **kwargs) -> str:
|
||||
"""Create application from Dockerfile."""
|
||||
app = await client.create_application_dockerfile(kwargs)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": "Application created from Dockerfile",
|
||||
"application": app,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def create_application_docker_image(client: CoolifyClient, **kwargs) -> str:
|
||||
"""Create application from Docker image."""
|
||||
app = await client.create_application_docker_image(kwargs)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": "Application created from Docker image",
|
||||
"application": app,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def create_application_compose(client: CoolifyClient, **kwargs) -> str:
|
||||
"""Create application from Docker Compose."""
|
||||
app = await client.create_application_docker_compose(kwargs)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": "Application created from Docker Compose",
|
||||
"application": app,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def update_application(client: CoolifyClient, uuid: str, **kwargs) -> str:
|
||||
"""Update application settings."""
|
||||
data = {k: v for k, v in kwargs.items() if v is not None and k != "uuid"}
|
||||
app = await client.update_application(uuid, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Application '{uuid}' updated successfully",
|
||||
"application": app,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def delete_application(
|
||||
client: CoolifyClient,
|
||||
uuid: str,
|
||||
delete_configurations: bool = True,
|
||||
delete_volumes: bool = True,
|
||||
docker_cleanup: bool = True,
|
||||
delete_connected_networks: bool = True,
|
||||
) -> str:
|
||||
"""Delete an application."""
|
||||
result_data = await client.delete_application(
|
||||
uuid,
|
||||
delete_configurations=delete_configurations,
|
||||
delete_volumes=delete_volumes,
|
||||
docker_cleanup=docker_cleanup,
|
||||
delete_connected_networks=delete_connected_networks,
|
||||
)
|
||||
result = {"success": True, "message": f"Application '{uuid}' deleted", "data": result_data}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def start_application(
|
||||
client: CoolifyClient,
|
||||
uuid: str,
|
||||
force: bool = False,
|
||||
instant_deploy: bool = False,
|
||||
) -> str:
|
||||
"""Deploy/start application."""
|
||||
result_data = await client.start_application(uuid, force=force, instant_deploy=instant_deploy)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Application '{uuid}' deployment queued",
|
||||
"data": result_data,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def stop_application(client: CoolifyClient, uuid: str, docker_cleanup: bool = True) -> str:
|
||||
"""Stop application."""
|
||||
result_data = await client.stop_application(uuid, docker_cleanup=docker_cleanup)
|
||||
result = {"success": True, "message": f"Application '{uuid}' stopping", "data": result_data}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def restart_application(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Restart application."""
|
||||
result_data = await client.restart_application(uuid)
|
||||
result = {"success": True, "message": f"Application '{uuid}' restarting", "data": result_data}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def get_application_logs(client: CoolifyClient, uuid: str, lines: int = 100) -> str:
|
||||
"""Get application logs."""
|
||||
logs = await client.get_application_logs(uuid, lines=lines)
|
||||
result = {"success": True, "logs": logs}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def list_application_envs(client: CoolifyClient, uuid: str) -> str:
|
||||
"""List application environment variables."""
|
||||
envs = await client.list_application_envs(uuid)
|
||||
result = {"success": True, "count": len(envs), "envs": envs}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def create_application_env(
|
||||
client: CoolifyClient,
|
||||
uuid: str,
|
||||
key: str,
|
||||
value: str,
|
||||
is_preview: bool = False,
|
||||
is_literal: bool = False,
|
||||
is_multiline: bool = False,
|
||||
is_shown_once: bool = False,
|
||||
) -> str:
|
||||
"""Create application environment variable."""
|
||||
data = {
|
||||
"key": key,
|
||||
"value": value,
|
||||
"is_preview": is_preview,
|
||||
"is_literal": is_literal,
|
||||
"is_multiline": is_multiline,
|
||||
"is_shown_once": is_shown_once,
|
||||
}
|
||||
result_data = await client.create_application_env(uuid, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Env var '{key}' created",
|
||||
"data": result_data,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def update_application_env(
|
||||
client: CoolifyClient,
|
||||
uuid: str,
|
||||
key: str,
|
||||
value: str,
|
||||
is_preview: bool | None = None,
|
||||
is_literal: bool | None = None,
|
||||
is_multiline: bool | None = None,
|
||||
is_shown_once: bool | None = None,
|
||||
) -> str:
|
||||
"""Update application environment variable."""
|
||||
data = {"key": key, "value": value}
|
||||
if is_preview is not None:
|
||||
data["is_preview"] = is_preview
|
||||
if is_literal is not None:
|
||||
data["is_literal"] = is_literal
|
||||
if is_multiline is not None:
|
||||
data["is_multiline"] = is_multiline
|
||||
if is_shown_once is not None:
|
||||
data["is_shown_once"] = is_shown_once
|
||||
result_data = await client.update_application_env(uuid, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Env var '{key}' updated",
|
||||
"data": result_data,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def update_application_envs_bulk(client: CoolifyClient, uuid: str, data: list[dict]) -> str:
|
||||
"""Bulk update application environment variables."""
|
||||
result_data = await client.update_application_envs_bulk(uuid, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Bulk update {len(data)} env vars",
|
||||
"data": result_data,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def delete_application_env(client: CoolifyClient, uuid: str, env_uuid: str) -> str:
|
||||
"""Delete application environment variable."""
|
||||
await client.delete_application_env(uuid, env_uuid)
|
||||
result = {"success": True, "message": f"Env var '{env_uuid}' deleted"}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
162
plugins/coolify/handlers/deployments.py
Normal file
162
plugins/coolify/handlers/deployments.py
Normal file
@@ -0,0 +1,162 @@
|
||||
"""Deployment Handler — manages Coolify deployments."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.coolify.client import CoolifyClient
|
||||
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator."""
|
||||
return [
|
||||
{
|
||||
"name": "list_deployments",
|
||||
"method_name": "list_deployments",
|
||||
"description": "List all running deployments on the Coolify instance.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_deployment",
|
||||
"method_name": "get_deployment",
|
||||
"description": "Get details of a specific deployment by UUID.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Deployment UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "cancel_deployment",
|
||||
"method_name": "cancel_deployment",
|
||||
"description": "Cancel a running deployment.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Deployment UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "deploy",
|
||||
"method_name": "deploy",
|
||||
"description": (
|
||||
"Trigger deployment by tag name or resource UUID. "
|
||||
"Can deploy multiple resources at once using comma-separated values."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"tag": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Tag name(s), comma-separated",
|
||||
},
|
||||
"uuid": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Resource UUID(s), comma-separated",
|
||||
},
|
||||
"force": {
|
||||
"type": "boolean",
|
||||
"description": "Force rebuild without cache",
|
||||
"default": False,
|
||||
},
|
||||
},
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "list_app_deployments",
|
||||
"method_name": "list_app_deployments",
|
||||
"description": "List deployment history for a specific application.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Application UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"skip": {
|
||||
"type": "integer",
|
||||
"description": "Number of deployments to skip",
|
||||
"default": 0,
|
||||
"minimum": 0,
|
||||
},
|
||||
"take": {
|
||||
"type": "integer",
|
||||
"description": "Number of deployments to return",
|
||||
"default": 10,
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# --- Handler Functions ---
|
||||
|
||||
|
||||
async def list_deployments(client: CoolifyClient) -> str:
|
||||
"""List running deployments."""
|
||||
deployments = await client.list_deployments()
|
||||
result = {"success": True, "count": len(deployments), "deployments": deployments}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def get_deployment(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Get deployment details."""
|
||||
deployment = await client.get_deployment(uuid)
|
||||
result = {"success": True, "deployment": deployment}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def cancel_deployment(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Cancel a deployment."""
|
||||
result_data = await client.cancel_deployment(uuid)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Deployment '{uuid}' cancelled",
|
||||
"data": result_data,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def deploy(
|
||||
client: CoolifyClient,
|
||||
tag: str | None = None,
|
||||
uuid: str | None = None,
|
||||
force: bool = False,
|
||||
) -> str:
|
||||
"""Deploy by tag or UUID."""
|
||||
result_data = await client.deploy(tag=tag, uuid=uuid, force=force)
|
||||
result = {"success": True, "message": "Deployment triggered", "data": result_data}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def list_app_deployments(
|
||||
client: CoolifyClient, uuid: str, skip: int = 0, take: int = 10
|
||||
) -> str:
|
||||
"""List deployment history for an application."""
|
||||
deployments = await client.list_app_deployments(uuid, skip=skip, take=take)
|
||||
result = {"success": True, "count": len(deployments), "deployments": deployments}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
286
plugins/coolify/handlers/servers.py
Normal file
286
plugins/coolify/handlers/servers.py
Normal file
@@ -0,0 +1,286 @@
|
||||
"""Server Handler — manages Coolify servers."""
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from plugins.coolify.client import CoolifyClient
|
||||
|
||||
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""Return tool specifications for ToolGenerator."""
|
||||
return [
|
||||
{
|
||||
"name": "list_servers",
|
||||
"method_name": "list_servers",
|
||||
"description": "List all servers registered in the Coolify instance.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_server",
|
||||
"method_name": "get_server",
|
||||
"description": "Get details of a specific server by UUID, including settings.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "create_server",
|
||||
"method_name": "create_server",
|
||||
"description": (
|
||||
"Register a new server in Coolify. "
|
||||
"Requires name, IP, port, user, and a private key UUID for SSH access."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Server name",
|
||||
"minLength": 1,
|
||||
},
|
||||
"ip": {
|
||||
"type": "string",
|
||||
"description": "Server IP address",
|
||||
"minLength": 1,
|
||||
},
|
||||
"port": {
|
||||
"type": "integer",
|
||||
"description": "SSH port",
|
||||
"default": 22,
|
||||
},
|
||||
"user": {
|
||||
"type": "string",
|
||||
"description": "SSH user",
|
||||
"default": "root",
|
||||
},
|
||||
"private_key_uuid": {
|
||||
"type": "string",
|
||||
"description": "Private key UUID for SSH authentication",
|
||||
"minLength": 1,
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Server description",
|
||||
},
|
||||
"is_build_server": {
|
||||
"type": "boolean",
|
||||
"description": "Use as build server",
|
||||
"default": False,
|
||||
},
|
||||
"instant_validate": {
|
||||
"type": "boolean",
|
||||
"description": "Validate server immediately after creation",
|
||||
"default": False,
|
||||
},
|
||||
"proxy_type": {
|
||||
"type": "string",
|
||||
"description": "Proxy type for the server",
|
||||
"enum": ["traefik", "caddy", "none"],
|
||||
"default": "traefik",
|
||||
},
|
||||
},
|
||||
"required": ["name", "ip", "private_key_uuid"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "update_server",
|
||||
"method_name": "update_server",
|
||||
"description": "Update server configuration.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Server name",
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Server description",
|
||||
},
|
||||
"ip": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "Server IP address",
|
||||
},
|
||||
"port": {
|
||||
"anyOf": [{"type": "integer"}, {"type": "null"}],
|
||||
"description": "SSH port",
|
||||
},
|
||||
"user": {
|
||||
"anyOf": [{"type": "string"}, {"type": "null"}],
|
||||
"description": "SSH user",
|
||||
},
|
||||
"proxy_type": {
|
||||
"anyOf": [
|
||||
{"type": "string", "enum": ["traefik", "caddy", "none"]},
|
||||
{"type": "null"},
|
||||
],
|
||||
"description": "Proxy type",
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "write",
|
||||
},
|
||||
{
|
||||
"name": "delete_server",
|
||||
"method_name": "delete_server",
|
||||
"description": "Delete a server from Coolify. This cannot be undone!",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "admin",
|
||||
},
|
||||
{
|
||||
"name": "get_server_resources",
|
||||
"method_name": "get_server_resources",
|
||||
"description": (
|
||||
"Get all resources (applications, databases, services) "
|
||||
"deployed on a specific server."
|
||||
),
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "get_server_domains",
|
||||
"method_name": "get_server_domains",
|
||||
"description": "Get all domains configured on a specific server.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
{
|
||||
"name": "validate_server",
|
||||
"method_name": "validate_server",
|
||||
"description": "Validate server connectivity and configuration.",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"uuid": {
|
||||
"type": "string",
|
||||
"description": "Server UUID",
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"required": ["uuid"],
|
||||
},
|
||||
"scope": "read",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# --- Handler Functions ---
|
||||
|
||||
|
||||
async def list_servers(client: CoolifyClient) -> str:
|
||||
"""List all servers."""
|
||||
servers = await client.list_servers()
|
||||
result = {"success": True, "count": len(servers), "servers": servers}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def get_server(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Get server details."""
|
||||
server = await client.get_server(uuid)
|
||||
result = {"success": True, "server": server}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def create_server(client: CoolifyClient, **kwargs) -> str:
|
||||
"""Create a new server."""
|
||||
server = await client.create_server(kwargs)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": "Server created successfully",
|
||||
"server": server,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def update_server(client: CoolifyClient, uuid: str, **kwargs) -> str:
|
||||
"""Update server configuration."""
|
||||
data = {k: v for k, v in kwargs.items() if v is not None and k != "uuid"}
|
||||
server = await client.update_server(uuid, data)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": f"Server '{uuid}' updated",
|
||||
"server": server,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def delete_server(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Delete a server."""
|
||||
await client.delete_server(uuid)
|
||||
result = {"success": True, "message": f"Server '{uuid}' deleted"}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def get_server_resources(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Get server resources."""
|
||||
resources = await client.get_server_resources(uuid)
|
||||
result = {"success": True, "count": len(resources), "resources": resources}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def get_server_domains(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Get server domains."""
|
||||
domains = await client.get_server_domains(uuid)
|
||||
result = {"success": True, "domains": domains}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
async def validate_server(client: CoolifyClient, uuid: str) -> str:
|
||||
"""Validate server."""
|
||||
result_data = await client.validate_server(uuid)
|
||||
result = {
|
||||
"success": True,
|
||||
"message": "Server validation started",
|
||||
"data": result_data,
|
||||
}
|
||||
return json.dumps(result, indent=2, ensure_ascii=False)
|
||||
106
plugins/coolify/plugin.py
Normal file
106
plugins/coolify/plugin.py
Normal file
@@ -0,0 +1,106 @@
|
||||
"""
|
||||
Coolify Plugin — Clean Architecture
|
||||
|
||||
AI-driven deployment management for Coolify instances.
|
||||
Modular handlers for applications, deployments, and servers.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from plugins.base import BasePlugin
|
||||
from plugins.coolify import handlers
|
||||
from plugins.coolify.client import CoolifyClient
|
||||
|
||||
|
||||
class CoolifyPlugin(BasePlugin):
|
||||
"""
|
||||
Coolify project plugin — Clean architecture.
|
||||
|
||||
Provides Coolify deployment management capabilities including:
|
||||
- Application management (CRUD, lifecycle, env vars, logs)
|
||||
- Deployment control (list, cancel, deploy by tag/UUID)
|
||||
- Server management (CRUD, resources, domains, validation)
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def get_plugin_name() -> str:
|
||||
"""Return plugin type identifier."""
|
||||
return "coolify"
|
||||
|
||||
@staticmethod
|
||||
def get_required_config_keys() -> list[str]:
|
||||
"""Return required configuration keys."""
|
||||
return ["url", "token"]
|
||||
|
||||
def __init__(self, config: dict[str, Any], project_id: str | None = None):
|
||||
"""
|
||||
Initialize Coolify plugin with handlers.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary containing:
|
||||
- url: Coolify instance URL
|
||||
- token: API token for Bearer authentication
|
||||
project_id: Optional project ID (auto-generated if not provided)
|
||||
"""
|
||||
super().__init__(config, project_id=project_id)
|
||||
|
||||
self.client = CoolifyClient(
|
||||
site_url=config["url"],
|
||||
token=config["token"],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_tool_specifications() -> list[dict[str, Any]]:
|
||||
"""
|
||||
Return all tool specifications for ToolGenerator.
|
||||
|
||||
Returns:
|
||||
List of tool specification dictionaries
|
||||
"""
|
||||
specs = []
|
||||
|
||||
specs.extend(handlers.applications.get_tool_specifications())
|
||||
specs.extend(handlers.deployments.get_tool_specifications())
|
||||
specs.extend(handlers.servers.get_tool_specifications())
|
||||
|
||||
return specs
|
||||
|
||||
def __getattr__(self, name: str):
|
||||
"""
|
||||
Dynamically delegate method calls to appropriate handlers.
|
||||
|
||||
Args:
|
||||
name: Method name being called
|
||||
|
||||
Returns:
|
||||
Handler function from the appropriate handler module
|
||||
"""
|
||||
handler_modules = [
|
||||
handlers.applications,
|
||||
handlers.deployments,
|
||||
handlers.servers,
|
||||
]
|
||||
|
||||
for module in handler_modules:
|
||||
if hasattr(module, name):
|
||||
func = getattr(module, name)
|
||||
|
||||
async def wrapper(_func=func, **kwargs):
|
||||
return await _func(self.client, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
|
||||
|
||||
async def health_check(self) -> dict[str, Any]:
|
||||
"""
|
||||
Check if Coolify instance is accessible.
|
||||
|
||||
Returns:
|
||||
Dict containing health check result
|
||||
"""
|
||||
try:
|
||||
await self.client.request("GET", "version")
|
||||
return {"healthy": True, "message": "Coolify instance is accessible"}
|
||||
except Exception as e:
|
||||
return {"healthy": False, "message": f"Coolify health check failed: {str(e)}"}
|
||||
1
plugins/coolify/schemas/__init__.py
Normal file
1
plugins/coolify/schemas/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Coolify Plugin Schemas"""
|
||||
55
plugins/coolify/schemas/common.py
Normal file
55
plugins/coolify/schemas/common.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""
|
||||
Common Pydantic Schemas for Coolify Plugin
|
||||
|
||||
Shared validation schemas used across Coolify handlers.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
class Site(BaseModel):
|
||||
"""Coolify site configuration."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
site_id: str = Field(..., description="Site identifier")
|
||||
url: str = Field(..., description="Coolify instance URL")
|
||||
token: str = Field(..., description="API token for Bearer authentication")
|
||||
alias: str | None = Field(None, description="Site alias")
|
||||
|
||||
|
||||
class PaginationParams(BaseModel):
|
||||
"""Pagination parameters for list endpoints."""
|
||||
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
skip: int = Field(default=0, ge=0, description="Number of items to skip")
|
||||
take: int = Field(default=10, ge=1, le=100, description="Number of items to take")
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Standard error response."""
|
||||
|
||||
error: bool = Field(default=True)
|
||||
message: str = Field(..., description="Error message")
|
||||
details: dict[str, Any] | None = Field(None, description="Additional error details")
|
||||
|
||||
|
||||
class SuccessResponse(BaseModel):
|
||||
"""Standard success response."""
|
||||
|
||||
success: bool = Field(default=True)
|
||||
message: str = Field(..., description="Success message")
|
||||
data: dict[str, Any] | None = Field(None, description="Response data")
|
||||
|
||||
|
||||
class CoolifyTimestamps(BaseModel):
|
||||
"""Common timestamp fields."""
|
||||
|
||||
model_config = ConfigDict(extra="allow")
|
||||
|
||||
created_at: datetime | None = None
|
||||
updated_at: datetime | None = None
|
||||
Reference in New Issue
Block a user