scalingo 3.5.0 → 4.0.beta2

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 (243) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +1 -1
  3. data/.github/workflows/ruby.yml +1 -1
  4. data/.rubocop.yml +40 -0
  5. data/CHANGELOG.md +22 -1
  6. data/Gemfile +2 -0
  7. data/README.md +13 -62
  8. data/bin/console +11 -1
  9. data/bin/lint +2 -0
  10. data/bin/setup +21 -6
  11. data/bin/specs +2 -0
  12. data/lib/scalingo/api/client.rb +21 -39
  13. data/lib/scalingo/api/endpoint.rb +88 -11
  14. data/lib/scalingo/auth/keys.rb +4 -50
  15. data/lib/scalingo/auth/scm_integrations.rb +4 -51
  16. data/lib/scalingo/auth/tokens.rb +5 -72
  17. data/lib/scalingo/auth/two_factor_auth.rb +4 -55
  18. data/lib/scalingo/auth/user.rb +3 -38
  19. data/lib/scalingo/bearer_token.rb +16 -9
  20. data/lib/scalingo/billing/profile.rb +3 -40
  21. data/lib/scalingo/client.rb +21 -31
  22. data/lib/scalingo/configuration.rb +0 -24
  23. data/lib/scalingo/core_client.rb +9 -29
  24. data/lib/scalingo/database/backups.rb +9 -0
  25. data/lib/scalingo/database/databases.rb +8 -0
  26. data/lib/scalingo/{regional_database.rb → database.rb} +3 -3
  27. data/lib/scalingo/faraday/extract_meta.rb +33 -0
  28. data/lib/scalingo/faraday/extract_root_value.rb +18 -0
  29. data/lib/scalingo/faraday/response.rb +36 -0
  30. data/lib/scalingo/regional/addons.rb +18 -130
  31. data/lib/scalingo/regional/apps.rb +10 -103
  32. data/lib/scalingo/regional/autoscalers.rb +5 -64
  33. data/lib/scalingo/regional/collaborators.rb +4 -51
  34. data/lib/scalingo/regional/containers.rb +4 -51
  35. data/lib/scalingo/regional/deployments.rb +3 -38
  36. data/lib/scalingo/regional/domains.rb +5 -64
  37. data/lib/scalingo/regional/environment.rb +6 -77
  38. data/lib/scalingo/regional/events.rb +5 -50
  39. data/lib/scalingo/regional/logs.rb +10 -28
  40. data/lib/scalingo/regional/metrics.rb +2 -34
  41. data/lib/scalingo/regional/notifiers.rb +7 -90
  42. data/lib/scalingo/regional/operations.rb +5 -18
  43. data/lib/scalingo/regional/scm_repo_links.rb +8 -103
  44. data/lib/scalingo/token_holder.rb +1 -46
  45. data/lib/scalingo/version.rb +1 -1
  46. data/scalingo.gemspec +4 -1
  47. metadata +61 -205
  48. data/lib/scalingo/api/response.rb +0 -69
  49. data/lib/scalingo/regional_database/backups.rb +0 -44
  50. data/lib/scalingo/regional_database/databases.rb +0 -31
  51. data/samples/auth/keys/_meta.json +0 -13
  52. data/samples/auth/keys/all-200.json +0 -62
  53. data/samples/auth/keys/create-201.json +0 -67
  54. data/samples/auth/keys/create-422.json +0 -34
  55. data/samples/auth/keys/destroy-204.json +0 -19
  56. data/samples/auth/keys/destroy-404.json +0 -19
  57. data/samples/auth/keys/show-200.json +0 -60
  58. data/samples/auth/keys/show-404.json +0 -19
  59. data/samples/auth/scm_integrations/_meta.json +0 -14
  60. data/samples/auth/scm_integrations/all-200.json +0 -41
  61. data/samples/auth/scm_integrations/create-201.json +0 -41
  62. data/samples/auth/scm_integrations/create-422.json +0 -36
  63. data/samples/auth/scm_integrations/destroy-204.json +0 -15
  64. data/samples/auth/scm_integrations/destroy-404.json +0 -23
  65. data/samples/auth/scm_integrations/show-200.json +0 -34
  66. data/samples/auth/scm_integrations/show-404.json +0 -23
  67. data/samples/auth/tokens/_meta.json +0 -13
  68. data/samples/auth/tokens/all-200.json +0 -32
  69. data/samples/auth/tokens/create-201.json +0 -37
  70. data/samples/auth/tokens/destroy-204.json +0 -19
  71. data/samples/auth/tokens/destroy-404.json +0 -19
  72. data/samples/auth/tokens/exchange-200.json +0 -25
  73. data/samples/auth/tokens/exchange-401.json +0 -24
  74. data/samples/auth/tokens/renew-200.json +0 -32
  75. data/samples/auth/tokens/renew-404.json +0 -20
  76. data/samples/auth/two_factor_auth/_meta.json +0 -10
  77. data/samples/auth/two_factor_auth/disable-not-initiated.json +0 -23
  78. data/samples/auth/two_factor_auth/disable-success.json +0 -29
  79. data/samples/auth/two_factor_auth/initiate-already-enabled.json +0 -29
  80. data/samples/auth/two_factor_auth/initiate-success.json +0 -36
  81. data/samples/auth/two_factor_auth/initiate-wrong-provider.json +0 -29
  82. data/samples/auth/two_factor_auth/status.json +0 -29
  83. data/samples/auth/two_factor_auth/validate-not-initiated.json +0 -29
  84. data/samples/auth/two_factor_auth/validate-success.json +0 -49
  85. data/samples/auth/two_factor_auth/validate-wrong.json +0 -29
  86. data/samples/auth/user/_meta.json +0 -10
  87. data/samples/auth/user/self.json +0 -54
  88. data/samples/auth/user/stop-free-trial.json +0 -24
  89. data/samples/auth/user/update-200.json +0 -59
  90. data/samples/auth/user/update-422.json +0 -33
  91. data/samples/billing/profile/_meta.json +0 -23
  92. data/samples/billing/profile/create-201.json +0 -50
  93. data/samples/billing/profile/create-400.json +0 -27
  94. data/samples/billing/profile/create-422.json +0 -44
  95. data/samples/billing/profile/show-200.json +0 -41
  96. data/samples/billing/profile/show-404.json +0 -22
  97. data/samples/billing/profile/update-200.json +0 -47
  98. data/samples/billing/profile/update-422.json +0 -32
  99. data/samples/regional/addons/_meta.json +0 -22
  100. data/samples/regional/addons/categories-guest.json +0 -36
  101. data/samples/regional/addons/categories-logged.json +0 -37
  102. data/samples/regional/addons/destroy-204.json +0 -19
  103. data/samples/regional/addons/destroy-404.json +0 -24
  104. data/samples/regional/addons/find-200.json +0 -48
  105. data/samples/regional/addons/find-404.json +0 -24
  106. data/samples/regional/addons/for-200.json +0 -50
  107. data/samples/regional/addons/providers-guest.json +0 -588
  108. data/samples/regional/addons/providers-logged.json +0 -705
  109. data/samples/regional/addons/provision-201.json +0 -58
  110. data/samples/regional/addons/provision-400.json +0 -29
  111. data/samples/regional/addons/sso-200.json +0 -49
  112. data/samples/regional/addons/sso-404.json +0 -24
  113. data/samples/regional/addons/token-200.json +0 -49
  114. data/samples/regional/addons/token-404.json +0 -24
  115. data/samples/regional/addons/update-200.json +0 -58
  116. data/samples/regional/addons/update-404.json +0 -30
  117. data/samples/regional/apps/_meta.json +0 -52
  118. data/samples/regional/apps/all.json +0 -99
  119. data/samples/regional/apps/create-201.json +0 -66
  120. data/samples/regional/apps/create-422.json +0 -34
  121. data/samples/regional/apps/destroy-204.json +0 -19
  122. data/samples/regional/apps/destroy-404.json +0 -24
  123. data/samples/regional/apps/destroy-422.json +0 -27
  124. data/samples/regional/apps/find-200.json +0 -60
  125. data/samples/regional/apps/find-404.json +0 -24
  126. data/samples/regional/apps/logs_url.json +0 -62
  127. data/samples/regional/apps/rename-200.json +0 -65
  128. data/samples/regional/apps/rename-404.json +0 -29
  129. data/samples/regional/apps/rename-422.json +0 -33
  130. data/samples/regional/apps/transfer-200.json +0 -65
  131. data/samples/regional/apps/transfer-404.json +0 -27
  132. data/samples/regional/apps/transfer-422.json +0 -34
  133. data/samples/regional/apps/update-200.json +0 -66
  134. data/samples/regional/apps/update-stack-404.json +0 -30
  135. data/samples/regional/autoscalers/_meta.json +0 -27
  136. data/samples/regional/autoscalers/create-201.json +0 -49
  137. data/samples/regional/autoscalers/create-500.json +0 -32
  138. data/samples/regional/autoscalers/destroy-204.json +0 -20
  139. data/samples/regional/autoscalers/destroy-404.json +0 -25
  140. data/samples/regional/autoscalers/find-200.json +0 -39
  141. data/samples/regional/autoscalers/find-404.json +0 -25
  142. data/samples/regional/autoscalers/for-200.json +0 -41
  143. data/samples/regional/autoscalers/update-200.json +0 -45
  144. data/samples/regional/autoscalers/update-404.json +0 -31
  145. data/samples/regional/autoscalers/update-500.json +0 -30
  146. data/samples/regional/collaborators/_meta.json +0 -17
  147. data/samples/regional/collaborators/accept-200.json +0 -60
  148. data/samples/regional/collaborators/accept-400.json +0 -24
  149. data/samples/regional/collaborators/accept-404.json +0 -24
  150. data/samples/regional/collaborators/destroy-204.json +0 -19
  151. data/samples/regional/collaborators/destroy-404.json +0 -24
  152. data/samples/regional/collaborators/for-200.json +0 -34
  153. data/samples/regional/collaborators/invite-201.json +0 -37
  154. data/samples/regional/collaborators/invite-422.json +0 -34
  155. data/samples/regional/containers/_meta.json +0 -25
  156. data/samples/regional/containers/for-200.json +0 -39
  157. data/samples/regional/containers/restart-202.json +0 -28
  158. data/samples/regional/containers/restart-422.json +0 -33
  159. data/samples/regional/containers/scale-202.json +0 -48
  160. data/samples/regional/containers/scale-422.json +0 -36
  161. data/samples/regional/containers/sizes-guest.json +0 -115
  162. data/samples/regional/containers/sizes-logged.json +0 -116
  163. data/samples/regional/deployments/_meta.json +0 -8
  164. data/samples/regional/deployments/find-200.json +0 -45
  165. data/samples/regional/deployments/find-404.json +0 -24
  166. data/samples/regional/deployments/for-with-paging.json +0 -35
  167. data/samples/regional/deployments/for-without-pages.json +0 -56
  168. data/samples/regional/deployments/logs-200.json +0 -22
  169. data/samples/regional/deployments/logs-404.json +0 -24
  170. data/samples/regional/domains/_meta.json +0 -21
  171. data/samples/regional/domains/create-201.json +0 -44
  172. data/samples/regional/domains/create-422.json +0 -33
  173. data/samples/regional/domains/destroy-204.json +0 -19
  174. data/samples/regional/domains/destroy-404.json +0 -24
  175. data/samples/regional/domains/find-200.json +0 -38
  176. data/samples/regional/domains/find-404.json +0 -24
  177. data/samples/regional/domains/for-200.json +0 -40
  178. data/samples/regional/domains/update-200.json +0 -44
  179. data/samples/regional/domains/update-404.json +0 -30
  180. data/samples/regional/domains/update-422.json +0 -33
  181. data/samples/regional/environment/_meta.json +0 -43
  182. data/samples/regional/environment/bulk-destroy-204.json +0 -19
  183. data/samples/regional/environment/bulk-update-200.json +0 -70
  184. data/samples/regional/environment/create-201.json +0 -36
  185. data/samples/regional/environment/create-422.json +0 -37
  186. data/samples/regional/environment/destroy-204.json +0 -19
  187. data/samples/regional/environment/destroy-404.json +0 -24
  188. data/samples/regional/environment/for-200.json +0 -31
  189. data/samples/regional/environment/update-200.json +0 -35
  190. data/samples/regional/environment/update-404.json +0 -30
  191. data/samples/regional/events/_meta.json +0 -3
  192. data/samples/regional/events/all-200.json +0 -619
  193. data/samples/regional/events/categories-guest.json +0 -66
  194. data/samples/regional/events/categories-logged.json +0 -67
  195. data/samples/regional/events/for-200.json +0 -404
  196. data/samples/regional/events/types-guest.json +0 -288
  197. data/samples/regional/events/types-logged.json +0 -289
  198. data/samples/regional/logs/_meta.json +0 -11
  199. data/samples/regional/logs/archives-200.json +0 -28
  200. data/samples/regional/logs/get-guest-200.json +0 -18
  201. data/samples/regional/logs/get-logged-200.json +0 -19
  202. data/samples/regional/logs/get-with-limit-200.json +0 -18
  203. data/samples/regional/metrics/_meta.json +0 -15
  204. data/samples/regional/metrics/for-invalid-400.json +0 -23
  205. data/samples/regional/metrics/for-valid-cpu-200.json +0 -747
  206. data/samples/regional/metrics/for-valid-router-404.json +0 -23
  207. data/samples/regional/metrics/types-guest.json +0 -66
  208. data/samples/regional/metrics/types-logged.json +0 -67
  209. data/samples/regional/notifiers/_meta.json +0 -23
  210. data/samples/regional/notifiers/create-201.json +0 -55
  211. data/samples/regional/notifiers/create-404.json +0 -30
  212. data/samples/regional/notifiers/create-422.json +0 -36
  213. data/samples/regional/notifiers/destroy-204.json +0 -19
  214. data/samples/regional/notifiers/destroy-404.json +0 -24
  215. data/samples/regional/notifiers/find-200.json +0 -47
  216. data/samples/regional/notifiers/find-404.json +0 -24
  217. data/samples/regional/notifiers/for-200.json +0 -49
  218. data/samples/regional/notifiers/platforms-guest.json +0 -184
  219. data/samples/regional/notifiers/platforms-logged.json +0 -185
  220. data/samples/regional/notifiers/test-200.json +0 -22
  221. data/samples/regional/notifiers/test-404.json +0 -25
  222. data/samples/regional/notifiers/update-200.json +0 -53
  223. data/samples/regional/operations/_meta.json +0 -5
  224. data/samples/regional/operations/find-200.json +0 -31
  225. data/samples/regional/operations/find-404.json +0 -24
  226. data/samples/regional/scm_repo_links/_meta.json +0 -22
  227. data/samples/regional/scm_repo_links/create-201.json +0 -54
  228. data/samples/regional/scm_repo_links/destroy-204.json +0 -15
  229. data/samples/regional/scm_repo_links/manual-deploy-200.json +0 -32
  230. data/samples/regional/scm_repo_links/show-200.json +0 -43
  231. data/samples/regional/scm_repo_links/update-200.json +0 -50
  232. data/samples/regional_database/backups/_meta.json +0 -4
  233. data/samples/regional_database/backups/archive-200.json +0 -24
  234. data/samples/regional_database/backups/archive-400.json +0 -24
  235. data/samples/regional_database/backups/create-201.json +0 -32
  236. data/samples/regional_database/backups/create-400.json +0 -24
  237. data/samples/regional_database/backups/for-200.json +0 -52
  238. data/samples/regional_database/backups/for-400.json +0 -24
  239. data/samples/regional_database/databases/_meta.json +0 -3
  240. data/samples/regional_database/databases/find-200.json +0 -47
  241. data/samples/regional_database/databases/find-400.json +0 -24
  242. data/samples/regional_database/databases/upgrade-202.json +0 -39
  243. 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: 5b71852cdaac8d4401623f89773aa2191bc598dadab8f95c18cf466adb5996f6
