kitchen-linode 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,27 +1,26 @@
1
- # Encoding: UTF-8
1
+ require_relative "../../spec_helper"
2
+ require_relative "../../../lib/kitchen/driver/linode"
2
3
 
3
- require_relative '../../spec_helper'
4
- require_relative '../../../lib/kitchen/driver/linode'
5
-
6
- require 'logger'
7
- require 'stringio'
8
- require 'rspec'
9
- require 'kitchen'
10
- require 'kitchen/driver/linode'
11
- require 'kitchen/provisioner/dummy'
12
- require 'kitchen/transport/dummy'
13
- require 'kitchen/verifier/dummy'
14
- require 'fog'
4
+ require "logger"
5
+ require "stringio"
6
+ require "rspec"
7
+ require "kitchen"
8
+ require "kitchen/driver/linode"
9
+ require "kitchen/provisioner/dummy"
10
+ require "kitchen/transport/dummy"
11
+ require "kitchen/verifier/dummy"
12
+ require "fog/linode"
15
13
 
16
14
  describe Kitchen::Driver::Linode do
17
15
  let(:logged_output) { StringIO.new }
18
16
  let(:logger) { Logger.new(logged_output) }
19
- let(:config) { Hash.new }
20
- let(:state) { Hash.new }
21
- let(:rsa) { File.expand_path('~/.ssh/id_rsa') }
22
- let(:instance_name) { 'the_thing' }
17
+ let(:config) { {} }
18
+ let(:state) { {} }
19
+ let(:rsa) { File.expand_path("~/.ssh/id_rsa") }
20
+ let(:uuid_password) { "397a60bf-c7ac-4f5a-90c8-994fd835af8f" }
21
+ let(:instance_name) { "kitchen-test" }
23
22
  let(:transport) { Kitchen::Transport::Dummy.new }
24
- let(:platform) { Kitchen::Platform.new(name: 'fake_platform') }
23
+ let(:platform) { Kitchen::Platform.new(name: "linode/test") }
25
24
  let(:driver) { Kitchen::Driver::Linode.new(config) }
26
25
 
27
26
  let(:instance) do
@@ -30,7 +29,7 @@ describe Kitchen::Driver::Linode do
30
29
  transport: transport,
31
30
  logger: logger,
32
31
  platform: platform,
33
- to_str: 'instance'
32
+ to_str: "instance"
34
33
  )
35
34
  end
36
35
 
@@ -41,132 +40,297 @@ describe Kitchen::Driver::Linode do
41
40
  .and_return(instance)
42
41
  allow(File).to receive(:exist?).and_call_original
43
42
  allow(File).to receive(:exist?).with(rsa).and_return(true)
43
+ allow(SecureRandom).to receive(:uuid).and_return(uuid_password)
44
+ # skip sleeping so we're not waiting
45
+ Retryable.configure do |config|
46
+ config.sleep_method = lambda { |n| nil }
47
+ end
48
+ allow(driver).to receive(:sleep).and_return(nil)
44
49
  end
45
-
46
- describe '#finalize_config' do
50
+
51
+ describe "#finalize_config" do
47
52
  before(:each) { allow(File).to receive(:exist?).and_return(false) }
48
53
 
49
- context 'private key, public key, and api key provided' do
54
+ context "private key, public key, and api token provided" do
50
55
  let(:config) do
51
- { private_key_path: '/tmp/key',
52
- public_key_path: '/tmp/key.pub',
53
- api_key: 'mykey' }
56
+ { private_key_path: "/tmp/key",
57
+ public_key_path: "/tmp/key.pub",
58
+ linode_token: "mytoken" }
54
59
  end
55
60
 
56
- it 'raises no error' do
61
+ it "raises no error" do
57
62
  expect(driver.finalize_config!(instance)).to be
58
63
  end
59
64
  end
60
65
  end
61
66
 
62
- describe '#initialize' do
63
- context 'default options' do
64
- context 'only a RSA SSH key available for the user' do
67
+ describe "#initialize" do
68
+ context "default options" do
69
+ context "only a RSA SSH key available for the user" do
65
70
  before(:each) do
66
71
  allow(File).to receive(:exist?).and_return(false)
67
72
  allow(File).to receive(:exist?).with(rsa).and_return(true)
73
+ allow(File).to receive(:exist?).with(rsa + ".pub").and_return(true)
68
74
  end
69
75
 
