journaled 3.1.0 → 4.0.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: d758f3a24c9170a8b7585c330c59a80483ac763640ad847e229f16123d6f9f1b
4
- data.tar.gz: adb70f763fd7daffcdc89a02898846a55132461f6329a67d787ce7fd10f73257
3
+ metadata.gz: 4cefb08a9d7ab294f9084f150f89cda921982fffdb0ec7b0a5bc702ede1d23b8
4
+ data.tar.gz: 21a484a10c58a3f551e95af064b5f718b9496ba7840ac6c1ac84142b859942db
5
5
  SHA512:
6
- metadata.gz: 346e26235e8de20fc34e0a888b2d3bf98189d73aaa9968db4af9addcf04b8c8577bb0f8e31d5443d85549f0da6c6defae8f554d328fc328732ae029a90704e2f
7
- data.tar.gz: 1be21eed56a1ac95e27e13712831bd45fe0c4e8cf3b896b19e7ad9037b924a9d6543828ddb6d2112cbd25aa77aa464e7e99fd4fd8a4288a89c7d11ad2836915b
6
+ metadata.gz: 59d07f836a83dde4e4a24a8a5f326ff5cbc7a751d92fb6df66ee22b5b31ac1aa63326675167d45bcd8c572a0e4afeb831774a468eb45097a71c6940a79ce2681
7
+ data.tar.gz: 807c603b1085819ed003ed8053f81b6fec59ba895c920bc24ac178b0d3188fac554a2ed9b2dcb80ceceb2a26fc5effe6af5d52819f66d041ad7959cfc30957da
data/README.md CHANGED
@@ -24,16 +24,16 @@ durable, eventually consistent record that discrete events happened.
24
24
  [configure ActiveJob](https://guides.rubyonrails.org/active_job_basics.html)
25
25
  to use one of the following queue adapters:
26
26
 
27
- - `:delayed_job` (via `delayed_job_active_record`)
28
- - `:que`
29
- - `:good_job`
30
- - `:delayed`
27
+ - `:delayed_job` (via `delayed_job_active_record`)
28
+ - `:que`
29
+ - `:good_job`
30
+ - `:delayed`
31
31
 
32
- Ensure that your queue adapter is not configured to delete jobs on failure.
32
+ Ensure that your queue adapter is not configured to delete jobs on failure.
33
33
 
34
- **If you launch your application in production mode and the gem detects that
35
- `ActiveJob::Base.queue_adapter` is not in the above list, it will raise an exception
36
- and prevent your application from performing unsafe journaling.**
34
+ **If you launch your application in production mode and the gem detects that
35
+ `ActiveJob::Base.queue_adapter` is not in the above list, it will raise an exception
36
+ and prevent your application from performing unsafe journaling.**
37
37
 
38
38
  2. To integrate Journaled into your application, simply include the gem in your
39
39
  app's Gemfile.
@@ -51,20 +51,21 @@ app's Gemfile.
51
51
  require 'journaled/rspec'
52
52
  ```
53
53
 
54
- 3. You will also need to define the following environment variables to allow Journaled to publish events to your AWS Kinesis event stream:
54
+ 3. You will need to set the following config in an initializer to allow Journaled to publish events to your AWS Kinesis event stream:
55
55
 
56
- * `JOURNALED_STREAM_NAME`
56
+ ```ruby
57
+ Journaled.default_stream_name = "my_app_#{Rails.env}_events"
58
+ ```
57
59
 
58
- Special case: if your `Journaled::Event` objects override the
59
- `#journaled_app_name` method to a non-nil value e.g. `my_app`, you will
60
- instead need to provide a corresponding
61
- `[upcased_app_name]_JOURNALED_STREAM_NAME` variable for each distinct
62
- value, e.g. `MY_APP_JOURNALED_STREAM_NAME`. You can provide a default value
63
- for all `Journaled::Event`s in an initializer like this:
60
+ You may also define a `#journaled_stream_name` method on `Journaled::Event` instances:
64
61
 
65
62
  ```ruby
66
- Journaled.default_app_name = 'my_app'
67
- ```
63
+ def journaled_stream_name
64
+ "my_app_#{Rails.env}_alternate_events"
65
+ end
66
+ ````
67
+
68
+ 3. You may also need to define environment variables to allow Journaled to publish events to your AWS Kinesis event stream:
68
69
 
69
70
  You may optionally define the following ENV vars to specify AWS
70
71
  credentials outside of the locations that the AWS SDK normally looks:
@@ -82,6 +83,41 @@ app's Gemfile.
82
83
 
83
84
  The AWS principal whose credentials are in the environment will need to be allowed to assume this role.
84
85
 
86
+ ### Upgrading from 3.1.0
87
+
88
+ Versions of Journaled prior to 4.0 relied directly on environment variables for stream names, but now stream names are configured directly.
89
+ When upgrading, you can use the following configuration to maintain the previous behavior:
90
+
91
+ ```ruby
92
+ Journaled.default_stream_name = ENV['JOURNALED_STREAM_NAME']
93
+ ```
94
+
95
+ If you previously specified a `Journaled.default_app_name`, you would have required a more precise environment variable name (substitute `{{upcase_app_name}}`):
96
+
97
+ ```ruby
98
+ Journaled.default_stream_name = ENV["{{upcase_app_name}}_JOURNALED_STREAM_NAME"]
99
+ ```
100
+
101
+ And if you had defined any `journaled_app_name` methods on `Journaled::Event` instances, you can replace them with the following:
102
+
103
+ ```ruby
104
+ def journaled_stream_name
105
+ ENV['{{upcase_app_name}}_JOURNALED_STREAM_NAME']
106
+ end
107
+ ```
108
+
109
+ When upgrading from 3.1 or below, `Journaled::DeliveryJob` will handle any jobs that remain in the queue by accepting an `app_name` argument. **This behavior will be removed in version 5.0**, so it is recommended to upgrade one major version at a time.
110
+
111
+ ### Upgrading from 2.5.0
112
+
113
+ Versions of Journaled prior to 3.0 relied direclty on `delayed_job` and a "performable" class called `Journaled::Delivery`.
114
+ In 3.0, this was superceded by an ActiveJob class called `Journaled::DeliveryJob`, but the `Journaled::Delivery` class was not removed until 4.0.
115
+
116
+ Therefore, when upgrading from 2.5.0 or below, it is recommended to first upgrade to 3.1.0 (to allow any `Journaled::Delivery` jobs to finish working off) before upgrading to 4.0+.
117
+
118
+ The upgrade to 3.1.0 will require a working ActiveJob config. ActiveJob can be configured globally by setting `ActiveJob::Base.queue_adapter`, or just for Journaled jobs by setting `Journaled::DeliveryJob.queue_adapter`.
119
+ The `:delayed_job` queue adapter will allow you to continue relying on `delayed_job`. You may also consider switching your app(s) to [`delayed`](https://github.com/Betterment/delayed) and using the `:delayed` queue adapter.
120
+
85
121
  ## Usage
86
122
 
87
123
  ### Configuration
@@ -12,15 +12,24 @@ module Journaled
12
12
  raise KinesisTemporaryFailure
13
13
  end
14
14
 
15
- def perform(serialized_event:, partition_key:, app_name:)
15
+ UNSPECIFIED = Object.new
16
+ private_constant :UNSPECIFIED
17
+
18
+ def perform(serialized_event:, partition_key:, stream_name: UNSPECIFIED, app_name: UNSPECIFIED)
16
19
  @serialized_event = serialized_event
17
20
  @partition_key = partition_key
18
- @app_name = app_name
21
+ if app_name != UNSPECIFIED
22
+ @stream_name = self.class.legacy_computed_stream_name(app_name: app_name)
23
+ elsif stream_name != UNSPECIFIED && !stream_name.nil?
24
+ @stream_name = stream_name
25
+ else
26
+ raise(ArgumentError, 'missing keyword: stream_name')
27
+ end
19
28
 
20
29
  journal!
21
30
  end
22
31
 
23
- def self.stream_name(app_name:)
32
+ def self.legacy_computed_stream_name(app_name:)
24
33
  env_var_name = [app_name&.upcase, 'JOURNALED_STREAM_NAME'].compact.join('_')
25
34
  ENV.fetch(env_var_name)
26
35
  end
@@ -37,7 +46,7 @@ module Journaled
37
46
 
38
47
  private
39
48
 
40
- attr_reader :serialized_event, :partition_key, :app_name
49
+ attr_reader :serialized_event, :partition_key, :stream_name
41
50
 
42
51
  def journal!
43
52
  kinesis_client.put_record record if Journaled.enabled?
@@ -45,7 +54,7 @@ module Journaled
45
54
 
46
55
  def record
47
56
  {
48
- stream_name: self.class.stream_name(app_name: app_name),
57
+ stream_name: stream_name,
49
58
  data: serialized_event,
50
59
  partition_key: partition_key,
51
60
  }
@@ -6,7 +6,7 @@ class Journaled::Change
6
6
  :database_operation,
7
7
  :logical_operation,
8
8
  :changes,
9
- :journaled_app_name,
9
+ :journaled_stream_name,
10
10
  :journaled_enqueue_opts,
11
11
  :actor
12
12
 
@@ -22,7 +22,7 @@ class Journaled::Change
22
22
  database_operation:,
23
23
  logical_operation:,
24
24
  changes:,
25
- journaled_app_name:,
25
+ journaled_stream_name:,
26
26
  journaled_enqueue_opts:,
27
27
  actor:)
28
28
  @table_name = table_name
@@ -30,7 +30,7 @@ class Journaled::Change
30
30
  @database_operation = database_operation
31
31
  @logical_operation = logical_operation
32
32
  @changes = changes
33
- @journaled_app_name = journaled_app_name
33
+ @journaled_stream_name = journaled_stream_name
34
34
  @journaled_enqueue_opts = journaled_enqueue_opts
35
35
  @actor = actor
36
36
  end
@@ -27,7 +27,7 @@ class Journaled::ChangeWriter
27
27
  database_operation: database_operation,
28
28
  logical_operation: logical_operation,
29
29
  changes: JSON.dump(changes),
30
- journaled_app_name: journaled_app_name,
30
+ journaled_stream_name: journaled_stream_name,
31
31
  journaled_enqueue_opts: model.journaled_enqueue_opts,
32
32
  actor: actor_uri,
33
33
  )
