kitchen-linode 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }