Skip to content

Commit e604ea2

Browse files
waterlinkandreasf
authored andcommitted
Follow next_url links in paginated API responses
Fixes #2 Fixes #3 [#135595571] Signed-off-by: Andreas Fleig <[email protected]>
1 parent 4c2ff64 commit e604ea2

File tree

10 files changed

+144
-30
lines changed

10 files changed

+144
-30
lines changed

cfmysql/api_client.go

Lines changed: 38 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,22 @@ type ApiClientImpl struct {
2121
}
2222

2323
func (self *ApiClientImpl) GetServiceInstances(cliConnection plugin.CliConnection) ([]pluginModels.ServiceInstance, error) {
24-
instanceResponse, err := self.getFromCfApi("/v2/service_instances", cliConnection)
25-
if err != nil {
26-
return nil, fmt.Errorf("Unable to retrieve service instances: %s", err)
24+
var err error
25+
var allInstances []pluginModels.ServiceInstance
26+
nextUrl := "/v2/service_instances"
27+
28+
for nextUrl != "" {
29+
instanceResponse, err := self.getFromCfApi(nextUrl, cliConnection)
30+
if err != nil {
31+
return nil, fmt.Errorf("Unable to retrieve service instances: %s", err)
32+
}
33+
34+
var instances []pluginModels.ServiceInstance
35+
nextUrl, instances, err = deserializeInstances(instanceResponse)
36+
allInstances = append(allInstances, instances...)
2737
}
2838

29-
return deserializeInstances(instanceResponse)
39+
return allInstances, err
3040
}
3141

3242
func (self *ApiClientImpl) getFromCfApi(path string, cliConnection plugin.CliConnection) ([]byte, error) {
@@ -43,35 +53,49 @@ func (self *ApiClientImpl) getFromCfApi(path string, cliConnection plugin.CliCon
4353
return self.HttpClient.Get(endpoint + path, accessToken)
4454
}
4555

46-
func deserializeInstances(jsonResponse []byte) ([]pluginModels.ServiceInstance, error) {
56+
func deserializeInstances(jsonResponse []byte) (string, []pluginModels.ServiceInstance, error) {
4757
paginatedResources := new(resources.PaginatedServiceInstanceResources)
4858
err := json.Unmarshal(jsonResponse, paginatedResources)
4959

5060
if err != nil {
51-
return nil, fmt.Errorf("Unable to deserialize service instances: %s", err)
61+
return "", nil, fmt.Errorf("Unable to deserialize service instances: %s", err)
5262
}
5363

54-
return paginatedResources.ToModel(), nil
64+
return paginatedResources.NextUrl, paginatedResources.ToModel(), nil
5565
}
5666

5767
func (self *ApiClientImpl) GetServiceBindings(cliConnection plugin.CliConnection) ([]pluginModels.ServiceBinding, error) {
58-
bindingsResp, err := self.getFromCfApi("/v2/service_bindings", cliConnection)
59-
if err != nil {
60-
return nil, fmt.Errorf("Unable to call service bindings endpoint: %s", err)
68+
var allBindings []pluginModels.ServiceBinding
69+
nextUrl := "/v2/service_bindings"
70+
71+
for nextUrl != "" {
72+
bindingsResp, err := self.getFromCfApi(nextUrl, cliConnection)
73+
if err != nil {
74+
return nil, fmt.Errorf("Unable to call service bindings endpoint: %s", err)
75+
}
76+
77+
var bindings []pluginModels.ServiceBinding
78+
nextUrl, bindings, err = deserializeBindings(bindingsResp)
79+
if err != nil {
80+
return nil, fmt.Errorf("Unable to deserialize service bindings: %s", err)
81+
}
82+
83+
allBindings = append(allBindings, bindings...)
6184
}
6285

63-
return deserializeBindings(bindingsResp)
86+
return allBindings, nil
6487
}
6588

66-
func deserializeBindings(bindingResponse []byte) ([]pluginModels.ServiceBinding, error) {
89+
func deserializeBindings(bindingResponse []byte) (string, []pluginModels.ServiceBinding, error) {
6790
paginatedResources := new(resources.PaginatedServiceBindingResources)
6891
err := json.Unmarshal(bindingResponse, paginatedResources)
6992

7093
if err != nil {
71-
return nil, fmt.Errorf("Unable to deserialize service bindings: %s", err)
94+
return "", nil, fmt.Errorf("Unable to deserialize service bindings: %s", err)
7295
}
7396

74-
return paginatedResources.ToModel()
97+
bindings, err := paginatedResources.ToModel()
98+
return paginatedResources.NextUrl, bindings, err
7599
}
76100

77101
func (self *ApiClientImpl) GetStartedApps(cliConnection plugin.CliConnection) ([]sdkModels.GetAppsModel, error) {

cfmysql/api_client_test.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@ var _ = Describe("ApiClient", func() {
2525
switch url {
2626
case "https://cf.api.url/v2/service_bindings":
2727
return test_resources.LoadResource("test_resources/service_bindings.json"), nil
28+
case "https://cf.api.url/v2/service_bindings?page=2":
29+
return test_resources.LoadResource("test_resources/service_bindings_page2.json"), nil
2830
case "https://cf.api.url/v2/service_instances":
2931
return test_resources.LoadResource("test_resources/service_instances.json"), nil
32+
case "https://cf.api.url/v2/service_instances?page=2":
33+
return test_resources.LoadResource("test_resources/service_instances_page2.json"), nil
3034
default:
3135
return nil, fmt.Errorf("URL not handled in mock: %s", url)
3236
}
@@ -83,15 +87,19 @@ var _ = Describe("ApiClient", func() {
8387
instances, err := apiClient.GetServiceInstances(cliConnection)
8488

8589
Expect(err).To(BeNil())
86-
Expect(instances).To(HaveLen(4))
90+
Expect(instances).To(HaveLen(5))
8791

88-
Expect(cliConnection.AccessTokenCallCount()).To(Equal(1))
89-
Expect(cliConnection.ApiEndpointCallCount()).To(Equal(1))
92+
Expect(cliConnection.AccessTokenCallCount()).To(Equal(2))
93+
Expect(cliConnection.ApiEndpointCallCount()).To(Equal(2))
9094

91-
Expect(mockHttp.GetCallCount()).To(Equal(1))
95+
Expect(mockHttp.GetCallCount()).To(Equal(2))
9296
url, access_token := mockHttp.GetArgsForCall(0)
9397
Expect(url).To(Equal("https://cf.api.url/v2/service_instances"))
9498
Expect(access_token).To(Equal("bearer my-secret-token"))
99+
100+
url2, access_token2 := mockHttp.GetArgsForCall(1)
101+
Expect(url2).To(Equal("https://cf.api.url/v2/service_instances?page=2"))
102+
Expect(access_token2).To(Equal("bearer my-secret-token"))
95103
})
96104
})
97105

@@ -104,17 +112,21 @@ var _ = Describe("ApiClient", func() {
104112
bindings, err := apiClient.GetServiceBindings(cliConnection)
105113

106114
Expect(err).To(BeNil())
107-
Expect(bindings).To(HaveLen(5))
115+
Expect(bindings).To(HaveLen(6))
108116
Expect(bindings[0].Port).To(Equal("3306"))
109117
Expect(bindings[3].Port).To(Equal("54321"))
110118

111-
Expect(cliConnection.AccessTokenCallCount()).To(Equal(1))
112-
Expect(cliConnection.ApiEndpointCallCount()).To(Equal(1))
119+
Expect(cliConnection.AccessTokenCallCount()).To(Equal(2))
120+
Expect(cliConnection.ApiEndpointCallCount()).To(Equal(2))
113121

114-
Expect(mockHttp.GetCallCount()).To(Equal(1))
122+
Expect(mockHttp.GetCallCount()).To(Equal(2))
115123
url, access_token := mockHttp.GetArgsForCall(0)
116124
Expect(url).To(Equal("https://cf.api.url/v2/service_bindings"))
117125
Expect(access_token).To(Equal("bearer my-secret-token"))
126+
127+
url2, access_token2 := mockHttp.GetArgsForCall(1)
128+
Expect(url2).To(Equal("https://cf.api.url/v2/service_bindings?page=2"))
129+
Expect(access_token2).To(Equal("bearer my-secret-token"))
118130
})
119131
})
120132
})

cfmysql/plugin.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func (self *MysqlPlugin) GetMetadata() plugin.PluginMetadata {
2424
Version: plugin.VersionType{
2525
Major: 1,
2626
Minor: 3,
27-
Build: 4,
27+
Build: 5,
2828
},
2929
MinCliVersion: plugin.VersionType{
3030
Major: 6,

cfmysql/plugin_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var _ = Describe("Plugin", func() {
2121
Expect(mysqlPlugin.GetMetadata().Version).To(Equal(plugin.VersionType{
2222
Major: 1,
2323
Minor: 3,
24-
Build: 4,
24+
Build: 5,
2525
}))
2626
})
2727
})

cfmysql/resources/resources.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
type PaginatedServiceBindingResources struct {
1313
TotalResults int `json:"total_results"`
14+
NextUrl string `json:"next_url"`
1415
Resources []ServiceBindingResource
1516
}
1617

@@ -37,6 +38,7 @@ type MysqlCredentials struct {
3738

3839
type PaginatedServiceInstanceResources struct {
3940
TotalResults int `json:"total_results"`
41+
NextUrl string `json:"next_url"`
4042
Resources []ServiceInstanceResource
4143
}
4244

cfmysql/resources/resources_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ var _ = Describe("Resources", func() {
2121

2222
Expect(err).To(BeNil())
2323
Expect(paginatedResources.Resources).To(HaveLen(4))
24+
Expect(paginatedResources.NextUrl).To(Equal("/v2/service_instances?page=2"))
2425

2526
Expect(paginatedResources.Resources[0].Entity.Name).To(Equal("database-a"))
2627
Expect(paginatedResources.Resources[0].Metadata.GUID).To(Equal("service-instance-guid-a"))
@@ -86,6 +87,7 @@ var _ = Describe("Resources", func() {
8687

8788
Expect(err).To(BeNil())
8889
Expect(paginatedResources.Resources).To(HaveLen(5))
90+
Expect(paginatedResources.NextUrl).To(Equal("/v2/service_bindings?page=2"))
8991

9092
Expect(paginatedResources.Resources[0].Entity.ServiceInstanceGUID).To(Equal("service-instance-guid-a"))
9193
Expect(paginatedResources.Resources[0].Entity.Credentials.Uri).To(Equal("mysql://username-a:[email protected]:3306/dbname-a?reconnect=true"))

cfmysql/test_resources/service_bindings.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"total_results": 2,
3-
"total_pages": 1,
2+
"total_results": 6,
3+
"total_pages": 2,
44
"prev_url": null,
5-
"next_url": null,
5+
"next_url": "/v2/service_bindings?page=2",
66
"resources": [
77
{
88
"metadata": {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"total_results": 6,
3+
"total_pages": 2,
4+
"prev_url": null,
5+
"next_url": null,
6+
"resources": [
7+
{
8+
"metadata": {
9+
"guid": "guid-service-binding-f",
10+
"url": "/v2/service_bindings/guid-service-binding-f",
11+
"created_at": "2016-11-15T09:22:15Z",
12+
"updated_at": null
13+
},
14+
"entity": {
15+
"app_guid": "app-guid-f",
16+
"service_instance_guid": "service-instance-guid-f",
17+
"credentials": {
18+
"jdbcUrl": "jdbc:mysql://database-f.host/dbname-f?user=username-f&password=password-f",
19+
"uri": "mysql://username-f:[email protected]:3306/dbname-f?reconnect=true",
20+
"name": "dbname-f",
21+
"hostname": "database-f.host",
22+
"port": "3306",
23+
"username": "username-f",
24+
"password": "password-f"
25+
},
26+
"binding_options": {},
27+
"gateway_data": null,
28+
"gateway_name": "",
29+
"syslog_drain_url": null,
30+
"volume_mounts": [],
31+
"app_url": "/v2/apps/app-guid-f",
32+
"service_instance_url": "/v2/service_instances/service-instance-guid-f"
33+
}
34+
}
35+
]
36+
}

cfmysql/test_resources/service_instances.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
2-
"total_results": 4,
3-
"total_pages": 1,
2+
"total_results": 5,
3+
"total_pages": 2,
44
"prev_url": null,
5-
"next_url": null,
5+
"next_url": "/v2/service_instances?page=2",
66
"resources": [
77
{
88
"metadata": {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"total_results": 5,
3+
"total_pages": 2,
4+
"prev_url": null,
5+
"next_url": null,
6+
"resources": [
7+
{
8+
"metadata": {
9+
"guid": "service-instance-guid-f",
10+
"url": "/v2/service_instances/service-instance-guid-f",
11+
"created_at": "2016-11-14T14:56:40Z",
12+
"updated_at": null
13+
},
14+
"entity": {
15+
"name": "database-f",
16+
"credentials": {},
17+
"service_plan_guid": "service-plan-guid",
18+
"space_guid": "space-guid",
19+
"gateway_data": null,
20+
"dashboard_url": "https://dashboard.url/service-instance-guid-f",
21+
"type": "managed_service_instance",
22+
"last_operation": {
23+
"type": "create",
24+
"state": "succeeded",
25+
"description": "",
26+
"updated_at": null,
27+
"created_at": "2016-11-14T14:56:41Z"
28+
},
29+
"tags": [],
30+
"space_url": "/v2/spaces/space-guid",
31+
"service_plan_url": "/v2/service_plans/service-plan-guid",
32+
"service_bindings_url": "/v2/service_instances/service-instance-guid-f/service_bindings",
33+
"service_keys_url": "/v2/service_instances/service-instance-guid-f/service_keys",
34+
"routes_url": "/v2/service_instances/service-instance-guid-f/routes"
35+
}
36+
}
37+
]
38+
}

0 commit comments

Comments
 (0)