@@ -57,11 +57,11 @@ class Journaled::ChangeWriter
57
57
  end
58
58
  end
59
59
 
60
- def journaled_app_name
61
- if model.class.respond_to?(:journaled_app_name)
62
- model.class.journaled_app_name
60
+ def journaled_stream_name
61
+ if model.class.respond_to?(:journaled_stream_name)
62
+ model.class.journaled_stream_name
63
63
  else
64
- Journaled.default_app_name
64
+ Journaled.default_stream_name
65
65
  end
66
66
  end
67
67
  end
@@ -35,8 +35,8 @@ module Journaled::Event
35
35
  event_type
36
36
  end
37
37
 
38
- def journaled_app_name
39
- Journaled.default_app_name
38
+ def journaled_stream_name
39
+ Journaled.default_stream_name
40
40
  end
41
41
 
42
42
  private
@@ -3,7 +3,7 @@ class Journaled::Writer
3
3
  journaled_schema_name
4
4
  journaled_partition_key
5
5
  journaled_attributes
6
- journaled_app_name
6
+ journaled_stream_name
7
7
  journaled_enqueue_opts
8
8
  ).freeze
9
9
 
@@ -41,7 +41,7 @@ class Journaled::Writer
41
41
  {
42
42
  serialized_event: serialized_event,
43
43
  partition_key: journaled_partition_key,
44
- app_name: journaled_app_name,
44
+ stream_name: journaled_stream_name,
45
45
  }
