Files
wild-cloud/wild-cli/internal/external/base.go

131 lines
2.9 KiB
Go

package external
import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"time"
)
// Tool represents an external command-line tool
type Tool interface {
Name() string
BinaryName() string
IsInstalled() bool
Version() (string, error)
Execute(ctx context.Context, args ...string) ([]byte, error)
ExecuteWithInput(ctx context.Context, input string, args ...string) ([]byte, error)
}
// BaseTool provides common functionality for external tools
type BaseTool struct {
name string
binaryName string
binaryPath string
timeout time.Duration
}
// NewBaseTool creates a new base tool
func NewBaseTool(name, binaryName string) *BaseTool {
return &BaseTool{
name: name,
binaryName: binaryName,
timeout: 5 * time.Minute, // Default timeout
}
}
// Name returns the tool name
func (t *BaseTool) Name() string {
return t.name
}
// BinaryName returns the binary name
func (t *BaseTool) BinaryName() string {
return t.binaryName
}
// IsInstalled checks if the tool is available in PATH
func (t *BaseTool) IsInstalled() bool {
if t.binaryPath == "" {
path, err := exec.LookPath(t.binaryName)
if err != nil {
return false
}
t.binaryPath = path
}
return true
}
// Version returns the tool version
func (t *BaseTool) Version() (string, error) {
if !t.IsInstalled() {
return "", fmt.Errorf("tool %s not installed", t.name)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
output, err := t.Execute(ctx, "--version")
if err != nil {
// Try alternative version flags
output, err = t.Execute(ctx, "version")
if err != nil {
output, err = t.Execute(ctx, "-v")
if err != nil {
return "", fmt.Errorf("getting version: %w", err)
}
}
}
return string(output), nil
}
// Execute runs the tool with given arguments
func (t *BaseTool) Execute(ctx context.Context, args ...string) ([]byte, error) {
return t.ExecuteWithInput(ctx, "", args...)
}
// ExecuteWithInput runs the tool with stdin input
func (t *BaseTool) ExecuteWithInput(ctx context.Context, input string, args ...string) ([]byte, error) {
if !t.IsInstalled() {
return nil, fmt.Errorf("tool %s not installed", t.name)
}
// Create command with timeout
ctx, cancel := context.WithTimeout(ctx, t.timeout)
defer cancel()
cmd := exec.CommandContext(ctx, t.binaryPath, args...)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if input != "" {
cmd.Stdin = bytes.NewBufferString(input)
}
// Set environment
cmd.Env = os.Environ()
err := cmd.Run()
if err != nil {
return nil, fmt.Errorf("executing %s %v: %w\nstderr: %s",
t.name, args, err, stderr.String())
}
return stdout.Bytes(), nil
}
// SetTimeout sets the execution timeout
func (t *BaseTool) SetTimeout(timeout time.Duration) {
t.timeout = timeout
}
// SetBinaryPath explicitly sets the binary path (useful for testing)
func (t *BaseTool) SetBinaryPath(path string) {
t.binaryPath = path
}