Skip to content

Commit

Permalink
Add Limit and Continue functional list options for client
Browse files Browse the repository at this point in the history
  • Loading branch information
rajathagasthya committed Feb 27, 2019
1 parent fde7f38 commit fce0993
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 1 deletion.
168 changes: 168 additions & 0 deletions pkg/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1430,6 +1430,130 @@ var _ = Describe("Client", func() {
close(done)
}, serverSideTimeoutSeconds)

It("should filter results using limit and continue options", func(done Done) {
By("creating 4 deployments")
dep1 := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "deployment-1",
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
},
},
}
dep1, err := clientset.AppsV1().Deployments(ns).Create(dep1)
Expect(err).NotTo(HaveOccurred())
dep2 := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "deployment-2",
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
},
},
}
dep2, err = clientset.AppsV1().Deployments(ns).Create(dep2)
Expect(err).NotTo(HaveOccurred())
dep3 := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "deployment-3",
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
},
},
}
dep3, err = clientset.AppsV1().Deployments(ns).Create(dep3)
Expect(err).NotTo(HaveOccurred())
dep4 := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "deployment-4",
},
Spec: appsv1.DeploymentSpec{
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{"foo": "bar"},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "nginx", Image: "nginx"}}},
},
},
}
dep4, err = clientset.AppsV1().Deployments(ns).Create(dep4)
Expect(err).NotTo(HaveOccurred())

cl, err := client.New(cfg, client.Options{})
Expect(err).NotTo(HaveOccurred())

By("listing 1 deployment when limit=1 is used")
deps := &appsv1.DeploymentList{}
err = cl.List(context.Background(), deps,
client.Limit(1),
)
Expect(err).NotTo(HaveOccurred())

Expect(deps.Items).NotTo(BeEmpty())
Expect(1).To(Equal(len(deps.Items)))
Expect(deps.Continue).NotTo(BeEmpty())
actual := deps.Items[0]
Expect(actual.Name).To(Equal(dep1.Name))

continueToken := deps.Continue

By("listing the next deployment when previous continuation token is used and limit=1")
deps = &appsv1.DeploymentList{}
err = cl.List(context.Background(), deps,
client.Limit(1),
client.Continue(continueToken),
)
Expect(err).NotTo(HaveOccurred())

Expect(deps.Items).NotTo(BeEmpty())
Expect(1).To(Equal(len(deps.Items)))
Expect(deps.Continue).NotTo(BeEmpty())
actual = deps.Items[0]
Expect(actual.Name).To(Equal(dep2.Name))

continueToken = deps.Continue

By("listing the 2 remaining deployments when previous continuation token is used without a limit")
deps = &appsv1.DeploymentList{}
err = cl.List(context.Background(), deps,
client.Continue(continueToken),
)
Expect(err).NotTo(HaveOccurred())

Expect(deps.Items).NotTo(BeEmpty())
Expect(2).To(Equal(len(deps.Items)))
Expect(deps.Continue).To(BeEmpty())
first := deps.Items[0]
Expect(first.Name).To(Equal(dep3.Name))
second := deps.Items[1]
Expect(second.Name).To(Equal(dep4.Name))

deleteDeployment(dep1, ns)
deleteDeployment(dep2, ns)
deleteDeployment(dep3, ns)
deleteDeployment(dep4, ns)

close(done)
}, serverSideTimeoutSeconds)

PIt("should fail if the object doesn't have meta", func() {

})
Expand Down Expand Up @@ -1838,6 +1962,50 @@ var _ = Describe("Client", func() {
Expect(lo.Namespace).To(Equal("test"))
})

It("should be able to set Limit", func() {
lo := &client.ListOptions{}
lo = lo.SetLimit(1)
Expect(lo.Limit).To(Equal(int64(1)))
})

It("should be created from Limit", func() {
lo := &client.ListOptions{}
client.Limit(1)(lo)
Expect(lo).NotTo(BeNil())
Expect(lo.Limit).To(Equal(int64(1)))
})

