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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bdc8c4374f7b3bf47715b791d8d6f04295818b075210ef382c5f57b138f90c49
4
- data.tar.gz: e809b8da64edc2b4ebebfd87f2e301475d6b6117fccb3acc0aed997b459d9218
3
+ metadata.gz: 300608f90255d2884190ff472e70781927269af457bf549d57443edc396186ca
4
+ data.tar.gz: 5dd5a558b911dd7dae7486f0984ddeac4fa8c05d5d3407e5ef269d64fe7a3e4c
5
5
  SHA512:
6
- metadata.gz: c7abc446ff40916c9872b910ff44a06ac502ebe459bc65b7eea13a0ff38da1fa877e63ac3f3b1c413ed054d7b9c22290c05b941592d5e2e3908378d27b2da99e
7
- data.tar.gz: 812eb1ab44fedbb0e5e42e8d5ac16eeb6e46b74421d7d962c89266dc572b2540b26feaf914547403d477ac3cc220b38ee1e35971d4ba9fb00e61c48c83322cd3
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
@@ -5,6 +5,6 @@ class Tpt::Rails::ConfigurationGenerator < ::Rails::Generators::Base
5
5
 
6
6
  def configure
7
7
  route "mount Tpt::Rails::Engine => '/'"
8
- template 'config/initializers/tpt_rails.rb'
8
+ directory 'config'
9
9
  end
10
10
  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
@@ -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: "true"
42
+ sidecar.istio.io/inject: "false"
43
43
  spec:
44
44
  # Specifies where our Kube secrets are coming from
45
45
  imagePullSecrets:
@@ -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"
@@ -1,6 +1,8 @@
1
1
  name: <%= Tpt::Rails.app_name %>
2
2
  deploy_env: production
3
3
 
4
+ hostname: <%= Tpt::Rails.app_name %>-production.teacherspayteachers.com
5
+
4
6
  annotations:
5
7
  costEnv: production
6
8
 
@@ -1,6 +1,8 @@
1
1
  name: <%= Tpt::Rails.app_name %>
2
2
  deploy_env: staging
3
3
 
4
+ hostname: <%= Tpt::Rails.app_name %>-staging.teacherspayteachers.com
5
+
4
6
  annotations:
5
7
  costEnv: staging
6
8
 
@@ -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.0'
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.0
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-18 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,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