Initial Commit
This commit is contained in:
commit
83a4424ace
|
@ -0,0 +1 @@
|
||||||
|
/lure
|
|
@ -0,0 +1,5 @@
|
||||||
|
# LURE (Linux User REpository)
|
||||||
|
|
||||||
|
LURE is intended to bring the AUR to all distros. It is currently in an ***alpha*** state and may not be stable. It can download a repository, build packages in it using a bash script similar to [PKGBUILD](https://wiki.archlinux.org/title/PKGBUILD), and then install them using your system package manager.
|
||||||
|
|
||||||
|
LURE is written in pure Go and has zero dependencies after it's built. The only things LURE needs are a command for privilege elevation such as `sudo`, `doas`, etc. as well as a supported package manager. Currently, LURE supports `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. If a supported package manager exists on your system, it will be detected and used automatically.
|
|
@ -0,0 +1,383 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/apk"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/arch"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/deb"
|
||||||
|
_ "github.com/goreleaser/nfpm/v2/rpm"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"golang.org/x/sys/cpu"
|
||||||
|
|
||||||
|
"github.com/goreleaser/nfpm/v2"
|
||||||
|
"github.com/goreleaser/nfpm/v2/files"
|
||||||
|
"go.arsenm.dev/lure/distro"
|
||||||
|
"go.arsenm.dev/lure/download"
|
||||||
|
"go.arsenm.dev/lure/internal/shutils/decoder"
|
||||||
|
"go.arsenm.dev/lure/manager"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BuildVars represents the script variables required
|
||||||
|
// to build a package
|
||||||
|
type BuildVars struct {
|
||||||
|
Name string `sh:"name,required"`
|
||||||
|
Version string `sh:"version,required"`
|
||||||
|
Release int `sh:"release,required"`
|
||||||
|
Epoch uint `sh:"epoch"`
|
||||||
|
Description string `sh:"desc"`
|
||||||
|
Homepage string `sh:"homepage"`
|
||||||
|
Architectures []string `sh:"architectures"`
|
||||||
|
Licenses []string `sh:"license"`
|
||||||
|
Provides []string `sh:"provides"`
|
||||||
|
Conflicts []string `sh:"conflicts"`
|
||||||
|
Depends []string `sh:"deps"`
|
||||||
|
BuildDepends []string `sh:"build_deps"`
|
||||||
|
Replaces []string `sh:"replaces"`
|
||||||
|
Sources []string `sh:"sources"`
|
||||||
|
Checksums []string `sh:"checksums"`
|
||||||
|
Backup []string `sh:"backup"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCmd(c *cli.Context) error {
|
||||||
|
script := c.String("script")
|
||||||
|
|
||||||
|
mgr := manager.Detect()
|
||||||
|
if mgr == nil {
|
||||||
|
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
_, pkgNames, err := buildPackage(c.Context, script, mgr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error building package").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Package(s) built successfully").Any("names", pkgNames).Send()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPackage(ctx context.Context, script string, mgr manager.Manager) ([]string, []string, error) {
|
||||||
|
info, err := distro.ParseOSRelease(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fl, err := os.Open(script)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fl.Close()
|
||||||
|
|
||||||
|
env := genBuildEnv(info)
|
||||||
|
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.Env(expand.ListEnviron(env...)),
|
||||||
|
interp.StdIO(os.Stdin, os.Stdout, os.Stderr),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(ctx, file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := decoder.New(info, runner)
|
||||||
|
|
||||||
|
var vars BuildVars
|
||||||
|
err = dec.DecodeVars(&vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Building package").Str("name", vars.Name).Str("version", vars.Version).Send()
|
||||||
|
|
||||||
|
baseDir := filepath.Join(cacheDir, "pkgs", vars.Name)
|
||||||
|
srcdir := filepath.Join(baseDir, "src")
|
||||||
|
pkgdir := filepath.Join(baseDir, "pkg")
|
||||||
|
|
||||||
|
err = os.RemoveAll(baseDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(srcdir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(pkgdir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(vars.BuildDepends) > 0 {
|
||||||
|
log.Info("Installing build dependencies").Send()
|
||||||
|
installPkgs(ctx, vars.BuildDepends, mgr)
|
||||||
|
}
|
||||||
|
|
||||||
|
var builtDeps, builtNames, repoDeps []string
|
||||||
|
if len(vars.Depends) > 0 {
|
||||||
|
log.Info("Installing dependencies").Send()
|
||||||
|
|
||||||
|
scripts, notFound := findPkgs(vars.Depends)
|
||||||
|
for _, script := range scripts {
|
||||||
|
pkgPaths, pkgNames, err := buildPackage(ctx, script, mgr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
builtDeps = append(builtDeps, pkgPaths...)
|
||||||
|
builtNames = append(builtNames, pkgNames...)
|
||||||
|
builtNames = append(builtNames, filepath.Base(filepath.Dir(script)))
|
||||||
|
}
|
||||||
|
repoDeps = notFound
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Downloading sources").Send()
|
||||||
|
|
||||||
|
err = getSources(ctx, srcdir, &vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setDirVars(ctx, runner, srcdir, pkgdir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fn, ok := dec.GetFunc("build")
|
||||||
|
if ok {
|
||||||
|
log.Info("Executing build()").Send()
|
||||||
|
|
||||||
|
err = fn(ctx, srcdir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn, ok = dec.GetFunc("package")
|
||||||
|
if ok {
|
||||||
|
log.Info("Executing package()").Send()
|
||||||
|
|
||||||
|
err = fn(ctx, srcdir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq(
|
||||||
|
&repoDeps,
|
||||||
|
&builtDeps,
|
||||||
|
&builtNames,
|
||||||
|
)
|
||||||
|
|
||||||
|
pkgInfo := &nfpm.Info{
|
||||||
|
Name: vars.Name,
|
||||||
|
Description: vars.Description,
|
||||||
|
Arch: runtime.GOARCH,
|
||||||
|
Version: vars.Version,
|
||||||
|
Release: strconv.Itoa(vars.Release),
|
||||||
|
Homepage: vars.Homepage,
|
||||||
|
License: strings.Join(vars.Licenses, ", "),
|
||||||
|
Overridables: nfpm.Overridables{
|
||||||
|
Depends: append(repoDeps, builtNames...),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if pkgInfo.Arch == "arm" {
|
||||||
|
pkgInfo.Arch = checkARMVariant()
|
||||||
|
}
|
||||||
|
|
||||||
|
contents := []*files.Content{}
|
||||||
|
filepath.Walk(pkgdir, func(path string, fi os.FileInfo, err error) error {
|
||||||
|
trimmed := strings.TrimPrefix(path, pkgdir)
|
||||||
|
|
||||||
|
if fi.IsDir() {
|
||||||
|
f, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f.Readdirnames(1)
|
||||||
|
if err != io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = append(contents, &files.Content{
|
||||||
|
Source: path,
|
||||||
|
Destination: trimmed,
|
||||||
|
Type: "dir",
|
||||||
|
FileInfo: &files.ContentFileInfo{
|
||||||
|
MTime: fi.ModTime(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.Mode()&os.ModeSymlink != 0 {
|
||||||
|
link, err := os.Readlink(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = append(contents, &files.Content{
|
||||||
|
Source: link,
|
||||||
|
Destination: trimmed,
|
||||||
|
Type: "symlink",
|
||||||
|
FileInfo: &files.ContentFileInfo{
|
||||||
|
MTime: fi.ModTime(),
|
||||||
|
Mode: fi.Mode(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fileContent := &files.Content{
|
||||||
|
Source: path,
|
||||||
|
Destination: trimmed,
|
||||||
|
FileInfo: &files.ContentFileInfo{
|
||||||
|
MTime: fi.ModTime(),
|
||||||
|
Mode: fi.Mode(),
|
||||||
|
Size: fi.Size(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if slices.Contains(vars.Backup, trimmed) {
|
||||||
|
fileContent.Type = "config|noreplace"
|
||||||
|
}
|
||||||
|
|
||||||
|
contents = append(contents, fileContent)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
pkgInfo.Overridables.Contents = contents
|
||||||
|
|
||||||
|
packager, err := nfpm.Get(mgr.Format())
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkgName := packager.ConventionalFileName(pkgInfo)
|
||||||
|
pkgPath := filepath.Join(baseDir, pkgName)
|
||||||
|
|
||||||
|
pkgPaths := append(builtDeps, pkgPath)
|
||||||
|
pkgNames := append(builtNames, vars.Name)
|
||||||
|
|
||||||
|
pkgFile, err := os.Create(pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = packager.Package(pkgInfo, pkgFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
uniq(&pkgPaths, &pkgNames)
|
||||||
|
|
||||||
|
return pkgPaths, pkgNames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func genBuildEnv(info *distro.OSRelease) []string {
|
||||||
|
env := os.Environ()
|
||||||
|
env = append(
|
||||||
|
env,
|
||||||
|
"DISTRO_NAME="+info.Name,
|
||||||
|
"DISTRO_PRETTY_NAME="+info.PrettyName,
|
||||||
|
"DISTRO_ID="+info.ID,
|
||||||
|
"DISTRO_BUILD_ID="+info.BuildID,
|
||||||
|
|
||||||
|
"ARCH="+runtime.GOARCH,
|
||||||
|
"NCPU="+strconv.Itoa(runtime.NumCPU()),
|
||||||
|
)
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSources(ctx context.Context, srcdir string, bv *BuildVars) error {
|
||||||
|
if len(bv.Sources) != len(bv.Checksums) {
|
||||||
|
log.Fatal("The checksums array must be the same length as sources")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, src := range bv.Sources {
|
||||||
|
opts := download.GetOptions{
|
||||||
|
SourceURL: src,
|
||||||
|
Destination: srcdir,
|
||||||
|
EncloseGit: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if bv.Checksums[i] != "SKIP" {
|
||||||
|
checksum, err := hex.DecodeString(bv.Checksums[i])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.SHA256Sum = checksum
|
||||||
|
}
|
||||||
|
|
||||||
|
err := download.Get(ctx, opts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDirVars sets srcdir and pkgdir. It's a very hacky way of doing so,
|
||||||
|
// but setting the runner's Env and Vars fields doesn't seem to work.
|
||||||
|
func setDirVars(ctx context.Context, runner *interp.Runner, srcdir, pkgdir string) error {
|
||||||
|
cmd := "srcdir='" + srcdir + "'\npkgdir='" + pkgdir + "'\n"
|
||||||
|
fl, err := syntax.NewParser().Parse(strings.NewReader(cmd), "vars")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return runner.Run(ctx, fl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkARMVariant checks which variant of ARM lure is running
|
||||||
|
// on, by using the same detection method as Go itself
|
||||||
|
func checkARMVariant() string {
|
||||||
|
armEnv := os.Getenv("LURE_ARM_VARIANT")
|
||||||
|
// ensure value has "arm" prefix, such as arm5 or arm6
|
||||||
|
if strings.HasPrefix(armEnv, "arm") {
|
||||||
|
return armEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
if cpu.ARM.HasVFPv3 {
|
||||||
|
return "arm7"
|
||||||
|
} else if cpu.ARM.HasVFP {
|
||||||
|
return "arm6"
|
||||||
|
} else {
|
||||||
|
return "arm5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func uniq(ss ...*[]string) {
|
||||||
|
for _, s := range ss {
|
||||||
|
slices.Sort(*s)
|
||||||
|
*s = slices.Compact(*s)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"go.arsenm.dev/lure/manager"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cacheDir string
|
||||||
|
cfgPath string
|
||||||
|
config Config
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
RootCmd string `toml:"rootCmd"`
|
||||||
|
Repos []Repo `toml:"repo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo struct {
|
||||||
|
Name string `toml:"name"`
|
||||||
|
URL string `toml:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultConfig = Config{
|
||||||
|
RootCmd: "sudo",
|
||||||
|
Repos: []Repo{
|
||||||
|
{
|
||||||
|
Name: "default",
|
||||||
|
URL: "https://github.com/Arsen6331/lure-repo.git",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cfg, cache, err := makeDirs()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error creating directories").Err(err).Send()
|
||||||
|
}
|
||||||
|
cacheDir = cache
|
||||||
|
|
||||||
|
cfgPath = filepath.Join(cfg, "lure.toml")
|
||||||
|
|
||||||
|
cfgFl, err := os.Open(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
defer cfgFl.Close()
|
||||||
|
|
||||||
|
err = toml.NewDecoder(cfgFl).Decode(&config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error decoding config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.DefaultRootCmd = config.RootCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeDirs() (string, string, error) {
|
||||||
|
cfgDir, err := os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseCfgPath := filepath.Join(cfgDir, "lure")
|
||||||
|
|
||||||
|
err = os.MkdirAll(baseCfgPath, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgPath := filepath.Join(baseCfgPath, "lure.toml")
|
||||||
|
|
||||||
|
if _, err := os.Stat(cfgPath); err != nil {
|
||||||
|
cfgFl, err := os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgFl.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheDir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
baseCachePath := filepath.Join(cacheDir, "lure")
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Join(baseCachePath, "repo"), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Join(baseCachePath, "pkgs"), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseCfgPath, baseCachePath, nil
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
package distro
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.arsenm.dev/lure/internal/shutils"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrParse = errors.New("could not parse os-release file")
|
||||||
|
|
||||||
|
type OSRelease struct {
|
||||||
|
Name string
|
||||||
|
PrettyName string
|
||||||
|
ID string
|
||||||
|
BuildID string
|
||||||
|
ANSIColor string
|
||||||
|
HomeURL string
|
||||||
|
DocumentationURL string
|
||||||
|
SupportURL string
|
||||||
|
BugReportURL string
|
||||||
|
Logo string
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSReleaseName returns the NAME field of the
|
||||||
|
func ParseOSRelease(ctx context.Context) (*OSRelease, error) {
|
||||||
|
fl, err := os.Open("/usr/lib/os-release")
|
||||||
|
if err != nil {
|
||||||
|
fl, err = os.Open("/etc/os-release")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := syntax.NewParser().Parse(fl, "/usr/lib/os-release")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fl.Close()
|
||||||
|
|
||||||
|
// Create new shell interpreter with nop open, exec, readdir, and stat handlers
|
||||||
|
// as well as no environment variables in order to prevent vulnerabilities
|
||||||
|
// caused by changing the os-release file.
|
||||||
|
runner, err := interp.New(
|
||||||
|
interp.OpenHandler(shutils.NopOpen),
|
||||||
|
interp.ExecHandler(shutils.NopExec),
|
||||||
|
interp.ReadDirHandler(shutils.NopReadDir),
|
||||||
|
interp.StatHandler(shutils.NopStat),
|
||||||
|
interp.Env(expand.ListEnviron()),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(ctx, file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, ErrParse
|
||||||
|
}
|
||||||
|
|
||||||
|
return &OSRelease{
|
||||||
|
Name: runner.Vars["NAME"].Str,
|
||||||
|
PrettyName: runner.Vars["PRETTY_NAME"].Str,
|
||||||
|
ID: runner.Vars["ID"].Str,
|
||||||
|
BuildID: runner.Vars["BUILD_ID"].Str,
|
||||||
|
ANSIColor: runner.Vars["ANSI_COLOR"].Str,
|
||||||
|
HomeURL: runner.Vars["HOME_URL"].Str,
|
||||||
|
DocumentationURL: runner.Vars["DOCUMENTATION_URL"].Str,
|
||||||
|
SupportURL: runner.Vars["SUPPORT_URL"].Str,
|
||||||
|
BugReportURL: runner.Vars["BUG_REPORT_URL"].Str,
|
||||||
|
Logo: runner.Vars["LOGO"].Str,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -0,0 +1,223 @@
|
||||||
|
package download
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
|
"github.com/mholt/archiver/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrChecksumMismatch = errors.New("checksums did not match")
|
||||||
|
|
||||||
|
type GetOptions struct {
|
||||||
|
SourceURL string
|
||||||
|
Destination string
|
||||||
|
SHA256Sum []byte
|
||||||
|
// EncloseGit determines if Get will create an enclosing
|
||||||
|
// directory for git repos
|
||||||
|
EncloseGit bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get downloads from a URL
|
||||||
|
func Get(ctx context.Context, opts GetOptions) error {
|
||||||
|
dest, err := filepath.Abs(opts.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
opts.Destination = dest
|
||||||
|
|
||||||
|
err = os.MkdirAll(opts.Destination, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := url.Parse(opts.SourceURL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
query := src.Query()
|
||||||
|
|
||||||
|
tag := query.Get("~tag")
|
||||||
|
query.Del("~tag")
|
||||||
|
|
||||||
|
branch := src.Query().Get("~branch")
|
||||||
|
query.Del("~branch")
|
||||||
|
|
||||||
|
commit := src.Query().Get("~commit")
|
||||||
|
query.Del("~commit")
|
||||||
|
|
||||||
|
var refName plumbing.ReferenceName
|
||||||
|
if tag != "" {
|
||||||
|
refName = plumbing.NewTagReferenceName(tag)
|
||||||
|
} else if branch != "" {
|
||||||
|
refName = plumbing.NewBranchReferenceName(branch)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(src.Scheme, "git+") {
|
||||||
|
src.Scheme = strings.TrimPrefix(src.Scheme, "git+")
|
||||||
|
src.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
name := path.Base(src.Path)
|
||||||
|
name = strings.TrimSuffix(name, ".git")
|
||||||
|
|
||||||
|
dstDir := opts.Destination
|
||||||
|
if opts.EncloseGit {
|
||||||
|
dstDir = filepath.Join(opts.Destination, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := git.PlainCloneContext(ctx, dstDir, false, &git.CloneOptions{
|
||||||
|
URL: src.String(),
|
||||||
|
Progress: os.Stderr,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := repo.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
checkoutOpts := &git.CheckoutOptions{}
|
||||||
|
if refName != "" {
|
||||||
|
checkoutOpts.Branch = refName
|
||||||
|
} else if commit != "" {
|
||||||
|
checkoutOpts.Hash = plumbing.NewHash(commit)
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Checkout(checkoutOpts)
|
||||||
|
} else {
|
||||||
|
name := query.Get("~name")
|
||||||
|
query.Del("~name")
|
||||||
|
|
||||||
|
archive := query.Get("~archive")
|
||||||
|
query.Del("~archive")
|
||||||
|
|
||||||
|
src.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
name = path.Base(src.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, src.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha256.New()
|
||||||
|
|
||||||
|
format, input, err := archiver.Identify(name, res.Body)
|
||||||
|
if err == archiver.ErrNoMatch || archive == "false" {
|
||||||
|
fl, err := os.Create(filepath.Join(opts.Destination, name))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w := io.MultiWriter(hash, fl)
|
||||||
|
|
||||||
|
_, err = io.Copy(w, input)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Body.Close()
|
||||||
|
fl.Close()
|
||||||
|
|
||||||
|
if opts.SHA256Sum != nil {
|
||||||
|
sum := hash.Sum(nil)
|
||||||
|
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||||
|
return ErrChecksumMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
r := io.TeeReader(input, hash)
|
||||||
|
fname := format.Name()
|
||||||
|
|
||||||
|
switch format := format.(type) {
|
||||||
|
case archiver.Extractor:
|
||||||
|
err = format.Extract(ctx, r, nil, func(ctx context.Context, f archiver.File) error {
|
||||||
|
fr, err := f.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fr.Close()
|
||||||
|
|
||||||
|
path := filepath.Join(opts.Destination, f.NameInArchive)
|
||||||
|
|
||||||
|
err = os.MkdirAll(filepath.Dir(path), 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.IsDir() {
|
||||||
|
err = os.Mkdir(path, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
outFl, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer outFl.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(outFl, fr)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case archiver.Decompressor:
|
||||||
|
rc, err := format.OpenReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
path := filepath.Join(opts.Destination, name)
|
||||||
|
path = strings.TrimSuffix(path, fname)
|
||||||
|
|
||||||
|
outFl, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(outFl, rc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.SHA256Sum != nil {
|
||||||
|
sum := hash.Sum(nil)
|
||||||
|
if !bytes.Equal(opts.SHA256Sum, sum) {
|
||||||
|
return ErrChecksumMismatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
module go.arsenm.dev/lure
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
|
replace github.com/goreleaser/nfpm/v2 => github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.6
|
||||||
|
github.com/go-git/go-git/v5 v5.4.2
|
||||||
|
github.com/goreleaser/nfpm/v2 v2.18.1
|
||||||
|
github.com/mholt/archiver/v4 v4.0.0-alpha.7
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5
|
||||||
|
github.com/urfave/cli/v2 v2.16.3
|
||||||
|
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a
|
||||||
|
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
|
||||||
|
mvdan.cc/sh/v3 v3.5.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/AlekSi/pointer v1.2.0 // indirect
|
||||||
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||||
|
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||||
|
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c // indirect
|
||||||
|
github.com/acomagu/bufpipe v1.0.3 // indirect
|
||||||
|
github.com/andybalholm/brotli v1.0.4 // indirect
|
||||||
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
|
||||||
|
github.com/cavaliergopher/cpio v1.0.1 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
|
github.com/dsnet/compress v0.0.1 // indirect
|
||||||
|
github.com/emirpasic/gods v1.12.0 // indirect
|
||||||
|
github.com/frankban/quicktest v1.14.3 // indirect
|
||||||
|
github.com/go-git/gcfg v1.5.0 // indirect
|
||||||
|
github.com/go-git/go-billy/v5 v5.3.1 // indirect
|
||||||
|
github.com/gobwas/glob v0.2.3 // indirect
|
||||||
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
|
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/gookit/color v1.5.1 // indirect
|
||||||
|
github.com/goreleaser/chglog v0.2.2 // indirect
|
||||||
|
github.com/goreleaser/fileglob v1.3.0 // indirect
|
||||||
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
|
github.com/imdario/mergo v0.3.13 // indirect
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
|
github.com/kevinburke/ssh_config v1.1.0 // indirect
|
||||||
|
github.com/klauspost/compress v1.15.5 // indirect
|
||||||
|
github.com/klauspost/pgzip v1.2.5 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.2 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.14 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/sergi/go-diff v1.2.0 // indirect
|
||||||
|
github.com/therootcompany/xz v1.0.1 // indirect
|
||||||
|
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||||
|
github.com/xanzy/ssh-agent v0.3.1 // indirect
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
|
||||||
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
|
||||||
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||||
|
golang.org/x/text v0.3.7 // indirect
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,252 @@
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw=
|
||||||
|
github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
|
||||||
|
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
|
||||||
|
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
|
||||||
|
github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5 h1:SFWe7Ho60w43hXEIxCdiAXZvUyM9GF/L90jMK42s8gU=
|
||||||
|
github.com/Arsen6331/nfpm/v2 v2.0.0-20220922210414-eae88e8ea4b5/go.mod h1:O4K1mvEORY78CSCInptGG5MWJ19yr9xFTgWWUtY1R7o=
|
||||||
|
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
|
||||||
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
|
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||||
|
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
|
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||||
|
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||||
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
|
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
|
||||||
|
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||||
|
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
|
||||||
|
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
|
||||||
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c h1:bNpaLLv2Y4kslsdkdCwAYu8Bak1aGVtxwi8Z/wy4Yuo=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
|
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f h1:CGq7OieOz3wyQJ1fO8S0eO9TCW1JyvLrf8fhzz1i8ko=
|
||||||
|
github.com/ProtonMail/gopenpgp/v2 v2.2.2 h1:u2m7xt+CZWj88qK1UUNBoXeJCFJwJCZ/Ff4ymGoxEXs=
|
||||||
|
github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
|
||||||
|
github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
|
||||||
|
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||||
|
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
|
||||||
|
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||||
|
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
|
||||||
|
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
|
||||||
|
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
|
||||||
|
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
|
||||||
|
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||||
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||||
|
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||||
|
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||||
|
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||||
|
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||||
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||||
|
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
|
||||||
|
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||||
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
|
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||||
|
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||||
|
github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
|
github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
|
||||||
|
github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
|
||||||
|
github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
|
||||||
|
github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
|
||||||
|
github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
|
||||||
|
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
|
||||||
|
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||||
|
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||||
|
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
|
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||||
|
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e h1:6Jn9JtfCn20uycra92LxTkq5yfBKNSFlRJPBk8/Cxhg=
|
||||||
|
github.com/google/rpmpack v0.0.0-20220314092521-38642b5e571e/go.mod h1:83rLnx5vhPyN/mDzBYJWtiPf+9xnSVQynTpqZWe7OnY=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
|
||||||
|
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
|
github.com/goreleaser/chglog v0.2.2 h1:V7nf07baXtGAgGevvqgW2MM4kZ6gOr12vKNSAU3VIZ0=
|
||||||
|
github.com/goreleaser/chglog v0.2.2/go.mod h1:2s5JwtCOWjZa8AIneL+xdUl9SRuigCjRHNHsX30dupE=
|
||||||
|
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
|
||||||
|
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
|
||||||
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
|
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||||
|
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||||
|
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||||
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
|
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
|
github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/kevinburke/ssh_config v1.1.0 h1:pH/t1WS9NzT8go394IqZeJTMHVm6Cr6ZJ6AQ+mdNo/o=
|
||||||
|
github.com/kevinburke/ssh_config v1.1.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
|
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||||
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
|
github.com/klauspost/compress v1.15.5 h1:qyCLMz2JCrKADihKOh9FxnW3houKeNsp2h5OEz0QSEA=
|
||||||
|
github.com/klauspost/compress v1.15.5/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
|
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||||
|
github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
|
||||||
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
|
||||||
|
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||||
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
|
github.com/mholt/archiver/v4 v4.0.0-alpha.7 h1:xzByj8G8tj0Oq7ZYYU4+ixL/CVb5ruWCm0EZQ1PjOkE=
|
||||||
|
github.com/mholt/archiver/v4 v4.0.0-alpha.7/go.mod h1:Fs8qUkO74HHaidabihzYephJH8qmGD/nCP6tE5xC9BM=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||||
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||||
|
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
|
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
|
||||||
|
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||||
|
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||||
|
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||||
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
|
github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs=
|
||||||
|
github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||||
|
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||||
|
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||||
|
github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
|
||||||
|
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||||
|
github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk=
|
||||||
|
github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
|
||||||
|
github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
|
||||||
|
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
|
||||||
|
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
|
||||||
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
|
||||||
|
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
|
||||||
|
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a h1:9SF7Af1OnN9BlWk9yNYCZuv/uAhDMhzfZxtWJEqy6EE=
|
||||||
|
go.arsenm.dev/logger v0.0.0-20220630204155-5ba23e583f0a/go.mod h1:RV2qydKDdoyaRkhAq8JEGvojR8eJ6bjq5WnSIlH7gYw=
|
||||||
|
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA=
|
||||||
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b h1:SCE/18RnFsLrjydh/R/s5EVvHoZprqEQUuoxK8q2Pc4=
|
||||||
|
golang.org/x/exp v0.0.0-20220916125017-b168a2c6b86b/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||||
|
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
mvdan.cc/sh/v3 v3.5.1 h1:hmP3UOw4f+EYexsJjFxvU38+kn+V/s2CclXHanIBkmQ=
|
||||||
|
mvdan.cc/sh/v3 v3.5.1/go.mod h1:1JcoyAKm1lZw/2bZje/iYKWicU/KMd0rsyJeKHnsK4E=
|
|
@ -0,0 +1,75 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"go.arsenm.dev/lure/manager"
|
||||||
|
)
|
||||||
|
|
||||||
|
func installCmd(c *cli.Context) error {
|
||||||
|
args := c.Args()
|
||||||
|
if args.Len() < 1 {
|
||||||
|
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr := manager.Detect()
|
||||||
|
if mgr == nil {
|
||||||
|
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
installPkgs(c.Context, args.Slice(), mgr)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func installPkgs(ctx context.Context, pkgs []string, mgr manager.Manager) {
|
||||||
|
err := pullRepos(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error pulling repositories").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
scripts, notFound := findPkgs(pkgs)
|
||||||
|
|
||||||
|
if len(notFound) > 0 {
|
||||||
|
err = mgr.Install(notFound...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error installing native packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
installScripts(ctx, mgr, scripts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func installScripts(ctx context.Context, mgr manager.Manager, scripts []string) {
|
||||||
|
for _, script := range scripts {
|
||||||
|
builtPkgs, _, err := buildPackage(ctx, script, mgr)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error building package").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mgr.InstallLocal(builtPkgs...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error installing package").Err(err).Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeCmd(c *cli.Context) error {
|
||||||
|
args := c.Args()
|
||||||
|
if args.Len() < 1 {
|
||||||
|
log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr := manager.Detect()
|
||||||
|
if mgr == nil {
|
||||||
|
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err := mgr.Remove(c.Args().Slice()...)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error removing packages").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,214 @@
|
||||||
|
package decoder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"go.arsenm.dev/lure/distro"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrInvalidType = errors.New("val must be a pointer to a struct")
|
||||||
|
|
||||||
|
type NotFoundError struct {
|
||||||
|
stype string
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nfe NotFoundError) Error() string {
|
||||||
|
return "required " + nfe.stype + " '" + nfe.name + "' could not be found"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder provides methods for decoding variable values
|
||||||
|
type Decoder struct {
|
||||||
|
info *distro.OSRelease
|
||||||
|
runner *interp.Runner
|
||||||
|
Overrides bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new variable decoder
|
||||||
|
func New(info *distro.OSRelease, runner *interp.Runner) *Decoder {
|
||||||
|
return &Decoder{info, runner, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVar decodes a variable to val using reflection.
|
||||||
|
// Structs should use the "sh" struct tag.
|
||||||
|
func (d *Decoder) DecodeVar(name string, val any) error {
|
||||||
|
variable := d.getVar(name)
|
||||||
|
if variable == nil {
|
||||||
|
return NotFoundError{"variable", name}
|
||||||
|
}
|
||||||
|
|
||||||
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
Result: val,
|
||||||
|
TagName: "sh",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch variable.Kind {
|
||||||
|
case expand.Indexed:
|
||||||
|
return dec.Decode(variable.List)
|
||||||
|
case expand.Associative:
|
||||||
|
return dec.Decode(variable.Map)
|
||||||
|
default:
|
||||||
|
return dec.Decode(variable.Str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVars decodes all variables to val using reflection.
|
||||||
|
// Structs should use the "sh" struct tag.
|
||||||
|
func (d *Decoder) DecodeVars(val any) error {
|
||||||
|
valKind := reflect.TypeOf(val).Kind()
|
||||||
|
if valKind != reflect.Pointer {
|
||||||
|
return ErrInvalidType
|
||||||
|
} else {
|
||||||
|
elemKind := reflect.TypeOf(val).Elem().Kind()
|
||||||
|
if elemKind != reflect.Struct {
|
||||||
|
return ErrInvalidType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rVal := reflect.ValueOf(val).Elem()
|
||||||
|
|
||||||
|
for i := 0; i < rVal.NumField(); i++ {
|
||||||
|
field := rVal.Field(i)
|
||||||
|
fieldType := rVal.Type().Field(i)
|
||||||
|
|
||||||
|
if !fieldType.IsExported() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
name := fieldType.Name
|
||||||
|
tag := fieldType.Tag.Get("sh")
|
||||||
|
required := false
|
||||||
|
if tag != "" {
|
||||||
|
if strings.Contains(tag, ",") {
|
||||||
|
splitTag := strings.Split(tag, ",")
|
||||||
|
name = splitTag[0]
|
||||||
|
|
||||||
|
if len(splitTag) > 1 {
|
||||||
|
if slices.Contains(splitTag, "required") {
|
||||||
|
required = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
name = tag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newVal := reflect.New(field.Type())
|
||||||
|
err := d.DecodeVar(name, newVal.Interface())
|
||||||
|
if _, ok := err.(NotFoundError); ok && !required {
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
field.Set(newVal.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ScriptFunc func(ctx context.Context, sir string, args ...string) error
|
||||||
|
|
||||||
|
// GetFunc returns a function corresponding to a bash function
|
||||||
|
// with the given name
|
||||||
|
func (d *Decoder) GetFunc(name string) (ScriptFunc, bool) {
|
||||||
|
fn := d.getFunc(name)
|
||||||
|
if fn == nil {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(ctx context.Context, dir string, args ...string) error {
|
||||||
|
sub := d.runner.Subshell()
|
||||||
|
interp.Params(args...)(sub)
|
||||||
|
interp.Dir(dir)(sub)
|
||||||
|
return sub.Run(ctx, fn)
|
||||||
|
}, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFunc writes the contents of a bash function to w.
|
||||||
|
func (d *Decoder) WriteFunc(name string, w io.Writer) error {
|
||||||
|
fn := d.getFunc(name)
|
||||||
|
if fn == nil {
|
||||||
|
return NotFoundError{"function", name}
|
||||||
|
}
|
||||||
|
|
||||||
|
printer := syntax.NewPrinter()
|
||||||
|
|
||||||
|
// Print individual statements instead of the entire block
|
||||||
|
block := fn.Cmd.(*syntax.Block)
|
||||||
|
for _, stmt := range block.Stmts {
|
||||||
|
err := printer.Print(w, stmt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.WriteString(w, "\n")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Decoder) getFunc(name string) *syntax.Stmt {
|
||||||
|
names := d.genPossibleNames(name)
|
||||||
|
for _, fnName := range names {
|
||||||
|
fn, ok := d.runner.Funcs[fnName]
|
||||||
|
if ok {
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVar gets a variable based on its name, taking into account
|
||||||
|
// override variables and nameref variables.
|
||||||
|
func (d *Decoder) getVar(name string) *expand.Variable {
|
||||||
|
names := d.genPossibleNames(name)
|
||||||
|
for _, varName := range names {
|
||||||
|
val, ok := d.runner.Vars[varName]
|
||||||
|
if ok {
|
||||||
|
// Resolve nameref variables
|
||||||
|
_, resolved := val.Resolve(expand.FuncEnviron(func(s string) string {
|
||||||
|
if val, ok := d.runner.Vars[s]; ok {
|
||||||
|
return val.String()
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}))
|
||||||
|
val = resolved
|
||||||
|
|
||||||
|
return &val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// genPossibleNames generates a slice of the possible names that
|
||||||
|
// could be used in the order that they should be checked
|
||||||
|
func (d *Decoder) genPossibleNames(name string) []string {
|
||||||
|
if !d.Overrides {
|
||||||
|
return []string{name}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []string{
|
||||||
|
fmt.Sprintf("%s_%s_%s", name, runtime.GOARCH, d.info.ID),
|
||||||
|
fmt.Sprintf("%s_%s", name, d.info.ID),
|
||||||
|
fmt.Sprintf("%s_%s", name, runtime.GOARCH),
|
||||||
|
name,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package shutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ExecFuncs map[string]func(interp.HandlerContext, []string) uint8
|
||||||
|
|
||||||
|
func (ef ExecFuncs) ExecHandler(ctx context.Context, args []string) error {
|
||||||
|
name := args[0]
|
||||||
|
|
||||||
|
if fn, ok := ef[name]; ok {
|
||||||
|
hctx := interp.HandlerCtx(ctx)
|
||||||
|
ec := fn(hctx, args)
|
||||||
|
if ec != 0 {
|
||||||
|
return interp.NewExitStatus(ec)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defExec := interp.DefaultExecHandler(2 * time.Second)
|
||||||
|
return defExec(ctx, args)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package shutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NopReadDir(context.Context, string) ([]os.FileInfo, error) {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
func NopStat(context.Context, string, bool) (os.FileInfo, error) {
|
||||||
|
return nil, os.ErrNotExist
|
||||||
|
}
|
||||||
|
|
||||||
|
func NopExec(context.Context, []string) error {
|
||||||
|
return exec.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func NopOpen(context.Context, string, int, os.FileMode) (io.ReadWriteCloser, error) {
|
||||||
|
return NopRWC{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type NopRWC struct{}
|
||||||
|
|
||||||
|
func (NopRWC) Read([]byte) (int, error) {
|
||||||
|
return 0, os.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NopRWC) Write([]byte) (int, error) {
|
||||||
|
return 0, os.ErrClosed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (NopRWC) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,109 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"go.arsenm.dev/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var log = logger.NewPretty(os.Stderr)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
defer cancel()
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
// Exit the program after a maximum of 200ms
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
app := &cli.App{
|
||||||
|
Name: "lure",
|
||||||
|
Usage: "Linux User REpository",
|
||||||
|
Commands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "install",
|
||||||
|
Usage: "Install a new package",
|
||||||
|
Aliases: []string{"in"},
|
||||||
|
Action: installCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "remove",
|
||||||
|
Usage: "Remove an installed package",
|
||||||
|
Aliases: []string{"rm"},
|
||||||
|
Action: removeCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "upgrade",
|
||||||
|
Usage: "Upgrade all installed packages",
|
||||||
|
Aliases: []string{"up"},
|
||||||
|
Action: upgradeCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "script",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Value: "lure.sh",
|
||||||
|
Usage: "Path to the build script",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "build",
|
||||||
|
Usage: "Build a local package",
|
||||||
|
Action: buildCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Name of the new repo",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "url",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "URL of the new repo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "addrepo",
|
||||||
|
Usage: "Add a new repository",
|
||||||
|
Aliases: []string{"ar"},
|
||||||
|
Action: addrepoCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "name",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Name of the repo to be deleted",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Name: "removerepo",
|
||||||
|
Usage: "Remove an existing repository",
|
||||||
|
Aliases: []string{"rr"},
|
||||||
|
Action: removerepoCmd,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "refresh",
|
||||||
|
Usage: "Pull all repositories that have changed",
|
||||||
|
Aliases: []string{"ref"},
|
||||||
|
Action: refreshCmd,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := app.RunContext(ctx, os.Args)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error while running app").Err(err).Send()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APK represents the APK package manager
|
||||||
|
type APK struct {
|
||||||
|
rootCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*APK) Exists() bool {
|
||||||
|
_, err := exec.LookPath("apk")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*APK) Name() string {
|
||||||
|
return "apk"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*APK) Format() string {
|
||||||
|
return "apk"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APK) SetRootCmd(s string) {
|
||||||
|
a.rootCmd = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APK) Sync() error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "update")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apk: sync: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APK) Install(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "add")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apk: install: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APK) InstallLocal(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "add", "--allow-untrusted")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apk: installlocal: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APK) Remove(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "del")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apk: remove: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APK) Upgrade(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "upgrade")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apk: upgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APK) UpgradeAll() error {
|
||||||
|
return a.Upgrade()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APK) ListInstalled() (map[string]string, error) {
|
||||||
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apk", "list", "-I")
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
name, info, ok := strings.Cut(scanner.Text(), "-")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
version, _, ok := strings.Cut(info, " ")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
out[name] = version
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// APT represents the APT package manager
|
||||||
|
type APT struct {
|
||||||
|
rootCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*APT) Exists() bool {
|
||||||
|
_, err := exec.LookPath("apt")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*APT) Name() string {
|
||||||
|
return "apt"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*APT) Format() string {
|
||||||
|
return "deb"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APT) SetRootCmd(s string) {
|
||||||
|
a.rootCmd = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APT) Sync() error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "update", "-y")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apt: sync: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APT) Install(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "install", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apt: install: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APT) InstallLocal(pkgs ...string) error {
|
||||||
|
return a.Install(pkgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APT) Remove(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "remove", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apt: remove: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APT) Upgrade(pkgs ...string) error {
|
||||||
|
return a.Install(pkgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APT) UpgradeAll() error {
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "apt", "upgrade", "-y")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("apt: upgradeall: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *APT) ListInstalled() (map[string]string, error) {
|
||||||
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command(getRootCmd(a.rootCmd), "dpkg-query", "-f", "${Package}\u200b${Version}\\n", "-W")
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[name] = version
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DNF represents the DNF package manager
|
||||||
|
type DNF struct {
|
||||||
|
rootCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DNF) Exists() bool {
|
||||||
|
_, err := exec.LookPath("dnf")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DNF) Name() string {
|
||||||
|
return "dnf"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DNF) Format() string {
|
||||||
|
return "rpm"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNF) SetRootCmd(s string) {
|
||||||
|
d.rootCmd = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNF) Sync() error {
|
||||||
|
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "upgrade", "--assumeno")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dnf: sync: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNF) Install(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "install", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dnf: install: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNF) InstallLocal(pkgs ...string) error {
|
||||||
|
return d.Install(pkgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNF) Remove(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "remove", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dnf: remove: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNF) Upgrade(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "upgrade", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dnf: upgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNF) UpgradeAll() error {
|
||||||
|
cmd := exec.Command(getRootCmd(d.rootCmd), "dnf", "upgrade", "-y")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dnf: upgradeall: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DNF) ListInstalled() (map[string]string, error) {
|
||||||
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command(getRootCmd(d.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[name] = version
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultRootCmd is the command used for privilege elevation by default
|
||||||
|
var DefaultRootCmd = "sudo"
|
||||||
|
|
||||||
|
var managers = []Manager{
|
||||||
|
&Pacman{},
|
||||||
|
&APT{},
|
||||||
|
&DNF{},
|
||||||
|
&YUM{},
|
||||||
|
&APK{},
|
||||||
|
&Zypper{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a new package manager
|
||||||
|
func Register(m Manager) {
|
||||||
|
managers = append(managers, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager represents a system package manager
|
||||||
|
type Manager interface {
|
||||||
|
// Name returns the name of the manager.
|
||||||
|
Name() string
|
||||||
|
// Format returns the packaging format of the manager.
|
||||||
|
// Examples: rpm, deb, apk
|
||||||
|
Format() string
|
||||||
|
// Returns true if the package manager exists on the system.
|
||||||
|
Exists() bool
|
||||||
|
// Sets the command used to elevate privileges. Defaults to DefaultRootCmd.
|
||||||
|
SetRootCmd(string)
|
||||||
|
// Sync fetches repositories without installing anything
|
||||||
|
Sync() error
|
||||||
|
// Install installs packages
|
||||||
|
Install(...string) error
|
||||||
|
// Remove uninstalls packages
|
||||||
|
Remove(...string) error
|
||||||
|
// Upgrade upgrades packages
|
||||||
|
Upgrade(...string) error
|
||||||
|
// InstallLocal installs packages from local files rather than repos
|
||||||
|
InstallLocal(...string) error
|
||||||
|
// UpgradeAll upgrades all packages
|
||||||
|
UpgradeAll() error
|
||||||
|
// ListInstalled returns all installed packages mapped to their versions
|
||||||
|
ListInstalled() (map[string]string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect returns the package manager detected on the system
|
||||||
|
func Detect() Manager {
|
||||||
|
for _, mgr := range managers {
|
||||||
|
if mgr.Exists() {
|
||||||
|
return mgr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the package manager with the given name
|
||||||
|
func Get(name string) Manager {
|
||||||
|
for _, mgr := range managers {
|
||||||
|
if mgr.Name() == name {
|
||||||
|
return mgr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRootCmd returns rootCmd if it's not empty, otherwise returns DefaultRootCmd
|
||||||
|
func getRootCmd(rootCmd string) string {
|
||||||
|
if rootCmd != "" {
|
||||||
|
return rootCmd
|
||||||
|
}
|
||||||
|
return DefaultRootCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func setCmdEnv(cmd *exec.Cmd) {
|
||||||
|
cmd.Env = os.Environ()
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pacman represents the Pacman package manager
|
||||||
|
type Pacman struct {
|
||||||
|
rootCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Pacman) Exists() bool {
|
||||||
|
_, err := exec.LookPath("pacman")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Pacman) Name() string {
|
||||||
|
return "pacman"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Pacman) Format() string {
|
||||||
|
return "archlinux"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) SetRootCmd(s string) {
|
||||||
|
p.rootCmd = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) Sync() error {
|
||||||
|
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-Sy")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pacman: sync: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) Install(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-S")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pacman: install: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) InstallLocal(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-U")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pacman: installlocal: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) Remove(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-R")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pacman: remove: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) Upgrade(pkgs ...string) error {
|
||||||
|
return p.Install(pkgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) UpgradeAll() error {
|
||||||
|
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "--noconfirm", "-Su")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("pacman: upgradeall: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Pacman) ListInstalled() (map[string]string, error) {
|
||||||
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command(getRootCmd(p.rootCmd), "pacman", "-Q")
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
name, version, ok := strings.Cut(scanner.Text(), " ")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[name] = version
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YUM represents the YUM package manager
|
||||||
|
type YUM struct {
|
||||||
|
rootCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*YUM) Exists() bool {
|
||||||
|
_, err := exec.LookPath("yum")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*YUM) Name() string {
|
||||||
|
return "yum"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*YUM) Format() string {
|
||||||
|
return "rpm"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YUM) SetRootCmd(s string) {
|
||||||
|
y.rootCmd = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YUM) Sync() error {
|
||||||
|
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "upgrade", "--assumeno")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("yum: sync: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YUM) Install(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "install", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("yum: install: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YUM) InstallLocal(pkgs ...string) error {
|
||||||
|
return y.Install(pkgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YUM) Remove(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "remove", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("yum: remove: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YUM) Upgrade(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "upgrade", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("yum: upgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YUM) UpgradeAll() error {
|
||||||
|
cmd := exec.Command(getRootCmd(y.rootCmd), "yum", "upgrade", "-y")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("yum: upgradeall: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (y *YUM) ListInstalled() (map[string]string, error) {
|
||||||
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command(getRootCmd(y.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[name] = version
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package manager
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Zypper represents the Zypper package manager
|
||||||
|
type Zypper struct {
|
||||||
|
rootCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Zypper) Exists() bool {
|
||||||
|
_, err := exec.LookPath("zypper")
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Zypper) Name() string {
|
||||||
|
return "zypper"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Zypper) Format() string {
|
||||||
|
return "rpm"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zypper) SetRootCmd(s string) {
|
||||||
|
z.rootCmd = s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zypper) Sync() error {
|
||||||
|
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "refresh")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("zypper: sync: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zypper) Install(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "install", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("zypper: install: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zypper) InstallLocal(pkgs ...string) error {
|
||||||
|
return z.Install(pkgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zypper) Remove(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "remove", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("zypper: remove: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zypper) Upgrade(pkgs ...string) error {
|
||||||
|
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "update", "-y")
|
||||||
|
cmd.Args = append(cmd.Args, pkgs...)
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("zypper: upgrade: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zypper) UpgradeAll() error {
|
||||||
|
cmd := exec.Command(getRootCmd(z.rootCmd), "zypper", "update", "-y")
|
||||||
|
setCmdEnv(cmd)
|
||||||
|
err := cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("zypper: upgradeall: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zypper) ListInstalled() (map[string]string, error) {
|
||||||
|
out := map[string]string{}
|
||||||
|
cmd := exec.Command(getRootCmd(z.rootCmd), "rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
|
||||||
|
|
||||||
|
stdout, err := cmd.StdoutPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(stdout)
|
||||||
|
for scanner.Scan() {
|
||||||
|
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[name] = version
|
||||||
|
}
|
||||||
|
|
||||||
|
err = scanner.Err()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -0,0 +1,232 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/pelletier/go-toml/v2"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"go.arsenm.dev/lure/download"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PkgNotFoundError struct {
|
||||||
|
pkgName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PkgNotFoundError) Error() string {
|
||||||
|
return "package '" + p.pkgName + "' could not be found in any repository"
|
||||||
|
}
|
||||||
|
|
||||||
|
func addrepoCmd(c *cli.Context) error {
|
||||||
|
name := c.String("name")
|
||||||
|
repoURL := c.String("url")
|
||||||
|
|
||||||
|
for _, repo := range config.Repos {
|
||||||
|
if repo.URL == repoURL {
|
||||||
|
log.Fatal("Repo already exists").Str("name", repo.Name).Send()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Repos = append(config.Repos, Repo{
|
||||||
|
Name: name,
|
||||||
|
URL: repoURL,
|
||||||
|
})
|
||||||
|
|
||||||
|
cfgFl, err := os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(cfgFl).Encode(&config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding config").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removerepoCmd(c *cli.Context) error {
|
||||||
|
name := c.String("name")
|
||||||
|
|
||||||
|
found := false
|
||||||
|
index := 0
|
||||||
|
for i, repo := range config.Repos {
|
||||||
|
if repo.Name == name {
|
||||||
|
index = i
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
log.Fatal("Repo does not exist").Str("name", name).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Repos = slices.Delete(config.Repos, index, index+1)
|
||||||
|
|
||||||
|
cfgFl, err := os.Create(cfgPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error opening config file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = toml.NewEncoder(cfgFl).Encode(&config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error encoding config").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(filepath.Join(cacheDir, "repo", name))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error removing repo directory").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshCmd(c *cli.Context) error {
|
||||||
|
err := pullRepos(c.Context)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error pulling repos").Err(err).Send()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPkg(pkg string) ([]string, error) {
|
||||||
|
baseRepoDir := filepath.Join(cacheDir, "repo")
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
for _, repo := range config.Repos {
|
||||||
|
repoDir := filepath.Join(baseRepoDir, repo.Name)
|
||||||
|
err := os.MkdirAll(repoDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
glob := filepath.Join(repoDir, pkg, "lure.sh")
|
||||||
|
matches, err := filepath.Glob(glob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(matches) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
out = append(out, matches...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(out) == 0 {
|
||||||
|
return nil, PkgNotFoundError{pkgName: pkg}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pkgPrompt(options []string) ([]string, error) {
|
||||||
|
names := make([]string, len(options))
|
||||||
|
for i, option := range options {
|
||||||
|
names[i] = filepath.Base(filepath.Dir(option))
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := &survey.MultiSelect{
|
||||||
|
Options: names,
|
||||||
|
Message: "Choose which package(s) to install",
|
||||||
|
}
|
||||||
|
|
||||||
|
var choices []int
|
||||||
|
err := survey.AskOne(prompt, &choices)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
for _, i := range choices {
|
||||||
|
out = append(out, options[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findPkgs(pkgs []string) (scripts, notFound []string) {
|
||||||
|
for _, pkg := range pkgs {
|
||||||
|
found, err := findPkg(pkg)
|
||||||
|
if _, ok := err.(PkgNotFoundError); ok {
|
||||||
|
notFound = append(notFound, pkg)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(found) == 1 {
|
||||||
|
scripts = append(scripts, found...)
|
||||||
|
} else {
|
||||||
|
choices, err := pkgPrompt(found)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error prompting for package choices").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
scripts = append(scripts, choices...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pullRepos(ctx context.Context) error {
|
||||||
|
baseRepoDir := filepath.Join(cacheDir, "repo")
|
||||||
|
|
||||||
|
for _, repo := range config.Repos {
|
||||||
|
repoURL, err := url.Parse(repo.URL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Pulling repository").Str("name", repo.Name).Send()
|
||||||
|
repoDir := filepath.Join(baseRepoDir, repo.Name)
|
||||||
|
|
||||||
|
gitDir := filepath.Join(repoDir, ".git")
|
||||||
|
if fi, err := os.Stat(gitDir); err == nil && fi.IsDir() {
|
||||||
|
r, err := git.PlainOpen(repoDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
w, err := r.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = w.PullContext(ctx, &git.PullOptions{Progress: os.Stderr})
|
||||||
|
if err == git.NoErrAlreadyUpToDate {
|
||||||
|
log.Info("Repository up to date").Str("name", repo.Name).Send()
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(repoDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(repoDir, 0o755)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(repoURL.Scheme, "git+") {
|
||||||
|
repoURL.Scheme = "git+" + repoURL.Scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
err = download.Get(ctx, download.GetOptions{
|
||||||
|
SourceURL: repoURL.String(),
|
||||||
|
Destination: repoDir,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"go.arsenm.dev/lure/distro"
|
||||||
|
"go.arsenm.dev/lure/internal/shutils/decoder"
|
||||||
|
"go.arsenm.dev/lure/manager"
|
||||||
|
"mvdan.cc/sh/v3/interp"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
func upgradeCmd(c *cli.Context) error {
|
||||||
|
info, err := distro.ParseOSRelease(c.Context)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error parsing os-release file").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr := manager.Detect()
|
||||||
|
if mgr == nil {
|
||||||
|
log.Fatal("Unable to detect supported package manager on system").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
updates, err := checkForUpdates(c.Context, mgr, info)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error checking for updates").Err(err).Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(updates) > 0 {
|
||||||
|
installPkgs(c.Context, updates, mgr)
|
||||||
|
} else {
|
||||||
|
log.Info("There is nothing to do.").Send()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkForUpdates(ctx context.Context, mgr manager.Manager, info *distro.OSRelease) ([]string, error) {
|
||||||
|
installed, err := mgr.ListInstalled()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []string
|
||||||
|
for name, version := range installed {
|
||||||
|
scripts, err := findPkg(name)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// since we're not using a glob, we can assume a single item
|
||||||
|
script := scripts[0]
|
||||||
|
|
||||||
|
fl, err := os.Open(script)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := syntax.NewParser().Parse(fl, "lure.sh")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
runner, err := interp.New()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = runner.Run(ctx, file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dec := decoder.New(info, runner)
|
||||||
|
|
||||||
|
var vars BuildVars
|
||||||
|
err = dec.DecodeVars(&vars)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoVer := vars.Version
|
||||||
|
if vars.Release != 0 && vars.Epoch == 0 {
|
||||||
|
repoVer = fmt.Sprintf("%s-%d", vars.Version, vars.Release)
|
||||||
|
} else if vars.Release != 0 && vars.Epoch != 0 {
|
||||||
|
repoVer = fmt.Sprintf("%d:%s-%d", vars.Epoch, vars.Version, vars.Release)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := vercmp(repoVer, version)
|
||||||
|
if c == 0 || c == -1 {
|
||||||
|
continue
|
||||||
|
} else if c == 1 {
|
||||||
|
out = append(out, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// vercmp compares two version strings.
|
||||||
|
// It returns 1 if v1 is greater,
|
||||||
|
// 0 if the versions are equal,
|
||||||
|
// and -1 if v2 is greater
|
||||||
|
func vercmp(v1, v2 string) int {
|
||||||
|
if v1 == v2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return sepVerCmp(sepLabel(v1), sepLabel(v2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func sepVerCmp(e1, e2 []string) int {
|
||||||
|
if slices.Equal(e1, e2) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// proc stores the amount of elements processed
|
||||||
|
proc := 0
|
||||||
|
|
||||||
|
for i := 0; i < len(e1); i++ {
|
||||||
|
proc++
|
||||||
|
|
||||||
|
if i >= len(e2) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
elem1 := e1[i]
|
||||||
|
elem2 := e2[i]
|
||||||
|
|
||||||
|
if elem1 == elem2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if isNumElem(elem1) && isNumElem(elem2) {
|
||||||
|
elem1v, err := strconv.ParseInt(elem1, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// error should never happen due to isNumElem()
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
elem2v, err := strconv.ParseInt(elem2, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
// error should never happen due to isNumElem()
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if elem1v > elem2v {
|
||||||
|
return 1
|
||||||
|
} else if elem1v < elem2v {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
} else if isNumElem(elem1) && isAlphaElem(elem2) {
|
||||||
|
return 1
|
||||||
|
} else if isAlphaElem(elem1) && isNumElem(elem2) {
|
||||||
|
return -1
|
||||||
|
} else if isAlphaElem(elem1) && isAlphaElem(elem2) {
|
||||||
|
if elem1 > elem2 {
|
||||||
|
return 1
|
||||||
|
} else if elem1 < elem2 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if proc < len(e2) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func sepLabel(label string) []string {
|
||||||
|
const (
|
||||||
|
other = iota
|
||||||
|
alpha
|
||||||
|
num
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
curType uint8
|
||||||
|
out []string
|
||||||
|
sb strings.Builder
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, char := range label {
|
||||||
|
if isNum(char) {
|
||||||
|
if curType != num && curType != other {
|
||||||
|
out = append(out, sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteRune(char)
|
||||||
|
curType = num
|
||||||
|
} else if isAlpha(char) {
|
||||||
|
if curType != alpha && curType != other {
|
||||||
|
out = append(out, sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteRune(char)
|
||||||
|
curType = alpha
|
||||||
|
} else {
|
||||||
|
if curType != other {
|
||||||
|
out = append(out, sb.String())
|
||||||
|
sb.Reset()
|
||||||
|
}
|
||||||
|
curType = other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sb.Len() != 0 {
|
||||||
|
out = append(out, sb.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNumElem(s string) bool {
|
||||||
|
// Check only the first rune as all elements
|
||||||
|
// should consist of the same type of rune
|
||||||
|
return isNum([]rune(s[:1])[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func isNum(r rune) bool {
|
||||||
|
return r >= '0' && r <= '9'
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAlphaElem(s string) bool {
|
||||||
|
// Check only the first rune as all elements
|
||||||
|
// should consist of the same type of rune
|
||||||
|
return isAlpha([]rune(s[:1])[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func isAlpha(r rune) bool {
|
||||||
|
return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z')
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSepLabel(t *testing.T) {
|
||||||
|
type item struct {
|
||||||
|
label string
|
||||||
|
expected []string
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []item{
|
||||||
|
{"2.0.1", []string{"2", "0", "1"}},
|
||||||
|
{"v0.0.1", []string{"v", "0", "0", "1"}},
|
||||||
|
{"2xFg33.+f.5", []string{"2", "xFg", "33", "f", "5"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, it := range table {
|
||||||
|
t.Run(it.label, func(t *testing.T) {
|
||||||
|
s := sepLabel(it.label)
|
||||||
|
if !slices.Equal(s, it.expected) {
|
||||||
|
t.Errorf("Expected %v, got %v", it.expected, s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerCmp(t *testing.T) {
|
||||||
|
type item struct {
|
||||||
|
v1, v2 string
|
||||||
|
expected int
|
||||||
|
}
|
||||||
|
|
||||||
|
table := []item{
|
||||||
|
{"1.0010", "1.9", 1},
|
||||||
|
{"1.05", "1.5", 0},
|
||||||
|
{"1.0", "1", 1},
|
||||||
|
{"1", "1.0", -1},
|
||||||
|
{"2.50", "2.5", 1},
|
||||||
|
{"FC5", "fc4", -1},
|
||||||
|
{"2a", "2.0", -1},
|
||||||
|
{"1.0", "1.fc4", 1},
|
||||||
|
{"3.0.0_fc", "3.0.0.fc", 0},
|
||||||
|
{"4.1__", "4.1+", 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, it := range table {
|
||||||
|
t.Run(it.v1+"/"+it.v2, func(t *testing.T) {
|
||||||
|
c := vercmp(it.v1, it.v2)
|
||||||
|
if c != it.expected {
|
||||||
|
t.Errorf("Expected %d, got %d", it.expected, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure opposite comparison gives opposite value
|
||||||
|
c = -vercmp(it.v2, it.v1)
|
||||||
|
if c != it.expected {
|
||||||
|
t.Errorf("Expected %d, got %d (opposite)", it.expected, c)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue