github.com/sl1pm4t/consul@v1.4.5-0.20190325224627-74c31c540f9c/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  }