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
data/lib/scalingo/client.rb
CHANGED
@@ -4,24 +4,33 @@ require "faraday_middleware"
|
|
4
4
|
require "scalingo/bearer_token"
|
5
5
|
require "scalingo/errors"
|
6
6
|
require "scalingo/auth"
|
7
|
+
require "scalingo/billing"
|
7
8
|
require "scalingo/regional"
|
8
9
|
|
9
10
|
module Scalingo
|
10
11
|
class Client
|
11
12
|
extend Forwardable
|
12
13
|
|
14
|
+
attr_reader :config
|
15
|
+
|
16
|
+
def initialize(attributes = {})
|
17
|
+
@config = Configuration.new(attributes, Scalingo.config)
|
18
|
+
|
19
|
+
define_regions!
|
20
|
+
end
|
21
|
+
|
13
22
|
## Authentication helpers / Token management
|
14
23
|
attr_reader :token
|
15
24
|
|
16
25
|
def token=(input)
|
17
|
-
@token = input.is_a?(BearerToken) ? input : BearerToken.new(input.to_s)
|
26
|
+
@token = input.is_a?(BearerToken) ? input : BearerToken.new(input.to_s, raise_on_expired: config.raise_on_expired_token)
|
18
27
|
end
|
19
28
|
|
20
29
|
def authenticated?
|
21
30
|
token.present? && !token.expired?
|
22
31
|
end
|
23
32
|
|
24
|
-
def authenticate_with(access_token: nil, bearer_token: nil,
|
33
|
+
def authenticate_with(access_token: nil, bearer_token: nil, expires_at: nil)
|
25
34
|
if !access_token && !bearer_token
|
26
35
|
raise ArgumentError, "You must supply one of `access_token` or `bearer_token`"
|
27
36
|
end
|
@@ -30,18 +39,19 @@ module Scalingo
|
|
30
39
|
raise ArgumentError, "You cannot supply both `access_token` and `bearer_token`"
|
31
40
|
end
|
32
41
|
|
33
|
-
if
|
34
|
-
raise ArgumentError, "`
|
42
|
+
if expires_at && !bearer_token
|
43
|
+
raise ArgumentError, "`expires_at` can only be used with `bearer_token`. `access_token` have a 1 hour expiration."
|
35
44
|
end
|
36
45
|
|
37
46
|
if access_token
|
38
|
-
expiration = Time.now +
|
47
|
+
expiration = Time.now + config.exchanged_token_validity
|
39
48
|
response = auth.tokens.exchange(access_token)
|
40
49
|
|
41
50
|
if response.successful?
|
42
51
|
self.token = BearerToken.new(
|
43
52
|
response.data[:token],
|
44
|
-
|
53
|
+
expires_at: expiration,
|
54
|
+
raise_on_expired: config.raise_on_expired_token,
|
45
55
|
)
|
46
56
|
end
|
47
57
|
|
@@ -49,35 +59,33 @@ module Scalingo
|
|
49
59
|
end
|
50
60
|
|
51
61
|
if bearer_token
|
52
|
-
self.token =
|
62
|
+
self.token = if expires_at
|
63
|
+
token = bearer_token.is_a?(BearerToken) ? bearer_token.value : bearer_token.to_s
|
64
|
+
|
65
|
+
BearerToken.new(
|
66
|
+
token,
|
67
|
+
expires_at: expires_at,
|
68
|
+
raise_on_expired: config.raise_on_expired_token,
|
69
|
+
)
|
70
|
+
else
|
71
|
+
bearer_token
|
72
|
+
end
|
73
|
+
|
74
|
+
true
|
53
75
|
end
|
54
76
|
end
|
55
77
|
|
56
78
|
## API clients
|
57
79
|
def auth
|
58
|
-
@auth ||= Auth.new(self,
|
80
|
+
@auth ||= Auth.new(self, config.auth)
|
59
81
|
end
|
60
82
|
|
61
|
-
def
|
62
|
-
|
83
|
+
def billing
|
84
|
+
@billing ||= Billing.new(self, config.billing)
|
63
85
|
end
|
64
86
|
|
65
|
-
|
66
|
-
|
67
|
-
raise ArgumentError, "Scalingo: no url configured for region #{region}"
|
68
|
-
end
|
69
|
-
|
70
|
-
define_method(region) do
|
71
|
-
region_client = instance_variable_get :"@#{region}"
|
72
|
-
|
73
|
-
unless region_client
|
74
|
-
region_client = Regional.new(self, Scalingo.config.urls[region])
|
75
|
-
|
76
|
-
instance_variable_set :"@#{region}", region_client
|
77
|
-
end
|
78
|
-
|
79
|
-
region_client
|
80
|
-
end
|
87
|
+
def region(name = nil)
|
88
|
+
public_send(name || config.default_region)
|
81
89
|
end
|
82
90
|
|
83
91
|
## Delegations
|
@@ -101,5 +109,23 @@ module Scalingo
|
|
101
109
|
def_delegator :region, :notifiers
|
102
110
|
def_delegator :region, :operations
|
103
111
|
def_delegator :region, :scm_repo_links
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def define_regions!
|
116
|
+
config.regions.each_pair do |region, _|
|
117
|
+
define_singleton_method(region) do
|
118
|
+
region_client = instance_variable_get :"@#{region}"
|
119
|
+
|
120
|
+
unless region_client
|
121
|
+
region_client = Regional.new(self, config.regions.public_send(region))
|
122
|
+
|
123
|
+
instance_variable_set :"@#{region}", region_client
|
124
|
+
end
|
125
|
+
|
126
|
+
region_client
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
104
130
|
end
|
105
131
|
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "active_support/core_ext/numeric/time"
|
2
|
+
require "scalingo/version"
|
3
|
+
require "ostruct"
|
4
|
+
|
5
|
+
module Scalingo
|
6
|
+
class Configuration
|
7
|
+
ATTRIBUTES = [
|
8
|
+
# URL to the authentication API
|
9
|
+
:auth,
|
10
|
+
|
11
|
+
# URL to the billing API
|
12
|
+
:billing,
|
13
|
+
|
14
|
+
# List of regions under the form {"region_id": root_url}
|
15
|
+
:regions,
|
16
|
+
|
17
|
+
# Default region. Must match a key in `regions`
|
18
|
+
:default_region,
|
19
|
+
|
20
|
+
# Wether to use https or http
|
21
|
+
:https,
|
22
|
+
|
23
|
+
# Configure the User Agent header
|
24
|
+
:user_agent,
|
25
|
+
|
26
|
+
# For how long is a bearer token considered valid (it will raise passed this delay).
|
27
|
+
# Set to nil to never raise.
|
28
|
+
:exchanged_token_validity,
|
29
|
+
|
30
|
+
# Raise an exception when trying to use an authenticated connection without a bearer token set
|
31
|
+
# Having this setting to true prevents performing requests that would fail due to lack of authentication headers.
|
32
|
+
:raise_on_missing_authentication,
|
33
|
+
|
34
|
+
# Raise an exception when the bearer token in use is supposed to be invalid
|
35
|
+
:raise_on_expired_token,
|
36
|
+
|
37
|
+
# These headers will be added to every request. Individual methods may override them.
|
38
|
+
# This should be a hash or a callable object that returns a hash.
|
39
|
+
:additional_headers
|
40
|
+
]
|
41
|
+
|
42
|
+
ATTRIBUTES.each { |attr| attr_accessor(attr) }
|
43
|
+
|
44
|
+
def self.default
|
45
|
+
new(
|
46
|
+
auth: "https://auth.scalingo.com/v1",
|
47
|
+
billing: "https://cashmachine.scalingo.com",
|
48
|
+
regions: {
|
49
|
+
agora_fr1: "https://api.agora-fr1.scalingo.com/v1",
|
50
|
+
osc_fr1: "https://api.osc-fr1.scalingo.com/v1",
|
51
|
+
osc_secnum_fr1: "https://api.osc-secnum-fr1.scalingo.com/v1"
|
52
|
+
},
|
53
|
+
default_region: :osc_fr1,
|
54
|
+
user_agent: "Scalingo Ruby Client v#{Scalingo::VERSION}",
|
55
|
+
exchanged_token_validity: 1.hour,
|
56
|
+
raise_on_missing_authentication: true,
|
57
|
+
raise_on_expired_token: false,
|
58
|
+
additional_headers: {},
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(attributes = {}, parent = nil)
|
63
|
+
ATTRIBUTES.each do |name|
|
64
|
+
public_send("#{name}=", attributes.fetch(name, parent&.public_send(name)))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def regions=(input)
|
69
|
+
if input.is_a?(OpenStruct)
|
70
|
+
@regions = input
|
71
|
+
return
|
72
|
+
end
|
73
|
+
|
74
|
+
if input.is_a?(Hash)
|
75
|
+
@regions = OpenStruct.new(input)
|
76
|
+
return
|
77
|
+
end
|
78
|
+
|
79
|
+
raise ArgumentError, "wrong type for argument"
|
80
|
+
end
|
81
|
+
|
82
|
+
def default_region=(input)
|
83
|
+
input = input.to_sym
|
84
|
+
|
85
|
+
raise ArgumentError, "No regions named `#{input}`" unless regions.respond_to?(input)
|
86
|
+
|
87
|
+
@default_region = input
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.config
|
92
|
+
@config ||= Configuration.default
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.configure
|
96
|
+
yield config
|
97
|
+
end
|
98
|
+
end
|
@@ -83,7 +83,7 @@ module Scalingo
|
|
83
83
|
def categories(headers = nil, &block)
|
84
84
|
data = nil
|
85
85
|
|
86
|
-
response = connection(
|
86
|
+
response = connection(fallback_to_guest: true).get(
|
87
87
|
"addon_categories",
|
88
88
|
data,
|
89
89
|
headers,
|
@@ -96,7 +96,7 @@ module Scalingo
|
|
96
96
|
def providers(headers = nil, &block)
|
97
97
|
data = nil
|
98
98
|
|
99
|
-
response = connection(
|
99
|
+
response = connection(fallback_to_guest: true).get(
|
100
100
|
"addon_providers",
|
101
101
|
data,
|
102
102
|
headers,
|
@@ -29,7 +29,7 @@ module Scalingo
|
|
29
29
|
def types(headers = nil, &block)
|
30
30
|
data = nil
|
31
31
|
|
32
|
-
response = connection(
|
32
|
+
response = connection(fallback_to_guest: true).get(
|
33
33
|
"event_types",
|
34
34
|
data,
|
35
35
|
headers,
|
@@ -42,7 +42,7 @@ module Scalingo
|
|
42
42
|
def categories(headers = nil, &block)
|
43
43
|
data = nil
|
44
44
|
|
45
|
-
response = connection(
|
45
|
+
response = connection(fallback_to_guest: true).get(
|
46
46
|
"event_categories",
|
47
47
|
data,
|
48
48
|
headers,
|
@@ -3,10 +3,18 @@ require "scalingo/api/endpoint"
|
|
3
3
|
module Scalingo
|
4
4
|
class Regional::Operations < API::Endpoint
|
5
5
|
def find(app_id, operation_id, headers = nil, &block)
|
6
|
+
get(
|
7
|
+
"apps/#{app_id}/operations/#{operation_id}",
|
8
|
+
headers,
|
9
|
+
&block
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(url, headers = nil, &block)
|
6
14
|
data = nil
|
7
15
|
|
8
16
|
response = connection.get(
|
9
|
-
|
17
|
+
url,
|
10
18
|
data,
|
11
19
|
headers,
|
12
20
|
&block
|
data/lib/scalingo/version.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
{
|
2
|
+
"id": "882d7733-923b-40dc-88e6-f1324e48c42a",
|
3
|
+
"create": {
|
4
|
+
"valid": {
|
5
|
+
"name": "Billing profile",
|
6
|
+
"address_line1": "1, rue de la paix",
|
7
|
+
"address_zip": "75001",
|
8
|
+
"address_city": "Paris",
|
9
|
+
"address_country": "France"
|
10
|
+
},
|
11
|
+
"invalid": {
|
12
|
+
"some": "attribute"
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"update": {
|
16
|
+
"valid": {
|
17
|
+
"address_city": "New Somecity"
|
18
|
+
},
|
19
|
+
"invalid": {
|
20
|
+
"address_country": "not a country"
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
@@ -0,0 +1,50 @@
|
|
1
|
+
{
|
2
|
+
"path": "/profiles",
|
3
|
+
"method": "post",
|
4
|
+
"request": {
|
5
|
+
"headers": {
|
6
|
+
"Authorization": "Bearer the-bearer-token",
|
7
|
+
"Content-Type": "application/json"
|
8
|
+
},
|
9
|
+
"json_body": {
|
10
|
+
"profile": {
|
11
|
+
"name": "Billing profile",
|
12
|
+
"address_line1": "1, rue de la paix",
|
13
|
+
"address_zip": "75001",
|
14
|
+
"address_city": "Paris",
|
15
|
+
"address_country": "France"
|
16
|
+
}
|
17
|
+
}
|
18
|
+
},
|
19
|
+
"response": {
|
20
|
+
"status": 201,
|
21
|
+
"headers": {
|
22
|
+
"Date": "Fri, 12 Jun 2020 15:46:11 GMT",
|
23
|
+
"Content-Type": "application/json; charset=utf-8",
|
24
|
+
"Transfer-Encoding": "chunked",
|
25
|
+
"Connection": "keep-alive",
|
26
|
+
"Cache-Control": "no-cache"
|
27
|
+
},
|
28
|
+
"json_body": {
|
29
|
+
"profile": {
|
30
|
+
"id": "882d7733-923b-40dc-88e6-f1324e48c42a",
|
31
|
+
"name": "Billing profile",
|
32
|
+
"email": null,
|
33
|
+
"address_line1": "1, rue de la paix",
|
34
|
+
"address_line2": null,
|
35
|
+
"address_city": "Paris",
|
36
|
+
"balance": 0,
|
37
|
+
"address_zip": "75001",
|
38
|
+
"address_state": null,
|
39
|
+
"address_country": "FR",
|
40
|
+
"vat_number": null,
|
41
|
+
"company": null,
|
42
|
+
"payment_method": "sepa",
|
43
|
+
"created_at": "2020-05-29T13:01:46.217Z",
|
44
|
+
"updated_at": "2020-06-12T15:45:43.535Z",
|
45
|
+
"credit": 0,
|
46
|
+
"stripe_payment_method": null
|
47
|
+
}
|
48
|
+
}
|
49
|
+
}
|
50
|
+
}
|
@@ -0,0 +1,27 @@
|
|
1
|
+
{
|
2
|
+
"path": "/profiles",
|
3
|
+
"method": "post",
|
4
|
+
"request": {
|
5
|
+
"headers": {
|
6
|
+
"Authorization": "Bearer the-bearer-token",
|
7
|
+
"Content-Type": "application/json"
|
8
|
+
},
|
9
|
+
"json_body": {
|
10
|
+
"profile": {
|
11
|
+
}
|
12
|
+
}
|
13
|
+
},
|
14
|
+
"response": {
|
15
|
+
"status": 400,
|
16
|
+
"headers": {
|
17
|
+
"Date": "Fri, 12 Jun 2020 15:46:10 GMT",
|
18
|
+
"Content-Type": "application/json; charset=utf-8",
|
19
|
+
"Transfer-Encoding": "chunked",
|
20
|
+
"Connection": "keep-alive",
|
21
|
+
"Cache-Control": "no-cache"
|
22
|
+
},
|
23
|
+
"json_body": {
|
24
|
+
"error": "profile is required"
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
{
|
2
|
+
"path": "/profiles",
|
3
|
+
"method": "post",
|
4
|
+
"request": {
|
5
|
+
"headers": {
|
6
|
+
"Authorization": "Bearer the-bearer-token",
|
7
|
+
"Content-Type": "application/json"
|
8
|
+
},
|
9
|
+
"json_body": {
|
10
|
+
"profile": {
|
11
|
+
"some": "attribute"
|
12
|
+
}
|
13
|
+
}
|
14
|
+
},
|
15
|
+
"response": {
|
16
|
+
"status": 422,
|
17
|
+
"headers": {
|
18
|
+
"Date": "Fri, 12 Jun 2020 15:46:11 GMT",
|
19
|
+
"Content-Type": "application/json; charset=utf-8",
|
20
|
+
"Transfer-Encoding": "chunked",
|
21
|
+
"Connection": "keep-alive",
|
22
|
+
"Cache-Control": "no-cache"
|
23
|
+
},
|
24
|
+
"json_body": {
|
25
|
+
"errors": {
|
26
|
+
"name": [
|
27
|
+
"can't be blank"
|
28
|
+
],
|
29
|
+
"address_line1": [
|
30
|
+
"can't be blank"
|
31
|
+
],
|
32
|
+
"address_zip": [
|
33
|
+
"can't be blank"
|
34
|
+
],
|
35
|
+
"address_city": [
|
36
|
+
"can't be blank"
|
37
|
+
],
|
38
|
+
"address_country": [
|
39
|
+
"can't be blank"
|
40
|
+
]
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|