tpt-rails 1.5.1 → 1.6.1
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 +36 -1
- data/app/models/tpt/rails/application_event.rb +30 -0
- data/app/models/tpt/rails/event_bridge_publisher.rb +133 -0
- data/lib/generators/tpt/rails/configuration/configuration_generator.rb +1 -1
- data/lib/generators/tpt/rails/configuration/templates/config/puma.rb +52 -0
- data/lib/generators/tpt/rails/kubernetes/templates/kubernetes/helm/templates/service.yaml.tt +1 -0
- data/lib/generators/tpt/rails/kubernetes/templates/kubernetes/helm/values.prod.yaml.tt +2 -0
- data/lib/generators/tpt/rails/kubernetes/templates/kubernetes/helm/values.staging.yaml.tt +2 -0
- data/lib/tpt/rails/version.rb +1 -1
- metadata +33 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0395183e764d77487d191133045533748b1120aa919bd2904d19e923c58b5c1d'
|
4
|
+
data.tar.gz: f8591848d97fb4324aed4318183198db27ccfcff116ad43f806cd2322aadcb57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3093e622d8895edf09b3d2ee025c1356e89bdd6d7ea95dd623e5f870e8d2adab915bc4f279c548f24d0636ee902825c163b1ea61f7ce21ef5a66c970dc0c55f8
|
7
|
+
data.tar.gz: 11459ad795f7603c051f7c509ff475bdfd21bd52ce37b26ad4792da3a833fdea0a4294f614965cd28a0716f51ed79039b5fe39ebde21458e2901595e6f0ca1e7
|
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,35 @@ 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. Add EventBridge to your gemfile:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
gem "aws-sdk-eventbridge", "~> 1.3.0"
|
75
|
+
```
|
76
|
+
|
77
|
+
2. In an initializer, pass in the `Datadog::Statsd` object you're using to the class method ::set_metrics.
|
78
|
+
(Failure to do so will result in an error.) When testing, you can pass in a local Statsd, eg:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
Tpt::Rails::EventBridgePublisher.set_metrics(
|
82
|
+
Datadog::Statsd.new('localhost', 8125)
|
83
|
+
)
|
84
|
+
```
|
85
|
+
|
86
|
+
3. By default (#new with no arguments) EventBridgePublisher will try to use the following environment
|
87
|
+
variables: `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `TPT_EVENT_SOURCE`, `TPT_SHARED_EVENT_BUS_NAME`.
|
88
|
+
|
89
|
+
Additional configuration can be set as part of the constructor;
|
90
|
+
[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.
|
91
|
+
|
92
|
+
4. Create events by instantiating or inheriting from `Tpt::Rails::ApplicationEvent`. Event types are derived from the text before
|
93
|
+
"Event", so ResourceEvent has the event_type "Resource". Pass in an event or an array of events to EventBridgePublisher#publish
|
94
|
+
and they will be published immediately. Large arrays will be batched (default 10 per batch).
|
95
|
+
|
63
96
|
### Error reporting (e.g. Bugsnag/Rollbar)
|
64
97
|
|
65
98
|
If you configure `rollbar_access_token` & `rollbar_enabled`, or `bugsnag_api_key` then your project
|
@@ -81,10 +114,12 @@ Tpt::Rails provides the following endpoint:
|
|
81
114
|
/internal/health-check
|
82
115
|
|
83
116
|
Two checks are added automatically:
|
117
|
+
|
84
118
|
- An ActiveRecord database check is performed if ActiveRecord is used in the app.
|
85
119
|
- A Redis check is performed if Redis is used in the app.
|
86
120
|
|
87
121
|
You may add more health checks as follows:
|
122
|
+
|
88
123
|
```ruby
|
89
124
|
config.health_check(:foo) { 1 == 1 }
|
90
125
|
```
|
@@ -132,8 +167,8 @@ gem 'tpt-rails', path: '/your/local/path/to/tpt/rails'
|
|
132
167
|
First ensure you have permissions to publish to rubygems.org.
|
133
168
|
|
134
169
|
Once you've merged, check out the `master` branch and execute:
|
170
|
+
|
135
171
|
```sh
|
136
172
|
bundle exec gem bump --push --tag --version patch # patch/minor/major/X.X.X
|
137
173
|
bundle exec gem release
|
138
174
|
```
|
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
|
+
attr_accessor :batch_size
|
4
|
+
|
5
|
+
def initialize(
|
6
|
+
event_bus_name: default_event_bus_name,
|
7
|
+
event_source: default_event_source,
|
8
|
+
batch_size: 10,
|
9
|
+
client_options: {}
|
10
|
+
)
|
11
|
+
@event_bus_name = event_bus_name
|
12
|
+
@event_source = event_source
|
13
|
+
@batch_size = batch_size
|
14
|
+
@client = make_client(client_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def publish(events)
|
18
|
+
events = [events] unless events.is_a?(Array)
|
19
|
+
|
20
|
+
logger.debug "Publishing #{events.length} events to EventBridge in #{events.length/@batch_size} batch(es)"
|
21
|
+
|
22
|
+
metrics.time('tpt.event_bridge.publish') do
|
23
|
+
events.each_slice(@batch_size) do |batch|
|
24
|
+
publish_batch(batch)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
events
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
attr_reader :client, :event_bus_name, :event_source
|
33
|
+
|
34
|
+
def publish_batch(events)
|
35
|
+
metrics.time('tpt.event_bridge.publish_batch') do
|
36
|
+
wrapped_events = wrap_events(events)
|
37
|
+
logger.debug({ msg: "Publishing batch of #{events.length} events to EventBridge", events: wrapped_events })
|
38
|
+
res = client.put_events(entries: wrapped_events)
|
39
|
+
handle_response(res, events)
|
40
|
+
end
|
41
|
+
rescue StandardError => e
|
42
|
+
logger.error({
|
43
|
+
msg: 'Failed to publish events',
|
44
|
+
events: events,
|
45
|
+
error: e
|
46
|
+
})
|
47
|
+
metrics.increment('tpt.event_bridge.publish_failure', by: events.length, tags: ["error_code:publish_batch_failure"])
|
48
|
+
|
49
|
+
# re-reraise the error for users to handle
|
50
|
+
raise e
|
51
|
+
end
|
52
|
+
|
53
|
+
def wrap_events(events)
|
54
|
+
events.map do |event|
|
55
|
+
{
|
56
|
+
source: event_source,
|
57
|
+
resources: [],
|
58
|
+
event_bus_name: event_bus_name,
|
59
|
+
detail: deep_camelize(event.as_json).to_json,
|
60
|
+
detail_type: event.event_type,
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def handle_response(res, events)
|
66
|
+
res.data.entries.each_with_index do |entry, index|
|
67
|
+
if entry.error_code.present?
|
68
|
+
metrics.increment('tpt.event_bridge.publish_failure', tags: ["error_code:#{entry.error_code}"])
|
69
|
+
|
70
|
+
logger.error({
|
71
|
+
msg: "Error publishing event to EventBridge",
|
72
|
+
error_code: entry.error_code,
|
73
|
+
error_message: entry.error_message,
|
74
|
+
event: events[index]
|
75
|
+
})
|
76
|
+
elsif entry.event_id.present?
|
77
|
+
metrics.increment('tpt.event_bridge.publish_success')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
res.data.entries
|
82
|
+
end
|
83
|
+
|
84
|
+
def make_client(options)
|
85
|
+
all_options = {
|
86
|
+
access_key_id: ENV.fetch('AWS_ACCESS_KEY_ID', nil),
|
87
|
+
secret_access_key: ENV.fetch('AWS_SECRET_ACCESS_KEY', nil),
|
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
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Puma can serve each request in a thread from an internal thread pool.
|
2
|
+
# The `threads` method setting takes two numbers: a minimum and maximum.
|
3
|
+
# Any libraries that use thread pools should be configured to match
|
4
|
+
# the maximum value specified for Puma. Default is set to 5 threads for minimum
|
5
|
+
# and maximum; this matches the default thread size of Active Record.
|
6
|
+
#
|
7
|
+
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
|
8
|
+
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
|
9
|
+
threads min_threads_count, max_threads_count
|
10
|
+
|
11
|
+
# Specifies the `worker_timeout` threshold that Puma will use to wait before
|
12
|
+
# terminating a worker in development environments.
|
13
|
+
#
|
14
|
+
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
|
15
|
+
|
16
|
+
# Specifies the `port` that Puma will listen on to receive requests; default is 3000.
|
17
|
+
#
|
18
|
+
port ENV.fetch("PORT") { 3000 }
|
19
|
+
|
20
|
+
# Specifies the `environment` that Puma will run in.
|
21
|
+
#
|
22
|
+
environment ENV.fetch("RAILS_ENV") { "development" }
|
23
|
+
|
24
|
+
# Specifies the `pidfile` that Puma will use.
|
25
|
+
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
|
26
|
+
|
27
|
+
before_fork do
|
28
|
+
Tpt::Rails::PumaStatsCollector.run(metrics_client: Tpt::Rails.statsd)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Specifies the number of `workers` to boot in clustered mode.
|
32
|
+
# Workers are forked web server processes. If using threads and workers together
|
33
|
+
# the concurrency of the application would be max `threads` * `workers`.
|
34
|
+
# Workers do not work on JRuby or Windows (both of which do not support
|
35
|
+
# processes).
|
36
|
+
|
37
|
+
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
|
38
|
+
|
39
|
+
on_worker_boot do
|
40
|
+
# See: https://devcenter.heroku.com/articles/deploying-rails-applications-with-the-puma-web-server#on-worker-boot
|
41
|
+
ActiveRecord::Base.establish_connection
|
42
|
+
end
|
43
|
+
|
44
|
+
# Use the `preload_app!` method when specifying a `workers` number.
|
45
|
+
# This directive tells Puma to first boot the application and load code
|
46
|
+
# before forking the application. This takes advantage of Copy On Write
|
47
|
+
# process behavior so workers use less memory.
|
48
|
+
|
49
|
+
preload_app!
|
50
|
+
|
51
|
+
# Allow puma to be restarted by `rails restart` command.
|
52
|
+
plugin :tmp_restart
|
data/lib/generators/tpt/rails/kubernetes/templates/kubernetes/helm/templates/service.yaml.tt
CHANGED
@@ -36,6 +36,7 @@ metadata:
|
|
36
36
|
- request_label:
|
37
37
|
- frontend
|
38
38
|
{{- if eq .Values.service.type "LoadBalancer" }}
|
39
|
+
external-dns.alpha.kubernetes.io/hostname: {{ .Values.hostname }}
|
39
40
|
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
|
40
41
|
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
|
41
42
|
service.beta.kubernetes.io/aws-load-balancer-internal: "0.0.0.0/0"
|
data/lib/tpt/rails/version.rb
CHANGED
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.
|
4
|
+
version: 1.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- TpT
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
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,11 +116,14 @@ 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
|
95
125
|
- lib/generators/tpt/rails/configuration/templates/config/initializers/tpt_rails.rb
|
126
|
+
- lib/generators/tpt/rails/configuration/templates/config/puma.rb
|
96
127
|
- lib/generators/tpt/rails/continuous_integration/continuous_integration_generator.rb
|
97
128
|
- lib/generators/tpt/rails/continuous_integration/templates/ci/Dockerfile.tt
|
98
129
|
- lib/generators/tpt/rails/continuous_integration/templates/ci/docker-compose.yaml.tt
|