4
+ data.tar.gz: 061daaeac33ab02683b9a826a419ab86c04ddf9ecc01a001ef3abf1942c16e2e
5
5
  SHA512:
6
- metadata.gz: bd981473899a583adab7c970aa2255fd0f5c70d112d3358241d512e7c300f7d710d5cc6c71ac73050f07bfe36b7aad0bf74fadc8219cfd7dbf5e245e1a9f80f5
7
- data.tar.gz: 524aea983112d2e5a323151fc974104b8890e7b133f0bcaaa974a7884f93e76caf2f5a78894e831092467f4ea1de4ab5b91d1a3c9ec1becd856e9ff94e499073
6
+ metadata.gz: f06e001affb8d65086b146ef55714fd28f6a3876f456031f6f6c0ee08c60e456592fcaf37c7808e428e9804118b6cc2a1bd564f89738f89e75b1d376edb7b40e
7
+ data.tar.gz: 3114674390afd5031c900713431c34f976ec73969d61025d1e9a0d74b347277ec9829bbdfa70c944f5bf0c3ccbd468b700040e03978ad0a534ad79747ed46dc7
@@ -5,4 +5,4 @@ updates:
5
5
  schedule:
6
6
  interval: "monthly"
7
7
  reviewers:
8
- - "ksol"
8
+ - "aurelien-reeves-scalingo"
@@ -27,7 +27,7 @@ jobs:
27
27
  runs-on: ubuntu-latest
