github.com/kjdelisle/consul@v1.4.5/command/watch/watch.go (about)

     1  package watch
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"flag"
     7  	"fmt"
     8  	"os"
     9  	osexec "os/exec"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/consul/agent"
    14  	"github.com/hashicorp/consul/agent/exec"
    15  	"github.com/hashicorp/consul/command/flags"
    16  	consulwatch "github.com/hashicorp/consul/watch"
    17  	"github.com/mitchellh/cli"
    18  )
    19  
    20  func New(ui cli.Ui, shutdownCh <-chan struct{}) *cmd {
    21  	c := &cmd{UI: ui, shutdownCh: shutdownCh}
    22  	c.init()
    23  	return c
    24  }
    25  
    26  type cmd struct {
    27  	UI    cli.Ui
    28  	flags *flag.FlagSet
    29  	http  *flags.HTTPFlags
    30  	help  string
    31  
    32  	shutdownCh <-chan struct{}
    33  
    34  	// flags
    35  	watchType   string
    36  	key         string
    37  	prefix      string
    38  	service     string
    39  	tag         string
    40  	passingOnly string
    41  	state       string
    42  	name        string
    43  	shell       bool
    44  }
    45  
    46  func (c *cmd) init() {
    47  	c.flags = flag.NewFlagSet("", flag.ContinueOnError)
    48  	c.flags.StringVar(&c.watchType, "type", "",
    49  		"Specifies the watch type. One of key, keyprefix, services, nodes, "+
    50  			"service, checks, or event.")
    51  	c.flags.StringVar(&c.key, "key", "",
    52  		"Specifies the key to watch. Only for 'key' type.")
    53  	c.flags.StringVar(&c.prefix, "prefix", "",
    54  		"Specifies the key prefix to watch. Only for 'keyprefix' type.")
    55  	c.flags.StringVar(&c.service, "service", "",
    56  		"Specifies the service to watch. Required for 'service' type, "+
    57  			"optional for 'checks' type.")
    58  	c.flags.StringVar(&c.tag, "tag", "",
    59  		"Specifies the service tag to filter on. Optional for 'service' type.")
    60  	c.flags.StringVar(&c.passingOnly, "passingonly", "",
    61  		"Specifies if only hosts passing all checks are displayed. "+
    62  			"Optional for 'service' type, must be one of `[true|false]`. Defaults false.")
    63  	c.flags.BoolVar(&c.shell, "shell", true,
    64  		"Use a shell to run the command (can set a custom shell via the SHELL "+
    65  			"environment variable).")
    66  	c.flags.StringVar(&c.state, "state", "",
    67  		"Specifies the states to watch. Optional for 'checks' type.")
    68  	c.flags.StringVar(&c.name, "name", "",
    69  		"Specifies an event name to watch. Only for 'event' type.")
    70  
    71  	c.http = &flags.HTTPFlags{}
    72  	flags.Merge(c.flags, c.http.ClientFlags())
    73  	flags.Merge(c.flags, c.http.ServerFlags())
    74  	c.help = flags.Usage(help, c.flags)
    75  }
    76  
    77  func (c *cmd) Run(args []string) int {
    78  	if err := c.flags.Parse(args); err != nil {
    79  		return 1
    80  	}
    81  
    82  	// Check for a type
    83  	if c.watchType == "" {
    84  		c.UI.Error("Watch type must be specified")
    85  		c.UI.Error("")
    86  		c.UI.Error(c.Help())
    87  		return 1
    88  	}
    89  
    90  	// Compile the watch parameters
    91  	params := make(map[string]interface{})
    92  	if c.watchType != "" {
    93  		params["type"] = c.watchType
    94  	}
    95  	if c.http.Datacenter() != "" {
    96  		params["datacenter"] = c.http.Datacenter()
    97  	}
    98  	if c.http.Token() != "" {
    99  		params["token"] = c.http.Token()
   100  	}
   101  	if c.key != "" {
   102  		params["key"] = c.key
   103  	}
   104  	if c.prefix != "" {
   105  		params["prefix"] = c.prefix
   106  	}
   107  	if c.service != "" {
   108  		params["service"] = c.service
   109  	}
   110  	if c.tag != "" {
   111  		params["tag"] = c.tag
   112  	}
   113  	if c.http.Stale() {
   114  		params["stale"] = c.http.Stale()
   115  	}
   116  	if c.state != "" {
   117  		params["state"] = c.state
   118  	}
   119  	if c.name != "" {
   120  		params["name"] = c.name
   121  	}
   122  	if c.passingOnly != "" {
   123  		b, err := strconv.ParseBool(c.passingOnly)
   124  		if err != nil {
   125  			c.UI.Error(fmt.Sprintf("Failed to parse passingonly flag: %s", err))
   126  			return 1
   127  		}
   128  		params["passingonly"] = b
   129  	}
   130  
   131  	// Create the watch
   132  	wp, err := consulwatch.Parse(params)
   133  	if err != nil {
   134  		c.UI.Error(fmt.Sprintf("%s", err))
   135  		return 1
   136  	}
   137  
   138  	if strings.HasPrefix(wp.Type, "connect_") || strings.HasPrefix(wp.Type, "agent_") {
   139  		c.UI.Error(fmt.Sprintf("Type %s is not supported in the CLI tool", wp.Type))
   140  		return 1
   141  	}
   142  
   143  	// Create and test the HTTP client
   144  	client, err := c.http.APIClient()
   145  	if err != nil {
   146  		c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
   147  		return 1
   148  	}
   149  	_, err = client.Agent().NodeName()
   150  	if err != nil {
   151  		c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
   152  		return 1
   153  	}
   154  
   155  	// Setup handler
   156  
   157  	// errExit:
   158  	//	0: false
   159  	//	1: true
   160  	errExit := 0
   161  	if len(c.flags.Args()) == 0 {
   162  		wp.Handler = func(idx uint64, data interface{}) {
   163  			defer wp.Stop()
   164  			buf, err := json.MarshalIndent(data, "", "    ")
   165  			if err != nil {
   166  				c.UI.Error(fmt.Sprintf("Error encoding output: %s", err))
   167  				errExit = 1
   168  			}
   169  			c.UI.Output(string(buf))
   170  		}
   171  	} else {
   172  		wp.Handler = func(idx uint64, data interface{}) {
   173  			doneCh := make(chan struct{})
   174  			defer close(doneCh)
   175  			logFn := func(err error) {
   176  				c.UI.Error(fmt.Sprintf("Warning, could not forward signal: %s", err))
   177  			}
   178  
   179  			// Create the command
   180  			var buf bytes.Buffer
   181  			var err error
   182  			var cmd *osexec.Cmd
   183  			if !c.shell {
   184  				cmd, err = exec.Subprocess(c.flags.Args())
   185  			} else {
   186  				cmd, err = exec.Script(strings.Join(c.flags.Args(), " "))
   187  			}
   188  			if err != nil {
   189  				c.UI.Error(fmt.Sprintf("Error executing handler: %s", err))
   190  				goto ERR
   191  			}
   192  			cmd.Env = append(os.Environ(),
   193  				"CONSUL_INDEX="+strconv.FormatUint(idx, 10),
   194  			)
   195  
   196  			// Encode the input
   197  			if err = json.NewEncoder(&buf).Encode(data); err != nil {
   198  				c.UI.Error(fmt.Sprintf("Error encoding output: %s", err))
   199  				goto ERR
   200  			}
   201  			cmd.Stdin = &buf
   202  			cmd.Stdout = os.Stdout
   203  			cmd.Stderr = os.Stderr
   204  
   205  			// Run the handler.
   206  			if err := cmd.Start(); err != nil {
   207  				c.UI.Error(fmt.Sprintf("Error starting handler: %s", err))
   208  				goto ERR
   209  			}
   210  
   211  			// Set up signal forwarding.
   212  			agent.ForwardSignals(cmd, logFn, doneCh)
   213  
   214  			// Wait for the handler to complete.
   215  			if err := cmd.Wait(); err != nil {
   216  				c.UI.Error(fmt.Sprintf("Error executing handler: %s", err))
   217  				goto ERR
   218  			}
   219  			return
   220  		ERR:
   221  			wp.Stop()
   222  			errExit = 1
   223  		}
   224  	}
   225  
   226  	// Watch for a shutdown
   227  	go func() {
   228  		<-c.shutdownCh
   229  		wp.Stop()
   230  		os.Exit(0)
   231  	}()
   232  
   233  	// Run the watch
   234  	if err := wp.Run(c.http.Addr()); err != nil {
   235  		c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
   236  		return 1
   237  	}
   238  
   239  	return errExit
   240  }
   241  
   242  func (c *cmd) Synopsis() string {
   243  	return synopsis
   244  }
   245  
   246  func (c *cmd) Help() string {
   247  	return c.help
   248  }
   249  
   250  const synopsis = "Watch for changes in Consul"
   251  const help = `
   252  Usage: consul watch [options] [child...]
   253  
   254    Watches for changes in a given data view from Consul. If a child process
   255    is specified, it will be invoked with the latest results on changes. Otherwise,
   256    the latest values are dumped to stdout and the watch terminates.
   257  
   258    Providing the watch type is required, and other parameters may be required
   259    or supported depending on the watch type.
   260  `