tpt-rails 1.5.3 → 1.6.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 186ac4371bdca9ada2a7d9b56af210cb801eb8be69f7f52ef61f88bc9c3456e2
4
- data.tar.gz: ce8c4ad020f1fc1fc148f0b94ea1f0267f5578137f56207a863bcc1543bb5b03
3
+ metadata.gz: 300608f90255d2884190ff472e70781927269af457bf549d57443edc396186ca
4
+ data.tar.gz: 5dd5a558b911dd7dae7486f0984ddeac4fa8c05d5d3407e5ef269d64fe7a3e4c
5
5
  SHA512:
6
- metadata.gz: 9e038cdd076adedf6e8fa84c18fae51e1640d8be70b00e507e03ce56503138c18c3ebaf583935fd418a23b1ce3c1dcd50d7978a54dd28fff78ba46af26b4d0d3
7
- data.tar.gz: 17e293fcb6566795d5d220e68dcff771b5b69c4bcc6985514aeb523f3175e195350abb538121589435b50037f749efbe4149e3a95e800a0ec54c6c38d80a97a0
6
+ metadata.gz: 849e1766a9d4a6668516bb1d52d313e375bd823d2723668fb9917c218e79444e71400803cfecdea4f4ec7f8521832fea4c962d36af814b93fe1d4735e228d3c0
7
+ data.tar.gz: 7865a91a0e8b6553647ee022d84617aad7ef590c5e9fce5f64ea30eeeb25f7dd60ea0f71ea28702cad0f0eed20f02cd588467c657edb4610bd679437e2a63784
data/README.md CHANGED
@@ -22,11 +22,13 @@ can configure this gem and enable optional features in that file.
22
22
  See the documentation in [lib/tpt/rails.rb](lib/tpt/rails.rb).
23
23
 
24
24
  For Kubernetes configuration:
25
+
25
26
  ```sh
26
27
  rails generate tpt:rails:kubernetes
27
28
  ```
28
29
 
29
30
  For CI/CD configuration:
31
+
30
32
  ```sh
31
33
  rails generate tpt:rails:continuous_integration
32
34
  ```
@@ -45,6 +47,8 @@ This gem provides the following features:
45
47
  - Adds an error test at `/internal/error-test` that reports an error and emits a metric
46
48
  - Kubernetes/Helm configuration
47
49
  - CI/CD configuration
50
+ - EventBridge integration
51
+ - Adds the aws and aws eventbridge gems, and the EventBridgePublisher class
48
52
 
49
53
  See below for more details.
50
54
 
@@ -60,6 +64,29 @@ Tpt::Rails.statsd.increment('foo', tags: ['a:b', 'c:d'])
60
64
 
61
65
  See the documentation in [lib/tpt/rails.rb](lib/tpt/rails.rb).
62
66
 
