package sink_test import ( "bytes" "context" "fmt" "os" "os/signal" "edge-infra.dev/pkg/lib/cli/rags" "edge-infra.dev/pkg/lib/cli/sink" ) type client struct { user string password string endpoint string } func (c client) ping() bool { return true } // cliExt is a CLI extension that handles binding flags for client and making // it available to commands. it also decorates the logger with the endpoint and // the command name type cliExt struct { client client } // RegisterFlags is called during command setup func (c *cliExt) RegisterFlags(rs *rags.RagSet) { rs.StringVar(&c.client.user, "user", "tom", "service user") rs.StringVar(&c.client.password, "password", "hunter22", "trust me") rs.StringVar(&c.client.endpoint, "endpoint", "https://ncr.com", "the point where you end") } // BeforeRun is called after the bound flags have been parsed, allowing the // extension to act on the bound values and mutate the command context func (c *cliExt) BeforeRun(ctx context.Context, r sink.Run) (context.Context, sink.Run, error) { r.Log = r.Log. WithName(r.Cmd().Name()). WithValues("endpoint", c.client.endpoint) // We would normally instantiate a client here and confirm it works if !c.client.ping() { return ctx, r, fmt.Errorf("failed to instantiate client") } r.Log.WithName("before").Info("instantiated client") return ctx, r, nil } func NewCmd() *sink.Command { var ( force bool e = &cliExt{} ) cmd := &sink.Command{ Use: "client-user [flags] ", Extensions: []sink.Extension{e}, Flags: []*rags.Rag{ { Name: "force", Usage: "Force operation in case of conflict", Value: &rags.Bool{Var: &force}, }, }, Exec: func(_ context.Context, r sink.Run) error { // We can now use the instantiated cliExt and the flags we bound // explicitly if !e.client.ping() && !force { return fmt.Errorf("operation failed, try again with --force") } r.Log.Info("successfully hit service") return nil }, } // Command constructors can manipulate the command's output before the // execution phase: cmd.SetOut(os.Stdout) cmd.SetErr(os.Stdout) return cmd } func ExampleCommand_ParseAndRun() { // Use ParseAndRun to parse and execute in one call cmd := NewCmd() // Wire up control signals so that our CLI can be cancelled via SIGTERM, etc ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() if err := cmd.ParseAndRun(ctx, []string{}); err != nil { cancel() os.Exit(1) } // Output: // client-user:before: instantiated client endpoint=https://ncr.com // client-user: successfully hit service endpoint=https://ncr.com } func ExampleCommand_Parse() { // To access the parsed command state without executing, run Parse and Run // separately cmd := NewCmd() if err := cmd.Parse([]string{}); err != nil { fmt.Fprintln(os.Stderr, err.Error()) } // Wire up control signals so that our CLI can be cancelled via SIGTERM, etc ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel() if err := cmd.Run(ctx); err != nil { cancel() os.Exit(1) } // Output: // client-user:before: instantiated client endpoint=https://ncr.com // client-user: successfully hit service endpoint=https://ncr.com } func ExampleCommand_output() { cmd := &sink.Command{ Use: "fetcher", Short: "Fetch data from a remote service", Exec: func(_ context.Context, r sink.Run) error { log := r.Log.WithName("fetcher") // By default the CLI logger will write to the error output stream, to avoid // polluting output log.Info("fetching data") data := fetchData() // Get the configured standard output stream for this run and write the // data to it fmt.Fprintln(r.Out(), data) log.Info("data fetched") return nil }, } // Set Err and Out explicitly so that we can access the outputs in the example errWriter := new(bytes.Buffer) cmd.SetErr(errWriter) outWriter := new(bytes.Buffer) cmd.SetOut(outWriter) if err := cmd.ParseAndRun(context.Background(), []string{}); err != nil { os.Exit(1) } fmt.Println("standard out:", outWriter.String()) fmt.Println("standard error:", errWriter.String()) // Output: // standard out: this data is going to come over stdout for consumption by other programs // // standard error: fetcher: fetching data // fetcher: data fetched } func fetchData() string { return "this data is going to come over stdout for consumption by other programs" }