firehose_integration 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/lib/firehose_integration.rb +9 -0
  6. data/lib/firehose_integration/active_record_relation.rb +6 -0
  7. data/lib/firehose_integration/jobs/kinesis_bulk_job.rb +22 -0
  8. data/lib/firehose_integration/jobs/kinesis_job.rb +16 -0
  9. data/lib/firehose_integration/jobs/kinesis_single_object_job.rb +24 -0
  10. data/lib/firehose_integration/models/concerns/kinesis_event.rb +71 -0
  11. data/lib/firehose_integration/version.rb +3 -0
  12. data/lib/tasks/firehose_integration_tasks.rake +4 -0
  13. data/test/cassettes/kinesis_bulk_failure.yml +55 -0
  14. data/test/cassettes/kinesis_bulk_success.yml +52 -0
  15. data/test/cassettes/kinesis_failure.yml +55 -0
  16. data/test/cassettes/kinesis_success.yml +52 -0
  17. data/test/dummy/README.rdoc +28 -0
  18. data/test/dummy/Rakefile +6 -0
  19. data/test/dummy/app/assets/javascripts/application.js +13 -0
  20. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  21. data/test/dummy/app/controllers/application_controller.rb +5 -0
  22. data/test/dummy/app/helpers/application_helper.rb +2 -0
  23. data/test/dummy/app/kinesis_serializers/dummy_model_kinesis_serializer.rb +17 -0
  24. data/test/dummy/app/models/dummy_model.rb +3 -0
  25. data/test/dummy/app/models/stupid_model.rb +3 -0
  26. data/test/dummy/app/views/layouts/application.html.erb +13 -0
  27. data/test/dummy/bin/bundle +3 -0
  28. data/test/dummy/bin/rails +4 -0
  29. data/test/dummy/bin/rake +4 -0
  30. data/test/dummy/bin/setup +29 -0
  31. data/test/dummy/config.ru +4 -0
  32. data/test/dummy/config/application.rb +32 -0
  33. data/test/dummy/config/boot.rb +5 -0
  34. data/test/dummy/config/database.yml +25 -0
  35. data/test/dummy/config/environment.rb +5 -0
  36. data/test/dummy/config/environments/development.rb +41 -0
  37. data/test/dummy/config/environments/production.rb +79 -0
  38. data/test/dummy/config/environments/test.rb +42 -0
  39. data/test/dummy/config/initializers/assets.rb +11 -0
  40. data/test/dummy/config/initializers/aws.rb +6 -0
  41. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  42. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  43. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  44. data/test/dummy/config/initializers/inflections.rb +16 -0
  45. data/test/dummy/config/initializers/mime_types.rb +4 -0
  46. data/test/dummy/config/initializers/session_store.rb +3 -0
  47. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/test/dummy/config/locales/en.yml +23 -0
  49. data/test/dummy/config/routes.rb +56 -0
  50. data/test/dummy/config/secrets.yml +22 -0
  51. data/test/dummy/db/development.sqlite3 +0 -0
  52. data/test/dummy/db/migrate/20160526190814_create_dummy_models.rb +7 -0
  53. data/test/dummy/db/migrate/20160526203033_create_stupid_model.rb +7 -0
  54. data/test/dummy/db/schema.rb +26 -0
  55. data/test/dummy/db/test.sqlite3 +0 -0
  56. data/test/dummy/log/development.log +34 -0
  57. data/test/dummy/log/test.log +3669 -0
  58. data/test/dummy/public/404.html +67 -0
  59. data/test/dummy/public/422.html +67 -0
  60. data/test/dummy/public/500.html +66 -0
  61. data/test/dummy/public/favicon.ico +0 -0
  62. data/test/firehose_integration_test.rb +7 -0
  63. data/test/fixtures/dummy_models.yml +3 -0
  64. data/test/jobs/kinesis_bulk_job_test.rb +26 -0
  65. data/test/jobs/kinesis_job_test.rb +26 -0
  66. data/test/jobs/kinesis_single_object_test.rb +39 -0
  67. data/test/models/concerns/kinesis_event_test.rb +27 -0
  68. data/test/test_helper.rb +29 -0
  69. metadata +266 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ba2eded2ae69513146014504efed4d2aaa8bc666
