diff --git a/pkg/tools/etcd_tools.go b/pkg/tools/etcd_tools.go index b3fcb1facae..8f7ff6618a6 100644 --- a/pkg/tools/etcd_tools.go +++ b/pkg/tools/etcd_tools.go @@ -164,6 +164,12 @@ func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}, resourceVersi if err != nil { return err } + h.decodeNodeList(nodes, slicePtr) + return nil +} + +// decodeNodeList walks the tree of each node in the list and decodes into the specified object +func (h *EtcdHelper) decodeNodeList(nodes []*etcd.Node, slicePtr interface{}) error { pv := reflect.ValueOf(slicePtr) if pv.Type().Kind() != reflect.Ptr || pv.Type().Elem().Kind() != reflect.Slice { // This should not happen at runtime. @@ -171,16 +177,20 @@ func (h *EtcdHelper) ExtractList(key string, slicePtr interface{}, resourceVersi } v := pv.Elem() for _, node := range nodes { - obj := reflect.New(v.Type().Elem()) - err = h.Codec.DecodeInto([]byte(node.Value), obj.Interface().(runtime.Object)) - if h.ResourceVersioner != nil { - _ = h.ResourceVersioner.SetResourceVersion(obj.Interface().(runtime.Object), node.ModifiedIndex) - // being unable to set the version does not prevent the object from being extracted + if node.Dir { + h.decodeNodeList(node.Nodes, slicePtr) + } else { + obj := reflect.New(v.Type().Elem()) + err := h.Codec.DecodeInto([]byte(node.Value), obj.Interface().(runtime.Object)) + if h.ResourceVersioner != nil { + _ = h.ResourceVersioner.SetResourceVersion(obj.Interface().(runtime.Object), node.ModifiedIndex) + // being unable to set the version does not prevent the object from being extracted + } + if err != nil { + return err + } + v.Set(reflect.Append(v, obj.Elem())) } - if err != nil { - return err - } - v.Set(reflect.Append(v, obj.Elem())) } return nil } diff --git a/pkg/tools/etcd_tools_test.go b/pkg/tools/etcd_tools_test.go index 54c6432c05f..40918825690 100644 --- a/pkg/tools/etcd_tools_test.go +++ b/pkg/tools/etcd_tools_test.go @@ -108,6 +108,104 @@ func TestExtractToList(t *testing.T) { } } +// TestExtractToListAcrossDirectories ensures that the client excludes directories and flattens tree-response - simulates cross-namespace query +func TestExtractToListAcrossDirectories(t *testing.T) { + fakeClient := NewFakeEtcdClient(t) + fakeClient.Data["/some/key"] = EtcdResponseWithError{ + R: &etcd.Response{ + EtcdIndex: 10, + Node: &etcd.Node{ + Nodes: []*etcd.Node{ + { + Value: `{"id": "directory1"}`, + Dir: true, + Nodes: []*etcd.Node{ + { + Value: `{"id":"foo"}`, + ModifiedIndex: 1, + }, + }, + }, + { + Value: `{"id": "directory2"}`, + Dir: true, + Nodes: []*etcd.Node{ + { + Value: `{"id":"bar"}`, + ModifiedIndex: 2, + }, + }, + }, + }, + }, + }, + } + expect := api.PodList{ + JSONBase: api.JSONBase{ResourceVersion: 10}, + Items: []api.Pod{ + {JSONBase: api.JSONBase{ID: "foo", ResourceVersion: 1}}, + {JSONBase: api.JSONBase{ID: "bar", ResourceVersion: 2}}, + }, + } + + var got api.PodList + helper := EtcdHelper{fakeClient, latest.Codec, versioner} + err := helper.ExtractToList("/some/key", &got) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if e, a := expect, got; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %#v, got %#v", e, a) + } +} + +func TestExtractToListExcludesDirectories(t *testing.T) { + fakeClient := NewFakeEtcdClient(t) + fakeClient.Data["/some/key"] = EtcdResponseWithError{ + R: &etcd.Response{ + EtcdIndex: 10, + Node: &etcd.Node{ + Nodes: []*etcd.Node{ + { + Value: `{"id":"foo"}`, + ModifiedIndex: 1, + }, + { + Value: `{"id":"bar"}`, + ModifiedIndex: 2, + }, + { + Value: `{"id":"baz"}`, + ModifiedIndex: 3, + }, + { + Value: `{"id": "directory"}`, + Dir: true, + }, + }, + }, + }, + } + expect := api.PodList{ + JSONBase: api.JSONBase{ResourceVersion: 10}, + Items: []api.Pod{ + {JSONBase: api.JSONBase{ID: "foo", ResourceVersion: 1}}, + {JSONBase: api.JSONBase{ID: "bar", ResourceVersion: 2}}, + {JSONBase: api.JSONBase{ID: "baz", ResourceVersion: 3}}, + }, + } + + var got api.PodList + helper := EtcdHelper{fakeClient, latest.Codec, versioner} + err := helper.ExtractToList("/some/key", &got) + if err != nil { + t.Errorf("Unexpected error %v", err) + } + if e, a := expect, got; !reflect.DeepEqual(e, a) { + t.Errorf("Expected %#v, got %#v", e, a) + } +} + func TestExtractObj(t *testing.T) { fakeClient := NewFakeEtcdClient(t) expect := api.Pod{TypeMeta: api.TypeMeta{ID: "foo"}}