Skip to content

Commit 7b60362

Browse files
author
Nimrod Shneor
committed
Add support for implicit paging in un/structured clients
1 parent 9e78e65 commit 7b60362

File tree

4 files changed

+128
-0
lines changed

4 files changed

+128
-0
lines changed

pkg/client/client_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package client_test
1919
import (
2020
"context"
2121
"fmt"
22+
"strconv"
2223
"sync/atomic"
2324
"time"
2425

@@ -47,6 +48,14 @@ func deleteDeployment(ctx context.Context, dep *appsv1.Deployment, ns string) {
4748
}
4849
}
4950

51+
func deletePod(ctx context.Context, pod *corev1.Pod, ns string) {
52+
_, err := clientset.CoreV1().Pods(ns).Get(ctx, pod.Name, metav1.GetOptions{})
53+
if err == nil {
54+
err = clientset.CoreV1().Pods(ns).Delete(ctx, pod.Name, metav1.DeleteOptions{})
55+
Expect(err).NotTo(HaveOccurred())
56+
}
57+
}
58+
5059
func deleteNamespace(ctx context.Context, ns *corev1.Namespace) {
5160
ns, err := clientset.CoreV1().Namespaces().Get(ctx, ns.Name, metav1.GetOptions{})
5261
if err != nil {
@@ -2085,6 +2094,45 @@ var _ = Describe("Client", func() {
20852094
Expect(deps.Items[1].Name).To(Equal(dep4.Name))
20862095
}, serverSideTimeoutSeconds)
20872096

2097+
It("should list in pages large sets of objects using ListPages", func() {
2098+
buildPod := func(suffix string) *corev1.Pod {
2099+
return &corev1.Pod{
2100+
ObjectMeta: metav1.ObjectMeta{
2101+
Name: fmt.Sprintf("pod-%s", suffix),
2102+
},
2103+
Spec: corev1.PodSpec{
2104+
Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}},
2105+
},
2106+
}
2107+
}
2108+
2109+
By("creating 150 pods")
2110+
workLoad := 150
2111+
for workLoad > 0 {
2112+
pod := buildPod(strconv.Itoa(workLoad))
2113+
defer deletePod(ctx, pod, ns)
2114+
pod, err := clientset.
2115+
CoreV1().
2116+
Pods(ns).
2117+
Create(ctx, pod, metav1.CreateOptions{})
2118+
Expect(err).NotTo(HaveOccurred())
2119+
workLoad--
2120+
defer deletePod(ctx, pod, ns)
2121+
}
2122+
2123+
cl, err := client.New(cfg, client.Options{})
2124+
Expect(err).NotTo(HaveOccurred())
2125+
2126+
By("listing all pods with ListPages")
2127+
pods := &corev1.PodList{}
2128+
err = cl.ListPages(context.Background(), pods, func(obj client.ObjectList) error { return nil })
2129+
Expect(err).NotTo(HaveOccurred())
2130+
Expect(pods.Items).To(HaveLen(150))
2131+
Expect(pods.Continue).NotTo(BeEmpty())
2132+
Expect(pods.Items[0].Name).To(Equal("pod-1"))
2133+
2134+
}, serverSideTimeoutSeconds)
2135+
20882136
PIt("should fail if the object doesn't have meta", func() {
20892137

20902138
})

pkg/client/interfaces.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,13 @@ type Reader interface {
5555
// successful call, Items field in the list will be populated with the
5656
// result returned from the server.
5757
List(ctx context.Context, list ObjectList, opts ...ListOption) error
58+
59+
// Retrieves a list of objects in "chunks" (of size one hundred by default)
60+
// for a given namespace and list options.
61+
// One can pass a callback function to process each chunk recieved from the server.
62+
// On a successful call, Items field in the list will be populated with the
63+
// result returned from the server.
64+
ListPages(ctx context.Context, obj ObjectList, callback func(obj ObjectList) error, opts ...ListOption) error
5865
}
5966

6067
// Writer knows how to create, delete, and update Kubernetes objects.

pkg/client/options.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ import (
2323
"k8s.io/apimachinery/pkg/selection"
2424
)
2525

26+
const (
27+
// DefaultPageLimit represents the default limit used for ListPaging when no "Limit"
28+
// is specified as ListOption.
29+
DefaultPageLimit = 100
30+
)
31+
2632
// {{{ "Functional" Option Interfaces
2733

2834
// CreateOption is some configuration that modifies options for a create request.

pkg/client/typed_client.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package client
1919
import (
2020
"context"
2121

22+
apimeta "k8s.io/apimachinery/pkg/api/meta"
2223
"k8s.io/apimachinery/pkg/runtime"
2324
)
2425

@@ -143,6 +144,72 @@ func (c *typedClient) Get(ctx context.Context, key ObjectKey, obj Object) error
143144
Name(key.Name).Do(ctx).Into(obj)
144145
}
145146

147+
func (c *typedClient) ListPages(ctx context.Context, obj ObjectList,
148+
callback func(obj ObjectList) error, opts ...ListOption) error {
149+
r, err := c.cache.getResource(obj)
150+
if err != nil {
151+
return err
152+
}
153+
listOpts := ListOptions{}
154+
listOpts.ApplyOptions(opts)
155+
156+
// Fetch items at chunks of one hundred if not specified differently.
157+
if listOpts.Limit == 0 {
158+
Limit(DefaultPageLimit).ApplyToList(&listOpts)
159+
}
160+
161+
// Retrieve initial chunck of data.
162+
var allItems []runtime.Object
163+
var interimResult ObjectList
164+
err = r.Get().
165+
NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
166+
Resource(r.resource()).
167+
VersionedParams(listOpts.AsListOptions(), c.paramCodec).
168+
Do(ctx).Into(interimResult)
169+
if err != nil {
170+
return err
171+
}
172+
173+
if err := callback(interimResult); err != nil {
174+
return err
175+
}
176+
177+
items, err := apimeta.ExtractList(interimResult)
178+
if err != nil {
179+
return err
180+
}
181+
allItems = append(allItems, items...)
182+
183+
// Continue while there are more chunks.
184+
for {
185+
if interimResult.GetContinue() == "" {
186+
break
187+
}
188+
189+
Continue(interimResult.GetContinue()).ApplyToList(&listOpts)
190+
err = r.Get().
191+
NamespaceIfScoped(listOpts.Namespace, r.isNamespaced()).
192+
Resource(r.resource()).
193+
VersionedParams(listOpts.AsListOptions(), c.paramCodec).
194+
Do(ctx).Into(interimResult)
195+
if err != nil {
196+
return err
197+
}
198+
199+
if err := callback(interimResult); err != nil {
200+
return err
201+
}
202+
203+
items, err = apimeta.ExtractList(interimResult)
204+
if err != nil {
205+
return err
206+
}
207+
allItems = append(allItems, items...)
208+
}
209+
210+
return apimeta.SetList(obj, allItems)
211+
}
212+
146213
// List implements client.Client
147214
func (c *typedClient) List(ctx context.Context, obj ObjectList, opts ...ListOption) error {
148215
r, err := c.cache.getResource(obj)

0 commit comments

Comments
 (0)