4

Im providing command line tool with several command and sub commands, Im using cobra command line and I’ve two separate commands that first is prerequisite to other

e.g. the first command is preferring the environment by creating temp folder and validate some file

The second command should get some properties from the first command

and user should execute it like

btr prepare
btr run

when the run command is executed it should get some data from the prepare command outcome

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
    Use:   "btr",
    Short: "piping process",
}


    var prepare = &cobra.Command{
        Use:   "pre",
        Short: "Prepare The environment" ,
        Run: func(cmd *cobra.Command, args []string) {
        
        //This creating the temp folder and validate some configuration file    
           tmpFolderPath,parsedFile := exe.PreProcess()
        },
    }
    
    
    var initProcess = &cobra.Command{
        Use:   “run”,
        Short: “run process”,
        Run: func(cmd *cobra.Command, args []string) {
            
      //Here I need those two properties 
    
             run(tmpFolderPath, ParsedFile)
        },
    }

func init() {

    rootCmd.AddCommand(prepare,initProcess)
    
}

UPDATE

Well, the answer below doest really help. I need to share state between two command in local & cloud env) , how I can do it that if I run the command line commands from shell script that call to 1 command and then call to the second which need to get some state from the first command, I need E2E solution with code real example in my context

update 2

let say that I understand that I need config file (json) ,

Where should I create it (path)?

When to clean it ?

in case I use 1file How should I validate to store data which is relevant for specific process and take other process data when needed (guid ) ?

lets say I've config like following

type config struct{
    
    path string,
    wd string,
    id string,//guid?
    
}
Community
  • 1
  • 1
  • How are the two cobra commands run? Surely you can only run one at a time from the CLI? Or does one call the other? – Zak Apr 23 '18 at 08:49
  • @Zak - I run one at a time... –  Apr 23 '18 at 08:50
  • 1
    Are they always run one after another? Maybe you could follow the unix philosophy https://en.wikipedia.org/wiki/Unix_philosophy#Origin. Specifically the points in origin. "Expect the output of every program to become the input to another". You could output the things you need from command 1, to stdout and then use that as the input to command 2 using "|" pipe. I don't think polluting the env is a good idea, the vars should only exist for the lifetime of the program – Zak Apr 23 '18 at 08:53
  • @Zak - I think that `viper` could be better, what do you think? –  Apr 23 '18 at 08:54
  • I think this problem is bigger than the CLI library that you choose. There's no good way of storing state between running of CLI tools / commands. Unless you are persisting to some kind of file or chaining them with pipe. The ENV can be considered a type of file. – Zak Apr 23 '18 at 08:56
  • @Zak - ok, can you please provide your suggestion as answer please ? –  Apr 23 '18 at 08:58
  • What do you mean with "E2E solution"? – Ivan Beldad Apr 25 '18 at 21:52
  • @IvandelaBeldad - "productive solution" ... –  Apr 26 '18 at 14:22
  • be wary that cloud systems might have ephimeral file systems that are subject to data loss. eg: heroku – Nidhin David Apr 28 '18 at 08:45
  • 2
    You've gotten perfectly reasonable answers but you seem to want something that isn't really reasonable. If you want stateful operations you need to either 1) Run a daemon process and interact with it via your CLI tool or 2) Generate and store a configuration data somewhere on the system. Both have the same problem, however, that whatever state first command created, could be undone before running the next. *You still have to deal with the state potentially being bogus or missing when you run the second command*. It seems like you're fixated on a solution rather than solving the actual problem. – Thomas Apr 29 '18 at 21:25
  • after reading the (good) answers already there and seeing you dont like it, why not just implement a third command, which combines the first two in just one invocation? – mikezter May 02 '18 at 12:06

2 Answers2

2

If you are trying to persist state across execution of different commands of a command line tool, the you have 2 choices.

  1. Write the state to some kind of file (consider the env a file in this case).
  2. Output values from the first command in a way that can be used as the input to another command.

On 1.

I think it's good practice for any arguments etc. to only survive for the lifetime of the running of the CLI tool. The equivalent of the smallest scoped variables possible in programming. This works well when you need to persist the state indefinitely, but if your state is no longer used after the initProcess command has finished then this is probably not the right option.

On 2.

There is some precedence for this. The unix philosophy (wikipedia) suggests to:

Expect the output of every program to become the input to another

So you could pick some output (to stdout) format for the first prepare command that could be used as the input for the second initProcess command. And then use | pipe to run the output of one into the other.

Example:

func Run(cmd *cobra.Command, args []string) {
    //This creating the temp folder and validate some configuration file    
    tmpFolderPath,parsedFile := exe.PreProcess()
    fmt.Println(tmpFolderPath)
    fmt.Println(parsedFile)
}

func InitProcess(cmd *cobra.Command, args []string) {

    tmpFolder, parsedFile := args[0], args[1]

}

Then to run the program, and pipe the commands together:

