nvoi 0.1.5 → 0.1.6

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.
Files changed (130) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/todo/refactor/00-overview.md +171 -0
  3. data/.claude/todo/refactor/01-objects.md +96 -0
  4. data/.claude/todo/refactor/02-utils.md +143 -0
  5. data/.claude/todo/refactor/03-external-cloud.md +164 -0
  6. data/.claude/todo/refactor/04-external-dns.md +104 -0
  7. data/.claude/todo/refactor/05-external.md +133 -0
  8. data/.claude/todo/refactor/06-cli.md +123 -0
  9. data/.claude/todo/refactor/07-cli-deploy-command.md +177 -0
  10. data/.claude/todo/refactor/08-cli-deploy-steps.md +201 -0
  11. data/.claude/todo/refactor/09-cli-delete-command.md +169 -0
  12. data/.claude/todo/refactor/10-cli-exec-command.md +157 -0
  13. data/.claude/todo/refactor/11-cli-credentials-command.md +190 -0
  14. data/.claude/todo/refactor/_target.md +79 -0
  15. data/.claude/todo/scaleway.impl.md +644 -0
  16. data/.claude/todo/scaleway.reference.md +520 -0
  17. data/Gemfile +1 -0
  18. data/Gemfile.lock +12 -2
  19. data/doc/config-schema.yaml +44 -11
  20. data/examples/golang/deploy.enc +0 -0
  21. data/examples/golang/main.go +18 -0
  22. data/exe/nvoi +3 -1
  23. data/lib/nvoi/cli/credentials/edit/command.rb +384 -0
  24. data/lib/nvoi/cli/credentials/show/command.rb +35 -0
  25. data/lib/nvoi/cli/db/command.rb +308 -0
  26. data/lib/nvoi/cli/delete/command.rb +75 -0
  27. data/lib/nvoi/cli/delete/steps/detach_volumes.rb +98 -0
  28. data/lib/nvoi/cli/delete/steps/teardown_dns.rb +49 -0
  29. data/lib/nvoi/cli/delete/steps/teardown_firewall.rb +46 -0
  30. data/lib/nvoi/cli/delete/steps/teardown_network.rb +30 -0
  31. data/lib/nvoi/cli/delete/steps/teardown_server.rb +50 -0
  32. data/lib/nvoi/cli/delete/steps/teardown_tunnel.rb +44 -0
  33. data/lib/nvoi/cli/delete/steps/teardown_volume.rb +61 -0
  34. data/lib/nvoi/cli/deploy/command.rb +184 -0
  35. data/lib/nvoi/cli/deploy/steps/build_image.rb +27 -0
  36. data/lib/nvoi/cli/deploy/steps/cleanup_images.rb +42 -0
  37. data/lib/nvoi/cli/deploy/steps/configure_tunnel.rb +100 -0
  38. data/lib/nvoi/cli/deploy/steps/deploy_service.rb +396 -0
  39. data/lib/nvoi/cli/deploy/steps/provision_network.rb +44 -0
  40. data/lib/nvoi/cli/deploy/steps/provision_server.rb +143 -0
  41. data/lib/nvoi/cli/deploy/steps/provision_volume.rb +171 -0
  42. data/lib/nvoi/cli/deploy/steps/setup_k3s.rb +481 -0
  43. data/lib/nvoi/cli/exec/command.rb +173 -0
  44. data/lib/nvoi/cli.rb +83 -142
  45. data/lib/nvoi/config_api/actions/app.rb +53 -0
  46. data/lib/nvoi/config_api/actions/compute_provider.rb +55 -0
  47. data/lib/nvoi/config_api/actions/database.rb +70 -0
  48. data/lib/nvoi/config_api/actions/env.rb +32 -0
  49. data/lib/nvoi/config_api/actions/secret.rb +32 -0
  50. data/lib/nvoi/config_api/actions/server.rb +66 -0
  51. data/lib/nvoi/config_api/actions/volume.rb +40 -0
  52. data/lib/nvoi/config_api/base.rb +44 -0
  53. data/lib/nvoi/config_api/result.rb +26 -0
  54. data/lib/nvoi/config_api.rb +70 -0
  55. data/lib/nvoi/errors.rb +68 -50
  56. data/lib/nvoi/external/cloud/aws.rb +425 -0
  57. data/lib/nvoi/external/cloud/base.rb +99 -0
  58. data/lib/nvoi/external/cloud/factory.rb +48 -0
  59. data/lib/nvoi/external/cloud/hetzner.rb +376 -0
  60. data/lib/nvoi/external/cloud/scaleway.rb +533 -0
  61. data/lib/nvoi/external/cloud.rb +15 -0
  62. data/lib/nvoi/external/containerd.rb +82 -0
  63. data/lib/nvoi/external/database/mysql.rb +84 -0
  64. data/lib/nvoi/external/database/postgres.rb +82 -0
  65. data/lib/nvoi/external/database/provider.rb +65 -0
  66. data/lib/nvoi/external/database/sqlite.rb +72 -0
  67. data/lib/nvoi/external/database.rb +22 -0
  68. data/lib/nvoi/external/dns/cloudflare.rb +292 -0
  69. data/lib/nvoi/external/kubectl.rb +65 -0
  70. data/lib/nvoi/external/ssh.rb +106 -0
  71. data/lib/nvoi/objects/config_override.rb +60 -0
  72. data/lib/nvoi/objects/configuration.rb +463 -0
  73. data/lib/nvoi/objects/database.rb +56 -0
  74. data/lib/nvoi/objects/dns.rb +14 -0
  75. data/lib/nvoi/objects/firewall.rb +11 -0
  76. data/lib/nvoi/objects/network.rb +11 -0
  77. data/lib/nvoi/objects/server.rb +14 -0
  78. data/lib/nvoi/objects/service_spec.rb +26 -0
  79. data/lib/nvoi/objects/tunnel.rb +14 -0
  80. data/lib/nvoi/objects/volume.rb +17 -0
  81. data/lib/nvoi/utils/config_loader.rb +172 -0
  82. data/lib/nvoi/utils/constants.rb +61 -0
  83. data/lib/nvoi/{credentials/manager.rb → utils/credential_store.rb} +16 -16
  84. data/lib/nvoi/{credentials → utils}/crypto.rb +8 -5
  85. data/lib/nvoi/{config → utils}/env_resolver.rb +10 -2
  86. data/lib/nvoi/utils/logger.rb +84 -0
  87. data/lib/nvoi/{config/naming.rb → utils/namer.rb} +28 -25
  88. data/lib/nvoi/{deployer → utils}/retry.rb +23 -3
  89. data/lib/nvoi/utils/templates.rb +62 -0
  90. data/lib/nvoi/version.rb +1 -1
  91. data/lib/nvoi.rb +10 -54
  92. data/templates/error-backend.yaml.erb +134 -0
  93. metadata +97 -44
  94. data/examples/golang/deploy.yml +0 -54
  95. data/lib/nvoi/cloudflare/client.rb +0 -287
  96. data/lib/nvoi/config/config.rb +0 -248
  97. data/lib/nvoi/config/loader.rb +0 -102
  98. data/lib/nvoi/config/ssh_keys.rb +0 -82
  99. data/lib/nvoi/config/types.rb +0 -274
  100. data/lib/nvoi/constants.rb +0 -59
  101. data/lib/nvoi/credentials/editor.rb +0 -272
  102. data/lib/nvoi/deployer/cleaner.rb +0 -36
  103. data/lib/nvoi/deployer/image_builder.rb +0 -23
  104. data/lib/nvoi/deployer/infrastructure.rb +0 -126
  105. data/lib/nvoi/deployer/orchestrator.rb +0 -146
  106. data/lib/nvoi/deployer/service_deployer.rb +0 -311
  107. data/lib/nvoi/deployer/tunnel_manager.rb +0 -57
  108. data/lib/nvoi/deployer/types.rb +0 -8
  109. data/lib/nvoi/k8s/renderer.rb +0 -44
  110. data/lib/nvoi/k8s/templates.rb +0 -29
  111. data/lib/nvoi/logger.rb +0 -72
  112. data/lib/nvoi/providers/aws.rb +0 -403
  113. data/lib/nvoi/providers/base.rb +0 -111
  114. data/lib/nvoi/providers/hetzner.rb +0 -288
  115. data/lib/nvoi/providers/hetzner_client.rb +0 -170
  116. data/lib/nvoi/remote/docker_manager.rb +0 -203
  117. data/lib/nvoi/remote/ssh_executor.rb +0 -72
  118. data/lib/nvoi/remote/volume_manager.rb +0 -103
  119. data/lib/nvoi/service/delete.rb +0 -234
  120. data/lib/nvoi/service/deploy.rb +0 -80
  121. data/lib/nvoi/service/exec.rb +0 -144
  122. data/lib/nvoi/service/provider.rb +0 -36
  123. data/lib/nvoi/steps/application_deployer.rb +0 -26
  124. data/lib/nvoi/steps/database_provisioner.rb +0 -60
  125. data/lib/nvoi/steps/k3s_cluster_setup.rb +0 -105
  126. data/lib/nvoi/steps/k3s_provisioner.rb +0 -351
  127. data/lib/nvoi/steps/server_provisioner.rb +0 -43
  128. data/lib/nvoi/steps/services_provisioner.rb +0 -29
  129. data/lib/nvoi/steps/tunnel_configurator.rb +0 -66
  130. data/lib/nvoi/steps/volume_provisioner.rb +0 -154
