Skip to content

Commit a5965da

Browse files
Add Limit and Continue functional list options for client
1 parent 72ab3fe commit a5965da

File tree

2 files changed

+161
-1
lines changed

2 files changed

+161
-1
lines changed

pkg/client/client_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,89 @@ var _ = Describe("Client", func() {
17051705
close(done)
17061706
}, serverSideTimeoutSeconds)
17071707

1708+
It("should filter results using limit and continue options", func() {
1709+
1710+
makeDeployment := func(suffix string) *appsv1.Deployment {
1711+
return &appsv1.Deployment{
1712+
ObjectMeta: metav1.ObjectMeta{
1713+
Name: fmt.Sprintf("deployment-%s", suffix),
1714+
},
1715+
Spec: appsv1.DeploymentSpec{
1716+
Selector: &metav1.LabelSelector{
1717+
MatchLabels: map[string]string{"foo": "bar"},
1718+
},
1719+
Template: corev1.PodTemplateSpec{
1720+
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
1721+
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
1722+
},
1723+
},
1724+
}
1725+
}
1726+
1727+
By("creating 4 deployments")
1728+
dep1 := makeDeployment("1")
1729+
dep1, err := clientset.AppsV1().Deployments(ns).Create(dep1)
1730+
Expect(err).NotTo(HaveOccurred())
1731+
defer deleteDeployment(dep1, ns)
1732+
1733+
dep2 := makeDeployment("2")
1734+
dep2, err = clientset.AppsV1().Deployments(ns).Create(dep2)
1735+
Expect(err).NotTo(HaveOccurred())
1736+
defer deleteDeployment(dep2, ns)
1737+
1738+
dep3 := makeDeployment("3")
1739+
dep3, err = clientset.AppsV1().Deployments(ns).Create(dep3)
1740+
Expect(err).NotTo(HaveOccurred())
1741+
defer deleteDeployment(dep3, ns)
1742+
1743+
dep4 := makeDeployment("4")
1744+
dep4, err = clientset.AppsV1().Deployments(ns).Create(dep4)
1745+
Expect(err).NotTo(HaveOccurred())
1746+
defer deleteDeployment(dep4, ns)
1747+
1748+
cl, err := client.New(cfg, client.Options{})
1749+
Expect(err).NotTo(HaveOccurred())
1750+
1751+
By("listing 1 deployment when limit=1 is used")
1752+
deps := &appsv1.DeploymentList{}
1753+
err = cl.List(context.Background(), deps,
1754+
client.Limit(1),
1755+
)
1756+
Expect(err).NotTo(HaveOccurred())
1757+
1758+
Expect(deps.Items).To(HaveLen(1))
1759+
Expect(deps.Continue).NotTo(BeEmpty())
1760+
Expect(deps.Items[0].Name).To(Equal(dep1.Name))
1761+
1762+
continueToken := deps.Continue
1763+
1764+
By("listing the next deployment when previous continuation token is used and limit=1")
1765+
deps = &appsv1.DeploymentList{}
1766+
err = cl.List(context.Background(), deps,
1767+
client.Limit(1),
1768+
client.Continue(continueToken),
1769+
)
1770+
Expect(err).NotTo(HaveOccurred())
1771+
1772+
Expect(deps.Items).To(HaveLen(1))
1773+
Expect(deps.Continue).NotTo(BeEmpty())
1774+
Expect(deps.Items[0].Name).To(Equal(dep2.Name))
1775+
1776+
continueToken = deps.Continue
1777+
1778+
By("listing the 2 remaining deployments when previous continuation token is used without a limit")
1779+
deps = &appsv1.DeploymentList{}
1780+
err = cl.List(context.Background(), deps,
1781+
client.Continue(continueToken),
1782+
)
1783+
Expect(err).NotTo(HaveOccurred())
1784+
1785+
Expect(deps.Items).To(HaveLen(2))
1786+
Expect(deps.Continue).To(BeEmpty())
1787+
Expect(deps.Items[0].Name).To(Equal(dep3.Name))
1788+
Expect(deps.Items[1].Name).To(Equal(dep4.Name))
1789+
}, serverSideTimeoutSeconds)
1790+
17081791
PIt("should fail if the object doesn't have meta", func() {
17091792

17101793
})
@@ -2129,6 +2212,52 @@ var _ = Describe("Client", func() {
21292212
Expect(lo.Namespace).To(Equal("test"))
21302213
})
21312214

