mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-10 06:48:04 -05:00
### CHANGES - Introduce `cmd` directory for all main application binaries. - Move all Go packages into the `internal` directory. - Rename the `restapi` package to `server` for clarity. - Consolidate patterns and strategies into a new `data` directory. - Group all auxiliary scripts into a new `scripts` directory. - Move all documentation and images into a `docs` directory. - Update all Go import paths to reflect the new structure. - Adjust CI/CD workflows and build commands for new layout.
201 lines
5.6 KiB
Go
201 lines
5.6 KiB
Go
package template
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ExtensionExecutor handles the secure execution of extensions
|
|
// It uses the registry to verify extensions before running them
|
|
type ExtensionExecutor struct {
|
|
registry *ExtensionRegistry
|
|
}
|
|
|
|
// NewExtensionExecutor creates a new executor instance
|
|
// It requires a registry to verify extensions
|
|
func NewExtensionExecutor(registry *ExtensionRegistry) *ExtensionExecutor {
|
|
return &ExtensionExecutor{
|
|
registry: registry,
|
|
}
|
|
}
|
|
|
|
// Execute runs an extension with the given operation and value string
|
|
// name: the registered name of the extension
|
|
// operation: the operation to perform
|
|
// value: the input value(s) for the operation
|
|
// In extension_executor.go
|
|
func (e *ExtensionExecutor) Execute(name, operation, value string) (string, error) {
|
|
// Get and verify extension from registry
|
|
ext, err := e.registry.GetExtension(name)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get extension: %w", err)
|
|
}
|
|
|
|
// Format the command using our template system
|
|
cmdStr, err := e.formatCommand(ext, operation, value)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to format command: %w", err)
|
|
}
|
|
|
|
// Split the command string into command and arguments
|
|
cmdParts := strings.Fields(cmdStr)
|
|
if len(cmdParts) < 1 {
|
|
return "", fmt.Errorf("empty command after formatting")
|
|
}
|
|
|
|
// Create command with the Executable and formatted arguments
|
|
cmd := exec.Command("sh", "-c", cmdStr)
|
|
//cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
|
|
|
|
// Set up environment if specified
|
|
if len(ext.Env) > 0 {
|
|
cmd.Env = append(os.Environ(), ext.Env...)
|
|
}
|
|
|
|
// Execute based on output method
|
|
outputMethod := ext.GetOutputMethod()
|
|
if outputMethod == "file" {
|
|
return e.executeWithFile(cmd, ext)
|
|
}
|
|
return e.executeStdout(cmd, ext)
|
|
}
|
|
|
|
// formatCommand uses fabric's template system to format the command
|
|
// It creates a variables map for the template system using the input values
|
|
func (e *ExtensionExecutor) formatCommand(ext *ExtensionDefinition, operation string, value string) (string, error) {
|
|
// Get operation config
|
|
opConfig, exists := ext.Operations[operation]
|
|
if !exists {
|
|
return "", fmt.Errorf("operation %s not found for extension %s", operation, ext.Name)
|
|
}
|
|
|
|
vars := make(map[string]string)
|
|
vars["executable"] = ext.Executable
|
|
vars["operation"] = operation
|
|
vars["value"] = value
|
|
|
|
// Split on pipe for numbered variables
|
|
values := strings.Split(value, "|")
|
|
for i, val := range values {
|
|
vars[fmt.Sprintf("%d", i+1)] = val
|
|
}
|
|
|
|
return ApplyTemplate(opConfig.CmdTemplate, vars, "")
|
|
}
|
|
|
|
// executeStdout runs the command and captures its stdout
|
|
func (e *ExtensionExecutor) executeStdout(cmd *exec.Cmd, ext *ExtensionDefinition) (string, error) {
|
|
var stdout bytes.Buffer
|
|
var stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
//debug output
|
|
fmt.Printf("Executing command: %s\n", cmd.String())
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return "", fmt.Errorf("execution failed: %w\nstderr: %s", err, stderr.String())
|
|
}
|
|
|
|
return stdout.String(), nil
|
|
}
|
|
|
|
// executeWithFile runs the command and handles file-based output
|
|
func (e *ExtensionExecutor) executeWithFile(cmd *exec.Cmd, ext *ExtensionDefinition) (string, error) {
|
|
// Parse timeout - this is now a first-class field
|
|
timeout, err := time.ParseDuration(ext.Timeout)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid timeout format: %w", err)
|
|
}
|
|
|
|
// Create context with timeout
|
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
defer cancel()
|
|
// Store the original environment
|
|
originalEnv := cmd.Env
|
|
// Create a new command with context. This might reset Env, depending on the Go version.
|
|
cmd = exec.CommandContext(ctx, cmd.Path, cmd.Args[1:]...)
|
|
// Restore the environment variables explicitly
|
|
cmd.Env = originalEnv
|
|
|
|
fileConfig := ext.GetFileConfig()
|
|
if fileConfig == nil {
|
|
return "", fmt.Errorf("no file configuration found")
|
|
}
|
|
|
|
// Handle path from stdout case
|
|
if pathFromStdout, ok := fileConfig["path_from_stdout"].(bool); ok && pathFromStdout {
|
|
return e.handlePathFromStdout(cmd, ext)
|
|
}
|
|
|
|
// Handle fixed file case
|
|
workDir, _ := fileConfig["work_dir"].(string)
|
|
outputFile, _ := fileConfig["output_file"].(string)
|
|
|
|
if outputFile == "" {
|
|
return "", fmt.Errorf("no output file specified in configuration")
|
|
}
|
|
|
|
// Set working directory if specified
|
|
if workDir != "" {
|
|
cmd.Dir = workDir
|
|
}
|
|
|
|
var stderr bytes.Buffer
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
if ctx.Err() == context.DeadlineExceeded {
|
|
return "", fmt.Errorf("execution timed out after %v", timeout)
|
|
}
|
|
return "", fmt.Errorf("execution failed: %w\nerr: %s", err, stderr.String())
|
|
}
|
|
|
|
// Construct full file path
|
|
outputPath := outputFile
|
|
if workDir != "" {
|
|
outputPath = filepath.Join(workDir, outputFile)
|
|
}
|
|
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read output file: %w", err)
|
|
}
|
|
|
|
// Handle cleanup if enabled
|
|
if ext.IsCleanupEnabled() {
|
|
defer os.Remove(outputPath)
|
|
}
|
|
|
|
return string(content), nil
|
|
}
|
|
|
|
// Helper method to handle path from stdout case
|
|
func (e *ExtensionExecutor) handlePathFromStdout(cmd *exec.Cmd, ext *ExtensionDefinition) (string, error) {
|
|
var stdout, stderr bytes.Buffer
|
|
cmd.Stdout = &stdout
|
|
cmd.Stderr = &stderr
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return "", fmt.Errorf("failed to get output path: %w\nerr: %s", err, stderr.String())
|
|
}
|
|
|
|
outputPath := strings.TrimSpace(stdout.String())
|
|
content, err := os.ReadFile(outputPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to read output file: %w", err)
|
|
}
|
|
|
|
if ext.IsCleanupEnabled() {
|
|
defer os.Remove(outputPath)
|
|
}
|
|
|
|
return string(content), nil
|
|
}
|