Search code examples
gocommand-line-interfacego-cobra

how do I pass a command status to Postrun in cobra cli


Cobra CLI has support for PostRun to invoke after execution of a command.

https://github.com/spf13/cobra#prerun-and-postrun-hooks

How do I pass on the command status to PostRun call? I have requirement of posting the command status after its execution to a server


Solution

  • Cleaner approach would be to leverage the Annotations provided in cobra command

    package main
    
    import (
        "fmt"
    
        "github.com/spf13/cobra"
    )
    
    
    func main() {
        var rootCmd = &cobra.Command{
            Use:   "root [sub]",
            Short: "My root command",
            Run: func(cmd *cobra.Command, args []string) {
                // Do your processing here
                // Set the command annotations
                cmd.Annotations = make(map[string]string)
                cmd.Annotations["status"] = "status_goes_here"
                cmd.Annotations["error"] = "error_goes_here"
            },
            PostRun: func(cmd *cobra.Command, args []string) {
                // Retrieve the annotations
                fmt.Println(cmd.Annotations["status"])
                fmt.Println(cmd.Annotations["error"])
            },
        }
    
        rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
        rootCmd.Execute()
    }
    

    Really liked the approach @Bracken has taken here, Although there are some tweaks that will make it work

    package main
    
    import (
        "fmt"
        "errors"
    
        "github.com/spf13/cobra"
    )
    
    type wrapper struct {
        err error
    }
    
    // RunE fails to proceed further in case of error resulting in not executing PostRun actions
    func (w *wrapper) Run(f func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) {
        return func(cmd *cobra.Command, args []string) {
            err := f(cmd, args)
            w.err = err
        }
    }
    
    func (w *wrapper) PostRun(f func(cmd *cobra.Command, args []string, cmdErr error)) func(cmd *cobra.Command, args []string) {
        return func(cmd *cobra.Command, args []string) {
            f(cmd, args, w.err)
        }
    }
    
    func main() {
        cmdWrap := wrapper{}
        var rootCmd = &cobra.Command{
            Use:   "root [sub]",
            Short: "My root command",
            Run: cmdWrap.Run(func(cmd *cobra.Command, args []string) error {
                return errors.New("i'm not in the book, you know")
            }),
            PostRun: cmdWrap.PostRun(func(cmd *cobra.Command, args []string, cmdErr error) {
                fmt.Printf("error was %v\n", cmdErr)
            }),
        }
    
        rootCmd.SetArgs([]string{"sub", "arg1", "arg2"})
        rootCmd.Execute()
    }
    
    

    ----- Old Answer -----

    If I understand it right, There is some status that needs to be passed from cmd.Execute to PostRun.

    You can use ExecuteContext(ctx context.Context) method instead of Execute() and set whatever key-value needs to be set in context.

    ctx := context.WithValue(context.Background(), "status", "statusValue")
    rootCmd.ExecuteContext(ctx)
    

    Same values can be Retrieved inside PostRun using cmd.Context()

    PostRun: func(cmd *cobra.Command, args []string) {
       ctx := cmd.Context()
       status := ctx.Value("status")
    }