shoryuken 2.0.11 → 2.1.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
  SHA1:
3
- metadata.gz: 9859d893c7542fbdac0224f68e55a8b49f0fd984
4
- data.tar.gz: d3e5419d74c6766c95c4b8ad20333bdced3cfd57
3
+ metadata.gz: 2708ff803215783c123f043eb85cd5ee2581dc63
4
+ data.tar.gz: ac1a9753af6e5b482624c37cfe8f3df6af27ae06
5
5
  SHA512:
6
- metadata.gz: cb5725e4def20f4c82a785db1c8a055ba59892979608ce95855888bfbd321c5310f491284a2d6ef319e7bc1b3c1f186d91e376cc6b35faef600c26d4fa453325
7
- data.tar.gz: 15274a15cd3dc7f067d4ae54a40921b90b703e09e2002ed32049ce41f6dd1261629de3b3fa842656d574455f01b13e4558536997c93d4f5ebd05880f1c80b9e2
6
+ metadata.gz: 8f5e83670ab44a2eb84010d1187fcd2dfa699cf6d8ad944ff1e03a36574bd5f3508169799799ee03eec0b00c4e075e664d17334542e842ca5e7d9a46e09a2a81
7
+ data.tar.gz: 613cec158423dde7e53518a25d02a1418a4e7fc0b4e73b94fe70b9450f03e01364986bc5df5f347a782d2d2034dd3a800eece93bd806246fd59df81158c185c0
data/.codeclimate.yml ADDED
@@ -0,0 +1,23 @@
1
+ ---
2
+ engines:
3
+ brakeman:
4
+ enabled: true
5
+ bundler-audit:
6
+ enabled: true
7
+ duplication:
8
+ enabled: true
9
+ config:
10
+ languages:
11
+ - ruby
12
+ fixme:
13
+ enabled: true
14
+ reek:
15
+ enabled: true
16
+ rubocop:
17
+ enabled: true
18
+ ratings:
19
+ paths:
20
+ - "**.rb"
21
+ # exclude_paths:
22
+ # - "**/vendor/**/*"
23
+ # - "*/spec/**/*"
data/.travis.yml CHANGED
@@ -1,13 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- # - 1.9.3
4
- # - 1.9.2
5
3
  - 2.0.0
6
4
  - 2.1.0
7
5
  - 2.2.0
8
- # - ruby-head
9
- # - jruby-19mode
10
- # - jruby-head
11
6
 
12
7
  notifications:
13
8
  email:
@@ -17,3 +12,5 @@ notifications:
17
12
  script: SPEC_ALL=true bundle exec rspec spec
18
13
  before_install:
19
14
  - gem update bundler
