scalingo 3.5.0 → 4.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (241) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +41 -0
  3. data/CHANGELOG.md +14 -1
  4. data/Gemfile +2 -0
  5. data/README.md +13 -62
  6. data/bin/console +11 -1
  7. data/bin/lint +2 -0
  8. data/bin/setup +21 -6
  9. data/bin/specs +2 -0
  10. data/lib/scalingo/api/client.rb +21 -39
  11. data/lib/scalingo/api/endpoint.rb +88 -11
  12. data/lib/scalingo/auth/keys.rb +4 -50
  13. data/lib/scalingo/auth/scm_integrations.rb +4 -51
  14. data/lib/scalingo/auth/tokens.rb +5 -72
  15. data/lib/scalingo/auth/two_factor_auth.rb +4 -55
  16. data/lib/scalingo/auth/user.rb +3 -38
  17. data/lib/scalingo/bearer_token.rb +16 -9
  18. data/lib/scalingo/billing/profile.rb +3 -40
  19. data/lib/scalingo/client.rb +21 -31
  20. data/lib/scalingo/configuration.rb +0 -24
  21. data/lib/scalingo/core_client.rb +9 -29
  22. data/lib/scalingo/database/backups.rb +9 -0
  23. data/lib/scalingo/database/databases.rb +8 -0
  24. data/lib/scalingo/{regional_database.rb → database.rb} +3 -3
  25. data/lib/scalingo/faraday/extract_meta.rb +33 -0
  26. data/lib/scalingo/faraday/extract_root_value.rb +18 -0
  27. data/lib/scalingo/faraday/response.rb +36 -0
  28. data/lib/scalingo/regional/addons.rb +18 -130
  29. data/lib/scalingo/regional/apps.rb +10 -103
  30. data/lib/scalingo/regional/autoscalers.rb +5 -64
  31. data/lib/scalingo/regional/collaborators.rb +4 -51
  32. data/lib/scalingo/regional/containers.rb +4 -51
  33. data/lib/scalingo/regional/deployments.rb +3 -38
  34. data/lib/scalingo/regional/domains.rb +5 -64
  35. data/lib/scalingo/regional/environment.rb +6 -77
  36. data/lib/scalingo/regional/events.rb +5 -50
  37. data/lib/scalingo/regional/logs.rb +10 -28
  38. data/lib/scalingo/regional/metrics.rb +2 -34
  39. data/lib/scalingo/regional/notifiers.rb +7 -90
  40. data/lib/scalingo/regional/operations.rb +5 -18
  41. data/lib/scalingo/regional/scm_repo_links.rb +8 -103
  42. data/lib/scalingo/token_holder.rb +1 -46
  43. data/lib/scalingo/version.rb +1 -1
  44. data/scalingo.gemspec +3 -0
  45. metadata +59 -200
  46. data/lib/scalingo/api/response.rb +0 -69
  47. data/lib/scalingo/regional_database/backups.rb +0 -44
  48. data/lib/scalingo/regional_database/databases.rb +0 -31
  49. data/samples/auth/keys/_meta.json +0 -13
  50. data/samples/auth/keys/all-200.json +0 -62
  51. data/samples/auth/keys/create-201.json +0 -67
  52. data/samples/auth/keys/create-422.json +0 -34
  53. data/samples/auth/keys/destroy-204.json +0 -19
  54. data/samples/auth/keys/destroy-404.json +0 -19
  55. data/samples/auth/keys/show-200.json +0 -60
  56. data/samples/auth/keys/show-404.json +0 -19
  57. data/samples/auth/scm_integrations/_meta.json +0 -14
  58. data/samples/auth/scm_integrations/all-200.json +0 -41
  59. data/samples/auth/scm_integrations/create-201.json +0 -41
  60. data/samples/auth/scm_integrations/create-422.json +0 -36
  61. data/samples/auth/scm_integrations/destroy-204.json +0 -15
  62. data/samples/auth/scm_integrations/destroy-404.json +0 -23
  63. data/samples/auth/scm_integrations/show-200.json +0 -34
  64. data/samples/auth/scm_integrations/show-404.json +0 -23
  65. data/samples/auth/tokens/_meta.json +0 -13
  66. data/samples/auth/tokens/all-200.json +0 -32
  67. data/samples/auth/tokens/create-201.json +0 -37
  68. data/samples/auth/tokens/destroy-204.json +0 -19
  69. data/samples/auth/tokens/destroy-404.json +0 -19
  70. data/samples/auth/tokens/exchange-200.json +0 -25
  71. data/samples/auth/tokens/exchange-401.json +0 -24
  72. data/samples/auth/tokens/renew-200.json +0 -32
  73. data/samples/auth/tokens/renew-404.json +0 -20
  74. data/samples/auth/two_factor_auth/_meta.json +0 -10
  75. data/samples/auth/two_factor_auth/disable-not-initiated.json +0 -23
  76. data/samples/auth/two_factor_auth/disable-success.json +0 -29
  77. data/samples/auth/two_factor_auth/initiate-already-enabled.json +0 -29
  78. data/samples/auth/two_factor_auth/initiate-success.json +0 -36
  79. data/samples/auth/two_factor_auth/initiate-wrong-provider.json +0 -29
  80. data/samples/auth/two_factor_auth/status.json +0 -29
  81. data/samples/auth/two_factor_auth/validate-not-initiated.json +0 -29
  82. data/samples/auth/two_factor_auth/validate-success.json +0 -49
  83. data/samples/auth/two_factor_auth/validate-wrong.json +0 -29
  84. data/samples/auth/user/_meta.json +0 -10
  85. data/samples/auth/user/self.json +0 -54
  86. data/samples/auth/user/stop-free-trial.json +0 -24
  87. data/samples/auth/user/update-200.json +0 -59
  88. data/samples/auth/user/update-422.json +0 -33
  89. data/samples/billing/profile/_meta.json +0 -23
  90. data/samples/billing/profile/create-201.json +0 -50
  91. data/samples/billing/profile/create-400.json +0 -27
  92. data/samples/billing/profile/create-422.json +0 -44
  93. data/samples/billing/profile/show-200.json +0 -41
  94. data/samples/billing/profile/show-404.json +0 -22
  95. data/samples/billing/profile/update-200.json +0 -47
  96. data/samples/billing/profile/update-422.json +0 -32
  97. data/samples/regional/addons/_meta.json +0 -22
  98. data/samples/regional/addons/categories-guest.json +0 -36
  99. data/samples/regional/addons/categories-logged.json +0 -37
  100. data/samples/regional/addons/destroy-204.json +0 -19
  101. data/samples/regional/addons/destroy-404.json +0 -24
  102. data/samples/regional/addons/find-200.json +0 -48
  103. data/samples/regional/addons/find-404.json +0 -24
  104. data/samples/regional/addons/for-200.json +0 -50
  105. data/samples/regional/addons/providers-guest.json +0 -588
  106. data/samples/regional/addons/providers-logged.json +0 -705
  107. data/samples/regional/addons/provision-201.json +0 -58
  108. data/samples/regional/addons/provision-400.json +0 -29
  109. data/samples/regional/addons/sso-200.json +0 -49
  110. data/samples/regional/addons/sso-404.json +0 -24
  111. data/samples/regional/addons/token-200.json +0 -49
  112. data/samples/regional/addons/token-404.json +0 -24
  113. data/samples/regional/addons/update-200.json +0 -58
  114. data/samples/regional/addons/update-404.json +0 -30
  115. data/samples/regional/apps/_meta.json +0 -52
  116. data/samples/regional/apps/all.json +0 -99
  117. data/samples/regional/apps/create-201.json +0 -66
  118. data/samples/regional/apps/create-422.json +0 -34
  119. data/samples/regional/apps/destroy-204.json +0 -19
  120. data/samples/regional/apps/destroy-404.json +0 -24
  121. data/samples/regional/apps/destroy-422.json +0 -27
  122. data/samples/regional/apps/find-200.json +0 -60
  123. data/samples/regional/apps/find-404.json +0 -24
  124. data/samples/regional/apps/logs_url.json +0 -62
  125. data/samples/regional/apps/rename-200.json +0 -65
  126. data/samples/regional/apps/rename-404.json +0 -29
  127. data/samples/regional/apps/rename-422.json +0 -33
  128. data/samples/regional/apps/transfer-200.json +0 -65
  129. data/samples/regional/apps/transfer-404.json +0 -27
  130. data/samples/regional/apps/transfer-422.json +0 -34
  131. data/samples/regional/apps/update-200.json +0 -66
  132. data/samples/regional/apps/update-stack-404.json +0 -30
  133. data/samples/regional/autoscalers/_meta.json +0 -27
  134. data/samples/regional/autoscalers/create-201.json +0 -49
  135. data/samples/regional/autoscalers/create-500.json +0 -32
  136. data/samples/regional/autoscalers/destroy-204.json +0 -20
  137. data/samples/regional/autoscalers/destroy-404.json +0 -25
  138. data/samples/regional/autoscalers/find-200.json +0 -39
  139. data/samples/regional/autoscalers/find-404.json +0 -25
  140. data/samples/regional/autoscalers/for-200.json +0 -41
  141. data/samples/regional/autoscalers/update-200.json +0 -45
  142. data/samples/regional/autoscalers/update-404.json +0 -31
  143. data/samples/regional/autoscalers/update-500.json +0 -30
  144. data/samples/regional/collaborators/_meta.json +0 -17
  145. data/samples/regional/collaborators/accept-200.json +0 -60
  146. data/samples/regional/collaborators/accept-400.json +0 -24
  147. data/samples/regional/collaborators/accept-404.json +0 -24
  148. data/samples/regional/collaborators/destroy-204.json +0 -19
  149. data/samples/regional/collaborators/destroy-404.json +0 -24
  150. data/samples/regional/collaborators/for-200.json +0 -34
  151. data/samples/regional/collaborators/invite-201.json +0 -37
  152. data/samples/regional/collaborators/invite-422.json +0 -34
  153. data/samples/regional/containers/_meta.json +0 -25
  154. data/samples/regional/containers/for-200.json +0 -39
  155. data/samples/regional/containers/restart-202.json +0 -28
  156. data/samples/regional/containers/restart-422.json +0 -33
  157. data/samples/regional/containers/scale-202.json +0 -48
  158. data/samples/regional/containers/scale-422.json +0 -36
  159. data/samples/regional/containers/sizes-guest.json +0 -115
  160. data/samples/regional/containers/sizes-logged.json +0 -116
  161. data/samples/regional/deployments/_meta.json +0 -8
  162. data/samples/regional/deployments/find-200.json +0 -45
  163. data/samples/regional/deployments/find-404.json +0 -24
  164. data/samples/regional/deployments/for-with-paging.json +0 -35
  165. data/samples/regional/deployments/for-without-pages.json +0 -56
  166. data/samples/regional/deployments/logs-200.json +0 -22
  167. data/samples/regional/deployments/logs-404.json +0 -24
  168. data/samples/regional/domains/_meta.json +0 -21
  169. data/samples/regional/domains/create-201.json +0 -44
  170. data/samples/regional/domains/create-422.json +0 -33
  171. data/samples/regional/domains/destroy-204.json +0 -19
  172. data/samples/regional/domains/destroy-404.json +0 -24
  173. data/samples/regional/domains/find-200.json +0 -38
  174. data/samples/regional/domains/find-404.json +0 -24
  175. data/samples/regional/domains/for-200.json +0 -40
  176. data/samples/regional/domains/update-200.json +0 -44
  177. data/samples/regional/domains/update-404.json +0 -30
  178. data/samples/regional/domains/update-422.json +0 -33
  179. data/samples/regional/environment/_meta.json +0 -43
  180. data/samples/regional/environment/bulk-destroy-204.json +0 -19
  181. data/samples/regional/environment/bulk-update-200.json +0 -70
  182. data/samples/regional/environment/create-201.json +0 -36
  183. data/samples/regional/environment/create-422.json +0 -37
  184. data/samples/regional/environment/destroy-204.json +0 -19
  185. data/samples/regional/environment/destroy-404.json +0 -24
  186. data/samples/regional/environment/for-200.json +0 -31
  187. data/samples/regional/environment/update-200.json +0 -35
  188. data/samples/regional/environment/update-404.json +0 -30
  189. data/samples/regional/events/_meta.json +0 -3
  190. data/samples/regional/events/all-200.json +0 -619
  191. data/samples/regional/events/categories-guest.json +0 -66
  192. data/samples/regional/events/categories-logged.json +0 -67
  193. data/samples/regional/events/for-200.json +0 -404
  194. data/samples/regional/events/types-guest.json +0 -288
  195. data/samples/regional/events/types-logged.json +0 -289
  196. data/samples/regional/logs/_meta.json +0 -11
  197. data/samples/regional/logs/archives-200.json +0 -28
  198. data/samples/regional/logs/get-guest-200.json +0 -18
  199. data/samples/regional/logs/get-logged-200.json +0 -19
  200. data/samples/regional/logs/get-with-limit-200.json +0 -18
  201. data/samples/regional/metrics/_meta.json +0 -15
  202. data/samples/regional/metrics/for-invalid-400.json +0 -23
  203. data/samples/regional/metrics/for-valid-cpu-200.json +0 -747
  204. data/samples/regional/metrics/for-valid-router-404.json +0 -23
  205. data/samples/regional/metrics/types-guest.json +0 -66
  206. data/samples/regional/metrics/types-logged.json +0 -67
  207. data/samples/regional/notifiers/_meta.json +0 -23
  208. data/samples/regional/notifiers/create-201.json +0 -55
  209. data/samples/regional/notifiers/create-404.json +0 -30
  210. data/samples/regional/notifiers/create-422.json +0 -36
  211. data/samples/regional/notifiers/destroy-204.json +0 -19
  212. data/samples/regional/notifiers/destroy-404.json +0 -24
  213. data/samples/regional/notifiers/find-200.json +0 -47
  214. data/samples/regional/notifiers/find-404.json +0 -24
  215. data/samples/regional/notifiers/for-200.json +0 -49
  216. data/samples/regional/notifiers/platforms-guest.json +0 -184
  217. data/samples/regional/notifiers/platforms-logged.json +0 -185
  218. data/samples/regional/notifiers/test-200.json +0 -22
  219. data/samples/regional/notifiers/test-404.json +0 -25
  220. data/samples/regional/notifiers/update-200.json +0 -53
  221. data/samples/regional/operations/_meta.json +0 -5
  222. data/samples/regional/operations/find-200.json +0 -31
  223. data/samples/regional/operations/find-404.json +0 -24
  224. data/samples/regional/scm_repo_links/_meta.json +0 -22
  225. data/samples/regional/scm_repo_links/create-201.json +0 -54
  226. data/samples/regional/scm_repo_links/destroy-204.json +0 -15
  227. data/samples/regional/scm_repo_links/manual-deploy-200.json +0 -32
  228. data/samples/regional/scm_repo_links/show-200.json +0 -43
  229. data/samples/regional/scm_repo_links/update-200.json +0 -50
  230. data/samples/regional_database/backups/_meta.json +0 -4
  231. data/samples/regional_database/backups/archive-200.json +0 -24
  232. data/samples/regional_database/backups/archive-400.json +0 -24
  233. data/samples/regional_database/backups/create-201.json +0 -32
  234. data/samples/regional_database/backups/create-400.json +0 -24
  235. data/samples/regional_database/backups/for-200.json +0 -52
  236. data/samples/regional_database/backups/for-400.json +0 -24
  237. data/samples/regional_database/databases/_meta.json +0 -3
  238. data/samples/regional_database/databases/find-200.json +0 -47
  239. data/samples/regional_database/databases/find-400.json +0 -24
  240. data/samples/regional_database/databases/upgrade-202.json +0 -39
  241. data/samples/regional_database/databases/upgrade-400.json +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 98e17d7eb47e72aad2aefb6a07c6096542b1debd1533b15353b79ae7681c95f0
