active_elastic_job 1.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 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: []