46
46
  end
47
47
 
@@ -1,3 +1,3 @@
1
1
  module Journaled
2
- VERSION = "3.1.0".freeze
2
+ VERSION = "4.0.0".freeze
3
3
  end
data/lib/journaled.rb CHANGED
@@ -8,7 +8,7 @@ require "journaled/engine"
8
8
  module Journaled
9
9
  SUPPORTED_QUEUE_ADAPTERS = %w(delayed delayed_job good_job que).freeze
10
10
 
11
- mattr_accessor :default_app_name
11
+ mattr_accessor :default_stream_name
12
12
  mattr_accessor(:job_priority) { 20 }
13
13
  mattr_accessor(:http_idle_timeout) { 5 }
14
14
  mattr_accessor(:http_open_timeout) { 2 }
@@ -5,11 +5,7 @@ RSpec.describe Journaled::DeliveryJob do
5
5
  let(:partition_key) { 'fake_partition_key' }
6
6
  let(:serialized_event) { '{"foo":"bar"}' }
7
7
  let(:kinesis_client) { Aws::Kinesis::Client.new(stub_responses: true) }
8
- let(:args) { { serialized_event: serialized_event, partition_key: partition_key, app_name: nil } }
9
-
10
- around do |example|
11
- with_env(JOURNALED_STREAM_NAME: stream_name) { example.run }
12
- end
8
+ let(:args) { { serialized_event: serialized_event, partition_key: partition_key, stream_name: stream_name } }
13
9
 
14
10
  describe '#perform' do
15
11
  let(:return_status_body) { { shard_id: '101', sequence_number: '101123' } }
@@ -19,6 +15,7 @@ RSpec.describe Journaled::DeliveryJob do
19
15
  allow(Aws::AssumeRoleCredentials).to receive(:new).and_call_original
20
16
  allow(Aws::Kinesis::Client).to receive(:new).and_return kinesis_client
21
17
  kinesis_client.stub_responses(:put_record, return_status_body)
18
+ allow(kinesis_client).to receive(:put_record).and_call_original
22
19
 
23
20
  allow(Journaled).to receive(:enabled?).and_return(true)
24
21
  end
@@ -28,6 +25,11 @@ RSpec.describe Journaled::DeliveryJob do
28
25
 
29
26
  expect(event.shard_id).to eq '101'
30
27
  expect(event.sequence_number).to eq '101123'
28
+ expect(kinesis_client).to have_received(:put_record).with(
29
+ stream_name: 'test_events',
30
+ data: '{"foo":"bar"}',
31
+ partition_key: 'fake_partition_key',
32
+ )
31
33
  end
32
34
 
33
35
  context 'when JOURNALED_IAM_ROLE_ARN is defined' do