4
- data.tar.gz: f224c29ecdeaba0fe9bba5d3bd3898fb9ed34de6c1ad6a58fddab89fcce2cedc
3
+ metadata.gz: 2e81dc5d1e89b30c848e17b2ef1ef0cbe73fd96555659225cf4d6bbd3558fa60
4
+ data.tar.gz: 6ebe2d660f1c2d696839d8db23a48932057003390fe55bedebf427898b36df27
5
5
  SHA512:
6
- metadata.gz: bd981473899a583adab7c970aa2255fd0f5c70d112d3358241d512e7c300f7d710d5cc6c71ac73050f07bfe36b7aad0bf74fadc8219cfd7dbf5e245e1a9f80f5
7
- data.tar.gz: 524aea983112d2e5a323151fc974104b8890e7b133f0bcaaa974a7884f93e76caf2f5a78894e831092467f4ea1de4ab5b91d1a3c9ec1becd856e9ff94e499073
6
+ metadata.gz: 3b851279a78169fc9ae01c8eea2c3e5ba9cf9898af057d3941a7d481fb6dd4372b4ada7beb54638c6b6fb0fb734a463bac4773cecc91af0462210a27372d3598
7
+ data.tar.gz: b9617375ab3230a1993874b8fc4b64e34cf07d9e1d7f9fc3341f37dd5c9d885b54cce1099d18bc51ba07fb990dedb6d950eb839efbe6881aea1e849851ba2f79
data/.rubocop.yml CHANGED
@@ -3,6 +3,7 @@ require:
3
3
  - standard-custom
