diff --git a/command/agent/kvs_endpoint.go b/command/agent/kvs_endpoint.go index 12c6171e58..2a47e9a1b4 100644 --- a/command/agent/kvs_endpoint.go +++ b/command/agent/kvs_endpoint.go @@ -19,10 +19,21 @@ func (s *HTTPServer) KVSEndpoint(resp http.ResponseWriter, req *http.Request) (i // Pull out the key name, validation left to each sub-handler args.Key = strings.TrimPrefix(req.URL.Path, "/v1/kv/") + // Check for a key list + keyList := false + params := req.URL.Query() + if _, ok := params["keys"]; ok { + keyList = true + } + // Switch on the method switch req.Method { case "GET": - return s.KVSGet(resp, req, &args) + if keyList { + return s.KVSGetKeys(resp, req, &args) + } else { + return s.KVSGet(resp, req, &args) + } case "PUT": return s.KVSPut(resp, req, &args) case "DELETE": @@ -60,6 +71,38 @@ func (s *HTTPServer) KVSGet(resp http.ResponseWriter, req *http.Request, args *s return out.Entries, nil } +// KVSGetKeys handles a GET request for keys +func (s *HTTPServer) KVSGetKeys(resp http.ResponseWriter, req *http.Request, args *structs.KeyRequest) (interface{}, error) { + // Check for a seperator + var sep string + params := req.URL.Query() + if _, ok := params["seperator"]; ok { + sep = params.Get("seperator") + } + + // Construct the args + listArgs := structs.KeyListRequest{ + Datacenter: args.Datacenter, + Prefix: args.Key, + Seperator: sep, + QueryOptions: args.QueryOptions, + } + + // Make the RPC + var out structs.IndexedKeyList + if err := s.agent.RPC("KVS.ListKeys", &listArgs, &out); err != nil { + return nil, err + } + setMeta(resp, &out.QueryMeta) + + // Check if we get a not found + if len(out.Keys) == 0 { + resp.WriteHeader(404) + return nil, nil + } + return out.Keys, nil +} + // KVSPut handles a PUT request func (s *HTTPServer) KVSPut(resp http.ResponseWriter, req *http.Request, args *structs.KeyRequest) (interface{}, error) { if missingKey(resp, args) { diff --git a/command/agent/kvs_endpoint_test.go b/command/agent/kvs_endpoint_test.go index f28ef17cf2..8d3679e92c 100644 --- a/command/agent/kvs_endpoint_test.go +++ b/command/agent/kvs_endpoint_test.go @@ -7,6 +7,7 @@ import ( "net/http" "net/http/httptest" "os" + "reflect" "testing" "time" ) @@ -281,3 +282,64 @@ func TestKVSEndpoint_CAS(t *testing.T) { t.Fatalf("bad: %v", d) } } + +func TestKVSEndpoint_ListKeys(t *testing.T) { + dir, srv := makeHTTPServer(t) + defer os.RemoveAll(dir) + defer srv.Shutdown() + defer srv.agent.Shutdown() + + // Wait for a leader + time.Sleep(100 * time.Millisecond) + + keys := []string{ + "bar", + "baz", + "foo/sub1", + "foo/sub2", + "zip", + } + + for _, key := range keys { + buf := bytes.NewBuffer([]byte("test")) + req, err := http.NewRequest("PUT", "/v1/kv/"+key, buf) + if err != nil { + t.Fatalf("err: %v", err) + } + + resp := httptest.NewRecorder() + obj, err := srv.KVSEndpoint(resp, req) + if err != nil { + t.Fatalf("err: %v", err) + } + + if res := obj.(bool); !res { + t.Fatalf("should work") + } + } + + { + // Get all the keys + req, err := http.NewRequest("GET", "/v1/kv/?keys&seperator=/", nil) + if err != nil { + t.Fatalf("err: %v", err) + } + + resp := httptest.NewRecorder() + obj, err := srv.KVSEndpoint(resp, req) + if err != nil { + t.Fatalf("err: %v", err) + } + assertIndex(t, resp) + + res, ok := obj.([]string) + if !ok { + t.Fatalf("should work") + } + + expect := []string{"bar", "baz", "foo/", "zip"} + if !reflect.DeepEqual(res, expect) { + t.Fatalf("bad: %v", res) + } + } +}