diffend-monitor 0.2.27
Sign up to get free protection for your applications and to get access to all the features.
- 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
|