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 }