4
+ data.tar.gz: 91994144647e8daeaab259af8f9e0dcfa7242b5a
5
+ SHA512:
6
+ metadata.gz: b5604bb6df7f159e31c5837603cce360fbf390a0d66f59e0a6eb62874d0c2643637bb2d047774e8eafd97851411b5205cc707d95efab9d5ad7975f6fbc481197
7
+ data.tar.gz: 0a75b00665f45736d94974a8ca275b4960eb8b3f554b3fa0c69472a5e3cfdfed02eef6760e18ae0ebf56f84af0af7f24c526467fcf61d9d65619bddfa5a18be2
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2016 onomojo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = FirehoseIntegration
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'FirehoseIntegration'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,9 @@
1
+ require "firehose_integration/active_record_relation"
2
+
3
+ require "firehose_integration/jobs/kinesis_job"
4
+ require "firehose_integration/jobs/kinesis_bulk_job"
5
+ require "firehose_integration/jobs/kinesis_single_object_job"
6
+
7
+ require "firehose_integration/models/concerns/kinesis_event"
8
+ module FirehoseIntegration
9
+ end
@@ -0,0 +1,6 @@
1
+ class ActiveRecord::Relation
2
+ def update_all_with_kinesis(params)
3
+ self.update_all params
4
+ FirehoseIntegration::KinesisSingleObjectJob.perform_later(ancestors.first.to_s, self.pluck(:id))
5
+ end
6
+ end
@@ -0,0 +1,22 @@
1
+ module FirehoseIntegration
2
+ class KinesisBulkJob < ActiveJob::Base
3
+ queue_as :kinesis_events_bulk
4
+ def perform(stream, data)
5
+ client = Aws::Firehose::Client.new(region:'us-east-1')
6
+
7
+ records = []
8
+ data.each do |d|
9
+ records << {
10
+ data: "#{d}\n"
11
+ }
12
+ end
13
+
14
+ params = {
15
+ delivery_stream_name: stream,
16
+ records: records
17
+ }
18
+
19
+ client.put_record_batch params
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module FirehoseIntegration
2
+ class KinesisJob < ActiveJob::Base
3
+ queue_as :kinesis_events
4
+ def perform(stream, data)
5
+ client = Aws::Firehose::Client.new(region:'us-east-1')
6
+
7
+ params = {
8
+ delivery_stream_name: stream,
9
+ record: {
10
+ data: "#{data}\n"
11
+ }
12
+ }
13
+ client.put_record params
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,24 @@
1
+ module FirehoseIntegration
2
+ class KinesisSingleObjectJob < ActiveJob::Base
3
+ queue_as :kinesis_events_single
4
+
5
+ def perform(class_name, ids)
6
+ client = Aws::Firehose::Client.new(region:'us-east-1')
7
+ results = []
8
+ ids.each do |id|
9
+ object = class_name.constantize.find(id)
10
+ stream = object.class.kinesis_stream_name
11
+ data = object.to_kinesis
12
+
13
+ params = {
14
+ delivery_stream_name: stream,
15
+ record: {
16
+ data: "#{data}\n"
17
+ }
18
+ }
19
+ results << client.put_record(params)
20
+ end
21
+ results
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,71 @@
1
+ module FirehoseIntegration
2
+ module KinesisEvent
3
+ extend ActiveSupport::Concern
4
+
5
+ MAX_REDSHIFT_STRING_SIZE = 65535
6
+
7
+ module ClassMethods
8
+ def firehose_integratable
9
+ after_commit :send_kinesis_event, unless: Proc.new { |instance| instance.try(:skip_kinesis_event) }
10
+
11
+ begin
12
+ include "#{self.model_name.name}KinesisSerializer".constantize
13
+ rescue
14
+ end
15
+ end
16
+
17
+ def kinesis_stream_name
18
+ raise(NoMethodError, "Model must define class method kinesis_stream_name")
19
+ end
20
+ end
21
+
22
+ included do
23
+
24
+ def to_kinesis
25
+ raise(NoMethodError, "Model must define instance method to_kinesis")
26
+ end
27
+
28
+ def prepare_for_redshift(field)
29
+ if field.present?
30
+ if field.is_a? Array
31
+ output = []
32
+ field.each do |f|
33
+ output << massage_data_for_redshift(f)
34
+ end
35
+ return output.join("|")
36
+ end
37
+ end
38
+ end
39
+
40
+ def massage_data_for_redshift field
41
+ if field.is_a? String
42
+ output = escape_string_for_redshift(field).truncate(MAX_REDSHIFT_STRING_SIZE)
43
+ Rails.logger.info "Redshift data has been truncated due to length: #{output.truncate(50)}" if output.size == MAX_REDSHIFT_STRING_SIZE
44
+ output
45
+ else
46
+ field
47
+ end
48
+ end
49
+
50
+ def escape_string_for_redshift field
51
+ return field unless field.is_a? String
52
+ output = field
53
+ {
54
+ '\n' => '\\n',
55
+ '\r' => '\\r',
56
+ '\|' => '&verbar;'
57
+ }.each do |pattern, replacement|
58
+ output = output.gsub(Regexp.new(pattern), replacement)
59
+ end
60
+ output
61
+ end
62
+
63
+
64
+ def send_kinesis_event
65
+ KinesisJob.perform_later(self.class.kinesis_stream_name, self.to_kinesis)
66
+ self.kinesis_extra_serialization if self.methods.include? :kinesis_extra_serialization
67
+ end
68
+ end
69
+ end
70
+ end
71
+ ActiveRecord::Base.send :include, FirehoseIntegration::KinesisEvent
@@ -0,0 +1,3 @@
1
+ module FirehoseIntegration
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :firehose_integration do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,55 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://firehose.us-east-1.amazonaws.com/
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"DeliveryStreamName":"invalid-stream-name","Records":[{"Data":"c29tZSBkYXRhCg=="}]}'
9
+ headers:
10
+ Content-Type:
11
+ - application/x-amz-json-1.1
12
+ Accept-Encoding:
13
+ - ''
14
+ User-Agent:
15
+ - aws-sdk-ruby2/2.3.8 ruby/2.2.1 x86_64-darwin15
16
+ X-Amz-Target:
17
+ - Firehose_20150804.PutRecordBatch
18
+ X-Amz-Date:
19
+ - 20160526T204248Z
20
+ Host:
21
+ - firehose.us-east-1.amazonaws.com
22
+ X-Amz-Content-Sha256:
23
+ - 51d39395cb7e526eb7bac24de83e3012137401a5d77dcaf4308119c1c69a8f33
24
+ Authorization:
25
+ - AWS4-HMAC-SHA256 Credential=akid/20160526/us-east-1/firehose/aws4_request,
26
+ SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target,
27
+ Signature=bb4def5cb89830d9f160f15e40b4327f556bd719d7b9cc1c06084bf43e342ef2
28
+ Content-Length:
29
+ - '84'
30
+ Accept:
31
+ - "*/*"
32
+ response:
33
+ status:
34
+ code: 400
35
+ message: Bad Request
36
+ headers:
37
+ X-Amzn-Requestid:
38
+ - 69cc5bea-2382-11e6-aaa0-171acafcc56d
39
+ X-Amz-Id-2:
40
+ - sJghDMvGF8Lt4ULOjnCoI0pj42bU/9gRZ+uLaCTZCDaRcKRyVHnr9GcucTdP8S8fgkWwPsVY3+o8tUzVXoMzHg==
41
+ Content-Type:
42
+ - application/x-amz-json-1.1
43
+ Content-Length:
44
+ - '117'
45
+ Date:
46
+ - Thu, 26 May 2016 20:42:50 GMT
47
+ Connection:
48
+ - close
49
+ body:
50
+ encoding: UTF-8
51
+ string: '{"__type":"ResourceNotFoundException","message":"Firehose invalid-stream-name
52
+ not found under account 000111222333."}'
53
+ http_version:
54
+ recorded_at: Thu, 26 May 2016 20:42:48 GMT
55
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,52 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://firehose.us-east-1.amazonaws.com/
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"DeliveryStreamName":"test-stream","Records":[{"Data":"c29tZSBkYXRhCg=="},{"Data":"bW9yZSBkYXRhCg=="}]}'
9
+ headers:
10
+ Content-Type:
11
+ - application/x-amz-json-1.1
12
+ Accept-Encoding:
13
+ - ''
14
+ User-Agent:
15
+ - aws-sdk-ruby2/2.3.8 ruby/2.2.1 x86_64-darwin15
16
+ X-Amz-Target:
17
+ - Firehose_20150804.PutRecordBatch
18
+ X-Amz-Date:
19
+ - 20160526T204248Z
20
+ Host:
21
+ - firehose.us-east-1.amazonaws.com
22
+ X-Amz-Content-Sha256:
23
+ - 46abc6a5b3638e3d8b76072597aade6f9ebad6eee1e4935c232a1be5e3842214
24
+ Authorization:
25
+ - AWS4-HMAC-SHA256 Credential=akid/20160526/us-east-1/firehose/aws4_request,
26
+ SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target,
27
+ Signature=bad19647525c88c8307912d614088b00d9c26a644934eac53c62530eacef1ef6
28
+ Content-Length:
29
+ - '104'
30
+ Accept:
31
+ - "*/*"
32
+ response:
33
+ status:
34
+ code: 200
35
+ message: OK
36
+ headers:
37
+ X-Amzn-Requestid:
38
+ - 6a098c7c-2382-11e6-9ea8-514fc73df602
39
+ X-Amz-Id-2:
40
+ - 8ZperIhPiVSMSHSeCAxfkbqbqgyofdOwaTrcrhvi8UP/ocNmmlKYbSvsAoFKhjVO0qH0XHXF3SoaETk+QvZhjw==
41
+ Content-Type:
42
+ - application/x-amz-json-1.1
43
+ Content-Length:
44
+ - '90'
45
+ Date:
46
+ - Thu, 17 Mar 2016 15:43:58 GMT
47
+ body:
48
+ encoding: UTF-8
49
+ string: '{"FailedPutCount":0,"RequestResponses":[{"RecordId":"SOMEID"},{"RecordId":"SOMEOTHERID"}]}'
50
+ http_version:
51
+ recorded_at: Thu, 17 Mar 2016 15:43:58 GMT
52
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,55 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://firehose.us-east-1.amazonaws.com/
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"DeliveryStreamName":"invalid-stream-name","Record":{"Data":"c29tZSBkYXRhCg=="}}'
9
+ headers:
10
+ Content-Type:
11
+ - application/x-amz-json-1.1
12
+ Accept-Encoding:
13
+ - ''
14
+ User-Agent:
15
+ - aws-sdk-ruby2/2.3.8 ruby/2.2.1 x86_64-darwin15
16
+ X-Amz-Target:
17
+ - Firehose_20150804.PutRecord
18
+ X-Amz-Date:
19
+ - 20160526T210311Z
20
+ Host:
21
+ - firehose.us-east-1.amazonaws.com
22
+ X-Amz-Content-Sha256:
23
+ - 174411b44d27135ef894b2bfc487dacf8753738bc6070405ece770a2c5acfd65
24
+ Authorization:
25
+ - AWS4-HMAC-SHA256 Credential=akid/20160526/us-east-1/firehose/aws4_request,
26
+ SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target,
27
+ Signature=ae6474afcfb57bd28f8540c6e43fb12bae6cd41fe2db9b1994ce47c514b87703
28
+ Content-Length:
29
+ - '81'
30
+ Accept:
31
+ - "*/*"
32
+ response:
33
+ status:
34
+ code: 400
35
+ message: Bad Request
36
+ headers:
37
+ X-Amzn-Requestid:
38
+ - 42fabd77-2385-11e6-be61-91f005091ebf
39
+ X-Amz-Id-2:
40
+ - XKgPSA6n5lN6sk6tvNz+5wUS46riOeVei6jvqCPucXpQEy2/EzjzpzwHn8hknKQg8dWvq9iVcAjfH66qN0C4JdkRjEdnQQFn
41
+ Content-Type:
42
+ - application/x-amz-json-1.1
43
+ Content-Length:
44
+ - '117'
45
+ Date:
46
+ - Thu, 26 May 2016 21:03:14 GMT
47
+ Connection:
48
+ - close
49
+ body:
50
+ encoding: UTF-8
51
+ string: '{"__type":"ResourceNotFoundException","message":"Firehose invalid-stream-name
52
+ not found under account 000111222333."}'
53
+ http_version:
54
+ recorded_at: Thu, 26 May 2016 21:03:12 GMT
55
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,52 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: https://firehose.us-east-1.amazonaws.com/
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"DeliveryStreamName":"some-stream","Record":{"Data":"c29tZSBkYXRhCg=="}}'
9
+ headers:
10
+ Content-Type:
11
+ - application/x-amz-json-1.1
12
+ Accept-Encoding:
13
+ - ''
14
+ User-Agent:
15
+ - aws-sdk-ruby2/2.3.8 ruby/2.2.1 x86_64-darwin15
16
+ X-Amz-Target:
17
+ - Firehose_20150804.PutRecord
18
+ X-Amz-Date:
19
+ - 20160526T205525Z
20
+ Host:
21
+ - firehose.us-east-1.amazonaws.com
22
+ X-Amz-Content-Sha256:
23
+ - b770027680e36df60472bda3598bc24ab1b28083f6c0bae0f858715d2830ed83
24
+ Authorization:
25
+ - AWS4-HMAC-SHA256 Credential=akid/20160526/us-east-1/firehose/aws4_request,
26
+ SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-amz-target,
27
+ Signature=5dd75f094a5b67f726c7ea3333102a3d9a870975e9357efa48d13c35ae5e26c6
28
+ Content-Length:
29
+ - '73'
30
+ Accept:
31
+ - "*/*"
32
+ response:
33
+ status:
34
+ code: 200
35
+ message: OK
36
+ headers:
37
+ X-Amzn-Requestid:
38
+ - 2cfe307f-2384-11e6-9868-69152542a0c3
39
+ X-Amz-Id-2:
40
+ - GWN297SaqQ9EdAgTzh9MzxSU3OFyemuVtiiKIz+idFwspK9imwSwhLzEM3jjVqTl8joR1DcvfFO4riIj0spnOfjRoBqHS5cC
41
+ Content-Type:
42
+ - application/x-amz-json-1.1
43
+ Content-Length:
44
+ - '21'
45
+ Date:
46
+ - Thu, 26 May 2016 20:55:27 GMT
47
+ body:
48
+ encoding: UTF-8
49
+ string: '{"RecordId":"SOMEID"}'
50
+ http_version:
51
+ recorded_at: Thu, 26 May 2016 20:55:25 GMT
52
+ recorded_with: VCR 2.9.3