active_elastic_job 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d19d4e4ef144ceabffd14c20daae3b285df14616
4
+ data.tar.gz: 70be0d9706ef4ae1f7a91d14380522e5263a2c31
5
+ SHA512:
6
+ metadata.gz: 829d4cd3713427e6b85ed76a0a9e212b63ad5ab1232c901d30f2ad5360db90af1e242ef0fe8f37c0d01475911ad3d88e8d5dfdb22c41e17fcc3d9f04c32f5486
7
+ data.tar.gz: 7cea59eaaa8091a0cfa95c103ef373874f31225223e60d87e5ce9c41b5135a9ab4db6099a66f5f6d0c384174372a838c947624e71e1a3b099b74cc905983b17d
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'active_elastic_job/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.platform = Gem::Platform::RUBY
9
+ spec.name = 'active_elastic_job'
10
+ spec.version = ActiveElasticJob::VERSION
11
+ spec.authors = ['Tawan Sierek']
12
+ spec.email = ['tawan@sierek.com']
13
+ spec.description = 'Active Elastic Job is a simple to use Active Job backend for Rails applications deployed on the Amazon Elastic Beanstalk platform.'
14
+ spec.summary = spec.description
15
+ spec.license = 'MIT'
16
+ spec.homepage = 'https://github.com/tawan/active-elastic-job'
17
+
18
+ spec.files = Dir.glob('lib/**/*') + [ 'active-elastic-job.gemspec' ]
19
+ spec.executables = []
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.required_ruby_version = '>= 1.9.3'
24
+
25
+ spec.add_development_dependency 'bundler'
26
+ spec.add_development_dependency 'rspec', '~> 3.4'
27
+ spec.add_development_dependency 'dotenv'
28
+ spec.add_development_dependency 'fuubar'
29
+ spec.add_development_dependency 'rails', '~> 4.2'
30
+ spec.add_development_dependency 'rdoc'
31
+ spec.add_dependency 'aws-sdk', '~> 2'
32
+ end
@@ -0,0 +1,9 @@
1
+ require 'aws-sdk-core'
2
+ require 'active_elastic_job/version'
3
+ require 'active_job/queue_adapters/active_elastic_job_adapter'
4
+ require 'active_elastic_job/rack/sqs_message_consumer'
5
+ require 'active_elastic_job/message_verifier'
6
+
7
+ module ActiveElasticJob; end;
8
+
9
+ require "active_elastic_job/railtie" if defined? Rails
@@ -0,0 +1,32 @@
1
+ require 'active_support/security_utils'
2
+
3
+ module ActiveElasticJob
4
+ class MessageVerifier #:nodoc:
5
+
6
+ # Raised when digest generated by
7
+ # <tt>ActiveJob::QueueAdapters::ActiveElasticJobAdapter</tt> could not
8
+ # be verified.
9
+ class InvalidDigest < StandardError
10
+ end
11
+
12
+ def initialize(secret)
13
+ @secret = secret
14
+ end
15
+
16
+ def verify(message, digest)
17
+ if message.nil? || message.blank? || digest.nil? || digest.blank?
18
+ raise InvalidDigest
19
+ end
20
+
21
+ unless ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(message))
22
+ raise InvalidDigest
23
+ end
24
+ true
25
+ end
26
+
27
+ def generate_digest(message)
28
+ require 'openssl' unless defined?(OpenSSL)
29
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get('SHA1').new, @secret, message)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,47 @@
1
+ require "action_dispatch"
2
+
3
+ module ActiveElasticJob
4
+ module Rack
5
+ # This middleware intercepts requests which are sent by the SQS daemon
6
+ # running in {Amazon Elastic Beanstalk worker environments}[http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html].
7
+ # It does this by looking at the +User-Agent+ header.
8
+ # Furthermore, it verifies the digest which is sent along with a legit SQS message,
9
+ # and passed as an HTTP header in the resulting request.
10
+ # The digest is based on Rails' +secrets.secret_key_base+.
11
+ # Therefore, the application running in the web environment, which generates
12
+ # the digest, and the application running in the worker
13
+ # environment, which verifies the digest, have to use the *same*
14
+ # +secrets.secret_key_base+ setting.
15
+ class SqsMessageConsumer
16
+ def initialize(app) #:nodoc:
17
+ @app = app
18
+ end
19
+
20
+ def call(env) #:nodoc:
21
+ request = ActionDispatch::Request.new env
22
+ if request.headers['User-Agent'] =~ /aws-sqsd/
23
+ begin
24
+ verify(request)
25
+ job = JSON.load(request.body)
26
+ ActiveJob::Base.execute(job)
27
+ rescue ActiveElasticJob::MessageVerifier::InvalidDigest => e
28
+ return ['403', {'Content-Type' => env['text/plain'] }, ["incorrect digest"]]
29
+ rescue StandardError => e
30
+ return ['500', {'Content-Type' => env['text/plain'] }, [e.message]]
31
+ end
32
+ return ['200', {'Content-Type' => 'application/json' }, [ '' ]]
33
+ end
34
+ @app.call(env)
35
+ end
36
+
37
+ private
38
+
39
+ def verify(request)
40
+ secret_key_base = Rails.application.secrets[:secret_key_base]
41
+ verifier = ActiveElasticJob::MessageVerifier.new(secret_key_base)
42
+ digest = request.headers['HTTP_X_AWS_SQSD_ATTR_MESSAGE_DIGEST']
43
+ verifier.verify(request.body.string, digest)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,7 @@
1
+ module ActiveElasticJob
2
+ class Railtie < Rails::Railtie
3
+ initializer "active_elastic_job.insert_middleware" do |app|
4
+ app.config.middleware.use "ActiveElasticJob::Rack::SqsMessageConsumer"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveElasticJob
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,66 @@
1
+ module ActiveJob
2
+ module QueueAdapters
3
+ # == Active Elastic Job adapter for Active Job
4
+ #
5
+ # Active Elastic Job provides (1) an adapter (this class) for Rails'
6
+ # Active Job framework and (2) a Rack middleware to process job requests,
7
+ # which are sent by the SQS daemon running in {Amazon Elastic Beanstalk worker
8
+ # environments}[http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/using-features-managing-env-tiers.html].
9
+ #
10
+ # This adapter serializes job objects and sends them as a message to an
11
+ # Amazon SQS queue specified by the job's queue name, see <tt>ActiveJob::Base.queue_as</tt>
12
+ #
13
+ # To use Active Elastic Job, set the queue_adapter config to +:active_elastic_job+.
14
+ #
15
+ # Rails.application.config.active_job.queue_adapter = :active_elastic_job
16
+ class ActiveElasticJobAdapter
17
+ class << self
18
+ def enqueue(job) #:nodoc:
19
+ enqueue_at(job, Time.now)
20
+ end
21
+
22
+ def enqueue_at(job, timestamp) #:nodoc:
23
+ queue_url = aws_sqs_client.create_queue(queue_name: job.queue_name.to_s).queue_url
24
+ message_body = JSON.dump(job.serialize)
25
+ delay = (timestamp - Time.current.to_f).to_i + 1
26
+ if delay > 15.minutes
27
+ msg =<<-MSG
28
+ Jobs cannot be scheduled more than 15 minutes into the future.
29
+ See http://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html
30
+ for further details!
31
+ MSG
32
+ raise RangeError, 'The maximum allowed delay is 15 minutes' if delay > 15.minutes
33
+ end
34
+
35
+ aws_sqs_client.send_message(
36
+ queue_url: queue_url,
37
+ message_body: message_body,
38
+ delay_seconds: delay,
39
+ message_attributes: {
40
+ "message_digest" => {
41
+ string_value: message_digest(message_body),
42
+ data_type: "String"
43
+ }
44
+ }
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ def aws_sqs_client
51
+ Aws::SQS::Client.new(
52
+ access_key_id: ENV['AWS_ACCESS_KEY_ID'],
53
+ secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
54
+ region: ENV['AWS_REGION']
55
+ )
56
+ end
57
+
58
+ def message_digest(messsage_body)
59
+ secret_key_base = Rails.application.secrets[:secret_key_base]
60
+ verifier = ActiveElasticJob::MessageVerifier.new(secret_key_base)
61
+ verifier.generate_digest(messsage_body)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_elastic_job
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Tawan Sierek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.4'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dotenv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: fuubar
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '4.2'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '4.2'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rdoc
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: aws-sdk
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '2'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '2'
111
+ description: Active Elastic Job is a simple to use Active Job backend for Rails applications
112
+ deployed on the Amazon Elastic Beanstalk platform.
113
+ email:
114
+ - tawan@sierek.com
115
+ executables: []
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - active-elastic-job.gemspec
120
+ - lib/active_elastic_job.rb
121
+ - lib/active_elastic_job/message_verifier.rb
122
+ - lib/active_elastic_job/rack/sqs_message_consumer.rb
123
+ - lib/active_elastic_job/railtie.rb
124
+ - lib/active_elastic_job/version.rb
125
+ - lib/active_job/queue_adapters/active_elastic_job_adapter.rb
126
+ homepage: https://github.com/tawan/active-elastic-job
127
+ licenses:
128
+ - MIT
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 1.9.3
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubyforge_project:
146
+ rubygems_version: 2.4.5.1
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Active Elastic Job is a simple to use Active Job backend for Rails applications
150
+ deployed on the Amazon Elastic Beanstalk platform.
151
+ test_files: []