scalingo 3.0.0.beta.1 → 3.0.0.beta.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|