Skip to content
This repository was archived by the owner on Oct 12, 2023. It is now read-only.

Commit d256ec3

Browse files
Allow for passing in skip and top parameters to calls that list subscriptions, queues and topics #234
The current implementation that lists resources (using ATOM) doesn't allow passing in skip, which would allow you to list more than the default page size of 100 items. This PR adds in skip (and top, which controls the size of the page) for listing queues, topics and subscriptions which allows you, over multiple calls and incrementing skip, to get the list of all entities. Fixes #231
2 parents 80fa1bc + 82d7a09 commit d256ec3

File tree

10 files changed

+256
-10
lines changed

10 files changed

+256
-10
lines changed

Makefile

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ test-race: ARGS=-race ## Run tests with race detector
4040
test-cover: ARGS=-cover -coverprofile=cover.out -v ## Run tests in verbose mode with coverage
4141
$(TEST_TARGETS): NAME=$(MAKECMDGOALS:test-%=%)
4242
$(TEST_TARGETS): test
43-
check test tests: cyclo lint vet terraform.tfstate; $(info $(M) running $(NAME:%=% )tests…) @ ## Run tests
44-
$Q cd $(BASE) && $(GO) test -timeout $(TIMEOUT)s $(ARGS) $(TESTPKGS) 2>&1 | $(GOJUNITRPT) > report.xml
43+
check test tests: lint vet terraform.tfstate; $(info $(M) running $(NAME:%=% )tests…) @ ## Run tests
44+
$Q cd $(BASE) && \
45+
$(GO) test -timeout $(TIMEOUT)s $(ARGS) $(TESTPKGS) 2>&1 | tee gotestoutput.log && \
46+
$(GOJUNITRPT) < gotestoutput.log > report.xml && \
47+
rm -f gotestoutput.log
4548

4649
.PHONY: vet
4750
vet: $(GOLINT) ; $(info $(M) running vet…) @ ## Run vet

azuredeploy.tf

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,14 @@ output "AZURE_TENANT_ID" {
154154
}
155155

156156
output "AZURE_CLIENT_ID" {
157-
value = compact(concat(azuread_application.test.*.application_id, list(data.azurerm_client_config.current.client_id)))[0]
157+
value = compact(
158+
concat(azuread_application.test.*.application_id, [data.azurerm_client_config.current.client_id])
159+
)[0]
158160
}
159161

160162
output "AZURE_CLIENT_SECRET" {
161-
value = compact(concat(azuread_service_principal_password.test.*.value, list(var.azure_client_secret)))[0]
163+
value = compact(
164+
concat(azuread_service_principal_password.test.*.value, [var.azure_client_secret])
165+
)[0]
162166
sensitive = true
163167
}

internal/manager_common.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
)
7+
8+
// ConstructAtomPath adds the proper parameters for skip and top
9+
// This is common for the list operations for queues, topics and subscriptions.
10+
func ConstructAtomPath(basePath string, skip int, top int) string {
11+
values := url.Values{}
12+
13+
if skip > 0 {
14+
values.Add("$skip", fmt.Sprintf("%d", skip))
15+
}
16+
17+
if top > 0 {
18+
values.Add("$top", fmt.Sprintf("%d", top))
19+
}
20+
21+
if len(values) == 0 {
22+
return basePath
23+
}
24+
25+
return fmt.Sprintf("%s?%s", basePath, values.Encode())
26+
}

internal/manager_common_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package internal
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestConstructAtomPath(t *testing.T) {
10+
basePath := ConstructAtomPath("/something", 1, 2)
11+
12+
// I'm assuming the ordering is non-deterministic since the underlying values are just a map
13+
assert.Truef(t, basePath == "/something?%24skip=1&%24top=2" || basePath == "/something?%24top=2&%24skip=1", "%s wasn't one of our two variations", basePath)
14+
15+
basePath = ConstructAtomPath("/something", 0, -1)
16+
assert.EqualValues(t, "/something", basePath, "Values <= 0 are ignored")
17+
18+
basePath = ConstructAtomPath("/something", -1, 0)
19+
assert.EqualValues(t, "/something", basePath, "Values <= 0 are ignored")
20+
}

queue_manager.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/devigned/tab"
1414