@@ -68,11 +70,64 @@ RSpec.describe Journaled::DeliveryJob do
68
70
  end
69
71
  end
70
72
 
71
- context 'when the stream name env var is NOT set' do
73
+ context 'when the stream name is not set' do
72
74
  let(:stream_name) { nil }
73
75
 
74
76
  it 'raises an KeyError error' do
75
- expect { described_class.perform_now(args) }.to raise_error KeyError
77
+ expect { described_class.perform_now(args) }.to raise_error ArgumentError, 'missing keyword: stream_name'
78
+ end
79
+ end
80
+
81
+ unless Gem::Version.new(Journaled::VERSION) < Gem::Version.new('5.0.0')
82
+ raise <<~MSG
83
+ Hey! I see that you're bumping the version to 5.0!
84
+
85
+ This is a reminder to:
86
+ - remove the `app_name` argument (and related logic) from `Journaled::DeliveryJob`,
87
+ - remove the following app_name test contexts, and
88
+ - make `stream_name` a required kwarg
89
+
90
+ Thanks!
91
+ MSG
92
+ end
93
+
94
+ context 'when the legacy app_name argument is present but nil' do
95
+ let(:args) { { serialized_event: serialized_event, partition_key: partition_key, app_name: nil } }
96
+
97
+ around do |example|
98
+ with_env(JOURNALED_STREAM_NAME: 'legacy_stream_name') { example.run }
99
+ end
100
+
101
+ it 'makes requests to AWS to put the event on the Kinesis with the correct body' do
102
+ event = described_class.perform_now(args)
103
+
104
+ expect(event.shard_id).to eq '101'
105
+ expect(event.sequence_number).to eq '101123'
106
+ expect(kinesis_client).to have_received(:put_record).with(
107
+ stream_name: 'legacy_stream_name',
108
+ data: '{"foo":"bar"}',
109
+ partition_key: 'fake_partition_key',
110
+ )
111
+ end
112
+ end
113
+
114
+ context 'when the legacy app_name argument is present and has a value' do
115
+ let(:args) { { serialized_event: serialized_event, partition_key: partition_key, app_name: 'pied_piper' } }
116
+
117
+ around do |example|
118
+ with_env(PIED_PIPER_JOURNALED_STREAM_NAME: 'pied_piper_events') { example.run }
119
+ end
120
+
121
+ it 'makes requests to AWS to put the event on the Kinesis with the correct body' do
122
+ event = described_class.perform_now(args)
123
+
124
+ expect(event.shard_id).to eq '101'
125
+ expect(event.sequence_number).to eq '101123'
126
+ expect(kinesis_client).to have_received(:put_record).with(
127
+ stream_name: 'pied_piper_events',
128
+ data: '{"foo":"bar"}',
129
+ partition_key: 'fake_partition_key',
130
+ )
76
131
  end
77
132
  end
78
133
 
@@ -137,11 +192,11 @@ RSpec.describe Journaled::DeliveryJob do
137
192
  end
138
193
  end
139
194
 
140
- describe ".stream_name" do
195
+ describe ".legacy_computed_stream_name" do
141
196
  context "when app_name is unspecified" do
142
197
  it "is fetched from a prefixed ENV var if specified" do
143
198
  allow(ENV).to receive(:fetch).and_return("expected_stream_name")
144
- expect(described_class.stream_name(app_name: nil)).to eq("expected_stream_name")
199
+ expect(described_class.legacy_computed_stream_name(app_name: nil)).to eq("expected_stream_name")
145
200
  expect(ENV).to have_received(:fetch).with("JOURNALED_STREAM_NAME")
146
201
  end
147
202
  end
@@ -149,7 +204,7 @@ RSpec.describe Journaled::DeliveryJob do
149
204
  context "when app_name is specified" do
150
205
  it "is fetched from a prefixed ENV var if specified" do
151
206
  allow(ENV).to receive(:fetch).and_return("expected_stream_name")
152
- expect(described_class.stream_name(app_name: "my_funky_app_name")).to eq("expected_stream_name")
207
+ expect(described_class.legacy_computed_stream_name(app_name: "my_funky_app_name")).to eq("expected_stream_name")
153
208
  expect(ENV).to have_received(:fetch).with("MY_FUNKY_APP_NAME_JOURNALED_STREAM_NAME")
154
209
  end
155
210
  end
@@ -138,30 +138,30 @@ RSpec.describe Journaled::ChangeWriter do
138
138
  expect(subject.journaled_change_for("update", {}).logical_operation).to eq("identity_change")
139
139
  end
140
140
 
141
- it "doesn't set journaled_app_name if model class doesn't respond to it" do
142
- expect(subject.journaled_change_for("update", {}).journaled_app_name).to eq(nil)
141
+ it "doesn't set journaled_stream_name if model class doesn't respond to it" do
142
+ expect(subject.journaled_change_for("update", {}).journaled_stream_name).to eq(nil)
143
143
  end