4
4
  - standard-performance
5
5
  - rubocop-performance
6
+ - rubocop-rspec
6
7
 
7
8
  inherit_gem:
8
9
  standard: config/base.yml
@@ -18,3 +19,43 @@ AllCops:
18
19
  - 'tmp/**/*'
19
20
  - 'vendor/**/*'
20
21
  - '.git/**/*'
22
+
23
+ # Line-base counting is not reliable enough
24
+ RSpec/ExampleLength:
25
+ Enabled: false
26
+
27
+ # Deprecated cop, will be removed
28
+ RSpec/FilePath:
29
+ Enabled: false
30
+
31
+ # Not convinced by how strict this cop is by default
32
+ RSpec/MultipleExpectations:
33
+ Enabled: false
34
+
35
+ RSpec/MultipleMemoizedHelpers:
36
+ Enabled: false
37
+
38
+ # Really not convinced by this one
39
+ RSpec/NamedSubject:
40
+ Enabled: false
41
+
42
+ # Default is 3, but 4 is used and relevant (class / method / global state / local state)
43
+ RSpec/NestedGroups:
44
+ Max: 4
45
+
46
+ # Not always relevant for libs
47
+ RSpec/SpecFilePathFormat:
48
+ Exclude:
49
+ - 'spec/scalingo/faraday/**/*'
50
+
51
+ # Not sure about this one - requires more research
52
+ RSpec/MessageSpies:
53
+ Enabled: false
54
+
55
+ # Not sure about this one - requires more research
56
+ RSpec/StubbedMock:
57
+ Enabled: false
58
+
59
+ # Not sure about this one - requires more research
60
+ RSpec/SubjectStub:
61
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,4 +1,17 @@
1
- ## Unreleased
1
+ ## 4.0.beta1 - 2024-04-15
2
+
3
+ * Breaking change: exceptions are raised on error responses (4xx, 5xx) and other errors (connection issue, timeouts)
4
+ * Trying to reach an endpoint without having the client authenticated will raise an exception without attempting the request
5
+ * Same when the token is expired. Expiration date is read from the token directly
6
+ * Associated configuration options have been removed
7
+ * Breaking change: rework DB api exposition
8
+ * Specs: rewrite all specs
9
+ * Breaking change: endpoint methods declaration is simplified:
10
+ * based on URI templates
11
+ * argument and method names are unified
12
+ * one "main" internal method, `Endpoint#request`
13
+ * Breaking change: automatic digging of the value if the reponse body is an object with a single key
14
+ * Breaking change: remove `Scalingo::API::Reponse` in favor of `Faraday::Response`
2
15
 
