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 `