alephant-publisher-queue 2.3.1 → 2.4.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: 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