diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 08d3c63f..ea5eaaee 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -103,7 +103,7 @@ jobs:
- name: Check scripts with Shellcheck
uses: essentialkaos/shellcheck-action@v1
with:
- files: scripts/*
+ files: scripts/bibop-dep scripts/bibop-docker scripts/bibop-entrypoint scripts/bibop-libtest-gen scripts/bibop-linked scripts/bibop-massive scripts/bibop-multi-check scripts/bibop-so-exported
Hadolint:
name: Hadolint
diff --git a/COOKBOOK.md b/COOKBOOK.md
index 6b3e3f8a..9b2d1e5d 100644
--- a/COOKBOOK.md
+++ b/COOKBOOK.md
@@ -18,6 +18,7 @@
* [Common](#common)
* [`exit`](#exit)
* [`wait`](#wait)
+ * [`template`](#template)
* [Input/Output](#inputoutput)
* [`expect`](#expect)
* [`print`](#print)
@@ -490,6 +491,71 @@ command "echo 'ABCD'" "Simple echo command"
+##### `template`
+
+Creates a file from a template. If file already exists, it will be rewritten with the same UID, GID and mode.
+
+▲ _Note that 'template' action will not automatically backup the destination file if it already exists (use `backup` and `backup-restore` actions to preserve the original file). Also, the created file will remain after tests execution if it was created outside the working directory._
+
+You can use the following methods in your templates:
+
+- `Var "name"` - get variable value;
+- `Is "name" "value"` - compare variable value.
+
+Simple example:
+
+```
+# Sysconfig for postgresql service
+
+PG_ENGINE=""
+PG_POSTMASTER=""
+{{ if not .Is "data_dir" "" }}
+PG_DATA="{{ .Var "data_dir" }}/db"
+{{ else }}
+PG_DATA=""
+{{ end }}
+PG_LOG=""
+PG_UPLOG=""
+PG_SOCKET_DIR=""
+TIMEOUT=""
+DISABLE_AUTO_NUMA=""
+```
+
+**Syntax:** `template [file-mode]`
+
+**Arguments:**
+
+* `source` - Path to template file (_String_)
+* `dest` - Destination path (_String_)
+* `file-mode` - Destination file mode (_Integer_) [Optional | 644]
+
+**Negative form:** No
+
+**Example:**
+
+```yang
+command "-" "Create configuration file"
+ template app.template /etc/myapp.conf
+```
+
+```yang
+command "-" "Create configuration file"
+ template app.template /etc/myapp.conf 640
+```
+
+```yang
+command "-" "Replace configuration file"
+ backup /etc/myapp.conf
+ template app.template /etc/myapp.conf 640
+
+...
+
+command "-" "Restore original configuration file"
+ backup-restore /etc/myapp.conf
+```
+
+
+
#### Input/Output
Be aware that the output store limited to 2 Mb of data for each stream (`stdout` _and_ `stderr`). So if command generates lots of output data, it better to use `expect` action to working with the output.
@@ -563,6 +629,8 @@ command "echo 'ABCD'" "Simple echo command"
wait-output 10.0
```
+
+
##### `output-match`
Checks output with given [regular expression](https://en.wikipedia.org/wiki/Regular_expression).
diff --git a/README.md b/README.md
index 658e5825..b9a728f6 100644
--- a/README.md
+++ b/README.md
@@ -15,13 +15,13 @@
-`bibop` is a utility for testing command-line tools and daemons. Initially, this utility was created for testing packages from [ESSENTIAL KAOS Public Repository](https://pkgs.kaos.st).
+`bibop` is a utility for testing command-line tools, packages and daemons. Initially, this utility was created for testing packages from [ESSENTIAL KAOS Public Repository](https://kaos.sh/kaos-repo).
Information about bibop recipe syntax you can find in our [cookbook](COOKBOOK.md).
### Usage demo
-[![demo](https://gh.kaos.st/bibop-600.gif)](#usage-demo)
+https://github.com/essentialkaos/bibop/assets/182020/c63dc147-fa44-40df-92e2-12f530c411af
### Installation
@@ -99,7 +99,8 @@ Usage: bibop {options} recipe
Options
--dry-run, -D Parse and validate recipe
- --extra, -X Print the last lines from command output if action was failed
+ --extra, -X lines Number of output lines for failed action (default: 10)
+ --pause, -P duration Pause between commands in seconds
--list-packages, -L List required packages
--list-packages-flat, -L1 List required packages in one line (useful for scripts)
--variables, -V List recipe variables
@@ -128,6 +129,12 @@ Examples
bibop app.recipe --tag init,service
Run tests from app.recipe and execute commands with tags init and service
+ bibop app.recipe --extra
+ Run tests from app.recipe and print the last 10 lines from command output if action was failed
+
+ bibop app.recipe --extra=50
+ Run tests from app.recipe and print the last 50 lines from command output if action was failed
+
bibop app.recipe --format json 1> ~/results/app.json
Run tests from app.recipe and save result in JSON format
diff --git a/action/auxi.go b/action/auxi.go
index 6000d3e0..cbf3ce12 100644
--- a/action/auxi.go
+++ b/action/auxi.go
@@ -30,7 +30,7 @@ type OutputContainer struct {
// ////////////////////////////////////////////////////////////////////////////////// //
// escapeCharRegex is regexp for searching escape characters
-var escapeCharRegex = regexp.MustCompile(`\x1b\[[0-9\;]+m`)
+var escapeCharRegex = regexp.MustCompile("[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))")
// ////////////////////////////////////////////////////////////////////////////////// //
@@ -101,11 +101,11 @@ func (c *OutputContainer) Tail(lines int) string {
}
if line == lines {
- return strings.TrimRight(string(data[i+1:]), " \n\r")
+ return strings.Trim(string(data[i+1:]), " \n\r")
}
}
- return strings.TrimRight(string(data), " \n\r")
+ return strings.Trim(string(data), " \n\r")
}
// IsEmpty returns true if container is empty
@@ -156,5 +156,6 @@ func fmtValue(v string) string {
// sanitizeData removes escape characters
func sanitizeData(data []byte) []byte {
+ data = bytes.ReplaceAll(data, []byte("\r"), nil)
return escapeCharRegex.ReplaceAll(data, nil)
}
diff --git a/action/basic.go b/action/basic.go
index 289ae5dd..b599a95f 100644
--- a/action/basic.go
+++ b/action/basic.go
@@ -29,7 +29,7 @@ func Wait(action *recipe.Action) error {
return err
}
- durSec = mathutil.BetweenF64(durSec, 0.01, 3600.0)
+ durSec = mathutil.Between(durSec, 0.01, 3600.0)
time.Sleep(timeutil.SecondsToDuration(durSec))
diff --git a/action/fs.go b/action/fs.go
index 382de2d0..ce116e44 100644
--- a/action/fs.go
+++ b/action/fs.go
@@ -604,7 +604,7 @@ func Cleanup(action *recipe.Action) error {
err = os.RemoveAll(obj)
if err != nil {
- return fmt.Errorf("Can't remove object %q: %v", err)
+ return fmt.Errorf("Can't remove object %q: %v", obj, err)
}
}
diff --git a/action/http.go b/action/http.go
index 4f976c17..dc48b84c 100644
--- a/action/http.go
+++ b/action/http.go
@@ -297,16 +297,7 @@ func HTTPSetHeader(action *recipe.Action) error {
// ////////////////////////////////////////////////////////////////////////////////// //
-// isHTTPMethodSupported returns true if HTTP method is supported
-func isHTTPMethodSupported(method string) bool {
- switch method {
- case req.GET, req.POST, req.DELETE, req.PUT, req.PATCH, req.HEAD:
- return true
- }
-
- return false
-}
-
+// checkRequestData checks request data
func checkRequestData(method, payload string) error {
switch method {
case req.GET, req.POST, req.DELETE, req.PUT, req.PATCH, req.HEAD:
@@ -353,6 +344,6 @@ func makeHTTPRequest(action *recipe.Action, method, url, payload string) *req.Re
// parseJSONQuery converts json query to slice
func parseJSONQuery(q string) []string {
- q = strings.Replace(q, "[", ".[", -1)
+ q = strings.ReplaceAll(q, "[", ".[")
return strings.Split(q, ".")
}
diff --git a/action/io.go b/action/io.go
index 3e809804..e22e350a 100644
--- a/action/io.go
+++ b/action/io.go
@@ -48,7 +48,7 @@ func Expect(action *recipe.Action, output *OutputContainer) error {
}
start := time.Now()
- timeout = mathutil.BetweenF64(timeout, 0.01, 3600.0)
+ timeout = mathutil.Between(timeout, 0.01, 3600.0)
timeoutDur := timeutil.SecondsToDuration(timeout)
for range time.NewTicker(_DATA_READ_PERIOD).C {
@@ -98,7 +98,7 @@ func Input(action *recipe.Action, input *os.File, output *OutputContainer) error
}
if !strings.HasSuffix(text, "\n") {
- text = text + "\n"
+ text += "\n"
}
output.Purge()
diff --git a/action/service.go b/action/service.go
index 4124ed65..377fd9b8 100644
--- a/action/service.go
+++ b/action/service.go
@@ -109,7 +109,7 @@ func WaitService(action *recipe.Action) error {
}
start := time.Now()
- timeout = mathutil.BetweenF64(timeout, 0.01, 3600.0)
+ timeout = mathutil.Between(timeout, 0.01, 3600.0)
timeoutDur := timeutil.SecondsToDuration(timeout)
for range time.NewTicker(time.Second / 2).C {
diff --git a/action/system.go b/action/system.go
index e83adc7c..f5370e7e 100644
--- a/action/system.go
+++ b/action/system.go
@@ -87,7 +87,7 @@ func WaitPID(action *recipe.Action) error {
}
start := time.Now()
- timeout = mathutil.BetweenF64(timeout, 0.01, 3600.0)
+ timeout = mathutil.Between(timeout, 0.01, 3600.0)
timeoutDur := timeutil.SecondsToDuration(timeout)
for range time.NewTicker(25 * time.Millisecond).C {
@@ -147,7 +147,7 @@ func WaitFS(action *recipe.Action) error {
}
start := time.Now()
- timeout = mathutil.BetweenF64(timeout, 0.01, 3600.0)
+ timeout = mathutil.Between(timeout, 0.01, 3600.0)
timeoutDur := timeutil.SecondsToDuration(timeout)
for range time.NewTicker(25 * time.Millisecond).C {
@@ -203,7 +203,7 @@ func WaitConnect(action *recipe.Action) error {
}
start := time.Now()
- timeout = mathutil.BetweenF64(timeout, 0.01, 3600.0)
+ timeout = mathutil.Between(timeout, 0.01, 3600.0)
timeoutDur := timeutil.SecondsToDuration(timeout)
for range time.NewTicker(25 * time.Millisecond).C {
@@ -264,7 +264,7 @@ func Connect(action *recipe.Action) error {
timeout = 1.0
}
- timeout = mathutil.BetweenF64(timeout, 0.01, 3600.0)
+ timeout = mathutil.Between(timeout, 0.01, 3600.0)
timeoutDur := timeutil.SecondsToDuration(timeout)
conn, err := net.DialTimeout(network, address, timeoutDur)
diff --git a/action/template.go b/action/template.go
new file mode 100644
index 00000000..ce4b4468
--- /dev/null
+++ b/action/template.go
@@ -0,0 +1,114 @@
+package action
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+// //
+// Copyright (c) 2023 ESSENTIAL KAOS //
+// Apache License, Version 2.0 //
+// //
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "text/template"
+
+ "github.com/essentialkaos/bibop/recipe"
+)
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// varWrapper is a recipe wrapper for accessing variables
+type varWrapper struct {
+ r *recipe.Recipe
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// Template is action processor for "template"
+func Template(action *recipe.Action) error {
+ mode := uint64(0644)
+ source, err := action.GetS(0)
+
+ if err != nil {
+ return err
+ }
+
+ dest, err := action.GetS(1)
+
+ if err != nil {
+ return err
+ }
+
+ isSafePath, err := checkPathSafety(action.Command.Recipe, source)
+
+ if err != nil {
+ return err
+ }
+
+ if !isSafePath {
+ return fmt.Errorf("Action uses unsafe path (%s)", source)
+ }
+
+ isSafePath, err = checkPathSafety(action.Command.Recipe, dest)
+
+ if err != nil {
+ return err
+ }
+
+ if !isSafePath {
+ return fmt.Errorf("Action uses unsafe path (%s)", dest)
+ }
+
+ if action.Has(2) {
+ modeStr, _ := action.GetS(1)
+ mode, err = strconv.ParseUint(modeStr, 8, 32)
+
+ if err != nil {
+ return err
+ }
+ }
+
+ tmplData, err := os.ReadFile(source)
+
+ if err != nil {
+ fmt.Errorf("Can't read template %q: %v", source, err)
+ }
+
+ tmpl, err := template.New("").Parse(string(tmplData))
+
+ if err != nil {
+ return fmt.Errorf("Can't parse template %q: %v", source, err)
+ }
+
+ fd, err := os.OpenFile(dest, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(mode))
+
+ if err != nil {
+ return fmt.Errorf("Can't save template data into %q: %v", dest, err)
+ }
+
+ defer fd.Close()
+
+ vw := &varWrapper{action.Command.Recipe}
+ err = tmpl.Execute(fd, vw)
+
+ if err != nil {
+ return fmt.Errorf("Can't render template %q: %v", source, err)
+ }
+
+ return nil
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
+// Var returns variable value
+func (vw *varWrapper) Var(name string) string {
+ return vw.r.GetVariable(name, true)
+}
+
+// Is compares variable value
+func (vw *varWrapper) Is(name, value string) bool {
+ return vw.r.GetVariable(name, true) == value
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
diff --git a/cli/barcode.go b/cli/barcode.go
index 3f08d816..ce2f092f 100644
--- a/cli/barcode.go
+++ b/cli/barcode.go
@@ -55,7 +55,7 @@ func getPackagesInfo(pkgs []string) ([]byte, error) {
// getRPMPackagesInfo returns info about installed packages from rpm
func getRPMPackagesInfo(pkgs []string) ([]byte, error) {
- cmd := exec.Command("rpm", "-q", "--queryformat", "%{FILEMD5S}\n")
+ cmd := exec.Command("rpm", "-q", "--qf", "%{pkgid}\n")
cmd.Env = []string{"LC_ALL=C"}
cmd.Args = append(cmd.Args, pkgs...)
diff --git a/cli/cli.go b/cli/cli.go
index 8c773f18..96f16c55 100644
--- a/cli/cli.go
+++ b/cli/cli.go
@@ -18,7 +18,6 @@ import (
"github.com/essentialkaos/ek/v12/fmtutil/panel"
"github.com/essentialkaos/ek/v12/fmtutil/table"
"github.com/essentialkaos/ek/v12/fsutil"
- "github.com/essentialkaos/ek/v12/mathutil"
"github.com/essentialkaos/ek/v12/options"
"github.com/essentialkaos/ek/v12/req"
"github.com/essentialkaos/ek/v12/strutil"
@@ -41,7 +40,7 @@ import (
// Application info
const (
APP = "bibop"
- VER = "7.5.0"
+ VER = "8.0.0"
DESC = "Utility for testing command-line tools"
)
@@ -56,6 +55,7 @@ const (
OPT_BARCODE = "B:barcode"
OPT_EXTRA = "X:extra"
OPT_TIME = "T:time"
+ OPT_PAUSE = "P:pause"
OPT_FORMAT = "f:format"
OPT_DIR = "d:dir"
OPT_PATH = "p:path"
@@ -81,8 +81,9 @@ var optMap = options.Map{
OPT_LIST_PACKAGES_FLAT: {Type: options.BOOL},
OPT_VARIABLES: {Type: options.BOOL},
OPT_BARCODE: {Type: options.BOOL},
- OPT_EXTRA: {Type: options.MIXED},
+ OPT_EXTRA: {Type: options.INT, Value: 10, Min: 1, Max: 256},
OPT_TIME: {Type: options.BOOL},
+ OPT_PAUSE: {Type: options.FLOAT, Max: 60},
OPT_FORMAT: {},
OPT_DIR: {},
OPT_PATH: {},
@@ -261,7 +262,7 @@ func process(file string) {
switch {
case options.GetB(OPT_LIST_PACKAGES),
options.GetB(OPT_LIST_PACKAGES_FLAT):
- listPackages(r.Packages)
+ listPackages(r)
os.Exit(0)
case options.GetB(OPT_VARIABLES):
listVariables(r)
@@ -278,13 +279,11 @@ func process(file string) {
cfg := &executor.Config{
Quiet: options.GetB(OPT_QUIET),
DisableCleanup: options.GetB(OPT_NO_CLEANUP),
+ DebugLines: options.GetI(OPT_EXTRA),
+ Pause: options.GetF(OPT_PAUSE),
ErrsDir: errDir,
}
- if options.GetB(OPT_EXTRA) {
- cfg.DebugLines = mathutil.Max(10, options.GetI(OPT_EXTRA))
- }
-
e := executor.NewExecutor(cfg)
tags := strutil.Fields(options.GetS(OPT_TAG))
@@ -320,16 +319,16 @@ func validate(e *executor.Executor, r *recipe.Recipe, tags []string) {
}
// listPackages shows list packages required by recipe
-func listPackages(pkgs []string) {
- if len(pkgs) == 0 {
+func listPackages(r *recipe.Recipe) {
+ if len(r.Packages) == 0 {
return
}
if options.GetB(OPT_LIST_PACKAGES_FLAT) {
- fmt.Println(strings.Join(pkgs, " "))
+ fmt.Println(strings.Join(r.Packages, " "))
} else {
fmtc.If(!rawOutput).NewLine()
- for _, pkg := range pkgs {
+ for _, pkg := range r.Packages {
fmtc.If(!rawOutput).Printf("{s-}•{!} %s\n", pkg)
fmtc.If(rawOutput).Printf("%s\n", pkg)
}
@@ -362,11 +361,12 @@ func getValidationConfig(tags []string) *executor.ValidationConfig {
if options.GetB(OPT_DRY_RUN) {
vc.IgnoreDependencies = true
+ vc.IgnorePackages = true
vc.IgnorePrivileges = true
}
if options.GetB(OPT_IGNORE_PACKAGES) {
- vc.IgnoreDependencies = true
+ vc.IgnorePackages = true
}
return vc
@@ -430,11 +430,11 @@ func printCompletion() int {
switch options.GetS(OPT_COMPLETION) {
case "bash":
- fmt.Printf(bash.Generate(info, "bibop", "recipe"))
+ fmt.Print(bash.Generate(info, "bibop", "recipe"))
case "fish":
- fmt.Printf(fish.Generate(info, "bibop"))
+ fmt.Print(fish.Generate(info, "bibop"))
case "zsh":
- fmt.Printf(zsh.Generate(info, optMap, "bibop", "*.recipe"))
+ fmt.Print(zsh.Generate(info, optMap, "bibop", "*.recipe"))
default:
return 1
}
@@ -444,12 +444,7 @@ func printCompletion() int {
// printMan prints man page
func printMan() {
- fmt.Println(
- man.Generate(
- genUsage(),
- genAbout(""),
- ),
- )
+ fmt.Println(man.Generate(genUsage(), genAbout("")))
}
// genUsage generates usage info
@@ -459,7 +454,8 @@ func genUsage() *usage.Info {
info.AppNameColorTag = "{*}" + colorTagApp
info.AddOption(OPT_DRY_RUN, "Parse and validate recipe")
- info.AddOption(OPT_EXTRA, "Print the last lines from command output if action was failed", "?lines")
+ info.AddOption(OPT_EXTRA, "Number of output lines for failed action {s-}(default: 10){!}", "lines")
+ info.AddOption(OPT_PAUSE, "Pause between commands in seconds", "duration")
info.AddOption(OPT_LIST_PACKAGES, "List required packages")
info.AddOption(OPT_LIST_PACKAGES_FLAT, "List required packages in one line {s-}(useful for scripts){!}")
info.AddOption(OPT_VARIABLES, "List recipe variables")
diff --git a/cli/executor/executor.go b/cli/executor/executor.go
index ffa34ff2..99d4c3c1 100644
--- a/cli/executor/executor.go
+++ b/cli/executor/executor.go
@@ -28,7 +28,7 @@ import (
"github.com/essentialkaos/ek/v12/timeutil"
"github.com/essentialkaos/ek/v12/tmp"
- "github.com/google/goterm/term"
+ "github.com/creack/pty"
"github.com/essentialkaos/bibop/action"
"github.com/essentialkaos/bibop/recipe"
@@ -55,6 +55,7 @@ type Executor struct {
// ExecutorConfig contains executor configuration
type Config struct {
ErrsDir string
+ Pause float64
DebugLines int
Quiet bool
DisableCleanup bool
@@ -63,6 +64,7 @@ type Config struct {
// ValidationConfig is config for validation
type ValidationConfig struct {
Tags []string
+ IgnorePackages bool
IgnoreDependencies bool
IgnorePrivileges bool
}
@@ -71,7 +73,13 @@ type ValidationConfig struct {
type CommandEnv struct {
cmd *exec.Cmd
output *action.OutputContainer
- pty *term.PTY
+ term *PTY
+}
+
+// PTY contains pseudo-terminal structs
+type PTY struct {
+ pty *os.File
+ tty *os.File
}
// ////////////////////////////////////////////////////////////////////////////////// //
@@ -137,6 +145,7 @@ var handlers = map[string]action.Handler{
recipe.ACTION_LIB_EXPORTED: action.LibExported,
recipe.ACTION_PYTHON2_PACKAGE: action.Python2Package,
recipe.ACTION_PYTHON3_PACKAGE: action.Python3Package,
+ recipe.ACTION_TEMPLATE: action.Template,
}
var temp *tmp.Temp
@@ -163,10 +172,14 @@ func (e *Executor) Validate(r *recipe.Recipe, cfg *ValidationConfig) []error {
errs.Add(checkRecipePrivileges(r))
}
- if !cfg.IgnoreDependencies {
+ if !cfg.IgnorePackages {
errs.Add(checkPackages(r))
}
+ if !cfg.IgnoreDependencies {
+ errs.Add(checkDependencies(r))
+ }
+
if !errs.HasErrors() {
return nil
}
@@ -201,6 +214,23 @@ func (e *Executor) Run(rr render.Renderer, r *recipe.Recipe, tags []string) bool
// ////////////////////////////////////////////////////////////////////////////////// //
+// Close closes tty and pty
+func (t *PTY) Close() {
+ if t == nil {
+ return
+ }
+
+ if t.pty != nil {
+ t.pty.Close()
+ }
+
+ if t.tty != nil {
+ t.pty.Close()
+ }
+}
+
+// ////////////////////////////////////////////////////////////////////////////////// //
+
// applyRecipeOptions applies recipe options to executor
func applyRecipeOptions(e *Executor, rr render.Renderer, r *recipe.Recipe) {
if r.HTTPSSkipVerify {
@@ -254,7 +284,9 @@ func processRecipe(e *Executor, rr render.Renderer, r *recipe.Recipe, tags []str
rr.CommandDone(command, isLastCommand)
}
- if r.Delay > 0 {
+ if e.config.Pause > 0 {
+ time.Sleep(timeutil.SecondsToDuration(e.config.Pause))
+ } else if r.Delay > 0 {
time.Sleep(timeutil.SecondsToDuration(r.Delay))
}
}
@@ -290,7 +322,7 @@ func runCommand(e *Executor, rr render.Renderer, c *recipe.Command) bool {
}
if err != nil {
- if !e.config.Quiet && e.config.DebugLines > 0 && cmdEnv != nil && !cmdEnv.output.IsEmpty() {
+ if !e.config.Quiet && cmdEnv != nil && !cmdEnv.output.IsEmpty() {
fmtc.NewLine()
panel.Panel(
"☴ OUTPUT", "{y}",
@@ -319,7 +351,7 @@ func execCommand(c *recipe.Command) (*CommandEnv, error) {
return nil, err
}
- cmdEnv.pty, err = createPseudoTerminal(cmdEnv.cmd)
+ cmdEnv.term, err = createPTY(cmdEnv.cmd)
if err != nil {
return nil, err
@@ -332,6 +364,7 @@ func execCommand(c *recipe.Command) (*CommandEnv, error) {
err = cmdEnv.cmd.Start()
if err != nil {
+ cmdEnv.term.Close()
return nil, err
}
@@ -408,7 +441,7 @@ func runAction(a *recipe.Action, cmdEnv *CommandEnv) error {
case recipe.ACTION_EXPECT:
return action.Expect(a, cmdEnv.output)
case recipe.ACTION_PRINT:
- return action.Input(a, cmdEnv.pty.Master, cmdEnv.output)
+ return action.Input(a, cmdEnv.term.pty, cmdEnv.output)
case recipe.ACTION_WAIT_OUTPUT:
return action.WaitOutput(a, cmdEnv.output)
case recipe.ACTION_OUTPUT_CONTAINS:
@@ -436,25 +469,20 @@ func runAction(a *recipe.Action, cmdEnv *CommandEnv) error {
return handler(a)
}
-// createPseudoTerminal creates pseudo-terminal
-func createPseudoTerminal(cmd *exec.Cmd) (*term.PTY, error) {
- pty, err := term.OpenPTY()
+// createPTY creates pseudo-terminal
+func createPTY(cmd *exec.Cmd) (*PTY, error) {
+ p, t, err := pty.Open()
if err != nil {
return nil, err
}
- termios := &term.Termios{}
- termios.Raw()
- termios.Set(pty.Slave)
-
+ cmd.Stdin, cmd.Stdout, cmd.Stderr = t, t, t
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true, Setctty: true}
- cmd.Stdin = pty.Slave
- cmd.Stdout = pty.Slave
- cmd.Stderr = pty.Slave
+ pty.Setsize(p, &pty.Winsize{Rows: 80, Cols: 256})
- return pty, nil
+ return &PTY{pty: p, tty: t}, nil
}
// outputIOLoop reads data from reader and writes it to output store
@@ -462,14 +490,15 @@ func outputIOLoop(cmdEnv *CommandEnv) {
buf := make([]byte, 8192)
for {
- n, _ := cmdEnv.pty.Master.Read(buf[:cap(buf)])
+ n, _ := cmdEnv.term.pty.Read(buf[:cap(buf)])
if n > 0 {
cmdEnv.output.Write(buf[:n])
+ continue
}
if cmdEnv.cmd.ProcessState != nil && cmdEnv.cmd.ProcessState.Exited() {
- cmdEnv.pty.Close()
+ cmdEnv.term.Close()
return
}
}
diff --git a/cli/executor/validators.go b/cli/executor/validators.go
index a4393ad9..25f5b0ca 100644
--- a/cli/executor/validators.go
+++ b/cli/executor/validators.go
@@ -121,7 +121,7 @@ func checkRecipeVariables(r *recipe.Recipe) []error {
return errs
}
-// checkPackages checks packages
+// checkPackages checks if required packages are installed on the system
func checkPackages(r *recipe.Recipe) []error {
if len(r.Packages) == 0 {
return nil
@@ -137,6 +137,67 @@ func checkPackages(r *recipe.Recipe) []error {
return []error{errors.New("Can't check required packages availability: Unsupported OS")}
}
+// checkDependencies checks if all required binaries are present on the system
+func checkDependencies(r *recipe.Recipe) []error {
+ var errs []error
+
+ binCache := make(map[string]bool)
+
+ for _, c := range r.Commands {
+ for _, a := range c.Actions {
+ var binary string
+
+ switch a.Name {
+ case recipe.ACTION_SERVICE_PRESENT:
+ binary = "systemctl"
+ case recipe.ACTION_SERVICE_ENABLED:
+ binary = "systemctl"
+ case recipe.ACTION_SERVICE_WORKS:
+ binary = "systemctl"
+ case recipe.ACTION_WAIT_SERVICE:
+ binary = "systemctl"
+ case recipe.ACTION_LIB_LOADED:
+ binary = "ldconfig"
+ case recipe.ACTION_LIB_CONFIG:
+ binary = "pkg-config"
+ case recipe.ACTION_LIB_LINKED:
+ binary = "readelf"
+ case recipe.ACTION_LIB_RPATH:
+ binary = "readelf"
+ case recipe.ACTION_LIB_SONAME:
+ binary = "readelf"
+ case recipe.ACTION_LIB_EXPORTED:
+ binary = "nm"
+ case recipe.ACTION_PYTHON2_PACKAGE:
+ binary = "python"
+ case recipe.ACTION_PYTHON3_PACKAGE:
+ binary = "python3"
+ }
+
+ if !hasBinary(binCache, binary) {
+ errs = append(errs, fmt.Errorf(
+ "Line %d: Action %q requires %q binary", a.Line, a.Name, binary,
+ ))
+ }
+ }
+ }
+
+ return errs
+}
+
+// hasBinary checks if binary is present on the system
+func hasBinary(binCache map[string]bool, binary string) bool {
+ isExist, ok := binCache[binary]
+
+ if ok {
+ return isExist
+ }
+
+ binCache[binary] = env.Which(binary) != ""
+
+ return binCache[binary]
+}
+
// getDynamicVars returns slice with dynamic vars
func getDynamicVars(a *recipe.Action) []string {
switch a.Name {
diff --git a/cli/support/support.go b/cli/support/support.go
index 9374af07..cfdd9493 100644
--- a/cli/support/support.go
+++ b/cli/support/support.go
@@ -11,6 +11,7 @@ import (
"fmt"
"os"
"runtime"
+ "runtime/debug"
"strings"
"github.com/essentialkaos/ek/v12/fmtc"
@@ -67,6 +68,10 @@ func showApplicationInfo(app, ver, gitRev string) {
runtime.GOOS, runtime.GOARCH,
))
+ if gitRev == "" {
+ gitRev = extractGitRevFromBuildInfo()
+ }
+
if gitRev != "" {
if !fmtc.DisableColors && fmtc.IsTrueColorSupported() {
printInfo(7, "Git SHA", gitRev+getHashColorBullet(gitRev))
@@ -107,6 +112,23 @@ func showDepsInfo(gomod []byte) {
}
}
+// extractGitRevFromBuildInfo extracts git SHA from embedded build info
+func extractGitRevFromBuildInfo() string {
+ info, ok := debug.ReadBuildInfo()
+
+ if !ok {
+ return ""
+ }
+
+ for _, s := range info.Settings {
+ if s.Key == "vcs.revision" && len(s.Value) > 7 {
+ return s.Value[:7]
+ }
+ }
+
+ return ""
+}
+
// getHashColorBullet return bullet with color from hash
func getHashColorBullet(v string) string {
if len(v) > 6 {
@@ -118,7 +140,7 @@ func getHashColorBullet(v string) string {
// printInfo formats and prints info record
func printInfo(size int, name, value string) {
- name = name + ":"
+ name += ":"
size++
if value == "" {
diff --git a/cli/support/support_linux.go b/cli/support/support_linux.go
index 99f08a05..ef1c5671 100644
--- a/cli/support/support_linux.go
+++ b/cli/support/support_linux.go
@@ -15,6 +15,7 @@ import (
"github.com/essentialkaos/ek/v12/fmtutil"
"github.com/essentialkaos/ek/v12/fsutil"
"github.com/essentialkaos/ek/v12/system"
+ "github.com/essentialkaos/ek/v12/system/container"
)
// ////////////////////////////////////////////////////////////////////////////////// //
@@ -26,13 +27,14 @@ func showOSInfo() {
if err == nil {
fmtutil.Separator(false, "OS INFO")
- printInfo(12, "Name", osInfo.Name)
- printInfo(12, "Pretty Name", osInfo.PrettyName)
- printInfo(12, "Version", osInfo.VersionID)
+ printInfo(12, "Name", osInfo.ColoredName())
+ printInfo(12, "Pretty Name", osInfo.ColoredPrettyName())
+ printInfo(12, "Version", osInfo.Version)
printInfo(12, "ID", osInfo.ID)
printInfo(12, "ID Like", osInfo.IDLike)
printInfo(12, "Version ID", osInfo.VersionID)
printInfo(12, "Version Code", osInfo.VersionCodename)
+ printInfo(12, "Platform ID", osInfo.PlatformID)
printInfo(12, "CPE", osInfo.CPEName)
}
@@ -40,11 +42,9 @@ func showOSInfo() {
if err != nil {
return
- } else {
- if osInfo == nil {
- fmtutil.Separator(false, "SYSTEM INFO")
- printInfo(12, "Name", systemInfo.OS)
- }
+ } else if osInfo == nil {
+ fmtutil.Separator(false, "SYSTEM INFO")
+ printInfo(12, "Name", systemInfo.OS)
}
printInfo(12, "Arch", systemInfo.Arch)
@@ -52,11 +52,13 @@ func showOSInfo() {
containerEngine := "No"
- switch {
- case fsutil.IsExist("/.dockerenv"):
+ switch container.GetEngine() {
+ case container.DOCKER:
containerEngine = "Yes (Docker)"
- case fsutil.IsExist("/run/.containerenv"):
+ case container.PODMAN:
containerEngine = "Yes (Podman)"
+ case container.LXC:
+ containerEngine = "Yes (LXC)"
}
fmtc.NewLine()
diff --git a/go.mod b/go.mod
index 86436757..da5d1708 100644
--- a/go.mod
+++ b/go.mod
@@ -4,15 +4,15 @@ go 1.18
require (
github.com/buger/jsonparser v1.1.1
+ github.com/creack/pty v1.1.20
github.com/essentialkaos/check v1.4.0
github.com/essentialkaos/depsy v1.1.0
- github.com/essentialkaos/ek/v12 v12.79.0
- github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2
+ github.com/essentialkaos/ek/v12 v12.83.2
)
require (
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
- golang.org/x/sys v0.12.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
)
diff --git a/go.sum b/go.sum
index e9ebc632..6920c7dc 100644
--- a/go.sum
+++ b/go.sum
@@ -1,14 +1,14 @@
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/creack/pty v1.1.20 h1:VIPb/a2s17qNeQgDnkfZC35RScx+blkKF8GV68n80J4=
+github.com/creack/pty v1.1.20/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/essentialkaos/check v1.4.0 h1:kWdFxu9odCxUqo1NNFNJmguGrDHgwi3A8daXX1nkuKk=
github.com/essentialkaos/check v1.4.0/go.mod h1:LMKPZ2H+9PXe7Y2gEoKyVAwUqXVgx7KtgibfsHJPus0=
github.com/essentialkaos/depsy v1.1.0 h1:U6dp687UkQwXlZU17Hg2KMxbp3nfZAoZ8duaeUFYvJI=
github.com/essentialkaos/depsy v1.1.0/go.mod h1:kpiTAV17dyByVnrbNaMcZt2jRwvuXClUYOzpyJQwtG8=
-github.com/essentialkaos/ek/v12 v12.79.0 h1:Dq/bCqk8/N5h/r5jJA2UHc1YoUEVYcc7xnR0DI5L9wA=
-github.com/essentialkaos/ek/v12 v12.79.0/go.mod h1:S9/XSKhEAdylL3PF8GAnUeKKyd92VrDGR4YGacHfz0c=
-github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2 h1:CVuJwN34x4xM2aT4sIKhmeib40NeBPhRihNjQmpJsA4=
-github.com/google/goterm v0.0.0-20200907032337-555d40f16ae2/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
+github.com/essentialkaos/ek/v12 v12.83.2 h1:gXFwLIBAZsdi5uT/vJj9ka/rd94jLR1NF6OGxAYbgkQ=
+github.com/essentialkaos/ek/v12 v12.83.2/go.mod h1:X0gkyjBCP4QiD+sV4D52aquLDLGUmHteMEL7Rsgbev0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -17,5 +17,5 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsK
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
-golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
-golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/parser/parser.go b/parser/parser.go
index 6fd04967..79b7c9a3 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -273,7 +273,7 @@ func getTokenInfo(keyword string) recipe.TokenInfo {
// isUselessRecipeLine return if line doesn't contains recipe data
func isUselessRecipeLine(line string) bool {
// Skip empty lines
- if line == "" || strings.Replace(line, " ", "", -1) == "" {
+ if line == "" || strings.ReplaceAll(line, " ", "") == "" {
return true
}
diff --git a/recipe/recipe_test.go b/recipe/recipe_test.go
index 1d60b7c5..ff471bfa 100644
--- a/recipe/recipe_test.go
+++ b/recipe/recipe_test.go
@@ -284,7 +284,7 @@ func (s *RecipeSuite) TestPythonVariables(c *C) {
python3Bin = "_unknown_"
c.Assert(evalPythonCode(3, "test"), Equals, "")
- c.Assert(getPythonBindingSuffix(), Equals, "")
+ c.Assert(getPythonBindingSuffix(3), Equals, "")
python3Bin = "python3"
}
diff --git a/recipe/runtime_variables.go b/recipe/runtime_variables.go
index ec33575b..e8b9e538 100644
--- a/recipe/runtime_variables.go
+++ b/recipe/runtime_variables.go
@@ -8,7 +8,6 @@ package recipe
// ////////////////////////////////////////////////////////////////////////////////// //
import (
- "fmt"
"os"
"os/exec"
"strconv"
@@ -153,7 +152,7 @@ func getRuntimeVariable(name string, r *Recipe) string {
dynVarCache[name] = getPythonSiteArch(3)
case "PYTHON3_BINDING_SUFFIX":
- dynVarCache[name] = getPythonBindingSuffix()
+ dynVarCache[name] = getPythonBindingSuffix(3)
case "LIBDIR":
dynVarCache[name] = getLibDir(false)
@@ -195,21 +194,8 @@ func getPythonSiteArch(majorVersion int) string {
}
// getPythonBindingSuffix returns suffix for Python bindings
-func getPythonBindingSuffix() string {
- version := getPythonVersion(3)
-
- if version == "" {
- return ""
- }
-
- version = strutil.Exclude(version, ".")
- systemInfo := getSystemInfo()
-
- if systemInfo == nil {
- return ""
- }
-
- return fmt.Sprintf(".cpython-%sm-%s-linux-gnu.so", version, systemInfo.Arch)
+func getPythonBindingSuffix(majorVersion int) string {
+ return evalPythonCode(majorVersion, `import sysconfig; print(sysconfig.get_config_var("EXT_SUFFIX"))`)
}
// evalPythonCode evaluates Python code
diff --git a/recipe/tokens.go b/recipe/tokens.go
index 64f3340e..58e55db0 100644
--- a/recipe/tokens.go
+++ b/recipe/tokens.go
@@ -106,6 +106,8 @@ const (
ACTION_PYTHON2_PACKAGE = "python2-package"
ACTION_PYTHON3_PACKAGE = "python3-package"
+
+ ACTION_TEMPLATE = "template"
)
// ////////////////////////////////////////////////////////////////////////////////// //
@@ -217,4 +219,6 @@ var Tokens = []TokenInfo{
{ACTION_PYTHON2_PACKAGE, 1, 1, false, false},
{ACTION_PYTHON3_PACKAGE, 1, 1, false, false},
+
+ {ACTION_TEMPLATE, 2, 3, false, false},
}
diff --git a/render/renderer_terminal.go b/render/renderer_terminal.go
index cc8a1820..8cb508f7 100644
--- a/render/renderer_terminal.go
+++ b/render/renderer_terminal.go
@@ -187,7 +187,7 @@ func (rr *TerminalRenderer) Result(passes, fails, skips int) {
}
d := rr.formatDuration(time.Since(rr.start), true)
- d = strings.Replace(d, ".", "{s-}.", -1) + "{!}"
+ d = strings.ReplaceAll(d, ".", "{s-}.") + "{!}"
fmtc.NewLine()
fmtc.Println(" {*}Duration:{!} " + d)
diff --git a/render/renderer_xml.go b/render/renderer_xml.go
index f363e15d..e2b22599 100644
--- a/render/renderer_xml.go
+++ b/render/renderer_xml.go
@@ -150,9 +150,9 @@ func (rr *XMLRenderer) Result(passes, fails, skips int) {
// ////////////////////////////////////////////////////////////////////////////////// //
func (rr *XMLRenderer) escapeData(data string) string {
- data = strings.Replace(data, "<", "<", -1)
- data = strings.Replace(data, ">", ">", -1)
- data = strings.Replace(data, "&", "&", -1)
+ data = strings.ReplaceAll(data, "<", "<")
+ data = strings.ReplaceAll(data, ">", ">")
+ data = strings.ReplaceAll(data, "&", "&")
return data
}
diff --git a/scripts/README.md b/scripts/README.md
new file mode 100644
index 00000000..315225da
--- /dev/null
+++ b/scripts/README.md
@@ -0,0 +1,157 @@
+### `bibop` scripts
+
+- `bibop-dep` — utility for installing/uninstalling recipe dependecnies
+- `bibop-docker` — `bibop` docker/podman wrapper
+- `bibop-libtest-gen` — utility for generating compilation tests for libraries
+- `bibop-libtest-gen` — utility listing linked shared libraries
+- `bibop-massive` — utility for mass package testing
+- `bibop-multi-check` — utility for checking different versions of package
+- `bibop-so-exported` — utility for generating exported symbols tests
+
+#### `bibop-dep`
+
+```
+Usage: bibop-dep {options} {action}
+
+Actions
+
+ install, i Install packages
+ reinstall, r Reinstall packages
+ uninstall, u Uninstall packages
+
+Options
+
+ --enablerepo, -ER repo Enable repository
+ --disablerepo, -DR repo Disable repository
+ --yes, -y Automatically answer yes for all questions
+ --no-color, -nc Disable colors in output
+ --help, -h Show this help message
+ --version, -v Show information about version
+
+Examples
+
+ bibop-dep install -ER kaos-testing myapp.recipe
+ Install packages for myapp recipe with enabled kaos-testing repository
+
+ bibop-dep install -ER kaos-testing,epel,cbr -y myapp.recipe
+ Install packages for myapp recipe with enabled repositories
+
+ bibop-dep uninstall
+ Uninstall all packages installed by previous transaction
+```
+
+#### `bibop-libtest-gen`
+
+```
+Usage: bibop-libtest-gen {options} devel-package
+
+Options
+
+ --list-libs, -L List all libs in package
+ --output, -o name Output source file (default: test.c)
+ --lib, -l name Lib name
+ --no-color, -nc Disable colors in output
+ --help, -h Show this help message
+ --version, -v Show information about version
+
+Examples
+
+ bibop-libtest-gen dirac-devel-1.0.2-15.el7.x86_64.rpm
+ Generate test.c with all required headers for RPM package
+
+ bibop-libtest-gen dirac-devel
+ Generate test.c with all required headers for installed package
+```
+
+#### `bibop-linked`
+
+```
+Usage: bibop-linked {options} binary-file
+
+Options
+
+ --no-color, -nc Disable colors in output
+ --help, -h Show this help message
+ --version, -v Show information about version
+
+Examples
+
+ bibop-linked /usr/bin/curl
+ List required shared libraries for binary file
+
+ bibop-linked /usr/lib64/libcurl.so.4
+ List required shared libraries for other library
+```
+
+#### `bibop-massive`
+
+```
+Usage: bibop-massive {options} recipe…
+
+Options
+
+ --validate, -V Just validate recipes
+ --recheck, -R Run only failed checks
+ --fresh, -F Clean all caches before run
+ --interrupt, -X Interrupt checks after first error
+ --barcode, -B Print unique barcode for every test
+ --enablerepo, -ER repo Enable repository
+ --disablerepo, -DR repo Disable repository
+ --error-dir, -e dir Path to directory with tests errors
+ --log, -l file Path to log file
+ --no-color, -nc Disable colors in output
+ --help, -h Show this help message
+ --version, -v Show information about version
+
+Examples
+
+ bibop-massive ~/tests/
+ Run all tests in given directory
+
+ bibop-massive -ER kaos-testing ~/tests/package1.recipe ~/tests/package2.recipe
+ Run 2 tests with enabled repository 'kaos-testing' for installing packages
+
+ bibop-massive -ER kaos-testing,epel,cbr ~/tests/package1.recipe
+ Run verbose test with enabled repositories for installing packages
+```
+
+#### `bibop-multi-check`
+
+```
+Usage: bibop-multi-check {options} recipe package-list
+
+Options
+
+ --enablerepo, -ER repo Enable repository
+ --disablerepo, -DR repo Disable repository
+ --error-dir, -e dir Path to directory with tests errors
+ --log, -l file Path to log file
+ --no-color, -nc Disable colors in output
+ --help, -h Show this help message
+ --version, -v Show information about version
+
+Examples
+
+ bibop-multi-check app.recipe package.list
+ Run tests for every package in list
+
+ bibop-multi-check -ER kaos-testing,epel,cbr ~/tests/package1.recipe app.recipe package.list
+ Run tests with enabled repositories for installing packages
+```
+
+#### `bibop-so-exported`
+
+```
+Usage: bibop-so-exported {options} package-name
+
+Options
+
+ --no-color, -nc Disable colors in output
+ --help, -h Show this help message
+ --version, -v Show information about version
+
+Examples
+
+ bibop-so-exported zlib
+ Create tests for exported symbols for shared libraries in package zlib
+```
diff --git a/scripts/bibop-linked b/scripts/bibop-linked
index 129141af..33268f9f 100755
--- a/scripts/bibop-linked
+++ b/scripts/bibop-linked
@@ -178,9 +178,9 @@ usage() {
show ""
show "Options" $BOLD
show ""
- show " ${CL_GREEN}--no-color, -nc${CL_NORM} ${CL_DARK}..........${CL_NORM} Disable colors in output"
- show " ${CL_GREEN}--help, -h${CL_NORM} ${CL_DARK}...............${CL_NORM} Show this help message"
- show " ${CL_GREEN}--version, -v${CL_NORM} ${CL_DARK}............${CL_NORM} Show information about version"
+ show " ${CL_GREEN}--no-color, -nc${CL_NORM} ${CL_DARK}..${CL_NORM} Disable colors in output"
+ show " ${CL_GREEN}--help, -h${CL_NORM} ${CL_DARK}.......${CL_NORM} Show this help message"
+ show " ${CL_GREEN}--version, -v${CL_NORM} ${CL_DARK}....${CL_NORM} Show information about version"
show ""
show "Examples" $BOLD
show ""
diff --git a/scripts/bibop-massive b/scripts/bibop-massive
index b1d0b73c..0c89025c 100755
--- a/scripts/bibop-massive
+++ b/scripts/bibop-massive
@@ -3,7 +3,7 @@
################################################################################
APP="bibop-massive"
-VER="1.11.0"
+VER="1.12.0"
DESC="Utility for mass package testing"
################################################################################
@@ -100,6 +100,9 @@ max_recipe_len=0
# Path to package manager log file
pm_log=""
+# Pre install transaction ID
+preinstall_transaction_id=""
+
# Canceled flag
is_canceled=""
@@ -262,12 +265,12 @@ processRecipes() {
for recipe in "$@" ; do
if [[ -d $recipe ]] ; then
- recipe_list=$(find "$recipe" -type f -name "*.recipe" | grep -vE '\-el[7-9]')
+ recipe_list=$(find "$recipe" -name "*.recipe")
if [[ -n "$recipe_list" ]] ; then
recipes="$recipes $recipe_list"
fi
- elif [[ -f $recipe && $recipe = *.recipe ]]; then
+ elif [[ -e $recipe && $recipe = *.recipe ]]; then
recipes="$recipes $recipe"
else
error "Can't use $recipe as a recipe source"
@@ -296,9 +299,14 @@ filterRecipes() {
for recipe in $recipe_list ; do
if [[ -n "$dist" && -z "$validate" ]] ; then
- # Check for dist specific recipe
- if [[ -f "${recipe%.recipe}-el${dist}.recipe" ]] ; then
- recipe="${recipe%.recipe}-el${dist}.recipe"
+ if [[ $recipe =~ .*-el[7-9]\.recipe$ ]] ; then
+ if [[ "$recipe" != "${recipe%-el?.recipe}-el${dist}.recipe" ]] ; then
+ continue
+ fi
+ else
+ if [[ -e "${recipe%.recipe}-el${dist}.recipe" ]] ; then
+ continue
+ fi
fi
fi
@@ -728,6 +736,8 @@ checkPackagesAvailability() {
installPackages() {
local opts pkg_count tmp_output status problems
+ preinstall_transaction_id=$(getLastTransactionID)
+
log "Installing packages → $*"
truncate -s 0 "$pm_log"
@@ -770,12 +780,16 @@ installPackages() {
installPackagesVerbose() {
local pkg_list="$*"
+ preinstall_transaction_id=$(getLastTransactionID)
+
show "Installing ${pkg_list// /, }…\n" $BOLD
# shellcheck disable=SC2046
LC_ALL=C yum -y $(getPMOpts) install "$@"
- return $?
+ status=$?
+
+ return $status
}
# Uninstall required packages
@@ -789,6 +803,11 @@ uninstallPackages() {
truncate -s 0 "$pm_log"
+ if [[ "$preinstall_transaction_id" == "$(getLastTransactionID)" ]] ; then
+ log "No packages were installed, nothing to uninstall"
+ return $STATUS_OK
+ fi
+
tmp_output=$(mktemp)
yum -y history undo last &> "$tmp_output"
@@ -824,6 +843,11 @@ uninstallPackages() {
uninstallPackagesVerbose() {
show "Uninstalling packages…\n" $BOLD
+ if [[ "$preinstall_transaction_id" == "$(getLastTransactionID)" ]] ; then
+ show "No packages were installed, nothing to uninstall. Continue…" $GREY
+ return $STATUS_OK
+ fi
+
yum -y history undo last
return $?
@@ -956,6 +980,14 @@ cleanPMCache() {
fi
}
+# Get ID of the latest transaction in history
+#
+# Code: No
+# Echo: ID (Number)
+getLastTransactionID() {
+ yum history list | grep -A2 'ID ' | tail -1 | tr -s ' ' | sed 's/^ \+//' | cut -f1 -d' '
+}
+
# Generate list of YUM/DNF options
#
# Code: No
diff --git a/scripts/bibop-so-exported b/scripts/bibop-so-exported
index 8a6b602b..5d60ecc0 100755
--- a/scripts/bibop-so-exported
+++ b/scripts/bibop-so-exported
@@ -204,9 +204,9 @@ usage() {
show ""
show "Options" $BOLD
show ""
- show " ${CL_GREEN}--no-color, -nc${CL_NORM} ${CL_DARK}..........${CL_NORM} Disable colors in output"
- show " ${CL_GREEN}--help, -h${CL_NORM} ${CL_DARK}...............${CL_NORM} Show this help message"
- show " ${CL_GREEN}--version, -v${CL_NORM} ${CL_DARK}............${CL_NORM} Show information about version"
+ show " ${CL_GREEN}--no-color, -nc${CL_NORM} ${CL_DARK}..${CL_NORM} Disable colors in output"
+ show " ${CL_GREEN}--help, -h${CL_NORM} ${CL_DARK}.......${CL_NORM} Show this help message"
+ show " ${CL_GREEN}--version, -v${CL_NORM} ${CL_DARK}....${CL_NORM} Show information about version"
show ""
show "Examples" $BOLD
show ""