github.com/kjdelisle/consul@v1.4.5/agent/kvs_endpoint.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"github.com/hashicorp/consul/agent/structs"
    12  	"github.com/hashicorp/consul/api"
    13  )
    14  
    15  const (
    16  	// maxKVSize is used to limit the maximum payload length
    17  	// of a KV entry. If it exceeds this amount, the client is
    18  	// likely abusing the KV store.
    19  	maxKVSize = 512 * 1024
    20  )
    21  
    22  func (s *HTTPServer) KVSEndpoint(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    23  	// Set default DC
    24  	args := structs.KeyRequest{}
    25  	if done := s.parse(resp, req, &args.Datacenter, &args.QueryOptions); done {
    26  		return nil, nil
    27  	}
    28  
    29  	// Pull out the key name, validation left to each sub-handler
    30  	args.Key = strings.TrimPrefix(req.URL.Path, "/v1/kv/")
    31  
    32  	// Check for a key list
    33  	keyList := false
    34  	params := req.URL.Query()
    35  	if _, ok := params["keys"]; ok {
    36  		keyList = true
    37  	}
    38  
    39  	// Switch on the method
    40  	switch req.Method {
    41  	case "GET":
    42  		if keyList {
    43  			return s.KVSGetKeys(resp, req, &args)
    44  		}
    45  		return s.KVSGet(resp, req, &args)
    46  	case "PUT":
    47  		return s.KVSPut(resp, req, &args)
    48  	case "DELETE":
    49  		return s.KVSDelete(resp, req, &args)
    50  	default:
    51  		return nil, MethodNotAllowedError{req.Method, []string{"GET", "PUT", "DELETE"}}
    52  	}
    53  }
    54  
    55  // KVSGet handles a GET request
    56  func (s *HTTPServer) KVSGet(resp http.ResponseWriter, req *http.Request, args *structs.KeyRequest) (interface{}, error) {
    57  	// Check for recurse
    58  	method := "KVS.Get"
    59  	params := req.URL.Query()
    60  	if _, ok := params["recurse"]; ok {
    61  		method = "KVS.List"
    62  	} else if missingKey(resp, args) {
    63  		return nil, nil
    64  	}
    65  
    66  	// Make the RPC
    67  	var out structs.IndexedDirEntries
    68  	if err := s.agent.RPC(method, &args, &out); err != nil {
    69  		return nil, err
    70  	}
    71  	setMeta(resp, &out.QueryMeta)
    72  
    73  	// Check if we get a not found
    74  	if len(out.Entries) == 0 {
    75  		resp.WriteHeader(http.StatusNotFound)
    76  		return nil, nil
    77  	}
    78  
    79  	// Check if we are in raw mode with a normal get, write out
    80  	// the raw body
    81  	if _, ok := params["raw"]; ok && method == "KVS.Get" {
    82  		body := out.Entries[0].Value
    83  		resp.Header().Set("Content-Length", strconv.FormatInt(int64(len(body)), 10))
    84  		resp.Write(body)
    85  		return nil, nil
    86  	}
    87  
    88  	return out.Entries, nil
    89  }
    90  
    91  // KVSGetKeys handles a GET request for keys
    92  func (s *HTTPServer) KVSGetKeys(resp http.ResponseWriter, req *http.Request, args *structs.KeyRequest) (interface{}, error) {
    93  	// Check for a separator, due to historic spelling error,
    94  	// we now are forced to check for both spellings
    95  	var sep string
    96  	params := req.URL.Query()
    97  	if _, ok := params["seperator"]; ok {
    98  		sep = params.Get("seperator")
    99  	}
   100  	if _, ok := params["separator"]; ok {
   101  		sep = params.Get("separator")
   102  	}
   103  
   104  	// Construct the args
   105  	listArgs := structs.KeyListRequest{
   106  		Datacenter:   args.Datacenter,
   107  		Prefix:       args.Key,
   108  		Seperator:    sep,
   109  		QueryOptions: args.QueryOptions,
   110  	}
   111  
   112  	// Make the RPC
   113  	var out structs.IndexedKeyList
   114  	if err := s.agent.RPC("KVS.ListKeys", &listArgs, &out); err != nil {
   115  		return nil, err
   116  	}
   117  	setMeta(resp, &out.QueryMeta)
   118  
   119  	// Check if we get a not found. We do not generate
   120  	// not found for the root, but just provide the empty list
   121  	if len(out.Keys) == 0 && listArgs.Prefix != "" {
   122  		resp.WriteHeader(http.StatusNotFound)
   123  		return nil, nil
   124  	}
   125  
   126  	// Use empty list instead of null
   127  	if out.Keys == nil {
   128  		out.Keys = []string{}
   129  	}
   130  	return out.Keys, nil
   131  }
   132  
   133  // KVSPut handles a PUT request
   134  func (s *HTTPServer) KVSPut(resp http.ResponseWriter, req *http.Request, args *structs.KeyRequest) (interface{}, error) {
   135  	if missingKey(resp, args) {
   136  		return nil, nil
   137  	}
   138  	if conflictingFlags(resp, req, "cas", "acquire", "release") {
   139  		return nil, nil
   140  	}
   141  	applyReq := structs.KVSRequest{
   142  		Datacenter: args.Datacenter,
   143  		Op:         api.KVSet,
   144  		DirEnt: structs.DirEntry{
   145  			Key:   args.Key,
   146  			Flags: 0,
   147  			Value: nil,
   148  		},
   149  	}
   150  	applyReq.Token = args.Token
   151  
   152  	// Check for flags
   153  	params := req.URL.Query()
   154  	if _, ok := params["flags"]; ok {
   155  		flagVal, err := strconv.ParseUint(params.Get("flags"), 10, 64)
   156  		if err != nil {
   157  			return nil, err
   158  		}
   159  		applyReq.DirEnt.Flags = flagVal
   160  	}
   161  
   162  	// Check for cas value
   163  	if _, ok := params["cas"]; ok {
   164  		casVal, err := strconv.ParseUint(params.Get("cas"), 10, 64)
   165  		if err != nil {
   166  			return nil, err
   167  		}
   168  		applyReq.DirEnt.ModifyIndex = casVal
   169  		applyReq.Op = api.KVCAS
   170  	}
   171  
   172  	// Check for lock acquisition
   173  	if _, ok := params["acquire"]; ok {
   174  		applyReq.DirEnt.Session = params.Get("acquire")
   175  		applyReq.Op = api.KVLock
   176  	}
   177  
   178  	// Check for lock release
   179  	if _, ok := params["release"]; ok {
   180  		applyReq.DirEnt.Session = params.Get("release")
   181  		applyReq.Op = api.KVUnlock
   182  	}
   183  
   184  	// Check the content-length
   185  	if req.ContentLength > maxKVSize {
   186  		resp.WriteHeader(http.StatusRequestEntityTooLarge)
   187  		fmt.Fprintf(resp, "Value exceeds %d byte limit", maxKVSize)
   188  		return nil, nil
   189  	}
   190  
   191  	// Copy the value
   192  	buf := bytes.NewBuffer(nil)
   193  	if _, err := io.Copy(buf, req.Body); err != nil {
   194  		return nil, err
   195  	}
   196  	applyReq.DirEnt.Value = buf.Bytes()
   197  
   198  	// Make the RPC
   199  	var out bool
   200  	if err := s.agent.RPC("KVS.Apply", &applyReq, &out); err != nil {
   201  		return nil, err
   202  	}
   203  
   204  	// Only use the out value if this was a CAS
   205  	if applyReq.Op == api.KVSet {
   206  		return true, nil
   207  	}
   208  	return out, nil
   209  }
   210  
   211  // KVSPut handles a DELETE request
   212  func (s *HTTPServer) KVSDelete(resp http.ResponseWriter, req *http.Request, args *structs.KeyRequest) (interface{}, error) {
   213  	if conflictingFlags(resp, req, "recurse", "cas") {
   214  		return nil, nil
   215  	}
   216  	applyReq := structs.KVSRequest{
   217  		Datacenter: args.Datacenter,
   218  		Op:         api.KVDelete,
   219  		DirEnt: structs.DirEntry{
   220  			Key: args.Key,
   221  		},
   222  	}
   223  	applyReq.Token = args.Token
   224  
   225  	// Check for recurse
   226  	params := req.URL.Query()
   227  	if _, ok := params["recurse"]; ok {
   228  		applyReq.Op = api.KVDeleteTree
   229  	} else if missingKey(resp, args) {
   230  		return nil, nil
   231  	}
   232  
   233  	// Check for cas value
   234  	if _, ok := params["cas"]; ok {
   235  		casVal, err := strconv.ParseUint(params.Get("cas"), 10, 64)
   236  		if err != nil {
   237  			return nil, err
   238  		}
   239  		applyReq.DirEnt.ModifyIndex = casVal
   240  		applyReq.Op = api.KVDeleteCAS
   241  	}
   242  
   243  	// Make the RPC
   244  	var out bool
   245  	if err := s.agent.RPC("KVS.Apply", &applyReq, &out); err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	// Only use the out value if this was a CAS
   250  	if applyReq.Op == api.KVDeleteCAS {
   251  		return out, nil
   252  	}
   253  	return true, nil
   254  }
   255  
   256  // missingKey checks if the key is missing
   257  func missingKey(resp http.ResponseWriter, args *structs.KeyRequest) bool {
   258  	if args.Key == "" {
   259  		resp.WriteHeader(http.StatusBadRequest)
   260  		fmt.Fprint(resp, "Missing key name")
   261  		return true
   262  	}
   263  	return false
   264  }
   265  
   266  // conflictingFlags determines if non-composable flags were passed in a request.
   267  func conflictingFlags(resp http.ResponseWriter, req *http.Request, flags ...string) bool {
   268  	params := req.URL.Query()
   269  
   270  	found := false
   271  	for _, conflict := range flags {
   272  		if _, ok := params[conflict]; ok {
   273  			if found {
   274  				resp.WriteHeader(http.StatusBadRequest)
   275  				fmt.Fprint(resp, "Conflicting flags: "+params.Encode())
   276  				return true
   277  			}
   278  			found = true
   279  		}
   280  	}
   281  
   282  	return false
   283  }