- Introduced unit tests for the main package to ensure compilation. - Added tests for the manager, including validation of upload sessions and handling of Blender binary paths. - Implemented tests for job token generation and validation, ensuring security and integrity. - Created tests for configuration management and database schema to verify functionality. - Added tests for logger and runner components to enhance overall test coverage and reliability.
163 lines
4.8 KiB
Go
163 lines
4.8 KiB
Go
// Package blender handles Blender binary management and execution.
|
|
package blender
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"jiggablend/internal/runner/api"
|
|
"jiggablend/internal/runner/workspace"
|
|
)
|
|
|
|
// Manager handles Blender binary downloads and management.
|
|
type Manager struct {
|
|
manager *api.ManagerClient
|
|
workspaceDir string
|
|
}
|
|
|
|
// NewManager creates a new Blender manager.
|
|
func NewManager(managerClient *api.ManagerClient, workspaceDir string) *Manager {
|
|
return &Manager{
|
|
manager: managerClient,
|
|
workspaceDir: workspaceDir,
|
|
}
|
|
}
|
|
|
|
// GetBinaryPath returns the path to the Blender binary for a specific version.
|
|
// Downloads from manager and extracts if not already present.
|
|
func (m *Manager) GetBinaryPath(version string) (string, error) {
|
|
blenderDir := filepath.Join(m.workspaceDir, "blender-versions")
|
|
if err := os.MkdirAll(blenderDir, 0755); err != nil {
|
|
return "", fmt.Errorf("failed to create blender directory: %w", err)
|
|
}
|
|
|
|
// Check if already installed - look for version folder first
|
|
versionDir := filepath.Join(blenderDir, version)
|
|
binaryPath := filepath.Join(versionDir, "blender")
|
|
|
|
// Check if version folder exists and contains the binary
|
|
if versionInfo, err := os.Stat(versionDir); err == nil && versionInfo.IsDir() {
|
|
// Version folder exists, check if binary is present
|
|
if binaryInfo, err := os.Stat(binaryPath); err == nil {
|
|
// Verify it's actually a file (not a directory)
|
|
if !binaryInfo.IsDir() {
|
|
absBinaryPath, err := ResolveBinaryPath(binaryPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
log.Printf("Found existing Blender %s installation at %s", version, absBinaryPath)
|
|
return absBinaryPath, nil
|
|
}
|
|
}
|
|
// Version folder exists but binary is missing - might be incomplete installation
|
|
log.Printf("Version folder %s exists but binary not found, will re-download", versionDir)
|
|
}
|
|
|
|
// Download from manager
|
|
log.Printf("Downloading Blender %s from manager", version)
|
|
|
|
reader, err := m.manager.DownloadBlender(version)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer reader.Close()
|
|
|
|
// Manager serves pre-decompressed .tar files - extract directly
|
|
log.Printf("Extracting Blender %s...", version)
|
|
if err := workspace.ExtractTarStripPrefix(reader, versionDir); err != nil {
|
|
return "", fmt.Errorf("failed to extract blender: %w", err)
|
|
}
|
|
|
|
// Verify binary exists
|
|
if _, err := os.Stat(binaryPath); err != nil {
|
|
return "", fmt.Errorf("blender binary not found after extraction")
|
|
}
|
|
|
|
absBinaryPath, err := ResolveBinaryPath(binaryPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
log.Printf("Blender %s installed at %s", version, absBinaryPath)
|
|
return absBinaryPath, nil
|
|
}
|
|
|
|
// GetBinaryForJob returns the Blender binary path for a job.
|
|
// Uses the version from metadata or falls back to system blender.
|
|
func (m *Manager) GetBinaryForJob(version string) (string, error) {
|
|
if version == "" {
|
|
return ResolveBinaryPath("blender")
|
|
}
|
|
|
|
return m.GetBinaryPath(version)
|
|
}
|
|
|
|
// ResolveBinaryPath resolves a Blender executable to an absolute path.
|
|
func ResolveBinaryPath(blenderBinary string) (string, error) {
|
|
if blenderBinary == "" {
|
|
return "", fmt.Errorf("blender binary path is empty")
|
|
}
|
|
|
|
if strings.Contains(blenderBinary, string(filepath.Separator)) {
|
|
absPath, err := filepath.Abs(blenderBinary)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to resolve blender binary path %q: %w", blenderBinary, err)
|
|
}
|
|
return absPath, nil
|
|
}
|
|
|
|
resolvedPath, err := exec.LookPath(blenderBinary)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to locate blender binary %q in PATH: %w", blenderBinary, err)
|
|
}
|
|
absPath, err := filepath.Abs(resolvedPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to resolve blender binary path %q: %w", resolvedPath, err)
|
|
}
|
|
return absPath, nil
|
|
}
|
|
|
|
// TarballEnv returns a copy of baseEnv with LD_LIBRARY_PATH set so that a
|
|
// tarball Blender installation can find its bundled libs (e.g. lib/python3.x).
|
|
// If blenderBinary is the system "blender" or has no path component, baseEnv is
|
|
// returned unchanged.
|
|
func TarballEnv(blenderBinary string, baseEnv []string) []string {
|
|
if blenderBinary == "" || blenderBinary == "blender" {
|
|
return baseEnv
|
|
}
|
|
if !strings.Contains(blenderBinary, string(os.PathSeparator)) {
|
|
return baseEnv
|
|
}
|
|
blenderDir := filepath.Dir(blenderBinary)
|
|
libDir := filepath.Join(blenderDir, "lib")
|
|
ldLib := libDir
|
|
for _, e := range baseEnv {
|
|
if strings.HasPrefix(e, "LD_LIBRARY_PATH=") {
|
|
existing := strings.TrimPrefix(e, "LD_LIBRARY_PATH=")
|
|
if existing != "" {
|
|
ldLib = libDir + ":" + existing
|
|
}
|
|
break
|
|
}
|
|
}
|
|
out := make([]string, 0, len(baseEnv)+1)
|
|
done := false
|
|
for _, e := range baseEnv {
|
|
if strings.HasPrefix(e, "LD_LIBRARY_PATH=") {
|
|
out = append(out, "LD_LIBRARY_PATH="+ldLib)
|
|
done = true
|
|
continue
|
|
}
|
|
out = append(out, e)
|
|
}
|
|
if !done {
|
|
out = append(out, "LD_LIBRARY_PATH="+ldLib)
|
|
}
|
|
return out
|
|
}
|
|
|