scalingo 3.1.0 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
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