It("should ignore Limit when converted to metav1.ListOptions and watch is true", func() {
lo := &client.ListOptions{
Raw: &metav1.ListOptions{Watch: true},
}
mlo := lo.SetLimit(1).AsListOptions()
Expect(mlo).NotTo(BeNil())
Expect(mlo.Limit).To(BeZero())
})

It("should be able to set Continue token", func() {
lo := &client.ListOptions{}
lo = lo.SetContinueToken("foo")
Expect(lo.Continue).To(Equal("foo"))
})

It("should be created from Continue", func() {
lo := &client.ListOptions{}
client.Continue("foo")(lo)
Expect(lo).NotTo(BeNil())
Expect(lo.Continue).To(Equal("foo"))
})

It("should ignore Continue token when converted to metav1.ListOptions and watch is true", func() {
lo := &client.ListOptions{
Raw: &metav1.ListOptions{Watch: true},
}
mlo := lo.SetContinueToken("foo").AsListOptions()
Expect(mlo).NotTo(BeNil())
Expect(mlo.Limit).To(BeZero())
})

It("should allow pre-built ListOptions", func() {
lo := &client.ListOptions{}
newLo := &client.ListOptions{}
Expand Down
47 changes: 46 additions & 1 deletion pkg/client/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,20 @@ type ListOptions struct {
// non-namespaced objects, or to list across all namespaces.
Namespace string

// Limit specifies the maximum number of results to return from the server. The server may
// not support this field on all resource types, but if it does and more results remain it
// will set the continue field on the returned list object. This field is not supported if watch
// is true in the Raw ListOptions.
Limit int64
// Continue is a token returned by the server that lets a client retrieve chunks of results
// from the server by specifying limit. The server may reject requests for continuation tokens
// it does not recognize and will return a 410 error if the token can no longer be used because
// it has expired. This field is not supported if watch is true in the Raw ListOptions.
Continue string

// Raw represents raw ListOptions, as passed to the API server. Note
// that these may not be respected by all implementations of interface,
// and the LabelSelector and FieldSelector fields are ignored.
// and the LabelSelector, FieldSelector, Limit and Continue fields are ignored.
Raw *metav1.ListOptions
}

Expand Down Expand Up @@ -248,6 +259,10 @@ func (o *ListOptions) AsListOptions() *metav1.ListOptions {
if o.FieldSelector != nil {
o.Raw.FieldSelector = o.FieldSelector.String()
}
if !o.Raw.Watch {
o.Raw.Limit = o.Limit
o.Raw.Continue = o.Continue
}
return o.Raw
}

Expand Down Expand Up @@ -290,6 +305,20 @@ func (o *ListOptions) InNamespace(ns string) *ListOptions {
return o
}

// SetLimit is a convenience function that sets the limit, and
// then returns the options. It mutates the list options.
func (o *ListOptions) SetLimit(n int64) *ListOptions {
o.Limit = n
return o
}

// SetContinueToken is a convenience function that sets the continuation
// token, and then returns the options. It mutates the list options.
func (o *ListOptions) SetContinueToken(token string) *ListOptions {
o.Continue = token
return o
}

// MatchingLabels is a functional option that sets the LabelSelector field of
// a ListOptions struct.
func MatchingLabels(lbls map[string]string) ListOptionFunc {
Expand All @@ -316,6 +345,22 @@ func InNamespace(ns string) ListOptionFunc {
}
}

// Limit is a functional option that sets the Limit field of
// a ListOptions struct.
func Limit(n int64) ListOptionFunc {
return func(opts *ListOptions) {
opts.Limit = n
}
}

// Continue is a functional option that sets the Continue field of
// a ListOptions struct.
func Continue(token string) ListOptionFunc {
return func(opts *ListOptions) {
opts.Continue = token
}
}

// UseListOptions is a functional option that replaces the fields of a
// ListOptions struct with those of a different ListOptions struct.
//
Expand Down

0 comments on commit fce0993

Please sign in to comment.