67
+ ### EventBridge
68
+
69
+ `Tpt::Rails::EventBridgePublisher` is the class used to emit events to EventBridge (in batches). To use:
70
+
71
+ 1. In an initializer, pass in the `Datadog::Statsd` object you're using to the class method ::set_metrics.
72
+ (Failure to do so will result in an error.) When testing, you can pass in a local Statsd, eg:
73
+
74
+ ```ruby
75
+ Tpt::Rails::EventBridgePublisher.set_metrics(
76
+ Datadog::Statsd.new('localhost', 8125)
77
+ )
78
+ ```
79
+
80
+ 2. By default (#new with no arguments) EventBridgePublisher will try to use the following environment
81
+ variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `TPT_EVENT_SOURCE`, `TPT_SHARED_EVENT_BUS_NAME`.
82
+
83
+ Additional configuration can be set as part of the constructor;
84
+ [see event_bridge_publisher.rb implementation](https://github.com/TeachersPayTeachers/tpt-rails/blob/ad768d3bac0c8bd28e60a48a89aed36f01c4e17b/app/models/tpt/rails/event_bridge_publisher.rb) for more details.
85
+
86
+ 3. Create events by instantiating or inheriting from `Tpt::Rails::ApplicationEvent`. Event types are derived from the text before
87
+ "Event", so ResourceEvent has the event_type "Resource". Pass in an event or an array of events to EventBridgePublisher#publish
88
+ and they will be published immediately. Large arrays will be batched (default 10 per batch).
89
+
63
90
  ### Error reporting (e.g. Bugsnag/Rollbar)
64
91
 
65
92
  If you configure `rollbar_access_token` & `rollbar_enabled`, or `bugsnag_api_key` then your project
@@ -81,10 +108,12 @@ Tpt::Rails provides the following endpoint:
81
108
  /internal/health-check
82
109
 
83
110
  Two checks are added automatically:
111
+
84
112
  - An ActiveRecord database check is performed if ActiveRecord is used in the app.
85
113
  - A Redis check is performed if Redis is used in the app.
86
114
 
87
115
  You may add more health checks as follows:
116
+
88
117
  ```ruby
89
118
  config.health_check(:foo) { 1 == 1 }
90
119
  ```
@@ -132,8 +161,8 @@ gem 'tpt-rails', path: '/your/local/path/to/tpt/rails'
132
161
  First ensure you have permissions to publish to rubygems.org.
133
162
 
134
163
  Once you've merged, check out the `master` branch and execute:
164
+
135
165
  ```sh
136
166
  bundle exec gem bump --push --tag --version patch # patch/minor/major/X.X.X
137
167
  bundle exec gem release
138
168
  ```
139
-
@@ -0,0 +1,30 @@
1
+ module Tpt::Rails
2
+ class ApplicationEvent
3
+ include ActiveModel::Serializers::JSON
4
+
5
+ def initialize(data, metadata: {})
6
+ @data = data || {}
7
+ @metadata = metadata
8
+ end
9
+
10
+ def event_type
11
+ self.class.name.demodulize.gsub(/Event$/, '')
12
+ end
13
+
14
+ def ==(other)
15
+ data == other.data
16
+ end
17
+
18
+ def attributes
19
+ data.merge(metadata: metadata)
20
+ end
21
+
22
+ protected
23
+ attr_reader :data, :metadata
24
+
25
+ private
26
+ def read_attribute_for_serialization(attr)
27
+ attributes[attr]
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,133 @@
1
+ module Tpt::Rails
2
+ class EventBridgePublisher
3
+ def initialize(
4
+ event_bus_name: default_event_bus_name,
5
+ event_source: default_event_source,
6
+ batch_size: 10,
7
+ client_options: {}
8
+ )
9
+ @event_bus_name = event_bus_name
10
+ @event_source = event_source
11
+ @batch_size = batch_size
12
+ @client = make_client(client_options)
13
+ end
14
+
15
+ def publish(events)
16
+ events = [events] unless events.is_a?(Array)
17
+
18
+ logger.debug "Publishing #{events.length} events to EventBridge in #{events.length/@batch_size} batch(es)"
19
+
20
+ metrics.time('tpt.event_bridge.publish') do
21
+ events.each_slice(@batch_size) do |batch|
22
+ publish_batch(batch)
23
+ end
24
+ end
25
+
26
+ events
27
+ end
28
+
29
+ protected
30
+ def publish_batch(events)
31
+ metrics.time('tpt.event_bridge.publish_batch') do
32
+ wrapped_events = wrap_events(events)
33
+ logger.debug({ msg: "Publishing batch of #{events.length} events to EventBridge", events: wrapped_events })
34
+ res = client.put_events(entries: wrapped_events)
35
+ handle_response(res, events)
36
+ end
37
+ rescue StandardError => e
38
+ logger.error({
39
+ msg: 'Failed to publish events',
40
+ events: events,
41
+ error: e
42
+ })
43
+ metrics.increment('tpt.event_bridge.publish_failure', by: events.length, tags: ["error_code:publish_batch_failure"])
44
+
45
+ # re-reraise the error for users to handle
46
+ raise e
47
+ end
48
+
49
+ def wrap_events(events)
50
+ events.map do |event|
51
+ {
52
+ source: event_source,
53
+ resources: [],
54
+ event_bus_name: event_bus_name,
55
+ detail: deep_camelize(event.as_json).to_json,
56
+ detail_type: event.event_type,
57
+ }
58
+ end
59
+ end
60
+
61
+ def handle_response(res, events)
62
+ res.data.entries.each_with_index do |entry, index|
63
+ if entry.error_code.present?
64
+ metrics.increment('tpt.event_bridge.publish_failure', tags: ["error_code:#{entry.error_code}"])
65
+
66
+ logger.error({
67
+ msg: "Error publishing event to EventBridge",
68
+ error_code: entry.error_code,
69
+ error_message: entry.error_message,
70
+ event: events[index]
71
+ })
72
+ elsif entry.event_id.present?
73
+ metrics.increment('tpt.event_bridge.publish_success')
74
+ end
75
+ end
76
+
77
+ res.data.entries
78
+ end
79
+
80
+ private
81
+
82
+ attr_reader :client, :event_bus_name, :event_source
83
+
84
+ def make_client(options)
85
+ all_options = {
86
+ access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID'),
87
+ secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY'),
88
+ region: ENV.fetch('AWS_REGION', 'us-east-1'),
89
+ http_open_timeout: 1, # default is 15
90
+ http_read_timeout: 1, # default is 60
91
+ }.merge(options)
92
+
93
+ Aws::EventBridge::Client.new(all_options)
94
+ end
95
+
96
+ def deep_camelize(hash)
97
+ hash.deep_transform_keys do |k|
98
+ # ignore keys like "__internal" (for now)
99
+ if k.to_s.starts_with?("_")
100
+ k
101
+ else
102
+ k.to_s.camelize(:lower)
103
+ end
104
+ end
105
+ end
106
+
107
+ def default_event_bus_name
108
+ ENV.fetch('TPT_SHARED_EVENT_BUS_NAME', 'shared-event-bus-dev')
109
+ end
110
+
111
+ def default_event_source
112
+ ENV.fetch('TPT_EVENT_SOURCE')
113
+ end
114
+
115
+ def logger
116
+ Rails.logger
117
+ end
118
+
119
+ def self.set_metrics(statsd)
120
+ unless statsd.is_a?(Datadog::Statsd)
121
+ raise "argument passed to EventBridgePublisher::set_metrics is not a Datadog::Statsd class instance"
122
+ end
123
+
124
+ @@statsd = statsd
125
+ end
126
+
127
+ def metrics
128
+ raise "metrics object not set; use EventBridgePublisher#set_metrics" unless @@statsd
129
+
130
+ @@statsd
131
+ end
132
+ end
133
+ end
@@ -1,6 +1,6 @@
1
1
  module Tpt
2
2
  module Rails
3
3
  # Do not change this manually. Our tooling will do it automatically.
4
- VERSION = '1.5.3'
4
+ VERSION = '1.6.0'
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tpt-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.3
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TpT
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-25 00:00:00.000000000 Z
11
+ date: 2021-12-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -58,6 +58,34 @@ dependencies:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
60
  version: '0.40'
61
+ - !ruby/object:Gem::Dependency
62
+ name: aws-sdk-core
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 3.114.0
68
+ type: :runtime
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 3.114.0
75
+ - !ruby/object:Gem::Dependency
76
+ name: aws-sdk-eventbridge
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 1.3.0
82
+ type: :runtime
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 1.3.0
61
89
  - !ruby/object:Gem::Dependency
62
90
  name: sqlite3
63
91
  requirement: !ruby/object:Gem::Requirement
@@ -88,7 +116,9 @@ files:
88
116
  - app/helpers/tpt/rails/application_helper.rb
89
117
  - app/jobs/tpt/rails/application_job.rb
90
118
  - app/mailers/tpt/rails/application_mailer.rb
119
+ - app/models/tpt/rails/application_event.rb
91
120
  - app/models/tpt/rails/application_record.rb
121
+ - app/models/tpt/rails/event_bridge_publisher.rb
92
122
  - app/views/layouts/tpt/rails/application.html.erb
93
123
  - config/routes.rb
94
124
  - lib/generators/tpt/rails/configuration/configuration_generator.rb