shoryuken 2.0.11 → 2.1.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
  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