alephant-publisher-queue 2.3.1 → 2.4.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: 0d60cd615024dd56ba59fa1f6a316209cdc41b33
4
- data.tar.gz: 20c7079768247abb47894bb1c63adde51a68e1ae
3
+ metadata.gz: e90e5d717583700bb627398ffec0f305383ab17b
4
+ data.tar.gz: 7f6fdf2a4159bb0276e512829bb9d8d4788775e5
5
5
  SHA512:
6
- metadata.gz: 53244a4df986d2819a3283af1db929c1437b68e35d79ed3d4743f9414b4ef8b7113ac2784da905e8a75491d58c917149bc12926f86d003bd1ad0cc9011745872
7
- data.tar.gz: db98c4ca0561ac8abc2b35be49592325b95048d65a7636968f41153e8f2c75a5476d20864145af27e95c2caf5af9aa071412c6453dcc632bfea650fa3c6d02dc
6
+ metadata.gz: acdc9518ef0f946002f0e6b07a8cb122076e6d9661b37fcee23339bdc221226b01c9d7300d936879f0fd919591701ce8749bcaf451217eb97c0532f6781f4bd6
7
+ data.tar.gz: cf959951bd12ec476daf6e473560b0c73ac52a3d6eb29f9ce21419af704298cae47a861eac42e66a2f6f96776d4e168c7c134ac519f768c08ab970810ac62b93
@@ -1,9 +1,24 @@
1
+ sudo: false
2
+ cache: bundler
1
3
  language: ruby
4
+
5
+ # Ruby build matrix
2
6
  rvm:
3
- - "jruby"
4
- notifications:
5
- email:
6
- recipients:
7
- - kenoir@gmail.com
8
- on_failure: change
9
- on_success: never
7
+ - 2.0
8
+ - 2.1
9
+ - 2.2
10
+ - 2.3.0
11
+ - 2.3.1
12
+ - jruby
13
+
14
+ # Environment variables
15
+ env:
16
+ - RUBYOPT="-W0"
17
+
18
+ # Ensure we don't build for *every* commit (doesn't apply to PR builds)
19
+ branches:
20
+ only:
21
+ - master
22
+
23
+ script:
24
+ - bundle exec rspec --format documentation
data/README.md CHANGED
@@ -11,6 +11,7 @@ Static publishing to S3 based on SQS messages.
11
11
  - S3 bucket.
12
12
  - SQS Queue.
13
13
  - Dynamo DB table.
14
+ - Elasticache (if using the "revalidate" pattern)
14
15
 
15
16
  ## Migrating from [Alephant::Publisher](https://github.com/BBC-News/alephant-publisher)
16
17
 
@@ -91,7 +92,7 @@ end
91
92
  {{ content }}
92
93
  ```
93
94
 
94
- ## Usage
95
+ ## Usage (standard setup - non-revalidate)
95
96
 
96
97
  ```ruby
97
98
  require "alephant/logger"
@@ -101,9 +102,9 @@ module MyApp
101
102
  def self.run!
102
103
  loop do
103
104
  Alephant::Publisher::Queue.create(options).run!
104
- rescue => e
105
- Alephant::Logger.get_logger.warn "Error: #{e.message}"
106
105
  end
106
+ rescue => e
107
+ Alephant::Logger.get_logger.error "Error: #{e.message}"
107
108
  end
108
109
 
109
110
  private
@@ -150,6 +151,111 @@ S3 Path:
150
151
  S3 / bucket-id / example-s3-path / renderer-id / foo / 7e0c33c476b1089500d5f172102ec03e / 1
