scalingo 3.0.0.beta.1 → 3.0.0.beta.2
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -1
- data/CHANGELOG.md +13 -1
- data/README.md +41 -27
- data/lib/scalingo.rb +1 -1
- data/lib/scalingo/api/client.rb +34 -17
- data/lib/scalingo/api/endpoint.rb +1 -1
- data/lib/scalingo/api/response.rb +21 -32
- data/lib/scalingo/bearer_token.rb +11 -5
- data/lib/scalingo/billing.rb +11 -0
- data/lib/scalingo/billing/profile.rb +46 -0
- data/lib/scalingo/client.rb +52 -26
- data/lib/scalingo/configuration.rb +98 -0
- data/lib/scalingo/regional/addons.rb +2 -2
- data/lib/scalingo/regional/containers.rb +1 -1
- data/lib/scalingo/regional/events.rb +2 -2
- data/lib/scalingo/regional/logs.rb +1 -1
- data/lib/scalingo/regional/metrics.rb +1 -1
- data/lib/scalingo/regional/notifiers.rb +1 -1
- data/lib/scalingo/regional/operations.rb +9 -1
- data/lib/scalingo/version.rb +1 -1
- data/samples/billing/profile/_meta.json +23 -0
- data/samples/billing/profile/create-201.json +50 -0
- data/samples/billing/profile/create-400.json +27 -0
- data/samples/billing/profile/create-422.json +44 -0
- data/samples/billing/profile/show-200.json +41 -0
- data/samples/billing/profile/show-404.json +22 -0
- data/samples/billing/profile/update-200.json +47 -0
- data/samples/billing/profile/update-422.json +32 -0
- data/scalingo.gemspec +1 -3
- data/spec/scalingo/api/client_spec.rb +168 -0
- data/spec/scalingo/api/endpoint_spec.rb +30 -0
- data/spec/scalingo/api/response_spec.rb +285 -0
- data/spec/scalingo/auth_spec.rb +15 -0
- data/spec/scalingo/bearer_token_spec.rb +72 -0
- data/spec/scalingo/billing/profile_spec.rb +55 -0
- data/spec/scalingo/client_spec.rb +93 -0
- data/spec/scalingo/configuration_spec.rb +55 -0
- data/spec/scalingo/regional/operations_spec.rb +11 -3
- data/spec/scalingo/regional_spec.rb +14 -0
- metadata +33 -40
- data/Gemfile.lock +0 -110
- data/lib/scalingo/config.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6582ca860b783b598a3f31874afc37958ead26c29c17c099e6cba1ef094f933d
|
4
|
+
data.tar.gz: c0206b25118775599be97e0c6a7ef06c40914a34711c882a21268521f964ae24
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1e7e41bb52927a17d3de0c840f281754ee1907a000db36735ec779d5115a345f91bf944aad068421e9292fcb3ab9f92db49690a25e61f4f81cd8ff45c625f550
|
7
|
+
data.tar.gz: e2ebc1468f18304b3086ad927db4e89f09ef1d6256395a6f72a089776eeba8643b923bec88af1690f0264786dd46d6982b421f741b99f23a86ec513a3841fba9
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,4 +1,16 @@
|
|
1
|
-
##
|
1
|
+
## Unreleased
|
2
|
+
|
3
|
+
## 3.0.0.beta.2 - 2020/06/18
|
4
|
+
|
5
|
+
* Bugfix: do not "dig" into the response body if it is not a 2XX (#18, #19)
|
6
|
+
* Rework configuration, add specs over it (#17)
|
7
|
+
* Rename `BearerToken#expires_in` to `expires_at` (#16)
|
8
|
+
* Rename argument `allow_guest:` for `API::Endpoint#connection` to `fallback_to_guest:` (#16)
|
9
|
+
* Response objects can access the operation directly using `.operation` (#15)
|
10
|
+
* New API: billing api (#14)
|
11
|
+
* New endpoint: `profile`. Methods: `show`, `create`, `update`
|
12
|
+
|
13
|
+
## 3.0.0.beta.1 - 2020/06/12
|
2
14
|
|
3
15
|
* Full rewrite of the gem, **zero** backwards compatibility. Refer to the `README` for more information.
|
4
16
|
|
data/README.md
CHANGED
@@ -13,12 +13,12 @@ You can check the version 2 at [the v2 branch of this repository](https://github
|
|
13
13
|
### The road to 3.0.0
|
14
14
|
|
15
15
|
This gem is still in beta version, but its API should not change a lot until a final release.
|
16
|
-
|
16
|
+
[This issue tracks the remaining work](https://github.com/Scalingo/scalingo-ruby-api/issues/13).
|
17
17
|
|
18
18
|
## Installation
|
19
19
|
|
20
20
|
```ruby
|
21
|
-
gem "scalingo", "3.0.0.beta.
|
21
|
+
gem "scalingo", "3.0.0.beta.2"
|
22
22
|
```
|
23
23
|
|
24
24
|
And then execute:
|
@@ -55,18 +55,29 @@ client.section.request(id, payload = {}, headers = nil, &block)
|
|
55
55
|
|
56
56
|
## Configuration
|
57
57
|
|
58
|
+
You can refer to the code below to configure the gem globally.
|
59
|
+
The values displayed match the default ones.
|
60
|
+
|
61
|
+
:warning: Configuration is copied when instanciating a `Scalingo::Client` object;
|
62
|
+
changing the configuration globally will therefore not affect already existing objects.
|
63
|
+
|
58
64
|
```ruby
|
59
65
|
Scalingo.configure do |config|
|
60
|
-
#
|
61
|
-
config.
|
66
|
+
# Authentication API url
|
67
|
+
config.auth = "https://auth.scalingo.com/v1"
|
68
|
+
|
69
|
+
# Billing API url
|
70
|
+
config.billing = "https://cashmachine.scalingo.com"
|
62
71
|
|
63
|
-
#
|
64
|
-
config.
|
72
|
+
# Known regions and their api's url
|
73
|
+
config.regions = {
|
74
|
+
agora_fr1: "https://api.agora-fr1.scalingo.com/v1",
|
75
|
+
osc_fr1: "https://api.osc-fr1.scalingo.com/v1",
|
76
|
+
osc_secnum_fr1: "https://api.osc-secnum-fr1.scalingo.com/v1"
|
77
|
+
}
|
65
78
|
|
66
|
-
#
|
67
|
-
config.
|
68
|
-
config.urls.osc_fr1 = "https://api.osc-fr1.scalingo.com/v1"
|
69
|
-
config.urls.osc_secnum_fr1 = "https://api.osc-secnum-fr1.scalingo.com/v1"
|
79
|
+
# Default region. Must match an entry in `regions`
|
80
|
+
config.default_region = :osc_fr1
|
70
81
|
|
71
82
|
# Configure the User Agent header
|
72
83
|
config.user_agent = "Scalingo Ruby Client v#{Scalingo::VERSION}"
|
@@ -75,38 +86,32 @@ Scalingo.configure do |config|
|
|
75
86
|
# Set to nil to never raise.
|
76
87
|
config.exchanged_token_validity = 1.hour
|
77
88
|
|
78
|
-
# Raise an exception when trying to use an authenticated connection without a bearer token set
|
79
89
|
# Having this setting to true prevents performing requests that would fail due to lack of authentication headers.
|
80
90
|
config.raise_on_missing_authentication = true
|
81
91
|
|
92
|
+
# Raise an exception when the bearer token in use is supposed to be invalid
|
93
|
+
config.raise_on_expired_token = false
|
94
|
+
|
82
95
|
# These headers will be added to every request. Individual methods may override them.
|
83
96
|
# This should be a hash or a callable object that returns a hash.
|
84
97
|
config.additional_headers = {}
|
85
|
-
|
86
|
-
# Raise an exception when the bearer token in use is supposed to be invalid
|
87
|
-
config.raise_on_expired_token = false
|
88
98
|
end
|
89
99
|
```
|
90
100
|
|
91
|
-
|
101
|
+
You can also configure each client separately.
|
102
|
+
Values not supplied will be copied from the global configuration.
|
92
103
|
|
93
104
|
```ruby
|
94
|
-
|
95
|
-
require "scalingo/config"
|
96
|
-
|
97
|
-
Scalingo.configure do |config|
|
98
|
-
config.regions = %i[my-regions]
|
99
|
-
end
|
100
|
-
|
101
|
-
require "scalingo/client"
|
105
|
+
scalingo = Scalingo::Client.new(raise_on_expired_token: false)
|
102
106
|
```
|
103
107
|
|
104
108
|
## Response object
|
105
109
|
|
106
110
|
Responses are parsed with the keys symbolized and then encapsulated in a `Scalingo::API::Response` object:
|
111
|
+
|
107
112
|
* `response.status` containts the HTTP status code
|
108
113
|
* `response.data` contains the "relevant" data, without the json root key (when relevant)
|
109
|
-
* `response.
|
114
|
+
* `response.full_body` contains the full response body
|
110
115
|
* `response.meta` contains the meta object, if there's any
|
111
116
|
* `response.headers` containts all the response headers
|
112
117
|
|
@@ -114,7 +119,16 @@ Some helper methods are defined on this object:
|
|
114
119
|
* `response.successful?` returns true when the code is 2XX
|
115
120
|
* `response.paginated?` returns true if the reponse has metadata relative to pagination
|
116
121
|
* `response.operation?` returns true if the response contains a header relative to an ongoing operation
|
117
|
-
* `response.
|
122
|
+
* `response.operation_url` returns the URL to query to get the status of the operation
|
123
|
+
* `response.operation` performs a request to retrieve the operation
|
124
|
+
|
125
|
+
## Other details on the code architecture
|
126
|
+
|
127
|
+
* `Scalingo::Client` instances hold configuration and the token used for authentication
|
128
|
+
* `Scalingo::API::Client` subclasses (`Scalingo::Auth`, `Scalingo::Billing`, `Scalingo::Regional`) provides access to the APIs.
|
129
|
+
You can use `connection` (returns a faraday instance) on those objects to perform any request freely.
|
130
|
+
* `Scalingo::API::Endpoint` subclasses (`Scalingo::Auth::User`) instances belong to an api client (cf previous point).
|
131
|
+
They provide quick and uniform access to expected requests.
|
118
132
|
|
119
133
|
## Examples
|
120
134
|
|
@@ -128,7 +142,7 @@ scalingo.authenticate_with(access_token: "my_access_token")
|
|
128
142
|
scalingo.authenticate_with(bearer_token: "my_bearer_jwt")
|
129
143
|
|
130
144
|
# Return your profile
|
131
|
-
scalingo.self
|
145
|
+
scalingo.user.self
|
132
146
|
|
133
147
|
# List your SSH Keys
|
134
148
|
scalingo.keys.all # OR scalingo.auth.keys.all
|
@@ -140,7 +154,7 @@ scalingo.keys.show("my-key-id")
|
|
140
154
|
scalingo.apps.all # OR scalingo.region.apps.all
|
141
155
|
|
142
156
|
# List your apps on osc-fr1
|
143
|
-
scalingo.osc_fr1.apps.all
|
157
|
+
scalingo.osc_fr1.apps.all # OR scalingo.region(:osc_fr1).apps.all
|
144
158
|
|
145
159
|
# Preview the creation of an app on the default region
|
146
160
|
scalingo.apps.create(name: "my-new-app", dry_run: true)
|
data/lib/scalingo.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
require "scalingo/
|
1
|
+
require "scalingo/configuration"
|
2
2
|
require "scalingo/client"
|
data/lib/scalingo/api/client.rb
CHANGED
@@ -10,40 +10,51 @@ module Scalingo
|
|
10
10
|
|
11
11
|
def self.register_handlers!(handlers)
|
12
12
|
handlers.each do |method_name, klass|
|
13
|
-
|
14
|
-
|
13
|
+
register_handler!(method_name, klass)
|
14
|
+
end
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
17
|
+
def self.register_handler!(method_name, klass)
|
18
|
+
define_method(method_name) do
|
19
|
+
value = instance_variable_get("@#{method_name}")
|
20
20
|
|
21
|
-
|
21
|
+
if value.nil?
|
22
|
+
value = klass.new(self)
|
23
|
+
instance_variable_set("@#{method_name}", value)
|
22
24
|
end
|
25
|
+
|
26
|
+
value
|
23
27
|
end
|
24
28
|
end
|
25
29
|
|
26
30
|
## Faraday objects
|
27
|
-
def
|
28
|
-
|
29
|
-
"User-Agent" =>
|
31
|
+
def headers
|
32
|
+
hash = {
|
33
|
+
"User-Agent" => scalingo.config.user_agent
|
30
34
|
}
|
31
35
|
|
32
|
-
if (extra =
|
33
|
-
extra.respond_to?(:call) ?
|
36
|
+
if (extra = scalingo.config.additional_headers).present?
|
37
|
+
extra.respond_to?(:call) ? hash.update(extra.call) : hash.update(extra)
|
34
38
|
end
|
35
39
|
|
40
|
+
hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def connection_options
|
36
44
|
{
|
37
45
|
url: url,
|
38
46
|
headers: headers
|
39
47
|
}
|
40
48
|
end
|
41
49
|
|
42
|
-
|
43
|
-
|
50
|
+
# Note: when `config.raise_on_missing_authentication` is set to false,
|
51
|
+
# this method may return the unauthenticated connection
|
52
|
+
# even with `fallback_to_guest: false`
|
53
|
+
def connection(fallback_to_guest: false)
|
54
|
+
if fallback_to_guest
|
44
55
|
begin
|
45
56
|
authenticated_connection
|
46
|
-
rescue
|
57
|
+
rescue Error::Unauthenticated
|
47
58
|
unauthenticated_connection
|
48
59
|
end
|
49
60
|
else
|
@@ -54,14 +65,20 @@ module Scalingo
|
|
54
65
|
def unauthenticated_connection
|
55
66
|
@unauthenticated_conn ||= Faraday.new(connection_options) { |conn|
|
56
67
|
conn.response :json, content_type: /\bjson$/, parser_options: {symbolize_names: true}
|
68
|
+
conn.request :json
|
57
69
|
}
|
58
70
|
end
|
59
71
|
|
60
72
|
def authenticated_connection
|
61
73
|
return @connection if @connection
|
62
74
|
|
63
|
-
|
64
|
-
|
75
|
+
# Missing token handling. Token expiration is handled in the `value` method.
|
76
|
+
unless scalingo.token&.value
|
77
|
+
if scalingo.config.raise_on_missing_authentication
|
78
|
+
raise Error::Unauthenticated
|
79
|
+
else
|
80
|
+
return unauthenticated_connection
|
81
|
+
end
|
65
82
|
end
|
66
83
|
|
67
84
|
@connection = Faraday.new(connection_options) { |conn|
|
@@ -1,49 +1,32 @@
|
|
1
1
|
module Scalingo
|
2
2
|
module API
|
3
3
|
class Response
|
4
|
-
def self.
|
5
|
-
|
6
|
-
|
4
|
+
def self.unpack(client, response, key: nil)
|
5
|
+
body = response.body
|
6
|
+
has_hash_body = body.present? && body.respond_to?(:key)
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
when Hash
|
11
|
-
transform_object(body, resource_class: resource_class)
|
12
|
-
when Array
|
13
|
-
body.map { |item| transform_object(item, resource_class: resource_class) }
|
14
|
-
else
|
15
|
-
body
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.transform_meta(body)
|
20
|
-
if body.present? && body.respond_to?(:key) && body.key?(:meta)
|
21
|
-
body[:meta]
|
22
|
-
end
|
23
|
-
end
|
8
|
+
data = body
|
9
|
+
meta = nil
|
24
10
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
else
|
29
|
-
response.body
|
11
|
+
if has_hash_body
|
12
|
+
data = body[key] if response.success? && key.present?
|
13
|
+
meta = body[:meta]
|
30
14
|
end
|
31
15
|
|
32
|
-
parsed = transform_body(data, resource_class: resource_class)
|
33
|
-
meta = transform_meta(response.body)
|
34
|
-
|
35
16
|
new(
|
17
|
+
client: client,
|
36
18
|
status: response.status,
|
37
19
|
headers: response.headers,
|
38
|
-
data:
|
20
|
+
data: data,
|
39
21
|
meta: meta,
|
40
|
-
full_body:
|
22
|
+
full_body: body,
|
41
23
|
)
|
42
24
|
end
|
43
25
|
|
44
|
-
attr_reader :status, :headers, :data, :full_body, :meta
|
26
|
+
attr_reader :client, :status, :headers, :data, :full_body, :meta
|
45
27
|
|
46
|
-
def initialize(status:, headers:, data:, full_body:, meta: nil)
|
28
|
+
def initialize(client:, status:, headers:, data:, full_body:, meta: nil)
|
29
|
+
@client = client
|
47
30
|
@status = status
|
48
31
|
@headers = headers
|
49
32
|
@data = data
|
@@ -60,11 +43,17 @@ module Scalingo
|
|
60
43
|
end
|
61
44
|
|
62
45
|
def operation
|
46
|
+
if operation? && client.respond_to?(:operations)
|
47
|
+
client.operations.get(operation_url)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def operation_url
|
63
52
|
headers[:location]
|
64
53
|
end
|
65
54
|
|
66
55
|
def operation?
|
67
|
-
|
56
|
+
operation_url.present?
|
68
57
|
end
|
69
58
|
end
|
70
59
|
end
|
@@ -1,18 +1,24 @@
|
|
1
1
|
module Scalingo
|
2
2
|
class BearerToken
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :expires_at
|
4
|
+
attr_writer :raise_on_expired
|
4
5
|
|
5
|
-
def initialize(value,
|
6
|
+
def initialize(value, expires_at: nil, raise_on_expired: false)
|
6
7
|
@value = value
|
7
|
-
@
|
8
|
+
@expires_at = expires_at if expires_at
|
9
|
+
@raise_on_expired = raise_on_expired
|
10
|
+
end
|
11
|
+
|
12
|
+
def raise_on_expired?
|
13
|
+
@raise_on_expired
|
8
14
|
end
|
9
15
|
|
10
16
|
def expired?
|
11
|
-
|
17
|
+
expires_at && expires_at <= Time.now
|
12
18
|
end
|
13
19
|
|
14
20
|
def value
|
15
|
-
raise Error::ExpiredToken if expired? &&
|
21
|
+
raise Error::ExpiredToken if expired? && raise_on_expired?
|
16
22
|
|
17
23
|
@value
|
18
24
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "scalingo/api/endpoint"
|
2
|
+
|
3
|
+
module Scalingo
|
4
|
+
class Billing::Profile < API::Endpoint
|
5
|
+
def show(headers = nil, &block)
|
6
|
+
data = nil
|
7
|
+
|
8
|
+
response = connection.get(
|
9
|
+
"profile",
|
10
|
+
data,
|
11
|
+
headers,
|
12
|
+
&block
|
13
|
+
)
|
14
|
+
|
15
|
+
unpack(response, key: :profile)
|
16
|
+
end
|
17
|
+
|
18
|
+
def create(payload = {}, headers = nil, &block)
|
19
|
+
data = {profile: payload}
|
20
|
+
|
21
|
+
response = connection.post(
|
22
|
+
"profiles",
|
23
|
+
data,
|
24
|
+
headers,
|
25
|
+
&block
|
26
|
+
)
|
27
|
+
|
28
|
+
unpack(response, key: :profile)
|
29
|
+
end
|
30
|
+
|
31
|
+
def update(id, payload = {}, headers = nil, &block)
|
32
|
+
data = {profile: payload}
|
33
|
+
|
34
|
+
response = connection.put(
|
35
|
+
"profiles/#{id}",
|
36
|
+
data,
|
37
|
+
headers,
|
38
|
+
&block
|
39
|
+
)
|
40
|
+
|
41
|
+
unpack(response, key: :profile)
|
42
|
+
end
|
43
|
+
|
44
|
+
alias_method :self, :show
|
45
|
+
end
|
46
|
+
end
|