scalingo 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +8 -0
  3. data/.github/workflows/publish.yml +1 -1
  4. data/.github/workflows/ruby.yml +2 -6
  5. data/.sclng/metadata.toml +8 -0
  6. data/CHANGELOG.md +7 -0
  7. data/README.md +32 -1
  8. data/lib/scalingo/api/client.rb +23 -9
  9. data/lib/scalingo/api/endpoint.rb +1 -0
  10. data/lib/scalingo/client.rb +17 -7
  11. data/lib/scalingo/regional/addons.rb +15 -0
  12. data/lib/scalingo/regional_database/backups.rb +44 -0
  13. data/lib/scalingo/regional_database/databases.rb +18 -0
  14. data/lib/scalingo/regional_database.rb +13 -0
  15. data/lib/scalingo/token_holder.rb +44 -13
  16. data/lib/scalingo/version.rb +1 -1
  17. data/samples/regional_database/backups/_meta.json +4 -0
  18. data/samples/regional_database/backups/archive-200.json +24 -0
  19. data/samples/regional_database/backups/archive-400.json +24 -0
  20. data/samples/regional_database/backups/create-201.json +30 -0
  21. data/samples/regional_database/backups/create-400.json +24 -0
  22. data/samples/regional_database/backups/for-200.json +52 -0
  23. data/samples/regional_database/backups/for-400.json +24 -0
  24. data/samples/regional_database/databases/_meta.json +3 -0
  25. data/samples/regional_database/databases/find-200.json +45 -0
  26. data/samples/regional_database/databases/find-400.json +24 -0
  27. data/spec/scalingo/api/client_spec.rb +41 -0
  28. data/spec/scalingo/regional/addons_spec.rb +20 -0
  29. data/spec/scalingo/regional_database/backups_spec.rb +58 -0
  30. data/spec/scalingo/regional_database/databases_spec.rb +23 -0
  31. data/spec/scalingo/regional_database_spec.rb +11 -0
  32. data/spec/scalingo/token_holder_spec.rb +81 -0
  33. metadata +26 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c6c044b3045115cfbb02eb44ff90880f115ad308f72962d3a2e396d094d57a1
4
- data.tar.gz: 108a902824f01dd1bfc7ccd347ce3a1915ff5e68298ad236b8cd786d85f7df1a
3
+ metadata.gz: bc653c0172dd4bc1b2b1cb80948582e0069c058d615f74dcc2bb9fac3d467245
4
+ data.tar.gz: 4482433e8ca28c3a7ab72d2f22471f2ef2f61a5aa6b0f34d40c2faba9ecb5d98
5
5
  SHA512:
6
- metadata.gz: 2bf6692720bdcaaebf692d5c9e714da314acd0e56cbab9d0d616e308efaeb70ec7bc03e0596e94ba80e554daa9f92bc7519dc8da6eb29faf801c11b4f8099a73
7
- data.tar.gz: 2ae4d7349ac2cc5821109273b7adf1d489ee9e99c721694c4c6e1f3449ecf2f7c0a4341aea46874c9bb5fa50d1f4642c48fbc4822fe5409bb646c9ae40322790
6
+ metadata.gz: 708a8fbe40bf16f20e0cb2fcd94506d2e2e21d3c02c0d9e521fc02396897835efb695fda4b472f0eafb48d29e262e3f679166af8c5e1efeab4978bf841539d15
7
+ data.tar.gz: 5d75e30a6bdef5ae37be20869ffb81da7ceb82cd01d1a6cb97387054de87c3e1b4c6e2d6927deaaee22eb8195dd5d6ab0b51e177dfd66b57fc0fca6ec4a289ff
@@ -0,0 +1,8 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "github-actions"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "monthly"
7
+ reviewers:
8
+ - "ksol"
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
 
12
12
  steps:
13
- - uses: actions/checkout@v2
13
+ - uses: actions/checkout@v3
14
14
  - name: Set up Ruby
15
15
  uses: ruby/setup-ruby@v1
16
16
  with:
@@ -1,7 +1,3 @@
1
- # This workflow uses actions that are not certified by GitHub.
2
- # They are provided by a third-party and are governed by
3
- # separate terms of service, privacy policy, and support
4
- # documentation.
5
1
  # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
2
  # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
3
 
@@ -18,7 +14,7 @@ jobs:
18
14
  lint:
19
15
  runs-on: ubuntu-latest
20
16
  steps:
21
- - uses: actions/checkout@v2
17
+ - uses: actions/checkout@v3
22
18
  - name: Set up Ruby
23
19
  uses: ruby/setup-ruby@v1
24
20
  with:
@@ -33,7 +29,7 @@ jobs:
33
29
  matrix:
34
30
  ruby-version: ['2.6', '2.7', '3.0']
35
31
  steps:
36
- - uses: actions/checkout@v2
32
+ - uses: actions/checkout@v3
37
33
  - name: Set up Ruby
