Add repository functionality to BPM #4

Merged
EnumDev merged 9 commits from remote-repositories-functionality into master 2024-09-09 08:45:45 +00:00
5 changed files with 280 additions and 16 deletions
Showing only changes of commit 123697e1dc - Show all commits

View File

@ -2,3 +2,7 @@ compilation_env: []
silent_compilation: false silent_compilation: false
compilation_dir: "/var/tmp/" compilation_dir: "/var/tmp/"
binary_output_dir: "/var/lib/bpm/compiled/" binary_output_dir: "/var/lib/bpm/compiled/"
repositories:
- name: example-repository
source: https://my-repo.xyz/
disabled: true

97
main.go
View File

@ -29,10 +29,12 @@ var yesAll = false
var buildSource = false var buildSource = false
var skipCheck = false var skipCheck = false
var keepTempDir = false var keepTempDir = false
var forceInstall = false var force = false
var showInstalled = false var showInstalled = false
var pkgListNumbers = false var pkgListNumbers = false
var pkgListNames = false var pkgListNames = false
var reinstall = false
var nosync = true
func main() { func main() {
utils.ReadConfig() utils.ReadConfig()
@ -48,6 +50,8 @@ const (
info info
list list
install install
update
sync
remove remove
file file
) )
@ -62,6 +66,10 @@ func getCommandType() commandType {
return list return list
case "install": case "install":
return install return install
case "update":
return update
case "sync":
return sync
case "remove": case "remove":
return remove return remove
case "file": case "file":
@ -91,16 +99,23 @@ func resolveCommand() {
continue continue
} }
} else { } else if showInstalled {
info = utils.GetPackageInfo(pkg, rootDir, false) info = utils.GetPackageInfo(pkg, rootDir, false)
if info == nil { if info == nil {
fmt.Printf("Package (%s) could not be found\n", pkg) fmt.Printf("Package (%s) is not installed\n", pkg)
continue continue
} }
} else {
entry, err := utils.GetRepositoryEntry(pkg)
if err != nil {
fmt.Printf("Package (%s) could not be found in any repository\n", pkg)
continue
}
info = &entry.Info
} }
fmt.Println("----------------") fmt.Println("----------------")
if showInstalled { if showInstalled {
fmt.Println(utils.CreateReadableInfo(false, false, true, false, true, info, rootDir)) fmt.Println(utils.CreateReadableInfo(true, true, true, false, true, info, rootDir))
} else { } else {
fmt.Println(utils.CreateReadableInfo(true, true, true, true, true, info, rootDir)) fmt.Println(utils.CreateReadableInfo(true, true, true, true, true, info, rootDir))
} }
@ -164,7 +179,7 @@ func resolveCommand() {
} }
verb = "build" verb = "build"
} }
if !forceInstall { if !force {
if pkgInfo.Arch != "any" && pkgInfo.Arch != utils.GetArch() { if pkgInfo.Arch != "any" && pkgInfo.Arch != utils.GetArch() {
fmt.Printf("skipping... cannot %s a package with a different architecture\n", verb) fmt.Printf("skipping... cannot %s a package with a different architecture\n", verb)
continue continue
@ -226,7 +241,7 @@ func resolveCommand() {
} }
} }
err = utils.InstallPackage(file, rootDir, verbose, forceInstall, buildSource, skipCheck, keepTempDir) err = utils.InstallPackage(file, rootDir, verbose, force, buildSource, skipCheck, keepTempDir)
if err != nil { if err != nil {
if pkgInfo.Type == "source" && keepTempDir { if pkgInfo.Type == "source" && keepTempDir {
fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name) fmt.Println("BPM temp directory was created at /var/tmp/bpm_source-" + pkgInfo.Name)
@ -238,6 +253,34 @@ func resolveCommand() {
fmt.Println("** It is recommended you delete the temporary bpm folder in /var/tmp **") fmt.Println("** It is recommended you delete the temporary bpm folder in /var/tmp **")
} }
} }
case sync:
if os.Getuid() != 0 {
fmt.Println("This subcommand needs to be run with superuser permissions")
os.Exit(0)
}
if !yesAll {
fmt.Printf("Are you sure you wish to sync all databases? [y\\N] ")
reader := bufio.NewReader(os.Stdin)
text, _ := reader.ReadString('\n')
if strings.TrimSpace(strings.ToLower(text)) != "y" && strings.TrimSpace(strings.ToLower(text)) != "yes" {
fmt.Println("Cancelling sync...")
os.Exit(0)
}
}
for _, repo := range utils.BPMConfig.Repositories {
if repo.Disabled {
if verbose {
fmt.Printf("Skipping repository (%s) because it is disabled\n", repo.Name)
}
continue
}
fmt.Printf("Fetching package database for repository (%s)...\n", repo.Name)
err := repo.SyncLocalDatabase()
if err != nil {
log.Fatal(err)
}
}
fmt.Println("All package databases synced successfully!")
case remove: case remove:
if os.Getuid() != 0 { if os.Getuid() != 0 {
fmt.Println("This subcommand needs to be run with superuser permissions") fmt.Println("This subcommand needs to be run with superuser permissions")
@ -344,11 +387,22 @@ func printHelp() {
fmt.Println(" -R=<path> lets you define the root path which will be used") fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt") fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" -f skips dependency and architecture checking") fmt.Println(" -f skips dependency, conflict and architecture checking")
fmt.Println(" -o=<path> set the binary package output directory (defaults to /var/lib/bpm/compiled)") fmt.Println(" -o=<path> set the binary package output directory (defaults to /var/lib/bpm/compiled)")
fmt.Println(" -c=<path> set the compilation directory (defaults to /var/tmp)") fmt.Println(" -c=<path> set the compilation directory (defaults to /var/tmp)")
fmt.Println(" -b creates a binary package from a source package after compilation and saves it in the binary package output directory") fmt.Println(" -b creates a binary package from a source package after compilation and saves it in the binary package output directory")
fmt.Println(" -k keeps the compilation directory created by BPM after source package installation") fmt.Println(" -k keeps the compilation directory created by BPM after source package installation")
fmt.Println("-> bpm update [-R, -v, -y, -f, --reinstall, --nosync] | updates all packages that are available in the repositories")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println(" -f skips dependency, conflict and architecture checking")
fmt.Println(" --reinstall Fetches and reinstalls all packages even if they do not have a newer version available")
fmt.Println(" --nosync Skips package database syncing")
fmt.Println("-> bpm sync [-R, -v] | Syncs package databases without updating packages")
fmt.Println(" -R=<path> lets you define the root path which will be used")
fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -y skips the confirmation prompt")
fmt.Println("-> bpm remove [-R, -v, -y] <packages...> | removes the following packages") fmt.Println("-> bpm remove [-R, -v, -y] <packages...> | removes the following packages")
fmt.Println(" -v Show additional information about what BPM is doing") fmt.Println(" -v Show additional information about what BPM is doing")
fmt.Println(" -R=<path> lets you define the root path which will be used") fmt.Println(" -R=<path> lets you define the root path which will be used")
@ -380,8 +434,23 @@ func resolveFlags() {
installFlagSet.BoolVar(&buildSource, "b", false, "Build binary package from source package") installFlagSet.BoolVar(&buildSource, "b", false, "Build binary package from source package")
installFlagSet.BoolVar(&skipCheck, "s", false, "Skip check function during source compilation") installFlagSet.BoolVar(&skipCheck, "s", false, "Skip check function during source compilation")
installFlagSet.BoolVar(&keepTempDir, "k", false, "Keep temporary directory after source compilation") installFlagSet.BoolVar(&keepTempDir, "k", false, "Keep temporary directory after source compilation")
installFlagSet.BoolVar(&forceInstall, "f", false, "Force installation by skipping architecture and dependency resolution") installFlagSet.BoolVar(&force, "f", false, "Force installation by skipping architecture and dependency resolution")
installFlagSet.Usage = printHelp installFlagSet.Usage = printHelp
// Update flags
updateFlagSet := flag.NewFlagSet("Update flags", flag.ExitOnError)
updateFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
updateFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
updateFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
updateFlagSet.BoolVar(&force, "f", false, "Force update by skipping architecture and dependency resolution")
updateFlagSet.BoolVar(&reinstall, "reinstall", false, "Fetches and reinstalls all packages even if they do not have a newer version available")
updateFlagSet.BoolVar(&nosync, "nosync", false, "Skips package database syncing")
updateFlagSet.Usage = printHelp
// Sync flags
syncFlagSet := flag.NewFlagSet("Sync flags", flag.ExitOnError)
syncFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
syncFlagSet.BoolVar(&verbose, "v", false, "Show additional information about what BPM is doing")
syncFlagSet.BoolVar(&yesAll, "y", false, "Skip confirmation prompts")
syncFlagSet.Usage = printHelp
// Remove flags // Remove flags
removeFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError) removeFlagSet := flag.NewFlagSet("Remove flags", flag.ExitOnError)
removeFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root") removeFlagSet.StringVar(&rootDir, "R", "/", "Set the destination root")
@ -415,6 +484,18 @@ func resolveFlags() {
return return
} }
subcommandArgs = installFlagSet.Args() subcommandArgs = installFlagSet.Args()
} else if getCommandType() == update {
err := updateFlagSet.Parse(subcommandArgs)
if err != nil {
return
}
subcommandArgs = updateFlagSet.Args()
} else if getCommandType() == sync {
err := syncFlagSet.Parse(subcommandArgs)
if err != nil {
return
}
subcommandArgs = syncFlagSet.Args()
} else if getCommandType() == remove { } else if getCommandType() == remove {
err := removeFlagSet.Parse(subcommandArgs) err := removeFlagSet.Parse(subcommandArgs)
if err != nil { if err != nil {

View File

@ -2,14 +2,16 @@ package utils
import ( import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"log"
"os" "os"
) )
type BPMConfigStruct struct { type BPMConfigStruct struct {
CompilationEnv []string `yaml:"compilation_env"` CompilationEnv []string `yaml:"compilation_env"`
SilentCompilation bool `yaml:"silent_compilation"` SilentCompilation bool `yaml:"silent_compilation"`
BinaryOutputDir string `yaml:"binary_output_dir"` BinaryOutputDir string `yaml:"binary_output_dir"`
CompilationDir string `yaml:"compilation_dir"` CompilationDir string `yaml:"compilation_dir"`
Repositories []*Repository `yaml:"repositories"`
} }
var BPMConfig BPMConfigStruct = BPMConfigStruct{ var BPMConfig BPMConfigStruct = BPMConfigStruct{
@ -21,14 +23,21 @@ var BPMConfig BPMConfigStruct = BPMConfigStruct{
func ReadConfig() { func ReadConfig() {
if _, err := os.Stat("/etc/bpm.conf"); os.IsNotExist(err) { if _, err := os.Stat("/etc/bpm.conf"); os.IsNotExist(err) {
return log.Fatal(err)
} }
bytes, err := os.ReadFile("/etc/bpm.conf") bytes, err := os.ReadFile("/etc/bpm.conf")
if err != nil { if err != nil {
return log.Fatal(err)
} }
err = yaml.Unmarshal(bytes, &BPMConfig) err = yaml.Unmarshal(bytes, &BPMConfig)
if err != nil { if err != nil {
return log.Fatal(err)
}
for _, repo := range BPMConfig.Repositories {
repo.Entries = make(map[string]*RepositoryEntry)
err := repo.ReadLocalDatabase()
if err != nil {
log.Fatal(err)
}
} }
} }

View File

@ -403,6 +403,15 @@ func CreateReadableInfo(showArchitecture, showType, showPackageRelations, showRe
appendMap("Conditional conflicting packages", pkgInfo.ConditionalConflicts) appendMap("Conditional conflicting packages", pkgInfo.ConditionalConflicts)
appendMap("Conditional optional dependencies", pkgInfo.ConditionalOptional) appendMap("Conditional optional dependencies", pkgInfo.ConditionalOptional)
} }
if showRemoteInfo {
arr := make([]string, 0)
for _, repo := range BPMConfig.Repositories {
if repo.ContainsPackage(pkgInfo.Name) {
arr = append(arr, repo.Name)
}
}
appendArray("Repositories", arr)
}
if showInstallationReason { if showInstallationReason {
if IsPackageInstalled(pkgInfo.Name, rootDir) { if IsPackageInstalled(pkgInfo.Name, rootDir) {
ret = append(ret, "Installed: yes") ret = append(ret, "Installed: yes")

161
utils/repo_utils.go Normal file
View File

@ -0,0 +1,161 @@
package utils
import (
"errors"
"gopkg.in/yaml.v3"
"io"
"net/http"
"net/url"
"os"
"path"
"strings"
)
type Repository struct {
Name string `yaml:"name"`
Source string `yaml:"source"`
Disabled bool `yaml:"disabled"`
Entries map[string]*RepositoryEntry
}
type RepositoryEntry struct {
Info PackageInfo `yaml:"info"`
Download string `yaml:"download"`
}
func (repo *Repository) ContainsPackage(pkg string) bool {
_, ok := repo.Entries[pkg]
return ok
}
func (repo *Repository) ReadLocalDatabase() error {
repoFile := "/var/lib/bpm/repositories/" + repo.Name + ".bpmdb"
if _, err := os.Stat(repoFile); err != nil {
return nil
}
bytes, err := os.ReadFile(repoFile)
if err != nil {
return err
}
data := string(bytes)
for _, b := range strings.Split(data, "---") {
entry := RepositoryEntry{
Info: PackageInfo{
Name: "",
Description: "",
Version: "",
Url: "",
License: "",
Arch: "",
Type: "",
Keep: make([]string, 0),
Depends: make([]string, 0),
ConditionalDepends: make(map[string][]string),
MakeDepends: make([]string, 0),
ConditionalMakeDepends: make(map[string][]string),
Conflicts: make([]string, 0),
ConditionalConflicts: make(map[string][]string),
Optional: make([]string, 0),
ConditionalOptional: make(map[string][]string),
Provides: make([]string, 0),
},
Download: "",
}
err := yaml.Unmarshal([]byte(b), &entry)
if err != nil {
return err
}
repo.Entries[entry.Info.Name] = &entry
}
return nil
}
func (repo *Repository) SyncLocalDatabase() error {
repoFile := "/var/lib/bpm/repositories/" + repo.Name + ".bpmdb"
err := os.MkdirAll(path.Dir(repoFile), 0755)
if err != nil {
return err
}
u, err := url.JoinPath(repo.Source, "database.bpmdb")
if err != nil {
return err
}
resp, err := http.Get(u)
if err != nil {
return err
}
defer resp.Body.Close()
out, err := os.Create(repoFile)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return nil
}
func GetRepository(name string) *Repository {
for _, repo := range BPMConfig.Repositories {
if repo.Name == name {
return repo
}
}
return nil
}
func GetRepositoryEntry(str string) (*RepositoryEntry, error) {
split := strings.Split(str, "/")
if len(split) == 1 {
pkgName := strings.TrimSpace(split[0])
if pkgName == "" {
return nil, errors.New("could not find repository entry for this package")
}
for _, repo := range BPMConfig.Repositories {
if repo.ContainsPackage(pkgName) {
return repo.Entries[pkgName], nil
}
}
return nil, errors.New("could not find repository entry for this package")
} else if len(split) == 2 {
repoName := strings.TrimSpace(split[0])
pkgName := strings.TrimSpace(split[1])
if repoName == "" || pkgName == "" {
return nil, errors.New("could not find repository entry for this package")
}
repo := GetRepository(repoName)
if repo == nil || !repo.ContainsPackage(pkgName) {
return nil, errors.New("could not find repository entry for this package")
}
return repo.Entries[pkgName], nil
} else {
return nil, errors.New("could not find repository entry for this package")
}
}
func (repo *Repository) FetchPackage(pkg, savePath string) (string, error) {
if !repo.ContainsPackage(pkg) {
return "", errors.New("Could not fetch package '" + pkg + "'")
}
entry := repo.Entries[pkg]
resp, err := http.Get(entry.Download)
if err != nil {
return "", err
}
defer resp.Body.Close()
out, err := os.Create(savePath)
if err != nil {
return "", err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return savePath, nil
}