Add hook functionality #9
40
main.go
40
main.go
@ -224,6 +224,7 @@ func resolveCommand() {
|
||||
operation := utils.BPMOperation{
|
||||
Actions: make([]utils.OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
RootDir: rootDir,
|
||||
ForceInstallationReason: ir,
|
||||
}
|
||||
@ -238,7 +239,7 @@ func resolveCommand() {
|
||||
if !reinstall && utils.IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && utils.GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
|
||||
continue
|
||||
}
|
||||
operation.Actions = append(operation.Actions, &utils.InstallPackageAction{
|
||||
operation.AppendAction(&utils.InstallPackageAction{
|
||||
File: pkg,
|
||||
IsDependency: false,
|
||||
BpmPackage: bpmpkg,
|
||||
@ -261,7 +262,7 @@ func resolveCommand() {
|
||||
if !reinstall && utils.IsPackageInstalled(entry.Info.Name, rootDir) && utils.GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
|
||||
continue
|
||||
}
|
||||
operation.Actions = append(operation.Actions, &utils.FetchPackageAction{
|
||||
operation.AppendAction(&utils.FetchPackageAction{
|
||||
IsDependency: false,
|
||||
RepositoryEntry: entry,
|
||||
})
|
||||
@ -327,6 +328,13 @@ func resolveCommand() {
|
||||
if err != nil {
|
||||
log.Fatalf("Error: could not complete operation: %s\n", err)
|
||||
}
|
||||
|
||||
// Executing hooks
|
||||
fmt.Println("Running hooks...")
|
||||
err = operation.RunHooks(verbose)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: could not run hooks: %s\n", err)
|
||||
}
|
||||
case update:
|
||||
if os.Getuid() != 0 {
|
||||
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
||||
@ -355,6 +363,7 @@ func resolveCommand() {
|
||||
operation := utils.BPMOperation{
|
||||
Actions: make([]utils.OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
RootDir: rootDir,
|
||||
ForceInstallationReason: utils.Unknown,
|
||||
}
|
||||
@ -378,7 +387,7 @@ func resolveCommand() {
|
||||
} else {
|
||||
comparison := utils.ComparePackageVersions(*entry.Info, *installedInfo)
|
||||
if comparison > 0 || reinstall {
|
||||
operation.Actions = append(operation.Actions, &utils.FetchPackageAction{
|
||||
operation.AppendAction(&utils.FetchPackageAction{
|
||||
IsDependency: false,
|
||||
RepositoryEntry: entry,
|
||||
})
|
||||
@ -421,6 +430,13 @@ func resolveCommand() {
|
||||
if err != nil {
|
||||
log.Fatalf("Error: could not complete operation: %s\n", err)
|
||||
}
|
||||
|
||||
// Executing hooks
|
||||
fmt.Println("Running hooks...")
|
||||
err = operation.RunHooks(verbose)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: could not run hooks: %s\n", err)
|
||||
}
|
||||
case sync:
|
||||
if os.Getuid() != 0 {
|
||||
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
||||
@ -455,6 +471,7 @@ func resolveCommand() {
|
||||
operation := &utils.BPMOperation{
|
||||
Actions: make([]utils.OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
RootDir: rootDir,
|
||||
}
|
||||
|
||||
@ -464,7 +481,7 @@ func resolveCommand() {
|
||||
if bpmpkg == nil {
|
||||
continue
|
||||
}
|
||||
operation.Actions = append(operation.Actions, &utils.RemovePackageAction{BpmPackage: bpmpkg})
|
||||
operation.AppendAction(&utils.RemovePackageAction{BpmPackage: bpmpkg})
|
||||
}
|
||||
|
||||
// Skip needed packages if the --unused flag is on
|
||||
@ -502,6 +519,13 @@ func resolveCommand() {
|
||||
if err != nil {
|
||||
log.Fatalf("Error: could not complete operation: %s\n", err)
|
||||
}
|
||||
|
||||
// Executing hooks
|
||||
fmt.Println("Running hooks...")
|
||||
err = operation.RunHooks(verbose)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: could not run hooks: %s\n", err)
|
||||
}
|
||||
case cleanup:
|
||||
if os.Getuid() != 0 {
|
||||
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
||||
@ -510,6 +534,7 @@ func resolveCommand() {
|
||||
operation := &utils.BPMOperation{
|
||||
Actions: make([]utils.OperationAction, 0),
|
||||
UnresolvedDepends: make([]string, 0),
|
||||
Changes: make(map[string]string),
|
||||
RootDir: rootDir,
|
||||
}
|
||||
|
||||
@ -538,6 +563,13 @@ func resolveCommand() {
|
||||
if err != nil {
|
||||
log.Fatalf("Error: could not complete operation: %s\n", err)
|
||||
}
|
||||
|
||||
// Executing hooks
|
||||
fmt.Println("Running hooks...")
|
||||
err = operation.RunHooks(verbose)
|
||||
if err != nil {
|
||||
log.Fatalf("Error: could not run hooks: %s\n", err)
|
||||
}
|
||||
case file:
|
||||
files := subcommandArgs
|
||||
if len(files) == 0 {
|
||||
|
155
utils/hooks.go
Normal file
155
utils/hooks.go
Normal file
@ -0,0 +1,155 @@
|
||||
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
|
||||
}
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
@ -12,6 +13,7 @@ import (
|
||||
type BPMOperation struct {
|
||||
Actions []OperationAction
|
||||
UnresolvedDepends []string
|
||||
Changes map[string]string
|
||||
RootDir string
|
||||
ForceInstallationReason InstallationReason
|
||||
}
|
||||
@ -35,12 +37,35 @@ func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) AppendAction(action OperationAction) {
|
||||
operation.InsertActionAt(len(operation.Actions), action)
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) {
|
||||
if len(operation.Actions) == index { // nil or empty slice or after last element
|
||||
operation.Actions = append(operation.Actions, action)
|
||||
} else {
|
||||
operation.Actions = append(operation.Actions[:index+1], operation.Actions[index:]...) // index < len(a)
|
||||
operation.Actions[index] = action
|
||||
}
|
||||
|
||||
if action.GetActionType() == "install" {
|
||||
pkgInfo := action.(*InstallPackageAction).BpmPackage.PkgInfo
|
||||
if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) {
|
||||
operation.Changes[pkgInfo.Name] = "install"
|
||||
} else {
|
||||
operation.Changes[pkgInfo.Name] = "upgrade"
|
||||
}
|
||||
} else if action.GetActionType() == "fetch" {
|
||||
pkgInfo := action.(*FetchPackageAction).RepositoryEntry.Info
|
||||
if !IsPackageInstalled(pkgInfo.Name, operation.RootDir) {
|
||||
operation.Changes[pkgInfo.Name] = "install"
|
||||
} else {
|
||||
operation.Changes[pkgInfo.Name] = "upgrade"
|
||||
}
|
||||
} else if action.GetActionType() == "remove" {
|
||||
operation.Changes[action.(*RemovePackageAction).BpmPackage.PkgInfo.Name] = "remove"
|
||||
}
|
||||
operation.Actions = append(operation.Actions[:index+1], operation.Actions[index:]...) // index < len(a)
|
||||
operation.Actions[index] = action
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) RemoveAction(pkg, actionType string) {
|
||||
@ -356,6 +381,32 @@ func (operation *BPMOperation) ShowOperationSummary() {
|
||||
}
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) RunHooks(verbose bool) error {
|
||||
// Get directory entries in hooks directory
|
||||
dirEntries, err := os.ReadDir(path.Join(operation.RootDir, "var/lib/bpm/hooks"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find all hooks, validate and execute them
|
||||
for _, entry := range dirEntries {
|
||||
if entry.Type().IsRegular() && strings.HasSuffix(entry.Name(), ".bpmhook") {
|
||||
hook, err := CreateHook(path.Join(operation.RootDir, "var/lib/bpm/hooks", entry.Name()))
|
||||
if err != nil {
|
||||
log.Printf("Error while reading hook (%s): %s", entry.Name(), err)
|
||||
}
|
||||
|
||||
err = hook.Execute(operation.Changes, verbose, operation.RootDir)
|
||||
if err != nil {
|
||||
log.Printf("Warning: could not execute hook (%s): %s\n", entry.Name(), err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (operation *BPMOperation) Execute(verbose, force bool) error {
|
||||
// Fetch packages from repositories
|
||||
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
||||
|
Loading…
x
Reference in New Issue
Block a user