diffend-monitor 0.2.27
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/.coditsu/ci.yml +3 -0
- data/.diffend.yml +3 -0
- data/.github/workflows/ci.yml +64 -0
- data/.gitignore +58 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +75 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.md +57 -0
- data/README.md +25 -0
- data/bin/bundle +114 -0
- data/bin/byebug +29 -0
- data/bin/htmldiff +29 -0
- data/bin/ldiff +29 -0
- data/bin/rake +29 -0
- data/bin/rspec +29 -0
- data/certs/mensfeld.pem +25 -0
- data/certs/tomaszpajor.pem +25 -0
- data/diffend.gemspec +28 -0
- data/lib/diffend.rb +142 -0
- data/lib/diffend/build_bundler_definition.rb +26 -0
- data/lib/diffend/commands.rb +13 -0
- data/lib/diffend/config/fetcher.rb +117 -0
- data/lib/diffend/config/file_finder.rb +38 -0
- data/lib/diffend/config/validator.rb +25 -0
- data/lib/diffend/errors.rb +27 -0
- data/lib/diffend/handle_errors/build_exception_payload.rb +30 -0
- data/lib/diffend/handle_errors/display_to_stdout.rb +17 -0
- data/lib/diffend/handle_errors/messages.rb +29 -0
- data/lib/diffend/handle_errors/report.rb +71 -0
- data/lib/diffend/monitor.rb +28 -0
- data/lib/diffend/request.rb +185 -0
- data/lib/diffend/request_object.rb +6 -0
- data/lib/diffend/track.rb +104 -0
- data/lib/diffend/voting.rb +132 -0
- data/lib/diffend/voting/versions/local.rb +304 -0
- data/lib/diffend/voting/versions/remote.rb +216 -0
- data/plugins.rb +5 -0
- data/scripts/generate_payload_for_file.rb +15 -0
- metadata +138 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diffend
|
4
|
+
module Config
|
5
|
+
# Class used to figure out the file from which we should load the settings
|
6
|
+
module FileFinder
|
7
|
+
# Names of the files or paths where we will look for the settings
|
8
|
+
#
|
9
|
+
# @note We do the double dot trick, to look outside of the current dir because when
|
10
|
+
# executed from a docker container, we copy the local uncommitted settings into the
|
11
|
+
# dir above the app location not to pollute the reset state of the git repo
|
12
|
+
#
|
13
|
+
# @note Order is important, as for local env we should load from
|
14
|
+
# local file (if present first)
|
15
|
+
FILE_NAMES = %w[
|
16
|
+
.diffend.yml
|
17
|
+
].map { |name| ["../#{name}", name] }.tap(&:flatten!).freeze
|
18
|
+
|
19
|
+
private_constant :FILE_NAMES
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Looks for Diffend settings file for a given env
|
23
|
+
#
|
24
|
+
# @param build_path [String] path of the current build
|
25
|
+
#
|
26
|
+
# @return [String] path to the file from which we should load all the settings
|
27
|
+
def call(build_path)
|
28
|
+
FILE_NAMES
|
29
|
+
.map { |name| File.join(build_path, name) }
|
30
|
+
.map { |name| Dir[name] }
|
31
|
+
.find { |selection| !selection.empty? }
|
32
|
+
.tap { |path| path || raise(Errors::MissingConfigurationFile) }
|
33
|
+
.first
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diffend
|
4
|
+
# Module for all the components related to setting up the config
|
5
|
+
module Config
|
6
|
+
# Class responsible for validating the config from .diffend.yml
|
7
|
+
module Validator
|
8
|
+
class << self
|
9
|
+
# @param config [OpenStruct] path of the current build
|
10
|
+
def call(config)
|
11
|
+
raise Errors::ProjectIdMissingInConfigurationFile if missing?(config, 'project_id')
|
12
|
+
raise Errors::ShareableIdMissingInConfigurationFile if missing?(config, 'shareable_id')
|
13
|
+
raise Errors::ShareableKeyMissingInConfigurationFile if missing?(config, 'shareable_key')
|
14
|
+
raise Errors::BuildPathMissingInConfigurationFile if missing?(config, 'build_path')
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def missing?(config, key)
|
20
|
+
config.public_send(key).nil? || config.public_send(key).empty?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diffend
|
4
|
+
# Build runner app errors
|
5
|
+
module Errors
|
6
|
+
# Base error class from which all the errors should inherit
|
7
|
+
BaseError = Class.new(StandardError)
|
8
|
+
# Raised when we couldn't find a valid configuration file
|
9
|
+
MissingConfigurationFile = Class.new(BaseError)
|
10
|
+
# Raised when configuration file is empty
|
11
|
+
EmptyConfigurationFile = Class.new(BaseError)
|
12
|
+
# Raised when configuration file is malformed
|
13
|
+
MalformedConfigurationFile = Class.new(BaseError)
|
14
|
+
# Raised when project_id is missing in configuration file
|
15
|
+
ProjectIdMissingInConfigurationFile = Class.new(BaseError)
|
16
|
+
# Raised when shareable_id is missing in configuration file
|
17
|
+
ShareableIdMissingInConfigurationFile = Class.new(BaseError)
|
18
|
+
# Raised when shareable_key is missing in configuration file
|
19
|
+
ShareableKeyMissingInConfigurationFile = Class.new(BaseError)
|
20
|
+
# Raised when build_path is missing in configuration file
|
21
|
+
BuildPathMissingInConfigurationFile = Class.new(BaseError)
|
22
|
+
# Raised when server-side error occurs
|
23
|
+
RequestServerError = Class.new(BaseError)
|
24
|
+
# Raised when we had an exception that we know how to handle
|
25
|
+
HandledException = Class.new(BaseError)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'securerandom'
|
4
|
+
|
5
|
+
module Diffend
|
6
|
+
module HandleErrors
|
7
|
+
# Module responsible for building exception payload
|
8
|
+
module BuildExceptionPayload
|
9
|
+
class << self
|
10
|
+
# Build exception payload
|
11
|
+
#
|
12
|
+
# @param exception [Exception, NilClass] expection that was raised
|
13
|
+
# @param payload [Hash] with versions to check
|
14
|
+
#
|
15
|
+
# @return [Hash]
|
16
|
+
def call(exception, payload)
|
17
|
+
{
|
18
|
+
request_id: SecureRandom.uuid,
|
19
|
+
payload: payload,
|
20
|
+
exception: {
|
21
|
+
class: exception&.class,
|
22
|
+
message: exception&.message,
|
23
|
+
backtrace: exception&.backtrace
|
24
|
+
}
|
25
|
+
}.freeze
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diffend
|
4
|
+
module HandleErrors
|
5
|
+
# Module responsible for displaying exception payload to stdout
|
6
|
+
module DisplayToStdout
|
7
|
+
class << self
|
8
|
+
# Display exception payload to stdout
|
9
|
+
#
|
10
|
+
# @param exception_payload [Hash]
|
11
|
+
def call(exception_payload)
|
12
|
+
puts exception_payload.to_json
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diffend
|
4
|
+
module HandleErrors
|
5
|
+
module Messages
|
6
|
+
PAYLOAD_DUMP = '^^^ Above is the dump of your request ^^^'
|
7
|
+
UNHANDLED_EXCEPTION = <<~MSG
|
8
|
+
\nSomething went really wrong. We recorded this incident in our system and will review it.\n
|
9
|
+
This is a bug, don't hesitate.\n
|
10
|
+
Create an issue at https://github.com/diffend-io/diffend-ruby/issues\n
|
11
|
+
MSG
|
12
|
+
UNSUPPORTED_RESPONSE = <<~MSG
|
13
|
+
\nAPI returned an unsupported response. We recorded this incident in our system and will review it.\n
|
14
|
+
This is a bug, don't hesitate.\n
|
15
|
+
Create an issue at https://github.com/diffend-io/diffend-ruby/issues\n
|
16
|
+
MSG
|
17
|
+
UNSUPPORTED_VERDICT = <<~MSG
|
18
|
+
\nAPI returned an unsupported verdict. We recorded this incident in our system and will review it.\n
|
19
|
+
This is a bug, don't hesitate.\n
|
20
|
+
Create an issue at https://github.com/diffend-io/diffend-ruby/issues\n
|
21
|
+
MSG
|
22
|
+
REQUEST_ERROR = <<~MSG
|
23
|
+
\nWe were unable to process your request at this time. We recorded this incident in our system and will review it.\n
|
24
|
+
If you think that this is a bug, don't hesitate.\n
|
25
|
+
Create an issue at https://github.com/diffend-io/diffend-ruby/issues\n
|
26
|
+
MSG
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Diffend
|
4
|
+
module HandleErrors
|
5
|
+
# Module responsible for reporting errors to diffend
|
6
|
+
module Report
|
7
|
+
class << self
|
8
|
+
# Execute request to Diffend
|
9
|
+
#
|
10
|
+
# @param exception [Exception] expection that was raised
|
11
|
+
# @param payload [Hash] with versions to check
|
12
|
+
# @param config [OpenStruct] Diffend config
|
13
|
+
# @param message [Symbol] message that we want to display
|
14
|
+
# @param report [Boolean] if true we will report the issue to diffend
|
15
|
+
# @param raise_exception [Boolean] if true we will raise an exception
|
16
|
+
#
|
17
|
+
# @return [Net::HTTPResponse] response from Diffend
|
18
|
+
def call(config:, message:, exception: nil, payload: {}, report: false, raise_exception: true)
|
19
|
+
exception_payload = prepare_exception_payload(exception, payload)
|
20
|
+
|
21
|
+
Bundler.ui.error(Diffend::HandleErrors::Messages::PAYLOAD_DUMP)
|
22
|
+
Bundler.ui.error(Diffend::HandleErrors::Messages.const_get(message.to_s.upcase))
|
23
|
+
|
24
|
+
if report
|
25
|
+
Diffend::Request.call(
|
26
|
+
build_request_object(config, exception_payload)
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
raise Diffend::Errors::HandledException if raise_exception
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param config [OpenStruct] diffend config
|
34
|
+
# @param payload [Hash]
|
35
|
+
#
|
36
|
+
# @return [Diffend::RequestObject]
|
37
|
+
def build_request_object(config, payload)
|
38
|
+
Diffend::RequestObject.new(
|
39
|
+
config: config,
|
40
|
+
url: errors_url(config.project_id),
|
41
|
+
payload: payload,
|
42
|
+
request_method: :post
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Prepare exception payload and display it to stdout
|
47
|
+
#
|
48
|
+
# @param exception [Exception] expection that was raised
|
49
|
+
# @param payload [Hash] with versions to check
|
50
|
+
#
|
51
|
+
# @return [Hash]
|
52
|
+
def prepare_exception_payload(exception, payload)
|
53
|
+
Diffend::HandleErrors::BuildExceptionPayload
|
54
|
+
.call(exception, payload)
|
55
|
+
.tap(&Diffend::HandleErrors::DisplayToStdout.method(:call))
|
56
|
+
end
|
57
|
+
|
58
|
+
# Provides diffend errors endpoint url
|
59
|
+
#
|
60
|
+
# @param project_id [String] diffend project_id
|
61
|
+
#
|
62
|
+
# @return [String] diffend endpoint
|
63
|
+
def errors_url(project_id)
|
64
|
+
return ENV['DIFFEND_ERROR_URL'] if ENV.key?('DIFFEND_ERROR_URL')
|
65
|
+
|
66
|
+
"https://my.diffend.io/api/projects/#{project_id}/errors"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
%w[
|
4
|
+
build_bundler_definition
|
5
|
+
errors
|
6
|
+
config/fetcher
|
7
|
+
config/file_finder
|
8
|
+
config/validator
|
9
|
+
commands
|
10
|
+
handle_errors/messages
|
11
|
+
handle_errors/build_exception_payload
|
12
|
+
handle_errors/display_to_stdout
|
13
|
+
handle_errors/report
|
14
|
+
request_object
|
15
|
+
request
|
16
|
+
voting
|
17
|
+
track
|
18
|
+
].each { |file| require "diffend/#{file}" }
|
19
|
+
|
20
|
+
%w[
|
21
|
+
versions/local
|
22
|
+
versions/remote
|
23
|
+
].each { |file| require "diffend/voting/#{file}" }
|
24
|
+
|
25
|
+
Thread.new do
|
26
|
+
track = Diffend::Track.new
|
27
|
+
track.start
|
28
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'openssl'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module Diffend
|
8
|
+
# Module responsible for doing request to Diffend
|
9
|
+
module Request
|
10
|
+
# Message displayed when connection issue occured and we will retry
|
11
|
+
CONNECTION_MESSAGE = 'We experienced a connection issue, retrying...'
|
12
|
+
# List of connection exceptions
|
13
|
+
CONNECTION_EXCEPTIONS = [
|
14
|
+
Errno::ECONNRESET,
|
15
|
+
Errno::ENETUNREACH,
|
16
|
+
Errno::EHOSTUNREACH,
|
17
|
+
Errno::ECONNREFUSED
|
18
|
+
].freeze
|
19
|
+
# Message displayed when timeout occured and we will retry
|
20
|
+
TIMEOUT_MESSAGE = 'We experienced a connection issue, retrying...'
|
21
|
+
# List of timeout exceptions
|
22
|
+
TIMEOUT_EXCEPTIONS = [
|
23
|
+
Net::OpenTimeout,
|
24
|
+
Net::ReadTimeout
|
25
|
+
].freeze
|
26
|
+
# Message displayed when server issue occured and we will retry
|
27
|
+
SERVER_ERROR_MESSAGE = 'We experienced a server-side issue, retrying...'
|
28
|
+
# List of server issues
|
29
|
+
#
|
30
|
+
# 500 - Internal Server Error
|
31
|
+
# 502 - Bad Gateway
|
32
|
+
# 503 - Service Unavailable
|
33
|
+
# 504 - Gateway Timeout
|
34
|
+
SERVER_ERRORS = [500, 502, 503, 504].freeze
|
35
|
+
# Number of retries
|
36
|
+
RETRIES = 3
|
37
|
+
# Request headers
|
38
|
+
HEADERS = { 'Content-Type': 'application/json' }.freeze
|
39
|
+
|
40
|
+
private_constant :HEADERS
|
41
|
+
|
42
|
+
class << self
|
43
|
+
# Execute request
|
44
|
+
#
|
45
|
+
# @param request_object [Diffend::RequestObject]
|
46
|
+
#
|
47
|
+
# @return [Net::HTTPResponse] response from Diffend
|
48
|
+
def call(request_object)
|
49
|
+
retry_count ||= -1
|
50
|
+
|
51
|
+
build_http(request_object.url) do |http, uri|
|
52
|
+
response = http.request(
|
53
|
+
build_request(
|
54
|
+
uri,
|
55
|
+
request_object.request_method,
|
56
|
+
request_object.config,
|
57
|
+
request_object.payload
|
58
|
+
)
|
59
|
+
)
|
60
|
+
|
61
|
+
if SERVER_ERRORS.include?(response.code.to_i)
|
62
|
+
raise Diffend::Errors::RequestServerError, response.code.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
response
|
66
|
+
end
|
67
|
+
rescue Diffend::Errors::RequestServerError => e
|
68
|
+
retry_count += 1
|
69
|
+
|
70
|
+
retry if handle_retry(SERVER_ERROR_MESSAGE, retry_count)
|
71
|
+
|
72
|
+
Diffend::HandleErrors::Report.call(
|
73
|
+
exception: e,
|
74
|
+
payload: request_object.payload,
|
75
|
+
config: request_object.config,
|
76
|
+
message: :request_error
|
77
|
+
)
|
78
|
+
rescue *CONNECTION_EXCEPTIONS => e
|
79
|
+
retry_count += 1
|
80
|
+
|
81
|
+
retry if handle_retry(CONNECTION_MESSAGE, retry_count)
|
82
|
+
|
83
|
+
Diffend::HandleErrors::Report.call(
|
84
|
+
exception: e,
|
85
|
+
payload: request_object.payload,
|
86
|
+
config: request_object.config,
|
87
|
+
message: :request_error
|
88
|
+
)
|
89
|
+
rescue *TIMEOUT_EXCEPTIONS => e
|
90
|
+
retry_count += 1
|
91
|
+
|
92
|
+
retry if handle_retry(TIMEOUT_MESSAGE, retry_count)
|
93
|
+
|
94
|
+
Diffend::HandleErrors::Report.call(
|
95
|
+
exception: e,
|
96
|
+
payload: request_object.payload,
|
97
|
+
config: request_object.config,
|
98
|
+
message: :request_error
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Handle retry
|
103
|
+
#
|
104
|
+
# @param message [String] message we want to display
|
105
|
+
# @param retry_count [Integer]
|
106
|
+
def handle_retry(message, retry_count)
|
107
|
+
return false if retry_count == RETRIES
|
108
|
+
|
109
|
+
Bundler.ui.error(message)
|
110
|
+
sleep(exponential_backoff(retry_count))
|
111
|
+
|
112
|
+
retry_count < RETRIES
|
113
|
+
end
|
114
|
+
|
115
|
+
# Builds http connection object
|
116
|
+
#
|
117
|
+
# @param url [String] command endpoint url
|
118
|
+
def build_http(url)
|
119
|
+
uri = URI(url)
|
120
|
+
|
121
|
+
Net::HTTP.start(
|
122
|
+
uri.host,
|
123
|
+
uri.port,
|
124
|
+
use_ssl: uri.scheme == 'https',
|
125
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE,
|
126
|
+
open_timeout: 5,
|
127
|
+
read_timeout: 5
|
128
|
+
) { |http| yield(http, uri) }
|
129
|
+
end
|
130
|
+
|
131
|
+
# Build http post request and assigns headers and payload
|
132
|
+
#
|
133
|
+
# @param uri [URI::HTTPS]
|
134
|
+
# @param request_method [Symbol]
|
135
|
+
# @param config [OpenStruct] Diffend config
|
136
|
+
# @param payload [Hash] with versions to check
|
137
|
+
#
|
138
|
+
# @return [Net::HTTP::Post, Net::HTTP::Put]
|
139
|
+
def build_request(uri, request_method, config, payload)
|
140
|
+
pick_request_method(request_method)
|
141
|
+
.new(uri.request_uri, HEADERS)
|
142
|
+
.tap { |request| assign_auth(request, config) }
|
143
|
+
.tap { |request| assign_payload(request, payload) }
|
144
|
+
end
|
145
|
+
|
146
|
+
# Pick request method
|
147
|
+
#
|
148
|
+
# @param request_method [Symbol]
|
149
|
+
#
|
150
|
+
# @return [Net::HTTP::Post, Net::HTTP::Put]
|
151
|
+
def pick_request_method(request_method)
|
152
|
+
case request_method
|
153
|
+
when :post
|
154
|
+
Net::HTTP::Post
|
155
|
+
when :put
|
156
|
+
Net::HTTP::Put
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# Assigns basic authorization if provided in the config
|
161
|
+
#
|
162
|
+
# @param request [Net::HTTP::Post] prepared http post
|
163
|
+
# @param config [OpenStruct] Diffend config
|
164
|
+
def assign_auth(request, config)
|
165
|
+
return unless config
|
166
|
+
return unless config.shareable_id
|
167
|
+
return unless config.shareable_key
|
168
|
+
|
169
|
+
request.basic_auth(config.shareable_id, config.shareable_key)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Assigns payload as json
|
173
|
+
#
|
174
|
+
# @param request [Net::HTTP::Post] prepared http post
|
175
|
+
# @param payload [Hash] with versions to check
|
176
|
+
def assign_payload(request, payload)
|
177
|
+
request.body = JSON.dump(payload: payload)
|
178
|
+
end
|
179
|
+
|
180
|
+
def exponential_backoff(retry_count)
|
181
|
+
2**(retry_count + 1)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|