144
144
 
145
145
  context "with journaled default app name set" do
146
146
  around do |example|
147
- orig_app_name = Journaled.default_app_name
148
- Journaled.default_app_name = "foo"
147
+ orig_app_name = Journaled.default_stream_name
148
+ Journaled.default_stream_name = "foo"
149
149
  example.run
150
- Journaled.default_app_name = orig_app_name
150
+ Journaled.default_stream_name = orig_app_name
151
151
  end
152
152
 
153
153
  it "passes through default" do
154
- expect(subject.journaled_change_for("update", {}).journaled_app_name).to eq("foo")
154
+ expect(subject.journaled_change_for("update", {}).journaled_stream_name).to eq("foo")
155
155
  end
156
156
  end
157
157
 
158
- context "when model class defines journaled_app_name" do
158
+ context "when model class defines journaled_stream_name" do
159
159
  before do
160
- allow(model_class).to receive(:journaled_app_name).and_return("my_app")
160
+ allow(model_class).to receive(:journaled_stream_name).and_return("my_app_events")
161
161
  end
162
162
 
163
- it "sets journaled_app_name if model_class responds to it" do
164
- expect(subject.journaled_change_for("update", {}).journaled_app_name).to eq("my_app")
163
+ it "sets journaled_stream_name if model_class responds to it" do
164
+ expect(subject.journaled_change_for("update", {}).journaled_stream_name).to eq("my_app_events")
165
165
  end
166
166
  end
167
167
  end
@@ -65,14 +65,14 @@ RSpec.describe Journaled::Event do
65
65
  end
66
66
  end
67
67
 
68
- describe '#journaled_app_name' do
68
+ describe '#journaled_stream_name' do
69
69
  it 'returns nil in the base class so it can be set explicitly in apps spanning multiple app domains' do
70
- expect(sample_journaled_event.journaled_app_name).to be_nil
70
+ expect(sample_journaled_event.journaled_stream_name).to be_nil
71
71
  end
72
72
 
73
73
  it 'returns the journaled default if set' do
74
- allow(Journaled).to receive(:default_app_name).and_return("my_app")
75
- expect(sample_journaled_event.journaled_app_name).to eq("my_app")
74
+ allow(Journaled).to receive(:default_stream_name).and_return("my_app_events")
75
+ expect(sample_journaled_event.journaled_stream_name).to eq("my_app_events")
76
76
  end
77
77
  end
78
78
 
@@ -18,7 +18,7 @@ RSpec.describe Journaled::Writer do
18
18
  journaled_schema_name: nil,
19
19
  journaled_attributes: {},
20
20
  journaled_partition_key: '',
21
- journaled_app_name: nil,
21
+ journaled_stream_name: nil,
22
22
  journaled_enqueue_opts: {},
23
23
  )
24
24
  end
@@ -34,7 +34,7 @@ RSpec.describe Journaled::Writer do
34
34
  journaled_schema_name: :fake_schema_name,
35
35
  journaled_attributes: { foo: :bar },
36
36
  journaled_partition_key: 'fake_partition_key',
37
- journaled_app_name: nil,
37
+ journaled_stream_name: nil,
38
38
  journaled_enqueue_opts: {},
39
39
  )
40
40
  end
@@ -75,7 +75,7 @@ RSpec.describe Journaled::Writer do
75
75
  journaled_schema_name: :fake_schema_name,
76
76
  journaled_attributes: journaled_event_attributes,
77
77
  journaled_partition_key: 'fake_partition_key',
78
- journaled_app_name: 'my_app',
78
+ journaled_stream_name: 'my_app_events',
79
79
  journaled_enqueue_opts: journaled_enqueue_opts,
80
80
  )
81
81
  end
@@ -104,7 +104,7 @@ RSpec.describe Journaled::Writer do
104
104
 
105
105
  it 'creates a delivery with the app name passed through' do
106
106
  expect { subject.journal! }.to change { enqueued_jobs.count }.from(0).to(1)
107
- expect(enqueued_jobs.first[:args].first).to include('app_name' => 'my_app')
107
+ expect(enqueued_jobs.first[:args].first).to include('stream_name' => 'my_app_events')
108
108
  end
109
109
 
110
110
  context 'when there is no job priority specified in the enqueue opts' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: journaled
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Lipson
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2021-06-02 00:00:00.000000000 Z
14
+ date: 2021-10-27 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: activejob
@@ -245,7 +245,6 @@ files:
245
245
  - app/models/journaled/change.rb
246
246
  - app/models/journaled/change_definition.rb
247
247
  - app/models/journaled/change_writer.rb
248
- - app/models/journaled/delivery.rb
249
248
  - app/models/journaled/event.rb
250
249
  - app/models/journaled/json_schema_model/validator.rb
251
250
  - app/models/journaled/not_truly_exceptional_error.rb