15
+ after_success:
16
+ - bundle exec codeclimate-test-reporter
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ ## [v2.0.11] - 2016-07-02
2
+
3
+ - Same as 2.0.10. Unfortunately 2.0.10 was removed `yanked` by mistake from RubyGems.
4
+ - [#b255bc3] https://github.com/phstc/shoryuken/commit/b255bc3
5
+
1
6
  ## [v2.0.10] - 2016-06-09
2
7
 
3
8
  - Fix manager #225
data/Gemfile CHANGED
@@ -5,5 +5,6 @@ gemspec
5
5
 
6
6
  group :test do
7
7
  gem 'codeclimate-test-reporter', require: nil
8
+ gem 'simplecov'
8
9
  gem 'multi_xml'
9
10
  end
File without changes
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ end
10
10
  desc 'Open Shoryuken pry console'
11
11
  task :console do
12
12
  require 'pry'
13
- require 'celluloid'
13
+ require 'celluloid/current'
14
14
  require 'shoryuken'
15
15
 
16
16
  config_file = File.join File.expand_path('..', __FILE__), 'shoryuken.yml'
@@ -4,17 +4,6 @@ class DefaultWorker
4
4
  shoryuken_options queue: 'default', auto_delete: true
5
5
 
6
6
  def perform(sqs_msg, body)
7
- puts "DefaultWorker: '#{body}'"
8
- end
9
- end
10
-
11
- # multiple workers for the same queue
12
- class DefaultWorker2
13
- include Shoryuken::Worker
14
-
15
- shoryuken_options queue: 'default', auto_delete: true
16
-
17
- def perform(sqs_msg, body)
18
- puts "DefaultWorker2: '#{body}'"
7
+ Shoryuken.logger.info("Received message: '#{body}'")
19
8
  end
20
9
  end
data/lib/shoryuken.rb CHANGED
@@ -6,6 +6,7 @@ require 'shoryuken/version'
6
6
  require 'shoryuken/core_ext'
7
7
  require 'shoryuken/util'
8
8
  require 'shoryuken/logging'
9
+ require 'shoryuken/aws_config'
9
10
  require 'shoryuken/environment_loader'
10
11
  require 'shoryuken/queue'
11
12
  require 'shoryuken/message'
@@ -72,6 +73,12 @@ module Shoryuken
72
73
  @@active_job_queue_name_prefixing = prefixing
73
74
  end
74
75
 
76
+ ##
77
+ # Configuration for Shoryuken server, use like:
78
+ #
79
+ # Shoryuken.configure_server do |config|
80
+ # config.aws = { :sqs_endpoint => '...', :access_key_id: '...', :secret_access_key: '...', region: '...' }
81
+ # end
75
82
  def configure_server
76
83
  yield self if server?
77
84
  end
@@ -82,8 +89,14 @@ module Shoryuken
82
89
  @server_chain
83
90
  end
84
91
 
92
+ ##
93
+ # Configuration for Shoryuken client, use like:
94
+ #
95
+ # Shoryuken.configure_client do |config|
96
+ # config.aws = { :sqs_endpoint => '...', :access_key_id: '...', :secret_access_key: '...', region: '...' }
97
+ # end
85
98
  def configure_client
86
- yield self
99
+ yield self unless server?
87
100
  end
88
101
 
89
102
  def client_middleware
@@ -118,6 +131,10 @@ module Shoryuken
118
131
  @stop_callback = block
119
132
  end
120
133
 
134
+ def aws=(hash)
135
+ Shoryuken::AwsConfig.setup(hash)
136
+ end
137
+
121
138
  # Register a block to run at a point in the Shoryuken lifecycle.
122
139
  # :startup, :quiet or :shutdown are valid events.
123
140
  #
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+ module Shoryuken
3
+ class AwsConfig
4
+ class << self
5
+ attr_writer :options
6
+
7
+ def options
8
+ @options ||= {}
9
+ end
10
+
11
+ def setup(hash)
12
+ # aws-sdk tries to load the credentials from the ENV variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
13
+ # when not explicit supplied
14
+ return if hash.empty?
15
+
16
+ self.options = hash
17
+
18
+ shoryuken_keys = %w(
19
+ account_id
20
+ sns_endpoint
21
+ sqs_endpoint
22
+ receive_message
23
+ ).map(&:to_sym)
24
+
25
+ @aws_options = hash.reject do |k, _|
26
+ shoryuken_keys.include?(k)
27
+ end
28
+
29
+ # assume credentials based authentication
30
+ credentials = Aws::Credentials.new(
31
+ @aws_options.delete(:access_key_id),
32
+ @aws_options.delete(:secret_access_key)
33
+ )
34
+
35
+ # but only if the configuration options have valid values
36
+ @aws_options.merge!(credentials: credentials) if credentials.set?
37
+
38
+ if (callback = Shoryuken.aws_initialization_callback)
39
+ Shoryuken.logger.info { 'Calling Shoryuken.on_aws_initialization block' }
40
+ callback.call(@aws_options)
41
+ end
42
+ end
43
+
44
+ def sns
45
+ Aws::SNS::Client.new(aws_client_options(:sns_endpoint))
46
+ end
47
+
48
+ def sqs
49
+ Aws::SQS::Client.new(aws_client_options(:sqs_endpoint))
50
+ end
51
+
52
+ private
53
+
54
+ def aws_client_options(service_endpoint_key)
55
+ environment_endpoint = ENV["AWS_#{service_endpoint_key.to_s.upcase}"]
56
+ explicit_endpoint = options[service_endpoint_key] || environment_endpoint
57
+ endpoint = {}.tap do |hash|
58
+ hash[:endpoint] = explicit_endpoint unless explicit_endpoint.to_s.empty?
59
+ end
60
+ @aws_options.to_h.merge(endpoint)
61
+ end
62
+ end
63
+ end
64
+ end
data/lib/shoryuken/cli.rb CHANGED
@@ -31,10 +31,15 @@ module Shoryuken
31
31
 
32
32
  options = parse_cli_args(args)
33
33
 
34
- daemonize(options)
35
- write_pid(options)
34
+ loader = EnvironmentLoader.setup_options(options)
36
35
 
37
- EnvironmentLoader.load(options)
36
+ # When cli args exist, override options in config file
37
+ Shoryuken.options.merge!(options)
38
+
39
+ daemonize(Shoryuken.options)
40
+ write_pid(Shoryuken.options)
41
+
42
+ loader.load
38
43
 
39
44
  load_celluloid
40
45
 
@@ -64,7 +69,7 @@ module Shoryuken
64
69
  private
65
70
 
66
71
  def load_celluloid
67
- require 'celluloid/autostart'
72
+ require 'celluloid/current'
68
73
  Celluloid.logger = (Shoryuken.options[:verbose] ? Shoryuken.logger : nil)
69
74
 
70
75
  require 'shoryuken/manager'
@@ -9,7 +9,7 @@ module Shoryuken
9
9
  end
10
10
 
11
11
  def sns
12
- @sns ||= Aws::SNS::Client.new(aws_client_options(:sns_endpoint))
12
+ @sns ||= Shoryuken::AwsConfig.sns
13
13
  end
14
14
 
15
15
  def sns_arn
@@ -17,7 +17,7 @@ module Shoryuken
17
17
  end
18
18
 
19
19
  def sqs
20
- @sqs ||= Aws::SQS::Client.new(aws_client_options(:sqs_endpoint))
20
+ @sqs ||= Shoryuken::AwsConfig.sqs
21
21
  end
22
22
 
23
23
  def topics(name)
@@ -26,16 +26,6 @@ module Shoryuken
26
26
 
27
27
  attr_accessor :account_id
28
28
  attr_writer :sns, :sqs, :sqs_resource, :sns_arn
29
-
30
- private
31
-
32
- def aws_client_options(service_endpoint_key)
33
- environment_endpoint = ENV["AWS_#{service_endpoint_key.to_s.upcase}"]
34
- explicit_endpoint = Shoryuken.options[:aws][service_endpoint_key] || environment_endpoint
35
- options = {}
36
- options[:endpoint] = explicit_endpoint unless explicit_endpoint.to_s.empty?
37
- options
38
- end
39
29
  end
40
30
  end
41
31
  end
@@ -2,23 +2,29 @@ module Shoryuken
2
2
  class EnvironmentLoader
3
3
  attr_reader :options
4
4
 
5
- def self.load(options)
6
- new(options).load
5
+ def self.setup_options(options)
6
+ instance = new(options)
7
+ instance.setup_options
8
+ instance
7
9
  end
8
10
 
9
11
  def self.load_for_rails_console
10
- load(config_file: (Rails.root + 'config' + 'shoryuken.yml'))
12
+ instance = setup_options(config_file: (Rails.root + 'config' + 'shoryuken.yml'))
13
+ instance.load
11
14
  end
12
15
 
13
16
  def initialize(options)
14
17
  @options = options
15
18
  end
16
19
 
17
- def load
18
- load_rails if options[:rails]
20
+ def setup_options
19
21
  initialize_options
20
22
  initialize_logger
21
23
  merge_cli_defined_queues
24
+ end
25
+
26
+ def load
27
+ load_rails if options[:rails]
22
28
  prefix_active_job_queue_names
23
29
  parse_queues
24
30
  require_workers
@@ -43,35 +49,12 @@ module Shoryuken
43
49
  YAML.load(ERB.new(IO.read(path)).result).deep_symbolize_keys
44
50
  end
45
51
 
52
+ # DEPRECATED: Please use configure_server and configure_client in
53
+ # https://github.com/phstc/shoryuken/blob/a81637d577b36c5cf245882733ea91a335b6602f/lib/shoryuken.rb#L82
54
+ # Please delete this method afert next release (v2.0.12 or later)
46
55
  def initialize_aws
47
- # aws-sdk tries to load the credentials from the ENV variables: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
48
- # when not explicit supplied
49
- return if Shoryuken.options[:aws].empty?
50
-
51
- shoryuken_keys = %w(
52
- account_id
53
- sns_endpoint
54
- sqs_endpoint
55
- receive_message).map(&:to_sym)
56
-
57
- aws_options = Shoryuken.options[:aws].reject do |k, v|
58
- shoryuken_keys.include?(k)
59
- end
60
-
61
- # assume credentials based authentication
62
- credentials = Aws::Credentials.new(
63
- aws_options.delete(:access_key_id),
64
- aws_options.delete(:secret_access_key))
65
-
66
- # but only if the configuration options have valid values
67
- aws_options = aws_options.merge(credentials: credentials) if credentials.set?
68
-
69
- if (callback = Shoryuken.aws_initialization_callback)
70
- Shoryuken.logger.info { 'Calling Shoryuken.on_aws_initialization block' }
71
- callback.call(aws_options)
72
- end
73
-
74
- Aws.config = aws_options
56
+ Shoryuken.logger.warn { "[DEPRECATION] aws in shoryuken.yml is deprecated. Please use configure_server and configure_client in your initializer"} unless Shoryuken.options[:aws].nil?
57
+ Shoryuken::AwsConfig.setup(Shoryuken.options[:aws])
75
58
  end
76
59
 
77
60
  def initialize_logger
@@ -153,7 +136,15 @@ module Shoryuken
153
136
  end
154
137
 
155
138
  def require_workers
156
- require Shoryuken.options[:require] if Shoryuken.options[:require]
139
+ required = Shoryuken.options[:require]
140
+
141
+ return unless required
142
+
143
+ if File.directory?(required)
144
+ Dir[File.join(required, '**', '*.rb')].each(&method(:require))
145
+ else
146
+ require required
147
+ end
157
148
  end
158
149
 
159
150
  def validate_queues
@@ -13,7 +13,7 @@ module Shoryuken
13
13
  # AWS limits the batch size by 10
14
14
  limit = limit > FETCH_LIMIT ? FETCH_LIMIT : limit
15
15
 
16
- options = (Shoryuken.options[:aws][:receive_message] || {}).dup
16
+ options = (Shoryuken::AwsConfig.options[:receive_message] || {}).dup
17
17
  options[:max_number_of_messages] = limit
18
18
  options[:message_attribute_names] = %w(All)
19
19
  options[:attribute_names] = %w(All)
@@ -32,7 +32,7 @@ module Shoryuken
32
32
  limit = batch ? FETCH_LIMIT : available_processors
33
33
 
34
34
  if (sqs_msgs = Array(receive_messages(queue, limit))).any?
35
- logger.info { "Found #{sqs_msgs.size} messages for '#{queue}'" }
35
+ logger.debug { "Found #{sqs_msgs.size} messages for '#{queue}'" }
36
36
 
37
37
  if batch
38
38
  @manager.async.assign(queue, patch_sqs_msgs!(sqs_msgs))
@@ -36,7 +36,7 @@ module Shoryuken
36
36
 
37
37
  def actor_died(actor, reason)
38
38
  return if @done
39
- logger.warn { 'Shoryuken died due to the following error, cannot recover, process exiting' }
39
+ logger.warn { "Shoryuken died due to the following error, cannot recover, process exiting: #{reason}" }
40
40
  exit 1
41
41
  end
42
42
  end
@@ -77,7 +77,7 @@ module Shoryuken
77
77
 
78
78
  def processor_died(processor, reason)
79
79
  watchdog("Manager#processor_died died") do
80
- logger.error { "Process died, reason: #{reason}" unless reason.to_s.empty? }
80
+ logger.error { "Process died, reason: #{reason}" }
81
81
 
82
82
  @threads.delete(processor.object_id)
83
83
  @busy.delete processor
@@ -134,8 +134,7 @@ module Shoryuken
134
134
  if @ready.empty?
135
135
  logger.debug { 'Pausing fetcher, because all processors are busy' }
136
136
 
137
- after(1) { dispatch }
138
-
137
+ dispatch_later
139
138
  return
140
139
  end
141
140
 
@@ -154,6 +153,13 @@ module Shoryuken
154
153
 
155
154
  private
156
155
 
156
+ def dispatch_later
157
+ @_dispatch_timer ||= after(1) do
158
+ @_dispatch_timer = nil
159
+ dispatch
160
+ end
161
+ end
162
+
157
163
  def build_processor
158
164
  processor = Processor.new_link(current_actor)
159
165
  processor.proxy_id = processor.object_id
@@ -196,7 +202,7 @@ module Shoryuken
196
202
  # get/remove the first queue in the list
197
203
  queue = @queues.shift
198
204
 
199
- unless defined?(::ActiveJob) || !Shoryuken.worker_registry.workers(queue).empty?
205
+ unless defined?(::ActiveJob) || !Shoryuken.worker_registry.workers(queue).empty?
200
206
  # when no worker registered pause the queue to avoid endless recursion
201
207
  logger.debug { "Pausing '#{queue}' for #{Shoryuken.options[:delay].to_f} seconds, because no workers registered" }
202
208
 
@@ -1,4 +1,4 @@
1
- require 'celluloid' unless defined?(Celluloid)
1
+ require 'celluloid/current' unless defined?(Celluloid)
2
2
 
3
3
  module Shoryuken
4
4
  module Middleware
@@ -7,11 +7,14 @@ module Shoryuken
7
7
  EXTEND_UPFRONT_SECONDS = 5
8
8
 
9
9
  def call(worker, queue, sqs_msg, body)
10
- timer = auto_visibility_timer(queue, sqs_msg, worker.class)
10
+ timer = auto_visibility_timer(worker, queue, sqs_msg, body)
11
11
  begin
12
12
  yield
13
13
  ensure
14
- timer.cancel if timer
14
+ if timer
15
+ timer.cancel
16
+ @visibility_extender.terminate
17
+ end
15
18
  end
16
19
  end
17
20
 
@@ -21,31 +24,32 @@ module Shoryuken
21
24
  include Celluloid
22
25
  include Util
23
26
 
24
- def auto_extend(queue, sqs_msg, worker_class)
27
+ def auto_extend(worker, queue, sqs_msg, body)
25
28
  queue_visibility_timeout = Shoryuken::Client.queues(queue).visibility_timeout
26
29
 
27
30
  every(queue_visibility_timeout - EXTEND_UPFRONT_SECONDS) do
28
31
  begin
29
32
  logger.debug do
30
- "Extending message #{worker_name(worker_class, sqs_msg)}/#{queue}/#{sqs_msg.message_id} " \
31
- "visibility timeout by #{queue_visibility_timeout}s."
33
+ "Extending message #{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
34
+ "visibility timeout by #{queue_visibility_timeout}s."
32
35
  end
33
36
 
34
37
  sqs_msg.change_visibility(visibility_timeout: queue_visibility_timeout)
35
38
  rescue => e
36
39
  logger.error do
37
- "Could not auto extend the message #{worker_class}/#{queue}/#{sqs_msg.message_id} " \
38
- "visibility timeout. Error: #{e.message}"
40
+ 'Could not auto extend the message ' \
41
+ "#{worker_name(worker.class, sqs_msg, body)}/#{queue}/#{sqs_msg.message_id} " \
42
+ "visibility timeout. Error: #{e.message}"
39
43
  end
40
44
  end
41
45
  end
42
46
  end
43
47
  end
44
48
 
45
- def auto_visibility_timer(queue, sqs_msg, worker_class)
46
- return unless worker_class.auto_visibility_timeout?
47
- @visibility_extender ||= MessageVisibilityExtender.new_link
48
- @visibility_extender.auto_extend(queue, sqs_msg, worker_class)
49
+ def auto_visibility_timer(worker, queue, sqs_msg, body)
50
+ return unless worker.class.auto_visibility_timeout?
51
+ @visibility_extender = MessageVisibilityExtender.new_link
52
+ @visibility_extender.auto_extend(worker, queue, sqs_msg, body)
49
53
  end
50
54
  end
51
55
  end
@@ -20,9 +20,7 @@ module Shoryuken
20
20
  private
21
21
 
22
22
  def handle_failure(sqs_msg, started_at, retry_intervals)
23
- attempts = sqs_msg.attributes['ApproximateReceiveCount']
24
-
25
- return unless attempts
23
+ return unless attempts = sqs_msg.attributes['ApproximateReceiveCount']
26
24
 
27
25
  attempts = attempts.to_i - 1
28
26
 
@@ -1,22 +1,21 @@
1
1
  module Shoryuken
2
2
  class Queue
3
+ FIFO_ATTR = 'FifoQueue'
4
+ MESSAGE_GROUP_ID = 'ShoryukenMessage'
5
+ VISIBILITY_TIMEOUT_ATTR = 'VisibilityTimeout'
6
+
3
7
  attr_accessor :name, :client, :url
4
8
 
5
9
  def initialize(client, name)
6
10
  self.name = name
7
11
  self.client = client
8
- begin
9
- self.url = client.get_queue_url(queue_name: name).queue_url
10
- rescue Aws::SQS::Errors::NonExistentQueue => e
11
- raise e, "The specified queue '#{name}' does not exist"
12
- end
12
+ self.url = client.get_queue_url(queue_name: name).queue_url
13
+ rescue Aws::SQS::Errors::NonExistentQueue => e
14
+ raise e, "The specified queue '#{name}' does not exist."
13
15
  end
14
16
 
15
17
  def visibility_timeout
16
- client.get_queue_attributes(
17
- queue_url: url,
18
- attribute_names: ['VisibilityTimeout']
19
- ).attributes['VisibilityTimeout'].to_i
18
+ queue_attributes.attributes[VISIBILITY_TIMEOUT_ATTR].to_i
20
19
  end
21
20
 
22
21
  def delete_messages(options)
@@ -41,49 +40,50 @@ module Shoryuken
41
40
  map { |m| Message.new(client, self, m) }
42
41
  end
43
42
 
43
+ def fifo?
44
+ @_fifo ||= queue_attributes.attributes[FIFO_ATTR] == 'true'
45
+ end
46
+
44
47
  private
45
48
 
49
+ def queue_attributes
50
+ # Note: Retrieving all queue attributes as requesting `FifoQueue` on non-FIFO queue raises error.
51
+ # See issue: https://github.com/aws/aws-sdk-ruby/issues/1350
52
+ client.get_queue_attributes(queue_url: url, attribute_names: ['All'])
53
+ end
54
+
46
55
  def sanitize_messages!(options)
47
- options = case
48
- when options.is_a?(Array)
49
- { entries: options.map.with_index do |m, index|
50
- { id: index.to_s }.merge(m.is_a?(Hash) ? m : { message_body: m })
51
- end }
52
- when options.is_a?(Hash)
53
- options
54
- end
56
+ if options.is_a?(Array)
57
+ entries = options.map.with_index do |m, index|
58
+ { id: index.to_s }.merge(m.is_a?(Hash) ? m : { message_body: m })
59
+ end
55
60
 
56
- validate_messages!(options)
61
+ options = { entries: entries }
62
+ end
63
+
64
+ options[:entries].each(&method(:sanitize_message!))
57
65
 
58
66
  options
59
67
  end
60
68
 
61
- def sanitize_message!(options)
62
- options = case
63
- when options.is_a?(String)
64
- # send_message('message')
65
- { message_body: options }
66
- when options.is_a?(Hash)
67
- options
68
- end
69
+ def add_fifo_attributes!(options)
70
+ return unless fifo?
69
71
 
70
- validate_message!(options)
72
+ options[:message_group_id] ||= MESSAGE_GROUP_ID
73
+ options[:message_deduplication_id] ||= Digest::SHA256.hexdigest(options[:message_body].to_s)
71
74
 
72
75
  options
73
76
  end
74
77
 
75
- def validate_messages!(options)
76
- options[:entries].map { |m| validate_message!(m) }
77
- end
78
+ def sanitize_message!(options)
79
+ options = { message_body: options } if options.is_a?(String)
78
80
 
79
- def validate_message!(options)
80
- body = options[:message_body]
81
- if body.is_a?(Hash)
81
+ if (body = options[:message_body]).is_a?(Hash)
82
82
  options[:message_body] = JSON.dump(body)
83
- elsif !body.is_a?(String)
84
- fail ArgumentError, "The message body must be a String and you passed a #{body.class}"
85
83
  end
86
84
 
85
+ add_fifo_attributes!(options)
86
+
87
87
  options
88
88
  end
89
89
  end
@@ -42,7 +42,9 @@ module Shoryuken
42
42
  && !sqs_msg.is_a?(Array) \
43
43
  && sqs_msg.message_attributes \
44
44
  && sqs_msg.message_attributes['shoryuken_class'] \
45
- && sqs_msg.message_attributes['shoryuken_class'][:string_value] == ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper.to_s
45
+ && sqs_msg.message_attributes['shoryuken_class'][:string_value] \
46
+ == ActiveJob::QueueAdapters::ShoryukenAdapter::JobWrapper.to_s \
47
+ && body
46
48
 
47
49
  "ActiveJob/#{body['job_class']}"
48
50
  else
@@ -1,3 +1,3 @@
1
1
  module Shoryuken
2
- VERSION = '2.0.11'
2
+ VERSION = '2.1.0'
3
3
  end
@@ -5,7 +5,7 @@ require 'shoryuken/launcher'
5
5
  RSpec.describe Shoryuken::CLI do
6
6
  let(:cli) { Shoryuken::CLI.instance }
7
7
 
8
- before(:each) do
8
+ before do
9
9
  # make sure we do not bail
10
10
  allow(cli).to receive(:exit)
11
11
 
@@ -32,7 +32,7 @@ RSpec.describe Shoryuken::CLI do
32
32
  cli.run(['--daemon', '--logfile', '/dev/null'])
33
33
  end
34
34
 
35
- it 'does NOT daemonize with --daemon --logfile' do
35
+ it 'does NOT daemonize with --logfile' do
36
36
  expect(Process).to_not receive(:daemon)
37
37
  cli.run(['--logfile', '/dev/null'])
38
38
  end
@@ -26,6 +26,7 @@ describe Shoryuken::Client do
26
26
  ENV['AWS_SNS_ENDPOINT'] = sns_endpoint
27
27
  ENV['AWS_REGION'] = 'us-east-1'
28
28
  Shoryuken.options[:aws] = {}
29
+ Shoryuken::AwsConfig.options = {}
29
30
  end
30
31
 
31
32
  it 'will use config file settings if set' do
@@ -57,6 +58,7 @@ describe Shoryuken::Client do
57
58
 
58
59
  def load_config_file_by_file_name(file_name)
59
60
  path_name = file_name ? File.join(File.expand_path('../../..', __FILE__), 'spec', file_name) : nil
60
- Shoryuken::EnvironmentLoader.load(config_file: path_name)
61
+ loader = Shoryuken::EnvironmentLoader.setup_options(config_file: path_name)
62
+ loader.load
61
63
  end
62
64
  end
@@ -2,13 +2,21 @@ require 'spec_helper'
2
2
 
3
3
  describe Shoryuken::Queue do
4
4
  let(:credentials) { Aws::Credentials.new('access_key_id', 'secret_access_key') }
5
- let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
6
- let(:queue_name) { 'shoryuken' }
7
- let(:queue_url) { 'https://eu-west-1.amazonaws.com:6059/123456789012/shoryuken' }
5
+ let(:sqs) { Aws::SQS::Client.new(stub_responses: true, credentials: credentials) }
6
+ let(:queue_name) { 'shoryuken' }
7
+ let(:queue_url) { 'https://eu-west-1.amazonaws.com:6059/123456789012/shoryuken' }
8
8
 
9
9
  subject { described_class.new(sqs, queue_name) }
10
+ before {
11
+ # Required as Aws::SQS::Client.get_queue_url returns 'String' when responses are stubbed,
12
+ # which is not accepted by Aws::SQS::Client.get_queue_attributes for :queue_name parameter.
13
+ allow(subject).to receive(:url).and_return(queue_url)
14
+ }
10
15
 
11
16
  describe '#send_message' do
17
+ before {
18
+ allow(subject).to receive(:fifo?).and_return(false)
19
+ }
12
20
  it 'accepts SQS request parameters' do
13
21
  # https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message-instance_method
14
22
  expect(sqs).to receive(:send_message).with(hash_including(message_body: 'msg1'))
@@ -22,95 +30,134 @@ describe Shoryuken::Queue do
22
30
  subject.send_message('msg1')
23
31
  end
24
32
 
25
- context 'when body is invalid' do
26
- it 'raises ArgumentError for nil' do
27
- expect {
28
- subject.send_message(message_body: nil)
29
- }.to raise_error(ArgumentError, 'The message body must be a String and you passed a NilClass')
30
- end
31
-
32
- it 'raises ArgumentError for Fixnum' do
33
- expect {
34
- subject.send_message(message_body: 1)
35
- }.to raise_error(ArgumentError, 'The message body must be a String and you passed a Fixnum')
36
- end
33
+ context 'when a client middleware' do
34
+ class MyClientMiddleware
35
+ def call(options)
36
+ options[:message_body] = 'changed'
37
37
 
38
- context 'when a client middleware' do
39
- class MyClientMiddleware
40
- def call(options)
41
- options[:message_body] = 'changed'
42
-
43
- yield
44
- end
38
+ yield
45
39
  end
40
+ end
46
41
 
47
- before do
48
- Shoryuken.configure_client do |config|
49
- config.client_middleware do |chain|
50
- chain.add MyClientMiddleware
51
- end
42
+ before do
43
+ allow(Shoryuken).to receive(:server?).and_return(false)
44
+ Shoryuken.configure_client do |config|
45
+ config.client_middleware do |chain|
46
+ chain.add MyClientMiddleware
52
47
  end
53
48
  end
49
+ end
54
50
 
55
- after do
56
- Shoryuken.configure_client do |config|
57
- config.client_middleware do |chain|
58
- chain.remove MyClientMiddleware
59
- end
51
+ after do
52
+ Shoryuken.configure_client do |config|
53
+ config.client_middleware do |chain|
54
+ chain.remove MyClientMiddleware
60
55
  end
61
56
  end
57
+ end
62
58
 
63
- it 'invokes MyClientMiddleware' do
64
- expect(sqs).to receive(:send_message).with(hash_including(message_body: 'changed'))
59
+ it 'invokes MyClientMiddleware' do
60
+ expect(sqs).to receive(:send_message).with(hash_including(message_body: 'changed'))
65
61
 
66
- subject.send_message(message_body: 'original')
67
- end
62
+ subject.send_message(message_body: 'original')
68
63
  end
69
64
  end
70
65
  end
71
66
 
72
67
  describe '#send_messages' do
68
+ before {
69
+ allow(subject).to receive(:fifo?).and_return(false)
70
+ }
73
71
  it 'accepts SQS request parameters' do
74
72
  # https://docs.aws.amazon.com/sdkforruby/api/Aws/SQS/Client.html#send_message_batch-instance_method
75
- expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1'}, { id: '1', message_body: 'msg2' }]))
73
+ expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{id: '0', message_body: 'msg1'}, {id: '1', message_body: 'msg2'}]))
76
74
 
