Fixes across 25 files addressing 46+ review comments: - pkg/ai/metrics.go: handle error from Close() on writable file handle - pkg/ansible: restore loop vars after loop, restore become settings, fix Upload with become=true and no password (use sudo -n), honour SSH timeout config, use E() helper for contextual errors, quote git refs in checkout commands - pkg/rag: validate chunk config, guard negative-to-uint64 conversion, use E() helper for errors, add context timeout to Ollama HTTP calls - pkg/deploy/python: fix exec.ExitError type assertion (was os.PathError), handle os.UserHomeDir() error - pkg/build/buildcmd: use cmd.Context() instead of context.Background() for proper Ctrl+C cancellation - install.bat: add curl timeouts, CRLF line endings, use --connect-timeout for archive downloads - install.sh: use absolute path for version check in CI mode - tools/rag: fix broken ingest.py function def, escape HTML in query.py, pin qdrant-client version, add markdown code block languages - internal/cmd/rag: add chunk size validation, env override handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
219 lines
6.1 KiB
Go
219 lines
6.1 KiB
Go
package coolify
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
|
|
"github.com/host-uk/core/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
|
|
}
|