2

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

gbvikas
  • 47
  • 4
  • 9

2 Answers2

2

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")
}
pratish
  • 36
  • 5
  • Thanks. the specific requirement is to pass a sub-command execution status to PostRun. Ex: cli1 I need to pass the execution/exit status to PostRun. If the command got executed without any panics, PostRun will send a Success message else Failure – gbvikas Jun 10 '21 at 06:43
  • @gbvikas, Do have a look at the updated answer. – pratish Jun 11 '21 at 04:17
  • thanks @pratish. This worked. Is there a way to use this in the root command so that all subcommand status can be handled in one place? – gbvikas Jun 11 '21 at 20:36
  • you can use Commands() function that returns all the subcommand for a command as mentioned here - https://github.com/spf13/cobra/blob/4590150168e93f4b017c6e33469e26590ba839df/command.go#L1133. This way you will be able to check Annotations of subcommand as well, giving you the status. – pratish Jun 13 '21 at 13:19
0

I would wrap your Run (or RunE) function and your PostRun function using high order functions to catch errors or panics and then pass them into your PostRun:

package main

import (
    "fmt"

    "github.com/spf13/cobra"
)

type wrapper struct {
    err error
}

func (w *wrapper) RunE(f func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) error {
    return func(cmd *cobra.Command, args []string) (err error) {
        defer func() {
            if r := recover(); r != nil {
                err = fmt.Errorf("panic: %v", r)
                w.err = err
            }
        }()
        err = f(cmd, args)
        w.err = err
        return
    }
}

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 cmdFail = &cobra.Command{
        Use:   "fail",
        Short: "Doesn't work",
        RunE: cmdWrap.RunE(func(cmd *cobra.Command, args []string) error {
            panic("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)
        }),
    }

    var rootCmd = &cobra.Command{}
    rootCmd.AddCommand(cmdFail)
    rootCmd.Execute()
}
Bracken
  • 989
  • 8
  • 21