77
- subject.send_messages(entries: [{ id: '0', message_body: 'msg1'}, { id: '1', message_body: 'msg2' }])
75
+ subject.send_messages(entries: [{id: '0', message_body: 'msg1'}, {id: '1', message_body: 'msg2'}])
78
76
  end
79
77
 
80
78
  it 'accepts an array of messages' do
81
- expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1', delay_seconds: 1, message_attributes: { attr: 'attr1' } }, { id: '1', message_body: 'msg2', delay_seconds: 1, message_attributes: { attr: 'attr2' } }]))
82
-
83
- subject.send_messages([
84
- {
85
- message_body: 'msg1',
86
- delay_seconds: 1,
87
- message_attributes: { attr: 'attr1' }
88
- }, {
89
- message_body: 'msg2',
90
- delay_seconds: 1,
91
- message_attributes: { attr: 'attr2' }
92
- }
93
- ])
79
+ options = { entries: [{ id: '0',
80
+ message_body: 'msg1',
81
+ delay_seconds: 1,
82
+ message_attributes: { attr: 'attr1' } },
83
+ { id: '1',
84
+ message_body: 'msg2',
85
+ delay_seconds: 1,
86
+ message_attributes: { attr: 'attr2' } }] }
87
+
88
+ expect(sqs).to receive(:send_message_batch).with(hash_including(options))
89
+
90
+ subject.send_messages([{ message_body: 'msg1',
91
+ delay_seconds: 1,
92
+ message_attributes: { attr: 'attr1' }
93
+ }, {
94
+ message_body: 'msg2',
95
+ delay_seconds: 1,
96
+ message_attributes: { attr: 'attr2' }
97
+ }])
98
+ end
99
+
100
+ context 'when FIFO' do
101
+ before do
102
+ allow(subject).to receive(:fifo?).and_return(true)
103
+ end
104
+
105
+ context 'and message_group_id and message_deduplication_id are absent' do
106
+ it 'sets default values' do
107
+ expect(sqs).to receive(:send_message_batch) do |arg|
108
+ first_entry = arg[:entries].first
109
+
110
+ expect(first_entry[:message_group_id]).to eq described_class::MESSAGE_GROUP_ID
111
+ expect(first_entry[:message_deduplication_id]).to be
112
+ end
113
+
114
+ subject.send_messages([{ message_body: 'msg1', message_attributes: { attr: 'attr1' } }])
115
+ end
116
+ end
117
+
118
+ context 'and message_group_id and message_deduplication_id are present' do
119
+ it 'preserves existing values' do
120
+ expect(sqs).to receive(:send_message_batch) do |arg|
121
+ first_entry = arg[:entries].first
122
+
123
+ expect(first_entry[:message_group_id]).to eq 'my group'
124
+ expect(first_entry[:message_deduplication_id]).to eq 'my id'
125
+ end
126
+
127
+ subject.send_messages([{ message_body: 'msg1',
128
+ message_attributes: { attr: 'attr1' },
129
+ message_group_id: 'my group',
130
+ message_deduplication_id: 'my id' }])
131
+ end
132
+ end
94
133
  end
