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 +4 -4
- data/.travis.yml +22 -7
- data/README.md +109 -3
- data/alephant-publisher-queue.gemspec +2 -0
- data/lib/alephant/publisher/queue.rb +4 -86
- data/lib/alephant/publisher/queue/options.rb +28 -19
- data/lib/alephant/publisher/queue/processor.rb +11 -9
- data/lib/alephant/publisher/queue/publisher.rb +89 -0
- data/lib/alephant/publisher/queue/revalidate_processor.rb +115 -0
- data/lib/alephant/publisher/queue/revalidate_writer.rb +96 -0
- data/lib/alephant/publisher/queue/version.rb +1 -1
- data/lib/alephant/publisher/queue/writer.rb +23 -2
- data/spec/alephant/publisher/queue/revalidate_processor_spec.rb +140 -0
- data/spec/alephant/publisher/queue/revalidate_writer_spec.rb +96 -0
- metadata +94 -60
- data/lib/alephant/publisher/queue/base_processor.rb +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e90e5d717583700bb627398ffec0f305383ab17b
|
4
|
+
data.tar.gz: 7f6fdf2a4159bb0276e512829bb9d8d4788775e5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: acdc9518ef0f946002f0e6b07a8cb122076e6d9661b37fcee23339bdc221226b01c9d7300d936879f0fd919591701ce8749bcaf451217eb97c0532f6781f4bd6
|
7
|
+
data.tar.gz: cf959951bd12ec476daf6e473560b0c73ac52a3d6eb29f9ce21419af704298cae47a861eac42e66a2f6f96776d4e168c7c134ac519f768c08ab970810ac62b93
|
data/.travis.yml
CHANGED
@@ -1,9 +1,24 @@
|
|
1
|
+
sudo: false
|
2
|
+
cache: bundler
|
1
3
|
language: ruby
|
4
|
+
|
5
|
+
# Ruby build matrix
|
2
6
|
rvm:
|
3
|
-
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
16
|
-
processor ||= Processor.new(opts
|
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
|
49
|
+
execute(@queue, QUEUE_OPTS, opts)
|
43
50
|
end
|
44
51
|
|
45
52
|
def add_writer(opts)
|
46
|
-
execute
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
8
|
-
attr_reader :
|
6
|
+
class Processor
|
7
|
+
attr_reader :opts
|
9
8
|
|
10
|
-
def initialize(
|
11
|
-
@
|
9
|
+
def initialize(opts = nil)
|
10
|
+
@opts = opts
|
12
11
|
end
|
13
12
|
|
14
13
|
def consume(msg)
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|