38
34
  # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
39
35
  # change this to (see https://github.com/ruby/setup-ruby#versioning):
@@ -0,0 +1,8 @@
1
+ dependencies = []
2
+ description = "Ruby API client"
3
+ destination_server = []
4
+ flags = ["global", "scalingo-app"]
5
+ languages = ["ruby"]
6
+ owner = "kevin@scalingo.com"
7
+ team = "UFS"
8
+ version = "1.1.0"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## Unreleased
2
2
 
3
+ ## 3.2.0 - 2022-12-23
4
+
5
+ * Removal: `Scalingo::Client#agora_fr1` had been removed since the region no longer exists.
6
+ * New: Add `addons#authenticate!` endpoint
7
+ * New API: database API
8
+ * New API: backup API
9
+
3
10
  ## 3.1.0
4
11
 
5
12
  * Compat: support for ActiveSupport (and therefore Rails) 7, @Intrepidd
data/README.md CHANGED
@@ -5,7 +5,7 @@ A ruby wrapper for the Scalingo API
5
5
  ### Migration from v2
6
6
 
7
7
  This gem is changing its name from `scalingo-ruby-api` to `scalingo`,
8
- and the versionning does **not** reset; the first major version of `scalingo`
8
+ and the versioning does **not** reset; the first major version of `scalingo`
9
9
  will therefore be `3.x.x`.
10
10
 
