scalingo 3.1.0 → 3.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +8 -0
- data/.github/workflows/publish.yml +1 -1
- data/.github/workflows/ruby.yml +2 -6
- data/.sclng/metadata.toml +8 -0
- data/CHANGELOG.md +7 -0
- data/README.md +32 -1
- data/lib/scalingo/api/client.rb +23 -9
- data/lib/scalingo/api/endpoint.rb +1 -0
- data/lib/scalingo/client.rb +17 -7
- data/lib/scalingo/regional/addons.rb +15 -0
- data/lib/scalingo/regional_database/backups.rb +44 -0
- data/lib/scalingo/regional_database/databases.rb +18 -0
- data/lib/scalingo/regional_database.rb +13 -0
- data/lib/scalingo/token_holder.rb +44 -13
- data/lib/scalingo/version.rb +1 -1
- data/samples/regional_database/backups/_meta.json +4 -0
- data/samples/regional_database/backups/archive-200.json +24 -0
- data/samples/regional_database/backups/archive-400.json +24 -0
- data/samples/regional_database/backups/create-201.json +30 -0
- data/samples/regional_database/backups/create-400.json +24 -0
- data/samples/regional_database/backups/for-200.json +52 -0
- data/samples/regional_database/backups/for-400.json +24 -0
- data/samples/regional_database/databases/_meta.json +3 -0
- data/samples/regional_database/databases/find-200.json +45 -0
- data/samples/regional_database/databases/find-400.json +24 -0
- data/spec/scalingo/api/client_spec.rb +41 -0
- data/spec/scalingo/regional/addons_spec.rb +20 -0
- data/spec/scalingo/regional_database/backups_spec.rb +58 -0
- data/spec/scalingo/regional_database/databases_spec.rb +23 -0
- data/spec/scalingo/regional_database_spec.rb +11 -0
- data/spec/scalingo/token_holder_spec.rb +81 -0
- metadata +26 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc653c0172dd4bc1b2b1cb80948582e0069c058d615f74dcc2bb9fac3d467245
|
4
|
+
data.tar.gz: 4482433e8ca28c3a7ab72d2f22471f2ef2f61a5aa6b0f34d40c2faba9ecb5d98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 708a8fbe40bf16f20e0cb2fcd94506d2e2e21d3c02c0d9e521fc02396897835efb695fda4b472f0eafb48d29e262e3f679166af8c5e1efeab4978bf841539d15
|
7
|
+
data.tar.gz: 5d75e30a6bdef5ae37be20869ffb81da7ceb82cd01d1a6cb97387054de87c3e1b4c6e2d6927deaaee22eb8195dd5d6ab0b51e177dfd66b57fc0fca6ec4a289ff
|
data/.github/workflows/ruby.yml
CHANGED
@@ -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@
|
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@
|
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):
|
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
|
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
|
data/lib/scalingo/api/client.rb
CHANGED
@@ -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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
data/lib/scalingo/client.rb
CHANGED
@@ -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 =
|
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
|
-
|
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 =
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
data/lib/scalingo/version.rb
CHANGED
@@ -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,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.
|
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:
|
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.
|
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
|