2215+
It("should be created from Limit", func() {
2216+
lo := &client.ListOptions{}
2217+
client.Limit(1)(lo)
2218+
Expect(lo).NotTo(BeNil())
2219+
Expect(lo.Limit).To(Equal(int64(1)))
2220+
})
2221+
2222+
It("should ignore Limit when converted to metav1.ListOptions and watch is true", func() {
2223+
lo := &client.ListOptions{
2224+
Limit: 1,
2225+
Raw: &metav1.ListOptions{Watch: true},
2226+
}
2227+
mlo := lo.AsListOptions()
2228+
Expect(mlo).NotTo(BeNil())
2229+
Expect(mlo.Limit).To(BeZero())
2230+
})
2231+
2232+
It("should be created from Continue", func() {
2233+
lo := &client.ListOptions{}
2234+
client.Continue("foo")(lo)
2235+
Expect(lo).NotTo(BeNil())
2236+
Expect(lo.Continue).To(Equal("foo"))
2237+
})
2238+
2239+
It("should ignore Continue token when converted to metav1.ListOptions and watch is true", func() {
2240+
lo := &client.ListOptions{
2241+
Continue: "foo",
2242+
Raw: &metav1.ListOptions{Watch: true},
2243+
}
2244+
mlo := lo.AsListOptions()
2245+
Expect(mlo).NotTo(BeNil())
2246+
Expect(mlo.Continue).To(BeEmpty())
2247+
})
2248+
2249+
It("should ignore both Limit and Continue token when converted to metav1.ListOptions and watch is true", func() {
2250+
lo := &client.ListOptions{
2251+
Limit: 1,
2252+
Continue: "foo",
2253+
Raw: &metav1.ListOptions{Watch: true},
2254+
}
2255+
mlo := lo.AsListOptions()
2256+
Expect(mlo).NotTo(BeNil())
2257+
Expect(mlo.Limit).To(BeZero())
2258+
Expect(mlo.Continue).To(BeEmpty())
2259+
})
2260+
21322261
It("should allow pre-built ListOptions", func() {
21332262
lo := &client.ListOptions{}
21342263
newLo := &client.ListOptions{}

pkg/client/options.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,9 +169,20 @@ type ListOptions struct {
169169
// non-namespaced objects, or to list across all namespaces.
170170
Namespace string
171171

172+
// Limit specifies the maximum number of results to return from the server. The server may
173+
// not support this field on all resource types, but if it does and more results remain it
174+
// will set the continue field on the returned list object. This field is not supported if watch
175+
// is true in the Raw ListOptions.
176+
Limit int64
177+
// Continue is a token returned by the server that lets a client retrieve chunks of results
178+
// from the server by specifying limit. The server may reject requests for continuation tokens
179+
// it does not recognize and will return a 410 error if the token can no longer be used because
180+
// it has expired. This field is not supported if watch is true in the Raw ListOptions.
181+
Continue string
182+
172183
// Raw represents raw ListOptions, as passed to the API server. Note
173184
// that these may not be respected by all implementations of interface,
174-
// and the LabelSelector and FieldSelector fields are ignored.
185+
// and the LabelSelector, FieldSelector, Limit and Continue fields are ignored.
175186
Raw *metav1.ListOptions
176187
}
177188

@@ -212,6 +223,10 @@ func (o *ListOptions) AsListOptions() *metav1.ListOptions {
212223
if o.FieldSelector != nil {
213224
o.Raw.FieldSelector = o.FieldSelector.String()
214225
}
226+
if !o.Raw.Watch {
227+
o.Raw.Limit = o.Limit
228+
o.Raw.Continue = o.Continue
229+
}
215230
return o.Raw
216231
}
217232

@@ -280,6 +295,22 @@ func InNamespace(ns string) ListOptionFunc {
280295
}
281296
}
282297

298+
// Limit is a functional option that sets the Limit field of
299+
// a ListOptions struct.
300+
func Limit(n int64) ListOptionFunc {
301+
return func(opts *ListOptions) {
302+
opts.Limit = n
303+
}
304+
}
305+
306+
// Continue is a functional option that sets the Continue field of
307+
// a ListOptions struct.
308+
func Continue(token string) ListOptionFunc {
309+
return func(opts *ListOptions) {
310+
opts.Continue = token
311+
}
312+
}
313+
283314
// UseListOptions is a functional option that replaces the fields of a
284315
// ListOptions struct with those of a different ListOptions struct.
285316
//

0 commit comments

Comments
 (0)