Skip to content

Commit fb4659d

Browse files
Add Limit and Continue functional list options for client
1 parent fde7f38 commit fb4659d

File tree

2 files changed

+214
-1
lines changed

2 files changed

+214
-1
lines changed

pkg/client/client_test.go

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,6 +1430,130 @@ var _ = Describe("Client", func() {
14301430
close(done)
14311431
}, serverSideTimeoutSeconds)
14321432

1433+
It("should filter results using limit and continue options", func(done Done) {
1434+
By("creating 4 deployments")
1435+
dep1 := &appsv1.Deployment{
1436+
ObjectMeta: metav1.ObjectMeta{
1437+
Name: "deployment-1",
1438+
},
1439+
Spec: appsv1.DeploymentSpec{
1440+
Selector: &metav1.LabelSelector{
1441+
MatchLabels: map[string]string{"foo": "bar"},
1442+
},
1443+
Template: corev1.PodTemplateSpec{
1444+
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
1445+
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
1446+
},
1447+
},
1448+
}
1449+
dep1, err := clientset.AppsV1().Deployments(ns).Create(dep1)
1450+
Expect(err).NotTo(HaveOccurred())
1451+
dep2 := &appsv1.Deployment{
1452+
ObjectMeta: metav1.ObjectMeta{
1453+
Name: "deployment-2",
1454+
},
1455+
Spec: appsv1.DeploymentSpec{
1456+
Selector: &metav1.LabelSelector{
1457+
MatchLabels: map[string]string{"foo": "bar"},
1458+
},
1459+
Template: corev1.PodTemplateSpec{
1460+
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
1461+
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
1462+
},
1463+
},
1464+
}
1465+
dep2, err = clientset.AppsV1().Deployments(ns).Create(dep2)
1466+
Expect(err).NotTo(HaveOccurred())
1467+
dep3 := &appsv1.Deployment{
1468+
ObjectMeta: metav1.ObjectMeta{
1469+
Name: "deployment-3",
1470+
},
1471+
Spec: appsv1.DeploymentSpec{
1472+
Selector: &metav1.LabelSelector{
1473+
MatchLabels: map[string]string{"foo": "bar"},
1474+
},
1475+
Template: corev1.PodTemplateSpec{
1476+
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
1477+
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
1478+
},
1479+
},
1480+
}
1481+
dep3, err = clientset.AppsV1().Deployments(ns).Create(dep3)
1482+
Expect(err).NotTo(HaveOccurred())
1483+
dep4 := &appsv1.Deployment{
1484+
ObjectMeta: metav1.ObjectMeta{
1485+
Name: "deployment-4",
1486+
},
1487+
Spec: appsv1.DeploymentSpec{
1488+
Selector: &metav1.LabelSelector{
1489+
MatchLabels: map[string]string{"foo": "bar"},
1490+
},
1491+
Template: corev1.PodTemplateSpec{
1492+
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
1493+
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
1494+
},
1495+
},
1496+
}
1497+
dep4, err = clientset.AppsV1().Deployments(ns).Create(dep4)
1498+
Expect(err).NotTo(HaveOccurred())
1499+
1500+
cl, err := client.New(cfg, client.Options{})
1501+
Expect(err).NotTo(HaveOccurred())
1502+
1503+
By("listing 1 deployment when limit=1 is used")
1504+
deps := &appsv1.DeploymentList{}
1505+
err = cl.List(context.Background(), deps,
1506+
client.Limit(1),
1507+
)
1508+
Expect(err).NotTo(HaveOccurred())
1509+
1510+
Expect(deps.Items).NotTo(BeEmpty())
1511+
Expect(1).To(Equal(len(deps.Items)))
1512+
Expect(deps.Continue).NotTo(BeEmpty())
1513+
actual := deps.Items[0]
1514+
Expect(actual.Name).To(Equal(dep1.Name))
1515+
1516+
continueToken := deps.Continue
1517+
1518+
By("listing the next deployment when previous continuation token is used and limit=1")
1519+
deps = &appsv1.DeploymentList{}
1520+
err = cl.List(context.Background(), deps,
1521+
client.Limit(1),
1522+
client.Continue(continueToken),
1523+
)
1524+
Expect(err).NotTo(HaveOccurred())
1525+
1526+
Expect(deps.Items).NotTo(BeEmpty())
1527+
Expect(1).To(Equal(len(deps.Items)))
1528+
Expect(deps.Continue).NotTo(BeEmpty())
1529+
actual = deps.Items[0]
1530+
Expect(actual.Name).To(Equal(dep2.Name))
1531+
1532+
continueToken = deps.Continue
1533+
1534+
By("listing the 2 remaining deployments when previous continuation token is used without a limit")
1535+
deps = &appsv1.DeploymentList{}
1536+
err = cl.List(context.Background(), deps,
1537+
client.Continue(continueToken),
1538+
)
1539+
Expect(err).NotTo(HaveOccurred())
1540+
1541+
Expect(deps.Items).NotTo(BeEmpty())
1542+
Expect(2).To(Equal(len(deps.Items)))
1543+
Expect(deps.Continue).To(BeEmpty())
1544+
first := deps.Items[0]
1545+
Expect(first.Name).To(Equal(dep3.Name))
1546+
second := deps.Items[1]
1547+
Expect(second.Name).To(Equal(dep4.Name))
1548+
1549+
deleteDeployment(dep1, ns)
1550+
deleteDeployment(dep2, ns)
1551+
deleteDeployment(dep3, ns)
1552+
deleteDeployment(dep4, ns)
1553+
1554+
close(done)
1555+
}, serverSideTimeoutSeconds)
1556+
14331557
PIt("should fail if the object doesn't have meta", func() {
14341558

14351559
})
@@ -1838,6 +1962,50 @@ var _ = Describe("Client", func() {
18381962
Expect(lo.Namespace).To(Equal("test"))
18391963
})
18401964

1965+
It("should be able to set Limit", func() {
1966+
lo := &client.ListOptions{}
1967+
lo = lo.SetLimit(1)
1968+
Expect(lo.Limit).To(Equal(int64(1)))
1969+
})
1970+
1971+
It("should be created from Limit", func() {
1972+
lo := &client.ListOptions{}
1973+
client.Limit(1)(lo)
1974+
Expect(lo).NotTo(BeNil())
1975+
Expect(lo.Limit).To(Equal(int64(1)))
1976+
})
1977+
1978+
It("should ignore Limit when converted to metav1.ListOptions and watch is true", func() {
1979+
lo := &client.ListOptions{
1980+
Raw: &metav1.ListOptions{Watch: true},
1981+
}
1982+
mlo := lo.SetLimit(1).AsListOptions()
1983+
Expect(mlo).NotTo(BeNil())
1984+
Expect(mlo.Limit).To(BeZero())
1985+
})
1986+
1987+
It("should be able to set Continue token", func() {
1988+
lo := &client.ListOptions{}
1989+
lo = lo.SetContinueToken("foo")
1990+
Expect(lo.Continue).To(Equal("foo"))
1991+
})
1992+
1993+
It("should be created from Continue", func() {
1994+
lo := &client.ListOptions{}
1995+
client.Continue("foo")(lo)
1996+
Expect(lo).NotTo(BeNil())
1997+
Expect(lo.Continue).To(Equal("foo"))
1998+
})
1999+
2000+
It("should ignore Continue token when converted to metav1.ListOptions and watch is true", func() {
2001+
lo := &client.ListOptions{
2002+
Raw: &metav1.ListOptions{Watch: true},
2003+
}
2004+
mlo := lo.SetContinueToken("foo").AsListOptions()
2005+
Expect(mlo).NotTo(BeNil())
2006+
Expect(mlo.Limit).To(BeZero())
2007+
})
2008+
18412009
It("should allow pre-built ListOptions", func() {
18422010
lo := &client.ListOptions{}
18432011
newLo := &client.ListOptions{}

pkg/client/interfaces.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,20 @@ type ListOptions struct {
205205
// non-namespaced objects, or to list across all namespaces.
206206
Namespace string
207207

208+
// Limit specifies the maximum number of results to return from the server. The server may
209+
// not support this field on all resource types, but if it does and more results remain it
210+
// will set the continue field on the returned list object. This field is not supported if watch
211+
// is true in the Raw ListOptions.
212+
Limit int64
213+
// Continue is a token returned by the server that lets a client retrieve chunks of results
214+
// from the server by specifying limit. The server may reject requests for continuation tokens
215+
// it does not recognize and will return a 410 error if the token can no longer be used because
216+
// it has expired. This field is not supported if watch is true in the Raw ListOptions.
217+
Continue string
218+
208219
// Raw represents raw ListOptions, as passed to the API server. Note
209220
// that these may not be respected by all implementations of interface,
210-
// and the LabelSelector and FieldSelector fields are ignored.
221+
// and the LabelSelector, FieldSelector, Limit and Continue fields are ignored.
211222
Raw *metav1.ListOptions
212223
}
213224

@@ -248,6 +259,10 @@ func (o *ListOptions) AsListOptions() *metav1.ListOptions {
248259
if o.FieldSelector != nil {
249260
o.Raw.FieldSelector = o.FieldSelector.String()
250261
}
262+
if !o.Raw.Watch {
263+
o.Raw.Limit = o.Limit
264+
o.Raw.Continue = o.Continue
265+
}
251266
return o.Raw
252267
}
253268

@@ -290,6 +305,20 @@ func (o *ListOptions) InNamespace(ns string) *ListOptions {
290305
return o
291306
}
292307

308+
// SetLimit is a convenience function that sets the limit, and
309+
// then returns the options. It mutates the list options.
310+
func (o *ListOptions) SetLimit(n int64) *ListOptions {
311+
o.Limit = n
312+
return o
313+
}
314+
315+
// SetContinueToken is a convenience function that sets the continuation
316+
// token, and then returns the options. It mutates the list options.
317+
func (o *ListOptions) SetContinueToken(token string) *ListOptions {
318+
o.Continue = token
319+
return o
320+
}
321+
293322
// MatchingLabels is a functional option that sets the LabelSelector field of
294323
// a ListOptions struct.
295324
func MatchingLabels(lbls map[string]string) ListOptionFunc {
@@ -316,6 +345,22 @@ func InNamespace(ns string) ListOptionFunc {
316345
}
317346
}
318347

348+
// Limit is a functional option that sets the Limit field of
349+
// a ListOptions struct.
350+
func Limit(n int64) ListOptionFunc {
351+
return func(opts *ListOptions) {
352+
opts.Limit = n
353+
}
354+
}
355+
356+
// Continue is a functional option that sets the Continue field of
357+
// a ListOptions struct.
358+
func Continue(token string) ListOptionFunc {
359+
return func(opts *ListOptions) {
360+
opts.Continue = token
361+
}
362+
}
363+
319364
// UseListOptions is a functional option that replaces the fields of a
320365
// ListOptions struct with those of a different ListOptions struct.
321366
//

0 commit comments

Comments
 (0)