From b678b1d645ea3b5baa5ce63710bc2cf360115ea1 Mon Sep 17 00:00:00 2001 From: dnoberon Date: Thu, 17 Oct 2019 15:48:57 -0600 Subject: [PATCH] added configuration --- .gitignore | 2 + bifrost/bifrost.go | 5 +- cmd/init.go | 308 +++++++++++++++++++++++++++++++++++++++++++++ cmd/run.go | 74 +++++++++++ readme.md | 26 +++- 5 files changed, 412 insertions(+), 3 deletions(-) create mode 100644 cmd/init.go create mode 100644 cmd/run.go diff --git a/.gitignore b/.gitignore index 1521c8b..f53fe98 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ dist +heimdall +heimdall_config.json diff --git a/bifrost/bifrost.go b/bifrost/bifrost.go index b18e5e6..d7044d6 100644 --- a/bifrost/bifrost.go +++ b/bifrost/bifrost.go @@ -34,8 +34,9 @@ type ManagerConfig struct { AbsolutePath string ProgramArguments []string - Timeout time.Duration - Repeat int + Timeout time.Duration `json:"-"` + TimeoutString string + Repeat int InParallelCount int diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 0000000..2e42c93 --- /dev/null +++ b/cmd/init.go @@ -0,0 +1,308 @@ +/* +Copyright © 2019 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "errors" + "io/ioutil" + "log" + "os" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + "github.com/segmentio/objconv/json" + + "github.com/dnoberon/heimdall/bifrost" + + "github.com/manifoldco/promptui" + + "github.com/spf13/cobra" +) + +// initCmd represents the init command +var initCmd = &cobra.Command{ + Use: "init", + Args: cobra.NoArgs, + Short: "Create a configuration for heimdall to replace command flag arguments", + Long: `Init creates a "heimdall_config.json" in the current directory. This configuration +will be read in when using the "heimdall run" command and +should take the place of any command flags you would use +normally`, + Run: func(cmd *cobra.Command, args []string) { + config := bifrost.ManagerConfig{} + + promptProgramPath(&config) + promptProgramArguments(&config) + promptRepeat(&config) + promptTimeout(&config) + promptParallel(&config) + promptVerbose(&config) + promptLog(&config) + + configFile, err := json.Marshal(config) + if err != nil { + log.Fatal(err) + } + + err = ioutil.WriteFile("heimdall_config.json", configFile, os.ModePerm) + if err != nil { + log.Fatal(err) + } + }, +} + +func promptProgramPath(config *bifrost.ManagerConfig) { + prompt := promptui.Prompt{ + Label: "Executable path", + Validate: emptyValidate, + } + + path, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + absolutePath, err := filepath.Abs(path) + if err != nil { + log.Fatal(err) + } + + config.AbsolutePath = absolutePath +} + +func promptProgramArguments(config *bifrost.ManagerConfig) { + prompt := promptui.Prompt{ + Label: "Program arguments separated by comma", + } + + arguments, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + if arguments == "" { + return + } + + config.ProgramArguments = strings.Split(arguments, ",") +} + +func promptTimeout(config *bifrost.ManagerConfig) { + prompt := promptui.Prompt{ + Label: "How long should we wait before killing your program? - e.g 10s, 1m, 1h ", + Default: "5m", + Validate: timeValidate, + } + + timeout, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + _, err = time.ParseDuration(timeout) + if err != nil { + log.Fatal(err) + } + + config.TimeoutString = timeout +} + +func promptRepeat(config *bifrost.ManagerConfig) { + prompt := promptui.Prompt{ + Label: "How many times should we repeat execution?", + Default: "1", + Validate: intValidate, + } + + repeat, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + repeatAmount, err := strconv.Atoi(repeat) + if err != nil { + log.Fatal(err) + } + + config.Repeat = repeatAmount +} + +func promptParallel(config *bifrost.ManagerConfig) { + prompt := promptui.Prompt{ + Label: "How many instances of your program should we run at the same time?", + Default: "1", + Validate: intValidate, + } + + instances, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + parallelCount, err := strconv.Atoi(instances) + if err != nil { + log.Fatal(err) + } + + config.InParallelCount = parallelCount +} + +func promptVerbose(config *bifrost.ManagerConfig) { + prompt := promptui.Prompt{ + Label: "Print all output to console? [Y/n] ", + Validate: confirmValidate, + Default: "y", + } + + confirm, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + config.Verbose = isYes(confirm) +} + +func promptLog(config *bifrost.ManagerConfig) { + prompt := promptui.Prompt{ + Label: "Log the output generated by your program? [Y/n] ", + Default: "y", + Validate: confirmValidate, + } + + confirm, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + if !isYes(confirm) { + return + } + + prompt = promptui.Prompt{ + Label: "What should we call the log file?", + Default: "heimdall.log", + } + + logName, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + config.LogName = logName + + prompt = promptui.Prompt{ + Label: "Overwrite existing logs? [y/N] ", + Validate: confirmValidate, + Default: "n", + } + + confirm, err = prompt.Run() + if err != nil { + log.Fatal(err) + } + + config.LogOverwrite = isYes(confirm) + + prompt = promptui.Prompt{ + Label: "Filter incoming logs? [y/N] ", + Validate: confirmValidate, + Default: "n", + } + + confirm, err = prompt.Run() + if err != nil { + log.Fatal(err) + } + + if isYes(confirm) { + prompt = promptui.Prompt{ + Label: "Regex expression for filtering logs", + Default: "", + Validate: regexValidate, + } + + expression, err := prompt.Run() + if err != nil { + log.Fatal(err) + } + + compiled, err := regexp.Compile(expression) + if err != nil { + log.Fatal(err) + } + + config.LogFilter = compiled + } + +} + +func emptyValidate(input string) error { + if input == "" { + return errors.New("Invalid text") + } + + return nil +} + +func confirmValidate(input string) error { + if input == "y" || input == "Y" || input == "N" || input == "n" || input == "yes" || input == "no" { + return nil + } + + return errors.New("Bad confirmation (yes, y, no, n)") +} + +func isYes(input string) bool { + return input == "y" || input == "yes" || input == "Y" +} + +func intValidate(input string) error { + _, err := strconv.Atoi(input) + + if err != nil { + return errors.New("Invalid number") + } + + return nil +} + +func regexValidate(input string) error { + _, err := regexp.Compile(input) + return err +} + +func timeValidate(input string) error { + _, err := time.ParseDuration(input) + + return err +} + +func init() { + rootCmd.AddCommand(initCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // initCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // initCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/run.go b/cmd/run.go new file mode 100644 index 0000000..e7525ed --- /dev/null +++ b/cmd/run.go @@ -0,0 +1,74 @@ +/* +Copyright © 2019 NAME HERE + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package cmd + +import ( + "encoding/json" + "io/ioutil" + "log" + "time" + + "github.com/dnoberon/heimdall/bifrost" + "github.com/spf13/cobra" +) + +// runCmd represents the run command +var runCmd = &cobra.Command{ + Use: "run", + Args: cobra.NoArgs, + Short: `Run heimdall using the "heimdall_config.json" file in the current directory `, + Long: `You must first generate a "heimdall_config.json" file before running +this command. If a file does not already exist please run +"heimdall init"`, + Run: func(cmd *cobra.Command, args []string) { + config := bifrost.ManagerConfig{} + + file, err := ioutil.ReadFile("heimdall_config.json") + if err != nil { + log.Fatal(err) + return + } + + err = json.Unmarshal(file, &config) + if err != nil { + log.Fatal(err) + return + } + + timeout, err := time.ParseDuration(config.TimeoutString) + if err != nil { + log.Fatal(err) + return + } + + config.Timeout = timeout + bifrost.Execute(config) + }, +} + +func init() { + rootCmd.AddCommand(runCmd) + + // Here you will define your flags and configuration settings. + + // Cobra supports Persistent Flags which will work for this command + // and all subcommands, e.g.: + // runCmd.PersistentFlags().String("foo", "", "A help for foo") + + // Cobra supports local flags which will only run when this command + // is called directly, e.g.: + // runCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/readme.md b/readme.md index 75d2e4b..101734b 100644 --- a/readme.md +++ b/readme.md @@ -34,6 +34,11 @@ development Usage: heimdall [flags] + +Available Commands: + help Help about any command + init Create a configuration for heimdall to replace command flag arguments + run Run heimdall using the "heimdall_config.json" file in the current directory Flags: -h, --help help for heimdall @@ -48,7 +53,7 @@ Flags: ``` -Let’s run through a quick example based on the problem that started this whole thing - a console application managing a third-party, hidden application. +Let’s run through a quick example based on the problem that started sthis whole thing - a console application managing a third-party, hidden application. I want heimdall to filter the logs that both my application and the hidden one outputs as well (here we filter for < and > characters as long as there is at least 1 preceding character) as killing my application if it hangs. @@ -56,6 +61,25 @@ Telling heimdall to do that is easy - `heimdall --timeout=30m --log --logFilter=<[^<>]+> exportApplication` +
+ +## Running `heimdall` with a configuration file + +This tool provides the option of generating a json configuration file for ease of use. All command line flag arguments are available and represented inside the configuration file. + +First, generate your configuration file using the `heimdall init` command and following the interactive prompts. + +```console +> heimdall init +Executable path: tester +✔ Program arguments separated by comma: █ + +``` + +Once your configuration file is generated you can run heimdall with a single command - + +`heimdall run` + ## Can't you do this with a bash or powershell script? You sure could - but you'd have to spend the time to build it, have separate scripts for at least windows and linux systems, and if you're using someone elses script, modify it to the point that it's going to work for your particular situation. You'll also have to handle multi-threading and logging yourself.