2017-10-17 08:25:13 +00:00
|
|
|
package watch
|
2014-08-21 23:02:41 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2017-10-17 08:25:13 +00:00
|
|
|
"flag"
|
2014-08-21 23:02:41 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
2017-10-25 09:18:07 +00:00
|
|
|
osexec "os/exec"
|
2014-08-21 23:02:41 +00:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
pkg refactor
command/agent/* -> agent/*
command/consul/* -> agent/consul/*
command/agent/command{,_test}.go -> command/agent{,_test}.go
command/base/command.go -> command/base.go
command/base/* -> command/*
commands.go -> command/commands.go
The script which did the refactor is:
(
cd $GOPATH/src/github.com/hashicorp/consul
git mv command/agent/command.go command/agent.go
git mv command/agent/command_test.go command/agent_test.go
git mv command/agent/flag_slice_value{,_test}.go command/
git mv command/agent .
git mv command/base/command.go command/base.go
git mv command/base/config_util{,_test}.go command/
git mv commands.go command/
git mv consul agent
rmdir command/base/
gsed -i -e 's|package agent|package command|' command/agent{,_test}.go
gsed -i -e 's|package agent|package command|' command/flag_slice_value{,_test}.go
gsed -i -e 's|package base|package command|' command/base.go command/config_util{,_test}.go
gsed -i -e 's|package main|package command|' command/commands.go
gsed -i -e 's|base.Command|BaseCommand|' command/commands.go
gsed -i -e 's|agent.Command|AgentCommand|' command/commands.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/commands.go
gsed -i -e 's|base\.||' command/commands.go
gsed -i -e 's|command\.||' command/commands.go
gsed -i -e 's|command|c|' main.go
gsed -i -e 's|range Commands|range command.Commands|' main.go
gsed -i -e 's|Commands: Commands|Commands: command.Commands|' main.go
gsed -i -e 's|base\.BoolValue|BoolValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.DurationValue|DurationValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.StringValue|StringValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.UintValue|UintValue|' command/operator_autopilot_set.go
gsed -i -e 's|\bCommand\b|BaseCommand|' command/base.go
gsed -i -e 's|BaseCommand Options|Command Options|' command/base.go
gsed -i -e 's|base.Command|BaseCommand|' command/*.go
gsed -i -e 's|c\.Command|c.BaseCommand|g' command/*.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/*_test.go
gsed -i -e 's|base\.||' command/*_test.go
gsed -i -e 's|\bCommand\b|AgentCommand|' command/agent{,_test}.go
gsed -i -e 's|cmd.AgentCommand|cmd.BaseCommand|' command/agent.go
gsed -i -e 's|cli.AgentCommand = new(Command)|cli.Command = new(AgentCommand)|' command/agent_test.go
gsed -i -e 's|exec.AgentCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|exec.BaseCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|NewTestAgent|agent.NewTestAgent|' command/agent_test.go
gsed -i -e 's|= TestConfig|= agent.TestConfig|' command/agent_test.go
gsed -i -e 's|: RetryJoin|: agent.RetryJoin|' command/agent_test.go
gsed -i -e 's|\.\./\.\./|../|' command/config_util_test.go
gsed -i -e 's|\bverifyUniqueListeners|VerifyUniqueListeners|' agent/config{,_test}.go command/agent.go
gsed -i -e 's|\bserfLANKeyring\b|SerfLANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bserfWANKeyring\b|SerfWANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bNewAgent\b|agent.New|g' command/agent{,_test}.go
gsed -i -e 's|\bNewAgent|New|' agent/{acl_test,agent,testagent}.go
gsed -i -e 's|\bAgent\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bBool\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDefaultConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDevConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bMergeConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bReadConfigPaths\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bParseMetaPair\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfLANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfWANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|circonus\.agent|circonus|g' command/agent{,_test}.go
gsed -i -e 's|logger\.agent|logger|g' command/agent{,_test}.go
gsed -i -e 's|metrics\.agent|metrics|g' command/agent{,_test}.go
gsed -i -e 's|// agent.Agent|// agent|' command/agent{,_test}.go
gsed -i -e 's|a\.agent\.Config|a.Config|' command/agent{,_test}.go
gsed -i -e 's|agent\.AppendSliceValue|AppendSliceValue|' command/{configtest,validate}.go
gsed -i -e 's|consul/consul|agent/consul|' GNUmakefile
gsed -i -e 's|\.\./test|../../test|' agent/consul/server_test.go
# fix imports
f=$(grep -rl 'github.com/hashicorp/consul/command/agent' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/command/agent|github.com/hashicorp/consul/agent|' $f
goimports -w $f
f=$(grep -rl 'github.com/hashicorp/consul/consul' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/consul|github.com/hashicorp/consul/agent/consul|' $f
goimports -w $f
goimports -w command/*.go main.go
)
2017-06-09 22:28:28 +00:00
|
|
|
"github.com/hashicorp/consul/agent"
|
2017-10-25 09:18:07 +00:00
|
|
|
"github.com/hashicorp/consul/agent/exec"
|
2019-04-30 14:59:00 +00:00
|
|
|
"github.com/hashicorp/consul/api"
|
2019-04-26 16:33:01 +00:00
|
|
|
consulwatch "github.com/hashicorp/consul/api/watch"
|
2017-10-17 08:25:13 +00:00
|
|
|
"github.com/hashicorp/consul/command/flags"
|
|
|
|
"github.com/mitchellh/cli"
|
2014-08-21 23:02:41 +00:00
|
|
|
)
|
|
|
|
|
2017-10-17 08:25:13 +00:00
|
|
|
func New(ui cli.Ui, shutdownCh <-chan struct{}) *cmd {
|
|
|
|
c := &cmd{UI: ui, shutdownCh: shutdownCh}
|
|
|
|
c.init()
|
|
|
|
return c
|
|
|
|
}
|
|
|
|
|
|
|
|
type cmd struct {
|
|
|
|
UI cli.Ui
|
|
|
|
flags *flag.FlagSet
|
|
|
|
http *flags.HTTPFlags
|
2017-10-17 13:44:20 +00:00
|
|
|
help string
|
2017-10-17 08:25:13 +00:00
|
|
|
|
|
|
|
shutdownCh <-chan struct{}
|
2014-08-21 23:02:41 +00:00
|
|
|
|
2017-10-11 12:51:18 +00:00
|
|
|
// flags
|
|
|
|
watchType string
|
|
|
|
key string
|
|
|
|
prefix string
|
|
|
|
service string
|
2019-04-29 19:28:01 +00:00
|
|
|
tag []string
|
2017-10-11 12:51:18 +00:00
|
|
|
passingOnly string
|
|
|
|
state string
|
|
|
|
name string
|
|
|
|
shell bool
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
|
|
|
|
2017-10-17 08:25:13 +00:00
|
|
|
func (c *cmd) init() {
|
|
|
|
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
|
|
|
|
c.flags.StringVar(&c.watchType, "type", "",
|
2017-05-02 23:08:07 +00:00
|
|
|
"Specifies the watch type. One of key, keyprefix, services, nodes, "+
|
2017-02-10 01:36:01 +00:00
|
|
|
"service, checks, or event.")
|
2017-10-17 08:25:13 +00:00
|
|
|
c.flags.StringVar(&c.key, "key", "",
|
2017-02-10 01:36:01 +00:00
|
|
|
"Specifies the key to watch. Only for 'key' type.")
|
2017-10-17 08:25:13 +00:00
|
|
|
c.flags.StringVar(&c.prefix, "prefix", "",
|
2017-02-10 01:36:01 +00:00
|
|
|
"Specifies the key prefix to watch. Only for 'keyprefix' type.")
|
2017-10-17 08:25:13 +00:00
|
|
|
c.flags.StringVar(&c.service, "service", "",
|
2017-02-10 01:36:01 +00:00
|
|
|
"Specifies the service to watch. Required for 'service' type, "+
|
|
|
|
"optional for 'checks' type.")
|
2019-04-29 19:28:01 +00:00
|
|
|
c.flags.Var((*flags.AppendSliceValue)(&c.tag), "tag", "Specifies the service tag(s) to filter on. "+
|
|
|
|
"Optional for 'service' type. May be specified multiple times")
|
2017-10-17 08:25:13 +00:00
|
|
|
c.flags.StringVar(&c.passingOnly, "passingonly", "",
|
2017-02-10 01:36:01 +00:00
|
|
|
"Specifies if only hosts passing all checks are displayed. "+
|
|
|
|
"Optional for 'service' type, must be one of `[true|false]`. Defaults false.")
|
2017-10-17 08:25:13 +00:00
|
|
|
c.flags.BoolVar(&c.shell, "shell", true,
|
2017-10-04 23:48:00 +00:00
|
|
|
"Use a shell to run the command (can set a custom shell via the SHELL "+
|
|
|
|
"environment variable).")
|
2017-10-17 08:25:13 +00:00
|
|
|
c.flags.StringVar(&c.state, "state", "",
|
2017-02-10 01:36:01 +00:00
|
|
|
"Specifies the states to watch. Optional for 'checks' type.")
|
2017-10-17 08:25:13 +00:00
|
|
|
c.flags.StringVar(&c.name, "name", "",
|
2017-02-10 01:36:01 +00:00
|
|
|
"Specifies an event name to watch. Only for 'event' type.")
|
2017-10-11 12:51:18 +00:00
|
|
|
|
2017-10-17 08:25:13 +00:00
|
|
|
c.http = &flags.HTTPFlags{}
|
|
|
|
flags.Merge(c.flags, c.http.ClientFlags())
|
|
|
|
flags.Merge(c.flags, c.http.ServerFlags())
|
2017-10-17 22:00:01 +00:00
|
|
|
c.help = flags.Usage(help, c.flags)
|
2017-10-11 12:51:18 +00:00
|
|
|
}
|
|
|
|
|
2019-04-30 14:59:00 +00:00
|
|
|
func (c *cmd) loadToken() (string, error) {
|
|
|
|
httpCfg := api.DefaultConfig()
|
|
|
|
c.http.MergeOntoConfig(httpCfg)
|
|
|
|
// Trigger the Client init to do any last-minute updates to the Config.
|
|
|
|
if _, err := api.NewClient(httpCfg); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return httpCfg.Token, nil
|
|
|
|
}
|
|
|
|
|
2017-10-17 08:25:13 +00:00
|
|
|
func (c *cmd) Run(args []string) int {
|
|
|
|
if err := c.flags.Parse(args); err != nil {
|
2014-08-21 23:02:41 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check for a type
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.watchType == "" {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error("Watch type must be specified")
|
|
|
|
c.UI.Error("")
|
|
|
|
c.UI.Error(c.Help())
|
2014-08-21 23:02:41 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2019-04-30 14:59:00 +00:00
|
|
|
token, err := c.loadToken()
|
|
|
|
if err != nil {
|
|
|
|
c.UI.Error(err.Error())
|
2019-04-26 17:49:28 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2014-08-21 23:02:41 +00:00
|
|
|
// Compile the watch parameters
|
|
|
|
params := make(map[string]interface{})
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.watchType != "" {
|
|
|
|
params["type"] = c.watchType
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2017-10-17 08:25:13 +00:00
|
|
|
if c.http.Datacenter() != "" {
|
|
|
|
params["datacenter"] = c.http.Datacenter()
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2019-04-26 17:49:28 +00:00
|
|
|
if token != "" {
|
|
|
|
params["token"] = token
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.key != "" {
|
|
|
|
params["key"] = c.key
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.prefix != "" {
|
|
|
|
params["prefix"] = c.prefix
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.service != "" {
|
|
|
|
params["service"] = c.service
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2019-04-29 19:28:01 +00:00
|
|
|
if len(c.tag) > 0 {
|
2017-10-11 12:51:18 +00:00
|
|
|
params["tag"] = c.tag
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2017-10-17 08:25:13 +00:00
|
|
|
if c.http.Stale() {
|
|
|
|
params["stale"] = c.http.Stale()
|
2016-05-10 07:09:15 +00:00
|
|
|
}
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.state != "" {
|
|
|
|
params["state"] = c.state
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.name != "" {
|
|
|
|
params["name"] = c.name
|
2014-08-28 22:51:12 +00:00
|
|
|
}
|
2017-10-11 12:51:18 +00:00
|
|
|
if c.passingOnly != "" {
|
|
|
|
b, err := strconv.ParseBool(c.passingOnly)
|
2014-08-21 23:02:41 +00:00
|
|
|
if err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Failed to parse passingonly flag: %s", err))
|
2014-08-21 23:02:41 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
params["passingonly"] = b
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the watch
|
2017-10-17 08:25:13 +00:00
|
|
|
wp, err := consulwatch.Parse(params)
|
2014-08-21 23:02:41 +00:00
|
|
|
if err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("%s", err))
|
2014-08-21 23:02:41 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2018-09-27 14:00:51 +00:00
|
|
|
if strings.HasPrefix(wp.Type, "connect_") || strings.HasPrefix(wp.Type, "agent_") {
|
2018-04-26 17:06:26 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Type %s is not supported in the CLI tool", wp.Type))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2021-08-09 16:00:33 +00:00
|
|
|
// Create and test that the API is accessible before starting a blocking
|
|
|
|
// loop for the watch.
|
|
|
|
//
|
|
|
|
// Consul does not have a /ping endpoint, so the /status/leader endpoint
|
|
|
|
// will be used as a substitute since it does not require an ACL token to
|
|
|
|
// query, and will always return a response to the client, unless there is a
|
|
|
|
// network communication error.
|
2017-10-17 08:25:13 +00:00
|
|
|
client, err := c.http.APIClient()
|
2014-08-21 23:02:41 +00:00
|
|
|
if err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
|
2014-08-21 23:02:41 +00:00
|
|
|
return 1
|
|
|
|
}
|
2021-08-09 16:00:33 +00:00
|
|
|
_, err = client.Status().Leader()
|
2014-08-21 23:02:41 +00:00
|
|
|
if err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
|
2014-08-21 23:02:41 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Setup handler
|
2014-12-06 11:50:38 +00:00
|
|
|
|
|
|
|
// errExit:
|
|
|
|
// 0: false
|
|
|
|
// 1: true
|
|
|
|
errExit := 0
|
2017-10-17 08:25:13 +00:00
|
|
|
if len(c.flags.Args()) == 0 {
|
2018-04-25 19:41:26 +00:00
|
|
|
wp.Handler = func(idx uint64, data interface{}) {
|
2014-08-21 23:02:41 +00:00
|
|
|
defer wp.Stop()
|
|
|
|
buf, err := json.MarshalIndent(data, "", " ")
|
|
|
|
if err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Error encoding output: %s", err))
|
2014-12-06 11:50:38 +00:00
|
|
|
errExit = 1
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Output(string(buf))
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
|
|
|
} else {
|
2018-04-25 19:41:26 +00:00
|
|
|
wp.Handler = func(idx uint64, data interface{}) {
|
2017-10-04 23:48:00 +00:00
|
|
|
doneCh := make(chan struct{})
|
|
|
|
defer close(doneCh)
|
|
|
|
logFn := func(err error) {
|
|
|
|
c.UI.Error(fmt.Sprintf("Warning, could not forward signal: %s", err))
|
|
|
|
}
|
|
|
|
|
2014-08-21 23:02:41 +00:00
|
|
|
// Create the command
|
|
|
|
var buf bytes.Buffer
|
|
|
|
var err error
|
2017-10-25 09:18:07 +00:00
|
|
|
var cmd *osexec.Cmd
|
2017-10-11 12:51:18 +00:00
|
|
|
if !c.shell {
|
2017-10-25 09:18:07 +00:00
|
|
|
cmd, err = exec.Subprocess(c.flags.Args())
|
2017-10-04 23:48:00 +00:00
|
|
|
} else {
|
2017-10-25 09:18:07 +00:00
|
|
|
cmd, err = exec.Script(strings.Join(c.flags.Args(), " "))
|
2017-10-04 23:48:00 +00:00
|
|
|
}
|
2014-08-21 23:02:41 +00:00
|
|
|
if err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Error executing handler: %s", err))
|
2014-08-21 23:02:41 +00:00
|
|
|
goto ERR
|
|
|
|
}
|
|
|
|
cmd.Env = append(os.Environ(),
|
2018-04-25 19:41:26 +00:00
|
|
|
"CONSUL_INDEX="+strconv.FormatUint(idx, 10),
|
2014-08-21 23:02:41 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Encode the input
|
|
|
|
if err = json.NewEncoder(&buf).Encode(data); err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Error encoding output: %s", err))
|
2014-08-21 23:02:41 +00:00
|
|
|
goto ERR
|
|
|
|
}
|
|
|
|
cmd.Stdin = &buf
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
2017-10-04 23:48:00 +00:00
|
|
|
// Run the handler.
|
|
|
|
if err := cmd.Start(); err != nil {
|
|
|
|
c.UI.Error(fmt.Sprintf("Error starting handler: %s", err))
|
|
|
|
goto ERR
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up signal forwarding.
|
|
|
|
agent.ForwardSignals(cmd, logFn, doneCh)
|
|
|
|
|
|
|
|
// Wait for the handler to complete.
|
|
|
|
if err := cmd.Wait(); err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Error executing handler: %s", err))
|
2014-08-21 23:02:41 +00:00
|
|
|
goto ERR
|
|
|
|
}
|
|
|
|
return
|
|
|
|
ERR:
|
|
|
|
wp.Stop()
|
2014-12-06 11:50:38 +00:00
|
|
|
errExit = 1
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Watch for a shutdown
|
|
|
|
go func() {
|
2017-10-17 08:25:13 +00:00
|
|
|
<-c.shutdownCh
|
2014-08-21 23:02:41 +00:00
|
|
|
wp.Stop()
|
|
|
|
os.Exit(0)
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Run the watch
|
2017-10-17 08:25:13 +00:00
|
|
|
if err := wp.Run(c.http.Addr()); err != nil {
|
2017-04-21 00:02:42 +00:00
|
|
|
c.UI.Error(fmt.Sprintf("Error querying Consul agent: %s", err))
|
2014-08-21 23:02:41 +00:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2014-12-06 11:50:38 +00:00
|
|
|
return errExit
|
2014-08-21 23:02:41 +00:00
|
|
|
}
|
|
|
|
|
2017-10-17 13:44:20 +00:00
|
|
|
func (c *cmd) Synopsis() string {
|
|
|
|
return synopsis
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *cmd) Help() string {
|
|
|
|
return c.help
|
|
|
|
}
|
|
|
|
|
|
|
|
const synopsis = "Watch for changes in Consul"
|
|
|
|
const help = `
|
|
|
|
Usage: consul watch [options] [child...]
|
2017-10-17 08:25:13 +00:00
|
|
|
|
|
|
|
Watches for changes in a given data view from Consul. If a child process
|
|
|
|
is specified, it will be invoked with the latest results on changes. Otherwise,
|
|
|
|
the latest values are dumped to stdout and the watch terminates.
|
|
|
|
|
|
|
|
Providing the watch type is required, and other parameters may be required
|
2017-10-17 13:44:20 +00:00
|
|
|
or supported depending on the watch type.
|
|
|
|
`
|