Added basic hook functionality
This commit is contained in:
parent
87c492a30c
commit
5f8e4f00ea
40
main.go
40
main.go
@ -224,6 +224,7 @@ func resolveCommand() {
|
|||||||
operation := utils.BPMOperation{
|
operation := utils.BPMOperation{
|
||||||
Actions: make([]utils.OperationAction, 0),
|
Actions: make([]utils.OperationAction, 0),
|
||||||
UnresolvedDepends: make([]string, 0),
|
UnresolvedDepends: make([]string, 0),
|
||||||
|
Changes: make(map[string]string),
|
||||||
RootDir: rootDir,
|
RootDir: rootDir,
|
||||||
ForceInstallationReason: ir,
|
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() {
|
if !reinstall && utils.IsPackageInstalled(bpmpkg.PkgInfo.Name, rootDir) && utils.GetPackageInfo(bpmpkg.PkgInfo.Name, rootDir).GetFullVersion() == bpmpkg.PkgInfo.GetFullVersion() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
operation.Actions = append(operation.Actions, &utils.InstallPackageAction{
|
operation.AppendAction(&utils.InstallPackageAction{
|
||||||
File: pkg,
|
File: pkg,
|
||||||
IsDependency: false,
|
IsDependency: false,
|
||||||
BpmPackage: bpmpkg,
|
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() {
|
if !reinstall && utils.IsPackageInstalled(entry.Info.Name, rootDir) && utils.GetPackageInfo(entry.Info.Name, rootDir).GetFullVersion() == entry.Info.GetFullVersion() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
operation.Actions = append(operation.Actions, &utils.FetchPackageAction{
|
operation.AppendAction(&utils.FetchPackageAction{
|
||||||
IsDependency: false,
|
IsDependency: false,
|
||||||
RepositoryEntry: entry,
|
RepositoryEntry: entry,
|
||||||
})
|
})
|
||||||
@ -327,6 +328,13 @@ func resolveCommand() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error: could not complete operation: %s\n", err)
|
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:
|
case update:
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
||||||
@ -355,6 +363,7 @@ func resolveCommand() {
|
|||||||
operation := utils.BPMOperation{
|
operation := utils.BPMOperation{
|
||||||
Actions: make([]utils.OperationAction, 0),
|
Actions: make([]utils.OperationAction, 0),
|
||||||
UnresolvedDepends: make([]string, 0),
|
UnresolvedDepends: make([]string, 0),
|
||||||
|
Changes: make(map[string]string),
|
||||||
RootDir: rootDir,
|
RootDir: rootDir,
|
||||||
ForceInstallationReason: utils.Unknown,
|
ForceInstallationReason: utils.Unknown,
|
||||||
}
|
}
|
||||||
@ -378,7 +387,7 @@ func resolveCommand() {
|
|||||||
} else {
|
} else {
|
||||||
comparison := utils.ComparePackageVersions(*entry.Info, *installedInfo)
|
comparison := utils.ComparePackageVersions(*entry.Info, *installedInfo)
|
||||||
if comparison > 0 || reinstall {
|
if comparison > 0 || reinstall {
|
||||||
operation.Actions = append(operation.Actions, &utils.FetchPackageAction{
|
operation.AppendAction(&utils.FetchPackageAction{
|
||||||
IsDependency: false,
|
IsDependency: false,
|
||||||
RepositoryEntry: entry,
|
RepositoryEntry: entry,
|
||||||
})
|
})
|
||||||
@ -421,6 +430,13 @@ func resolveCommand() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error: could not complete operation: %s\n", err)
|
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:
|
case sync:
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
||||||
@ -455,6 +471,7 @@ func resolveCommand() {
|
|||||||
operation := &utils.BPMOperation{
|
operation := &utils.BPMOperation{
|
||||||
Actions: make([]utils.OperationAction, 0),
|
Actions: make([]utils.OperationAction, 0),
|
||||||
UnresolvedDepends: make([]string, 0),
|
UnresolvedDepends: make([]string, 0),
|
||||||
|
Changes: make(map[string]string),
|
||||||
RootDir: rootDir,
|
RootDir: rootDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,7 +481,7 @@ func resolveCommand() {
|
|||||||
if bpmpkg == nil {
|
if bpmpkg == nil {
|
||||||
continue
|
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
|
// Skip needed packages if the --unused flag is on
|
||||||
@ -502,6 +519,13 @@ func resolveCommand() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error: could not complete operation: %s\n", err)
|
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:
|
case cleanup:
|
||||||
if os.Getuid() != 0 {
|
if os.Getuid() != 0 {
|
||||||
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
log.Fatalf("Error: this subcommand needs to be run with superuser permissions")
|
||||||
@ -510,6 +534,7 @@ func resolveCommand() {
|
|||||||
operation := &utils.BPMOperation{
|
operation := &utils.BPMOperation{
|
||||||
Actions: make([]utils.OperationAction, 0),
|
Actions: make([]utils.OperationAction, 0),
|
||||||
UnresolvedDepends: make([]string, 0),
|
UnresolvedDepends: make([]string, 0),
|
||||||
|
Changes: make(map[string]string),
|
||||||
RootDir: rootDir,
|
RootDir: rootDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,6 +563,13 @@ func resolveCommand() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error: could not complete operation: %s\n", err)
|
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:
|
case file:
|
||||||
files := subcommandArgs
|
files := subcommandArgs
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
|
149
utils/hooks.go
Normal file
149
utils/hooks.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"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 _, t1 := range hook.Targets {
|
||||||
|
if targetMet {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if hook.TargetType == "package" {
|
||||||
|
for t2, operation := range packageChanges {
|
||||||
|
if t1 == t2 && slices.Contains(hook.TriggerOperations, operation) {
|
||||||
|
targetMet = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for _, t2 := range modifiedFiles {
|
||||||
|
if strings.HasPrefix(t2.Path, t1) {
|
||||||
|
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"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
type BPMOperation struct {
|
type BPMOperation struct {
|
||||||
Actions []OperationAction
|
Actions []OperationAction
|
||||||
UnresolvedDepends []string
|
UnresolvedDepends []string
|
||||||
|
Changes map[string]string
|
||||||
RootDir string
|
RootDir string
|
||||||
ForceInstallationReason InstallationReason
|
ForceInstallationReason InstallationReason
|
||||||
}
|
}
|
||||||
@ -35,12 +37,35 @@ func (operation *BPMOperation) ActionsContainPackage(pkg string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (operation *BPMOperation) AppendAction(action OperationAction) {
|
||||||
|
operation.InsertActionAt(len(operation.Actions), action)
|
||||||
|
}
|
||||||
|
|
||||||
func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) {
|
func (operation *BPMOperation) InsertActionAt(index int, action OperationAction) {
|
||||||
if len(operation.Actions) == index { // nil or empty slice or after last element
|
if len(operation.Actions) == index { // nil or empty slice or after last element
|
||||||
operation.Actions = append(operation.Actions, action)
|
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) {
|
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 {
|
func (operation *BPMOperation) Execute(verbose, force bool) error {
|
||||||
// Fetch packages from repositories
|
// Fetch packages from repositories
|
||||||
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
if slices.ContainsFunc(operation.Actions, func(action OperationAction) bool {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user