3
16
  ## 3.5.0 - 2023-12-28
4
17
 
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in scalingo.gemspec
4
4
  gemspec
5
+
6
+ gem "simplecov"
data/README.md CHANGED
@@ -2,18 +2,10 @@
2
2
 
3
3
  A ruby wrapper for the Scalingo API
4
4
 
5
- ### Migration from v2
6
-
7
- This gem is changing its name from `scalingo-ruby-api` to `scalingo`,
8
- and the versioning does **not** reset; the first major version of `scalingo`
9
- will therefore be `3.x.x`.
10
-
11
- You can check the version 2 at [the v2 branch of this repository](https://github.com/Scalingo/scalingo-ruby-api/tree/v2)
12
-
13
5
  ## Installation
14
6
 
15
7
  ```ruby
16
- gem "scalingo", "3.0.0"
8
+ gem "scalingo"
17
9
  ```
18
10
 
19
11
  And then execute:
@@ -29,7 +21,7 @@ require "scalingo"
29
21
 
30
22
  scalingo = Scalingo::Client.new
31
23
  scalingo.authenticate_with(access_token: ENV["SCALINGO_TOKEN"])
32
- scalingo.user.self
24
+ scalingo.self
33
25
  ```
34
26
 
35
27
  ## Conventions
@@ -37,16 +29,13 @@ scalingo.user.self
37
29
  Most methods map to one (and only one) request, and their signature follows this format:
38
30
 
39
31
  ```ruby
40
- client.section.request(id, payload = {}, headers = nil, &block)
32
+ client.section.request(app_id:, id:, body:)
41
33
  ```
42
34
 
43
35
  * Depending on the request, there may be no id (collection and/or singular resource, such as `user`), one, or two ids (many resources are nested under an app).
44
36
  * Most of the time, this library won't do any processing of the payload, but there's a few things to know:
45
- * the root key shouldn't be specified, the library handles it
37
+ * the root key doesn't need to be specified, the library handles it
46
38
  * in some cases, the payload isn't passed as supplied (`metrics`, for instance, extracts the parts that are meant to be used as url fragments)
47
- * headers can be supplied on a per-request basis, using either the last argument or the block version:
48
- * when using the last argument, you may have to pass an empty hash payload (`{}`)
49
- * when using the block form, the faraday object is supplied as argument, and you can do any kind of treatment you would like
50
39
 
51
40
  ## Configuration
52
41
 
@@ -58,23 +47,13 @@ changing the configuration globally will therefore not affect already existing o
58
47
 
59
48
  ```ruby
60
49
  Scalingo.configure do |config|
61
- # Default region. Must be a supported region (agora_fr1, osc_fr1, osc_secnum_fr1)
50
+ # Default region. Must be a supported region (osc_fr1, osc_secnum_fr1)
62
51
  config.default_region = :osc_fr1
63
52
 
64
53
  # Configure the User Agent header
65
54
  config.user_agent = "Scalingo Ruby Client v#{Scalingo::VERSION}"
66
55
 
67
- # For how long is a bearer token considered valid (it will raise passed this delay).
68
- # Set to nil to never raise.
69
- config.exchanged_token_validity = 1.hour
70
-
71
- # Having this setting to true prevents performing requests that would fail due to lack of authentication headers.
72
- config.raise_on_missing_authentication = true
73
-
74
- # Raise an exception when the bearer token in use is supposed to be invalid
75
- config.raise_on_expired_token = false
76
-
77
- # These headers will be added to every request. Individual methods may override them.
56
+ # These headers will be added to every request
78
57
  # This should be a hash or a callable object that returns a hash.
79
58
  config.additional_headers = {}
80
59
 
@@ -87,26 +66,9 @@ You can also configure each client separately.
87
66
  Values not supplied will be copied from the global configuration.
88
67
 
89
68
  ```ruby
90
- scalingo = Scalingo::Client.new(raise_on_expired_token: false)
69
+ scalingo = Scalingo::Client.new(user_agent: "A new kind of agent")
91
70
  ```
92
71
 
93
- ## Response object
94
-
95
- Responses are parsed with the keys symbolized and then encapsulated in a `Scalingo::API::Response` object:
96
-
97
- * `response.status` containts the HTTP status code
98
- * `response.data` contains the "relevant" data, without the json root key (when relevant)
99
- * `response.full_body` contains the full response body
100
- * `response.meta` contains the meta object, if there's any
101
- * `response.headers` containts all the response headers
102
-
103
- Some helper methods are defined on this object:
104
- * `response.successful?` returns true when the code is 2XX
105
- * `response.paginated?` returns true if the reponse has metadata relative to pagination
106
- * `response.operation?` returns true if the response contains a header relative to an ongoing operation
107
- * `response.operation_url` returns the URL to query to get the status of the operation
108
- * `response.operation` performs a request to retrieve the operation
109
-
110
72
  ## Other details on the code architecture
111
73
 
112
74
  * `Scalingo::Client` instances hold configuration and the token used for authentication
@@ -127,13 +89,13 @@ scalingo.authenticate_with(access_token: "my_access_token")
127
89
  scalingo.authenticate_with(bearer_token: "my_bearer_jwt")
128
90
 
129
91
  # Return your profile
130
- scalingo.user.self
92
+ scalingo.self # or scalingo.auth.user.find
131
93
 
132
94
  # List your SSH Keys
133
95
  scalingo.keys.all # OR scalingo.auth.keys.all
134
96
 
135
97
  # Show one SSH Key
136
- scalingo.keys.show("my-key-id")
98
+ scalingo.keys.show(id: "my-key-id")
137
99
 
138
100
  # List your apps on the default region
139
101
  scalingo.apps.all # OR scalingo.region.apps.all
@@ -151,8 +113,6 @@ Requests to the [database API](https://developers.scalingo.com/databases/) requi
151
113
  extra authentication for each addon you want to interact with. [Addon authentication
152
114
  tokens are valid for one hour](https://developers.scalingo.com/addons#get-addon-token).
153
115
 
154
- Supported regions for database API are `db_api_osc_fr1` and `db_api_osc_secnum_fr1`.
155
-
156
116
  ```ruby
157
117
  require "scalingo"
158
118
 
@@ -160,23 +120,20 @@ scalingo = Scalingo::Client.new
160
120
  scalingo.authenticate_with(access_token: "my_access_token")
161
121
 
162
122
  # First, authenticate using the `addons` API
163
- scalingo.osc_fr1.addons.authenticate!(app_id, addon_id)
123
+ dbclient = scalingo.osc_fr1.addons.database_client_for(app_id:, id:)
164
124
 
165
125
  # Once authenticated for that specific addon, you can interact with
166
126
  # database and backup APIs.
167
127
  # IDs of databases are the IDs of the corresponding addons
168
128
 
169
129
  # get all information for a given database
170
- scalingo.db_api_osc_fr1.databases.find(addon_id)
130
+ dbclient.databases.find(id:)
171
131
 
172
132
  # get all backups for a given database
173
- scalingo.db_api_osc_fr1.backups.for(addon_id)
133
+ dbclient.backups.list(addon_id:)
174
134
 
175
135
  # get URL to download backup archive
176
- scalingo.db_api_osc_fr1.backups.archive(addon_id, backup_id)
177
-
178
- # you can omit the region to use the default one
179
- scalingo.databases.find(addon_id)
136
+ dbclient.backups.archive(addon_id:, id:)
180
137
 
181
138
  ```
182
139
 
@@ -193,9 +150,3 @@ bundle
193
150
  ```bash
194
151
  bundle exec rspec
195
152
  ```
196
-
197
- ### Release a new version
198
-
199
- ```bash
200
- # TODO
201
- ```
data/bin/console CHANGED
@@ -3,10 +3,20 @@
3
3
  require "bundler/setup"
4
4
  require "scalingo"
5
5
 
6
+ begin
7
+ require "dotenv"
8
+ Dotenv.load(".env.local")
9
+ rescue LoadError
10
+ puts("dotenv not available - no .env.local loading")
11
+ end
12
+
6
13
  # You can add fixtures and/or initialization code here to make experimenting
7
14
  # with your gem easier. You can also use a different console, if you like.
8
15
 
9
16
  # (If you use this, don't forget to add pry to your Gemfile!)
10
17
  require "pry"
11
18
 
12
- Pry.start
19
+ client = Scalingo::Client.new
20
+ client.authenticate_with(access_token: ENV["SCALINGO_API_TOKEN"]) if ENV["SCALINGO_API_TOKEN"].present?
21
+
22
+ Pry.start(binding, quiet: true)
data/bin/lint ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bash
2
+ bundle exec rubocop $@
data/bin/setup CHANGED
@@ -1,8 +1,23 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
1
+ #!/usr/bin/env ruby
2
+ require "fileutils"
5
3
 
6
- bundle install
4
+ # path to your application root.
5
+ GEM_ROOT = File.expand_path("..", __dir__)
7
6
 
8
- # Do any other automated setup that you need to do here
7
+ def system!(*args)
8
+ system(*args, exception: true)
9
+ end
10
+
11
+ FileUtils.chdir GEM_ROOT do
12
+ # This script is a way to set up or update your development environment automatically.
13
+ # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14
+ # Add necessary setup steps to this file.
15
+
16
+ puts "== Installing dependencies =="
17
+ system! "gem install bundler --conservative"
18
+ system("bundle check") || system!("bundle install")
19
+
20
+ # Creating useful but unversioned env files
21
+ puts "\n== Creating .env.*.local files =="
22
+ FileUtils.touch(".env.local")
23
+ end
data/bin/specs ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bash
2
+ bundle exec rspec $@
@@ -1,14 +1,20 @@
1
1
  require "scalingo/token_holder"
2
+ require "scalingo/faraday/response"
3
+ require "scalingo/faraday/extract_meta"
4
+ require "scalingo/faraday/extract_root_value"
5
+ require "active_support/core_ext/hash"
2
6
 
3
7
  module Scalingo
4
8
  module API
5
9
  class Client
6
10
  include TokenHolder
7
11
 
8
- attr_reader :config, :token_holder, :url
12
+ attr_reader :config, :token_holder, :url, :region
9
13
 
10
- def initialize(url, scalingo: nil, config: {})
14
+ def initialize(url, scalingo: nil, region: nil, config: {})
11
15
  @url = url
16
+ @region = region
17
+
12
18
  parent_config = Scalingo.config
13
19
 
14
20
  if scalingo
@@ -40,15 +46,15 @@ module Scalingo
40
46
  end
41
47
  end
42
48
 
49
+ # :nocov:
43
50
  def inspect
44
51
  str = %(<#{self.class}:0x#{object_id.to_s(16)} url:"#{@url}" methods:)
45
52
 
46
- methods = self.class.instance_methods - Scalingo::API::Client.instance_methods
47
- str << methods.to_s
48
-
53
+ str << self.class.instance_methods(false).to_s
49
54
  str << ">"
50
55
  str
51
56
  end
57
+ # :nocov:
52
58
 
53
59
  ## Faraday objects
54
60
  def headers
@@ -71,59 +77,35 @@ module Scalingo
71
77
  }
72
78
  end
73
79
 
74
- # Note: when `config.raise_on_missing_authentication` is set to false,
75
- # this method may return the unauthenticated connection
76
- # even with `fallback_to_guest: false`
77
- def connection(fallback_to_guest: false)
78
- authenticated_connection
79
- rescue Error::Unauthenticated
80
- raise unless fallback_to_guest
81
-
82
- unauthenticated_connection
83
- end
84
-
85
- def unauthenticated_connection
86
- @unauthenticated_conn ||= Faraday.new(connection_options) { |conn|
80
+ def guest_connection
81
+ @guest_connection ||= Faraday.new(connection_options) { |conn|
82
+ conn.response :extract_root_value
83
+ conn.response :extract_meta
87
84
  conn.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
85
+ conn.response :raise_error
88
86
  conn.request :json
89
87
 
90
88
  conn.adapter(config.faraday_adapter) if config.faraday_adapter
91
89
  }
92
90
  end
93
91
 
94
- def authenticated_connection
92
+ def connection
95
93
  return @connection if @connection
96
94
 
97
95
  # Missing token handling. Token expiration is handled in the `value` method.
98
- unless token_holder.token&.value
99
- if config.raise_on_missing_authentication
100
- raise Error::Unauthenticated
101
- else
102
- return unauthenticated_connection
103
- end
104
- end
96
+ raise Error::Unauthenticated unless token_holder.token&.value&.present?
105
97
 
106
98
  @connection = Faraday.new(connection_options) { |conn|
99
+ conn.response :extract_root_value
100
+ conn.response :extract_meta
107
101
  conn.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
108
102
  conn.request :json
103
+ conn.response :raise_error
109
104
  conn.request :authorization, "Bearer", -> { token_holder.token&.value }
110
105
 
111
106
  conn.adapter(config.faraday_adapter) if config.faraday_adapter
112
107
  }
113
108
  end
114
-
115
- def database_connection(database_id)
116
- raise Error::Unauthenticated unless token_holder.authenticated_for_database?(database_id)
117
-
118
- @database_connections ||= {}
119
- @database_connections[database_id] ||= Faraday.new(connection_options) { |conn|
120
- conn.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
121
- conn.request :json
122
- conn.request :authorization, "Bearer", -> { token_holder.database_tokens[database_id]&.value }
123
-
124
- conn.adapter(config.faraday_adapter) if config.faraday_adapter
125
- }
126
- end
127
109
  end
128
110
  end
129
111
  end
@@ -1,5 +1,5 @@
1
+ require "addressable/template"
1
2
  require "forwardable"
2
- require "scalingo/api/response"
3
3
 
4
4
  module Scalingo
5
5
  module API
@@ -7,28 +7,105 @@ module Scalingo
7
7
  extend Forwardable
8
8
  attr_reader :client
9
9
 
10
+ # Add a handler for a given endpoint
11
+ %i[get post put patch delete].each do |method|
12
+ # @example
13
+ # class Example < API::Endpoint
14
+ # get :all, "some-endpoint/{id}/subthings{?query*}", optional: [:query]
15
+ # post :create, "some-endpoint", root_key: :subthing
16
+ # end
17
+ define_singleton_method(method) do |name, path, **default_attrs, &default_block|
18
+ # @example
19
+ # endpoint = Example.new
20
+ # endpoint.all(id: "1", query: {page: 1})
21
+ # endpoint.create(name: "thing")
22
+ define_method(name) do |**runtime_attrs, &runtime_block|
23
+ params = {**default_attrs, **runtime_attrs}
24
+
25
+ request(method, path, **params) do |req|
26
+ default_block&.call(req, params)
27
+ runtime_block&.call(req, params)
28
+ end
29
+ end
30
+ end
31
+
32
+ # Those methods are not meant to be used outside of a class definition
33
+ private_class_method method
34
+ end
35
+
10
36
  def initialize(client)
11
37
  @client = client
12
38
  end
13
39
 
14
40
  def_delegator :client, :connection
15
- def_delegator :client, :database_connection
16
41
 
17
- def inspect
18
- str = %(<#{self.class}:0x#{object_id.to_s(16)} base_url:"#{@client.url}" endpoints:)
42
+ # Perform a request to the API.
43
+ # path can be an URI template; and faraday expect valid URIs - the parser raises when templates aren't fully expanded.
44
+ # therefore, we have to take care of the expansion before passing the path to faraday.
45
+ # note: This method is not unit-tested directly, but integrations tests are covering it extensively.
46
+ # @see https://github.com/sporkmonger/addressable?tab=readme-ov-file#uri-templates
47
+ # @see https://www.rfc-editor.org/rfc/rfc6570.txt
48
+ # @see https://github.com/lostisland/faraday/issues/1487
49
+ def request(method, path, body: nil, root_key: nil, connected: true, basic: nil, dry_run: false, params_as_body: false, **params, &block)
50
+ template = Addressable::Template.new(path)
19
51
 
20
- methods = self.class.instance_methods - Scalingo::API::Endpoint.instance_methods
52
+ # If the template has keys, we need to expand it with the params
53
+ if template.keys.present?
54
+ # We assume every variable in the template is required
55
+ expected_keys = Set.new(template.keys.map(&:to_sym))
56
+ # ... but we can opt out by specifying :optional when performing the request or in the endpoint definition
57
+ expected_keys -= params[:optional] if params[:optional].present?
21
58
 
22
- str << methods.to_s
23
- str << ">"
24
- str
59
+ # if any required key is missing, raise an error with the missing keys,
60
+ # as if it was a regular keyword argument that was not supplied
61
+ if expected_keys.present?
62
+ received_keys = Set.new(params.keys.map(&:to_sym))
63
+
64
+ unless received_keys.superset?(expected_keys)
65
+ missings = (expected_keys - received_keys).map { |item| sprintf("%p", item) }.join(" ")
66
+ raise ArgumentError, "missing keyword: #{missings}"
67
+ end
68
+ end
69
+
70
+ # Now, we can expand the template with the supplied params
71
+ actual_path = template.expand(params).to_s
72
+ else
73
+ # Otherwise, it's not a template but a string to be used as it is
74
+ actual_path = path
75
+ end
76
+
77
+ # we nest the given body under the root_key if it's present
78
+ request_body = body
79
+ request_body = {root_key => body} if request_body && root_key
80
+
81
+ # We can use the client in either connected or unconnected mode
82
+ conn = connected ? client.connection : client.guest_connection
83
+
84
+ # We can specify basic auth credentials if needed
85
+ conn.request :authorization, :basic, basic[:user], basic[:password] if basic.present?
86
+
87
+ # Finally, perform the request.
88
+ # Faraday sends params as query string for GET/HEAD/DELETE requests and as request body for the others;
89
+ # in some rare cases (variables bulk-delete) we need to send them as actual body.
90
+ if Faraday::METHODS_WITH_QUERY.include?(method.to_s) && params_as_body
91
+ conn.public_send(method, actual_path) do |req|
92
+ req.body = request_body
93
+ block&.call(req) || req
94
+ end
95
+ else
96
+ conn.public_send(method, actual_path, request_body, &block)
97
+ end
25
98
  end
26
99
 
27
- private
100
+ # :nocov:
101
+ def inspect
102
+ str = %(<#{self.class}:0x#{object_id.to_s(16)} base_url:"#{@client.url}" endpoints:)
28
103
 
29
- def unpack(*keys, &block)
30
- Response.unpack(client, keys: keys, &block)
104
+ str << self.class.instance_methods(false).to_s
105
+ str << ">"
106
+ str
31
107
  end
108
+ # :nocov:
32
109
  end
33
110
  end
34
111
  end
@@ -2,55 +2,9 @@ require "scalingo/api/endpoint"
2
2
 
3
3
  module Scalingo
4
4
  class Auth::Keys < API::Endpoint
5
- def all(headers = nil, &block)
6
- data = nil
7
-
8
- response = connection.get(
9
- "keys",
10
- data,
11
- headers,
12
- &block
13
- )
14
-
15
- unpack(:keys) { response }
16
- end
17
-
18
- def show(id, headers = nil, &block)
19
- data = nil
20
-
21
- response = connection.get(
22
- "keys/#{id}",
23
- data,
24
- headers,
25
- &block
26
- )
27
-
28
- unpack(:key) { response }
29
- end
30
-
31
- def create(payload, headers = nil, &block)
32
- data = {key: payload}
33
-
34
- response = connection.post(
35
- "keys",
36
- data,
37
- headers,
38
- &block
39
- )
40
-
41
- unpack(:key) { response }
42
- end
43
-
44
- def destroy(id, headers = nil, &block)
45
- data = nil
46
- response = connection.delete(
47
- "keys/#{id}",
48
- data,
49
- headers,
50
- &block
51
- )
52
-
53
- unpack { response }
54
- end
5
+ get :list, "keys"
6
+ get :find, "keys/{id}"
7
+ post :create, "keys", root_key: :key
8
+ delete :delete, "keys/{id}"
55
9
  end
56
10
  end
@@ -2,56 +2,9 @@ require "scalingo/api/endpoint"
2
2
 
3
3
  module Scalingo
4
4
  class Auth::ScmIntegrations < API::Endpoint
5
- def all(headers = nil, &block)
6
- data = nil
7
-
8
- response = connection.get(
9
- "scm_integrations",
10
- data,
11
- headers,
12
- &block
13
- )
14
-
15
- unpack(:scm_integrations) { response }
16
- end
17
-
18
- def show(id, headers = nil, &block)
19
- data = nil
20
-
21
- response = connection.get(
22
- "scm_integrations/#{id}",
23
- data,
24
- headers,
25
- &block
26
- )
27
-
28
- unpack(:scm_integration) { response }
29
- end
30
-
31
- def create(payload, headers = nil, &block)
32
- data = {scm_integration: payload}
33
-
34
- response = connection.post(
35
- "scm_integrations",
36
- data,
37
- headers,
38
- &block
39
- )
40
-
41
- unpack(:scm_integration) { response }
42
- end
43
-
44
- def destroy(id, headers = nil, &block)
45
- data = nil
46
-
47
- response = connection.delete(
48
- "scm_integrations/#{id}",
49
- data,
50
- headers,
51
- &block
52
- )
53
-
54
- unpack { response }
55
- end
5
+ get :list, "scm_integrations"
6
+ get :find, "scm_integrations/{id}"
7
+ post :create, "scm_integrations", root_key: :scm_integration
8
+ delete :delete, "scm_integrations/{id}"
56
9
  end
57
10
  end