hooks-ruby 0.0.2 → 0.0.4
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/README.md +22 -6
- data/hooks.gemspec +0 -1
- data/lib/hooks/app/api.rb +59 -11
- data/lib/hooks/app/auth/auth.rb +15 -6
- data/lib/hooks/app/endpoints/catchall.rb +15 -3
- data/lib/hooks/app/endpoints/health.rb +2 -2
- data/lib/hooks/app/endpoints/version.rb +1 -1
- data/lib/hooks/app/helpers.rb +39 -9
- data/lib/hooks/core/builder.rb +6 -0
- data/lib/hooks/core/component_access.rb +69 -0
- data/lib/hooks/core/config_loader.rb +41 -9
- data/lib/hooks/core/config_validator.rb +3 -0
- data/lib/hooks/core/failbot.rb +50 -0
- data/lib/hooks/core/global_components.rb +51 -0
- data/lib/hooks/core/log.rb +15 -0
- data/lib/hooks/core/plugin_loader.rb +190 -4
- data/lib/hooks/core/stats.rb +54 -0
- data/lib/hooks/plugins/auth/base.rb +4 -12
- data/lib/hooks/plugins/auth/hmac.rb +20 -18
- data/lib/hooks/plugins/auth/timestamp_validator.rb +133 -0
- data/lib/hooks/plugins/handlers/base.rb +5 -12
- data/lib/hooks/plugins/handlers/default.rb +32 -3
- data/lib/hooks/plugins/instruments/failbot.rb +32 -0
- data/lib/hooks/plugins/instruments/failbot_base.rb +72 -0
- data/lib/hooks/plugins/instruments/stats.rb +32 -0
- data/lib/hooks/plugins/instruments/stats_base.rb +88 -0
- data/lib/hooks/plugins/lifecycle.rb +5 -0
- data/lib/hooks/utils/retry.rb +37 -0
- data/lib/hooks/version.rb +4 -1
- data/lib/hooks.rb +23 -10
- metadata +11 -21
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "time"
|
4
|
+
|
5
|
+
module Hooks
|
6
|
+
module Plugins
|
7
|
+
module Auth
|
8
|
+
# Validates and parses timestamps for webhook authentication
|
9
|
+
#
|
10
|
+
# This class provides secure timestamp validation supporting both
|
11
|
+
# ISO 8601 UTC format and Unix timestamp format. It includes
|
12
|
+
# strict validation to prevent various injection attacks.
|
13
|
+
#
|
14
|
+
# @example Basic usage
|
15
|
+
# validator = TimestampValidator.new
|
16
|
+
# validator.valid?("1609459200", 300) # => true/false
|
17
|
+
# validator.parse("2021-01-01T00:00:00Z") # => 1609459200
|
18
|
+
#
|
19
|
+
# @api private
|
20
|
+
class TimestampValidator
|
21
|
+
# Validate timestamp against current time with tolerance
|
22
|
+
#
|
23
|
+
# @param timestamp_value [String] The timestamp string to validate
|
24
|
+
# @param tolerance [Integer] Maximum age in seconds (default: 300)
|
25
|
+
# @return [Boolean] true if timestamp is valid and within tolerance
|
26
|
+
def valid?(timestamp_value, tolerance = 300)
|
27
|
+
return false if timestamp_value.nil? || timestamp_value.strip.empty?
|
28
|
+
|
29
|
+
parsed_timestamp = parse(timestamp_value.strip)
|
30
|
+
return false unless parsed_timestamp.is_a?(Integer)
|
31
|
+
|
32
|
+
now = Time.now.utc.to_i
|
33
|
+
(now - parsed_timestamp).abs <= tolerance
|
34
|
+
end
|
35
|
+
|
36
|
+
# Parse timestamp value supporting both ISO 8601 UTC and Unix formats
|
37
|
+
#
|
38
|
+
# @param timestamp_value [String] The timestamp string to parse
|
39
|
+
# @return [Integer, nil] Epoch seconds if parsing succeeds, nil otherwise
|
40
|
+
# @note Security: Strict validation prevents various injection attacks
|
41
|
+
def parse(timestamp_value)
|
42
|
+
return nil if invalid_characters?(timestamp_value)
|
43
|
+
|
44
|
+
parse_iso8601_timestamp(timestamp_value) || parse_unix_timestamp(timestamp_value)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Check for control characters, whitespace, or null bytes
|
50
|
+
#
|
51
|
+
# @param timestamp_value [String] The timestamp to check
|
52
|
+
# @return [Boolean] true if contains invalid characters
|
53
|
+
def invalid_characters?(timestamp_value)
|
54
|
+
if timestamp_value =~ /[\u0000-\u001F\u007F-\u009F]/
|
55
|
+
log_warning("Timestamp contains invalid characters")
|
56
|
+
true
|
57
|
+
else
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Parse ISO 8601 UTC timestamp string (must have UTC indicator)
|
63
|
+
#
|
64
|
+
# @param timestamp_value [String] ISO 8601 timestamp string
|
65
|
+
# @return [Integer, nil] Epoch seconds if parsing succeeds, nil otherwise
|
66
|
+
def parse_iso8601_timestamp(timestamp_value)
|
67
|
+
# Handle space-separated format and convert to standard ISO format
|
68
|
+
if timestamp_value =~ /\A(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2}:\d{2}(?:\.\d+)?)(?: )\+0000\z/
|
69
|
+
timestamp_value = "#{$1}T#{$2}+00:00"
|
70
|
+
end
|
71
|
+
|
72
|
+
# Ensure the timestamp explicitly includes a UTC indicator
|
73
|
+
return nil unless timestamp_value =~ /(Z|\+00:00|\+0000)\z/
|
74
|
+
return nil unless iso8601_format?(timestamp_value)
|
75
|
+
|
76
|
+
parsed_time = parse_time_safely(timestamp_value)
|
77
|
+
return nil unless parsed_time&.utc_offset&.zero?
|
78
|
+
|
79
|
+
parsed_time.to_i
|
80
|
+
end
|
81
|
+
|
82
|
+
# Parse Unix timestamp string (must be positive integer, no leading zeros except for "0")
|
83
|
+
#
|
84
|
+
# @param timestamp_value [String] Unix timestamp string
|
85
|
+
# @return [Integer, nil] Epoch seconds if parsing succeeds, nil otherwise
|
86
|
+
def parse_unix_timestamp(timestamp_value)
|
87
|
+
return nil unless unix_format?(timestamp_value)
|
88
|
+
|
89
|
+
ts = timestamp_value.to_i
|
90
|
+
return nil if ts <= 0
|
91
|
+
|
92
|
+
ts
|
93
|
+
end
|
94
|
+
|
95
|
+
# Check if timestamp string looks like ISO 8601 format
|
96
|
+
#
|
97
|
+
# @param timestamp_value [String] The timestamp string to check
|
98
|
+
# @return [Boolean] true if it appears to be ISO 8601 format
|
99
|
+
def iso8601_format?(timestamp_value)
|
100
|
+
!!(timestamp_value =~ /\A\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?(Z|\+00:00|\+0000)?\z/)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Check if timestamp string looks like Unix timestamp format
|
104
|
+
#
|
105
|
+
# @param timestamp_value [String] The timestamp string to check
|
106
|
+
# @return [Boolean] true if it appears to be Unix timestamp format
|
107
|
+
def unix_format?(timestamp_value)
|
108
|
+
return true if timestamp_value == "0"
|
109
|
+
!!(timestamp_value =~ /\A[1-9]\d*\z/)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Safely parse time string with error handling
|
113
|
+
#
|
114
|
+
# @param timestamp_value [String] The timestamp string to parse
|
115
|
+
# @return [Time, nil] Parsed time object or nil if parsing fails
|
116
|
+
def parse_time_safely(timestamp_value)
|
117
|
+
Time.parse(timestamp_value)
|
118
|
+
rescue ArgumentError
|
119
|
+
nil
|
120
|
+
end
|
121
|
+
|
122
|
+
# Log warning message
|
123
|
+
#
|
124
|
+
# @param message [String] Warning message to log
|
125
|
+
def log_warning(message)
|
126
|
+
return unless defined?(Hooks::Log) && Hooks::Log.instance
|
127
|
+
|
128
|
+
Hooks::Log.instance.warn("Auth::TimestampValidator validation failed: #{message}")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../../core/global_components"
|
4
|
+
require_relative "../../core/component_access"
|
5
|
+
|
3
6
|
module Hooks
|
4
7
|
module Plugins
|
5
8
|
module Handlers
|
@@ -7,6 +10,8 @@ module Hooks
|
|
7
10
|
#
|
8
11
|
# All custom handlers must inherit from this class and implement the #call method
|
9
12
|
class Base
|
13
|
+
include Hooks::Core::ComponentAccess
|
14
|
+
|
10
15
|
# Process a webhook request
|
11
16
|
#
|
12
17
|
# @param payload [Hash, String] Parsed request body (JSON Hash) or raw string
|
@@ -17,18 +22,6 @@ module Hooks
|
|
17
22
|
def call(payload:, headers:, config:)
|
18
23
|
raise NotImplementedError, "Handler must implement #call method"
|
19
24
|
end
|
20
|
-
|
21
|
-
# Short logger accessor for all subclasses
|
22
|
-
# @return [Hooks::Log] Logger instance
|
23
|
-
#
|
24
|
-
# Provides a convenient way for handlers to log messages without needing
|
25
|
-
# to reference the full Hooks::Log namespace.
|
26
|
-
#
|
27
|
-
# @example Logging an error in an inherited class
|
28
|
-
# log.error("oh no an error occured")
|
29
|
-
def log
|
30
|
-
Hooks::Log.instance
|
31
|
-
end
|
32
25
|
end
|
33
26
|
end
|
34
27
|
end
|
@@ -1,8 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Default
|
4
|
-
#
|
3
|
+
# Default webhook handler implementation
|
4
|
+
#
|
5
|
+
# This handler provides a basic webhook processing implementation that can be used
|
6
|
+
# as a fallback when no custom handler is configured for an endpoint. It demonstrates
|
7
|
+
# the standard handler interface and provides basic logging functionality.
|
8
|
+
#
|
9
|
+
# @example Usage in endpoint configuration
|
10
|
+
# handler:
|
11
|
+
# type: DefaultHandler
|
12
|
+
#
|
13
|
+
# @see Hooks::Plugins::Handlers::Base
|
5
14
|
class DefaultHandler < Hooks::Plugins::Handlers::Base
|
15
|
+
# Process a webhook request with basic acknowledgment
|
16
|
+
#
|
17
|
+
# Provides a simple webhook processing implementation that logs the request
|
18
|
+
# and returns a standard acknowledgment response. This is useful for testing
|
19
|
+
# webhook endpoints or as a placeholder during development.
|
20
|
+
#
|
21
|
+
# @param payload [Hash, String] The webhook payload (parsed JSON or raw string)
|
22
|
+
# @param headers [Hash<String, String>] HTTP headers from the webhook request
|
23
|
+
# @param config [Hash] Endpoint configuration containing handler options
|
24
|
+
# @return [Hash] Response indicating successful processing
|
25
|
+
# @option config [Hash] :opts Additional handler-specific configuration options
|
26
|
+
#
|
27
|
+
# @example Basic usage
|
28
|
+
# handler = DefaultHandler.new
|
29
|
+
# response = handler.call(
|
30
|
+
# payload: { "event" => "push" },
|
31
|
+
# headers: { "Content-Type" => "application/json" },
|
32
|
+
# config: { opts: {} }
|
33
|
+
# )
|
34
|
+
# # => { message: "webhook processed successfully", handler: "DefaultHandler", timestamp: "..." }
|
6
35
|
def call(payload:, headers:, config:)
|
7
36
|
|
8
37
|
log.info("🔔 Default handler invoked for webhook 🔔")
|
@@ -15,7 +44,7 @@ class DefaultHandler < Hooks::Plugins::Handlers::Base
|
|
15
44
|
{
|
16
45
|
message: "webhook processed successfully",
|
17
46
|
handler: "DefaultHandler",
|
18
|
-
timestamp: Time.now.iso8601
|
47
|
+
timestamp: Time.now.utc.iso8601
|
19
48
|
}
|
20
49
|
end
|
21
50
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "failbot_base"
|
4
|
+
|
5
|
+
module Hooks
|
6
|
+
module Plugins
|
7
|
+
module Instruments
|
8
|
+
# Default failbot instrument implementation
|
9
|
+
#
|
10
|
+
# This is a no-op implementation that provides the error reporting interface
|
11
|
+
# without actually sending errors anywhere. It serves as a safe default when
|
12
|
+
# no custom error reporting implementation is configured.
|
13
|
+
#
|
14
|
+
# Users should replace this with their own implementation for services
|
15
|
+
# like Sentry, Rollbar, Honeybadger, etc.
|
16
|
+
#
|
17
|
+
# @example Replacing with a custom implementation
|
18
|
+
# # In your application initialization:
|
19
|
+
# custom_failbot = MySentryFailbotImplementation.new
|
20
|
+
# Hooks::Core::GlobalComponents.failbot = custom_failbot
|
21
|
+
#
|
22
|
+
# @see Hooks::Plugins::Instruments::FailbotBase
|
23
|
+
# @see Hooks::Core::GlobalComponents
|
24
|
+
class Failbot < FailbotBase
|
25
|
+
# Inherit from FailbotBase to provide a default no-op implementation
|
26
|
+
# of the error reporting instrument interface.
|
27
|
+
#
|
28
|
+
# All methods from FailbotBase are inherited and provide safe no-op behavior.
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../core/component_access"
|
4
|
+
|
5
|
+
module Hooks
|
6
|
+
module Plugins
|
7
|
+
module Instruments
|
8
|
+
# Base class for all failbot instrument plugins
|
9
|
+
#
|
10
|
+
# This class provides the foundation for implementing custom error reporting
|
11
|
+
# instruments. Subclasses should implement specific methods for their target
|
12
|
+
# error reporting service (Sentry, Rollbar, Honeybadger, etc.).
|
13
|
+
#
|
14
|
+
# @abstract Subclass and implement service-specific error reporting methods
|
15
|
+
# @example Implementing a custom failbot instrument
|
16
|
+
# class MySentryFailbot < Hooks::Plugins::Instruments::FailbotBase
|
17
|
+
# def report(error_or_message, context = {})
|
18
|
+
# case error_or_message
|
19
|
+
# when Exception
|
20
|
+
# Sentry.capture_exception(error_or_message, extra: context)
|
21
|
+
# else
|
22
|
+
# Sentry.capture_message(error_or_message.to_s, extra: context)
|
23
|
+
# end
|
24
|
+
# log.debug("Reported error to Sentry")
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# @see Hooks::Plugins::Instruments::Failbot
|
29
|
+
class FailbotBase
|
30
|
+
include Hooks::Core::ComponentAccess
|
31
|
+
|
32
|
+
# Report an error or message to the error tracking service
|
33
|
+
#
|
34
|
+
# This is a no-op implementation that subclasses should override
|
35
|
+
# to provide actual error reporting functionality.
|
36
|
+
#
|
37
|
+
# @param error_or_message [Exception, String] The error to report or message string
|
38
|
+
# @param context [Hash] Additional context information about the error
|
39
|
+
# @return [void]
|
40
|
+
# @note Subclasses should implement this method for their specific service
|
41
|
+
# @example Override in subclass
|
42
|
+
# def report(error_or_message, context = {})
|
43
|
+
# if error_or_message.is_a?(Exception)
|
44
|
+
# ErrorService.report_exception(error_or_message, context)
|
45
|
+
# else
|
46
|
+
# ErrorService.report_message(error_or_message, context)
|
47
|
+
# end
|
48
|
+
# end
|
49
|
+
def report(error_or_message, context = {})
|
50
|
+
# No-op implementation for base class
|
51
|
+
end
|
52
|
+
|
53
|
+
# Report a warning-level message
|
54
|
+
#
|
55
|
+
# This is a no-op implementation that subclasses should override
|
56
|
+
# to provide actual warning reporting functionality.
|
57
|
+
#
|
58
|
+
# @param message [String] Warning message to report
|
59
|
+
# @param context [Hash] Additional context information
|
60
|
+
# @return [void]
|
61
|
+
# @note Subclasses should implement this method for their specific service
|
62
|
+
# @example Override in subclass
|
63
|
+
# def warn(message, context = {})
|
64
|
+
# ErrorService.report_warning(message, context)
|
65
|
+
# end
|
66
|
+
def warn(message, context = {})
|
67
|
+
# No-op implementation for base class
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "stats_base"
|
4
|
+
|
5
|
+
module Hooks
|
6
|
+
module Plugins
|
7
|
+
module Instruments
|
8
|
+
# Default stats instrument implementation
|
9
|
+
#
|
10
|
+
# This is a no-op implementation that provides the stats interface without
|
11
|
+
# actually sending metrics anywhere. It serves as a safe default when no
|
12
|
+
# custom stats implementation is configured.
|
13
|
+
#
|
14
|
+
# Users should replace this with their own implementation for services
|
15
|
+
# like DataDog, New Relic, StatsD, etc.
|
16
|
+
#
|
17
|
+
# @example Replacing with a custom implementation
|
18
|
+
# # In your application initialization:
|
19
|
+
# custom_stats = MyCustomStatsImplementation.new
|
20
|
+
# Hooks::Core::GlobalComponents.stats = custom_stats
|
21
|
+
#
|
22
|
+
# @see Hooks::Plugins::Instruments::StatsBase
|
23
|
+
# @see Hooks::Core::GlobalComponents
|
24
|
+
class Stats < StatsBase
|
25
|
+
# Inherit from StatsBase to provide a default no-op implementation
|
26
|
+
# of the stats instrument interface.
|
27
|
+
#
|
28
|
+
# All methods from StatsBase are inherited and provide safe no-op behavior.
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../core/component_access"
|
4
|
+
|
5
|
+
module Hooks
|
6
|
+
module Plugins
|
7
|
+
module Instruments
|
8
|
+
# Base class for all stats instrument plugins
|
9
|
+
#
|
10
|
+
# This class provides the foundation for implementing custom metrics reporting
|
11
|
+
# instruments. Subclasses should implement specific methods for their target
|
12
|
+
# metrics service (DataDog, New Relic, StatsD, etc.).
|
13
|
+
#
|
14
|
+
# @abstract Subclass and implement service-specific metrics methods
|
15
|
+
# @example Implementing a custom stats instrument
|
16
|
+
# class MyStatsImplementation < Hooks::Plugins::Instruments::StatsBase
|
17
|
+
# def increment(metric_name, tags = {})
|
18
|
+
# # Send increment metric to your service
|
19
|
+
# MyMetricsService.increment(metric_name, tags)
|
20
|
+
# log.debug("Sent increment metric: #{metric_name}")
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def timing(metric_name, duration, tags = {})
|
24
|
+
# # Send timing metric to your service
|
25
|
+
# MyMetricsService.timing(metric_name, duration, tags)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# @see Hooks::Plugins::Instruments::Stats
|
30
|
+
class StatsBase
|
31
|
+
include Hooks::Core::ComponentAccess
|
32
|
+
|
33
|
+
# Record an increment metric
|
34
|
+
#
|
35
|
+
# This is a no-op implementation that subclasses should override
|
36
|
+
# to provide actual metrics reporting functionality.
|
37
|
+
#
|
38
|
+
# @param metric_name [String] Name of the metric to increment
|
39
|
+
# @param tags [Hash] Optional tags/labels for the metric
|
40
|
+
# @return [void]
|
41
|
+
# @note Subclasses should implement this method for their specific service
|
42
|
+
# @example Override in subclass
|
43
|
+
# def increment(metric_name, tags = {})
|
44
|
+
# statsd.increment(metric_name, tags: tags)
|
45
|
+
# end
|
46
|
+
def increment(metric_name, tags = {})
|
47
|
+
# No-op implementation for base class
|
48
|
+
end
|
49
|
+
|
50
|
+
# Record a timing/duration metric
|
51
|
+
#
|
52
|
+
# This is a no-op implementation that subclasses should override
|
53
|
+
# to provide actual metrics reporting functionality.
|
54
|
+
#
|
55
|
+
# @param metric_name [String] Name of the timing metric
|
56
|
+
# @param duration [Numeric] Duration value (typically in milliseconds)
|
57
|
+
# @param tags [Hash] Optional tags/labels for the metric
|
58
|
+
# @return [void]
|
59
|
+
# @note Subclasses should implement this method for their specific service
|
60
|
+
# @example Override in subclass
|
61
|
+
# def timing(metric_name, duration, tags = {})
|
62
|
+
# statsd.timing(metric_name, duration, tags: tags)
|
63
|
+
# end
|
64
|
+
def timing(metric_name, duration, tags = {})
|
65
|
+
# No-op implementation for base class
|
66
|
+
end
|
67
|
+
|
68
|
+
# Record a gauge metric
|
69
|
+
#
|
70
|
+
# This is a no-op implementation that subclasses should override
|
71
|
+
# to provide actual metrics reporting functionality.
|
72
|
+
#
|
73
|
+
# @param metric_name [String] Name of the gauge metric
|
74
|
+
# @param value [Numeric] Current value for the gauge
|
75
|
+
# @param tags [Hash] Optional tags/labels for the metric
|
76
|
+
# @return [void]
|
77
|
+
# @note Subclasses should implement this method for their specific service
|
78
|
+
# @example Override in subclass
|
79
|
+
# def gauge(metric_name, value, tags = {})
|
80
|
+
# statsd.gauge(metric_name, value, tags: tags)
|
81
|
+
# end
|
82
|
+
def gauge(metric_name, value, tags = {})
|
83
|
+
# No-op implementation for base class
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -1,11 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../core/global_components"
|
4
|
+
require_relative "../core/component_access"
|
5
|
+
|
3
6
|
module Hooks
|
4
7
|
module Plugins
|
5
8
|
# Base class for global lifecycle plugins
|
6
9
|
#
|
7
10
|
# Plugins can hook into request/response/error lifecycle events
|
8
11
|
class Lifecycle
|
12
|
+
include Hooks::Core::ComponentAccess
|
13
|
+
|
9
14
|
# Called before handler execution
|
10
15
|
#
|
11
16
|
# @param env [Hash] Rack environment
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "retryable"
|
4
|
+
|
5
|
+
# Utility module for retry functionality
|
6
|
+
module Retry
|
7
|
+
# This method should be called as early as possible in the startup of your application
|
8
|
+
# It sets up the Retryable gem with custom contexts and passes through a few options
|
9
|
+
# Should the number of retries be reached without success, the last exception will be raised
|
10
|
+
#
|
11
|
+
# @param log [Logger] The logger to use for retryable logging
|
12
|
+
# @raise [ArgumentError] If no logger is provided
|
13
|
+
# @return [void]
|
14
|
+
def self.setup!(log: nil, log_retries: ENV.fetch("RETRY_LOG_RETRIES", "true") == "true")
|
15
|
+
raise ArgumentError, "a logger must be provided" if log.nil?
|
16
|
+
|
17
|
+
log_method = lambda do |retries, exception|
|
18
|
+
# :nocov:
|
19
|
+
if log_retries
|
20
|
+
log.debug("[retry ##{retries}] #{exception.class}: #{exception.message} - #{exception.backtrace.join("\n")}")
|
21
|
+
end
|
22
|
+
# :nocov:
|
23
|
+
end
|
24
|
+
|
25
|
+
######## Retryable Configuration ########
|
26
|
+
# All defaults available here:
|
27
|
+
# https://github.com/nfedyashev/retryable/blob/6a04027e61607de559e15e48f281f3ccaa9750e8/lib/retryable/configuration.rb#L22-L33
|
28
|
+
Retryable.configure do |config|
|
29
|
+
config.contexts[:default] = {
|
30
|
+
on: [StandardError],
|
31
|
+
sleep: ENV.fetch("DEFAULT_RETRY_SLEEP", "1").to_i,
|
32
|
+
tries: ENV.fetch("DEFAULT_RETRY_TRIES", "10").to_i,
|
33
|
+
log_method:
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/hooks/version.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# Main Hooks module containing version information
|
3
4
|
module Hooks
|
4
|
-
|
5
|
+
# Current version of the Hooks webhook framework
|
6
|
+
# @return [String] The version string following semantic versioning
|
7
|
+
VERSION = "0.0.4".freeze
|
5
8
|
end
|
data/lib/hooks.rb
CHANGED
@@ -2,16 +2,29 @@
|
|
2
2
|
|
3
3
|
require_relative "hooks/version"
|
4
4
|
require_relative "hooks/core/builder"
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
require_relative "hooks/core/config_loader"
|
6
|
+
require_relative "hooks/core/config_validator"
|
7
|
+
require_relative "hooks/core/logger_factory"
|
8
|
+
require_relative "hooks/core/plugin_loader"
|
9
|
+
require_relative "hooks/core/global_components"
|
10
|
+
require_relative "hooks/core/component_access"
|
11
|
+
require_relative "hooks/core/log"
|
12
|
+
require_relative "hooks/core/failbot"
|
13
|
+
require_relative "hooks/core/stats"
|
14
|
+
require_relative "hooks/plugins/auth/base"
|
15
|
+
require_relative "hooks/plugins/auth/hmac"
|
16
|
+
require_relative "hooks/plugins/auth/shared_secret"
|
17
|
+
require_relative "hooks/plugins/handlers/base"
|
18
|
+
require_relative "hooks/plugins/handlers/default"
|
19
|
+
require_relative "hooks/plugins/lifecycle"
|
20
|
+
require_relative "hooks/plugins/instruments/stats_base"
|
21
|
+
require_relative "hooks/plugins/instruments/failbot_base"
|
22
|
+
require_relative "hooks/plugins/instruments/stats"
|
23
|
+
require_relative "hooks/plugins/instruments/failbot"
|
24
|
+
require_relative "hooks/utils/normalize"
|
25
|
+
require_relative "hooks/utils/retry"
|
26
|
+
require_relative "hooks/security"
|
27
|
+
require_relative "hooks/version"
|
15
28
|
|
16
29
|
# Main module for the Hooks webhook server framework
|
17
30
|
module Hooks
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hooks-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- github
|
@@ -78,26 +78,6 @@ dependencies:
|
|
78
78
|
- - "~>"
|
79
79
|
- !ruby/object:Gem::Version
|
80
80
|
version: '2.3'
|
81
|
-
- !ruby/object:Gem::Dependency
|
82
|
-
name: grape-swagger
|
83
|
-
requirement: !ruby/object:Gem::Requirement
|
84
|
-
requirements:
|
85
|
-
- - "~>"
|
86
|
-
- !ruby/object:Gem::Version
|
87
|
-
version: '2.1'
|
88
|
-
- - ">="
|
89
|
-
- !ruby/object:Gem::Version
|
90
|
-
version: 2.1.2
|
91
|
-
type: :runtime
|
92
|
-
prerelease: false
|
93
|
-
version_requirements: !ruby/object:Gem::Requirement
|
94
|
-
requirements:
|
95
|
-
- - "~>"
|
96
|
-
- !ruby/object:Gem::Version
|
97
|
-
version: '2.1'
|
98
|
-
- - ">="
|
99
|
-
- !ruby/object:Gem::Version
|
100
|
-
version: 2.1.2
|
101
81
|
- !ruby/object:Gem::Dependency
|
102
82
|
name: puma
|
103
83
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,19 +130,29 @@ files:
|
|
150
130
|
- lib/hooks/app/endpoints/version.rb
|
151
131
|
- lib/hooks/app/helpers.rb
|
152
132
|
- lib/hooks/core/builder.rb
|
133
|
+
- lib/hooks/core/component_access.rb
|
153
134
|
- lib/hooks/core/config_loader.rb
|
154
135
|
- lib/hooks/core/config_validator.rb
|
136
|
+
- lib/hooks/core/failbot.rb
|
137
|
+
- lib/hooks/core/global_components.rb
|
155
138
|
- lib/hooks/core/log.rb
|
156
139
|
- lib/hooks/core/logger_factory.rb
|
157
140
|
- lib/hooks/core/plugin_loader.rb
|
141
|
+
- lib/hooks/core/stats.rb
|
158
142
|
- lib/hooks/plugins/auth/base.rb
|
159
143
|
- lib/hooks/plugins/auth/hmac.rb
|
160
144
|
- lib/hooks/plugins/auth/shared_secret.rb
|
145
|
+
- lib/hooks/plugins/auth/timestamp_validator.rb
|
161
146
|
- lib/hooks/plugins/handlers/base.rb
|
162
147
|
- lib/hooks/plugins/handlers/default.rb
|
148
|
+
- lib/hooks/plugins/instruments/failbot.rb
|
149
|
+
- lib/hooks/plugins/instruments/failbot_base.rb
|
150
|
+
- lib/hooks/plugins/instruments/stats.rb
|
151
|
+
- lib/hooks/plugins/instruments/stats_base.rb
|
163
152
|
- lib/hooks/plugins/lifecycle.rb
|
164
153
|
- lib/hooks/security.rb
|
165
154
|
- lib/hooks/utils/normalize.rb
|
155
|
+
- lib/hooks/utils/retry.rb
|
166
156
|
- lib/hooks/version.rb
|
167
157
|
homepage: https://github.com/github/hooks
|
168
158
|
licenses:
|