@@ -293,7 +292,6 @@ files:
293
292
  - spec/models/database_change_protection_spec.rb
294
293
  - spec/models/journaled/actor_uri_provider_spec.rb
295
294
  - spec/models/journaled/change_writer_spec.rb
296
- - spec/models/journaled/delivery_spec.rb
297
295
  - spec/models/journaled/event_spec.rb
298
296
  - spec/models/journaled/json_schema_model/validator_spec.rb
299
297
  - spec/models/journaled/writer_spec.rb
@@ -319,7 +317,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
319
317
  - !ruby/object:Gem::Version
320
318
  version: '0'
321
319
  requirements: []
322
- rubygems_version: 3.1.2
320
+ rubygems_version: 3.0.1
323
321
  signing_key:
324
322
  specification_version: 4
325
323
  summary: Journaling for Betterment apps.
@@ -354,7 +352,6 @@ test_files:
354
352
  - spec/dummy/README.rdoc
355
353
  - spec/models/journaled/json_schema_model/validator_spec.rb
356
354
  - spec/models/journaled/actor_uri_provider_spec.rb
357
- - spec/models/journaled/delivery_spec.rb
358
355
  - spec/models/journaled/event_spec.rb
359
356
  - spec/models/journaled/change_writer_spec.rb
360
357
  - spec/models/journaled/writer_spec.rb
