...

Source file src/edge-infra.dev/pkg/lib/cli/sink/example_test.go

Documentation: edge-infra.dev/pkg/lib/cli/sink

     1  package sink_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"os"
     8  	"os/signal"
     9  
    10  	"edge-infra.dev/pkg/lib/cli/rags"
    11  	"edge-infra.dev/pkg/lib/cli/sink"
    12  )
    13  
    14  type client struct {
    15  	user     string
    16  	password string
    17  	endpoint string
    18  }
    19  
    20  func (c client) ping() bool {
    21  	return true
    22  }
    23  
    24  // cliExt is a CLI extension that handles binding flags for client and making
    25  // it available to commands. it also decorates the logger with the endpoint and
    26  // the command name
    27  type cliExt struct {
    28  	client client
    29  }
    30  
    31  // RegisterFlags is called during command setup
    32  func (c *cliExt) RegisterFlags(rs *rags.RagSet) {
    33  	rs.StringVar(&c.client.user, "user", "tom", "service user")
    34  	rs.StringVar(&c.client.password, "password", "hunter22", "trust me")
    35  	rs.StringVar(&c.client.endpoint, "endpoint", "https://ncr.com", "the point where you end")
    36  }
    37  
    38  // BeforeRun is called after the bound flags have been parsed, allowing the
    39  // extension to act on the bound values and mutate the command context
    40  func (c *cliExt) BeforeRun(ctx context.Context, r sink.Run) (context.Context, sink.Run, error) {
    41  	r.Log = r.Log.
    42  		WithName(r.Cmd().Name()).
    43  		WithValues("endpoint", c.client.endpoint)
    44  
    45  	// We would normally instantiate a client here and confirm it works
    46  	if !c.client.ping() {
    47  		return ctx, r, fmt.Errorf("failed to instantiate client")
    48  	}
    49  	r.Log.WithName("before").Info("instantiated client")
    50  
    51  	return ctx, r, nil
    52  }
    53  
    54  func NewCmd() *sink.Command {
    55  	var (
    56  		force bool
    57  		e     = &cliExt{}
    58  	)
    59  
    60  	cmd := &sink.Command{
    61  		Use:        "client-user [flags] <operation>",
    62  		Extensions: []sink.Extension{e},
    63  		Flags: []*rags.Rag{
    64  			{
    65  				Name:  "force",
    66  				Usage: "Force operation in case of conflict",
    67  				Value: &rags.Bool{Var: &force},
    68  			},
    69  		},
    70  		Exec: func(_ context.Context, r sink.Run) error {
    71  			// We can now use the instantiated cliExt and the flags we bound
    72  			// explicitly
    73  			if !e.client.ping() && !force {
    74  				return fmt.Errorf("operation failed, try again with --force")
    75  			}
    76  
    77  			r.Log.Info("successfully hit service")
    78  			return nil
    79  		},
    80  	}
    81  
    82  	// Command constructors can manipulate the command's output before the
    83  	// execution phase:
    84  	cmd.SetOut(os.Stdout)
    85  	cmd.SetErr(os.Stdout)
    86  
    87  	return cmd
    88  }
    89  
    90  func ExampleCommand_ParseAndRun() {
    91  	// Use ParseAndRun to parse and execute in one call
    92  	cmd := NewCmd()
    93  	// Wire up control signals so that our CLI can be cancelled via SIGTERM, etc
    94  	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
    95  	defer cancel()
    96  	if err := cmd.ParseAndRun(ctx, []string{}); err != nil {
    97  		cancel()
    98  		os.Exit(1)
    99  	}
   100  	// Output:
   101  	// client-user:before: instantiated client endpoint=https://ncr.com
   102  	// client-user: successfully hit service endpoint=https://ncr.com
   103  }
   104  
   105  func ExampleCommand_Parse() {
   106  	// To access the parsed command state without executing, run Parse and Run
   107  	// separately
   108  	cmd := NewCmd()
   109  	if err := cmd.Parse([]string{}); err != nil {
   110  		fmt.Fprintln(os.Stderr, err.Error())
   111  	}
   112  	// Wire up control signals so that our CLI can be cancelled via SIGTERM, etc
   113  	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
   114  	defer cancel()
   115  	if err := cmd.Run(ctx); err != nil {
   116  		cancel()
   117  		os.Exit(1)
   118  	}
   119  	// Output:
   120  	// client-user:before: instantiated client endpoint=https://ncr.com
   121  	// client-user: successfully hit service endpoint=https://ncr.com
   122  }
   123  
   124  func ExampleCommand_output() {
   125  	cmd := &sink.Command{
   126  		Use:   "fetcher",
   127  		Short: "Fetch data from a remote service",
   128  		Exec: func(_ context.Context, r sink.Run) error {
   129  			log := r.Log.WithName("fetcher")
   130  			// By default the CLI logger will write to the error output stream, to avoid
   131  			// polluting output
   132  			log.Info("fetching data")
   133  			data := fetchData()
   134  			// Get the configured standard output stream for this run and write the
   135  			// data to it
   136  			fmt.Fprintln(r.Out(), data)
   137  			log.Info("data fetched")
   138  			return nil
   139  		},
   140  	}
   141  
   142  	// Set Err and Out explicitly so that we can access the outputs in the example
   143  	errWriter := new(bytes.Buffer)
   144  	cmd.SetErr(errWriter)
   145  	outWriter := new(bytes.Buffer)
   146  	cmd.SetOut(outWriter)
   147  
   148  	if err := cmd.ParseAndRun(context.Background(), []string{}); err != nil {
   149  		os.Exit(1)
   150  	}
   151  
   152  	fmt.Println("standard out:", outWriter.String())
   153  	fmt.Println("standard error:", errWriter.String())
   154  	// Output:
   155  	// standard out: this data is going to come over stdout for consumption by other programs
   156  	//
   157  	// standard error: fetcher: fetching data
   158  	// fetcher: data fetched
   159  }
   160  
   161  func fetchData() string {
   162  	return "this data is going to come over stdout for consumption by other programs"
   163  }
   164  

View as plain text