70
- it 'uses the local user\'s RSA private key' do
76
+ it "uses the local user's RSA private key" do
71
77
  expect(driver[:private_key_path]).to eq(rsa)
72
78
  end
73
79
 
74
- it 'uses the local user\'s RSA public key' do
75
- expect(driver[:public_key_path]).to eq(rsa + '.pub')
80
+ it "uses the local user's RSA public key" do
81
+ expect(driver[:public_key_path]).to eq(rsa + ".pub")
76
82
  end
77
83
  end
78
-
79
- nils = [
80
- :server_name,
81
- :password
82
- ]
83
- nils.each do |i|
84
- it "defaults to no #{i}" do
85
- expect(driver[i]).to eq(nil)
86
- end
87
- end
88
-
89
- end
90
- context 'overridden options' do
91
- let(:config) do
92
- {
93
- image: 139,
94
- data_center: 10,
95
- flavor: 2,
96
- kernel: 215,
97
- username: 'someuser',
98
- server_name: 'thisserver',
99
- private_key_path: '/path/to/id_rsa',
100
- public_key_path: '/path/to/id_rsa.pub',
101
- password: 'somepassword'
102
- }
84
+
85
+ it "defaults to no label" do
86
+ expect(driver[:label]).to eq(nil)
103
87
  end
104
88
 
105
- it 'uses all the overridden options' do
106
- drv = driver
107
- config.each do |k, v|
108
- expect(drv[k]).to eq(v)
109
- end
89
+ it "defaults to a UUID as the password" do
90
+ expect(driver[:password]).to eq(uuid_password)
110
91
  end
111
92
 
112
- it 'overrides server name prefix with explicit server name, if given' do
113
- expect(driver[:server_name]).to eq(config[:server_name])
93
+ end
94
+ context "overridden options" do
95
+ config = {
96
+ linode_token: "mytesttoken",
97
+ password: "somepassword",
98
+ label: "thisserver",
99
+ tags: %w{kitchen deleteme},
100
+ hostname: "clevername",
101
+ image: "linode/ubuntu20.04",
102
+ region: "eu-central",
103
+ type: "g6-standard-2",
104
+ stackscript_id: 12345,
105
+ stackscript_data: { test: "1234" },
106
+ swap_size: 256,
107
+ private_ip: true,
108
+ authorized_users: ["timmy"],
109
+ private_key_path: "/path/to/id_rsa",
110
+ public_key_path: "/path/to/id_rsa.pub",
111
+ disable_ssh_password: false,
112
+ api_retries: 2,
113
+ }
114
+
115
+ let(:config) { config }
116
+
117
+ config.each do |key, value|
118
+ it "it uses the overridden #{key} option" do
119
+ expect(driver[key]).to eq(value)
120
+ end
114
121
  end
115
122
  end
116
123
  end
117
-
118
- describe '#create' do
119
- let(:server) do
120
- double(id: 'test123', wait_for: true, public_ip_address: %w(1.2.3.4))
121
- end
124
+
125
+ describe "#create" do
126
+ let(:linode_label) { "kitchen-test_500" }
122
127
  let(:driver) do
123
128
  d = super()
124
- allow(d).to receive(:create_server).and_return(server)
125
- allow(d).to receive(:do_ssh_setup).and_return(true)
129
+ allow(d).to receive(:setup_server).and_return(nil)
126
130
  d
127
131
  end
128
132
 
129
- context 'when a server is already created' do
130
- it 'does not create a new instance' do
131
- state[:server_id] = '1'
133
+ context "when a server is already created" do
134
+ it "does not create a new instance" do
135
+ state[:linode_id] = "1"
132
136
  expect(driver).not_to receive(:create_server)
133
137
  driver.create(state)
134
138
  end
135
139
  end
136
140
 
137
- context 'required options provided' do
138
- let(:config) do
141
+ context "required options provided" do
142
+ let(:driver) do
143
+ d = super()
144
+ allow(d).to receive(:setup_server).and_return(nil)
145
+ allow(d).to receive(:suffixes).and_return((500..505))
146
+ d
147
+ end
148
+ let(:config) {
139
149
  {
140
- username: 'someuser',
141
- api_key: 'somekey',
142
- disable_ssl_validation: false
150
+ linode_token: "somekey",
143
151
  }
152
+ }
153
+ before(:each) do
154
+ ENV["JOB_NAME"] = nil
155
+ ENV["GITHUB_JOB"] = nil
144
156
  end
