cli/pkg/deploy/python/python.go
Snider e5e6908416 fix: address PR review comments from CodeRabbit, Copilot, and Gemini
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>
2026-02-03 22:33:43 +00:00

147 lines
3.4 KiB
Go

package python
import (
"context"
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"sync"
"github.com/host-uk/core/pkg/framework/core"
"github.com/kluctl/go-embed-python/python"
)
var (
once sync.Once
ep *python.EmbeddedPython
initErr error
)
// Init initializes the embedded Python runtime.
func Init() error {
once.Do(func() {
ep, initErr = python.NewEmbeddedPython("core-deploy")
})
return initErr
}
// GetPython returns the embedded Python instance.
func GetPython() *python.EmbeddedPython {
return ep
}
// RunScript runs a Python script with the given code and returns stdout.
func RunScript(ctx context.Context, code string, args ...string) (string, error) {
if err := Init(); err != nil {
return "", err
}
// Write code to temp file
tmpFile, err := os.CreateTemp("", "core-*.py")
if err != nil {
return "", core.E("python", "create temp file", err)
}
defer func() { _ = os.Remove(tmpFile.Name()) }()
if _, err := tmpFile.WriteString(code); err != nil {
_ = tmpFile.Close()
return "", core.E("python", "write script", err)
}
_ = tmpFile.Close()
// Build args: script path + any additional args
cmdArgs := append([]string{tmpFile.Name()}, args...)
// Get the command
cmd, err := ep.PythonCmd(cmdArgs...)
if err != nil {
return "", core.E("python", "create command", err)
}
// Run with context
output, err := cmd.Output()
if err != nil {
// Try to get stderr for better error message
if exitErr, ok := err.(*exec.ExitError); ok {
return "", core.E("python", "run script", fmt.Errorf("%w: %s", err, string(exitErr.Stderr)))
}
return "", core.E("python", "run script", err)
}
return string(output), nil
}
// RunModule runs a Python module (python -m module_name).
func RunModule(ctx context.Context, module string, args ...string) (string, error) {
if err := Init(); err != nil {
return "", err
}
cmdArgs := append([]string{"-m", module}, args...)
cmd, err := ep.PythonCmd(cmdArgs...)
if err != nil {
return "", core.E("python", "create command", err)
}
output, err := cmd.Output()
if err != nil {
return "", core.E("python", fmt.Sprintf("run module %s", module), err)
}
return string(output), nil
}
// DevOpsPath returns the path to the DevOps repo.
func DevOpsPath() (string, error) {
if path := os.Getenv("DEVOPS_PATH"); path != "" {
return path, nil
}
home, err := os.UserHomeDir()
if err != nil {
return "", core.E("python", "get user home", err)
}
return filepath.Join(home, "Code", "DevOps"), nil
}
// CoolifyModulePath returns the path to the Coolify module_utils.
func CoolifyModulePath() (string, error) {
path, err := DevOpsPath()
if err != nil {
return "", err
}
return filepath.Join(path, "playbooks", "roles", "coolify", "module_utils"), nil
}
// CoolifyScript generates Python code to call the Coolify API.
func CoolifyScript(baseURL, apiToken, operation string, params map[string]any) (string, error) {
paramsJSON, err := json.Marshal(params)
if err != nil {
return "", core.E("python", "marshal params", err)
}
modulePath, err := CoolifyModulePath()
if err != nil {
return "", err
}
return fmt.Sprintf(`
import sys
import json
sys.path.insert(0, %q)
from swagger.coolify_api import CoolifyClient
client = CoolifyClient(
base_url=%q,
api_token=%q,
timeout=30,
verify_ssl=True,
)
params = json.loads(%q)
result = client._call(%q, params, check_response=False)
print(json.dumps(result))
`, modulePath, baseURL, apiToken, string(paramsJSON), operation), nil
}