@@ -0,0 +1,520 @@
1
+ # Scaleway API Reference Documentation
2
+
3
+ ## 1. Authentication
4
+
5
+ - **Base URL**: `https://api.scaleway.com`
6
+ - **Auth Header**: `X-Auth-Token: <secret_key>` (NOT Bearer token)
7
+ - **Content-Type**: `application/json`
8
+
9
+ ## 2. API Structure
10
+
11
+ Scaleway uses **zoned** and **regional** APIs:
12
+
13
+ ```
14
+ # Zoned APIs (Instances, Block Storage)
15
+ https://api.scaleway.com/instance/v1/zones/{zone}/servers
16
+ https://api.scaleway.com/block/v1alpha1/zones/{zone}/volumes
17
+
18
+ # Regional APIs (VPC/Private Networks)
19
+ https://api.scaleway.com/vpc/v2/regions/{region}/private-networks
20
+ ```
21
+
22
+ ### Zones
23
+ - `fr-par-1`, `fr-par-2`, `fr-par-3`
24
+ - `nl-ams-1`, `nl-ams-2`, `nl-ams-3`
25
+ - `pl-waw-1`, `pl-waw-2`, `pl-waw-3`
26
+
27
+ ### Regions
28
+ - `fr-par` (Paris)
29
+ - `nl-ams` (Amsterdam)
30
+ - `pl-waw` (Warsaw)
31
+
32
+ ### Zone to Region Mapping
33
+ - `fr-par-1`, `fr-par-2`, `fr-par-3` → `fr-par`
34
+ - `nl-ams-1`, `nl-ams-2`, `nl-ams-3` → `nl-ams`
35
+ - `pl-waw-1`, `pl-waw-2`, `pl-waw-3` → `pl-waw`
36
+
37
+ ---
38
+
39
+ ## 3. Servers (Instances) API
40
+
41
+ ### Endpoints
42
+
43
+ | Operation | Method | Path |
44
+ |-----------|--------|------|
45
+ | List servers | GET | `/instance/v1/zones/{zone}/servers` |
46
+ | Get server | GET | `/instance/v1/zones/{zone}/servers/{id}` |
47
+ | Create server | POST | `/instance/v1/zones/{zone}/servers` |
48
+ | Delete server | DELETE | `/instance/v1/zones/{zone}/servers/{id}` |
49
+ | Server action | POST | `/instance/v1/zones/{zone}/servers/{id}/action` |
50
+ | List server types | GET | `/instance/v1/zones/{zone}/products/servers` |
51
+ | List images | GET | `/instance/v1/zones/{zone}/images` |
52
+
53
+ ### Create Server Request Body
54
+
55
+ ```json
56
+ {
57
+ "name": "my-server",
58
+ "commercial_type": "DEV1-S",
59
+ "image": "881d7a33-4cfa-4046-b5cf-c33cb9c62fb6",
60
+ "project": "697ef834-9bd0-4181-ae29-b0bcd0e574ae",
61
+ "enable_ipv6": false,
62
+ "boot_type": "local",
63
+ "tags": ["tag1", "tag2"],
64
+ "security_group": "sg-uuid"
65
+ }
66
+ ```
67
+
68
+ ### Server Response Structure
69
+
70
+ ```json
71
+ {
72
+ "server": {
73
+ "id": "uuid",
74
+ "name": "my-server",
75
+ "state": "running",
76
+ "commercial_type": "DEV1-S",
77
+ "public_ip": {
78
+ "id": "ip-uuid",
79
+ "address": "51.15.x.x",
80
+ "dynamic": false
81
+ },
82
+ "private_ip": null,
83
+ "volumes": {},
84
+ "security_group": {
85
+ "id": "sg-uuid",
86
+ "name": "sg-name"
87
+ },
88
+ "tags": [],
89
+ "zone": "fr-par-1"
90
+ }
91
+ }
92
+ ```
93
+
94
+ ### Server States
95
+ - `stopped`
96
+ - `stopping`
97
+ - `starting`
98
+ - `running`
99
+ - `locked`
100
+
101
+ ### Server Action Request
102
+
103
+ ```json
104
+ {
105
+ "action": "poweron"
106
+ }
107
+ ```
108
+
109
+ Available actions: `poweron`, `poweroff`, `reboot`, `terminate`
110
+
111
+ ### List Server Types Response
112
+
113
+ ```json
114
+ {
115
+ "servers": {
116
+ "DEV1-S": {
117
+ "monthly_price": 0.0,
118
+ "hourly_price": 0.0,
119
+ "alt_names": [],
120
+ "per_volume_constraint": {
121
+ "l_ssd": {
122
+ "min_size": 20000000000,
123
+ "max_size": 80000000000
124
+ }
125
+ },
126
+ "volumes_constraint": {
127
+ "min_size": 20000000000,
128
+ "max_size": 80000000000
129
+ },
130
+ "ncpus": 2,
131
+ "ram": 2147483648,
132
+ "arch": "x86_64",
133
+ "baremetal": false
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Common Commercial Types
140
+ - `DEV1-S`, `DEV1-M`, `DEV1-L`, `DEV1-XL`
141
+ - `GP1-XS`, `GP1-S`, `GP1-M`, `GP1-L`, `GP1-XL`
142
+ - `PRO2-XXS`, `PRO2-XS`, `PRO2-S`, `PRO2-M`, `PRO2-L`
143
+
144
+ ---
145
+
146
+ ## 4. Security Groups API
147
+
148
+ ### Endpoints
149
+
150
+ | Operation | Method | Path |
151
+ |-----------|--------|------|
152
+ | List | GET | `/instance/v1/zones/{zone}/security_groups` |
153
+ | Get | GET | `/instance/v1/zones/{zone}/security_groups/{id}` |
154
+ | Create | POST | `/instance/v1/zones/{zone}/security_groups` |
155
+ | Update | PUT | `/instance/v1/zones/{zone}/security_groups/{id}` |
156
+ | Delete | DELETE | `/instance/v1/zones/{zone}/security_groups/{id}` |
157
+ | List rules | GET | `/instance/v1/zones/{zone}/security_groups/{id}/rules` |
158
+ | Create rule | POST | `/instance/v1/zones/{zone}/security_groups/{id}/rules` |
159
+ | Delete rule | DELETE | `/instance/v1/zones/{zone}/security_groups/{id}/rules/{rule_id}` |
160
+
161
+ ### Create Security Group Request
162
+
163
+ ```json
164
+ {
165
+ "name": "my-firewall",
166
+ "description": "SSH access",
167
+ "organization": "org-uuid",
168
+ "project": "project-uuid",
169
+ "stateful": true,
170
+ "inbound_default_policy": "drop",
171
+ "outbound_default_policy": "accept",
172
+ "enable_default_security": true
173
+ }
174
+ ```
175
+
176
+ ### Security Group Response
177
+
178
+ ```json
179
+ {
180
+ "security_group": {
181
+ "id": "uuid",
182
+ "name": "my-firewall",
183
+ "description": "SSH access",
184
+ "enable_default_security": true,
185
+ "inbound_default_policy": "drop",
186
+ "outbound_default_policy": "accept",
187
+ "organization": "org-uuid",
188
+ "project": "project-uuid",
189
+ "stateful": true,
190
+ "state": "available",
191
+ "servers": [],
192
+ "zone": "fr-par-1"
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### Create Rule Request
198
+
199
+ ```json
200
+ {
201
+ "protocol": "TCP",
202
+ "direction": "inbound",
203
+ "action": "accept",
204
+ "ip_range": "0.0.0.0/0",
205
+ "dest_port_from": 22,
206
+ "dest_port_to": 22,
207
+ "position": 1,
208
+ "editable": true
209
+ }
210
+ ```
211
+
212
+ ### Rule Protocols
213
+ - `TCP`
214
+ - `UDP`
215
+ - `ICMP`
216
+ - `ANY`
217
+
218
+ ### Rule Directions
219
+ - `inbound`
220
+ - `outbound`
221
+
222
+ ### Rule Actions
223
+ - `accept`
224
+ - `drop`
225
+
226
+ ---
227
+
228
+ ## 5. Private Networks (VPC) API
229
+
230
+ ### Endpoints
231
+
232
+ | Operation | Method | Path |
233
+ |-----------|--------|------|
234
+ | List | GET | `/vpc/v2/regions/{region}/private-networks` |
235
+ | Get | GET | `/vpc/v2/regions/{region}/private-networks/{id}` |
236
+ | Create | POST | `/vpc/v2/regions/{region}/private-networks` |
237
+ | Update | PATCH | `/vpc/v2/regions/{region}/private-networks/{id}` |
238
+ | Delete | DELETE | `/vpc/v2/regions/{region}/private-networks/{id}` |
239
+
240
+ ### Create Private Network Request
241
+
242
+ ```json
243
+ {
244
+ "name": "my-network",
245
+ "project_id": "project-uuid",
246
+ "subnets": ["10.0.1.0/24"],
247
+ "vpc_id": "vpc-uuid",
248
+ "tags": ["tag1"]
249
+ }
250
+ ```
251
+
252
+ ### Private Network Response
253
+
254
+ ```json
255
+ {
256
+ "id": "pn-uuid",
257
+ "name": "my-network",
258
+ "organization_id": "org-uuid",
259
+ "project_id": "project-uuid",
260
+ "region": "fr-par",
261
+ "subnets": [
262
+ {
263
+ "id": "subnet-uuid",
264
+ "subnet": "10.0.1.0/24",
265
+ "created_at": "2024-01-01T00:00:00Z",
266
+ "updated_at": "2024-01-01T00:00:00Z"
267
+ }
268
+ ],
269
+ "vpc_id": "vpc-uuid",
270
+ "dhcp_enabled": true,
271
+ "created_at": "2024-01-01T00:00:00Z",
272
+ "updated_at": "2024-01-01T00:00:00Z",
273
+ "tags": []
274
+ }
275
+ ```
276
+
277
+ ### List Response
278
+
279
+ ```json
280
+ {
281
+ "private_networks": [...],
282
+ "total_count": 1
283
+ }
284
+ ```
285
+
286
+ ---
287
+
288
+ ## 6. Private NICs API (Instance to Network Attachment)
289
+
290
+ ### Endpoints
291
+
292
+ | Operation | Method | Path |
293
+ |-----------|--------|------|
294
+ | List | GET | `/instance/v1/zones/{zone}/servers/{server_id}/private_nics` |
295
+ | Get | GET | `/instance/v1/zones/{zone}/servers/{server_id}/private_nics/{nic_id}` |
296
+ | Create | POST | `/instance/v1/zones/{zone}/servers/{server_id}/private_nics` |
297
+ | Update | PATCH | `/instance/v1/zones/{zone}/servers/{server_id}/private_nics/{nic_id}` |
298
+ | Delete | DELETE | `/instance/v1/zones/{zone}/servers/{server_id}/private_nics/{nic_id}` |
299
+
300
+ ### Create Private NIC Request
301
+
302
+ ```json
303
+ {
304
+ "private_network_id": "pn-uuid",
305
+ "ip_ids": []
306
+ }
307
+ ```
308
+
309
+ ### Private NIC Response
310
+
311
+ ```json
312
+ {
313
+ "private_nic": {
314
+ "id": "nic-uuid",
315
+ "server_id": "server-uuid",
316
+ "private_network_id": "pn-uuid",
317
+ "mac_address": "02:00:00:00:00:01",
318
+ "state": "available",
319
+ "tags": []
320
+ }
321
+ }
322
+ ```
323
+
324
+ ### Private NIC States
325
+ - `available`
326
+ - `syncing`
327
+ - `syncing_error`
328
+
329
+ ---
330
+
331
+ ## 7. Block Storage API
332
+
333
+ ### Endpoints
334
+
335
+ | Operation | Method | Path |
336
+ |-----------|--------|------|
337
+ | List | GET | `/block/v1alpha1/zones/{zone}/volumes` |
338
+ | Get | GET | `/block/v1alpha1/zones/{zone}/volumes/{id}` |
339
+ | Create | POST | `/block/v1alpha1/zones/{zone}/volumes` |
340
+ | Update | PATCH | `/block/v1alpha1/zones/{zone}/volumes/{id}` |
341
+ | Delete | DELETE | `/block/v1alpha1/zones/{zone}/volumes/{id}` |
342
+
343
+ ### Create Volume Request
344
+
345
+ ```json
346
+ {
347
+ "name": "my-volume",
348
+ "perf_iops": 5000,
349
+ "from_empty": {
350
+ "size": 53687091200
351
+ },
352
+ "project_id": "project-uuid",
353
+ "tags": []
354
+ }
355
+ ```
356
+
357
+ ### Volume Response
358
+
359
+ ```json
360
+ {
361
+ "id": "vol-uuid",
362
+ "name": "my-volume",
363
+ "type": "sbs_5k",
364
+ "size": 53687091200,
365
+ "project_id": "project-uuid",
366
+ "zone": "fr-par-1",
367
+ "specs": {
368
+ "perf_iops": 5000,
369
+ "class": "sbs"
370
+ },
371
+ "status": "available",
372
+ "created_at": "2024-01-01T00:00:00Z",
373
+ "updated_at": "2024-01-01T00:00:00Z",
374
+ "references": [],
375
+ "parent_snapshot_id": null,
376
+ "tags": []
377
+ }
378
+ ```
379
+
380
+ ### Volume IOPS Options
381
+ - `5000` - Standard
382
+ - `15000` - High performance
383
+
384
+ ### Volume Statuses
385
+ - `unknown_status`
386
+ - `creating`
387
+ - `available`
388
+ - `in_use`
389
+ - `deleting`
390
+ - `error`
391
+ - `snapshotting`
392
+ - `resizing`
393
+ - `locked`
394
+
395
+ ### Attaching Volumes to Instances
396
+
397
+ Volume attachment is done via Instance API by updating the server's volumes:
398
+
399
+ ```
400
+ PATCH /instance/v1/zones/{zone}/servers/{server_id}
401
+ ```
402
+
403
+ ```json
404
+ {
405
+ "volumes": {
406
+ "0": {
407
+ "id": "root-volume-uuid"
408
+ },
409
+ "1": {
410
+ "id": "block-volume-uuid"
411
+ }
412
+ }
413
+ }
414
+ ```
415
+
416
+ **Important Notes:**
417
+ - Since April 2024, Block API cannot create volumes attached to instances
418
+ - Volumes must be attached via Instance API
419
+ - Include ALL volumes (including root) when updating
420
+
421
+ ---
422
+
423
+ ## 8. VPC API
424
+
425
+ ### Endpoints
426
+
427
+ | Operation | Method | Path |
428
+ |-----------|--------|------|
429
+ | List | GET | `/vpc/v2/regions/{region}/vpcs` |
430
+ | Get | GET | `/vpc/v2/regions/{region}/vpcs/{id}` |
431
+ | Create | POST | `/vpc/v2/regions/{region}/vpcs` |
432
+ | Update | PATCH | `/vpc/v2/regions/{region}/vpcs/{id}` |
433
+ | Delete | DELETE | `/vpc/v2/regions/{region}/vpcs/{id}` |
434
+
435
+ ### Create VPC Request
436
+
437
+ ```json
438
+ {
439
+ "name": "my-vpc",
440
+ "project_id": "project-uuid",
441
+ "tags": [],
442
+ "enable_routing": true
443
+ }
444
+ ```
445
+
446
+ ### VPC Response
447
+
448
+ ```json
449
+ {
450
+ "id": "vpc-uuid",
451
+ "name": "my-vpc",
452
+ "organization_id": "org-uuid",
453
+ "project_id": "project-uuid",
454
+ "region": "fr-par",
455
+ "tags": [],
456
+ "is_default": false,
457
+ "routing_enabled": true,
458
+ "created_at": "2024-01-01T00:00:00Z",
459
+ "updated_at": "2024-01-01T00:00:00Z",
460
+ "private_network_count": 0
461
+ }
462
+ ```
463
+
464
+ ---
465
+
466
+ ## 9. Error Response Format
467
+
468
+ ```json
469
+ {
470
+ "type": "invalid_request_error",
471
+ "message": "Validation Error",
472
+ "fields": {
473
+ "name": ["name is required"]
474
+ }
475
+ }
476
+ ```
477
+
478
+ ### HTTP Status Codes
479
+ - `200` - Success
480
+ - `201` - Created
481
+ - `204` - No Content (successful delete)
482
+ - `400` - Bad Request
483
+ - `401` - Unauthorized
484
+ - `403` - Forbidden
485
+ - `404` - Not Found
486
+ - `409` - Conflict
487
+ - `422` - Unprocessable Entity
488
+ - `429` - Rate Limited
489
+ - `500` - Internal Server Error
490
+
491
+ ---
492
+
493
+ ## 10. Pagination
494
+
495
+ List endpoints support pagination:
496
+
497
+ ```
498
+ GET /instance/v1/zones/{zone}/servers?page=1&per_page=50
499
+ ```
500
+
501
+ Response includes:
502
+ ```json
503
+ {
504
+ "servers": [...],
505
+ "total_count": 100
506
+ }
507
+ ```
508
+
509
+ ---
510
+
511
+ ## 11. Sources
512
+
513
+ - [Scaleway Instance API](https://www.scaleway.com/en/developers/api/instances/)
514
+ - [Scaleway VPC API](https://www.scaleway.com/en/developers/api/vpc/)
515
+ - [Scaleway Block Storage API](https://www.scaleway.com/en/developers/api/block/)
516
+ - [Scaleway CLI - Instance](https://cli.scaleway.com/instance/)
517
+ - [Scaleway CLI - VPC](https://cli.scaleway.com/vpc/)
518
+ - [Scaleway CLI - Block](https://cli.scaleway.com/block/)
519
+ - [Security Groups API Reference](https://bump.sh/demo/hub/scaleway-developers/doc/instance-v1/group/endpoint-security-groups)
520
+ - [Scaleway API Overview](https://www.scaleway.com/en/developers/api/)
data/Gemfile CHANGED
@@ -6,4 +6,5 @@ gemspec
6
6
 
7
7
  group :development, :test do
8
8
  gem "rubocop-rails-omakase", require: false
9
+ gem "simplecov", require: false
9
10
  end
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- nvoi (0.1.5)
4
+ nvoi (0.1.6)
5
5
  aws-sdk-ec2 (~> 1.400)
6
6
  faraday (~> 2.7)
7
7
  net-scp (~> 4.0)
8
8
  net-ssh (~> 7.2)
9
9
  thor (~> 1.3)
10
+ zeitwerk (~> 2.6)
10
11
 
11
12
  GEM
12
13
  remote: https://rubygems.org/
@@ -28,7 +29,7 @@ GEM
28
29
  public_suffix (>= 2.0.2, < 8.0)
29
30
  ast (2.4.3)
30
31
  aws-eventstream (1.4.0)
31
- aws-partitions (1.1191.0)
32
+ aws-partitions (1.1192.0)
32
33
  aws-sdk-core (3.239.2)
33
34
  aws-eventstream (~> 1, >= 1.3.0)
34
35
  aws-partitions (~> 1, >= 1.992.0)
@@ -49,6 +50,7 @@ GEM
49
50
  crack (1.0.1)
50
51
  bigdecimal
51
52
  rexml
53
+ docile (1.4.1)
52
54
  drb (2.2.3)
53
55
  faraday (2.14.0)
54
56
  faraday-net_http (>= 2.0, < 3.5)
@@ -112,6 +114,12 @@ GEM
112
114
  rubocop-rails (>= 2.30)
113
115
  ruby-progressbar (1.13.0)
114
116
  securerandom (0.4.1)
117
+ simplecov (0.22.0)
118
+ docile (~> 1.1)
119
+ simplecov-html (~> 0.11)
120
+ simplecov_json_formatter (~> 0.1)
121
+ simplecov-html (0.13.2)
122
+ simplecov_json_formatter (0.1.4)
115
123
  thor (1.4.0)
116
124
  tzinfo (2.0.6)
117
125
  concurrent-ruby (~> 1.0)
@@ -123,6 +131,7 @@ GEM
123
131
  addressable (>= 2.8.0)
124
132
  crack (>= 0.3.2)
125
133
  hashdiff (>= 0.4.0, < 2.0.0)
134
+ zeitwerk (2.7.3)
126
135
 
127
136
  PLATFORMS
128
137
  aarch64-linux-gnu
@@ -145,6 +154,7 @@ DEPENDENCIES
145
154
  rake (~> 13.0)
146
155
  rubocop (~> 1.57)
147
156
  rubocop-rails-omakase
157
+ simplecov
148
158
  webmock (~> 3.19)
149
159
 
150
160
  BUNDLED WITH
@@ -187,6 +187,27 @@ components:
187
187
  minimum: 1
188
188
  default: 1
189
189
  example: 2
190
+ volumes:
191
+ type: object
192
+ description: Block storage volumes attached to this server (key is volume name)
193
+ additionalProperties:
194
+ $ref: "#/components/schemas/ServerVolumeConfig"
195
+ example:
196
+ database:
197
+ size: 20
198
+ uploads:
199
+ size: 10
200
+
201
+ ServerVolumeConfig:
202
+ type: object
203
+ description: Server-attached volume configuration
204
+ properties:
205
+ size:
206
+ type: integer
207
+ description: Volume size in GB
208
+ minimum: 10
209
+ default: 10
210
+ example: 20
190
211
 
191
212
  AppServiceConfig:
192
213
  type: object
@@ -227,13 +248,15 @@ components:
227
248
  description: Service-specific environment variables
228
249
  additionalProperties:
229
250
  type: string
230
- volumes:
251
+ mounts:
231
252
  type: object
232
- description: Volume mounts (key is volume name, value is mount path)
253
+ description: |
254
+ Mount server volumes into the container.
255
+ Key is volume name (must exist in server's volumes), value is container mount path.
256
+ Note: Services with mounts can only run on a single server.
233
257
  additionalProperties:
234
258
  type: string
235
259
  example:
236
- data: /app/data
237
260
  uploads: /app/public/uploads
238
261
 
239
262
  HealthCheckConfig:
@@ -294,10 +317,15 @@ components:
294
317
  type: string
295
318
  description: Docker image for database (managed databases only)
296
319
  example: postgres:15-alpine
297
- volume:
298
- type: string
299
- description: Volume mount path for data persistence
300
- example: /var/lib/postgresql/data
320
+ mount:
321
+ type: object
322
+ description: |
323
+ Mount a server volume for database data persistence.
324
+ Key is volume name (must exist in server's volumes), value is container mount path.
325
+ additionalProperties:
326
+ type: string
327
+ example:
328
+ database: /var/lib/postgresql/data
301
329
  secrets:
302
330
  type: object
303
331
  description: Database secrets (POSTGRES_PASSWORD, etc.)
@@ -331,10 +359,15 @@ components:
331
359
  description: Service environment variables
332
360
  additionalProperties:
333
361
  type: string
334
- volume:
335
- type: string
336
- description: Volume mount path for data persistence
337
- example: /data
362
+ mount:
363
+ type: object
364
+ description: |
365
+ Mount a server volume for data persistence.
366
+ Key is volume name (must exist in server's volumes), value is container mount path.
367
+ additionalProperties:
368
+ type: string
369
+ example:
370
+ redis_data: /data
338
371
 
339
372
  SSHKeyConfig:
340
373
  type: object
Binary file
@@ -97,6 +97,9 @@ func setupRouter() *gin.Engine {
97
97
  // Main endpoint: creates user on every visit, returns all users
98
98
  router.GET("/", handleVisit)
99
99
 
100
+ // Test custom error pages (nginx intercepts 502, 503, 504)
101
+ router.GET("/error/:code", handleError)
102
+
100
103
  return router
101
104
  }
102
105
 
@@ -164,6 +167,21 @@ func handleVisit(c *gin.Context) {
164
167
  })
165
168
  }
166
169
 
170
+ // Error handler for testing custom error pages (nginx intercepts 502, 503, 504)
171
+ func handleError(c *gin.Context) {
172
+ code := c.Param("code")
173
+ switch code {
174
+ case "502":
175
+ c.AbortWithStatus(http.StatusBadGateway)
176
+ case "503":
177
+ c.AbortWithStatus(http.StatusServiceUnavailable)
178
+ case "504":
179
+ c.AbortWithStatus(http.StatusGatewayTimeout)
180
+ default:
181
+ c.JSON(http.StatusOK, gin.H{"usage": "/error/{502,503,504}"})
182
+ }
183
+ }
184
+
167
185
  // Generate random name for demo purposes
168
186
  func generateRandomName() string {
169
187
  firstNames := []string{"Alice", "Bob", "Charlie", "Diana", "Eve", "Frank", "Grace", "Henry", "Ivy", "Jack"}
data/exe/nvoi CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
4
5
  require "nvoi"
6
+ require "nvoi/cli"
5
7
 
6
- Nvoi::CLI.start(ARGV)
8
+ Nvoi::Cli.start(ARGV)