95
134
 
96
135
  it 'accepts an array of string' do
97
- expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1'}, { id: '1', message_body: 'msg2' }]))
136
+ expect(sqs).to receive(:send_message_batch).with(hash_including(entries: [{ id: '0', message_body: 'msg1' }, { id: '1', message_body: 'msg2' }]))
98
137
 
99
138
  subject.send_messages(%w(msg1 msg2))
100
139
  end
140
+ end
141
+
142
+ describe '#fifo?' do
143
+ before do
144
+ attribute_response = double 'Aws::SQS::Types::GetQueueAttributesResponse'
145
+
146
+ allow(attribute_response).to receive(:attributes).and_return('FifoQueue' => fifo.to_s, 'ContentBasedDeduplication' => 'true')
147
+ allow(subject).to receive(:url).and_return(queue_url)
148
+ allow(sqs).to receive(:get_queue_attributes).with(queue_url: queue_url, attribute_names: ['All']).and_return(attribute_response)
149
+ end
101
150
 
102
- context 'when body is invalid' do
103
- it 'raises ArgumentError for nil' do
104
- expect {
105
- subject.send_messages(entries: [message_body: nil])
106
- }.to raise_error(ArgumentError, 'The message body must be a String and you passed a NilClass')
107
- end
151
+ context 'when queue is FIFO' do
152
+ let(:fifo) { true }
108
153
 