@@ -1,88 +0,0 @@
1
- class Journaled::Delivery # rubocop:disable Betterment/ActiveJobPerformable
2
- DEFAULT_REGION = 'us-east-1'.freeze
3
-
4
- def initialize(serialized_event:, partition_key:, app_name:)
5
- @serialized_event = serialized_event
6
- @partition_key = partition_key
7
- @app_name = app_name
8
- end
9
-
10
- def perform
11
- kinesis_client.put_record record if Journaled.enabled?
12
- rescue Aws::Kinesis::Errors::InternalFailure, Aws::Kinesis::Errors::ServiceUnavailable, Aws::Kinesis::Errors::Http503Error => e
13
- Rails.logger.error "Kinesis Error - Server Error occurred - #{e.class}"
14
- raise KinesisTemporaryFailure
15
- rescue Seahorse::Client::NetworkingError => e
16
- Rails.logger.error "Kinesis Error - Networking Error occurred - #{e.class}"
17
- raise KinesisTemporaryFailure
18
- end
19
-
20
- def stream_name
21
- env_var_name = [app_name&.upcase, 'JOURNALED_STREAM_NAME'].compact.join('_')
22
- ENV.fetch(env_var_name)
23
- end
24
-
25
- def kinesis_client_config
26
- {
27
- region: ENV.fetch('AWS_DEFAULT_REGION', DEFAULT_REGION),
28
- retry_limit: 0,
29
- http_idle_timeout: Journaled.http_idle_timeout,
30
- http_open_timeout: Journaled.http_open_timeout,
31
- http_read_timeout: Journaled.http_read_timeout,
32
- }.merge(credentials)
33
- end
34
-
35
- private
36
-
37
- attr_reader :serialized_event, :partition_key, :app_name
38
-
39
- def record
40
- {
41
- stream_name: stream_name,
42
- data: serialized_event,
43
- partition_key: partition_key,
44
- }
45
- end
46
-
47
- def kinesis_client
48
- Aws::Kinesis::Client.new(kinesis_client_config)
49
- end
50
-
51
- def credentials
52
- if ENV.key?('JOURNALED_IAM_ROLE_ARN')
53
- {
54
- credentials: iam_assume_role_credentials,
55
- }
56
- else
57
- legacy_credentials_hash_if_present
58
- end
59
- end
60
-
61
- def legacy_credentials_hash_if_present
62
- if ENV.key?('RUBY_AWS_ACCESS_KEY_ID')
63
- {
64
- access_key_id: ENV.fetch('RUBY_AWS_ACCESS_KEY_ID'),
65
- secret_access_key: ENV.fetch('RUBY_AWS_SECRET_ACCESS_KEY'),
66
- }
67
- else
68
- {}
69
- end
70
- end
71
-
72
- def sts_client
73
- Aws::STS::Client.new({
74
- region: ENV.fetch('AWS_DEFAULT_REGION', DEFAULT_REGION),
75
- }.merge(legacy_credentials_hash_if_present))
76
- end
77
-
78
- def iam_assume_role_credentials
79
- @iam_assume_role_credentials ||= Aws::AssumeRoleCredentials.new(
80
- client: sts_client,
81
- role_arn: ENV.fetch('JOURNALED_IAM_ROLE_ARN'),
82
- role_session_name: "JournaledAssumeRoleAccess",
83
- )
84
- end
85
-
86
- class KinesisTemporaryFailure < ::Journaled::NotTrulyExceptionalError
87
- end
88
- end
@@ -1,222 +0,0 @@
1
- require 'rails_helper'
2
-
3
- RSpec.describe Journaled::Delivery do
4
- let(:stream_name) { 'test_events' }
5
- let(:partition_key) { 'fake_partition_key' }
6
- let(:serialized_event) { '{"foo":"bar"}' }
7
- let(:kinesis_client) { Aws::Kinesis::Client.new(stub_responses: true) }
8
-
9
- around do |example|
10
- with_env(JOURNALED_STREAM_NAME: stream_name) { example.run }
11
- end
12
-
13
- subject { described_class.new serialized_event: serialized_event, partition_key: partition_key, app_name: nil }
14
-
15
- describe '#perform' do
16
- let(:return_status_body) { { shard_id: '101', sequence_number: '101123' } }
17
- let(:return_object) { instance_double Aws::Kinesis::Types::PutRecordOutput, return_status_body }
18
-
19
- before do
20
- allow(Aws::AssumeRoleCredentials).to receive(:new).and_call_original
21
- allow(Aws::Kinesis::Client).to receive(:new).and_return kinesis_client
22
- kinesis_client.stub_responses(:put_record, return_status_body)
23
-
24
- allow(Journaled).to receive(:enabled?).and_return(true)
25
- end
26
-
27
- it 'makes requests to AWS to put the event on the Kinesis with the correct body' do
28
- event = subject.perform
29
-
30
- expect(event.shard_id).to eq '101'
31
- expect(event.sequence_number).to eq '101123'
32
- end
33
-
34
- context 'when JOURNALED_IAM_ROLE_ARN is defined' do
35
- let(:aws_sts_client) { Aws::STS::Client.new(stub_responses: true) }
36
-
37
- around do |example|
38
- with_env(JOURNALED_IAM_ROLE_ARN: 'iam-role-arn-for-assuming-kinesis-access') { example.run }
39
- end
40
-
41
- before do
42
- allow(Aws::STS::Client).to receive(:new).and_return aws_sts_client
43
- aws_sts_client.stub_responses(:assume_role, assume_role_response)
44
- end
45
-
46
- let(:assume_role_response) do
47
- {
48
- assumed_role_user: {
49
- arn: 'iam-role-arn-for-assuming-kinesis-access',
50
- assumed_role_id: "ARO123EXAMPLE123:Bob",
51
- },
52
- credentials: {
53
- access_key_id: "AKIAIOSFODNN7EXAMPLE",
54
- secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY",
55
- session_token: "EXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI",
56
- expiration: Time.zone.parse("2011-07-15T23:28:33.359Z"),
57
- },
58
- }
59
- end
60
-
61
- it 'initializes a Kinesis client with assume role credentials' do
62
- subject.perform
63
-
64
- expect(Aws::AssumeRoleCredentials).to have_received(:new).with(
65
- client: aws_sts_client,
66
- role_arn: "iam-role-arn-for-assuming-kinesis-access",
67
- role_session_name: "JournaledAssumeRoleAccess",
68
- )
69
- end
70
- end
71
-
72
- context 'when the stream name env var is NOT set' do
73
- let(:stream_name) { nil }
74
-
75
- it 'raises an KeyError error' do
76
- expect { subject.perform }.to raise_error KeyError
77
- end
78
- end
79
-
80
- context 'when Amazon responds with an InternalFailure' do
81
- before do
82
- kinesis_client.stub_responses(:put_record, 'InternalFailure')
83
- end
84
-
85
- it 'catches the error and re-raises a subclass of NotTrulyExceptionalError and logs about the failure' do
86
- expect(Rails.logger).to receive(:error).with("Kinesis Error - Server Error occurred - Aws::Kinesis::Errors::InternalFailure").once
87
- expect { subject.perform }.to raise_error described_class::KinesisTemporaryFailure
88
- end
89
- end
90
-
91
- context 'when Amazon responds with a ServiceUnavailable' do
92
- before do
93
- kinesis_client.stub_responses(:put_record, 'ServiceUnavailable')
94
- end
95
-
96
- it 'catches the error and re-raises a subclass of NotTrulyExceptionalError and logs about the failure' do
97
- allow(Rails.logger).to receive(:error)
98
- expect { subject.perform }.to raise_error described_class::KinesisTemporaryFailure
99
- expect(Rails.logger).to have_received(:error).with(/\AKinesis Error/).once
100
- end
101
- end
102
-
103
- context 'when we receive a 504 Gateway timeout' do
104
- before do
105
- kinesis_client.stub_responses(:put_record, 'Aws::Kinesis::Errors::ServiceError')
106
- end
107
-
108
- it 'raises an error that subclasses Aws::Kinesis::Errors::ServiceError' do
109
- expect { subject.perform }.to raise_error Aws::Kinesis::Errors::ServiceError
110
- end
111
- end
112
-
113
- context 'when the IAM user does not have permission to put_record to the specified stream' do
114
- before do
115
- kinesis_client.stub_responses(:put_record, 'AccessDeniedException')
116
- end
117
-
118
- it 'raises an AccessDeniedException error' do
119
- expect { subject.perform }.to raise_error Aws::Kinesis::Errors::AccessDeniedException
120
- end
121
- end
122
-
123
- context 'when the request timesout' do
124
- before do
125
- kinesis_client.stub_responses(:put_record, Seahorse::Client::NetworkingError.new(Timeout::Error.new))
126
- end
127
-
128
- it 'catches the error and re-raises a subclass of NotTrulyExceptionalError and logs about the failure' do
129
- expect(Rails.logger).to receive(:error).with(
130
- "Kinesis Error - Networking Error occurred - Seahorse::Client::NetworkingError",
131
- ).once
132
- expect { subject.perform }.to raise_error described_class::KinesisTemporaryFailure
133
- end
134
- end
135
- end
136
-
137
- describe "#stream_name" do
138
- context "when app_name is unspecified" do
139
- subject { described_class.new serialized_event: serialized_event, partition_key: partition_key, app_name: nil }
140
-
141
- it "is fetched from a prefixed ENV var if specified" do
142
- allow(ENV).to receive(:fetch).and_return("expected_stream_name")
143
- expect(subject.stream_name).to eq("expected_stream_name")
144
- expect(ENV).to have_received(:fetch).with("JOURNALED_STREAM_NAME")
145
- end
146
- end
147
-
148
- context "when app_name is specified" do
149
- subject { described_class.new serialized_event: serialized_event, partition_key: partition_key, app_name: "my_funky_app_name" }
150
-
151
- it "is fetched from a prefixed ENV var if specified" do
152
- allow(ENV).to receive(:fetch).and_return("expected_stream_name")
153
- expect(subject.stream_name).to eq("expected_stream_name")
154
- expect(ENV).to have_received(:fetch).with("MY_FUNKY_APP_NAME_JOURNALED_STREAM_NAME")
155
- end
156
- end
157
- end
158
-
159
- describe "#kinesis_client_config" do
160
- it "is in us-east-1 by default" do
161
- with_env(AWS_DEFAULT_REGION: nil) do
162
- expect(subject.kinesis_client_config).to include(region: 'us-east-1')
163
- end
164
- end
165
-
166
- it "respects AWS_DEFAULT_REGION env var" do
167
- with_env(AWS_DEFAULT_REGION: 'us-west-2') do
168
- expect(subject.kinesis_client_config).to include(region: 'us-west-2')
169
- end
170
- end
171
-
172
- it "doesn't limit retry" do
173
- expect(subject.kinesis_client_config).to include(retry_limit: 0)
174
- end
175
-
176
- it "provides no AWS credentials by default" do
177
- with_env(RUBY_AWS_ACCESS_KEY_ID: nil, RUBY_AWS_SECRET_ACCESS_KEY: nil) do
178
- expect(subject.kinesis_client_config).not_to have_key(:access_key_id)
179
- expect(subject.kinesis_client_config).not_to have_key(:secret_access_key)
180
- end
181
- end
182
-
183
- it "will use legacy credentials if specified" do
184
- with_env(RUBY_AWS_ACCESS_KEY_ID: 'key_id', RUBY_AWS_SECRET_ACCESS_KEY: 'secret') do
185
- expect(subject.kinesis_client_config).to include(access_key_id: 'key_id', secret_access_key: 'secret')
186
- end
187
- end
188
-
189
- it "will set http_idle_timeout by default" do
190
- expect(subject.kinesis_client_config).to include(http_idle_timeout: 5)
191
- end
192
-
193
- it "will set http_open_timeout by default" do
194
- expect(subject.kinesis_client_config).to include(http_open_timeout: 2)
195
- end
196
-
197
- it "will set http_read_timeout by default" do
198
- expect(subject.kinesis_client_config).to include(http_read_timeout: 60)
199
- end
200
-
201
- context "when Journaled.http_idle_timeout is specified" do
202
- it "will set http_idle_timeout by specified value" do
203
- allow(Journaled).to receive(:http_idle_timeout).and_return(2)
204
- expect(subject.kinesis_client_config).to include(http_idle_timeout: 2)
205
- end
206
- end
207
-
208
- context "when Journaled.http_open_timeout is specified" do
209
- it "will set http_open_timeout by specified value" do
210
- allow(Journaled).to receive(:http_open_timeout).and_return(1)
211
- expect(subject.kinesis_client_config).to include(http_open_timeout: 1)
212
- end
213
- end
214
-
215
- context "when Journaled.http_read_timeout is specified" do
216
- it "will set http_read_timeout by specified value" do
217
- allow(Journaled).to receive(:http_read_timeout).and_return(2)
218
- expect(subject.kinesis_client_config).to include(http_read_timeout: 2)
219
- end
220
- end
221
- end
222
- end