./program pre | xargs ./program run
Zak
  • 5,515
  • 21
  • 33
  • One project that I know that does this well is `terraform` https://www.terraform.io/docs/state/ it basically persists everything that it needs in a file `terraform.tfstate` (using json syntax) – Zak Apr 23 '18 at 13:21
  • I dont want in json, I want in env ...why json is better? –  Apr 23 '18 at 13:24
  • I am not saying you should use json. Only that that is one of many formats. You can write to the environment if you wish. – Zak Apr 23 '18 at 13:27
  • You didnt provide any example code in my context...it will be great if you can add it and i'll close the question, thanks –  Apr 23 '18 at 18:37
  • Sorry but your answer doesnt answer my real requirements –  Apr 25 '18 at 16:15
  • HI,Thanks, Sorry but I need some E2E "productive" solution, the second solution IMO is not suitable for it, can you provide E2E solution for the first option (productive one ...) –  Apr 25 '18 at 21:14
  • btw, The reason that I put high bounty is that I need E2E productive solution for it , go playground example will help to understand your points better. –  Apr 25 '18 at 21:17
  • Your problem is that even if you opt to use environment variables, all commands that are run in shell get a child env (basically a copy of the environment) https://stackoverflow.com/questions/24938877/how-to-set-environment-variables-that-last-in-go This means that even if you opted for the env variables version you would have to chain the commands together anyway. If you cannot do that, then you should be writing the values to a file and reading them in the second command – Zak Apr 26 '18 at 07:32
  • Use a default location on the system to store a file that has the information you want to store, and let that location be configurable in the commands' settings. – Patrick Stephen Apr 26 '18 at 15:06
  • @PatrickStephen - there is example code which I can use as a reference ? –  Apr 26 '18 at 18:29
  • let say that I understand that I need config file (json) , how I should do it? where should I create it (path)? –  Apr 27 '18 at 07:23
2

Share information between commands

Like was said in comments, if you need to share data across commands, you'll need to persist it. The structure you use is no relevant, but for simplicity and because of JSON is the current most extended language for data exchange we'll use it.


Where should I create it (path)?

My recomendation is to use the home of the user. Many applications save its configuration here. This will allow a solution multi-environment easily. Say that your configuration file will be named myApp.

func configPath() string {
    cfgFile := ".myApp"
    usr, _ := user.Current()
    return path.Join(usr.HomeDir, cfgFile)
}


When to clean it ?

That obviously will depend on your requirements. But, if you always need to run pre and run in that order, I bet that you can clean it inmediately after run execution, when it won't be longer needed.


How to store it ?

That's easy. If what you need to save is your config struct you can do this:

func saveConfig(c config) {
    jsonC, _ := json.Marshal(c)
    ioutil.WriteFile(configPath(), jsonC, os.ModeAppend)
}


And to read it ?

func readConfig() config {
    data, _ := ioutil.ReadFile(configPath())
    var cfg config
    json.Unmarshal(data, &cfg)
    return cfg
}


Flow

// pre command
// persist to file the data you need
saveConfig(config{ 
    id:   "id",
    path: "path",
    wd:   "wd",
}) 

// run command
// retrieve the previously stored information
cfg := readConfig()

// from now you can use the data generated by `pre`

DISCLAIMER: I've removed all error handling for short.

Ivan Beldad
  • 2,285
  • 2
  • 21
  • 33
  • Instead of file I would recommend a db – Nidhin David Apr 28 '18 at 08:46
  • @NidhinDavid To use a database to share some basic information between commands? Not at all. – Ivan Beldad Apr 28 '18 at 11:11
  • This is the world of cloud computing and horizontal scaling! – Nidhin David Apr 28 '18 at 13:48
  • @NidhinDavid So you're recommending stop using file system entirely. This is store two strings, nothing more, set up a database for this is ridiculous. – Ivan Beldad Apr 28 '18 at 14:26
  • https://12factor.net/ - 12 factors for a standard app. Your recommendation breaks vi and viii...If you don't want to set up db then use a something like etcd – Nidhin David Apr 28 '18 at 15:57
  • @NidhinDavid Each problem must be solved using one methodology. I'm answering a concrete situation. In this case he is developing a cli. Extracted from your page: `"Who should read this document? Any developer building applications which run as a service."` Is that this situation? Absolutely not. – Ivan Beldad Apr 28 '18 at 16:46
  • @NidhinDavid Anyway, you're completely free to post your own answer if you're more right – Ivan Beldad Apr 28 '18 at 16:48
  • @IvandelaBeldad - This make sense +1, Can you please provide playground , I want to test it ... –  Apr 28 '18 at 17:24
  • @RaynD I suppose that is not possible to save files in it. But you can test it with your actual problem, the steps are well defined. If you have any problem let me know. – Ivan Beldad Apr 29 '18 at 03:13
  • https://stackoverflow.com/questions/2693081/does-google-app-engine-allow-creation-of-files-and-folders-on-the-server If you intend to run in local system (like `grep` `cat` etc..) then file is ok. But if you want to run it in cloud (as I said earlier) or if you want to horizontally scale across systems then file systems will be a (alteast) risk. – Nidhin David Apr 29 '18 at 14:43
  • @IvandelaBeldad - can you add the error handling and the "productive flow" like in playground please? –  May 02 '18 at 06:54
  • @RaynD [https://play.golang.org/p/phU4DG543cD](https://play.golang.org/p/phU4DG543cD) – Ivan Beldad May 02 '18 at 07:36
  • Thanks , i've closed the question :) –  May 02 '18 at 14:29