heroagent/pkg/builders/postgresql/builder.go
2025-04-23 04:18:28 +02:00

179 lines
5.1 KiB
Go

package postgresql
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/dependencies"
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/gosp"
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/postgres"
"github.com/freeflowuniverse/heroagent/pkg/system/builders/postgresql/verification"
)
// Constants for PostgreSQL installation
const (
DefaultInstallPrefix = "/opt/postgresql"
)
// Builder represents a PostgreSQL builder
type Builder struct {
InstallPrefix string
PostgresBuilder *postgres.PostgresBuilder
GoSPBuilder *gosp.GoSPBuilder
DependencyManager *dependencies.DependencyManager
Verifier *verification.Verifier
}
// NewBuilder creates a new PostgreSQL builder with default values
func NewBuilder() *Builder {
installPrefix := DefaultInstallPrefix
return &Builder{
InstallPrefix: installPrefix,
PostgresBuilder: postgres.NewPostgresBuilder().WithInstallPrefix(installPrefix),
GoSPBuilder: gosp.NewGoSPBuilder(installPrefix),
DependencyManager: dependencies.NewDependencyManager("bison", "flex", "libreadline-dev"),
Verifier: verification.NewVerifier(installPrefix),
}
}
// WithInstallPrefix sets the installation prefix
func (b *Builder) WithInstallPrefix(prefix string) *Builder {
b.InstallPrefix = prefix
b.PostgresBuilder.WithInstallPrefix(prefix)
b.GoSPBuilder = gosp.NewGoSPBuilder(prefix)
return b
}
// WithPostgresURL sets the PostgreSQL download URL
// RunPostgresInScreen starts PostgreSQL in a screen session
func (b *Builder) RunPostgresInScreen() error {
return b.PostgresBuilder.RunPostgresInScreen()
}
// CheckPostgresUser checks if PostgreSQL can be run as postgres user
func (b *Builder) CheckPostgresUser() error {
return b.PostgresBuilder.CheckPostgresUser()
}
func (b *Builder) WithPostgresURL(url string) *Builder {
b.PostgresBuilder.WithPostgresURL(url)
return b
}
// WithDependencies sets the dependencies to install
func (b *Builder) WithDependencies(deps ...string) *Builder {
b.DependencyManager.WithDependencies(deps...)
return b
}
// Build builds PostgreSQL
func (b *Builder) Build() error {
fmt.Println("=== Starting PostgreSQL Build ===")
// Install dependencies
fmt.Println("Installing dependencies...")
if err := b.DependencyManager.Install(); err != nil {
return fmt.Errorf("failed to install dependencies: %w", err)
}
// Build PostgreSQL
if err := b.PostgresBuilder.Build(); err != nil {
return fmt.Errorf("failed to build PostgreSQL: %w", err)
}
// Ensure Go is installed first to get its path
goInstaller := postgres.NewGoInstaller()
goPath, err := goInstaller.InstallGo()
if err != nil {
return fmt.Errorf("failed to ensure Go is installed: %w", err)
}
fmt.Printf("Using Go executable from: %s\n", goPath)
// Pass the Go path explicitly to the GoSPBuilder
b.GoSPBuilder.WithGoPath(goPath)
// For the Go stored procedure, we'll create and execute a shell script directly
// to ensure all environment variables are properly set
fmt.Println("Building Go stored procedure via shell script...")
tempDir, err := os.MkdirTemp("", "gosp-build-")
if err != nil {
return fmt.Errorf("failed to create temp directory: %w", err)
}
defer os.RemoveAll(tempDir)
// Create the Go source file in the temp directory
libPath := filepath.Join(tempDir, "gosp.go")
libSrc := `
package main
import "C"
import "fmt"
//export helloworld
func helloworld() {
fmt.Println("Hello from Go stored procedure!")
}
func main() {}
`
if err := os.WriteFile(libPath, []byte(libSrc), 0644); err != nil {
return fmt.Errorf("failed to write Go source file: %w", err)
}
// Create a shell script to build the Go stored procedure
buildScript := filepath.Join(tempDir, "build.sh")
buildScriptContent := fmt.Sprintf(`#!/bin/sh
set -e
# Set environment variables
export GOROOT=/usr/local/go
export GOPATH=/root/go
export PATH=/usr/local/go/bin:$PATH
echo "Current directory: $(pwd)"
echo "Go source file: %s"
echo "Output file: %s/lib/libgosp.so"
# Create output directory
mkdir -p %s/lib
# Run the build command
echo "Running: go build -buildmode=c-shared -o %s/lib/libgosp.so %s"
go build -buildmode=c-shared -o %s/lib/libgosp.so %s
echo "Go stored procedure built successfully!"
`,
libPath, b.InstallPrefix, b.InstallPrefix, b.InstallPrefix, libPath, b.InstallPrefix, libPath)
if err := os.WriteFile(buildScript, []byte(buildScriptContent), 0755); err != nil {
return fmt.Errorf("failed to write build script: %w", err)
}
// Execute the build script
cmd := exec.Command("/bin/sh", buildScript)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
fmt.Println("Executing build script:", buildScript)
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to run build script: %w", err)
}
// Verify the installation
fmt.Println("Verifying installation...")
success, err := b.Verifier.Verify()
if err != nil {
fmt.Printf("Warning: Verification had issues: %v\n", err)
}
if success {
fmt.Println("✅ Done! PostgreSQL installed and verified in:", b.InstallPrefix)
} else {
fmt.Println("⚠️ Done with warnings! PostgreSQL installed in:", b.InstallPrefix)
}
return nil
}