nucleus 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +18 -4
- data/README.md +28 -40
- data/Rakefile +137 -137
- data/config/nucleus_config.rb +0 -4
- data/lib/nucleus/adapter_resolver.rb +115 -115
- data/lib/nucleus/adapters/buildpack_translator.rb +79 -79
- data/lib/nucleus/adapters/v1/cloud_control/application.rb +108 -108
- data/lib/nucleus/adapters/v1/cloud_control/authentication.rb +27 -27
- data/lib/nucleus/adapters/v1/cloud_control/cloud_control.rb +153 -153
- data/lib/nucleus/adapters/v1/cloud_control/domains.rb +68 -68
- data/lib/nucleus/adapters/v1/cloud_control/logs.rb +103 -103
- data/lib/nucleus/adapters/v1/cloud_control/vars.rb +88 -88
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/domains.rb +149 -149
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/logs.rb +303 -303
- data/lib/nucleus/adapters/v1/cloud_foundry_v2/services.rb +286 -286
- data/lib/nucleus/adapters/v1/heroku/heroku.rb +2 -2
- data/lib/nucleus/adapters/v1/heroku/logs.rb +108 -108
- data/lib/nucleus/core/adapter_authentication_inductor.rb +0 -2
- data/lib/nucleus/core/adapter_extensions/auth/http_basic_auth_client.rb +37 -37
- data/lib/nucleus/core/adapter_extensions/http_client.rb +177 -177
- data/lib/nucleus/core/common/files/archive_extractor.rb +112 -112
- data/lib/nucleus/core/common/files/archiver.rb +91 -91
- data/lib/nucleus/core/common/logging/request_log_formatter.rb +48 -48
- data/lib/nucleus/core/error_messages.rb +127 -127
- data/lib/nucleus/core/models/abstract_model.rb +29 -29
- data/lib/nucleus/scripts/load_dependencies.rb +0 -1
- data/lib/nucleus/scripts/setup_config.rb +28 -28
- data/lib/nucleus/version.rb +3 -3
- data/nucleus.gemspec +10 -12
- data/spec/factories/models.rb +63 -61
- data/spec/integration/api/auth_spec.rb +58 -58
- data/spec/test_suites.rake +31 -31
- data/spec/unit/common/helpers/auth_helper_spec.rb +73 -73
- data/spec/unit/common/oauth2_auth_client_spec.rb +1 -1
- data/tasks/compatibility.rake +113 -113
- data/tasks/evaluation.rake +162 -162
- metadata +16 -30
@@ -127,7 +127,6 @@ module Nucleus
|
|
127
127
|
latest_version_id = release[:id]
|
128
128
|
end
|
129
129
|
end
|
130
|
-
latest_version_id
|
131
130
|
else
|
132
131
|
latest_version = 0
|
133
132
|
latest_version_id = nil
|
@@ -137,8 +136,9 @@ module Nucleus
|
|
137
136
|
latest_version_id = dyno[:release][:id]
|
138
137
|
end
|
139
138
|
end
|
140
|
-
latest_version_id
|
141
139
|
end
|
140
|
+
|
141
|
+
latest_version_id
|
142
142
|
end
|
143
143
|
end
|
144
144
|
end
|
@@ -1,108 +1,108 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module V1
|
4
|
-
class Heroku < Stub
|
5
|
-
module Logs
|
6
|
-
# Carriage return (newline in Mac OS) + line feed (newline in Unix) == CRLF (newline in Windows)
|
7
|
-
CRLF = "\r\n"
|
8
|
-
|
9
|
-
# @see Stub#logs
|
10
|
-
def logs(application_id)
|
11
|
-
# fails with 404 if application is not available and serves for timestamps
|
12
|
-
app = get("/apps/#{application_id}").body
|
13
|
-
|
14
|
-
available_log_files = []
|
15
|
-
available_log_types.keys.each do |type|
|
16
|
-
# TODO: right now, we always assume the log has recently been updated
|
17
|
-
available_log_files.push(id: type, name: type, type: type,
|
18
|
-
created_at: app[:created_at], updated_at: Time.now.utc.iso8601)
|
19
|
-
end
|
20
|
-
available_log_files
|
21
|
-
end
|
22
|
-
|
23
|
-
# @see Stub#log?
|
24
|
-
def log?(application_id, log_id)
|
25
|
-
# fails with 404 if application is not available
|
26
|
-
get("/apps/#{application_id}")
|
27
|
-
|
28
|
-
return true if log_id.to_sym == :all
|
29
|
-
return true if log_id.to_sym == :build
|
30
|
-
available_log_types.key? log_id.to_sym
|
31
|
-
end
|
32
|
-
|
33
|
-
# @see Stub#log_entries
|
34
|
-
def log_entries(application_id, log_id)
|
35
|
-
unless log?(application_id, log_id)
|
36
|
-
fail Errors::AdapterResourceNotFoundError,
|
37
|
-
"Invalid log file '#{log_id}', not available for application '#{application_id}'"
|
38
|
-
end
|
39
|
-
|
40
|
-
return build_log_entries(application_id) if log_id.to_sym == Enums::ApplicationLogfileType::BUILD
|
41
|
-
|
42
|
-
request_body = request_body(log_id.to_sym).merge(tail: false)
|
43
|
-
log = post("/apps/#{application_id}/log-sessions", body: request_body).body
|
44
|
-
logfile = get(log[:logplex_url], headers: {}).body
|
45
|
-
# process to entries
|
46
|
-
entries = []
|
47
|
-
# skip empty logs, which are detected as Hash by the http client
|
48
|
-
logfile.split(CRLF).each { |logfile_line| entries.push logfile_line } unless logfile == {}
|
49
|
-
entries
|
50
|
-
end
|
51
|
-
|
52
|
-
# @see Stub#tail
|
53
|
-
def tail(application_id, log_id, stream)
|
54
|
-
# Currently no tailing for build log possible
|
55
|
-
if log_id == Enums::ApplicationLogfileType::BUILD
|
56
|
-
entries = build_log_entries(application_id)
|
57
|
-
entries.each { |entry| stream.send_message(entry) }
|
58
|
-
stream.close
|
59
|
-
else
|
60
|
-
request_body = request_body(log_id.to_sym).merge(tail: true)
|
61
|
-
log = post("/apps/#{application_id}/log-sessions", body: request_body).body
|
62
|
-
tail_http_response(log[:logplex_url], stream)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
private
|
67
|
-
|
68
|
-
def available_log_types
|
69
|
-
log_types = {}
|
70
|
-
log_types[Enums::ApplicationLogfileType::API] = { source: 'heroku', dyno: 'api' }
|
71
|
-
log_types[Enums::ApplicationLogfileType::APPLICATION] = { source: 'app' }
|
72
|
-
log_types[Enums::ApplicationLogfileType::REQUEST] = { source: 'heroku', dyno: 'router' }
|
73
|
-
# TODO: filter only for web and worker dynos (must be merged manually :/)
|
74
|
-
log_types[Enums::ApplicationLogfileType::SYSTEM] = { source: 'heroku' }
|
75
|
-
log_types
|
76
|
-
end
|
77
|
-
|
78
|
-
def request_body(log_id)
|
79
|
-
return {} if log_id == :all
|
80
|
-
available_log_types[log_id]
|
81
|
-
end
|
82
|
-
|
83
|
-
def build_log_entries(application_id)
|
84
|
-
build_list = get("/apps/#{application_id}/builds").body
|
85
|
-
# limitation: show only the last 3 builds
|
86
|
-
entries = []
|
87
|
-
build_list.last(3).each do |build|
|
88
|
-
entries.push(*build_result_entries(application_id, build[:id]))
|
89
|
-
end
|
90
|
-
entries
|
91
|
-
end
|
92
|
-
|
93
|
-
def build_result_entries(application_id, build_id)
|
94
|
-
build_result = get("/apps/#{application_id}/builds/#{build_id}/result").body
|
95
|
-
entries = []
|
96
|
-
build_result[:lines].each do |line_entry|
|
97
|
-
# skip all blank lines
|
98
|
-
next if line_entry[:line].strip.empty?
|
99
|
-
# push and remove all trailing newline characters
|
100
|
-
entries.push line_entry[:line].chomp('')
|
101
|
-
end
|
102
|
-
entries
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module V1
|
4
|
+
class Heroku < Stub
|
5
|
+
module Logs
|
6
|
+
# Carriage return (newline in Mac OS) + line feed (newline in Unix) == CRLF (newline in Windows)
|
7
|
+
CRLF = "\r\n".freeze
|
8
|
+
|
9
|
+
# @see Stub#logs
|
10
|
+
def logs(application_id)
|
11
|
+
# fails with 404 if application is not available and serves for timestamps
|
12
|
+
app = get("/apps/#{application_id}").body
|
13
|
+
|
14
|
+
available_log_files = []
|
15
|
+
available_log_types.keys.each do |type|
|
16
|
+
# TODO: right now, we always assume the log has recently been updated
|
17
|
+
available_log_files.push(id: type, name: type, type: type,
|
18
|
+
created_at: app[:created_at], updated_at: Time.now.utc.iso8601)
|
19
|
+
end
|
20
|
+
available_log_files
|
21
|
+
end
|
22
|
+
|
23
|
+
# @see Stub#log?
|
24
|
+
def log?(application_id, log_id)
|
25
|
+
# fails with 404 if application is not available
|
26
|
+
get("/apps/#{application_id}")
|
27
|
+
|
28
|
+
return true if log_id.to_sym == :all
|
29
|
+
return true if log_id.to_sym == :build
|
30
|
+
available_log_types.key? log_id.to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
# @see Stub#log_entries
|
34
|
+
def log_entries(application_id, log_id)
|
35
|
+
unless log?(application_id, log_id)
|
36
|
+
fail Errors::AdapterResourceNotFoundError,
|
37
|
+
"Invalid log file '#{log_id}', not available for application '#{application_id}'"
|
38
|
+
end
|
39
|
+
|
40
|
+
return build_log_entries(application_id) if log_id.to_sym == Enums::ApplicationLogfileType::BUILD
|
41
|
+
|
42
|
+
request_body = request_body(log_id.to_sym).merge(tail: false)
|
43
|
+
log = post("/apps/#{application_id}/log-sessions", body: request_body).body
|
44
|
+
logfile = get(log[:logplex_url], headers: {}).body
|
45
|
+
# process to entries
|
46
|
+
entries = []
|
47
|
+
# skip empty logs, which are detected as Hash by the http client
|
48
|
+
logfile.split(CRLF).each { |logfile_line| entries.push logfile_line } unless logfile == {}
|
49
|
+
entries
|
50
|
+
end
|
51
|
+
|
52
|
+
# @see Stub#tail
|
53
|
+
def tail(application_id, log_id, stream)
|
54
|
+
# Currently no tailing for build log possible
|
55
|
+
if log_id == Enums::ApplicationLogfileType::BUILD
|
56
|
+
entries = build_log_entries(application_id)
|
57
|
+
entries.each { |entry| stream.send_message(entry) }
|
58
|
+
stream.close
|
59
|
+
else
|
60
|
+
request_body = request_body(log_id.to_sym).merge(tail: true)
|
61
|
+
log = post("/apps/#{application_id}/log-sessions", body: request_body).body
|
62
|
+
tail_http_response(log[:logplex_url], stream)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def available_log_types
|
69
|
+
log_types = {}
|
70
|
+
log_types[Enums::ApplicationLogfileType::API] = { source: 'heroku', dyno: 'api' }
|
71
|
+
log_types[Enums::ApplicationLogfileType::APPLICATION] = { source: 'app' }
|
72
|
+
log_types[Enums::ApplicationLogfileType::REQUEST] = { source: 'heroku', dyno: 'router' }
|
73
|
+
# TODO: filter only for web and worker dynos (must be merged manually :/)
|
74
|
+
log_types[Enums::ApplicationLogfileType::SYSTEM] = { source: 'heroku' }
|
75
|
+
log_types
|
76
|
+
end
|
77
|
+
|
78
|
+
def request_body(log_id)
|
79
|
+
return {} if log_id == :all
|
80
|
+
available_log_types[log_id]
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_log_entries(application_id)
|
84
|
+
build_list = get("/apps/#{application_id}/builds").body
|
85
|
+
# limitation: show only the last 3 builds
|
86
|
+
entries = []
|
87
|
+
build_list.last(3).each do |build|
|
88
|
+
entries.push(*build_result_entries(application_id, build[:id]))
|
89
|
+
end
|
90
|
+
entries
|
91
|
+
end
|
92
|
+
|
93
|
+
def build_result_entries(application_id, build_id)
|
94
|
+
build_result = get("/apps/#{application_id}/builds/#{build_id}/result").body
|
95
|
+
entries = []
|
96
|
+
build_result[:lines].each do |line_entry|
|
97
|
+
# skip all blank lines
|
98
|
+
next if line_entry[:line].strip.empty?
|
99
|
+
# push and remove all trailing newline characters
|
100
|
+
entries.push line_entry[:line].chomp('')
|
101
|
+
end
|
102
|
+
entries
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -15,8 +15,6 @@ module Nucleus
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
private
|
19
|
-
|
20
18
|
# Patch the actual method that is defined in an API version stub.
|
21
19
|
# The method shall than be able to update the authentication token if the initial authentication expired.<br>
|
22
20
|
# Only major authentication issues, e.g. if the credentials are repeatedly rejected,
|
@@ -1,37 +1,37 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
# Implementation of the AuthClient that works with the HTTP basic authentication.
|
4
|
-
class HttpBasicAuthClient < AuthClient
|
5
|
-
# Create a new instance of an {HttpBasicAuthClient}.
|
6
|
-
# @param [Boolean] check_certificates true if SSL certificates are to be validated,
|
7
|
-
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
8
|
-
# @yield [verify_ssl, username, password] Auth credentials verification block,
|
9
|
-
# must check if the combination of username and password is accepted by the endpoint.
|
10
|
-
# @yieldparam [Hash<String,String>] headers headers for an HTTP request,
|
11
|
-
# including the authentication header to be tested
|
12
|
-
# @yieldreturn [Boolean] true if the authentication was verified to be ok,
|
13
|
-
# false if an error occurred, e.g. with bad credentials
|
14
|
-
def initialize(check_certificates = true, &verification)
|
15
|
-
@verification = verification
|
16
|
-
super(check_certificates)
|
17
|
-
end
|
18
|
-
|
19
|
-
# @see AuthClient#authenticate
|
20
|
-
def authenticate(username, password)
|
21
|
-
packed_credentials = ["#{username}:#{password}"].pack('m*').
|
22
|
-
valid = @verification.call(verify_ssl, 'Authorization' => "Basic #{packed_credentials}")
|
23
|
-
fail Errors::EndpointAuthenticationError, 'Authentication failed, credentials seem to be invalid' unless valid
|
24
|
-
# verification passed, credentials are valid
|
25
|
-
@packed_credentials = packed_credentials
|
26
|
-
self
|
27
|
-
end
|
28
|
-
|
29
|
-
# @see AuthClient#auth_header
|
30
|
-
def auth_header
|
31
|
-
fail Errors::EndpointAuthenticationError,
|
32
|
-
'Authentication client was not authenticated yet' unless @packed_credentials
|
33
|
-
{ 'Authorization' => "Basic #{@packed_credentials}" }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
# Implementation of the AuthClient that works with the HTTP basic authentication.
|
4
|
+
class HttpBasicAuthClient < AuthClient
|
5
|
+
# Create a new instance of an {HttpBasicAuthClient}.
|
6
|
+
# @param [Boolean] check_certificates true if SSL certificates are to be validated,
|
7
|
+
# false if they are to be ignored (e.g. when using self-signed certificates in development environments)
|
8
|
+
# @yield [verify_ssl, username, password] Auth credentials verification block,
|
9
|
+
# must check if the combination of username and password is accepted by the endpoint.
|
10
|
+
# @yieldparam [Hash<String,String>] headers headers for an HTTP request,
|
11
|
+
# including the authentication header to be tested
|
12
|
+
# @yieldreturn [Boolean] true if the authentication was verified to be ok,
|
13
|
+
# false if an error occurred, e.g. with bad credentials
|
14
|
+
def initialize(check_certificates = true, &verification)
|
15
|
+
@verification = verification
|
16
|
+
super(check_certificates)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @see AuthClient#authenticate
|
20
|
+
def authenticate(username, password)
|
21
|
+
packed_credentials = ["#{username}:#{password}"].pack('m*').delete("\n")
|
22
|
+
valid = @verification.call(verify_ssl, 'Authorization' => "Basic #{packed_credentials}")
|
23
|
+
fail Errors::EndpointAuthenticationError, 'Authentication failed, credentials seem to be invalid' unless valid
|
24
|
+
# verification passed, credentials are valid
|
25
|
+
@packed_credentials = packed_credentials
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see AuthClient#auth_header
|
30
|
+
def auth_header
|
31
|
+
fail Errors::EndpointAuthenticationError,
|
32
|
+
'Authentication client was not authenticated yet' unless @packed_credentials
|
33
|
+
{ 'Authorization' => "Basic #{@packed_credentials}" }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,177 +1,177 @@
|
|
1
|
-
module Nucleus
|
2
|
-
module Adapters
|
3
|
-
module HttpClient
|
4
|
-
# Executes a HEAD request to the given URL.
|
5
|
-
#
|
6
|
-
# @param [String] path path to add to the endpoint URL
|
7
|
-
# @param [Hash] params options to call the post request with
|
8
|
-
# @option params [Array<int>] :expects ([200]) http status code that is expected
|
9
|
-
# @option params [Hash] :headers request headers to use with the request
|
10
|
-
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
11
|
-
# unprocessed response
|
12
|
-
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
13
|
-
def head(path, params = {})
|
14
|
-
execute_request(:head, [200], path, params, params.delete(:native_call) { false })
|
15
|
-
end
|
16
|
-
|
17
|
-
# Executes a GET request to the given URL.
|
18
|
-
#
|
19
|
-
# @param [String] path path to add to the endpoint URL
|
20
|
-
# @param [Hash] params options to call the post request with
|
21
|
-
# @option params [Array<int>] :expects ([200]) http status code that is expected
|
22
|
-
# @option params [Hash] :headers request headers to use with the request
|
23
|
-
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
24
|
-
# unprocessed response
|
25
|
-
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
26
|
-
def get(path, params = {})
|
27
|
-
execute_request(:get, [200], path, params, params.delete(:native_call) { false })
|
28
|
-
end
|
29
|
-
|
30
|
-
# Executes a POST request to the given URL.
|
31
|
-
#
|
32
|
-
# @param [String] path path to add to the endpoint URL
|
33
|
-
# @param [Hash] params options to call the post request with
|
34
|
-
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
35
|
-
# @option params [Hash] :body request body, will be converted to json format
|
36
|
-
# @option params [Hash] :headers request headers to use with the request
|
37
|
-
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
38
|
-
# unprocessed response
|
39
|
-
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
40
|
-
def post(path, params = {})
|
41
|
-
execute_request(:post, [200, 201], path, params, params.delete(:native_call) { false })
|
42
|
-
end
|
43
|
-
|
44
|
-
# Executes a PATCH request to the given URL.
|
45
|
-
#
|
46
|
-
# @param [String] path path to add to the endpoint URL
|
47
|
-
# @param [Hash] params options to call the post request with
|
48
|
-
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
49
|
-
# @option params [Hash] :body request body, will be converted to json format
|
50
|
-
# @option params [Hash] :headers request headers to use with the request
|
51
|
-
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
52
|
-
# unprocessed response
|
53
|
-
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
54
|
-
def patch(path, params = {})
|
55
|
-
execute_request(:patch, [200, 201], path, params, params.delete(:native_call) { false })
|
56
|
-
end
|
57
|
-
|
58
|
-
# Executes a PUT request to the given URL.
|
59
|
-
#
|
60
|
-
# @param [String] path path to add to the endpoint URL
|
61
|
-
# @param [Hash] params options to call the post request with
|
62
|
-
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
63
|
-
# @option params [Hash] :body request body, will be converted to json format
|
64
|
-
# @option params [Hash] :headers request headers to use with the request
|
65
|
-
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
66
|
-
# unprocessed response
|
67
|
-
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
68
|
-
def put(path, params = {})
|
69
|
-
execute_request(:put, [200, 201], path, params, params.delete(:native_call) { false })
|
70
|
-
end
|
71
|
-
|
72
|
-
# Executes a DELETE request to the given URL.
|
73
|
-
#
|
74
|
-
# @param [String] path path to add to the endpoint URL
|
75
|
-
# @param [Hash] params options to call the post request with
|
76
|
-
# @option params [Array<int>] :expects ([200,204]) http status code that is expected
|
77
|
-
# @option params [Hash] :headers request headers to use with the request
|
78
|
-
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
79
|
-
# unprocessed response
|
80
|
-
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
81
|
-
def delete(path, params = {})
|
82
|
-
execute_request(:delete, [200, 204], path, params, params.delete(:native_call) { false })
|
83
|
-
end
|
84
|
-
|
85
|
-
private
|
86
|
-
|
87
|
-
def execute_request(method, default_expect, path, params, native_call = false)
|
88
|
-
params[:expects] = default_expect unless params.key? :expects
|
89
|
-
params[:method] = method
|
90
|
-
|
91
|
-
url = Regexp::PERFECT_URL_PATTERN =~ path ? path : to_url(path)
|
92
|
-
response = Excon.new(url, excon_connection_params(params)).request(add_common_request_params(params))
|
93
|
-
# we never want the JSON string, but always the hash representation
|
94
|
-
response.body = hash_of(response.body)
|
95
|
-
response
|
96
|
-
rescue Excon::Errors::HTTPStatusError => e
|
97
|
-
handle_execute_request_error(e, url, native_call)
|
98
|
-
end
|
99
|
-
|
100
|
-
def handle_execute_request_error(e, url, native_call)
|
101
|
-
log.debug 'ERROR, Excon could not execute the request.'
|
102
|
-
# transform json response to Hash object
|
103
|
-
e.response.body = hash_of(e.response.body)
|
104
|
-
|
105
|
-
# if this is a native API call, do not further process the error
|
106
|
-
return e.response if native_call
|
107
|
-
|
108
|
-
# fail with adapter specific error handling
|
109
|
-
handle_error(e.response) if respond_to?(:handle_error)
|
110
|
-
fallback_error_handling(e, url)
|
111
|
-
end
|
112
|
-
|
113
|
-
def fallback_error_handling(e, url)
|
114
|
-
error_status = e.response.status
|
115
|
-
# arriving here, error could not be processed --> use fallback errors
|
116
|
-
if e.is_a? Excon::Errors::ServerError
|
117
|
-
fail Errors::UnknownAdapterCallError, e.message
|
118
|
-
elsif error_status == 404
|
119
|
-
log.error("Resource not found (404) at '#{url}', indicating an adapter issue")
|
120
|
-
fail Errors::UnknownAdapterCallError, 'Resource not found, probably the adapter must be updated'
|
121
|
-
elsif error_status == 401
|
122
|
-
fail Errors::EndpointAuthenticationError,
|
123
|
-
'Auth. failed, probably cache is outdated or permissions were revoked?'
|
124
|
-
else
|
125
|
-
log.error("Fallback error handling (#{error_status}) at '#{url}', indicating an adapter issue")
|
126
|
-
fail Errors::UnknownAdapterCallError, e.message
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
def to_url(path)
|
131
|
-
# insert missing slash, prevent double slashes
|
132
|
-
return "#{@endpoint_url}/#{path}" unless @endpoint_url.end_with?('/') || path.start_with?('/')
|
133
|
-
"#{@endpoint_url}#{path}"
|
134
|
-
end
|
135
|
-
|
136
|
-
def excon_connection_params(params)
|
137
|
-
middleware = Excon.defaults[:middlewares].dup
|
138
|
-
|
139
|
-
if params[:follow_redirects] == false
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
Excon::Middleware::Decompress].push(*middleware).uniq
|
144
|
-
|
145
|
-
{ middlewares: middleware, ssl_verify_peer: @check_certificates }
|
146
|
-
end
|
147
|
-
|
148
|
-
def hash_of(message_body)
|
149
|
-
return {} if message_body.nil? || message_body.empty?
|
150
|
-
begin
|
151
|
-
return Oj.load(message_body, symbol_keys: true)
|
152
|
-
rescue Oj::Error
|
153
|
-
# parsing failed, content probably is no valid JSON content
|
154
|
-
message_body
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
def add_common_request_params(params)
|
159
|
-
common_params = { connection_timeout: 610, write_timeout: 600, read_timeout: 600 }
|
160
|
-
# allow to follow redirects in the APIs
|
161
|
-
allowed_status_codes = params.key?(:expects) ? [*params[:expects]] : []
|
162
|
-
unless params[:follow_redirects] == false
|
163
|
-
allowed_status_codes.push(*[301, 302, 303, 307, 308])
|
164
|
-
end
|
165
|
-
|
166
|
-
params[:expects] = allowed_status_codes.uniq
|
167
|
-
# use default or customized headers
|
168
|
-
params[:headers] = headers unless params[:headers]
|
169
|
-
# specify encoding if not done yet: use only gzip since deflate does cause issues with VCR cassettes in tests
|
170
|
-
params[:headers]['Accept-Encoding'] = 'gzip' unless params[:headers].key? 'Accept-Encoding'
|
171
|
-
params[:body] = params[:body].to_json if params.key? :body
|
172
|
-
# merge and return
|
173
|
-
common_params.merge params
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
end
|
1
|
+
module Nucleus
|
2
|
+
module Adapters
|
3
|
+
module HttpClient
|
4
|
+
# Executes a HEAD request to the given URL.
|
5
|
+
#
|
6
|
+
# @param [String] path path to add to the endpoint URL
|
7
|
+
# @param [Hash] params options to call the post request with
|
8
|
+
# @option params [Array<int>] :expects ([200]) http status code that is expected
|
9
|
+
# @option params [Hash] :headers request headers to use with the request
|
10
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
11
|
+
# unprocessed response
|
12
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
13
|
+
def head(path, params = {})
|
14
|
+
execute_request(:head, [200], path, params, params.delete(:native_call) { false })
|
15
|
+
end
|
16
|
+
|
17
|
+
# Executes a GET request to the given URL.
|
18
|
+
#
|
19
|
+
# @param [String] path path to add to the endpoint URL
|
20
|
+
# @param [Hash] params options to call the post request with
|
21
|
+
# @option params [Array<int>] :expects ([200]) http status code that is expected
|
22
|
+
# @option params [Hash] :headers request headers to use with the request
|
23
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
24
|
+
# unprocessed response
|
25
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
26
|
+
def get(path, params = {})
|
27
|
+
execute_request(:get, [200], path, params, params.delete(:native_call) { false })
|
28
|
+
end
|
29
|
+
|
30
|
+
# Executes a POST request to the given URL.
|
31
|
+
#
|
32
|
+
# @param [String] path path to add to the endpoint URL
|
33
|
+
# @param [Hash] params options to call the post request with
|
34
|
+
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
35
|
+
# @option params [Hash] :body request body, will be converted to json format
|
36
|
+
# @option params [Hash] :headers request headers to use with the request
|
37
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
38
|
+
# unprocessed response
|
39
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
40
|
+
def post(path, params = {})
|
41
|
+
execute_request(:post, [200, 201], path, params, params.delete(:native_call) { false })
|
42
|
+
end
|
43
|
+
|
44
|
+
# Executes a PATCH request to the given URL.
|
45
|
+
#
|
46
|
+
# @param [String] path path to add to the endpoint URL
|
47
|
+
# @param [Hash] params options to call the post request with
|
48
|
+
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
49
|
+
# @option params [Hash] :body request body, will be converted to json format
|
50
|
+
# @option params [Hash] :headers request headers to use with the request
|
51
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
52
|
+
# unprocessed response
|
53
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
54
|
+
def patch(path, params = {})
|
55
|
+
execute_request(:patch, [200, 201], path, params, params.delete(:native_call) { false })
|
56
|
+
end
|
57
|
+
|
58
|
+
# Executes a PUT request to the given URL.
|
59
|
+
#
|
60
|
+
# @param [String] path path to add to the endpoint URL
|
61
|
+
# @param [Hash] params options to call the post request with
|
62
|
+
# @option params [Array<int>] :expects ([200,201]) http status code that is expected
|
63
|
+
# @option params [Hash] :body request body, will be converted to json format
|
64
|
+
# @option params [Hash] :headers request headers to use with the request
|
65
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
66
|
+
# unprocessed response
|
67
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
68
|
+
def put(path, params = {})
|
69
|
+
execute_request(:put, [200, 201], path, params, params.delete(:native_call) { false })
|
70
|
+
end
|
71
|
+
|
72
|
+
# Executes a DELETE request to the given URL.
|
73
|
+
#
|
74
|
+
# @param [String] path path to add to the endpoint URL
|
75
|
+
# @param [Hash] params options to call the post request with
|
76
|
+
# @option params [Array<int>] :expects ([200,204]) http status code that is expected
|
77
|
+
# @option params [Hash] :headers request headers to use with the request
|
78
|
+
# @option params [Boolean] :native_call if true the request is a native API call and shall return the
|
79
|
+
# unprocessed response
|
80
|
+
# @raise [Nucleus::Errors::AdapterError] if the call failed and did not return the expected code(s)
|
81
|
+
def delete(path, params = {})
|
82
|
+
execute_request(:delete, [200, 204], path, params, params.delete(:native_call) { false })
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def execute_request(method, default_expect, path, params, native_call = false)
|
88
|
+
params[:expects] = default_expect unless params.key? :expects
|
89
|
+
params[:method] = method
|
90
|
+
|
91
|
+
url = Regexp::PERFECT_URL_PATTERN =~ path ? path : to_url(path)
|
92
|
+
response = Excon.new(url, excon_connection_params(params)).request(add_common_request_params(params))
|
93
|
+
# we never want the JSON string, but always the hash representation
|
94
|
+
response.body = hash_of(response.body)
|
95
|
+
response
|
96
|
+
rescue Excon::Errors::HTTPStatusError => e
|
97
|
+
handle_execute_request_error(e, url, native_call)
|
98
|
+
end
|
99
|
+
|
100
|
+
def handle_execute_request_error(e, url, native_call)
|
101
|
+
log.debug 'ERROR, Excon could not execute the request.'
|
102
|
+
# transform json response to Hash object
|
103
|
+
e.response.body = hash_of(e.response.body)
|
104
|
+
|
105
|
+
# if this is a native API call, do not further process the error
|
106
|
+
return e.response if native_call
|
107
|
+
|
108
|
+
# fail with adapter specific error handling
|
109
|
+
handle_error(e.response) if respond_to?(:handle_error)
|
110
|
+
fallback_error_handling(e, url)
|
111
|
+
end
|
112
|
+
|
113
|
+
def fallback_error_handling(e, url)
|
114
|
+
error_status = e.response.status
|
115
|
+
# arriving here, error could not be processed --> use fallback errors
|
116
|
+
if e.is_a? Excon::Errors::ServerError
|
117
|
+
fail Errors::UnknownAdapterCallError, e.message
|
118
|
+
elsif error_status == 404
|
119
|
+
log.error("Resource not found (404) at '#{url}', indicating an adapter issue")
|
120
|
+
fail Errors::UnknownAdapterCallError, 'Resource not found, probably the adapter must be updated'
|
121
|
+
elsif error_status == 401
|
122
|
+
fail Errors::EndpointAuthenticationError,
|
123
|
+
'Auth. failed, probably cache is outdated or permissions were revoked?'
|
124
|
+
else
|
125
|
+
log.error("Fallback error handling (#{error_status}) at '#{url}', indicating an adapter issue")
|
126
|
+
fail Errors::UnknownAdapterCallError, e.message
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def to_url(path)
|
131
|
+
# insert missing slash, prevent double slashes
|
132
|
+
return "#{@endpoint_url}/#{path}" unless @endpoint_url.end_with?('/') || path.start_with?('/')
|
133
|
+
"#{@endpoint_url}#{path}"
|
134
|
+
end
|
135
|
+
|
136
|
+
def excon_connection_params(params)
|
137
|
+
middleware = Excon.defaults[:middlewares].dup
|
138
|
+
|
139
|
+
middleware = if params[:follow_redirects] == false
|
140
|
+
[Excon::Middleware::ResponseParser, Excon::Middleware::Decompress].push(*middleware).uniq
|
141
|
+
else
|
142
|
+
[Excon::Middleware::ResponseParser, Excon::Middleware::RedirectFollower,
|
143
|
+
Excon::Middleware::Decompress].push(*middleware).uniq
|
144
|
+
end
|
145
|
+
{ middlewares: middleware, ssl_verify_peer: @check_certificates }
|
146
|
+
end
|
147
|
+
|
148
|
+
def hash_of(message_body)
|
149
|
+
return {} if message_body.nil? || message_body.empty?
|
150
|
+
begin
|
151
|
+
return Oj.load(message_body, symbol_keys: true)
|
152
|
+
rescue Oj::Error
|
153
|
+
# parsing failed, content probably is no valid JSON content
|
154
|
+
message_body
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def add_common_request_params(params)
|
159
|
+
common_params = { connection_timeout: 610, write_timeout: 600, read_timeout: 600 }
|
160
|
+
# allow to follow redirects in the APIs
|
161
|
+
allowed_status_codes = params.key?(:expects) ? [*params[:expects]] : []
|
162
|
+
unless params[:follow_redirects] == false
|
163
|
+
allowed_status_codes.push(*[301, 302, 303, 307, 308])
|
164
|
+
end
|
165
|
+
|
166
|
+
params[:expects] = allowed_status_codes.uniq
|
167
|
+
# use default or customized headers
|
168
|
+
params[:headers] = headers unless params[:headers]
|
169
|
+
# specify encoding if not done yet: use only gzip since deflate does cause issues with VCR cassettes in tests
|
170
|
+
params[:headers]['Accept-Encoding'] = 'gzip' unless params[:headers].key? 'Accept-Encoding'
|
171
|
+
params[:body] = params[:body].to_json if params.key? :body
|
172
|
+
# merge and return
|
173
|
+
common_params.merge params
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|