udpdate main program | simplify loaders (no more meta table for distro/package) | add default loader (pkg, ser) values | manage context creation : instruction.CreateContext() | fix tests | no more -distro parameter, only -package

This commit is contained in:
xdrm-brackets 2018-11-10 12:37:31 +01:00
parent f7d930b588
commit ff06cccb89
15 changed files with 200 additions and 144 deletions

55
cmd/nix-amer/args.go Normal file
View File

@ -0,0 +1,55 @@
package main
import (
"flag"
"fmt"
"git.xdrm.io/xdrm-brackets/nix-amer/internal/instruction"
)
func GetArgs() (*instruction.ExecutionContext, string, error) {
setupFlags(flag.CommandLine)
longPack := flag.String("package", "", "")
longServ := flag.String("service", "", "")
pack := flag.String("p", "", "")
serv := flag.String("s", "", "")
flag.Parse()
// 1. fail on missing build file
if len(flag.Args()) < 1 {
return nil, "", fmt.Errorf("missing buildfile")
}
buildfile := flag.Arg(0)
// 2. override short version with long
if longPack != nil && len(*longPack) > 0 {
pack = longPack
}
if longServ != nil && len(*longServ) > 0 {
serv = longServ
}
// 3. fail on missing mandatory fields
if pack == nil || len(*pack) < 1 {
return nil, "", fmt.Errorf("missing -package")
}
// if serv == nil || len(*serv) < 1 { // default service
// return nil, "", fmt.Errorf("missing -service")
// }
// 3. Load context
ctx, err := instruction.CreateContext(*pack, *serv)
if err != nil {
return nil, "", err
}
/*DEBUG*/ //fmt.Printf("package: '%s' | '%s'\n", fpackage, lpackage)
/*DEBUG*/ //fmt.Printf("service: '%s' | '%s'\n", fservice, lservice)
return ctx, buildfile, nil
}
func setupFlags(f *flag.FlagSet) {
f.Usage = func() {
help()
}
}

View File