145
- let(:server) do
146
- double(id: 'test123', wait_for: true, public_ip_address: %w(1.2.3.4))
157
+
158
+ it "returns nil, but modifies the state" do
159
+ post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances")
160
+ .to_return(lambda { |request| create_response(request) })
161
+ list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances")
162
+ .to_return(list_response)
163
+ get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357")
164
+ .to_return(view_response(linode_label, "us-east", "linode/test", "g6-nanode-1"))
165
+ expect(driver.send(:create, state)).to eq(nil)
166
+ expect(post_stub).to have_been_made.times(1)
167
+ expect(list_stub).to have_been_made.times(1)
168
+ expect(get_stub).to have_been_made.times(1)
169
+ expect(state[:linode_id]).to eq(73577357)
170
+ expect(state[:linode_label]).to eq("kitchen-job-kitchen-test_500")
147
171
  end
148
172
 
149
- let(:driver) do
150
- d = described_class.new(config)
151
- allow(d).to receive(:create_server).and_return(server)
152
- allow(server).to receive(:id).and_return('test123')
173
+ it "handles rate limits and connection timeouts like a champ" do
174
+ post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances")
175
+ .to_return(
176
+ create_timeout_response,
177
+ create_timeout_response,
178
+ create_ratelimit_response,
179
+ create_ratelimit_response,
180
+ lambda { |request| create_response(request) }
181
+ )
182
+ list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances")
183
+ .to_return(list_response)
184
+ get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357")
185
+ .to_return(view_response(linode_label, "us-east", "linode/test", "g6-nanode-1"))
186
+ driver.send(:create, state)
187
+ expect(post_stub).to have_been_made.times(5)
188
+ expect(list_stub).to have_been_made.times(1)
189
+ expect(get_stub).to have_been_made.times(1)
190
+ expect(state[:linode_id]).to eq(73577357)
191
+ expect(state[:linode_label]).to eq("kitchen-job-kitchen-test_500")
192
+ end
153
193
 
154
- allow(server).to receive(:wait_for)
155
- .with(an_instance_of(Fixnum)).and_yield
156
- allow(d).to receive(:bourne_shell?).and_return(false)
157
- d
194
+ it "raises an error if we run out of retries" do
195
+ allow(driver).to receive(:sleep).and_return(nil) # skip sleeping so we're not waiting
196
+ post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances")
197
+ .to_return(
198
+ create_timeout_response,
199
+ create_timeout_response,
200
+ create_timeout_response,
201
+ create_timeout_response,
202
+ create_timeout_response
203
+ )
204
+ list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances")
205
+ .to_return(list_response)
206
+ expect { driver.send(:create, state) }.to raise_error(Kitchen::ActionFailed)
207
+ expect(list_stub).to have_been_made.times(1)
208
+ expect(post_stub).to have_been_made.times(5)
158
209
  end
159
210
 
160
- it 'returns nil, but modifies the state' do
161
- expect(driver.send(:create, state)).to eq(nil)
162
- expect(state[:server_id]).to eq('test123')
211
+ it "raises an error if the api says we provided garbage data" do
212
+ allow(driver).to receive(:sleep).and_return(nil) # skip sleeping so we're not waiting
213
+ post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances")
214
+ .to_return(create_bad_response)
215
+ list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances")
216
+ .to_return(list_response)
217
+ expect { driver.send(:create, state) }.to raise_error(Kitchen::UserError)
218
+ expect(list_stub).to have_been_made.times(1)
219
+ expect(post_stub).to have_been_made.times(1)
163
220
  end
164
221
 
165
- it 'throws an Action error when trying to create_server' do
222
+ it "it picks a different suffix when other servers exist" do
223
+ post_stub = stub_request(:post, "https://api.linode.com/v4/linode/instances")
224
+ .to_return(lambda { |request| create_response(request) })
225
+ list_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances")
226
+ .to_return(
227
+ body: '{"data": [{"label": "kitchen-job-kitchen-test_500"}, {"label": "kitchen-job-kitchen-test_501"}], "page": 1, "pages": 1, "results": 2}',
228
+ headers: { "Content-Type" => "application/json" }
229
+ )
230
+ get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357")
231
+ .to_return(view_response("kitchen-job-kitchen-test_502", "us-east", "linode/test", "g6-nanode-1"))
232
+ driver.send(:create, state)
233
+ expect(post_stub).to have_been_made.times(1)
234
+ expect(list_stub).to have_been_made.times(1)
235
+ expect(get_stub).to have_been_made.times(1)
236
+ expect(state[:linode_label]).to eq("kitchen-job-kitchen-test_502")
237
+ end
238
+
239
+ it "throws an Action error when trying to create_server" do
166
240
  allow(driver).to receive(:create_server).and_raise(Fog::Errors::Error)
