tpt-rails 1.5.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -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/deployment.yaml.tt +1 -1
- 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: 300608f90255d2884190ff472e70781927269af457bf549d57443edc396186ca
|
4
|
+
data.tar.gz: 5dd5a558b911dd7dae7486f0984ddeac4fa8c05d5d3407e5ef269d64fe7a3e4c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
@@ -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/deployment.yaml.tt
CHANGED
@@ -39,7 +39,7 @@ spec:
|
|
39
39
|
tpt/creator: {{ .Values.annotations.creator | default "UNKNOWN" | quote }}
|
40
40
|
annotations:
|
41
41
|
buildID: "{{ .Values.buildID }}"
|
42
|
-
sidecar.istio.io/inject: "
|
42
|
+
sidecar.istio.io/inject: "false"
|
43
43
|
spec:
|
44
44
|
# Specifies where our Kube secrets are coming from
|
45
45
|
imagePullSecrets:
|
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.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-
|
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
|