diff --git a/consul/kvs_endpoint.go b/consul/kvs_endpoint.go index b955f3428..8710f0074 100644 --- a/consul/kvs_endpoint.go +++ b/consul/kvs_endpoint.go @@ -115,3 +115,27 @@ func (k *KVS) List(args *structs.KeyRequest, reply *structs.IndexedDirEntries) e return nil }) } + +// ListKeys is used to list all keys with a given prefix to a seperator +func (k *KVS) ListKeys(args *structs.KeyListRequest, reply *structs.IndexedKeyList) error { + // Ensure we have a seperator + if args.Seperator == "" { + return fmt.Errorf("Missing seperator") + } + + // Forward if necessary + if done, err := k.srv.forward("KVS.ListKeys", args, args, reply); done { + return err + } + + // Get the local state + state := k.srv.fsm.State() + return k.srv.blockingRPC(&args.QueryOptions, + &reply.QueryMeta, + state.QueryTables("KVSListKeys"), + func() error { + var err error + reply.Index, reply.Keys, err = state.KVSListKeys(args.Prefix, args.Seperator) + return err + }) +} diff --git a/consul/kvs_endpoint_test.go b/consul/kvs_endpoint_test.go index d116e82c5..5c3eef621 100644 --- a/consul/kvs_endpoint_test.go +++ b/consul/kvs_endpoint_test.go @@ -171,3 +171,62 @@ func TestKVSEndpoint_List(t *testing.T) { } } } + +func TestKVSEndpoint_ListKeys(t *testing.T) { + dir1, s1 := testServer(t) + defer os.RemoveAll(dir1) + defer s1.Shutdown() + client := rpcClient(t, s1) + defer client.Close() + + // Wait for leader + time.Sleep(100 * time.Millisecond) + + keys := []string{ + "/test/key1", + "/test/key2", + "/test/sub/key3", + } + + for _, key := range keys { + arg := structs.KVSRequest{ + Datacenter: "dc1", + Op: structs.KVSSet, + DirEnt: structs.DirEntry{ + Key: key, + Flags: 1, + }, + } + var out bool + if err := client.Call("KVS.Apply", &arg, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + getR := structs.KeyListRequest{ + Datacenter: "dc1", + Prefix: "/test/", + Seperator: "/", + } + var dirent structs.IndexedKeyList + if err := client.Call("KVS.ListKeys", &getR, &dirent); err != nil { + t.Fatalf("err: %v", err) + } + + if dirent.Index == 0 { + t.Fatalf("Bad: %v", dirent) + } + if len(dirent.Keys) != 3 { + t.Fatalf("Bad: %v", dirent.Keys) + } + if dirent.Keys[0] != "/test/key1" { + t.Fatalf("Bad: %v", dirent.Keys) + } + if dirent.Keys[1] != "/test/key2" { + t.Fatalf("Bad: %v", dirent.Keys) + } + if dirent.Keys[2] != "/test/sub/" { + t.Fatalf("Bad: %v", dirent.Keys) + } + +} diff --git a/consul/structs/structs.go b/consul/structs/structs.go index 99740de50..2a672565f 100644 --- a/consul/structs/structs.go +++ b/consul/structs/structs.go @@ -313,11 +313,28 @@ func (r *KeyRequest) RequestDatacenter() string { return r.Datacenter } +// KeyListRequest is used to list keys +type KeyListRequest struct { + Datacenter string + Prefix string + Seperator string + QueryOptions +} + +func (r *KeyListRequest) RequestDatacenter() string { + return r.Datacenter +} + type IndexedDirEntries struct { Entries DirEntries QueryMeta } +type IndexedKeyList struct { + Keys []string + QueryMeta +} + // Decode is used to decode a MsgPack encoded object func Decode(buf []byte, out interface{}) error { var handle codec.MsgpackHandle