167
241
  expect { driver.send(:create, state) }.to raise_error(Kitchen::ActionFailed)
168
242
  end
169
243
  end
244
+
245
+ context "when all the label suffixes are taken" do
246
+ let(:compute) {
247
+ double(
248
+ servers: double(
249
+ all: double(
250
+ find: true
251
+ )
252
+ )
253
+ )
254
+ }
255
+ before(:each) do
256
+ {
257
+ compute: compute,
258
+ }.each do |k, v|
259
+ allow_any_instance_of(described_class).to receive(k).and_return(v)
260
+ end
261
+ end
262
+
263
+ it "throws a UserError" do
264
+ expect { driver.send(:create, state) }.to raise_error(Kitchen::UserError)
265
+ end
266
+ end
267
+
268
+ end
269
+
270
+ describe "#destroy" do
271
+ let(:linode_id) { "73577357" }
272
+ let(:linode_label) { "kitchen-test_500" }
273
+ let(:hostname) { "203.0.113.243" }
274
+ let(:state) {
275
+ {
276
+ linode_id: linode_id,
277
+ linode_label: linode_label,
278
+ hostname: hostname,
279
+ }
280
+ }
281
+ let(:config) {
282
+ {
283
+ linode_token: "somekey",
284
+ }
285
+ }
286
+ let(:driver) { described_class.new(config) }
287
+
288
+ context "when a server hasn't been created" do
289
+ it "does not destroy anything" do
290
+ state = {}
291
+ expect(driver).not_to receive(:compute)
292
+ expect(state).not_to receive(:delete)
293
+ driver.destroy(state)
294
+ expect(a_request(:get, "https://api.linode.com/v4/linode/instances/73577357"))
295
+ .not_to have_been_made
296
+ expect(a_request(:delete, "https://api.linode.com/v4/linode/instances/73577357"))
297
+ .not_to have_been_made
298
+ end
299
+ end
300
+
301
+ context "when a server doesn't exist" do
302
+ it "doesn't get nervous about the 404" do
303
+ get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357")
304
+ .to_return(status: [404, "Not Found"])
305
+ expect(state).to receive(:delete).with(:linode_id)
306
+ expect(state).to receive(:delete).with(:linode_label)
307
+ expect(state).to receive(:delete).with(:hostname)
308
+ expect(state).to receive(:delete).with(:ssh_key)
309
+ expect(state).to receive(:delete).with(:password)
310
+ driver.destroy(state)
311
+ expect(get_stub).to have_been_made.times(1)
312
+ expect(a_request(:delete, "https://api.linode.com/v4/linode/instances/73577357"))
313
+ .not_to have_been_made
314
+ end
315
+ end
316
+
317
+ context "when a server exists" do
318
+ it "properly nukes it" do
319
+ get_stub = stub_request(:get, "https://api.linode.com/v4/linode/instances/73577357")
320
+ .to_return(view_response(linode_label, "us-test", "linode/test", "testnode"))
321
+ delete_stub = stub_request(:delete, "https://api.linode.com/v4/linode/instances/73577357")
322
+ .to_return(delete_response)
323
+ expect(state).to receive(:delete).with(:linode_id)
324
+ expect(state).to receive(:delete).with(:linode_label)
325
+ expect(state).to receive(:delete).with(:hostname)
326
+ expect(state).to receive(:delete).with(:ssh_key)
327
+ expect(state).to receive(:delete).with(:password)
328
+ driver.destroy(state)
329
+ expect(get_stub).to have_been_made.times(1)
330
+ expect(delete_stub).to have_been_made.times(1)
331
+ end
332
+ end
333
+
170
334
  end
171
335
 
