go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv2/command/exec_watch_command.go (about) 1 // Copyright 2015 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package command 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 "os" 22 "os/exec" 23 "os/signal" 24 25 "github.com/coreos/etcd/client" 26 27 "github.com/urfave/cli" 28 ) 29 30 // NewExecWatchCommand returns the CLI command for "exec-watch". 31 func NewExecWatchCommand() cli.Command { 32 return cli.Command{ 33 Name: "exec-watch", 34 Usage: "watch a key for changes and exec an executable", 35 ArgsUsage: "<key> <command> [args...]", 36 Flags: []cli.Flag{ 37 cli.IntFlag{Name: "after-index", Value: 0, Usage: "watch after the given index"}, 38 cli.BoolFlag{Name: "recursive, r", Usage: "watch all values for key and child keys"}, 39 }, 40 Action: func(c *cli.Context) error { 41 execWatchCommandFunc(c, mustNewKeyAPI(c)) 42 return nil 43 }, 44 } 45 } 46 47 // execWatchCommandFunc executes the "exec-watch" command. 48 func execWatchCommandFunc(c *cli.Context, ki client.KeysAPI) { 49 args := c.Args() 50 argslen := len(args) 51 52 if argslen < 2 { 53 handleError(c, ExitBadArgs, errors.New("key and command to exec required")) 54 } 55 56 var ( 57 key string 58 cmdArgs []string 59 ) 60 61 foundSep := false 62 for i := range args { 63 if args[i] == "--" && i != 0 { 64 foundSep = true 65 break 66 } 67 } 68 69 if foundSep { 70 key = args[0] 71 cmdArgs = args[2:] 72 } else { 73 // If no flag is parsed, the order of key and cmdArgs will be switched and 74 // args will not contain `--`. 75 key = args[argslen-1] 76 cmdArgs = args[:argslen-1] 77 } 78 79 index := 0 80 if c.Int("after-index") != 0 { 81 index = c.Int("after-index") 82 } 83 84 recursive := c.Bool("recursive") 85 86 sigch := make(chan os.Signal, 1) 87 signal.Notify(sigch, os.Interrupt) 88 89 go func() { 90 <-sigch 91 os.Exit(0) 92 }() 93 94 w := ki.Watcher(key, &client.WatcherOptions{AfterIndex: uint64(index), Recursive: recursive}) 95 96 for { 97 resp, err := w.Next(context.TODO()) 98 if err != nil { 99 handleError(c, ExitServerError, err) 100 } 101 if resp.Node.Dir { 102 fmt.Fprintf(os.Stderr, "Ignored dir %s change\n", resp.Node.Key) 103 continue 104 } 105 106 cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...) 107 cmd.Env = environResponse(resp, os.Environ()) 108 109 cmd.Stdout = os.Stdout 110 cmd.Stderr = os.Stderr 111 112 go func() { 113 err := cmd.Start() 114 if err != nil { 115 fmt.Fprintf(os.Stderr, err.Error()) 116 os.Exit(1) 117 } 118 cmd.Wait() 119 }() 120 } 121 } 122 123 func environResponse(resp *client.Response, env []string) []string { 124 env = append(env, "ETCD_WATCH_ACTION="+resp.Action) 125 env = append(env, "ETCD_WATCH_MODIFIED_INDEX="+fmt.Sprintf("%d", resp.Node.ModifiedIndex)) 126 env = append(env, "ETCD_WATCH_KEY="+resp.Node.Key) 127 env = append(env, "ETCD_WATCH_VALUE="+resp.Node.Value) 128 return env 129 }