From 00ca80bbe5211b436d787e25347d41030bb6a9ae Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 19 Sep 2020 09:04:13 +0300 Subject: [PATCH 01/11] Update Travis config --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 809f0501..8ec7f455 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.13.x - 1.14.x + - 1.15.x - tip os: @@ -19,7 +19,7 @@ matrix: - go: tip env: - - SHELLCHECK_VERSION=0.7.1 HADOLINT_VERSION=1.17.6 + - SHELLCHECK_VERSION=0.7.1 HADOLINT_VERSION=1.18.0 before_install: - wget https://github.com/koalaman/shellcheck/releases/download/v${SHELLCHECK_VERSION}/shellcheck-v${SHELLCHECK_VERSION}.linux.x86_64.tar.xz From 1a84741b1df3f409a1ccfd3a476b3d4ec6f949de Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 19 Sep 2020 09:06:33 +0300 Subject: [PATCH 02/11] Minor UI fix --- cli/cli.go | 2 +- cli/executor/executor.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index 687a057c..1a71ac58 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -34,7 +34,7 @@ import ( // Application info const ( APP = "bibop" - VER = "2.4.0" + VER = "2.4.1" DESC = "Utility for testing command-line tools" ) diff --git a/cli/executor/executor.go b/cli/executor/executor.go index b973239a..c400f619 100644 --- a/cli/executor/executor.go +++ b/cli/executor/executor.go @@ -201,10 +201,6 @@ func processRecipe(e *Executor, r *recipe.Recipe, tags []string) { printCommandHeader(e, command) ok := runCommand(e, command) - if index+1 != len(r.Commands) && !e.config.Quiet { - fmtc.NewLine() - } - e.skipped-- if !ok { @@ -217,6 +213,10 @@ func processRecipe(e *Executor, r *recipe.Recipe, tags []string) { e.passes++ } + if index+1 != len(r.Commands) && !e.config.Quiet { + fmtc.NewLine() + } + if r.Delay > 0 { time.Sleep(timeutil.SecondsToDuration(r.Delay)) } From a13fd79134f79803d699dd7785cd924fdefa93c8 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 19 Sep 2020 09:30:16 +0300 Subject: [PATCH 03/11] Add option for disabling package installation check --- cli/cli.go | 73 ++++++++++++++++++++++---------------- cli/executor/validators.go | 2 +- 2 files changed, 43 insertions(+), 32 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index 1a71ac58..e19e8534 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -34,7 +34,7 @@ import ( // Application info const ( APP = "bibop" - VER = "2.4.1" + VER = "2.5.0" DESC = "Utility for testing command-line tools" ) @@ -42,16 +42,17 @@ const ( // Options const ( - OPT_DIR = "d:dir" - OPT_ERROR_DIR = "e:error-dir" - OPT_TAG = "t:tag" - OPT_QUIET = "q:quiet" - OPT_DRY_RUN = "D:dry-run" - OPT_LIST_PACKAGES = "L:list-packages" - OPT_NO_CLEANUP = "NC:no-cleanup" - OPT_NO_COLOR = "nc:no-color" - OPT_HELP = "h:help" - OPT_VER = "v:version" + OPT_DIR = "d:dir" + OPT_ERROR_DIR = "e:error-dir" + OPT_TAG = "t:tag" + OPT_QUIET = "q:quiet" + OPT_DRY_RUN = "D:dry-run" + OPT_LIST_PACKAGES = "L:list-packages" + OPT_INGORE_PACKAGES = "ip:ignore-packages" + OPT_NO_CLEANUP = "nl:no-cleanup" + OPT_NO_COLOR = "nc:no-color" + OPT_HELP = "h:help" + OPT_VER = "v:version" OPT_COMPLETION = "completion" ) @@ -59,16 +60,17 @@ const ( // ////////////////////////////////////////////////////////////////////////////////// // var optMap = options.Map{ - OPT_DIR: {}, - OPT_ERROR_DIR: {}, - OPT_TAG: {Mergeble: true}, - OPT_QUIET: {Type: options.BOOL}, - OPT_DRY_RUN: {Type: options.BOOL}, - OPT_LIST_PACKAGES: {Type: options.BOOL}, - OPT_NO_CLEANUP: {Type: options.BOOL}, - OPT_NO_COLOR: {Type: options.BOOL}, - OPT_HELP: {Type: options.BOOL, Alias: "u:usage"}, - OPT_VER: {Type: options.BOOL, Alias: "ver"}, + OPT_DIR: {}, + OPT_ERROR_DIR: {}, + OPT_TAG: {Mergeble: true}, + OPT_QUIET: {Type: options.BOOL}, + OPT_DRY_RUN: {Type: options.BOOL}, + OPT_LIST_PACKAGES: {Type: options.BOOL}, + OPT_INGORE_PACKAGES: {Type: options.BOOL}, + OPT_NO_CLEANUP: {Type: options.BOOL}, + OPT_NO_COLOR: {Type: options.BOOL}, + OPT_HELP: {Type: options.BOOL, Alias: "u:usage"}, + OPT_VER: {Type: options.BOOL, Alias: "ver"}, OPT_COMPLETION: {}, } @@ -199,14 +201,7 @@ func process(file string) { // validate validates recipe and print validation errors func validate(e *executor.Executor, r *recipe.Recipe, tags []string) { - vc := &executor.ValidationConfig{Tags: tags} - - if options.GetB(OPT_DRY_RUN) { - vc.IgnoreDependencies = true - vc.IgnorePrivileges = true - } - - errs := e.Validate(r, vc) + errs := e.Validate(r, getValidationConfig(tags)) if len(errs) == 0 { if options.GetB(OPT_DRY_RUN) { @@ -239,6 +234,21 @@ func listPackages(pkgs []string) { os.Exit(0) } +func getValidationConfig(tags []string) *executor.ValidationConfig { + vc := &executor.ValidationConfig{Tags: tags} + + if options.GetB(OPT_DRY_RUN) { + vc.IgnoreDependencies = true + vc.IgnorePrivileges = true + } + + if options.GetB(OPT_INGORE_PACKAGES) { + vc.IgnoreDependencies = true + } + + return vc +} + // printError prints error message to console func printError(f string, a ...interface{}) { fmtc.Fprintf(os.Stderr, "{r}"+f+"{!}\n", a...) @@ -266,12 +276,13 @@ func showUsage() { func genUsage() *usage.Info { info := usage.NewInfo("", "recipe") + info.AddOption(OPT_DRY_RUN, "Parse and validate recipe") + info.AddOption(OPT_LIST_PACKAGES, "List required packages") info.AddOption(OPT_DIR, "Path to working directory", "dir") info.AddOption(OPT_ERROR_DIR, "Path to directory for errors data", "dir") info.AddOption(OPT_TAG, "Command tag", "tag") info.AddOption(OPT_QUIET, "Quiet mode") - info.AddOption(OPT_DRY_RUN, "Parse and validate recipe") - info.AddOption(OPT_LIST_PACKAGES, "List required packages") + info.AddOption(OPT_INGORE_PACKAGES, "Do not check system for installed packages") info.AddOption(OPT_NO_CLEANUP, "Disable deleting files created during tests") info.AddOption(OPT_NO_COLOR, "Disable colors in output") info.AddOption(OPT_HELP, "Show this help message") diff --git a/cli/executor/validators.go b/cli/executor/validators.go index 4de7f4b7..6aed5cc3 100644 --- a/cli/executor/validators.go +++ b/cli/executor/validators.go @@ -132,7 +132,7 @@ func checkPackages(r *recipe.Recipe) []error { } switch systemInfo.Distribution { - case system.LINUX_CENTOS: + case system.LINUX_CENTOS, system.LINUX_RHEL, system.LINUX_FEDORA: return checkRPMPackages(r.Packages) default: return nil From 6d05af002731a6525e7dbec04dbb9e60bc442279 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 19 Sep 2020 09:38:41 +0300 Subject: [PATCH 04/11] Add option for adding path to PATH environment variable --- cli/cli.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/cli/cli.go b/cli/cli.go index e19e8534..1dbc9a5b 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -42,12 +42,13 @@ const ( // Options const ( + OPT_DRY_RUN = "D:dry-run" + OPT_LIST_PACKAGES = "L:list-packages" OPT_DIR = "d:dir" + OPT_PATH = "p:path" OPT_ERROR_DIR = "e:error-dir" OPT_TAG = "t:tag" OPT_QUIET = "q:quiet" - OPT_DRY_RUN = "D:dry-run" - OPT_LIST_PACKAGES = "L:list-packages" OPT_INGORE_PACKAGES = "ip:ignore-packages" OPT_NO_CLEANUP = "nl:no-cleanup" OPT_NO_COLOR = "nc:no-color" @@ -60,12 +61,13 @@ const ( // ////////////////////////////////////////////////////////////////////////////////// // var optMap = options.Map{ + OPT_DRY_RUN: {Type: options.BOOL}, + OPT_LIST_PACKAGES: {Type: options.BOOL}, OPT_DIR: {}, + OPT_PATH: {}, OPT_ERROR_DIR: {}, OPT_TAG: {Mergeble: true}, OPT_QUIET: {Type: options.BOOL}, - OPT_DRY_RUN: {Type: options.BOOL}, - OPT_LIST_PACKAGES: {Type: options.BOOL}, OPT_INGORE_PACKAGES: {Type: options.BOOL}, OPT_NO_CLEANUP: {Type: options.BOOL}, OPT_NO_COLOR: {Type: options.BOOL}, @@ -128,6 +130,15 @@ func configureUI() { // configureSubsystems configures bibop subsystems func configureSubsystems() { req.Global.SetUserAgent(APP, VER) + + if options.Has(OPT_PATH) { + newPath := os.Getenv("PATH") + ":" + options.GetS(OPT_PATH) + err := os.Setenv("PATH", newPath) + + if err != nil { + printErrorAndExit(err.Error()) + } + } } // validateOptions validates options @@ -279,6 +290,7 @@ func genUsage() *usage.Info { info.AddOption(OPT_DRY_RUN, "Parse and validate recipe") info.AddOption(OPT_LIST_PACKAGES, "List required packages") info.AddOption(OPT_DIR, "Path to working directory", "dir") + info.AddOption(OPT_PATH, "Path to directory with binaries", "path") info.AddOption(OPT_ERROR_DIR, "Path to directory for errors data", "dir") info.AddOption(OPT_TAG, "Command tag", "tag") info.AddOption(OPT_QUIET, "Quiet mode") From f0884ad94d0721975cfbc9fbba8d23f0f8362484 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 19 Sep 2020 09:39:46 +0300 Subject: [PATCH 05/11] Update README --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 853ff3c9..bd9ded62 100644 --- a/README.md +++ b/README.md @@ -101,13 +101,15 @@ Usage: bibop {options} recipe Options + --dry-run, -D Parse and validate recipe + --list-packages, -L List required packages --dir, -d dir Path to working directory + --path, -p path Path to directory with binaries --error-dir, -e dir Path to directory for errors data --tag, -t tag Command tag --quiet, -q Quiet mode - --ignore-packages, -ip Skip packages check - --dry-run, -D Parse and validate recipe - --list-packages, -L List required packages + --ignore-packages, -ip Do not check system for installed packages + --no-cleanup, -nl Disable deleting files created during tests --no-color, -nc Disable colors in output --help, -h Show this help message --version, -v Show version From f197afa41297d4147f80e371f51b46910483ba56 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Thu, 24 Sep 2020 14:11:54 +0300 Subject: [PATCH 06/11] Fix bug with reading and handling commands output --- action/io.go | 10 +++++----- cli/executor/executor.go | 29 +++++++++++++++++------------ output/output_store.go | 4 +--- 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/action/io.go b/action/io.go index 61e61667..022325ff 100644 --- a/action/io.go +++ b/action/io.go @@ -56,7 +56,7 @@ func Expect(action *recipe.Action, outputStore *output.Store) error { for range time.NewTicker(_DATA_READ_PERIOD).C { if bytes.Contains(stdout.Bytes(), []byte(substr)) || bytes.Contains(stderr.Bytes(), []byte(substr)) { - outputStore.Clear = true + outputStore.Purge() return nil } @@ -65,7 +65,7 @@ func Expect(action *recipe.Action, outputStore *output.Store) error { } } - outputStore.Clear = true + outputStore.Purge() return fmt.Errorf("Timeout (%g sec) reached", timeout) } @@ -106,9 +106,9 @@ func Input(action *recipe.Action, input io.Writer, outputStore *output.Store) er text = text + "\n" } - _, err = input.Write([]byte(text)) + outputStore.Purge() - outputStore.Clear = true + _, err = input.Write([]byte(text)) return err } @@ -159,6 +159,6 @@ func OutputContains(action *recipe.Action, outputStore *output.Store) error { // OutputTrim is action processor for "output-trim" func OutputTrim(action *recipe.Action, outputStore *output.Store) error { - outputStore.Clear = true + outputStore.Purge() return nil } diff --git a/cli/executor/executor.go b/cli/executor/executor.go index c400f619..9b5fd87e 100644 --- a/cli/executor/executor.go +++ b/cli/executor/executor.go @@ -37,7 +37,7 @@ import ( // ////////////////////////////////////////////////////////////////////////////////// // -const MAX_STORAGE_SIZE = 2 * 1024 * 1024 // 2 MB +const MAX_STORAGE_SIZE = 8 * 1024 * 1024 // 2 MB // ////////////////////////////////////////////////////////////////////////////////// // @@ -443,20 +443,25 @@ func connectOutputStore(cmd *exec.Cmd, outputStore *output.Store) { stdoutReader, _ := cmd.StdoutPipe() stderrReader, _ := cmd.StderrPipe() - go func(stdout, stderr io.Reader, outputStore *output.Store) { - for { - if outputStore.Clear { - outputStore.Purge() - } + go outputIOLoop(cmd, stdoutReader, outputStore.Stdout) + go outputIOLoop(cmd, stderrReader, outputStore.Stderr) +} - outputStore.Stdout.Write(ioutil.ReadAll(stdout)) - outputStore.Stderr.Write(ioutil.ReadAll(stderr)) +// outputIOLoop reads data from reader and writes it to output store container +func outputIOLoop(cmd *exec.Cmd, r io.Reader, c *output.Container) { + buf := make([]byte, 16384) - if cmd.ProcessState != nil && cmd.ProcessState.Exited() { - return - } + for { + n, _ := r.Read(buf[:cap(buf)]) + + if n > 0 { + c.Write(buf[:n]) } - }(stdoutReader, stderrReader, outputStore) + + if cmd.ProcessState != nil && cmd.ProcessState.Exited() { + return + } + } } // formatActionName format action name diff --git a/output/output_store.go b/output/output_store.go index 6b86ad12..563e1285 100644 --- a/output/output_store.go +++ b/output/output_store.go @@ -18,7 +18,6 @@ import ( type Store struct { Stdout *Container Stderr *Container - Clear bool } type Container struct { @@ -37,14 +36,13 @@ func NewStore(size int) *Store { return &Store{ &Container{size: size}, &Container{size: size}, - false, } } // ////////////////////////////////////////////////////////////////////////////////// // // Write writes data into buffer -func (c *Container) Write(data []byte, _ error) { +func (c *Container) Write(data []byte) { if len(data) == 0 { return } From 4afe326980e9a90ea056af507055b7a6a2fec4e1 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Thu, 24 Sep 2020 15:30:30 +0300 Subject: [PATCH 07/11] Add new actions for working with command output --- COOKBOOK.md | 48 ++++++++++++++++++++++++ action/io.go | 80 ++++++++++++++++++++++++++++++++++++++++ cli/executor/executor.go | 4 ++ recipe/tokens.go | 4 ++ 4 files changed, 136 insertions(+) diff --git a/COOKBOOK.md b/COOKBOOK.md index f82f66c9..269bf1ab 100644 --- a/COOKBOOK.md +++ b/COOKBOOK.md @@ -17,6 +17,8 @@ * [`wait`](#wait) * [Input/Output](#inputoutput) * [`expect`](#expect) + * [`expect-stdout`](#expect-stdout) + * [`expect-stderr`](#expect-stderr) * [`print`](#print) * [`wait-output`](#wait-output) * [`output-match`](#output-match) @@ -398,6 +400,52 @@ command "echo 'ABCD'" "Simple echo command with 1 seconds timeout"
+##### `expect-stdout` + +Expects some substring in command [standard output](http://www.linfo.org/standard_output.html). + +**Syntax:** `expect-stdout [max-wait]` + +**Arguments:** + +* `substr` - Substring for search (_String_) +* `max-wait` - Max wait time in seconds (_Float_) [Optional | 5 seconds] + +**Negative form:** No + +**Example:** + +```yang +command "myApp 1" "Simple command" + expect-stdout "Everything fine" + +``` + +
+ +##### `expect-stderr` + +Expects some substring in command [standard error](http://www.linfo.org/standard_error.html). + +**Syntax:** `expect-stderr [max-wait]` + +**Arguments:** + +* `substr` - Substring for search (_String_) +* `max-wait` - Max wait time in seconds (_Float_) [Optional | 5 seconds] + +**Negative form:** No + +**Example:** + +```yang +command "myApp ABCD" "Simple command" + expect-stderr "Error!" + +``` + +
+ ##### `print` Prints some data to `stdin`. diff --git a/action/io.go b/action/io.go index 022325ff..3934d5fc 100644 --- a/action/io.go +++ b/action/io.go @@ -70,6 +70,86 @@ func Expect(action *recipe.Action, outputStore *output.Store) error { return fmt.Errorf("Timeout (%g sec) reached", timeout) } +// ExpectStdout is action processor for "expect-stdout" +func ExpectStdout(action *recipe.Action, outputStore *output.Store) error { + var timeout float64 + + substr, err := action.GetS(0) + + if err != nil { + return err + } + + if action.Has(1) { + timeout, err = action.GetF(1) + + if err != nil { + return err + } + } else { + timeout = 5.0 + } + + start := time.Now() + timeout = mathutil.BetweenF64(timeout, 0.01, 3600.0) + timeoutDur := secondsToDuration(timeout) + + for range time.NewTicker(_DATA_READ_PERIOD).C { + if bytes.Contains(outputStore.Stderr.Bytes(), []byte(substr)) { + outputStore.Stderr.Purge() + return nil + } + + if time.Since(start) >= timeoutDur { + break + } + } + + outputStore.Stderr.Purge() + + return fmt.Errorf("Timeout (%g sec) reached", timeout) +} + +// ExpectStderr is action processor for "expect-stderr" +func ExpectStderr(action *recipe.Action, outputStore *output.Store) error { + var timeout float64 + + substr, err := action.GetS(0) + + if err != nil { + return err + } + + if action.Has(1) { + timeout, err = action.GetF(1) + + if err != nil { + return err + } + } else { + timeout = 5.0 + } + + start := time.Now() + timeout = mathutil.BetweenF64(timeout, 0.01, 3600.0) + timeoutDur := secondsToDuration(timeout) + + for range time.NewTicker(_DATA_READ_PERIOD).C { + if bytes.Contains(outputStore.Stdout.Bytes(), []byte(substr)) { + outputStore.Stdout.Purge() + return nil + } + + if time.Since(start) >= timeoutDur { + break + } + } + + outputStore.Stdout.Purge() + + return fmt.Errorf("Timeout (%g sec) reached", timeout) +} + // WaitOutput is action processor for "wait-output" func WaitOutput(action *recipe.Action, outputStore *output.Store) error { timeout, err := action.GetF(0) diff --git a/cli/executor/executor.go b/cli/executor/executor.go index 9b5fd87e..2d426245 100644 --- a/cli/executor/executor.go +++ b/cli/executor/executor.go @@ -411,6 +411,10 @@ func runAction(a *recipe.Action, cmd *exec.Cmd, input io.Writer, outputStore *ou return action.Exit(a, cmd) case recipe.ACTION_EXPECT: return action.Expect(a, outputStore) + case recipe.ACTION_EXPECT_STDOUT: + return action.ExpectStdout(a, outputStore) + case recipe.ACTION_EXPECT_STDERR: + return action.ExpectStderr(a, outputStore) case recipe.ACTION_PRINT: return action.Input(a, input, outputStore) case recipe.ACTION_WAIT_OUTPUT: diff --git a/recipe/tokens.go b/recipe/tokens.go index 6c634114..ac91ee32 100644 --- a/recipe/tokens.go +++ b/recipe/tokens.go @@ -22,6 +22,8 @@ const ( ACTION_WAIT = "wait" ACTION_EXPECT = "expect" + ACTION_EXPECT_STDOUT = "expect-stdout" + ACTION_EXPECT_STDERR = "expect-stderr" ACTION_WAIT_OUTPUT = "wait-output" ACTION_OUTPUT_MATCH = "output-match" ACTION_OUTPUT_CONTAINS = "output-contains" @@ -121,6 +123,8 @@ var Tokens = []TokenInfo{ {ACTION_WAIT, 1, 1, false, false}, {ACTION_EXPECT, 1, 2, false, false}, + {ACTION_EXPECT_STDOUT, 1, 2, false, false}, + {ACTION_EXPECT_STDERR, 1, 2, false, false}, {ACTION_WAIT_OUTPUT, 1, 1, false, false}, {ACTION_OUTPUT_MATCH, 1, 1, false, true}, {ACTION_OUTPUT_CONTAINS, 1, 1, false, true}, From b8dcfbaf27fee222b6def73624d1b16e8a94dfbc Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Fri, 25 Sep 2020 02:33:35 +0300 Subject: [PATCH 08/11] Add recipe option for disabling IO cache for pipes --- cli/executor/executor.go | 49 ++++++++++++++++++++++++++++++---------- parser/parser.go | 3 +++ parser/parser_test.go | 1 + recipe/recipe.go | 1 + recipe/tokens.go | 2 ++ testdata/test1.recipe | 1 + 6 files changed, 45 insertions(+), 12 deletions(-) diff --git a/cli/executor/executor.go b/cli/executor/executor.go index 2d426245..c3c9bda1 100644 --- a/cli/executor/executor.go +++ b/cli/executor/executor.go @@ -282,24 +282,21 @@ func runCommand(e *Executor, c *recipe.Command) bool { // execCommand executes command func execCommand(c *recipe.Command, outputStore *output.Store) (*exec.Cmd, io.Writer, error) { - var cmd *exec.Cmd + cmd, err := createCommand(c) - if c.User == "" { - cmdArgs := c.GetCmdlineArgs() - cmd = exec.Command(cmdArgs[0], cmdArgs[1:]...) - } else { - if !system.IsUserExist(c.User) { - return nil, nil, fmt.Errorf("Can't execute the command: user %s doesn't exist on the system", c.User) - } - - cmd = exec.Command("/sbin/runuser", "-s", "/bin/bash", c.User, "-c", c.GetCmdline()) + if err != nil { + return nil, nil, err } - input, _ := cmd.StdinPipe() + input, err := cmd.StdinPipe() + + if err != nil { + return nil, nil, err + } connectOutputStore(cmd, outputStore) - err := cmd.Start() + err = cmd.Start() if err != nil { return nil, nil, err @@ -310,6 +307,33 @@ func execCommand(c *recipe.Command, outputStore *output.Store) (*exec.Cmd, io.Wr return cmd, input, nil } +// createCommand creates command +func createCommand(c *recipe.Command) (*exec.Cmd, error) { + var cmdSlice []string + + if c.User != "" { + if !system.IsUserExist(c.User) { + return nil, fmt.Errorf("Can't execute the command: user %s doesn't exist on the system", c.User) + } + + cmdSlice = append(cmdSlice, "/sbin/runuser", "-s", "/bin/bash", c.User, "-c") + + if c.Recipe.Unbuffer { + cmdSlice = append(cmdSlice, "stdbuf -o0 -e0 -i0 "+c.GetCmdline()) + } else { + cmdSlice = append(cmdSlice, c.GetCmdline()) + } + } else { + if c.Recipe.Unbuffer { + cmdSlice = append(cmdSlice, "stdbuf", "-o0", "-e0", "-i0") + } + + cmdSlice = append(cmdSlice, c.GetCmdlineArgs()...) + } + + return exec.Command(cmdSlice[0], cmdSlice[1:]...), nil +} + // printBasicRecipeInfo print path to recipe and working dir func printBasicRecipeInfo(e *Executor, r *recipe.Recipe) { if e.config.Quiet { @@ -328,6 +352,7 @@ func printBasicRecipeInfo(e *Executor, r *recipe.Recipe) { printRecipeOptionFlag("Require root", r.RequireRoot) printRecipeOptionFlag("Fast finish", r.FastFinish) printRecipeOptionFlag("Lock workdir", r.LockWorkdir) + printRecipeOptionFlag("Unbuffered IO", r.Unbuffer) } // printResultInfo print info about finished test diff --git a/parser/parser.go b/parser/parser.go index 56dcb415..bcf176f2 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -208,6 +208,9 @@ func applyGlobalOptions(r *recipe.Recipe, e *entity, line uint16) error { case recipe.OPTION_LOCK_WORKDIR: r.LockWorkdir, err = getOptionBoolValue(e.info.Keyword, e.args[0]) + case recipe.OPTION_UNBUFFER: + r.Unbuffer, err = getOptionBoolValue(e.info.Keyword, e.args[0]) + case recipe.OPTION_DELAY: r.Delay, err = getOptionFloatValue(e.info.Keyword, e.args[0]) } diff --git a/parser/parser_test.go b/parser/parser_test.go index 91e448b0..050b9cb2 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -81,6 +81,7 @@ func (s *ParseSuite) TestBasicParsing(c *C) { c.Assert(recipe.RequireRoot, Equals, true) c.Assert(recipe.FastFinish, Equals, true) c.Assert(recipe.LockWorkdir, Equals, false) + c.Assert(recipe.Unbuffer, Equals, true) c.Assert(recipe.Delay, Equals, 1.23) c.Assert(recipe.Commands, HasLen, 2) c.Assert(recipe.Packages, DeepEquals, []string{"package1", "package2"}) diff --git a/recipe/recipe.go b/recipe/recipe.go index 58978468..f504bbb7 100644 --- a/recipe/recipe.go +++ b/recipe/recipe.go @@ -31,6 +31,7 @@ type Recipe struct { RequireRoot bool // Require root privileges FastFinish bool // Fast finish flag LockWorkdir bool // Locking workdir flag + Unbuffer bool // Disabled IO buffering Delay float64 // Delay between commands Packages []string // Package list Commands []*Command // Commands diff --git a/recipe/tokens.go b/recipe/tokens.go index ac91ee32..03632258 100644 --- a/recipe/tokens.go +++ b/recipe/tokens.go @@ -16,6 +16,7 @@ const ( OPTION_REQUIRE_ROOT = "require-root" OPTION_FAST_FINISH = "fast-finish" OPTION_LOCK_WORKDIR = "lock-workdir" + OPTION_UNBUFFER = "unbuffer" OPTION_DELAY = "delay" ACTION_EXIT = "exit" @@ -117,6 +118,7 @@ var Tokens = []TokenInfo{ {OPTION_REQUIRE_ROOT, 1, 1, true, false}, {OPTION_FAST_FINISH, 1, 1, true, false}, {OPTION_LOCK_WORKDIR, 1, 1, true, false}, + {OPTION_UNBUFFER, 1, 1, true, false}, {OPTION_DELAY, 1, 1, true, false}, {ACTION_EXIT, 1, 2, false, true}, diff --git a/testdata/test1.recipe b/testdata/test1.recipe index 458ba9ef..b1bc7b5f 100644 --- a/testdata/test1.recipe +++ b/testdata/test1.recipe @@ -6,6 +6,7 @@ unsafe-actions yes require-root no fast-finish yes lock-workdir no +unbuffer yes delay 1.23 var user nobody From d30ad53660eb71d7f5ab9cac65a8529b7315ef7a Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 26 Sep 2020 13:39:41 +0300 Subject: [PATCH 09/11] Add info about 'unbuffer' option to cookbook --- COOKBOOK.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/COOKBOOK.md b/COOKBOOK.md index 269bf1ab..e100237a 100644 --- a/COOKBOOK.md +++ b/COOKBOOK.md @@ -8,6 +8,7 @@ * [`require-root`](#require-root) * [`fast-finish`](#fast-finish) * [`lock-workdir`](#lock-workdir) + * [`unbuffer`](#unbuffer) * [`delay`](#delay) * [`command`](#command) * [Variables](#variables) @@ -200,6 +201,25 @@ lock-workdir no
+#### `unbuffer` + +Disables I/O stream buffering. + +**Syntax:** `unbuffer ` + +**Arguments:** + +* `flag` - Flag (_Boolean_) [`no` by default] + +**Example:** + +```yang +unbuffer yes + +``` + +
+ #### `delay` Delay between commands. From d96a54789c59464a3e327cd27494b9582c3c2f12 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 26 Sep 2020 14:51:09 +0300 Subject: [PATCH 10/11] Improve cookbook --- COOKBOOK.md | 192 +++++++++++++++++++++++++++------------------------- 1 file changed, 98 insertions(+), 94 deletions(-) diff --git a/COOKBOOK.md b/COOKBOOK.md index e100237a..d33d6071 100644 --- a/COOKBOOK.md +++ b/COOKBOOK.md @@ -100,7 +100,7 @@ dir "/home/john" ``` -
+ ### Global @@ -121,7 +121,7 @@ pkg php nginx libhttp2 libhttp2-devel ``` -
+ #### `unsafe-actions` @@ -140,7 +140,7 @@ unsafe-actions yes ``` -
+ #### `require-root` @@ -161,7 +161,7 @@ require-root yes ``` -
+ #### `fast-finish` @@ -180,7 +180,7 @@ fast-finish yes ``` -
+ #### `lock-workdir` @@ -199,7 +199,7 @@ lock-workdir no ``` -
+ #### `unbuffer` @@ -218,7 +218,7 @@ unbuffer yes ``` -
+ #### `delay` @@ -237,7 +237,7 @@ delay 1.5 ``` -
+ #### `command` @@ -284,7 +284,7 @@ command:init "my app initdb" "Init database" ``` -
+ ### Variables @@ -324,7 +324,7 @@ command "service start {service}" "Starting service" ``` -
+ ### Actions @@ -361,7 +361,7 @@ command "git clone git@github.com:user/repo.git" "Repository clone" ``` -
+ ##### `wait` @@ -383,7 +383,7 @@ command "echo 'ABCD'" "Simple echo command" ``` -
+ #### Input/Output @@ -418,7 +418,7 @@ command "echo 'ABCD'" "Simple echo command with 1 seconds timeout" ``` -
+ ##### `expect-stdout` @@ -441,7 +441,7 @@ command "myApp 1" "Simple command" ``` -
+ ##### `expect-stderr` @@ -464,7 +464,7 @@ command "myApp ABCD" "Simple command" ``` -
+ ##### `print` @@ -486,7 +486,7 @@ command "echo 'ABCD'" "Simple echo command" ``` -
+ ##### `wait-output` @@ -528,7 +528,7 @@ command "echo 'ABCD'" "Simple echo command" ``` -
+ ##### `output-contains` @@ -550,7 +550,7 @@ command "echo 'ABCD'" "Simple echo command" ``` -
+ ##### `output-trim` @@ -568,7 +568,7 @@ command "echo 'ABCD'" "Simple echo command" ``` -
+ #### Filesystem @@ -595,7 +595,7 @@ command "-" "Check environment" ``` -
+ ##### `mode` @@ -618,7 +618,7 @@ command "-" "Check environment" ``` -
+ ##### `owner` @@ -644,7 +644,7 @@ command "-" "Check environment" ``` -
+ ##### `exist` @@ -666,7 +666,7 @@ command "-" "Check environment" ``` -
+ ##### `readable` @@ -689,7 +689,7 @@ command "-" "Check environment" ``` -
+ ##### `writable` @@ -712,7 +712,7 @@ command "-" "Check environment" ``` -
+ ##### `executable` @@ -735,7 +735,7 @@ command "-" "Check environment" ``` -
+ ##### `dir` @@ -757,7 +757,7 @@ command "-" "Check environment" ``` -
+ ##### `empty` @@ -779,7 +779,7 @@ command "-" "Check environment" ``` -
+ ##### `empty-dir` @@ -801,7 +801,7 @@ command "-" "Check environment" ``` -
+ ##### `checksum` @@ -824,7 +824,7 @@ command "-" "Check environment" ``` -
+ ##### `checksum-read` @@ -847,7 +847,7 @@ command "-" "Check environment" ``` -
+ ##### `file-contains` @@ -870,7 +870,7 @@ command "-" "Check environment" ``` -
+ ##### `copy` @@ -893,7 +893,7 @@ command "-" "Check environment" ``` -
+ ##### `move` @@ -916,7 +916,7 @@ command "-" "Check environment" ``` -
+ ##### `touch` @@ -938,7 +938,7 @@ command "-" "Check environment" ``` -
+ ##### `mkdir` @@ -960,7 +960,7 @@ command "-" "Check environment" ``` -
+ ##### `remove` @@ -984,7 +984,7 @@ command "-" "Check environment" ``` -
+ ##### `chmod` @@ -1007,7 +1007,7 @@ command "-" "Check environment" ``` -
+ ##### `backup` @@ -1029,7 +1029,7 @@ command "-" "Configure environment" ``` -
+ ##### `backup-restore` @@ -1052,7 +1052,7 @@ command "-" "Configure environment" ``` -
+ #### System @@ -1076,7 +1076,7 @@ command "-" "Check environment" ``` -
+ ##### `wait-pid` @@ -1105,7 +1105,7 @@ command "-" "Check environment" ``` -
+ ##### `wait-fs` @@ -1134,7 +1134,7 @@ command "service myapp start" "Starting MyApp" ``` -
+ ##### `connect` @@ -1159,7 +1159,7 @@ command "-" "Check environment" ``` -
+ ##### `app` @@ -1181,7 +1181,7 @@ command "-" "Check environment" ``` -
+ ##### `signal` @@ -1218,7 +1218,7 @@ command "myapp --daemon" "Check my app" ``` -
+ ##### `env` @@ -1241,7 +1241,7 @@ command "-" "Check environment" ``` -
+ ##### `env-set` @@ -1264,7 +1264,7 @@ command "-" "Prepare environment" ``` -
+ #### Users/Groups @@ -1288,7 +1288,7 @@ command "-" "Check environment" ``` -
+ ##### `user-id` @@ -1311,7 +1311,7 @@ command "-" "Check environment" ``` -
+ ##### `user-gid` @@ -1334,7 +1334,7 @@ command "-" "Check environment" ``` -
+ ##### `user-group` @@ -1357,7 +1357,7 @@ command "-" "Check environment" ``` -
+ ##### `user-shell` @@ -1380,7 +1380,7 @@ command "-" "Check environment" ``` -
+ ##### `user-home` @@ -1403,7 +1403,7 @@ command "-" "Check environment" ``` -
+ ##### `group-exist` @@ -1425,7 +1425,7 @@ command "-" "Check environment" ``` -
+ ##### `group-id` @@ -1448,7 +1448,7 @@ command "-" "Check environment" ``` -
+ #### Services @@ -1472,7 +1472,7 @@ command "-" "Check environment" ``` -
+ ##### `service-enabled` @@ -1494,7 +1494,7 @@ command "-" "Check environment" ``` -
+ ##### `service-works` @@ -1516,7 +1516,7 @@ command "-" "Check environment" ``` -
+ #### HTTP @@ -1549,7 +1549,7 @@ command "-" "Make HTTP request" ``` -
+ ##### `http-header` @@ -1581,7 +1581,7 @@ command "-" "Make HTTP request" ``` -
+ ##### `http-contains` @@ -1606,7 +1606,7 @@ command "-" "Make HTTP request" ``` -
+ ##### `http-json` @@ -1631,7 +1631,7 @@ command "-" "Make HTTP request and check domain info" ``` -
+ ##### `http-set-auth` @@ -1660,7 +1660,7 @@ command "-" "Make HTTP request without auth" ``` -
+ ##### `http-set-header` @@ -1686,7 +1686,7 @@ command "-" "Make HTTP request" ``` -
+ #### Libraries @@ -1710,7 +1710,7 @@ command "-" "Check environment" ``` -
+ ##### `lib-header` @@ -1732,7 +1732,7 @@ command "-" "Check environment" ``` -
+ ##### `lib-config` @@ -1754,7 +1754,7 @@ command "-" "Check environment" ``` -
+ ##### `lib-exist` @@ -1777,7 +1777,7 @@ command "-" "Check environment" ``` -
+ ##### `lib-linked` @@ -1800,7 +1800,7 @@ command "-" "Check environment" ``` -
+ #### Python @@ -1824,7 +1824,7 @@ command "-" "Check Python module loading" ``` -
+ ##### `python3-module` @@ -1846,54 +1846,58 @@ command "-" "Check Python 3 module loading" ``` -
+ ## Examples ```yang -# Simple recipe for mkcryptpasswd utility +# Bibop recipe for MkCryptPasswd -command "mkcryptpasswd" "Generate basic hash for password" - expect "Please enter password" - print "MyPassword1234" +pkg mkcryptpasswd + +fast-finish yes + +var password MyPassword1234 +var salt SALT1234 +var salt_length 9 + +command "mkcryptpasswd -s" "Generate basic hash for password" + expect "Please enter password:" + print "{password}" expect "Hash: " exit 0 -command "mkcryptpasswd -sa SALT1234" "Generate hash for password with predefined salt" +command "mkcryptpasswd -s -sa {salt}" "Generate hash for password with predefined salt" expect "Please enter password" - print "MyPassword1234" - wait 1 - output-contains "$6$SALT1234$lTxNu4.6r/j81sirgJ.s9ai8AA3tJdp67XBWLFiE10tIharVYtzRJ9eJ9YEtQsiLzVtg94GrXAYjf40pWEEg7/" + print "{password}" + expect "$6${salt}$lTxNu4.6r/j81sirgJ.s9ai8AA3tJdp67XBWLFiE10tIharVYtzRJ9eJ9YEtQsiLzVtg94GrXAYjf40pWEEg7/" exit 0 -command "mkcryptpasswd -sa SALT1234 -1" "Generate MD5 hash for password with predefined salt" +command "mkcryptpasswd -s -sa {salt} -1" "Generate MD5 hash for password with predefined salt" expect "Please enter password" - print "MyPassword1234" - wait 1 - output-contains "$1$SALT1234$zIPLJYODoLlesdP3bf95S1" + print "{password}" + expect "$1${salt}$zIPLJYODoLlesdP3bf95S1" exit 0 -command "mkcryptpasswd -sa SALT1234 -5" "Generate SHA256 hash for password with predefined salt" +command "mkcryptpasswd -s -sa {salt} -5" "Generate SHA256 hash for password with predefined salt" expect "Please enter password" - print "MyPassword1234" - wait 1 - output-contains "$5$SALT1234$HOV.9Dkp4HSDzcfizNDG7x5ST4e74zcezvCJ8BWHuK8" + print "{password}" + expect "$5${salt}$HOV.9Dkp4HSDzcfizNDG7x5ST4e74zcezvCJ8BWHuK8" exit 0 -command "mkcryptpasswd -S" "Return error if password is too weak" +command "mkcryptpasswd -s -S" "Return error if password is too weak" expect "Please enter password" print "password" expect "Password is too weak: it is based on a dictionary word" print "password" - wait 0.5 + expect "Password is too weak: it is based on a dictionary word" print "password" - wait 0.5 - exit 1 + expect "Password is too weak: it is based on a dictionary word" + !exit 0 command "mkcryptpasswd --abcd" "Return error about unsupported argument" expect "Error! You used unsupported argument --abcd. Please check command syntax." - exit 1 - + !exit 0 ``` From 42decda92c6f54d8f3fd83b1ae2b93c4ae77cf43 Mon Sep 17 00:00:00 2001 From: Anton Novojilov Date: Sat, 26 Sep 2020 15:19:47 +0300 Subject: [PATCH 11/11] Add more docs --- cli/cli.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/cli.go b/cli/cli.go index 1dbc9a5b..ea278ca4 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -245,6 +245,7 @@ func listPackages(pkgs []string) { os.Exit(0) } +// getValidationConfig generates validation config func getValidationConfig(tags []string) *executor.ValidationConfig { vc := &executor.ValidationConfig{Tags: tags}