156 lines
3.5 KiB
Go
156 lines
3.5 KiB
Go
package utils
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"gopkg.in/yaml.v3"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"slices"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
type BPMHook struct {
|
|
SourcePath string
|
|
SourceContent string
|
|
TriggerOperations []string `yaml:"trigger_operations"`
|
|
TargetType string `yaml:"target_type"`
|
|
Targets []string `yaml:"targets"`
|
|
Depends []string `yaml:"depends"`
|
|
Run string `yaml:"run"`
|
|
}
|
|
|
|
// CreateHook returns a BPMHook instance based on the content of the given string
|
|
func CreateHook(sourcePath string) (*BPMHook, error) {
|
|
// Read hook from source path
|
|
bytes, err := os.ReadFile(sourcePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create base hook structure
|
|
hook := &BPMHook{
|
|
SourcePath: sourcePath,
|
|
SourceContent: string(bytes),
|
|
TriggerOperations: nil,
|
|
TargetType: "",
|
|
Targets: nil,
|
|
Depends: nil,
|
|
Run: "",
|
|
}
|
|
|
|
// Unmarshal yaml string
|
|
err = yaml.Unmarshal(bytes, hook)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Ensure hook is valid
|
|
if err := hook.IsValid(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return hook, nil
|
|
}
|
|
|
|
// IsValid ensures hook is valid
|
|
func (hook *BPMHook) IsValid() error {
|
|
ValidOperations := []string{"install", "upgrade", "remove"}
|
|
|
|
// Return error if any trigger operation is not valid or none are given
|
|
if len(hook.TriggerOperations) == 0 {
|
|
return errors.New("no trigger operations specified")
|
|
}
|
|
for _, operation := range hook.TriggerOperations {
|
|
if !slices.Contains(ValidOperations, operation) {
|
|
return errors.New("trigger operation '" + operation + "' is not valid")
|
|
}
|
|
}
|
|
|
|
if hook.TargetType != "package" && hook.TargetType != "path" {
|
|
return errors.New("target type '" + hook.TargetType + "' is not valid")
|
|
}
|
|
|
|
if len(hook.Run) == 0 {
|
|
return errors.New("command to run is empty")
|
|
}
|
|
|
|
// Return nil as hook is valid
|
|
return nil
|
|
}
|
|
|
|
// Execute hook if all conditions are met
|
|
func (hook *BPMHook) Execute(packageChanges map[string]string, verbose bool, rootDir string) error {
|
|
// Check if package dependencies are met
|
|
installedPackages, err := GetInstalledPackages(rootDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, depend := range hook.Depends {
|
|
if !slices.Contains(installedPackages, depend) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Get modified files slice
|
|
modifiedFiles := make([]*PackageFileEntry, 0)
|
|
for pkg, _ := range packageChanges {
|
|
modifiedFiles = append(modifiedFiles, GetPackageFiles(pkg, rootDir)...)
|
|
}
|
|
|
|
// Check if any targets are met
|
|
targetMet := false
|
|
for _, target := range hook.Targets {
|
|
if targetMet {
|
|
break
|
|
}
|
|
if hook.TargetType == "package" {
|
|
for change, operation := range packageChanges {
|
|
if target == change && slices.Contains(hook.TriggerOperations, operation) {
|
|
targetMet = true
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
glob, err := filepath.Glob(path.Join(rootDir, target))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, change := range modifiedFiles {
|
|
if slices.Contains(glob, path.Join(rootDir, change.Path)) {
|
|
targetMet = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if !targetMet {
|
|
return nil
|
|
}
|
|
|
|
// Execute the command
|
|
splitCommand := strings.Split(hook.Run, " ")
|
|
cmd := exec.Command(splitCommand[0], splitCommand[1:]...)
|
|
// Setup subprocess environment
|
|
cmd.Dir = "/"
|
|
// Run hook in chroot if using the -R flag
|
|
if rootDir != "/" {
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: rootDir}
|
|
}
|
|
|
|
if verbose {
|
|
fmt.Printf("Running hook (%s) with run command: %s\n", hook.SourcePath, strings.Join(splitCommand, " "))
|
|
}
|
|
|
|
err = cmd.Run()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|