1515
"github.com/Azure/azure-service-bus-go/atom"
16+
"github.com/Azure/azure-service-bus-go/internal"
1617
)
1718

1819
type (
@@ -54,6 +55,34 @@ type (
5455
}
5556
)
5657

58+
type (
59+
// ListQueuesOptions provides options for List() to control things like page size.
60+
// NOTE: Use the ListQueuesWith* methods to specify this.
61+
ListQueuesOptions struct {
62+
top int
63+
skip int
64+
}
65+
66+
// ListQueuesOption represents named options for listing topics
67+
ListQueuesOption func(*ListQueuesOptions) error
68+
)
69+
70+
// ListQueuesWithSkip will skip the specified number of entities
71+
func ListQueuesWithSkip(skip int) ListQueuesOption {
72+
return func(options *ListQueuesOptions) error {
73+
options.skip = skip
74+
return nil
75+
}
76+
}
77+
78+
// ListQueuesWithTop will return at most `top` results
79+
func ListQueuesWithTop(top int) ListQueuesOption {
80+
return func(options *ListQueuesOptions) error {
81+
options.top = top
82+
return nil
83+
}
84+
}
85+
5786
// TargetURI provides an absolute address to a target entity
5887
func (e Entity) TargetURI() string {
5988
split := strings.Split(e.ID, "?")
@@ -300,11 +329,21 @@ func (qm *QueueManager) Put(ctx context.Context, name string, opts ...QueueManag
300329
}
301330

302331
// List fetches all of the queues for a Service Bus Namespace
303-
func (qm *QueueManager) List(ctx context.Context) ([]*QueueEntity, error) {
332+
func (qm *QueueManager) List(ctx context.Context, options ...ListQueuesOption) ([]*QueueEntity, error) {
304333
ctx, span := qm.startSpanFromContext(ctx, "sb.QueueManager.List")
305334
defer span.End()
306335

307-
res, err := qm.entityManager.Get(ctx, `/$Resources/Queues`)
336+
listQueuesOptions := ListQueuesOptions{}
337+
338+
for _, option := range options {
339+
if err := option(&listQueuesOptions); err != nil {
340+
return nil, err
341+
}
342+
}
343+
344+
basePath := internal.ConstructAtomPath(`/$Resources/Queues`, listQueuesOptions.skip, listQueuesOptions.top)
345+
346+
res, err := qm.entityManager.Get(ctx, basePath)
308347
defer closeRes(ctx, res)
309348

310349
if err != nil {

queue_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,22 @@ func testListQueues(ctx context.Context, t *testing.T, qm *QueueManager, names [
308308
for _, name := range names {
309309
assert.Contains(t, queueNames, name)
310310
}
311+
312+
// there should be at least two entities but there could be others if the service isn't clean (which is fine)
313+
firstSet, err := qm.List(ctx, ListQueuesWithSkip(0), ListQueuesWithTop(1))
314+
assert.NoError(t, err)
315+
assert.EqualValues(t, 1, len(firstSet))
316+
317+
secondSet, err := qm.List(ctx, ListQueuesWithSkip(1), ListQueuesWithTop(1))
318+
assert.NoError(t, err)
319+
assert.EqualValues(t, 1, len(secondSet))
320+
321+
// sanity check - we didn't just retrieve the same entity twice.
322+
assert.NotEqualValues(t, firstSet[0].Name, secondSet[0].Name)
323+
324+
lastSet, err := qm.List(ctx, ListQueuesWithSkip(0), ListQueuesWithTop(2))
325+
assert.NoError(t, err)
326+
assert.EqualValues(t, 2, len(lastSet))
311327
}
312328

313329
func (suite *serviceBusSuite) randEntityName() string {

subscription_manager.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/devigned/tab"
1616

1717
"github.com/Azure/azure-service-bus-go/atom"
18+
"github.com/Azure/azure-service-bus-go/internal"
1819
)
1920

2021
type (
@@ -156,6 +157,34 @@ type (
156157
SubscriptionManagementOption func(*SubscriptionDescription) error
157158
)
158159

160+
type (
161+
// ListSubscriptionsOptions provides options for List() to control things like page size.
162+
// NOTE: Use the ListSubscriptionsWith* methods to specify this.
163+
ListSubscriptionsOptions struct {
164+
top int
165+
skip int
166+
}
167+
168+
//ListSubscriptionsOption represents named options for listing topics
169+
ListSubscriptionsOption func(*ListSubscriptionsOptions) error
170+
)
171+
172+
// ListSubscriptionsWithSkip will skip the specified number of entities
173+
func ListSubscriptionsWithSkip(skip int) ListSubscriptionsOption {
174+
return func(options *ListSubscriptionsOptions) error {
175+
options.skip = skip
176+
return nil
177+
}
178+
}
179+
180+
// ListSubscriptionsWithTop will return at most `top` results
181+
func ListSubscriptionsWithTop(top int) ListSubscriptionsOption {
182+
return func(options *ListSubscriptionsOptions) error {
183+
options.top = top
184+
return nil
185+
}
186+
}
187+
159188
// NewSubscriptionManager creates a new SubscriptionManager for a Service Bus Topic
160189
func (t *Topic) NewSubscriptionManager() *SubscriptionManager {
161190
return &SubscriptionManager{
@@ -249,11 +278,21 @@ func (sm *SubscriptionManager) Put(ctx context.Context, name string, opts ...Sub
249278
}
250279

251280
// List fetches all of the Topics for a Service Bus Namespace
252-
func (sm *SubscriptionManager) List(ctx context.Context) ([]*SubscriptionEntity, error) {
281+
func (sm *SubscriptionManager) List(ctx context.Context, options ...ListSubscriptionsOption) ([]*SubscriptionEntity, error) {
253282
ctx, span := sm.startSpanFromContext(ctx, "sb.SubscriptionManager.List")
254283
defer span.End()
255284

256-
res, err := sm.entityManager.Get(ctx, "/"+sm.Topic.Name+"/subscriptions")
285+
listSubscriptionsOptions := ListSubscriptionsOptions{}
286+
287+
for _, option := range options {
288+
if err := option(&listSubscriptionsOptions); err != nil {
289+
return nil, err
290+
}
291+
}
292+
293+
basePath := internal.ConstructAtomPath("/"+sm.Topic.Name+"/subscriptions", listSubscriptionsOptions.skip, listSubscriptionsOptions.top)
294+
295+
res, err := sm.entityManager.Get(ctx, basePath)
257296
defer closeRes(ctx, res)
258297

259298
if err != nil {

subscription_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,50 @@ func (suite *serviceBusSuite) TestSubscriptionManagement() {
245245
suite.testSubscriptionManager(tests)
246246
}
247247

248+
func (suite *serviceBusSuite) TestSubscriptionManagementReads() {
249+
tests := map[string]func(ctx context.Context, t *testing.T, sm *SubscriptionManager, topicName, name string){
250+
"TestListSubscriptions": testListSubscriptions,
251+
}
252+
253+
suite.testSubscriptionManager(tests)
254+
}
255+
256+
func testListSubscriptions(ctx context.Context, t *testing.T, sm *SubscriptionManager, _, name string) {
257+
names := []string{name + "-1", name + "-2"}
258+
259+
for _, name := range names {
260+
buildSubscription(ctx, t, sm, name)
261+
}
262+
263+
subs, err := sm.List(ctx)
264+
assert.Nil(t, err)
265+
assert.NotNil(t, subs)
266+
subNames := make([]string, len(subs))
267+
for idx, s := range subs {
268+
subNames[idx] = s.Name
269+
}
270+
271+
for _, name := range names {
272+
assert.Contains(t, subNames, name)
273+
}
274+
275+
// there should be at least two entities but there could be others if the service isn't clean (which is fine)
276+
firstSet, err := sm.List(ctx, ListSubscriptionsWithSkip(0), ListSubscriptionsWithTop(1))
277+
assert.NoError(t, err)
278+
assert.EqualValues(t, 1, len(firstSet))
279+
280+
secondSet, err := sm.List(ctx, ListSubscriptionsWithSkip(1), ListSubscriptionsWithTop(1))
281+
assert.NoError(t, err)
282+
assert.EqualValues(t, 1, len(secondSet))
283+
284+
// sanity check - we didn't just retrieve the same entity twice.
285+
assert.NotEqualValues(t, firstSet[0].Name, secondSet[0].Name)
286+
287+
lastSet, err := sm.List(ctx, ListSubscriptionsWithSkip(0), ListSubscriptionsWithTop(2))
288+
assert.NoError(t, err)
289+
assert.EqualValues(t, 2, len(lastSet))
290+
}
291+
248292
func testDefaultSubscription(ctx context.Context, t *testing.T, sm *SubscriptionManager, _, name string) {
249293
s := buildSubscription(ctx, t, sm, name)
250294
assert.False(t, *s.DeadLetteringOnMessageExpiration, "should not have dead lettering on expiration")

topic_manager.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/devigned/tab"
1313

1414
"github.com/Azure/azure-service-bus-go/atom"
15+
"github.com/Azure/azure-service-bus-go/internal"
1516
)
1617

1718
type (
@@ -49,6 +50,34 @@ type (
4950
TopicManagementOption func(*TopicDescription) error
5051
)
5152

53+
type (
54+
// ListTopicsOptions provides options for List() to control things like page size.
55+
// NOTE: Use the ListTopicsWith* methods to specify this.
56+
ListTopicsOptions struct {
57+
top int
58+
skip int
59+
}
60+
61+
// ListTopicsOption represents named options for listing topics
62+
ListTopicsOption func(*ListTopicsOptions) error
63+
)
64+
65+
// ListTopicsWithSkip will skip the specified number of entities
66+
func ListTopicsWithSkip(skip int) ListTopicsOption {
67+
return func(options *ListTopicsOptions) error {
68+
options.skip = skip
69+
return nil
70+
}
71+
}
72+
73+
// ListTopicsWithTop will return at most `top` results
74+
func ListTopicsWithTop(top int) ListTopicsOption {
75+
return func(options *ListTopicsOptions) error {
76+
options.top = top
77+
return nil
78+
}
79+
}
80+
5281
// NewTopicManager creates a new TopicManager for a Service Bus Namespace
5382
func (ns *Namespace) NewTopicManager() *TopicManager {
5483
return &TopicManager{
@@ -122,11 +151,21 @@ func (tm *TopicManager) Put(ctx context.Context, name string, opts ...TopicManag
122151
}
123152

124153
// List fetches all of the Topics for a Service Bus Namespace
125-
func (tm *TopicManager) List(ctx context.Context) ([]*TopicEntity, error) {
154+
func (tm *TopicManager) List(ctx context.Context, options ...ListTopicsOption) ([]*TopicEntity, error) {
126155
ctx, span := tm.startSpanFromContext(ctx, "sb.TopicManager.List")
127156
defer span.End()
128157

129-
res, err := tm.entityManager.Get(ctx, `/$Resources/Topics`)
158+
listTopicsOptions := ListTopicsOptions{}
159+
160+
for _, option := range options {
161+
if err := option(&listTopicsOptions); err != nil {
162+
return nil, err
163+
}
164+
}
165+
166+
basePath := internal.ConstructAtomPath("/$Resources/Topics", listTopicsOptions.skip, listTopicsOptions.top)
167+
168+
res, err := tm.entityManager.Get(ctx, basePath)
130169
defer closeRes(ctx, res)
131170

132171
if err != nil {

topic_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,22 @@ func testListTopics(ctx context.Context, t *testing.T, tm *TopicManager, names [
196196
for _, name := range names {
197197
assert.Contains(t, queueNames, name)
198198
}
199+
200+
// there should be at least two entities but there could be others if the service isn't clean (which is fine)
201+
firstSet, err := tm.List(ctx, ListTopicsWithSkip(0), ListTopicsWithTop(1))
202+
assert.NoError(t, err)
203+
assert.EqualValues(t, 1, len(firstSet))
204+
205+
secondSet, err := tm.List(ctx, ListTopicsWithSkip(1), ListTopicsWithTop(1))
206+
assert.NoError(t, err)
207+
assert.EqualValues(t, 1, len(secondSet))
208+
209+
// sanity check - we didn't just retrieve the same entity twice.
210+
assert.NotEqualValues(t, firstSet[0].Name, secondSet[0].Name)
211+
212+
lastSet, err := tm.List(ctx, ListTopicsWithSkip(0), ListTopicsWithTop(2))
213+
assert.NoError(t, err)
214+
assert.EqualValues(t, 2, len(lastSet))
199215
}
200216

201217
func (suite *serviceBusSuite) TestTopicManagement() {

0 commit comments

Comments
 (0)