eternity/cmd/main.go

372 lines
10 KiB
Go

package main
import (
"fmt"
"os"
"regexp"
"strings"
"git.oreonproject.org/oreonproject/eternity/common"
"git.oreonproject.org/oreonproject/eternity/lib"
"os/signal"
"path/filepath"
"github.com/Masterminds/semver"
"github.com/sassoftware/go-rpmutils"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: eternity <build/generate/convert/repo/package>")
os.Exit(1)
}
logger := &common.DefaultLogger
for i, arg := range os.Args {
if arg == "--help" || arg == "-h" || (i != 1 && arg == "help") {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "To ask for help, use 'eternity help <build/generate/convert/repo/package>, not --help, -h, or eternity <command> help.",
})
}
}
switch os.Args[1] {
case "build":
inMemory := true
if len(os.Args) > 2 {
if os.Args[2] == "-d" || os.Args[2] == "--disk" {
inMemory = false
}
}
// Allow the user to choose an output dir
var outputDir = "./" // Default output directory
// Loop through arguments to find "-o" or "--output"
for i := 2; i < len(os.Args); i++ {
if os.Args[i] == "-o" || os.Args[i] == "--output" {
// Check if next argument exists and is not another flag
if i+1 < len(os.Args) && !strings.HasPrefix(os.Args[i+1], "-") {
outputDir = os.Args[i+1]
if !filepath.IsAbs(outputDir) {
outputDir, _ = filepath.Abs(outputDir) // Convert to absolute path if relative
}
break
} else {
fmt.Println("FATAL: Output directory not specified or invalid after", os.Args[i])
os.Exit(1)
}
}
}
logger.LogFunc(lib.Log{
Level: "INFO",
Content: "Build directory set to: " + outputDir,
})
privateKey, err, vagueError := common.LoadPrivateKey(logger)
if err != nil || vagueError != nil {
common.LoadPrivateKeyHandleError(err, vagueError, logger)
}
config, err, vagueError := lib.ParseConfig("eternity.json", logger)
if err != nil || vagueError != nil {
common.EternityJsonHandleError(err, vagueError, logger)
} else {
var tempDir string
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func() {
for sig := range c {
fmt.Println("\nCaught", sig, "signal, exiting...")
if tempDir != "" {
common.BuildEPKRemoveTemporaryDirectory(tempDir, logger)
}
os.Exit(1)
}
}()
var size int64
size, tempDir, err, vagueError = lib.BuildEPK("./", inMemory, config.Build, logger)
if err != nil || vagueError != nil {
common.BuildEPKHandleError(tempDir, err, vagueError, logger)
} else {
filePath := filepath.Join(outputDir, config.Metadata.Name+"-"+config.Metadata.Version.String())
config.Metadata.DecompressedSize = size
err, vagueError := lib.PackageEPK(config.Metadata, config.Build, tempDir, filePath+".epk", privateKey, logger)
if err != nil || vagueError != nil {
common.PackageEPKHandleError(err, vagueError, logger)
} else {
common.BuildEPKRemoveTemporaryDirectory(tempDir, logger)
logger.LogFunc(lib.Log{
Level: "INFO",
Content: "Successfully built and packaged EPK at " + filePath + ".epk",
})
}
}
}
case "repo":
if len(os.Args) < 4 {
fmt.Println("Usage: eternity repo <build/generate> <directory>")
fmt.Println("See 'eternity help repo' for more information.")
os.Exit(1)
}
privateKey, err, vagueError := common.LoadPrivateKey(logger)
if err != nil || vagueError != nil {
common.LoadPrivateKeyHandleError(err, vagueError, logger)
}
switch os.Args[2] {
case "generate":
err, vagueError := lib.GenerateRepository(os.Args[3], privateKey, logger)
if err != nil || vagueError != nil {
common.GenerateRepositoryHandleError(err, vagueError, logger)
}
}
case "convert":
if len(os.Args) < 4 {
fmt.Println("Usage: eternity convert </path/to/rpm> [</path/to/output/epk>]")
os.Exit(1)
}
privateKey, err, vagueError := common.LoadPrivateKey(logger)
if err != nil || vagueError != nil {
common.LoadPrivateKeyHandleError(err, vagueError, logger)
}
rpmFile, err := os.Open(os.Args[2])
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error opening RPM: " + err.Error(),
})
}
rpm, err := rpmutils.ReadRpm(rpmFile)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error reading RPM: " + err.Error(),
})
}
name, err := rpm.Header.GetString(rpmutils.NAME)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error getting RPM name: " + err.Error(),
})
}
description, err := rpm.Header.GetString(rpmutils.DESCRIPTION)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error getting RPM description: " + err.Error(),
})
}
longDescription := logger.LogFunc(lib.Log{
Level: "INFO",
Content: "Enter a long description for the package: ",
Prompt: true,
})
version, err := rpm.Header.GetString(rpmutils.VERSION)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error getting RPM version: " + err.Error(),
})
}
release, err := rpm.Header.GetString(rpmutils.RELEASE)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error getting RPM release: " + err.Error(),
})
}
semanticVersion, err := semver.NewVersion(version + "-" + release)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error parsing RPM version and release: " + err.Error(),
})
}
author := logger.LogFunc(lib.Log{
Level: "PROMPT",
Content: "Enter your preferred author name. This must be the same as the author name used in " +
"eternity.json and associated with your keypair, otherwise it will cause issues with EPK" +
" verification and your repository will be rejected by Eon and cannot be trusted.",
Prompt: true,
})
license, err := rpm.Header.GetString(rpmutils.LICENSE)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error getting RPM license: " + err.Error(),
})
}
decompressedSize, err := rpm.Header.GetUint64s(rpmutils.SIZE)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error getting RPM size: " + err.Error(),
})
}
dependenciesRpm, err := rpm.Header.GetStrings(rpmutils.REQUIRENAME)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error getting RPM dependencies: " + err.Error(),
})
}
libRegex := regexp.MustCompile(`^([a-z]+)`)
var dependencies []string
for _, dependency := range dependenciesRpm {
dependencyMatch := libRegex.FindStringSubmatch(dependency)
if len(dependencyMatch) > 1 {
match := dependencyMatch[1]
if match != "rpmlib" && match != "rtld" {
var exists bool
for _, dep := range dependencies {
if dep == match {
exists = true
}
}
if !exists {
dependencies = append(dependencies, match)
}
}
}
}
fmt.Println(dependencies)
architecture, err := rpm.Header.GetString(rpmutils.ARCH)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error getting RPM architecture: " + err.Error(),
})
}
metadata := lib.Metadata{
Name: name,
Description: description,
LongDescription: longDescription,
Version: semanticVersion,
Author: author,
License: license,
Architecture: architecture,
Dependencies: dependencies,
DecompressedSize: int64(decompressedSize[0]),
SpecialFiles: lib.SpecialFiles{},
}
// Output the RPM data to a temporary directory
tempDir, err := os.MkdirTemp("", "eternity-convert-")
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error creating temporary directory: " + err.Error(),
})
}
err = rpm.ExpandPayload(filepath.Join(tempDir, "payload"))
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error expanding RPM payload: " + err.Error(),
})
}
// Package the EPK
err, vagueError = lib.PackageEPK(metadata, lib.Build{
TargetRoot: "payload",
}, tempDir, os.Args[3], privateKey, logger)
if err != nil || vagueError != nil {
common.PackageEPKHandleError(err, vagueError, logger)
} else {
logger.LogFunc(lib.Log{
Level: "INFO",
Content: "Successfully converted RPM to EPK at " + os.Args[3],
})
}
case "init":
// If it's passed with an argument, make a new folder with that name
if len(os.Args) > 2 {
if _, err := os.Stat(os.Args[2]); !os.IsNotExist(err) {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Directory " + os.Args[2] + " already exists.",
})
os.Exit(1)
}
err := os.MkdirAll(os.Args[2], 0755)
if err != nil {
fmt.Println("Error creating directory:", err)
os.Exit(1)
}
logger.LogFunc(lib.Log{
Level: "INFO",
Content: "Created directory " + os.Args[2],
})
err = common.CreateEternityJson(logger, os.Args[2])
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error creating eternity.json: " + err.Error(),
})
os.Exit(1)
}
} else {
// Otherwise, just create a new eternity.json in the current directory
dir, err := os.Getwd()
if err != nil {
fmt.Println("Error getting current directory:", err)
os.Exit(1)
}
err = common.CreateEternityJson(logger, dir)
if err != nil {
logger.LogFunc(lib.Log{
Level: "FATAL",
Content: "Error creating eternity.json: " + err.Error(),
})
os.Exit(1)
}
}
case "help":
if len(os.Args) > 2 {
switch os.Args[2] {
case "build":
fmt.Println("Usage: eternity build [-d/--disk]")
fmt.Println(" -d, --disk Build EPK on disk instead of in memory. This is useful for large EPKs.")
case "convert":
fmt.Println("Usage: eternity convert </path/to/rpm> </path/to/output/epk>")
case "repo":
fmt.Println("Usage: eternity repo <build/generate> <directory>")
fmt.Println(" build Build a repository from a directory containing EPK project directories.")
fmt.Println(" generate Generate a repository from a directory containing EPKs.")
case "package":
fmt.Println("Usage: eternity package <epk>")
case "init":
fmt.Println("Usage: eternity init [directory]")
fmt.Println(" directory Create a new directory with the given name and an eternity.json file. Defaults to the current directory.")
default:
fmt.Println("Usage: eternity help <build/generate/convert/repo/package>")
}
} else {
fmt.Println("Usage: eternity help <build/generate/convert/repo/package>")
}
}
}