11
11
  You can check the version 2 at [the v2 branch of this repository](https://github.com/Scalingo/scalingo-ruby-api/tree/v2)
@@ -145,6 +145,37 @@ scalingo.osc_fr1.apps.all # OR scalingo.region(:osc_fr1).apps.all
145
145
  scalingo.apps.create(name: "my-new-app", dry_run: true)
146
146
  ```
147
147
 
148
+ ### Interacting with databases
149
+
150
+ Requests to the [database API](https://developers.scalingo.com/databases/) requires
151
+ extra authentication for each addon you want to interact with. [Addon authentication
152
+ tokens are valid for one hour](https://developers.scalingo.com/addons#get-addon-token).
153
+
154
+ Supported regions for database API are `db_api_osc_fr1` and `db_api_osc_secnum_fr1`.
155
+
156
+ ```ruby
157
+ require "scalingo"
158
+
159
+ scalingo = Scalingo::Client.new
160
+ scalingo.authenticate_with(access_token: "my_access_token")
161
+
162
+ # First, authenticate using the `addons` API
163
+ scalingo.osc_fr1.addons.authenticate!(app_id, addon_id)
164
+
165
+ # Once authenticated for that specific addon, you can interact with
166
+ # database and backup APIs.
167
+ # IDs of databases are the IDs of the corresponding addons
168
+
169
+ # get all information for a given database
170
+ scalingo.db_api_osc_fr1.databases.find(addon_id)
171
+
172
+ # get all backups for a given database
173
+ scalingo.db_api_osc_fr1.backups.for(addon_id)
174
+
175
+ # get URL to download backup archive
176
+ scalingo.db_api_osc_fr1.backups.archive(addon_id, backup_id)
177
+ ```
178
+
148
179
  ## Development
149
180
 
150
181
  ### Install
@@ -75,15 +75,11 @@ module Scalingo
75
75
  # this method may return the unauthenticated connection
76
76
  # even with `fallback_to_guest: false`
77
77
  def connection(fallback_to_guest: false)
78
- if fallback_to_guest
79
- begin
80
- authenticated_connection
81
- rescue Error::Unauthenticated
82
- unauthenticated_connection
83
- end
84
- else
85
- authenticated_connection
86
- end
78
+ authenticated_connection
79
+ rescue Error::Unauthenticated
80
+ raise unless fallback_to_guest
81
+
82
+ unauthenticated_connection
87
83
  end
88
84
 
89
85
  def unauthenticated_connection
@@ -119,6 +115,24 @@ module Scalingo
119
115
  conn.adapter(config.faraday_adapter) if config.faraday_adapter
120
116
  }
121
117
  end
118
+
119
+ def database_connection(database_id)
120
+ raise Error::Unauthenticated unless token_holder.authenticated_for_database?(database_id)
121
+
122
+ @database_connections ||= {}
123
+ @database_connections[database_id] ||= Faraday.new(connection_options) { |conn|
124
+ conn.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
125
+ conn.request :json
126
+
127
+ bearer_token = token_holder.database_tokens[database_id]&.value
128
+ if bearer_token
129
+ auth_header = Faraday::Request::Authorization.header "Bearer", bearer_token
130
+ conn.headers[Faraday::Request::Authorization::KEY] = auth_header
131
+ end
132
+
133
+ conn.adapter(config.faraday_adapter) if config.faraday_adapter
134
+ }
135
+ end
122
136
  end
123
137
  end
124
138
  end
@@ -12,6 +12,7 @@ module Scalingo
12
12
  end
13
13
 
14
14
  def_delegator :client, :connection
15
+ def_delegator :client, :database_connection
15
16
 
16
17
  def inspect
17
18
  str = %(<#{self.class}:0x#{object_id.to_s(16)} base_url:"#{@client.url}" endpoints:)
@@ -2,6 +2,7 @@ require "scalingo/core_client"
2
2
  require "scalingo/auth"
3
3
  require "scalingo/billing"
4
4
  require "scalingo/regional"
5
+ require "scalingo/regional_database"
5
6
 
6
7
  module Scalingo
7
8
  class Client < CoreClient
@@ -20,19 +21,13 @@ module Scalingo
20
21
  )
21
22
  end
22
23
 
23
- def agora_fr1
24
- @agora_fr1 ||= Regional.new(
25
- "https://api.agora-fr1.scalingo.com/v1",
26
- scalingo: self,
27
- )
28
- end
29
-
30
24
  def osc_fr1
31
25
  @osc_fr1 ||= Regional.new(
32
26
  "https://api.osc-fr1.scalingo.com/v1",
33
27
  scalingo: self,
34
28
  )
35
29
  end
30
+ alias_method :apps_api_osc_fr1, :osc_fr1
36
31
 
37
32
  def osc_secnum_fr1
38
33
  @osc_secnum_fr1 ||= Regional.new(
@@ -40,5 +35,20 @@ module Scalingo
40
35
  scalingo: self,
41
36
  )
42
37
  end
38
+ alias_method :apps_api_osc_secnum_fr1, :osc_secnum_fr1
39
+
40
+ def db_api_osc_fr1
41
+ @db_api_osc_fr1 ||= RegionalDatabase.new(
42
+ "https://db-api.osc-fr1.scalingo.com/api",
43
+ scalingo: self,
44
+ )
45
+ end
46
+
47
+ def db_api_osc_secnum_fr1
48
+ @db_api_osc_secnum_fr1 ||= RegionalDatabase.new(
49
+ "https://db-api.osc-secnum-fr1.scalingo.com/api",
50
+ scalingo: self,
51
+ )
52
+ end
43
53
  end
44
54
  end
@@ -80,6 +80,21 @@ module Scalingo
80
80
  unpack(:addon) { response }
81
81
  end
82
82
 
83
+ def authenticate!(app_id, addon_id, headers = nil, &block)
84
+ response = token(app_id, addon_id, headers, &block)
85
+ return response unless response.status == 200
86
+
87
+ token = response.data[:token]
88
+ client.token_holder.authenticate_database_with_bearer_token(
89
+ addon_id,
90
+ token,
91
+ expires_at: Time.now + 1.hour,
92
+ raise_on_expired_token: client.config.raise_on_expired_token,
93
+ )
94
+
95
+ response
96
+ end
97
+
83
98
  def token(app_id, addon_id, headers = nil, &block)
84
99
  data = nil
85
100
 
@@ -0,0 +1,44 @@
1
+ require "scalingo/api/endpoint"
2
+
3
+ module Scalingo
4
+ class RegionalDatabase::Backups < API::Endpoint
5
+ def create(addon_id, headers = nil, &block)
6
+ data = nil
7
+
8
+ response = database_connection(addon_id).post(
9
+ "databases/#{addon_id}/backups",
10
+ data,
11
+ headers,
12
+ &block
13
+ )
14
+
15
+ unpack { response }
16
+ end
17
+
18
+ def for(addon_id, headers = nil, &block)
19
+ data = nil
20
+
21
+ response = database_connection(addon_id).get(
22
+ "databases/#{addon_id}/backups",
23
+ data,
24
+ headers,
25
+ &block
26
+ )
27
+
28
+ unpack(:database_backups) { response }
29
+ end
30
+
31
+ def archive(addon_id, backup_id, headers = nil, &block)
32
+ data = nil
33
+
34
+ response = database_connection(addon_id).get(
35
+ "databases/#{addon_id}/backups/#{backup_id}/archive",
36
+ data,
37
+ headers,
38
+ &block
39
+ )
40
+
41
+ unpack { response }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ require "scalingo/api/endpoint"
2
+
3
+ module Scalingo
4
+ class RegionalDatabase::Databases < API::Endpoint
5
+ def find(id, headers = nil, &block)
6
+ data = nil
7
+
8
+ response = database_connection(id).get(
9
+ "databases/#{id}",
10
+ data,
11
+ headers,
12
+ &block
13
+ )
14
+
15
+ unpack(:database) { response }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ require "scalingo/api/client"
2
+
3
+ module Scalingo
4
+ class RegionalDatabase < API::Client
5
+ require "scalingo/regional_database/databases"
6
+ require "scalingo/regional_database/backups"
7
+
8
+ register_handlers!(
9
+ databases: Databases,
10
+ backups: Backups,
11
+ )
12
+ end
13
+ end
@@ -4,28 +4,59 @@ module Scalingo
4
4
  module TokenHolder
5
5
  def self.included(base)
6
6
  base.attr_reader :token
7
+ base.attr_reader :database_tokens
7
8
  end
8
9
 
9
10
  def token=(input)
10
- @token = input.is_a?(BearerToken) ? input : BearerToken.new(input.to_s, raise_on_expired: config.raise_on_expired_token)
11
+ @token = bearer_token(input)
12
+ end
13
+
14
+ def add_database_token(database_id, token)
15
+ @database_tokens ||= {}
16
+ @database_tokens[database_id] = bearer_token(token)
11
17
  end
12
18
 
13
19
  def authenticated?
14
- token.present? && !token.expired?
20
+ valid?(token)
21
+ end
22
+
23
+ def authenticated_for_database?(database_id)
24
+ return false if database_tokens.nil?
25
+ return false unless database_tokens.has_key?(database_id)
26
+
27
+ valid?(database_tokens[database_id])
15
28
  end
16
29
 
17
30
  def authenticate_with_bearer_token(bearer_token, expires_at:, raise_on_expired_token:)
18
- self.token = if expires_at
19
- token = bearer_token.is_a?(BearerToken) ? bearer_token.value : bearer_token.to_s
20
-
21
- BearerToken.new(
22
- token,
23
- expires_at: expires_at,
24
- raise_on_expired: raise_on_expired_token,
25
- )
26
- else
27
- bearer_token
28
- end
31
+ self.token = build_bearer_token(bearer_token, expires_at: expires_at, raise_on_expired_token: raise_on_expired_token)
32
+ end
33
+
34
+ def authenticate_database_with_bearer_token(database_id, bearer_token, expires_at:, raise_on_expired_token:)
35
+ bearer_token = build_bearer_token(bearer_token, expires_at: expires_at, raise_on_expired_token: raise_on_expired_token)
36
+
37
+ add_database_token(database_id, bearer_token)
38
+ end
39
+
40
+ private
41
+
42
+ def valid?(token)
43
+ token.present? && !token.expired?
44
+ end
45
+
46
+ def bearer_token(token)
47
+ token.is_a?(BearerToken) ? token : BearerToken.new(token.to_s, raise_on_expired: config.raise_on_expired_token)
48
+ end
49
+
50
+ def build_bearer_token(bearer_token, expires_at:, raise_on_expired_token:)
51
+ return bearer_token unless expires_at
52
+
53
+ token = bearer_token.is_a?(BearerToken) ? bearer_token.value : bearer_token.to_s
54
+
55
+ BearerToken.new(
56
+ token,
57
+ expires_at: expires_at,
58
+ raise_on_expired: raise_on_expired_token,
59
+ )
29
60
  end
30
61
  end
31
62
  end
@@ -1,3 +1,3 @@
1
1
  module Scalingo
2
- VERSION = "3.1.0"
2
+ VERSION = "3.2.0"
3
3
  end
@@ -0,0 +1,4 @@
1
+ {
2
+ "addon_id": "ad-5ed10967884fef000f5e4fff",
3
+ "backup_id": "5bb95a904ffb096e9a2831b8"
4
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "path": "/databases/ad-5ed10967884fef000f5e4fff/backups/5bb95a904ffb096e9a2831b8/archive",
3
+ "method": "get",
4
+ "request": {
5
+ "headers": {
6
+ "Authorization": "Bearer the-bearer-token"
7
+ }
8
+ },
9
+ "response": {
10
+ "status": 200,
11
+ "headers": {
12
+ "Date": "Fri, 29 May 2020 13:08:59 GMT",
13
+ "Etag": "W/\"a9504bb2f6f87c65ff68074ae787831e\"",
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ "Transfer-Encoding": "chunked",
16
+ "Connection": "keep-alive",
17
+ "Cache-Control": "max-age=0, private, must-revalidate",
18
+ "Referrer-Policy": "strict-origin-when-cross-origin"
19
+ },
20
+ "json_body": {
21
+ "download_url": "https://regional-database.scalingo.test/databases/ad-5ed10967884fef000f5e4fff/backups/5bb95a904ffb096e9a2831b8/download?token=token1234"
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "path": "/databases/ad-5ed10967884fef000f5e4fff/backups/5bb95a904ffb096e9a2831b8/archive",
3
+ "method": "get",
4
+ "request": {
5
+ "headers": {
6
+ "Authorization": "Bearer the-bearer-token"
7
+ }
8
+ },
9
+ "response": {
10
+ "status": 400,
11
+ "headers": {
12
+ "Date": "Fri, 29 May 2020 13:08:59 GMT",
13
+ "Etag": "W/\"a9504bb2f6f87c65ff68074ae787831e\"",
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ "Transfer-Encoding": "chunked",
16
+ "Connection": "keep-alive",
17
+ "Cache-Control": "max-age=0, private, must-revalidate",
18
+ "Referrer-Policy": "strict-origin-when-cross-origin"
19
+ },
20
+ "json_body": {
21
+ "error": "unauthorized"
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "path": "/databases/ad-5ed10967884fef000f5e4fff/backups",
3
+ "method": "post",
4
+ "request": {
5
+ "headers": {
6
+ "Authorization": "Bearer the-bearer-token"
7
+ }
8
+ },
9
+ "response": {
10
+ "status": 201,
11
+ "headers": {
12
+ "Date": "Fri, 29 May 2020 13:08:59 GMT",
13
+ "Etag": "W/\"a9504bb2f6f87c65ff68074ae787831e\"",
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ "Transfer-Encoding": "chunked",
16
+ "Connection": "keep-alive",
17
+ "Cache-Control": "max-age=0, private, must-revalidate",
18
+ "Referrer-Policy": "strict-origin-when-cross-origin"
19
+ },
20
+ "json_body": {
21
+ "id": "5b8b36104ffb090be1ac3ce1",
22
+ "created_at": "2019-07-18T03:00:00.178+02:00",
23
+ "name": "20180902010000_kibana-3938",
24
+ "size": 0,
25
+ "status": "pending",
26
+ "database_id": "597601234ffb097af4f3099b",
27
+ "type": "postgresql"
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "path": "/databases/ad-5ed10967884fef000f5e4fff/backups",
3
+ "method": "post",
4
+ "request": {
5
+ "headers": {
6
+ "Authorization": "Bearer the-bearer-token"
7
+ }
8
+ },
9
+ "response": {
10
+ "status": 400,
11
+ "headers": {
12
+ "Date": "Fri, 29 May 2020 13:08:59 GMT",
13
+ "Etag": "W/\"a9504bb2f6f87c65ff68074ae787831e\"",
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ "Transfer-Encoding": "chunked",
16
+ "Connection": "keep-alive",
17
+ "Cache-Control": "max-age=0, private, must-revalidate",
18
+ "Referrer-Policy": "strict-origin-when-cross-origin"
19
+ },
20
+ "json_body": {
21
+ "error": "unauthorized"
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "path": "/databases/ad-5ed10967884fef000f5e4fff/backups",
3
+ "method": "get",
4
+ "request": {
5
+ "headers": {
6
+ "Authorization": "Bearer the-bearer-token"
7
+ }
8
+ },
9
+ "response": {
10
+ "status": 200,
11
+ "headers": {
12
+ "Date": "Fri, 29 May 2020 13:08:59 GMT",
13
+ "Etag": "W/\"a9504bb2f6f87c65ff68074ae787831e\"",
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ "Transfer-Encoding": "chunked",
16
+ "Connection": "keep-alive",
17
+ "Cache-Control": "max-age=0, private, must-revalidate",
18
+ "Referrer-Policy": "strict-origin-when-cross-origin"
19
+ },
20
+ "json_body": {
21
+ "database_backups": [
22
+ {
23
+ "id": "5bde44904ffb096c714be89c",
24
+ "created_at": "2018-11-04T02:00:00.154+01:00",
25
+ "name": "20181104010000_kibana-3938",
26
+ "size": 0,
27
+ "status": "pending",
28
+ "database_id": "597601234ffb097af4f3099b",
29
+ "type": "postgresql"
30
+ },
31
+ {
32
+ "id": "5bb95a904ffb096e9a2831b8",
33
+ "created_at": "2018-10-07T03:00:00.150+02:00",
34
+ "name": "20181007010000_kibana-3938",
35
+ "size": 0,
36
+ "status": "error",
37
+ "database_id": "597601234ffb097af4f3099b",
38
+ "type": "postgresql"
39
+ },
40
+ {
41
+ "id": "5b8b36104ffb090be1ac3ce1",
42
+ "created_at": "2018-09-02T03:00:00.178+02:00",
43
+ "name": "20180902010000_kibana-3938",
44
+ "size": 17484513608,
45
+ "status": "done",
46
+ "database_id": "597601234ffb097af4f3099b",
47
+ "type": "postgresql"
48
+ }
49
+ ]
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "path": "/databases/ad-5ed10967884fef000f5e4fff/backups",
3
+ "method": "get",
4
+ "request": {
5
+ "headers": {
6
+ "Authorization": "Bearer the-bearer-token"
7
+ }
8
+ },
9
+ "response": {
10
+ "status": 400,
11
+ "headers": {
12
+ "Date": "Fri, 29 May 2020 13:08:59 GMT",
13
+ "Etag": "W/\"a9504bb2f6f87c65ff68074ae787831e\"",
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ "Transfer-Encoding": "chunked",
16
+ "Connection": "keep-alive",
17
+ "Cache-Control": "max-age=0, private, must-revalidate",
18
+ "Referrer-Policy": "strict-origin-when-cross-origin"
19
+ },
20
+ "json_body": {
21
+ "error": "unauthorized"
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "id": "ad-5ed10967884fef000f5e4fff"
3
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "path": "/databases/ad-5ed10967884fef000f5e4fff",
3
+ "method": "get",
4
+ "request": {
5
+ "headers": {
6
+ "Authorization": "Bearer the-bearer-token"
7
+ }
8
+ },
9
+ "response": {
10
+ "status": 200,
11
+ "headers": {
12
+ "Date": "Fri, 29 May 2020 13:08:59 GMT",
13
+ "Etag": "W/\"a9504bb2f6f87c65ff68074ae787831e\"",
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ "Transfer-Encoding": "chunked",
16
+ "Connection": "keep-alive",
17
+ "Cache-Control": "max-age=0, private, must-revalidate",
18
+ "Referrer-Policy": "strict-origin-when-cross-origin"
19
+ },
20
+ "json_body": {
21
+ "database": {
22
+ "id": "ad-5ed10967884fef000f5e4fff",
23
+ "resource_id": "my-db-123",
24
+ "app_name": "my-app",
25
+ "created_at": "2019-02-05T15:38:14.343+01:00",
26
+ "encryption_at_rest": true,
27
+ "features": [
28
+ {
29
+ "name": "redis-rdb",
30
+ "status": "ACTIVATED"
31
+ }
32
+ ],
33
+ "plan": "free",
34
+ "status": "running",
35
+ "type_id": "5bf30d1104c87f000161285a",
36
+ "type_name": "redis",
37
+ "version_id": "5bf30d1104c87f000161285b",
38
+ "instances": [],
39
+ "readable_version": "3.2.9-1",
40
+ "periodic_backups_enabled": true,
41
+ "periodic_backups_scheduled_at": [0]
42
+ }
43
+ }
44
+ }
45
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "path": "/databases/ad-5ed10967884fef000f5e4fff",
3
+ "method": "get",
4
+ "request": {
5
+ "headers": {
6
+ "Authorization": "Bearer the-bearer-token"
7
+ }
8
+ },
9
+ "response": {
10
+ "status": 400,
11
+ "headers": {
12
+ "Date": "Fri, 29 May 2020 13:08:59 GMT",
13
+ "Etag": "W/\"a9504bb2f6f87c65ff68074ae787831e\"",
14
+ "Content-Type": "application/json; charset=utf-8",
15
+ "Transfer-Encoding": "chunked",
16
+ "Connection": "keep-alive",
17
+ "Cache-Control": "max-age=0, private, must-revalidate",
18
+ "Referrer-Policy": "strict-origin-when-cross-origin"
19
+ },
20
+ "json_body": {
21
+ "error": "unauthorized"
22
+ }
23
+ }
24
+ }
@@ -170,6 +170,47 @@ RSpec.describe Scalingo::API::Client do
170
170
  end
171
171
  end
172
172
 
173
+ describe "database_connection" do
174
+ let(:database_id) { "db-id-1234" }
175
+
176
+ context "without bearer token" do
177
+ let(:scalingo) { scalingo_guest }
178
+
179
+ it "raises" do
180
+ expect {
181
+ subject.database_connection(database_id)
182
+ }.to raise_error(Scalingo::Error::Unauthenticated)
183
+ end
184
+ end
185
+
186
+ context "with bearer token" do
187
+ it "has an authentication header set with a bearer scheme" do
188
+ scalingo.authenticate_database_with_bearer_token(
189
+ database_id,
190
+ "1234",
191
+ expires_at: Time.now + 1.hour,
192
+ raise_on_expired_token: false,
193
+ )
194
+ expect(subject.database_connection(database_id).headers["Authorization"]).to eq "Bearer #{subject.token_holder.database_tokens[database_id].value}"
195
+ end
196
+ end
197
+
198
+ context "with wrong bearer token" do
199
+ it "raises" do
200
+ database_id_2 = "db-id-5678"
201
+ scalingo.authenticate_database_with_bearer_token(
202
+ database_id_2,
203
+ "1234",
204
+ expires_at: Time.now + 1.hour,
205
+ raise_on_expired_token: false,
206
+ )
207
+ expect {
208
+ subject.database_connection(database_id)
209
+ }.to raise_error(Scalingo::Error::Unauthenticated)
210
+ end
211
+ end
212
+ end
213
+
173
214
  describe "connection" do
174
215
  context "logged" do
175
216
  context "no fallback to guest" do
@@ -115,6 +115,26 @@ RSpec.describe Scalingo::Regional::Addons do
115
115
  end
116
116
  end
117
117
 
118
+ describe_method "authenticate!" do
119
+ context "success" do
120
+ let(:arguments) { [meta[:app_id], meta[:id]] }
121
+ let(:stub_pattern) { "token-200" }
122
+
123
+ it_behaves_like "a singular object response"
124
+ it "authenticates" do
125
+ response
126
+ expect(scalingo.authenticated_for_database?(meta[:id])).to be true
127
+ end
128
+ end
129
+
130
+ context "not found" do
131
+ let(:arguments) { [meta[:app_id], meta[:not_found_id]] }
132
+ let(:stub_pattern) { "token-404" }
133
+
134
+ it_behaves_like "a not found response"
135
+ end
136
+ end
137
+
118
138
  describe_method "update" do
119
139
  context "success" do
120
140
  let(:arguments) { [meta[:app_id], meta[:id], meta[:update][:valid]] }
@@ -0,0 +1,58 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Scalingo::RegionalDatabase::Backups do
4
+ before do
5
+ scalingo.add_database_token(meta[:addon_id], "the-bearer-token")
6
+ end
7
+
8
+ describe_method "create" do
9
+ context "success" do
10
+ let(:arguments) { [meta[:addon_id]] }
11
+ let(:stub_pattern) { "create-201" }
12
+
13
+ it_behaves_like "a singular object response", 201
14
+ end
15
+
16
+ context "failure" do
17
+ let(:arguments) { [meta[:addon_id]] }
18
+ let(:stub_pattern) { "create-400" }
19
+
20
+ it_behaves_like "a client error"
21
+ end
22
+ end
23
+
24
+ describe_method "for" do
25
+ context "success" do
26
+ let(:arguments) { [meta[:addon_id]] }
27
+ let(:stub_pattern) { "for-200" }
28
+ let(:expected_count) { 3 }
29
+
30
+ it_behaves_like "a collection response"
31
+ it_behaves_like "a non-paginated collection"
32
+ end
33
+
34
+ context "failure" do
35
+ let(:arguments) { [meta[:addon_id]] }
36
+ let(:stub_pattern) { "for-400" }
37
+
38
+ it_behaves_like "a client error"
39
+ end
40
+ end
41
+
42
+ describe_method "archive" do
43
+ context "success" do
44
+ let(:arguments) { [meta[:addon_id], meta[:backup_id]] }
45
+ let(:stub_pattern) { "archive-200" }
46
+ let(:expected_keys) { %i[download_url] }
47
+
48
+ it_behaves_like "a singular object response"
49
+ end
50
+
51
+ context "failure" do
52
+ let(:arguments) { [meta[:addon_id], meta[:backup_id]] }
53
+ let(:stub_pattern) { "archive-400" }
54
+
55
+ it_behaves_like "a client error"
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Scalingo::RegionalDatabase::Databases do
4
+ before do
5
+ scalingo.add_database_token(meta[:id], "the-bearer-token")
6
+ end
7
+
8
+ describe_method "find" do
9
+ context "success" do
10
+ let(:arguments) { [meta[:id]] }
11
+ let(:stub_pattern) { "find-200" }
12
+
13
+ it_behaves_like "a singular object response"
14
+ end
15
+
16
+ context "failure" do
17
+ let(:arguments) { [meta[:id]] }
18
+ let(:stub_pattern) { "find-400" }
19
+
20
+ it_behaves_like "a client error"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Scalingo::RegionalDatabase do
4
+ subject { described_class.new("url") }
5
+
6
+ %w[databases backups].each do |section|
7
+ it "handles requests for #{section}" do
8
+ expect(subject.respond_to?(section)).to be true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,81 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe Scalingo::TokenHolder do
4
+ subject(:token_holder_dummy_class) do
5
+ Class.new {
6
+ include(Scalingo::TokenHolder)
7
+ attr_accessor :config
8
+ }
9
+ end
10
+
11
+ describe "authenticate_with_bearer_token" do
12
+ subject { token_holder.authenticate_with_bearer_token(token, expires_at: expires_at, raise_on_expired_token: false) }
13
+
14
+ let(:token_holder) do
15
+ holder = token_holder_dummy_class.new
16
+ holder.config = Scalingo::Configuration.new
17
+
18
+ holder
19
+ end
20
+
21
+ context "without expiration date" do
22
+ let(:token) { "1234" }
23
+ let(:expires_at) { nil }
24
+
25
+ it "set the auth token" do
26
+ expect(token_holder.authenticated?).to be false
27
+ subject
28
+ expect(token_holder.authenticated?).to be true
29
+ end
30
+ end
31
+
32
+ context "with an expiration date" do
33
+ let(:token) { "1234" }
34
+ let(:expires_at) { Time.now + 1.hour }
35
+
36
+ it "refresh the auth token" do
37
+ token_holder.authenticate_with_bearer_token(token, expires_at: 1.hour.ago, raise_on_expired_token: false)
38
+ expect(token_holder.authenticated?).to be false
39
+
40
+ subject
41
+ expect(token_holder.authenticated?).to be true
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "authenticate_database_with_bearer_token" do
47
+ subject { token_holder.authenticate_database_with_bearer_token(database_id, token, expires_at: expires_at, raise_on_expired_token: false) }
48
+
49
+ let(:token_holder) do
50
+ holder = token_holder_dummy_class.new
51
+ holder.config = Scalingo::Configuration.new
52
+
53
+ holder
54
+ end
55
+
56
+ let(:database_id) { "db-id-1234" }
57
+
58
+ context "without expiration date" do
59
+ let(:token) { "1234" }
60
+ let(:expires_at) { nil }
61
+
62
+ it "set the database auth token" do
63
+ expect(token_holder.authenticated_for_database?(database_id)).to be false
64
+ subject
65
+ expect(token_holder.authenticated_for_database?(database_id)).to be true
66
+ end
67
+ end
68
+
69
+ context "with an expiration date" do
70
+ let(:token) { "1234" }
71
+ let(:expires_at) { Time.now + 1.hour }
72
+
73
+ it "refresh the database token" do
74
+ token_holder.authenticate_database_with_bearer_token(database_id, token, expires_at: 1.hour.ago, raise_on_expired_token: false)
75
+ expect(token_holder.authenticated_for_database?(database_id)).to be false
76
+ subject
77
+ expect(token_holder.authenticated_for_database?(database_id)).to be true
78
+ end
79
+ end
80
+ end
81
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: scalingo
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leo Unbekandt
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-12-21 00:00:00.000000000 Z
12
+ date: 2022-12-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -185,10 +185,12 @@ executables: []
185
185
  extensions: []
186
186
  extra_rdoc_files: []
187
187
  files:
188
+ - ".github/dependabot.yml"
188
189
  - ".github/workflows/publish.yml"
189
190
  - ".github/workflows/ruby.yml"
190
191
  - ".gitignore"
191
192
  - ".rubocop.yml"
193
+ - ".sclng/metadata.toml"
192
194
  - CHANGELOG.md
193
195
  - Gemfile
194
196
  - LICENSE.txt
@@ -230,6 +232,9 @@ files:
230
232
  - lib/scalingo/regional/notifiers.rb
231
233
  - lib/scalingo/regional/operations.rb
232
234
  - lib/scalingo/regional/scm_repo_links.rb
235
+ - lib/scalingo/regional_database.rb
236
+ - lib/scalingo/regional_database/backups.rb
237
+ - lib/scalingo/regional_database/databases.rb
233
238
  - lib/scalingo/token_holder.rb
234
239
  - lib/scalingo/version.rb
235
240
  - samples/auth/keys/_meta.json
@@ -413,6 +418,16 @@ files:
413
418
  - samples/regional/scm_repo_links/manual-deploy-200.json
414
419
  - samples/regional/scm_repo_links/show-200.json
415
420
  - samples/regional/scm_repo_links/update-200.json
421
+ - samples/regional_database/backups/_meta.json
422
+ - samples/regional_database/backups/archive-200.json
423
+ - samples/regional_database/backups/archive-400.json
424
+ - samples/regional_database/backups/create-201.json
425
+ - samples/regional_database/backups/create-400.json
426
+ - samples/regional_database/backups/for-200.json
427
+ - samples/regional_database/backups/for-400.json
428
+ - samples/regional_database/databases/_meta.json
429
+ - samples/regional_database/databases/find-200.json
430
+ - samples/regional_database/databases/find-400.json
416
431
  - scalingo.gemspec
417
432
  - spec/scalingo/api/client_spec.rb
418
433
  - spec/scalingo/api/endpoint_spec.rb
@@ -442,7 +457,11 @@ files:
442
457
  - spec/scalingo/regional/notifiers_spec.rb
443
458
  - spec/scalingo/regional/operations_spec.rb
444
459
  - spec/scalingo/regional/scm_repo_links_spec.rb
460
+ - spec/scalingo/regional_database/backups_spec.rb
461
+ - spec/scalingo/regional_database/databases_spec.rb
462
+ - spec/scalingo/regional_database_spec.rb
445
463
  - spec/scalingo/regional_spec.rb
464
+ - spec/scalingo/token_holder_spec.rb
446
465
  homepage: https://www.scalingo.com
447
466
  licenses:
448
467
  - MIT
@@ -467,7 +486,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
467
486
  - !ruby/object:Gem::Version
468
487
  version: '0'
469
488
  requirements: []
470
- rubygems_version: 3.2.32
489
+ rubygems_version: 3.3.26
471
490
  signing_key:
472
491
  specification_version: 4
473
492
  summary: Ruby client for Scalingo APIs
@@ -500,4 +519,8 @@ test_files:
500
519
  - spec/scalingo/regional/notifiers_spec.rb
501
520
  - spec/scalingo/regional/operations_spec.rb
502
521
  - spec/scalingo/regional/scm_repo_links_spec.rb
522
+ - spec/scalingo/regional_database/backups_spec.rb
523
+ - spec/scalingo/regional_database/databases_spec.rb
524
+ - spec/scalingo/regional_database_spec.rb
503
525
  - spec/scalingo/regional_spec.rb
526
+ - spec/scalingo/token_holder_spec.rb