151
152
  ```
152
153
 
154
+ ## Usage (revalidate pattern)
155
+
156
+ ```ruby
157
+ require "addressable/uri"
158
+ require "alephant/logger"
159
+ require "alephant/publisher/queue"
160
+
161
+ module MyApp
162
+ class UrlGenerator
163
+ class << self
164
+ # This function is called to generate the URL to be requested as
165
+ # part of the rendering process. The return must be a URL as a string.
166
+ def generate(opts)
167
+ "http://example.com/?#{url_params(opts)}"
168
+ end
169
+
170
+ private
171
+
172
+ def url_params(params_hash)
173
+ uri = Addressable::URI.new
174
+ uri.query_values = params_hash
175
+ uri.query
176
+ end
177
+ end
178
+ end
179
+
180
+ class HttpResponseProcessor
181
+ class << self
182
+ # This function is called upon a successful HTTP response.
183
+ #
184
+ # Use it to modify or process the response of your HTTP request
185
+ # as you please, but there is one rule - the return value MUST
186
+ # be a JSON object.
187
+ def process(opts, status, body)
188
+ # our response is already JSON, pass it through
189
+ body
190
+ end
191
+
192
+ # If you wish to vary your revalidate TTL on a per-endpoint (or
193
+ # other logic) basis, you can do it here - simply return an Integer
194
+ # value.
195
+ #
196
+ # If nil is returned the 'revalidate_cache_ttl' config setting on the
197
+ # broker will be used as the default TTL, otherwise the default in
198
+ # 'alephant-broker' will be used.
199
+ def self.ttl(opts)
200
+ # 30s revalidate time for all
201
+ 30
202
+ end
203
+ end
204
+ end
205
+
206
+ def self.run!
207
+ loop do
208
+ Alephant::Publisher::Queue.create(options, processor).run!
209
+ end
210
+ rescue => e
211
+ Alephant::Logger.get_logger.error "Error: #{e.message}"
212
+ end
213
+
214
+ private
215
+
216
+ def self.processor
217
+ Alephant::Publisher::Queue::RevalidateProcessor.new(options, UrlGenerator, HttpResponseProcessor)
218
+ end
219
+
220
+ def self.options
221
+ Alephant::Publisher::Queue::Options.new.tap do |opts|
222
+ opts.add_queue(
223
+ :aws_account_id => 'example',
224
+ :sqs_queue_name => 'test_queue'
225
+ )
226
+ opts.add_writer(
227
+ :lookup_table_name => 'lookup-dynamo-table',
228
+ :s3_bucket_id => 'bucket-id',
229
+ :s3_object_path => 'example-s3-path',
230
+ :view_path => 'path/to/views'
231
+ )
232
+ opts.add_cache(
233
+ :elasticache_config_endpoint => 'example',
234
+ :elasticache_cache_version => '100',
235
+ :revalidate_cache_ttl => '30'
236
+ )
237
+ end
238
+ end
239
+ end
240
+ ```
241
+
242
+ Add a message to your SQS queue, with the following format (`id`, `batch_id`, `options`):
243
+
244
+ ```json
245
+ {
246
+ "id": "renderer_id",
247
+ "batch_id": null,
248
+ "options": {
249
+ "id": "foo",
250
+ "type": "chart"
251
+ }
252
+ }
253
+ ```
254
+
255
+ This will then make a HTTP GET request to the configured endpoint (via `UrlGenerator`), process the response (via `HttpResponseProcessor`), render and store your content.
256
+
257
+ You will not ordinarily need to push messages onto SQS manually, this will be handled via the broker in real use.
258
+
153
259
  ## Preview Server
154
260
 
155
261
  [Alephant Preview](https://github.com/BBC-News/alephant-preview) allows you to see the HTML generated by your templates, both standalone and in the context of a page.
@@ -32,6 +32,8 @@ Gem::Specification.new do |spec|
32
32
  spec.add_runtime_dependency "rake"
33
33
  spec.add_runtime_dependency "aws-sdk", "~> 1.0"
34
34
  spec.add_runtime_dependency "crimp"
35
+ spec.add_runtime_dependency "faraday"
36
+ spec.add_runtime_dependency "dalli-elasticache"
35
37
  spec.add_runtime_dependency "alephant-support"
36
38
  spec.add_runtime_dependency "alephant-sequencer", "~> 3"
37
39
  spec.add_runtime_dependency "alephant-cache"
@@ -2,9 +2,11 @@ require_relative "env"
2
2
 
3
3
  require "alephant/publisher/queue/version"
4
4
  require "alephant/publisher/queue/options"
5
+ require "alephant/publisher/queue/publisher"
5
6
  require "alephant/publisher/queue/sqs_helper/queue"
6
7
  require "alephant/publisher/queue/sqs_helper/archiver"
7
8
  require "alephant/publisher/queue/processor"
9
+ require "alephant/publisher/queue/revalidate_processor"
8
10
  require "alephant/logger"
9
11
  require "alephant/cache"
10
12
  require "json"
@@ -12,94 +14,10 @@ require "json"
12
14
  module Alephant
13
15
  module Publisher
14
16
  module Queue
15
- def self.create(opts = {}, processor = nil)
16
- processor ||= Processor.new(opts.writer)
17
+ def self.create(opts, processor = nil)
18
+ processor ||= Processor.new(opts)
17
19
  Publisher.new(opts, processor)
18
20
  end
19
-
20
- class Publisher
21
- include Alephant::Logger
22
-
23
- VISIBILITY_TIMEOUT = 60
24
- RECEIVE_WAIT_TIME = 15
25
-
26
- attr_reader :queue, :executor, :opts, :processor
27
-
28
- def initialize(opts, processor = nil)
29
- @opts = opts
30
- @processor = processor
31
-
32
- @queue = Alephant::Publisher::Queue::SQSHelper::Queue.new(
33
- aws_queue,
34
- archiver,
35
- opts.queue[:visibility_timeout] || VISIBILITY_TIMEOUT,
36
- opts.queue[:receive_wait_time] || RECEIVE_WAIT_TIME
37
- )
38
- end
39
-
40
- def run!
41
- loop { processor.consume(@queue.message) }
42
- end
43
-
44
- private
45
-
46
- def archiver
47
- Alephant::Publisher::Queue::SQSHelper::Archiver.new(archive_cache, archiver_opts)
48
- end
49
-
50
- def archiver_opts
51
- options = {
52
- :async_store => true,
53
- :log_archive_message => true,
54
- :log_validator => opts.queue[:log_validator]
55
- }
56
- options.each do |key, _value|
57
- options[key] = opts.queue[key] == "true" if whitelist_key(opts.queue, key)
58
- end
59
- end
60
-
61
- def whitelist_key(options, key)
62
- options.key?(key) && key != :log_validator
63
- end
64
-
65
- def archive_cache
66
- Alephant::Cache.new(
67
- opts.writer[:s3_bucket_id],
68
- opts.writer[:s3_object_path]
69
- )
70
- end
71
-
72
- def get_region
73
- opts.queue[:sqs_account_region] || AWS.config.region
74
- end
75
-
76
- def sqs_client
77
- @sqs_client ||= AWS::SQS.new(:region => get_region)
78
- end
79
-
80
- def sqs_queue_options
81
- (opts.queue[:aws_account_id].nil? ? {} : fallback).tap do |ops|
82
- logger.info(
83
- "event" => "SQSQueueOptionsConfigured",
84
- "options" => ops,
85
- "method" => "#{self.class}#sqs_queue_options"
86
- )
87
- end
88
- end
89
-
90
- def fallback
91
- {
92
- :queue_owner_aws_account_id => opts.queue[:aws_account_id]
93
- }
94
- end
95
-
96
- def aws_queue
97
- queue_url = sqs_client.queues.url_for(
98
- opts.queue[:sqs_queue_name], sqs_queue_options
99
- )
100
- sqs_client.queues[queue_url]
101
- end
102
- end
103
21
  end
104
22
  end
105
23
  end
@@ -9,7 +9,7 @@ module Alephant
9
9
  class Options
10
10
  include Alephant::Logger
11
11
 
12
- attr_reader :queue, :writer
12
+ attr_reader :queue, :writer, :cache
13
13
 
14
14
  QUEUE_OPTS = [
15
15
  :receive_wait_time,
@@ -20,7 +20,7 @@ module Alephant
20
20
  :log_archive_message,
21
21
  :log_validator,
22
22
  :async_store
23
- ]
23
+ ].freeze
24
24
 
25
25
  WRITER_OPTS = [
26
26
  :lookup_table_name,
@@ -31,38 +31,47 @@ module Alephant
31
31
  :sequence_id_path,
32
32
  :sequencer_table_name,
33
33
  :view_path
34
- ]
34
+ ].freeze
35
+
36
+ CACHE_OPTS = [
37
+ :elasticache_config_endpoint,
38
+ :elasticache_cache_version,
39
+ :revalidate_cache_ttl
40
+ ].freeze
35
41
 
36
42
  def initialize
37
43
  @queue = {}
38
44
  @writer = {}
45
+ @cache = {}
39
46
  end
40
47
 
41
48
  def add_queue(opts)
42
- execute @queue, QUEUE_OPTS, opts
49
+ execute(@queue, QUEUE_OPTS, opts)
43
50
  end
44
51
 
45
52
  def add_writer(opts)
46
- execute @writer, WRITER_OPTS, opts
53
+ execute(@writer, WRITER_OPTS, opts)
54
+ end
55
+
56
+ def add_cache(opts)
57
+ execute(@cache, CACHE_OPTS, opts)
47
58
  end
48
59
 
49
60
  private
50
61
 
51
62
  def execute(instance, type, opts)
52
- begin
53
- validate type, opts
54
- instance.merge! opts
55
- rescue InvalidKeySpecifiedError => e
56
- logger.metric "QueueOptionsInvalidKeySpecified"
57
- logger.error(
58
- "event" => "QueueOptionsKeyInvalid",
59
- "class" => e.class,
60
- "message" => e.message,
61
- "backtrace" => e.backtrace.join.to_s,
62
- "method" => "#{self.class}#validate"
63
- )
64
- puts e.message
65
- end
63
+ validate(type, opts)
64
+ instance.merge!(opts)
65
+ rescue InvalidKeySpecifiedError => e
66
+ logger.metric "QueueOptionsInvalidKeySpecified"
67
+ logger.error(
68
+ "event" => "QueueOptionsKeyInvalid",
69
+ "class" => e.class,
70
+ "message" => e.message,
71
+ "backtrace" => e.backtrace.join.to_s,
72
+ "method" => "#{self.class}#validate"
73
+ )
74
+ puts e.message
66
75
  end
67
76
 
68
77
  def validate(type, opts)
@@ -1,25 +1,27 @@
1
1
  require "alephant/publisher/queue/writer"
2
- require "alephant/publisher/queue/base_processor"
3
2
 
4
3
  module Alephant
5
4
  module Publisher
6
5
  module Queue
7
- class Processor < BaseProcessor
8
- attr_reader :writer_config
6
+ class Processor
7
+ attr_reader :opts
9
8
 
10
- def initialize(writer_config = {})
11
- @writer_config = writer_config
9
+ def initialize(opts = nil)
10
+ @opts = opts
12
11
  end
13
12
 
14
13
  def consume(msg)
15
- unless msg.nil?
16
- write msg
17
- msg.delete
18
- end
14
+ return if msg.nil?
15
+ write(msg)
16
+ msg.delete
19
17
  end
20
18
 
21
19
  private
22
20
 
21
+ def writer_config
22
+ opts ? opts.writer : {}
23
+ end
24
+
23
25
  def write(msg)
24
26
  Writer.new(writer_config, msg).run!
25
27
  end
@@ -0,0 +1,89 @@
1
+ module Alephant
2
+ module Publisher
3
+ module Queue
4
+ class Publisher
5
+ include Alephant::Logger
6
+
7
+ VISIBILITY_TIMEOUT = 60
8
+ RECEIVE_WAIT_TIME = 15
9
+
10
+ attr_reader :queue, :executor, :opts, :processor
11
+
12
+ def initialize(opts, processor = nil)
13
+ @opts = opts
14
+ @processor = processor
15
+
16
+ @queue = Alephant::Publisher::Queue::SQSHelper::Queue.new(
17
+ aws_queue,
18
+ archiver,
19
+ opts.queue[:visibility_timeout] || VISIBILITY_TIMEOUT,
20
+ opts.queue[:receive_wait_time] || RECEIVE_WAIT_TIME
21
+ )
22
+ end
23
+
24
+ def run!
25
+ loop { processor.consume(@queue.message) }
26
+ end
27
+
28
+ private
29
+
30
+ def archiver
31
+ Alephant::Publisher::Queue::SQSHelper::Archiver.new(archive_cache, archiver_opts)
32
+ end
33
+
34
+ def archiver_opts
35
+ options = {
36
+ :async_store => true,
37
+ :log_archive_message => true,
38
+ :log_validator => opts.queue[:log_validator]
39
+ }
40
+ options.each do |key, _value|
41
+ options[key] = opts.queue[key] == "true" if whitelist_key(opts.queue, key)
42
+ end
43
+ end
44
+
45
+ def whitelist_key(options, key)
46
+ options.key?(key) && key != :log_validator
47
+ end
48
+
49
+ def archive_cache
50
+ Alephant::Cache.new(
51
+ opts.writer[:s3_bucket_id],
52
+ opts.writer[:s3_object_path]
53
+ )
54
+ end
55
+
56
+ def get_region
57
+ opts.queue[:sqs_account_region] || AWS.config.region
58
+ end
59
+
60
+ def sqs_client
61
+ @sqs_client ||= AWS::SQS.new(region: get_region)
62
+ end
63
+
64
+ def sqs_queue_options
65
+ (opts.queue[:aws_account_id].nil? ? {} : fallback).tap do |ops|
66
+ logger.info(
67
+ "event" => "SQSQueueOptionsConfigured",
68
+ "options" => ops,
69
+ "method" => "#{self.class}#sqs_queue_options"
70
+ )
71
+ end
72
+ end
73
+
74
+ def fallback
75
+ {
76
+ :queue_owner_aws_account_id => opts.queue[:aws_account_id]
77
+ }
78
+ end
79
+
80
+ def aws_queue
81
+ queue_url = sqs_client.queues.url_for(
82
+ opts.queue[:sqs_queue_name], sqs_queue_options
83
+ )
84
+ sqs_client.queues[queue_url]
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end