109
- it 'raises ArgumentError for Fixnum' do
110
- expect {
111
- subject.send_messages(entries: [message_body: 1])
112
- }.to raise_error(ArgumentError, 'The message body must be a String and you passed a Fixnum')
113
- end
154
+ it { expect(subject.fifo?).to be }
155
+ end
156
+
157
+ context 'when queue is not FIFO' do
158
+ let(:fifo) { false }
159
+
160
+ it { expect(subject.fifo?).to_not be }
114
161
  end
115
162
  end
116
163
  end
data/spec/spec_helper.rb CHANGED
@@ -2,7 +2,7 @@ require 'bundler/setup'
2
2
  Bundler.setup
3
3
 
4
4
  require 'pry-byebug'
5
- require 'celluloid'
5
+ require 'celluloid/current'
6
6
  require 'shoryuken'
7
7
  require 'json'
8
8
  require 'multi_xml'
@@ -10,13 +10,13 @@ require 'dotenv'
10
10
  Dotenv.load
11
11
 
12
12
  if ENV['CODECLIMATE_REPO_TOKEN']
13
- require 'codeclimate-test-reporter'
14
- CodeClimate::TestReporter.start
13
+ require 'simplecov'
14
+ SimpleCov.start
15
15
  end
16
16
 
17
17
  config_file = File.join(File.expand_path('../..', __FILE__), 'spec', 'shoryuken.yml')