172
- end
336
+ end
@@ -0,0 +1,67 @@
1
+ HTTP/1.1 200 OK
2
+ Server: nginx
3
+ Date: Thu, 30 Jun 2022 15:22:08 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ X-OAuth-Scopes: images:read_only linodes:read_write stackscripts:read_only
7
+ X-Accepted-OAuth-Scopes: linodes:read_write
8
+ X-Frame-Options: DENY, DENY
9
+ Access-Control-Allow-Origin: *
10
+ Access-Control-Allow-Methods: HEAD, GET, OPTIONS, POST, PUT, DELETE
11
+ Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
12
+ X-Spec-Version: 4.129.0
13
+ X-Customer-UUID: DEADBEEF-DEAD-BEEF-DEADBEEFDEADBEEF
14
+ X-RateLimit-Limit: 10
15
+ X-RateLimit-Remaining: 8
16
+ X-RateLimit-Reset: 1656602552
17
+ Retry-After: 13
18
+ Access-Control-Allow-Credentials: true
19
+ Access-Control-Expose-Headers: X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
20
+ Cache-Control: private, max-age=60, s-maxage=60
21
+ Content-Security-Policy: default-src 'none'
22
+ Vary: Authorization, X-Filter
23
+ X-Content-Type-Options: nosniff
24
+ X-XSS-Protection: 1; mode=block
25
+ Strict-Transport-Security: max-age=31536000
26
+
27
+ {
28
+ "alerts": {
29
+ "cpu": 90,
30
+ "io": 10000,
31
+ "network_in": 10,
32
+ "network_out": 10,
33
+ "transfer_quota": 80
34
+ },
35
+ "backups": {
36
+ "enabled": false,
37
+ "last_successful": null,
38
+ "schedule": {
39
+ "day": null,
40
+ "window": null
41
+ }
42
+ },
43
+ "created": "2022-06-30T15:22:14",
44
+ "group": "",
45
+ "hypervisor": "kvm",
46
+ "id": 73577357,
47
+ "image": "<%= request_body["image"] %>",
48
+ "ipv4": [
49
+ "203.0.113.243"
50
+ ],
51
+ "ipv6": "2001:db8::f03c:93ff:fe92:5e2b/128",
52
+ "label": "<%= request_body["label"] %>",
53
+ "region": "<%= request_body["region"] %>",
54
+ "specs": {
55
+ "disk": 25600,
56
+ "memory": 1024,
57
+ "transfer": 1000,
58
+ "vcpus": 1
59
+ },
60
+ "status": "provisioning",
61
+ "tags": [
62
+ "kitchen"
63
+ ],
64
+ "type": "<%= request_body["type"] %>",
65
+ "updated": "2022-06-30T15:22:14",
66
+ "watchdog_enabled": true
67
+ }
@@ -0,0 +1,26 @@
1
+ HTTP/1.1 400 Bad Request
2
+ Server: nginx
3
+ Date: Thu, 30 Jun 2022 15:50:47 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ X-OAuth-Scopes: images:read_only linodes:read_write stackscripts:read_only
7
+ X-Accepted-OAuth-Scopes: linodes:read_write
8
+ X-Frame-Options: DENY
9
+ Access-Control-Allow-Origin: *
10
+ Access-Control-Allow-Methods: HEAD, GET, OPTIONS, POST, PUT, DELETE
11
+ Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
12
+ X-Spec-Version: 4.129.0
13
+ X-Customer-UUID: DEADBEEF-DEAD-BEEF-DEADBEEFDEADBEEF
14
+ X-RateLimit-Limit: 10
15
+ X-RateLimit-Remaining: 9
16
+ X-RateLimit-Reset: 1656604288
17
+ Retry-After: 30
18
+
19
+ {
20
+ "errors": [
21
+ {
22
+ "field": "type",
23
+ "reason": "A valid plan type by that ID was not found"
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,26 @@
1
+ HTTP/1.1 429 Too Many Requests
2
+ Server: nginx
3
+ Date: Thu, 30 Jun 2022 15:50:47 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ X-OAuth-Scopes: images:read_only linodes:read_write stackscripts:read_only
7
+ X-Accepted-OAuth-Scopes: linodes:read_write
8
+ X-Frame-Options: DENY
9
+ Access-Control-Allow-Origin: *
10
+ Access-Control-Allow-Methods: HEAD, GET, OPTIONS, POST, PUT, DELETE
11
+ Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
12
+ X-Spec-Version: 4.129.0
13
+ X-Customer-UUID: DEADBEEF-DEAD-BEEF-DEADBEEFDEADBEEF
14
+ X-RateLimit-Limit: 10
15
+ X-RateLimit-Remaining: 9
16
+ X-RateLimit-Reset: 1656604288
17
+ Retry-After: 5
18
+
19
+ {
20
+ "errors": [
21
+ {
22
+ "field": "type",
23
+ "reason": "Too many requests"
24
+ }
25
+ ]
26
+ }
@@ -0,0 +1,25 @@
1
+ HTTP/1.1 408 Request Timeout
2
+ Server: nginx
3
+ Date: Thu, 30 Jun 2022 15:50:47 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ X-OAuth-Scopes: images:read_only linodes:read_write stackscripts:read_only
7
+ X-Accepted-OAuth-Scopes: linodes:read_write
8
+ X-Frame-Options: DENY
9
+ Access-Control-Allow-Origin: *
10
+ Access-Control-Allow-Methods: HEAD, GET, OPTIONS, POST, PUT, DELETE
11
+ Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
12
+ X-Spec-Version: 4.129.0
13
+ X-Customer-UUID: DEADBEEF-DEAD-BEEF-DEADBEEFDEADBEEF
14
+ X-RateLimit-Limit: 10
15
+ X-RateLimit-Remaining: 9
16
+ X-RateLimit-Reset: 1656604288
17
+ Retry-After: 30
18
+
19
+ {
20
+ "errors": [
21
+ {
22
+ "reason": "Please try again"
23
+ }
24
+ ]
25
+ }
@@ -0,0 +1,27 @@
1
+ HTTP/1.1 200 OK
2
+ Server: nginx
3
+ Date: Thu, 30 Jun 2022 15:48:42 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ X-OAuth-Scopes: images:read_only linodes:read_write stackscripts:read_only
7
+ X-Accepted-OAuth-Scopes: linodes:read_write
8
+ X-Frame-Options: DENY, DENY
9
+ Access-Control-Allow-Origin: *
10
+ Access-Control-Allow-Methods: HEAD, GET, OPTIONS, POST, PUT, DELETE
11
+ Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
12
+ X-Spec-Version: 4.129.0
13
+ X-Customer-UUID: DEADBEEF-DEAD-BEEF-DEADBEEFDEADBEEF
14
+ X-RateLimit-Limit: 800
15
+ X-RateLimit-Remaining: 799
16
+ X-RateLimit-Reset: 1656604192
17
+ Retry-After: 60
18
+ Access-Control-Allow-Credentials: true
19
+ Access-Control-Expose-Headers: X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
20
+ Cache-Control: private, max-age=60, s-maxage=60
21
+ Content-Security-Policy: default-src 'none'
22
+ Vary: Authorization, X-Filter
23
+ X-Content-Type-Options: nosniff
24
+ X-XSS-Protection: 1; mode=block
25
+ Strict-Transport-Security: max-age=31536000
26
+
27
+ {}
@@ -0,0 +1,34 @@
1
+ HTTP/1.1 200 OK
2
+ Server: nginx
3
+ Date: Thu, 30 Jun 2022 15:29:35 GMT
4
+ Content-Type: application/json
5
+ Transfer-Encoding: chunked
6
+ Connection: keep-alive
7
+ Cache-Control: private, max-age=0, s-maxage=0, no-cache, no-store, private, max-age=60, s-maxage=60
8
+ X-OAuth-Scopes: images:read_only linodes:read_write stackscripts:read_only
9
+ X-Accepted-OAuth-Scopes: linodes:read_only
10
+ X-Frame-Options: DENY, DENY
11
+ Access-Control-Allow-Origin: *
12
+ Access-Control-Allow-Methods: HEAD, GET, OPTIONS, POST, PUT, DELETE
13
+ Access-Control-Allow-Headers: Authorization, Origin, X-Requested-With, Content-Type, Accept, X-Filter
14
+ X-Spec-Version: 4.129.0
15
+ Vary: Authorization, X-Filter, Authorization, X-Filter
16
+ X-Customer-UUID: DEADBEEF-DEAD-BEEF-DEADBEEFDEADBEEF
17
+ X-RateLimit-Limit: 800
18
+ X-RateLimit-Remaining: 798
19
+ X-RateLimit-Reset: 1656603045
20
+ Retry-After: 60
21
+ Access-Control-Allow-Credentials: true
22
+ Access-Control-Expose-Headers: X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Status
23
+ Content-Security-Policy: default-src 'none'
24
+ X-Content-Type-Options: nosniff
25
+ X-XSS-Protection: 1; mode=block
26
+ Strict-Transport-Security: max-age=31536000
27
+ Content-Encoding: gzip
28
+
29
+ {
30
+ "data": [],
31
+ "page": 1,
32
+ "pages": 1,
33
+ "results": 0
34
+ }