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
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
|
+
}
|