cli/pkg/deploy/coolify/client.go
Claude 52d358daa2 refactor: rename module from github.com/host-uk/core to forge.lthn.ai/core/cli
Move module identity to our own Forgejo instance. All import paths
updated across 434 Go files, sub-module go.mod files, and go.work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 05:53:52 +00:00

219 lines
6.1 KiB
Go

package coolify
import (
"context"
"encoding/json"
"fmt"
"os"
"sync"
"forge.lthn.ai/core/cli/pkg/deploy/python"
)
// Client wraps the Python CoolifyClient for Go usage.
type Client struct {
baseURL string
apiToken string
timeout int
verifySSL bool
mu sync.Mutex
}
// Config holds Coolify client configuration.
type Config struct {
BaseURL string
APIToken string
Timeout int
VerifySSL bool
}
// DefaultConfig returns default configuration from environment.
func DefaultConfig() Config {
return Config{
BaseURL: os.Getenv("COOLIFY_URL"),
APIToken: os.Getenv("COOLIFY_TOKEN"),
Timeout: 30,
VerifySSL: true,
}
}
// NewClient creates a new Coolify client.
func NewClient(cfg Config) (*Client, error) {
if cfg.BaseURL == "" {
return nil, fmt.Errorf("COOLIFY_URL not set")
}
if cfg.APIToken == "" {
return nil, fmt.Errorf("COOLIFY_TOKEN not set")
}
// Initialize Python runtime
if err := python.Init(); err != nil {
return nil, fmt.Errorf("failed to initialize Python: %w", err)
}
return &Client{
baseURL: cfg.BaseURL,
apiToken: cfg.APIToken,
timeout: cfg.Timeout,
verifySSL: cfg.VerifySSL,
}, nil
}
// Call invokes a Coolify API operation by operationId.
func (c *Client) Call(ctx context.Context, operationID string, params map[string]any) (map[string]any, error) {
c.mu.Lock()
defer c.mu.Unlock()
if params == nil {
params = map[string]any{}
}
// Generate and run Python script
script, err := python.CoolifyScript(c.baseURL, c.apiToken, operationID, params)
if err != nil {
return nil, fmt.Errorf("failed to generate script: %w", err)
}
output, err := python.RunScript(ctx, script)
if err != nil {
return nil, fmt.Errorf("API call %s failed: %w", operationID, err)
}
// Parse JSON result
var result map[string]any
if err := json.Unmarshal([]byte(output), &result); err != nil {
// Try parsing as array
var arrResult []any
if err2 := json.Unmarshal([]byte(output), &arrResult); err2 == nil {
return map[string]any{"result": arrResult}, nil
}
return nil, fmt.Errorf("failed to parse response: %w (output: %s)", err, output)
}
return result, nil
}
// ListServers returns all servers.
func (c *Client) ListServers(ctx context.Context) ([]map[string]any, error) {
result, err := c.Call(ctx, "list-servers", nil)
if err != nil {
return nil, err
}
return extractArray(result)
}
// GetServer returns a server by UUID.
func (c *Client) GetServer(ctx context.Context, uuid string) (map[string]any, error) {
return c.Call(ctx, "get-server-by-uuid", map[string]any{"uuid": uuid})
}
// ValidateServer validates a server by UUID.
func (c *Client) ValidateServer(ctx context.Context, uuid string) (map[string]any, error) {
return c.Call(ctx, "validate-server-by-uuid", map[string]any{"uuid": uuid})
}
// ListProjects returns all projects.
func (c *Client) ListProjects(ctx context.Context) ([]map[string]any, error) {
result, err := c.Call(ctx, "list-projects", nil)
if err != nil {
return nil, err
}
return extractArray(result)
}
// GetProject returns a project by UUID.
func (c *Client) GetProject(ctx context.Context, uuid string) (map[string]any, error) {
return c.Call(ctx, "get-project-by-uuid", map[string]any{"uuid": uuid})
}
// CreateProject creates a new project.
func (c *Client) CreateProject(ctx context.Context, name, description string) (map[string]any, error) {
return c.Call(ctx, "create-project", map[string]any{
"name": name,
"description": description,
})
}
// ListApplications returns all applications.
func (c *Client) ListApplications(ctx context.Context) ([]map[string]any, error) {
result, err := c.Call(ctx, "list-applications", nil)
if err != nil {
return nil, err
}
return extractArray(result)
}
// GetApplication returns an application by UUID.
func (c *Client) GetApplication(ctx context.Context, uuid string) (map[string]any, error) {
return c.Call(ctx, "get-application-by-uuid", map[string]any{"uuid": uuid})
}
// DeployApplication triggers deployment of an application.
func (c *Client) DeployApplication(ctx context.Context, uuid string) (map[string]any, error) {
return c.Call(ctx, "deploy-by-tag-or-uuid", map[string]any{"uuid": uuid})
}
// ListDatabases returns all databases.
func (c *Client) ListDatabases(ctx context.Context) ([]map[string]any, error) {
result, err := c.Call(ctx, "list-databases", nil)
if err != nil {
return nil, err
}
return extractArray(result)
}
// GetDatabase returns a database by UUID.
func (c *Client) GetDatabase(ctx context.Context, uuid string) (map[string]any, error) {
return c.Call(ctx, "get-database-by-uuid", map[string]any{"uuid": uuid})
}
// ListServices returns all services.
func (c *Client) ListServices(ctx context.Context) ([]map[string]any, error) {
result, err := c.Call(ctx, "list-services", nil)
if err != nil {
return nil, err
}
return extractArray(result)
}
// GetService returns a service by UUID.
func (c *Client) GetService(ctx context.Context, uuid string) (map[string]any, error) {
return c.Call(ctx, "get-service-by-uuid", map[string]any{"uuid": uuid})
}
// ListEnvironments returns environments for a project.
func (c *Client) ListEnvironments(ctx context.Context, projectUUID string) ([]map[string]any, error) {
result, err := c.Call(ctx, "get-environments", map[string]any{"project_uuid": projectUUID})
if err != nil {
return nil, err
}
return extractArray(result)
}
// GetTeam returns the current team.
func (c *Client) GetTeam(ctx context.Context) (map[string]any, error) {
return c.Call(ctx, "get-current-team", nil)
}
// GetTeamMembers returns members of the current team.
func (c *Client) GetTeamMembers(ctx context.Context) ([]map[string]any, error) {
result, err := c.Call(ctx, "get-current-team-members", nil)
if err != nil {
return nil, err
}
return extractArray(result)
}
// extractArray extracts an array from result["result"] or returns empty.
func extractArray(result map[string]any) ([]map[string]any, error) {
if arr, ok := result["result"].([]any); ok {
items := make([]map[string]any, 0, len(arr))
for _, item := range arr {
if m, ok := item.(map[string]any); ok {
items = append(items, m)
}
}
return items, nil
}
return nil, nil
}