Build and deployment improvements: Makefile: - Native library build targets with ASan support - Cross-platform compilation helpers - Performance benchmark targets - Security scan integration Docker: - secure-prod.Dockerfile: Hardened production image (non-root, minimal surface) - simple.Dockerfile: Lightweight development image Scripts: - build/: Go and native library build scripts, cross-platform builds - ci/: checks.sh, test.sh, verify-paths.sh for validation - benchmarks/: Local performance testing and regression tracking - dev/: Monitoring setup Dependencies: Update to latest stable with security patches Commands: - api-server/main.go: Server initialization updates - data_manager/data_sync.go: Data sync with visibility - errors/main.go: Error handling improvements - tui/: TUI improvements for group management
104 lines
3 KiB
Go
104 lines
3 KiB
Go
// Package main implements the ml errors command for querying task errors
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/jfraeys/fetch_ml/internal/errtypes"
|
|
)
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
fmt.Fprintln(os.Stderr, "Usage: errors <task_id> [--json]")
|
|
fmt.Fprintln(os.Stderr, " task_id: The task ID to query errors for")
|
|
fmt.Fprintln(os.Stderr, " --json: Output as JSON instead of formatted text")
|
|
os.Exit(1)
|
|
}
|
|
|
|
taskID := os.Args[1]
|
|
jsonOutput := len(os.Args) > 2 && os.Args[2] == "--json"
|
|
|
|
// Sanitize taskID to prevent path traversal
|
|
taskID = sanitizeTaskID(taskID)
|
|
|
|
// Determine base path from environment or default
|
|
basePath := os.Getenv("FETCH_ML_BASE_PATH")
|
|
if basePath == "" {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: failed to get home directory: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
basePath = filepath.Join(home, "ml_jobs")
|
|
}
|
|
|
|
// Try to read error file
|
|
errorPath := filepath.Join(basePath, "errors", taskID+".json")
|
|
// #nosec G304 -- taskID is sanitized by sanitizeTaskID to prevent path traversal
|
|
data, err := os.ReadFile(errorPath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
fmt.Fprintf(os.Stderr, "Error: no error record found for task %s\n", taskID)
|
|
fmt.Fprintf(os.Stderr, "Expected: %s\n", errorPath)
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, "Error: failed to read error file: %v\n", err)
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
|
|
var execErr errtypes.TaskExecutionError
|
|
if err := json.Unmarshal(data, &execErr); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: failed to parse error record: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if jsonOutput {
|
|
// Output as pretty-printed JSON
|
|
output, err := json.MarshalIndent(execErr, "", " ")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: failed to format error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Println(string(output))
|
|
} else {
|
|
// Output as formatted text
|
|
fmt.Printf("Error Report for Task: %s\n", execErr.TaskID)
|
|
fmt.Printf("Job Name: %s\n", execErr.JobName)
|
|
fmt.Printf("Phase: %s\n", execErr.Phase)
|
|
fmt.Printf("Time: %s\n", execErr.Timestamp.Format(time.RFC3339))
|
|
fmt.Printf("Recoverable: %v\n", execErr.Recoverable)
|
|
fmt.Println()
|
|
if execErr.Message != "" {
|
|
fmt.Printf("Message: %s\n", execErr.Message)
|
|
}
|
|
if execErr.Err != nil {
|
|
fmt.Printf("Underlying Error: %v\n", execErr.Err)
|
|
}
|
|
if len(execErr.Context) > 0 {
|
|
fmt.Println()
|
|
fmt.Println("Context:")
|
|
for key, value := range execErr.Context {
|
|
fmt.Printf(" %s: %s\n", key, value)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// sanitizeTaskID removes path separators and traversal sequences from task IDs.
|
|
// This prevents path traversal attacks when constructing file paths.
|
|
func sanitizeTaskID(taskID string) string {
|
|
// Remove any path separators
|
|
taskID = strings.ReplaceAll(taskID, "/", "_")
|
|
taskID = strings.ReplaceAll(taskID, string(filepath.Separator), "_")
|
|
|
|
// Remove parent directory references
|
|
taskID = strings.ReplaceAll(taskID, "..", "_")
|
|
|
|
// Clean the result
|
|
return filepath.Clean(taskID)
|
|
}
|