github.com/kjdelisle/consul@v1.4.5/agent/watch_handler.go (about) 1 package agent 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "encoding/json" 7 "fmt" 8 "io" 9 "log" 10 "net/http" 11 "os" 12 osexec "os/exec" 13 "strconv" 14 15 "github.com/armon/circbuf" 16 "github.com/hashicorp/consul/agent/exec" 17 "github.com/hashicorp/consul/watch" 18 "github.com/hashicorp/go-cleanhttp" 19 "golang.org/x/net/context" 20 ) 21 22 const ( 23 // Limit the size of a watch handlers's output to the 24 // last WatchBufSize. Prevents an enormous buffer 25 // from being captured 26 WatchBufSize = 4 * 1024 // 4KB 27 ) 28 29 // makeWatchHandler returns a handler for the given watch 30 func makeWatchHandler(logOutput io.Writer, handler interface{}) watch.HandlerFunc { 31 var args []string 32 var script string 33 34 // Figure out whether to run in shell or raw subprocess mode 35 switch h := handler.(type) { 36 case string: 37 script = h 38 case []string: 39 args = h 40 default: 41 panic(fmt.Errorf("unknown handler type %T", handler)) 42 } 43 44 logger := log.New(logOutput, "", log.LstdFlags) 45 fn := func(idx uint64, data interface{}) { 46 // Create the command 47 var cmd *osexec.Cmd 48 var err error 49 50 if len(args) > 0 { 51 cmd, err = exec.Subprocess(args) 52 } else { 53 cmd, err = exec.Script(script) 54 } 55 if err != nil { 56 logger.Printf("[ERR] agent: Failed to setup watch: %v", err) 57 return 58 } 59 60 cmd.Env = append(os.Environ(), 61 "CONSUL_INDEX="+strconv.FormatUint(idx, 10), 62 ) 63 64 // Collect the output 65 output, _ := circbuf.NewBuffer(WatchBufSize) 66 cmd.Stdout = output 67 cmd.Stderr = output 68 69 // Setup the input 70 var inp bytes.Buffer 71 enc := json.NewEncoder(&inp) 72 if err := enc.Encode(data); err != nil { 73 logger.Printf("[ERR] agent: Failed to encode data for watch '%v': %v", handler, err) 74 return 75 } 76 cmd.Stdin = &inp 77 78 // Run the handler 79 if err := cmd.Run(); err != nil { 80 logger.Printf("[ERR] agent: Failed to run watch handler '%v': %v", handler, err) 81 } 82 83 // Get the output, add a message about truncation 84 outputStr := string(output.Bytes()) 85 if output.TotalWritten() > output.Size() { 86 outputStr = fmt.Sprintf("Captured %d of %d bytes\n...\n%s", 87 output.Size(), output.TotalWritten(), outputStr) 88 } 89 90 // Log the output 91 logger.Printf("[DEBUG] agent: watch handler '%v' output: %s", handler, outputStr) 92 } 93 return fn 94 } 95 96 func makeHTTPWatchHandler(logOutput io.Writer, config *watch.HttpHandlerConfig) watch.HandlerFunc { 97 logger := log.New(logOutput, "", log.LstdFlags) 98 99 fn := func(idx uint64, data interface{}) { 100 trans := cleanhttp.DefaultTransport() 101 102 // Skip SSL certificate verification if TLSSkipVerify is true 103 if trans.TLSClientConfig == nil { 104 trans.TLSClientConfig = &tls.Config{ 105 InsecureSkipVerify: config.TLSSkipVerify, 106 } 107 } else { 108 trans.TLSClientConfig.InsecureSkipVerify = config.TLSSkipVerify 109 } 110 111 ctx := context.Background() 112 ctx, cancel := context.WithTimeout(ctx, config.Timeout) 113 defer cancel() 114 115 // Create the HTTP client. 116 httpClient := &http.Client{ 117 Transport: trans, 118 } 119 120 // Setup the input 121 var inp bytes.Buffer 122 enc := json.NewEncoder(&inp) 123 if err := enc.Encode(data); err != nil { 124 logger.Printf("[ERR] agent: Failed to encode data for http watch '%s': %v", config.Path, err) 125 return 126 } 127 128 req, err := http.NewRequest(config.Method, config.Path, &inp) 129 if err != nil { 130 logger.Printf("[ERR] agent: Failed to setup http watch: %v", err) 131 return 132 } 133 req = req.WithContext(ctx) 134 req.Header.Add("Content-Type", "application/json") 135 req.Header.Add("X-Consul-Index", strconv.FormatUint(idx, 10)) 136 for key, values := range config.Header { 137 for _, val := range values { 138 req.Header.Add(key, val) 139 } 140 } 141 resp, err := httpClient.Do(req) 142 if err != nil { 143 logger.Printf("[ERR] agent: Failed to invoke http watch handler '%s': %v", config.Path, err) 144 return 145 } 146 defer resp.Body.Close() 147 148 // Collect the output 149 output, _ := circbuf.NewBuffer(WatchBufSize) 150 io.Copy(output, resp.Body) 151 152 // Get the output, add a message about truncation 153 outputStr := string(output.Bytes()) 154 if output.TotalWritten() > output.Size() { 155 outputStr = fmt.Sprintf("Captured %d of %d bytes\n...\n%s", 156 output.Size(), output.TotalWritten(), outputStr) 157 } 158 159 if resp.StatusCode >= 200 && resp.StatusCode <= 299 { 160 // Log the output 161 logger.Printf("[TRACE] agent: http watch handler '%s' output: %s", config.Path, outputStr) 162 } else { 163 logger.Printf("[ERR] agent: http watch handler '%s' got '%s' with output: %s", 164 config.Path, resp.Status, outputStr) 165 } 166 } 167 return fn 168 }