18
18
 
19
- Shoryuken::EnvironmentLoader.load(config_file: config_file)
19
+ Shoryuken::EnvironmentLoader.setup_options(config_file: config_file)
20
20
 
21
21
  Shoryuken.logger.level = Logger::UNKNOWN
22
22
  Celluloid.logger.level = Logger::UNKNOWN
@@ -57,6 +57,9 @@ RSpec.configure do |config|
57
57
  Shoryuken.options[:concurrency] = 1
58
58
  Shoryuken.options[:delay] = 1
59
59
  Shoryuken.options[:timeout] = 1
60
+ Shoryuken.options[:daemon] = nil
61
+ Shoryuken.options[:logfile] = nil
62
+
60
63
  Shoryuken.options[:aws].delete(:receive_message)
61
64
 
62
65
  TestWorker.get_shoryuken_options.clear
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoryuken
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.11
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pablo Cantero
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2016-07-02 00:00:00.000000000 Z
12
+ date: 2016-12-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -132,20 +132,21 @@ executables:
132
132
  extensions: []
133
133
  extra_rdoc_files: []
134
134
  files:
135
+ - ".codeclimate.yml"
135
136
  - ".gitignore"
136
- - ".hound.yml"
137
137
  - ".rspec"
138
138
  - ".rubocop.yml"
139
139
  - ".travis.yml"
140
140
  - CHANGELOG.md
141
141
  - Gemfile
142
- - LICENSE.txt
142
+ - LICENSE
143
143
  - README.md
144
144
  - Rakefile
145
145
  - bin/shoryuken
146
146
  - examples/bootstrap_queues.rb
147
147
  - examples/default_worker.rb
148
148
  - lib/shoryuken.rb
149
+ - lib/shoryuken/aws_config.rb
149
150
  - lib/shoryuken/cli.rb
150
151
  - lib/shoryuken/client.rb
151
152
  - lib/shoryuken/core_ext.rb
data/.hound.yml DELETED
@@ -1,6 +0,0 @@
1
- ruby:
2
- enabled: true
3
- config_file: .rubocop.yml
4
-
5
- java_script:
6
- enabled: true