journaled 3.1.0 → 4.0.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: 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