28
28
  strategy:
29
29
  matrix:
30
- ruby-version: ['3.0', '3.1', '3.2', '3.3']
30
+ ruby-version: ['3.1', '3.2', '3.3', '3.4']
31
31
  steps:
32
32
  - uses: actions/checkout@v4
33
33
  - name: Set up Ruby
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,42 @@ 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
+ RSpec/SpecFilePathSuffix:
28
+ Enabled: true
29
+
30
+ # Not convinced by how strict this cop is by default
31
+ RSpec/MultipleExpectations:
32
+ Enabled: false
33
+
34
+ RSpec/MultipleMemoizedHelpers:
35
+ Enabled: false
36
+
37
+ # Really not convinced by this one
38
+ RSpec/NamedSubject:
39
+ Enabled: false
40
+
41
+ # Default is 3, but 4 is used and relevant (class / method / global state / local state)
42
+ RSpec/NestedGroups:
43
+ Max: 4
44
+
45
+ # Not always relevant for libs
46
+ RSpec/SpecFilePathFormat:
47
+ Exclude:
48
+ - 'spec/scalingo/faraday/**/*'
49
+
50
+ # Not sure about this one - requires more research
51
+ RSpec/MessageSpies:
52
+ Enabled: false
53
+
54
+ # Not sure about this one - requires more research
55
+ RSpec/StubbedMock:
56
+ Enabled: false
57
+
58
+ # Not sure about this one - requires more research
59
+ RSpec/SubjectStub:
60
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -1,4 +1,25 @@
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`
15
+
16
+ ## 4.0.beta2 - 2025-03-07
17
+
18
+ * Added support for Ruby on Rails 8.0 ([PR#67](https://github.com/Scalingo/scalingo-ruby-api/pull/67) by [@zaratan](https://github.com/zaratan))
19
+
20
+ ## 3.6.0 - 2025-03-07
21
+
22
+ * Added support for Ruby on Rails 8.0 ([PR#67](https://github.com/Scalingo/scalingo-ruby-api/pull/67) by [@zaratan](https://github.com/zaratan))
2
23
 
3
24
  ## 3.5.0 - 2023-12-28
4
25
 
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