@ -11,7 +11,7 @@ func help() {
fmt.Printf("\tnix-amer - the automatic setup tool you need\n") fmt.Printf("\tnix-amer - the automatic setup tool you need\n")
fmt.Printf("\n%s\n", clifmt.Color(1, "SYNOPSIS", true)) fmt.Printf("\n%s\n", clifmt.Color(1, "SYNOPSIS", true))
fmt.Printf("\tnix-amer [-d <linux-distro> [-p <package-manager>] [-s <service-manager>] <buildfile>\n") fmt.Printf("\tnix-amer [-p <package-manager>] [-s <service-manager>] <buildfile>\n")
fmt.Printf("\tnix-amer -help\n") fmt.Printf("\tnix-amer -help\n")
fmt.Printf("\tnix-amer -h\n") fmt.Printf("\tnix-amer -h\n")
fmt.Printf("\n") fmt.Printf("\n")
@ -28,11 +28,6 @@ func help() {
fmt.Printf("\n%s\n", clifmt.Color(1, "OPTIONS", true)) fmt.Printf("\n%s\n", clifmt.Color(1, "OPTIONS", true))
fmt.Printf("\t-d, -distro\n\t Define which linux distribution the target will run on.\n")
fmt.Printf("\t Available values are 'debian', 'ubuntu', 'alpine', 'solus',\n")
fmt.Printf("\t 'arch', 'centos' and 'fedora'.\n")
fmt.Printf("\n")
fmt.Printf("\t-p, -package\n\t Define which package manager to use ; normally it is\n") fmt.Printf("\t-p, -package\n\t Define which package manager to use ; normally it is\n")
fmt.Printf("\t automatically guessed if you define the -distro. Available\n") fmt.Printf("\t automatically guessed if you define the -distro. Available\n")
fmt.Printf("\t values are 'apt-get', 'eopkg', 'apk', 'yum', 'dnf' and 'pacman'.\n") fmt.Printf("\t values are 'apt-get', 'eopkg', 'apk', 'yum', 'dnf' and 'pacman'.\n")

View File

@ -1,5 +1,37 @@
package main package main
import (
"fmt"
"git.xdrm.io/xdrm-brackets/nix-amer/internal/buildfile"
"git.xdrm.io/xdrm-brackets/nix-amer/internal/clifmt"
"os"
)
func main() { func main() {
help()
// Manage arguments
ctx, bf, err := GetArgs()
if err != nil {
fmt.Printf("%s", err)
return
}
// 1. get buildfile reader
bfreader, err := os.Open(bf)
if err != nil {
fmt.Printf("cannot open buildfile | %s\n", err)
return
}
defer bfreader.Close()
// 2. parse buildfile
_, err = buildfile.NewReader(ctx, bfreader)
if err != nil {
fmt.Printf("%s%s\n", bf, err)
return
}
clifmt.Align("build file")
fmt.Printf("%s\n", clifmt.Color(32, "parsed"))
} }

View File

@ -2,6 +2,7 @@ package buildfile
import ( import (
"fmt" "fmt"
"git.xdrm.io/xdrm-brackets/nix-amer/internal/clifmt"
) )
type LineError struct { type LineError struct {
@ -10,5 +11,5 @@ type LineError struct {
} }
func (le LineError) Error() string { func (le LineError) Error() string {
return fmt.Sprintf("line %d | %s", le.Line, le.Err.Error()) return fmt.Sprintf(":%d %s", le.Line, clifmt.Color(31, le.Err.Error()))
} }

View File

@ -2,22 +2,32 @@ package buildfile
import ( import (
"bufio" "bufio"
"errors"
"git.xdrm.io/xdrm-brackets/nix-amer/internal/instruction" "git.xdrm.io/xdrm-brackets/nix-amer/internal/instruction"
"io" "io"
"strings"
) )
var ErrNullContext = errors.New("null context")
// Reader is the buildfile reader // Reader is the buildfile reader
type Reader struct { type Reader struct {
// linux distribution to run on // Context is the linux distribution-specified execution context (package manager, service manager, etc)
distribution string Context *instruction.ExecutionContext
// Content is the instruction list
Content []instruction.T Content []instruction.T
} }
// NewReader creates a new reader for the specified build file and linux distribution // NewReader creates a new reader for the specified build file and linux distribution
func NewReader(distro string, buildfile io.Reader) (*Reader, error) { func NewReader(ctx *instruction.ExecutionContext, buildfile io.Reader) (*Reader, error) {
// fail on null context
if ctx == nil {
return nil, ErrNullContext
}
r := &Reader{ r := &Reader{
distribution: distro, Context: ctx,
Content: make([]instruction.T, 0), Content: make([]instruction.T, 0),
} }
@ -33,6 +43,12 @@ func NewReader(distro string, buildfile io.Reader) (*Reader, error) {
} else if err != nil { } else if err != nil {
return nil, LineError{l, err} return nil, LineError{l, err}
} }
line = strings.Trim(line, " \t\r\n")
// ignore newline & comments
if len(line) < 1 || line[0] == '[' {
continue
}
// turn into instruction // turn into instruction
inst, err := instruction.Parse(line) inst, err := instruction.Parse(line)

View File

@ -34,11 +34,13 @@ func TestInstructionSyntax(t *testing.T) {
{"ins args\ncm args\n", 2, instruction.ErrInvalidSyntax}, {"ins args\ncm args\n", 2, instruction.ErrInvalidSyntax},
} }
ctx, _ := instruction.CreateContext("apt-get", "")
for i, test := range tests { for i, test := range tests {
// create reader // create reader
buffer := bytes.NewBufferString(test.File) buffer := bytes.NewBufferString(test.File)
_, err := NewReader("ubuntu", buffer) _, err := NewReader(ctx, buffer)
// no error expected // no error expected
if test.Err == nil { if test.Err == nil {

View File

@ -2,6 +2,7 @@ package instruction
import ( import (
"fmt" "fmt"
"git.xdrm.io/xdrm-brackets/nix-amer/internal/cnf"
"strings" "strings"
) )
@ -12,20 +13,24 @@ type config struct {
Path []string Path []string
// Value if the value to add or update // Value if the value to add or update
Value string Value string
// Format is the configuration format in use
Format *cnf.ConfigurationFormat
} }
func (d *config) Build(_args string) error { func (d *config) Build(_args string) error {
// 1. extract action (sub command) // 1. extract action (sub command)
split := strings.SplitN(_args, " ", 2) split := strings.Fields(_args)
// 2. check path // 2. check path
if len(split) < 2 { if len(split) < 2 {
return fmt.Errorf("missing configuration path") return fmt.Errorf("missing configuration path")
} }
path := split[0]
value := strings.Join(split[1:], "")
// 3. check path separator // 3. check path separator
splitPath := strings.Split(split[0], "@") splitPath := strings.Split(path, "@")
if len(splitPath) > 2 { if len(splitPath) > 2 {
return fmt.Errorf("invalid path (additional '@'?)") return fmt.Errorf("invalid path (additional '@'?)")
} }
@ -36,7 +41,7 @@ func (d *config) Build(_args string) error {
} }
// add value // add value
d.Value = split[1] d.Value = value
return nil return nil
} }

View File

@ -1,7 +1,7 @@
package instruction package instruction
import ( import (
"git.xdrm.io/xdrm-brackets/nix-amer/internal/cnf" "fmt"
"git.xdrm.io/xdrm-brackets/nix-amer/internal/pkg" "git.xdrm.io/xdrm-brackets/nix-amer/internal/pkg"
"git.xdrm.io/xdrm-brackets/nix-amer/internal/ser" "git.xdrm.io/xdrm-brackets/nix-amer/internal/ser"
) )
@ -16,6 +16,43 @@ type T interface {
type ExecutionContext struct { type ExecutionContext struct {
PackageManager pkg.PackageManager PackageManager pkg.PackageManager
Configuration cnf.ConfigurationFormat
ServiceManager ser.ServiceManager ServiceManager ser.ServiceManager
} }
// CreateContext creates an execution contet with the given package-manager and service-manager
// default values are taken from each go package (pkg, ser)
func CreateContext(_pkg, _ser string) (*ExecutionContext, error) {
// 1. fail if no value and no defaults
if len(_pkg)+len(pkg.DefaultManager) < 1 {
return nil, fmt.Errorf("missing package manager")
}
if len(_ser)+len(ser.DefaultManager) < 1 {
return nil, fmt.Errorf("missing service manager")
}
// 2. set default
if len(_pkg) < 1 {
_pkg = pkg.DefaultManager
}
if len(_ser) < 1 {
_ser = ser.DefaultManager
}
// 3. load managers
pkg, err := pkg.Load(_pkg)
if err != nil {
return nil, fmt.Errorf("package manager: %s", err)
}
ser, err := ser.Load(_ser)
if err != nil {
return nil, fmt.Errorf("service manager: %s", err)
}
// 4. build context
return &ExecutionContext{
PackageManager: pkg,
ServiceManager: ser,
}, nil
}

View File

@ -8,16 +8,15 @@ import (
func Parse(raw string) (T, error) { func Parse(raw string) (T, error) {
// 1. format (trim + split first space) // 1. format (trim + split first space)
raw = strings.Trim(raw, " \t\n")
cmd := strings.SplitN(raw, " ", 2) cmd := strings.SplitN(raw, " ", 2)
cmd[0] = strings.Trim(cmd[0], " \t") cmd[0] = strings.Trim(cmd[0], " \t")
cmd[1] = strings.Trim(cmd[1], " \t")
// 2. fail if invalid base syntax 'cmd args...' // 2. fail if invalid base syntax 'cmd args...'
// where command is 3 letters // where command is 3 letters
if len(cmd) < 2 || len(cmd[0]) < 3 || cmd[0][1] == ' ' { if len(cmd) < 2 || len(cmd[0]) < 3 || cmd[0][1] == ' ' {
return nil, ErrInvalidSyntax return nil, ErrInvalidSyntax
} }
cmd[1] = strings.Trim(cmd[1], " \t")
// 3. Extract instruction type // 3. Extract instruction type
switch cmd[0] { switch cmd[0] {
@ -29,10 +28,6 @@ func Parse(raw string) (T, error) {
i := &delete{} i := &delete{}
err := i.Build(cmd[1]) err := i.Build(cmd[1])
return i, err return i, err
case "upd":
i := &update{}
err := i.Build(cmd[1])
return i, err
case "ser": case "ser":
i := &service{} i := &service{}
err := i.Build(cmd[1]) err := i.Build(cmd[1])

View File

@ -1,5 +1,7 @@
package pkg package pkg
var DefaultManager = ""
// PackageManager is the common interface for all package-manager drivers (e.g. `dpkg` for debian-based, `pacman` for arch) // PackageManager is the common interface for all package-manager drivers (e.g. `dpkg` for debian-based, `pacman` for arch)
type PackageManager interface { type PackageManager interface {
// Name of executable (to check if installed and for debug) // Name of executable (to check if installed and for debug)

View File

@ -1,71 +1,38 @@
package pkg package pkg
import ( import (
"encoding/json"
"errors" "errors"
"os"
"os/exec"
"path/filepath"
) )
var ErrUnknownDistribution = errors.New("unknown linux distribution") var ErrUnknownManager = errors.New("unknown package manager")
var ErrNoCandidateInstalled = errors.New("no package-manager candidate installed") var ErrNotInstalled = errors.New("no candidate installed")
var ErrNoDriverFound = errors.New("no driver found for the package manager")
func Load(_distro string) (PackageManager, error) { func Load(_manager string) (PackageManager, error) {
// 1. load config file // 1. create manager (fail if unknown)
driverTable := filepath.Join(os.Getenv("GOPATH"), "src/git.xdrm.io/xdrm-brackets/nix-amer/meta/pkg-drivers.json") var manager PackageManager
file, err := os.Open(driverTable) switch _manager {
if err != nil {
return nil, err
}
defer file.Close()
// 2. Parse json
table := make(map[string][]string)
decoder := json.NewDecoder(file)
if err := decoder.Decode(&table); err != nil {
return nil, err
}
decoder = nil
// 3. Get available package-manager list for distro
available, exists := table[_distro]
if !exists || len(available) < 1 {
return nil, ErrUnknownDistribution
}
// 4. Check each available package-manager in order
selected := ""
for _, current := range available {
if exec.Command("which", current).Run() == nil {
selected = current
break
}
}
// no candidate installed
if len(selected) < 1 {
return nil, ErrNoCandidateInstalled
}
// 5. Instanciate
switch selected {
case "apt-get": case "apt-get":
return new(Apt), nil manager = new(Apt)
case "apk": case "apk":
return new(Apk), nil manager = new(Apk)
case "eopkg": case "eopkg":
return new(Eopkg), nil manager = new(Eopkg)
case "pacman": case "pacman":
return new(Pacman), nil manager = new(Pacman)
case "dnf": case "dnf":
return new(Dnf), nil manager = new(Dnf)
case "yum": case "yum":
return new(Yum), nil manager = new(Yum)
default:
return nil, ErrUnknownManager
} }
return nil, ErrNoDriverFound // 2. fail if not installed
// if exec.Command("which", manager.Name()).Run() != nil {
// return nil, ErrNotInstalled
// }
return manager, nil
} }

View File

@ -1,46 +1,23 @@
package pkg package pkg
import ( import (
"os/exec"
"strings"
"testing" "testing"
) )
func TestUnknownDistribution(t *testing.T) { func TestUnknownManager(t *testing.T) {
_, err := Load("invalid-distro-xxx") _, err := Load("apt-put")
if err != ErrUnknownDistribution { if err != ErrUnknownManager {
t.Errorf("expected <%s>, got <%s>", ErrUnknownDistribution, err) t.Errorf("expected <%s>, got <%s>", ErrUnknownManager, err)
t.Fail() t.Fail()
} }
} }
func TestNoCandidateInstalled(t *testing.T) { func TestKnownManager(t *testing.T) {
// get host distro _, err := Load("apt-get")
out, err := exec.Command("lsb_release", "-is").Output()
if err != nil { if err != nil {
t.Errorf("cannot get host linux distribution") t.Errorf("unexpected error <%s>", err)
t.Fail()
}
hostDistro := strings.ToLower(strings.Trim(string(out), " \n\t"))
// valid candidate
_, err = Load(hostDistro)
if err != nil {
t.Errorf("expected <nil>, got <%s>", err)
t.Fail()
}
// invalid candidate
distro := "solus"
if hostDistro == distro {
distro = "ubuntu"
}
_, err = Load(distro)
if err != ErrNoCandidateInstalled {
t.Errorf("expected <%s>, got <%s>", ErrNoCandidateInstalled, err)
t.Fail() t.Fail()
} }

View File

@ -1,5 +1,7 @@
package ser package ser
var DefaultManager = "systemd"
// ServiceManager is the common interface for service managers (systemd, init.d) // ServiceManager is the common interface for service managers (systemd, init.d)
type ServiceManager interface { type ServiceManager interface {
// Name of executable (to check if installed and for debug) // Name of executable (to check if installed and for debug)

View File

@ -2,31 +2,14 @@ package ser
import ( import (
"errors" "errors"
"os/exec"
) )
var ErrUnknownManager = errors.New("unknown service manager") var ErrUnknownManager = errors.New("unknown service manager")
var ErrNoCandidateInstalled = errors.New("no package-manager candidate installed") var ErrNotInstalled = errors.New("not candidate installed")
// available service managers
// var available = []string{"systemd", "init"}
var available = []string{"systemd"}
func Load(_manager string) (ServiceManager, error) { func Load(_manager string) (ServiceManager, error) {
// 1. fail if unknown manager // 1. create manager (fail if unknown)
known := false
for _, mgr := range available {
if _manager == mgr {
known = true
break
}
}
if !known {
return nil, ErrUnknownManager
}
// 2. Create manager accordingly
var manager ServiceManager var manager ServiceManager
switch _manager { switch _manager {
case "systemd": case "systemd":
@ -36,9 +19,9 @@ func Load(_manager string) (ServiceManager, error) {
} }
// 2. fail if not installed // 2. fail if not installed
if exec.Command("which", manager.Name()).Run() == nil { // if exec.Command("which", manager.Name()).Run() != nil {
return nil, ErrNoCandidateInstalled // return nil, ErrNotInstalled
} // }
return manager, nil return manager, nil

View File

@ -1,13 +0,0 @@
{
"ubuntu": [ "apt-get" ],
"debian": [ "apt-get" ],
"alpine": [ "apk" ],
"solus": [ "eopkg" ],
"arch": [ "pacman" ],
"centos": [ "dnf", "yum" ],
"fedora": [ "dnf", "yum" ]
}