cfoundry 0.5.1.rc2 → 0.5.1.rc3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cc_api_stub.rb +17 -0
- data/lib/cc_api_stub/applications.rb +53 -0
- data/lib/cc_api_stub/domains.rb +16 -0
- data/lib/cc_api_stub/frameworks.rb +22 -0
- data/lib/cc_api_stub/helper.rb +131 -0
- data/lib/cc_api_stub/login.rb +21 -0
- data/lib/cc_api_stub/organization_users.rb +21 -0
- data/lib/cc_api_stub/organizations.rb +70 -0
- data/lib/cc_api_stub/routes.rb +26 -0
- data/lib/cc_api_stub/runtimes.rb +22 -0
- data/lib/cc_api_stub/service_bindings.rb +22 -0
- data/lib/cc_api_stub/service_instances.rb +22 -0
- data/lib/cc_api_stub/services.rb +25 -0
- data/lib/cc_api_stub/spaces.rb +49 -0
- data/lib/cc_api_stub/users.rb +84 -0
- data/lib/cfoundry/baseclient.rb +24 -0
- data/lib/cfoundry/errors.rb +16 -133
- data/lib/cfoundry/v1/app.rb +6 -2
- data/lib/cfoundry/v2/app.rb +16 -4
- data/lib/cfoundry/v2/base.rb +10 -11
- data/lib/cfoundry/v2/client.rb +4 -0
- data/lib/cfoundry/version.rb +1 -1
- data/spec/cc_api_stub/applications_spec.rb +69 -0
- data/spec/cc_api_stub/domains_spec.rb +19 -0
- data/spec/cc_api_stub/frameworks_spec.rb +19 -0
- data/spec/cc_api_stub/login_spec.rb +20 -0
- data/spec/cc_api_stub/organization_users_spec.rb +19 -0
- data/spec/cc_api_stub/organizations_spec.rb +103 -0
- data/spec/cc_api_stub/routes_spec.rb +19 -0
- data/spec/cc_api_stub/runtimes_spec.rb +19 -0
- data/spec/cc_api_stub/service_bindings_spec.rb +13 -0
- data/spec/cc_api_stub/service_instances_spec.rb +19 -0
- data/spec/cc_api_stub/services_spec.rb +12 -0
- data/spec/cc_api_stub/spaces_spec.rb +38 -0
- data/spec/cc_api_stub/users_spec.rb +107 -0
- data/spec/cfoundry/baseclient_spec.rb +42 -2
- data/spec/cfoundry/v2/app_spec.rb +95 -0
- data/spec/fixtures/fake_cc_application.json +24 -0
- data/spec/fixtures/fake_cc_application_summary.json +57 -0
- data/spec/fixtures/fake_cc_created_application.json +11 -0
- data/spec/fixtures/fake_cc_created_domain.json +10 -0
- data/spec/fixtures/fake_cc_created_organization.json +11 -0
- data/spec/fixtures/fake_cc_created_route.json +13 -0
- data/spec/fixtures/fake_cc_created_service_instance.json +11 -0
- data/spec/fixtures/fake_cc_created_space.json +11 -0
- data/spec/fixtures/fake_cc_created_user.json +11 -0
- data/spec/fixtures/fake_cc_empty_search.json +7 -0
- data/spec/fixtures/fake_cc_frameworks.json +20 -0
- data/spec/fixtures/fake_cc_organization.json +144 -0
- data/spec/fixtures/fake_cc_organization_domains.json +34 -0
- data/spec/fixtures/fake_cc_organization_search.json +37 -0
- data/spec/fixtures/fake_cc_organization_summary.json +19 -0
- data/spec/fixtures/fake_cc_organization_users.json +81 -0
- data/spec/fixtures/fake_cc_runtimes.json +20 -0
- data/spec/fixtures/fake_cc_service_binding.json +22 -0
- data/spec/fixtures/fake_cc_service_bindings.json +24 -0
- data/spec/fixtures/fake_cc_service_instance.json +81 -0
- data/spec/fixtures/fake_cc_service_instances.json +0 -0
- data/spec/fixtures/fake_cc_service_types.json +124 -0
- data/spec/fixtures/fake_cc_space.json +45 -0
- data/spec/fixtures/fake_cc_space_summary.json +86 -0
- data/spec/fixtures/fake_cc_stats.json +29 -0
- data/spec/fixtures/fake_cc_user.json +112 -0
- data/spec/fixtures/fake_cc_user_organizations.json +92 -0
- data/spec/fixtures/fake_cc_user_with_managers.json +85 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/fake_helper.rb +0 -36
- data/spec/support/shared_examples/cc_api_stub_request_examples.rb +79 -0
- metadata +254 -144
@@ -0,0 +1,25 @@
|
|
1
|
+
module CcApiStub
|
2
|
+
module Services
|
3
|
+
extend Helper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def succeed_to_load
|
7
|
+
stub_get(collection_endpoint, {}, response(200, services_fixture))
|
8
|
+
end
|
9
|
+
|
10
|
+
def service_fixture_hash
|
11
|
+
MultiJson.load(services_fixture["resources"].first.to_json, :symbolize_keys => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def collection_endpoint
|
17
|
+
%r{/v2/services\?inline-relations-depth=1}
|
18
|
+
end
|
19
|
+
|
20
|
+
def services_fixture
|
21
|
+
Helper.load_fixtures("fake_cc_service_types")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module CcApiStub
|
2
|
+
module Spaces
|
3
|
+
extend Helper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def succeed_to_load(options={})
|
7
|
+
response_body = Helper.load_fixtures(options.delete(:fixture) || "fake_cc_#{object_name}", options)
|
8
|
+
stub_get(object_endpoint, {}, response(200, response_body))
|
9
|
+
end
|
10
|
+
|
11
|
+
def succeed_to_create
|
12
|
+
response_body = Helper.load_fixtures("fake_cc_created_space")
|
13
|
+
stub_post(collection_endpoint, {}, response(201, response_body))
|
14
|
+
end
|
15
|
+
|
16
|
+
def summary_fixture
|
17
|
+
Helper.load_fixtures("fake_cc_space_summary")
|
18
|
+
end
|
19
|
+
|
20
|
+
def succeed_to_load_summary(options={})
|
21
|
+
response_body = summary_fixture
|
22
|
+
response_body["services"] = [] if options.delete(:no_services)
|
23
|
+
stub_get(%r{/v2/spaces/[^/]+/summary$}, {}, response(200, response_body))
|
24
|
+
end
|
25
|
+
|
26
|
+
def space_fixture_hash
|
27
|
+
{
|
28
|
+
:metadata => {
|
29
|
+
:guid => "space-id-1",
|
30
|
+
:url => "/v2/spaces/space-id-1"
|
31
|
+
},
|
32
|
+
:entity => {
|
33
|
+
:name => "space-name-1"
|
34
|
+
}
|
35
|
+
}
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def collection_endpoint
|
41
|
+
%r{/v2/spaces$}
|
42
|
+
end
|
43
|
+
|
44
|
+
def object_endpoint
|
45
|
+
%r{/v2/spaces/[^/]+$}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module CcApiStub
|
2
|
+
module Users
|
3
|
+
extend Helper
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def succeed_to_load(options={})
|
7
|
+
response_body = Helper.load_fixtures(options[:fixture] || "fake_cc_user")
|
8
|
+
response_body["metadata"]["guid"] = options[:id] || "user-id-1"
|
9
|
+
|
10
|
+
if options[:no_organizations]
|
11
|
+
response_body["entity"]["organizations"] = []
|
12
|
+
else
|
13
|
+
def space(space_id) {"metadata" => { "guid" => space_id }, "entity" => {}} end
|
14
|
+
|
15
|
+
organization = response_body["entity"]["organizations"].first
|
16
|
+
organization["metadata"]["guid"] = options[:organization_id] || "organization-id-1"
|
17
|
+
organization["entity"]["spaces"] = [] if options[:no_spaces]
|
18
|
+
|
19
|
+
permissions = options[:permissions] || [:organization_manager]
|
20
|
+
|
21
|
+
response_body["entity"]["managed_organizations"] << organization if permissions.include?(:organization_manager)
|
22
|
+
response_body["entity"]["billing_managed_organizations"] << organization if permissions.include?(:organization_billing_manager)
|
23
|
+
response_body["entity"]["audited_organizations"] << organization if permissions.include?(:organization_auditor)
|
24
|
+
|
25
|
+
unless options[:no_spaces]
|
26
|
+
space = space("space-id-1")
|
27
|
+
response_body["entity"]["spaces"] << space if permissions.include?(:space_developer)
|
28
|
+
response_body["entity"]["managed_spaces"] << space if permissions.include?(:space_manager)
|
29
|
+
response_body["entity"]["audited_spaces"] << space if permissions.include?(:space_auditor)
|
30
|
+
|
31
|
+
space2 = space("space-id-2")
|
32
|
+
response_body["entity"]["spaces"] << space2 if permissions.include?(:space2_developer)
|
33
|
+
response_body["entity"]["managed_spaces"] << space2 if permissions.include?(:space2_manager)
|
34
|
+
response_body["entity"]["audited_spaces"] << space2 if permissions.include?(:space2_auditor)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
stub_get(%r{/v2/users/[^/]+\?inline-relations-depth=2$}, {}, response(200, response_body))
|
39
|
+
end
|
40
|
+
|
41
|
+
def fail_to_find
|
42
|
+
stub_get(object_endpoint, {}, response(404, {:code => 20003, :description => "The user could not be found"}))
|
43
|
+
end
|
44
|
+
|
45
|
+
def succeed_to_create
|
46
|
+
response_body = Helper.load_fixtures("fake_cc_created_user")
|
47
|
+
stub_post(collection_endpoint, {}, response(201, response_body))
|
48
|
+
end
|
49
|
+
|
50
|
+
def fail_to_create
|
51
|
+
CcApiStub::Helper.fail_request(:post, 500, {}, /users/)
|
52
|
+
end
|
53
|
+
|
54
|
+
def succeed_to_replace_permissions
|
55
|
+
stub_put(object_endpoint, {}, response(200, ""))
|
56
|
+
end
|
57
|
+
|
58
|
+
def fail_to_replace_permissions
|
59
|
+
stub_put(object_endpoint, {}, response(500))
|
60
|
+
end
|
61
|
+
|
62
|
+
def organizations_fixture
|
63
|
+
Helper.load_fixtures("fake_cc_user")["entity"]["organizations"]
|
64
|
+
end
|
65
|
+
|
66
|
+
def organization_fixture_hash(options={})
|
67
|
+
fixture = organizations_fixture.first
|
68
|
+
fixture["entity"].delete("spaces") if options[:no_spaces]
|
69
|
+
fixture["entity"].delete("managers") if options[:no_managers]
|
70
|
+
MultiJson.load(fixture.to_json, :symbolize_keys => true)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def collection_endpoint
|
76
|
+
%r{/v2/users$}
|
77
|
+
end
|
78
|
+
|
79
|
+
def object_endpoint
|
80
|
+
%r{/v2/users/[^/]+$}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
data/lib/cfoundry/baseclient.rb
CHANGED
@@ -93,6 +93,30 @@ module CFoundry
|
|
93
93
|
self.token = uaa.try_to_refresh_token!
|
94
94
|
end
|
95
95
|
|
96
|
+
def stream_url(url, &blk)
|
97
|
+
uri = URI.parse(url)
|
98
|
+
|
99
|
+
Net::HTTP.start(uri.host, uri.port) do |http|
|
100
|
+
http.read_timeout = 5
|
101
|
+
|
102
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
103
|
+
req["Authorization"] = token.auth_header if token
|
104
|
+
|
105
|
+
http.request(req) do |response|
|
106
|
+
case response
|
107
|
+
when Net::HTTPOK
|
108
|
+
response.read_body(&blk)
|
109
|
+
when Net::HTTPNotFound
|
110
|
+
raise CFoundry::NotFound.new(response.body, 404)
|
111
|
+
when Net::HTTPForbidden
|
112
|
+
raise CFoundry::Denied.new(response.body, 403)
|
113
|
+
else
|
114
|
+
raise CFoundry::BadResponse.new(response.body, response.code)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
96
120
|
private
|
97
121
|
|
98
122
|
def needs_token_refresh?
|
data/lib/cfoundry/errors.rb
CHANGED
@@ -126,7 +126,7 @@ module CFoundry
|
|
126
126
|
class UAAError < APIError
|
127
127
|
end
|
128
128
|
|
129
|
-
def self.define_error(class_name,
|
129
|
+
def self.define_error(class_name, code)
|
130
130
|
base =
|
131
131
|
case class_name
|
132
132
|
when /NotFound$/
|
@@ -135,142 +135,25 @@ module CFoundry
|
|
135
135
|
APIError
|
136
136
|
end
|
137
137
|
|
138
|
-
klass =
|
138
|
+
klass =
|
139
|
+
if const_defined?(class_name)
|
140
|
+
const_get(class_name)
|
141
|
+
else
|
142
|
+
Class.new(base)
|
143
|
+
end
|
139
144
|
|
140
|
-
|
141
|
-
APIError.error_classes[code] = klass
|
142
|
-
end
|
145
|
+
APIError.error_classes[code] = klass
|
143
146
|
|
144
|
-
|
147
|
+
unless const_defined?(class_name)
|
148
|
+
const_set(class_name, klass)
|
149
|
+
end
|
145
150
|
end
|
146
151
|
|
152
|
+
VENDOR_DIR = File.expand_path("../../../vendor", __FILE__)
|
147
153
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
["MessageParseError", 1001],
|
153
|
-
["InvalidRelation", 1002],
|
154
|
-
|
155
|
-
["UserInvalid", 20001],
|
156
|
-
["UaaIdTaken", 20002],
|
157
|
-
["UserNotFound", 20003, 201],
|
158
|
-
|
159
|
-
["OrganizationInvalid", 30001],
|
160
|
-
["OrganizationNameTaken", 30002],
|
161
|
-
["OrganizationNotFound", 30003],
|
162
|
-
|
163
|
-
["SpaceInvalid", 40001],
|
164
|
-
["SpaceNameTaken", 40002],
|
165
|
-
["SpaceUserNotInOrg", 40003],
|
166
|
-
["SpaceNotFound", 40004],
|
167
|
-
|
168
|
-
["ServiceAuthTokenInvalid", 50001],
|
169
|
-
["ServiceAuthTokenLabelTaken", 50002],
|
170
|
-
["ServiceAuthTokenNotFound", 50003],
|
171
|
-
|
172
|
-
["ServiceInstanceNameInvalid", 60001],
|
173
|
-
["ServiceInstanceNameTaken", 60002],
|
174
|
-
["ServiceInstanceServiceBindingWrongSpace", 60003],
|
175
|
-
["ServiceInstanceInvalid", 60003],
|
176
|
-
["ServiceInstanceNotFound", 60004],
|
177
|
-
|
178
|
-
["RuntimeInvalid", 70001],
|
179
|
-
["RuntimeNameTaken", 70002],
|
180
|
-
["RuntimeNotFound", 70003],
|
181
|
-
|
182
|
-
["FrameworkInvalid", 80001],
|
183
|
-
["FrameworkNameTaken", 80002],
|
184
|
-
["FrameworkNotFound", 80003],
|
185
|
-
|
186
|
-
["ServiceBindingInvalid", 90001],
|
187
|
-
["ServiceBindingDifferentSpaces", 90002],
|
188
|
-
["ServiceBindingAppServiceTaken", 90003],
|
189
|
-
["ServiceBindingNotFound", 90004],
|
190
|
-
|
191
|
-
["AppInvalid", 100001, 300],
|
192
|
-
["AppNameTaken", 100002],
|
193
|
-
["AppNotFound", 100004, 301],
|
194
|
-
|
195
|
-
["ServicePlanInvalid", 110001],
|
196
|
-
["ServicePlanNameTaken", 110002],
|
197
|
-
["ServicePlanNotFound", 110003],
|
198
|
-
|
199
|
-
["ServiceInvalid", 120001],
|
200
|
-
["ServiceLabelTaken", 120002],
|
201
|
-
["ServiceNotFound", 120003, 500],
|
202
|
-
|
203
|
-
["DomainInvalid", 130001],
|
204
|
-
["DomainNotFound", 130002],
|
205
|
-
["DomainNameTaken", 130003],
|
206
|
-
|
207
|
-
["LegacyApiWithoutDefaultSpace", 140001],
|
208
|
-
|
209
|
-
["AppPackageInvalid", 150001],
|
210
|
-
["AppPackageNotFound", 150002],
|
211
|
-
|
212
|
-
["AppBitsUploadInvalid", 160001],
|
213
|
-
|
214
|
-
["StagingError", 170001],
|
215
|
-
|
216
|
-
["SnapshotNotFound", 180001],
|
217
|
-
["ServiceGatewayError", 180002, 503],
|
218
|
-
["ServiceNotImplemented", 180003],
|
219
|
-
["SDSNotAvailable", 180004],
|
220
|
-
|
221
|
-
["FileError", 190001],
|
222
|
-
|
223
|
-
["StatsError", 200001],
|
224
|
-
|
225
|
-
["RouteInvalid", 210001],
|
226
|
-
["RouteNotFound", 210002],
|
227
|
-
["RouteHostTaken", 210003],
|
228
|
-
|
229
|
-
["InstancesError", 220001],
|
230
|
-
|
231
|
-
["BillingEventQueryInvalid", 230001],
|
232
|
-
|
233
|
-
# V1 Errors
|
234
|
-
["BadRequest", 100],
|
235
|
-
["DatabaseError", 101],
|
236
|
-
["LockingError", 102],
|
237
|
-
["SystemError", 111],
|
238
|
-
|
239
|
-
["Forbidden", 200],
|
240
|
-
["HttpsRequired", 202],
|
241
|
-
|
242
|
-
["AppNoResources", 302],
|
243
|
-
["AppFileNotFound", 303],
|
244
|
-
["AppInstanceNotFound", 304],
|
245
|
-
["AppStopped", 305],
|
246
|
-
["AppFileError", 306],
|
247
|
-
["AppInvalidRuntime", 307],
|
248
|
-
["AppInvalidFramework", 308],
|
249
|
-
["AppDebugDisallowed", 309],
|
250
|
-
["AppStagingError", 310],
|
251
|
-
|
252
|
-
["ResourcesUnknownPackageType", 400],
|
253
|
-
["ResourcesMissingResource", 401],
|
254
|
-
["ResourcesPackagingFailed", 402],
|
255
|
-
|
256
|
-
["BindingNotFound", 501],
|
257
|
-
["TokenNotFound", 502],
|
258
|
-
["AccountTooManyServices", 504],
|
259
|
-
["ExtensionNotImpl", 505],
|
260
|
-
["UnsupportedVersion", 506],
|
261
|
-
["SdsError", 507],
|
262
|
-
["SdsNotFound", 508],
|
263
|
-
|
264
|
-
["AccountNotEnoughMemory", 600],
|
265
|
-
["AccountAppsTooMany", 601],
|
266
|
-
["AccountAppTooManyUris", 602],
|
267
|
-
|
268
|
-
["UriInvalid", 700],
|
269
|
-
["UriAlreadyTaken", 701],
|
270
|
-
["UriNotAllowed", 702],
|
271
|
-
["StagingTimedOut", 800],
|
272
|
-
["StagingFailed", 801]
|
273
|
-
].each do |args|
|
274
|
-
define_error(*args)
|
154
|
+
%w{errors/v1.yml errors/v2.yml}.each do |errors|
|
155
|
+
YAML.load_file("#{VENDOR_DIR}/#{errors}").each do |code, meta|
|
156
|
+
define_error(meta["name"], code)
|
157
|
+
end
|
275
158
|
end
|
276
159
|
end
|
data/lib/cfoundry/v1/app.rb
CHANGED
@@ -116,17 +116,21 @@ module CFoundry::V1
|
|
116
116
|
end
|
117
117
|
|
118
118
|
# Start the application.
|
119
|
-
def start!
|
119
|
+
def start!(async = false)
|
120
120
|
self.state = "STARTED"
|
121
121
|
update!
|
122
122
|
end
|
123
123
|
|
124
124
|
# Restart the application.
|
125
|
-
def restart!
|
125
|
+
def restart!(async = false)
|
126
126
|
stop!
|
127
127
|
start!
|
128
128
|
end
|
129
129
|
|
130
|
+
def update!(async = false)
|
131
|
+
super()
|
132
|
+
end
|
133
|
+
|
130
134
|
# Determine application health.
|
131
135
|
#
|
132
136
|
# If all instances are running, returns "RUNNING". If only some are
|
data/lib/cfoundry/v2/app.rb
CHANGED
@@ -189,15 +189,27 @@ module CFoundry::V2
|
|
189
189
|
end
|
190
190
|
|
191
191
|
# Start the application.
|
192
|
-
def start!
|
192
|
+
def start!(async = false, &blk)
|
193
193
|
self.state = "STARTED"
|
194
|
-
update!
|
194
|
+
update!(async, &blk)
|
195
195
|
end
|
196
196
|
|
197
197
|
# Restart the application.
|
198
|
-
def restart!
|
198
|
+
def restart!(async = false, &blk)
|
199
199
|
stop!
|
200
|
-
start!
|
200
|
+
start!(async, &blk)
|
201
|
+
end
|
202
|
+
|
203
|
+
def update!(async = false)
|
204
|
+
response = @client.base.update_app(@guid, @diff, async)
|
205
|
+
|
206
|
+
yield response[:headers]["x-app-staging-log"] if block_given?
|
207
|
+
|
208
|
+
@manifest = @client.base.send(:parse_json, response[:body])
|
209
|
+
|
210
|
+
@diff.clear
|
211
|
+
|
212
|
+
true
|
201
213
|
end
|
202
214
|
|
203
215
|
# Determine application health.
|
data/lib/cfoundry/v2/base.rb
CHANGED
@@ -38,21 +38,12 @@ module CFoundry::V2
|
|
38
38
|
end
|
39
39
|
alias :file :files
|
40
40
|
|
41
|
-
def stream_file(guid, instance, *path)
|
41
|
+
def stream_file(guid, instance, *path, &blk)
|
42
42
|
path_and_options = path + [{:return_response => true, :follow_redirects => false}]
|
43
43
|
redirect = get("v2", "apps", guid, "instances", instance, "files", *path_and_options)
|
44
44
|
|
45
45
|
if location = redirect[:headers]["location"]
|
46
|
-
|
47
|
-
|
48
|
-
Net::HTTP.start(uri.host, uri.port) do |http|
|
49
|
-
req = Net::HTTP::Get.new(uri.request_uri)
|
50
|
-
req["Authorization"] = token.auth_header if token
|
51
|
-
|
52
|
-
http.request(req) do |response|
|
53
|
-
response.read_body { |chunk| yield chunk }
|
54
|
-
end
|
55
|
-
end
|
46
|
+
stream_url(location + "&tail", &blk)
|
56
47
|
else
|
57
48
|
yield redirect[:body]
|
58
49
|
end
|
@@ -70,6 +61,14 @@ module CFoundry::V2
|
|
70
61
|
get("v2", "apps", guid, "stats", :accept => :json)
|
71
62
|
end
|
72
63
|
|
64
|
+
def update_app(guid, diff, async = false)
|
65
|
+
put("v2", "apps", guid,
|
66
|
+
:content => :json,
|
67
|
+
:payload => diff,
|
68
|
+
:return_response => true,
|
69
|
+
:params => { :stage_async => !!async })
|
70
|
+
end
|
71
|
+
|
73
72
|
def all_pages(paginated)
|
74
73
|
payload = paginated[:resources]
|
75
74
|
|