alephant-publisher-queue 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b72175c8d5c49896aef2c2bc39cd254ddf0a9dc6
4
+ data.tar.gz: b1ccc179c4ff8eb62bd5cbe9bc8b2d95d4e6fc1b
5
+ SHA512:
6
+ metadata.gz: 740f828042a9107e2ab146f1ff1c2a1e43ac78aa18b147f5ad28ce33acae36bd4563ef84a937f911956a8ff26140bf019022fe75b0429d16c31931f91afed691
7
+ data.tar.gz: e6c7923dad564f59638387c5f05a330ad2d321928b069cbc63e0a845f99ba7c21fe64416c76246a657de8d8c4ffe7b0a78f37a4a3eede2fd2b9ae31c6dbd7568
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .rspec
19
+ .ruby-version
20
+ /config/*.yaml
21
+ /config/*.yml
22
+ /components
23
+ *.swp
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - "jruby"
4
+ notifications:
5
+ email:
6
+ recipients:
7
+ - kenoir@gmail.com
8
+ on_failure: change
9
+ on_success: never
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in alephant-publishing.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,6 @@
1
+ guard 'rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/.+\.rb$})
4
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
5
+ watch('spec/spec_helper.rb') { "spec" }
6
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Integralist
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,226 @@
1
+ # Alephant::Publisher::Queue
2
+
3
+ Static publishing to S3 based on SQS messages.
4
+
5
+ [![Build Status](https://travis-ci.org/BBC-News/alephant-publisher-queue.png?branch=master)](https://travis-ci.org/BBC-News/alephant-publisher-queue) [![Dependency Status](https://gemnasium.com/BBC-News/alephant-publisher-queue.png)](https://gemnasium.com/BBC-News/alephant-publisher-queue) [![Gem Version](https://badge.fury.io/rb/alephant-publisher-queue.png)](http://badge.fury.io/rb/alephant-publisher-queue)
6
+
7
+ ## Dependencies
8
+
9
+ - JRuby 1.7.8
10
+ - An AWS account (you'll need to create):
11
+ - An S3 bucket
12
+ - An SQS Queue (if no sequence id provided then `sequence_id` will be used)
13
+ - A Dynamo DB table (optional, will attempt to create if can't be found)
14
+
15
+ ## Migrating from [Alephant::Publisher](https://github.com/BBC-News/alephant-publisher)
16
+
17
+ 1. Add the new gem in your Gemfile (`gem 'alephant-publisher-queue'`).
18
+ 2. Run `bundle install`.
19
+ 3. Require the new gem in your app (`require 'alephant/publisher/queue'`).
20
+ 4. Note that the namespace has changed from `Alephant::Publisher` to `Alephant::Publisher::Queue`.
21
+
22
+ ## Installation
23
+
24
+ Add this line to your application's Gemfile:
25
+
26
+ gem 'alephant-publisher-queue'
27
+
28
+ And then execute:
29
+
30
+ $ bundle
31
+
32
+ Or install it yourself as:
33
+
34
+ $ gem install alephant-publisher-queue
35
+
36
+ ## Setup
37
+
38
+ Ensure you have a `config/aws.yml` in the format:
39
+
40
+ ```yaml
41
+ access_key_id: ACCESS_KEY_ID
42
+ secret_access_key: SECRET_ACCESS_KEY
43
+ ```
44
+
45
+ ## Usage
46
+
47
+ **In your application:**
48
+
49
+ ```rb
50
+ require 'alephant'
51
+
52
+ sequential_proc = Proc.new do |last_seen_id, data|
53
+ last_seen_id < data['sequence_id'].to_i
54
+ end
55
+
56
+ set_last_seen_proc = Proc.new do |data|
57
+ data['sequence_id'].to_i
58
+ end
59
+
60
+ opts = {
61
+ :s3_bucket_id => 'bucket-id',
62
+ :s3_object_path => 'path/to/object',
63
+ :s3_object_id => 'object_id',
64
+ :sequencer_table_name => 'your_dynamo_db_table',
65
+ :sqs_queue_url => 'https://your_amazon_sqs_queue_url',
66
+ :sequential_proc => sequential_proc,
67
+ :set_last_seen_proc => set_last_seen_proc,
68
+ :lookup_table_name => 'your_lookup_table'
69
+ }
70
+
71
+ logger = Logger.new
72
+
73
+ thread = Alephant::Alephant.new(opts, logger).run!
74
+ thread.join
75
+ ```
76
+
77
+ Publisher requires both queue options and writer options to be provided. To ensure a standard format you should use the `Options` class to generate your options before passing them onto the Publisher...
78
+
79
+ ```ruby
80
+ opts = Alephant::Publisher::Options.new
81
+ # => #<Alephant::Publisher::Options:0x0602f958 @queue={}, @writer={}>
82
+
83
+ opts.queue
84
+ # => {}
85
+ # empty to start with
86
+
87
+ opts.writer
88
+ # => {}
89
+ # empty to start with
90
+
91
+ opts.add_queue(:foo => "bar")
92
+ # The key 'foo' is invalid
93
+ # => nil
94
+
95
+ opts.queue
96
+ # => {}
97
+ # still empty as the foo key was invalid
98
+
99
+ opts.add_queue(:sqs_queue_url => "bar")
100
+ # => {:sqs_queue_url=>"bar"}
101
+
102
+ opts.queue
103
+ # => {:sqs_queue_url=>"bar"}
104
+
105
+ opts.add_writer(:sqs_queue_url => "bar")
106
+ # The key 'sqs_queue_url' is invalid
107
+ # => nil
108
+ # the sqs_queue_url key was valid for the queue options,
109
+ # but is invalid when trying to add it to the writer options
110
+
111
+ opts.add_writer(:msg_vary_id_path => "bar")
112
+ => {:msg_vary_id_path=>"bar"}
113
+
114
+ opts.writer
115
+ => {:msg_vary_id_path=>"bar"}
116
+ ```
117
+
118
+ logger is optional, and must confirm to the Ruby standard logger interface
119
+
120
+ Provide a view in a folder (fixtures are optional):
121
+
122
+ ```
123
+ └── views
124
+ ├── models
125
+ │   └── foo.rb
126
+ ├── fixtures
127
+ │   └── foo.json
128
+ └── templates
129
+ └── foo.mustache
130
+ ```
131
+
132
+ **SQS Message Format**
133
+
134
+ ```json
135
+ {
136
+ "content": "hello world",
137
+ "sequential_id": 1
138
+ }
139
+ ```
140
+
141
+ **foo.rb**
142
+
143
+ ```rb
144
+ module MyApp
145
+ module Views
146
+ class Foo < Alephant::Views::Base
147
+ def content
148
+ @data['content']
149
+ end
150
+ end
151
+ end
152
+ end
153
+ ```
154
+
155
+ **foo.mustache**
156
+
157
+ ```mustache
158
+ {{content}}
159
+ ```
160
+
161
+ **S3 Output**
162
+
163
+ ```
164
+ hello world
165
+ ```
166
+
167
+ ## Build the gem locally
168
+
169
+ If you want to test a modified version of the gem within your application without publishing it then you can follow these steps...
170
+
171
+ - `gem uninstall alephant-publisher-queue`
172
+ - `gem build alephant-publisher-queue.gemspec` (this will report the file generated which you reference in the next command)
173
+ - `gem install ./alephant-publisher-queue-0.0.1.gem`
174
+
175
+ Now you can test the gem from within your application as you've installed the gem from the local version rather than your published version
176
+
177
+ ## Preview Server
178
+
179
+ `alephant preview`
180
+
181
+ The included preview server allows you to see the html generated by your
182
+ templates, both standalone and in the context of a page.
183
+
184
+ **Standalone**
185
+
186
+ `/component/:id/?:fixture?`
187
+
188
+ ### Full page preview
189
+
190
+ When viewing the component in the context of a page, you'll need to retrieve a
191
+ mustache template to provide the page context.
192
+
193
+ When performing an update a regex is applied to replace the static hostnames in
194
+ the retrieved html.
195
+
196
+ **Environment Variables**
197
+
198
+ ```sh
199
+ STATIC_HOST_REGEX="static.(sandbox.dev|int|test|stage|live).yourapp(i)?.com\/"
200
+ PREVIEW_TEMPLATE_URL="http://yourapp.com/template"
201
+ ```
202
+
203
+ **Example Remote Template**
204
+
205
+ `id` is the component/folder name
206
+
207
+ `template` is the mustache template file name
208
+
209
+ `location_in_page` should be something like (for example) `page_head` (specified within a `preview.mustache` file that the consuming application needs to create).
210
+
211
+ - `http://localhost:4567/component/id/template`
212
+ - `http://localhost:4567/preview/id/template/location_in_page`
213
+
214
+ `alephant update`
215
+
216
+ **In page**
217
+
218
+ `/preview/:id/:region/?:fixture?`
219
+
220
+ ## Contributing
221
+
222
+ 1. [Fork it!](http://github.com/BBC-News/alephant-publisher-queue/fork)
223
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
224
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
225
+ 4. Push to the branch (`git push origin my-new-feature`)
226
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+
3
+ require 'rspec/core/rake_task'
4
+ require 'bundler/gem_tasks'
5
+ require 'alephant/publisher/queue'
6
+
7
+ require 'rake/rspec'
8
+
9
+ task :default => :spec
@@ -0,0 +1,40 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'alephant/publisher/queue/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "alephant-publisher-queue"
8
+ spec.version = Alephant::Publisher::Queue::VERSION
9
+ spec.authors = ["revett"]
10
+ spec.email = ["charlierevett@gmail.com"]
11
+ spec.summary = "Static publishing to S3 based on SQS messages"
12
+ spec.description = "Static publishing to S3 based on SQS messages"
13
+ spec.homepage = "https://github.com/BBC-News/alephant-publisher-queue"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rspec"
23
+ spec.add_development_dependency "rspec-nc"
24
+ spec.add_development_dependency "guard"
25
+ spec.add_development_dependency "guard-rspec"
26
+ spec.add_development_dependency "pry"
27
+ spec.add_development_dependency "pry-remote"
28
+ spec.add_development_dependency "pry-nav"
29
+ spec.add_development_dependency 'rake-rspec'
30
+
31
+ spec.add_runtime_dependency 'rake'
32
+ spec.add_runtime_dependency 'aws-sdk', '~> 1.0'
33
+ spec.add_runtime_dependency 'crimp'
34
+ spec.add_runtime_dependency 'alephant-support'
35
+ spec.add_runtime_dependency 'alephant-sequencer'
36
+ spec.add_runtime_dependency 'alephant-cache'
37
+ spec.add_runtime_dependency 'alephant-logger'
38
+ spec.add_runtime_dependency 'alephant-lookup'
39
+ spec.add_runtime_dependency 'alephant-renderer', '~> 0.1.0'
40
+ end
@@ -0,0 +1,11 @@
1
+ require 'aws-sdk'
2
+ require 'yaml'
3
+
4
+ config_file = 'config/aws.yaml'
5
+
6
+ AWS.eager_autoload!
7
+
8
+ if File.exists? config_file
9
+ config = YAML.load(File.read(config_file))
10
+ AWS.config(config)
11
+ end
@@ -0,0 +1,72 @@
1
+ require_relative 'env'
2
+
3
+ require 'alephant/publisher/queue/version'
4
+ require 'alephant/publisher/queue/options'
5
+ require 'alephant/publisher/queue/sqs_helper/queue'
6
+ require 'alephant/publisher/queue/sqs_helper/archiver'
7
+ require 'alephant/logger'
8
+ require 'alephant/publisher/queue/processor'
9
+ require 'json'
10
+
11
+ module Alephant
12
+ module Publisher
13
+ module Queue
14
+ include Logger
15
+
16
+ def self.create(opts = {}, processor = nil)
17
+ processor ||= Processor.new(opts.writer)
18
+ Publisher.new(opts, processor)
19
+ end
20
+
21
+ class Publisher
22
+ VISIBILITY_TIMEOUT = 60
23
+ RECEIVE_WAIT_TIME = 15
24
+
25
+ attr_reader :queue, :executor, :opts, :processor
26
+
27
+ def initialize(opts, processor = nil)
28
+ @opts = opts
29
+ @processor = processor
30
+
31
+ @queue = SQSHelper::Queue.new(
32
+ aws_queue,
33
+ archiver,
34
+ opts.queue[:visibility_timeout] || VISIBILITY_TIMEOUT,
35
+ opts.queue[:receive_wait_time] || RECEIVE_WAIT_TIME,
36
+ )
37
+ end
38
+
39
+ def run!
40
+ loop { processor.consume(@queue.message) }
41
+ end
42
+
43
+ private
44
+
45
+ def archiver
46
+ SQSHelper::Archiver.new(archive_cache)
47
+ end
48
+
49
+ def archive_cache
50
+ Cache.new(
51
+ opts.writer[:s3_bucket_id],
52
+ opts.writer[:s3_object_path]
53
+ )
54
+ end
55
+
56
+ def sqs_client
57
+ @sqs_client ||= AWS::SQS.new
58
+ end
59
+
60
+ def sqs_queue_options
61
+ opts.queue[:aws_account_id].nil? ? {} : { :queue_owner_aws_account_id => opts.queue[:aws_account_id] }
62
+ end
63
+
64
+ def aws_queue
65
+ queue_url = sqs_client.queues.url_for(opts.queue[:sqs_queue_name], sqs_queue_options)
66
+ sqs_client.queues[queue_url]
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,62 @@
1
+ require 'aws-sdk'
2
+ require 'alephant/logger'
3
+
4
+ module Alephant
5
+ module Publisher
6
+ module Queue
7
+ class InvalidKeySpecifiedError < StandardError; end
8
+
9
+ class Options
10
+ attr_reader :queue, :writer
11
+
12
+ QUEUE_OPTS = [
13
+ :receive_wait_time,
14
+ :sqs_queue_name,
15
+ :visibility_timeout,
16
+ :aws_account_id
17
+ ]
18
+
19
+ WRITER_OPTS = [
20
+ :lookup_table_name,
21
+ :msg_vary_id_path,
22
+ :renderer_id,
23
+ :s3_bucket_id,
24
+ :s3_object_path,
25
+ :sequence_id_path,
26
+ :sequencer_table_name,
27
+ :view_path
28
+ ]
29
+
30
+ def initialize
31
+ @queue = {}
32
+ @writer = {}
33
+ end
34
+
35
+ def add_queue(opts)
36
+ execute @queue, QUEUE_OPTS, opts
37
+ end
38
+
39
+ def add_writer(opts)
40
+ execute @writer, WRITER_OPTS, opts
41
+ end
42
+
43
+ private
44
+
45
+ def execute(instance, type, opts)
46
+ begin
47
+ validate type, opts
48
+ instance.merge! opts
49
+ rescue Exception => e
50
+ puts e.message
51
+ end
52
+ end
53
+
54
+ def validate(type, opts)
55
+ opts.each do |key, value|
56
+ raise InvalidKeySpecifiedError, "The key '#{key}' is invalid" unless type.include? key.to_sym
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,29 @@
1
+ require 'alephant/publisher/queue/writer'
2
+ require 'alephant/publisher/queue/processor/base'
3
+
4
+ module Alephant
5
+ module Publisher
6
+ module Queue
7
+ class Processor < BaseProcessor
8
+ attr_reader :writer_config
9
+
10
+ def initialize(writer_config = {})
11
+ @writer_config = writer_config
12
+ end
13
+
14
+ def consume(msg)
15
+ unless msg.nil?
16
+ write msg
17
+ msg.delete
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def write(msg)
24
+ Writer.new(writer_config, msg).run!
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module Alephant
2
+ module Publisher
3
+ module Queue
4
+ class BaseProcessor
5
+
6
+ def consume(msg)
7
+ raise NotImplementedError.new("You must implement the #consume(msg) method")
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ require 'date'
2
+
3
+ module Alephant
4
+ module Publisher
5
+ module Queue
6
+ module SQSHelper
7
+ class Archiver
8
+ attr_reader :cache, :async
9
+
10
+ def initialize(cache, async = true)
11
+ @async = async
12
+ @cache = cache
13
+ end
14
+
15
+ def see(message)
16
+ return if message.nil?
17
+ message.tap { |m| async ? async_store(m) : store(m) }
18
+ end
19
+
20
+ private
21
+
22
+ def async_store(m)
23
+ Thread.new { store(m) }
24
+ end
25
+
26
+ def store(m)
27
+ cache.put("archive/#{date_key}/#{m.id}", m.body, meta_for(m))
28
+ end
29
+
30
+ def date_key
31
+ DateTime.now.strftime('%d-%m-%Y_%H')
32
+ end
33
+
34
+ def meta_for(m)
35
+ {
36
+ :id => m.id,
37
+ :md5 => m.md5,
38
+ :logged_at => DateTime.now.to_s,
39
+ :queue => m.queue.url,
40
+ }
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
@@ -0,0 +1,53 @@
1
+ require 'aws-sdk'
2
+ require 'alephant/logger'
3
+
4
+ module Alephant
5
+ module Publisher
6
+ module Queue
7
+ module SQSHelper
8
+ class Queue
9
+ WAIT_TIME = 5
10
+ VISABILITY_TIMEOUT = 300
11
+
12
+ include Logger
13
+
14
+ attr_reader :queue, :timeout, :wait_time, :archiver
15
+
16
+ def initialize(queue, archiver = nil, timeout = VISABILITY_TIMEOUT, wait_time = WAIT_TIME)
17
+ @queue = queue
18
+ @archiver = archiver
19
+ @timeout = timeout
20
+ @wait_time = wait_time
21
+
22
+ logger.debug("Queue#initialize: reading from #{queue.url}")
23
+ end
24
+
25
+ def message
26
+ receive.tap { |m| process(m) unless m.nil? }
27
+ end
28
+
29
+ private
30
+
31
+ def process(m)
32
+ logger.info("Queue#message: received #{m.id}")
33
+ archive m
34
+ end
35
+
36
+ def archive(m)
37
+ archiver.see(m) unless archiver.nil?
38
+ rescue StandardError => e
39
+ logger.warn("Queue#archive: archive failed (#{e.message})");
40
+ end
41
+
42
+ def receive
43
+ queue.receive_message({
44
+ :visibility_timeout => timeout,
45
+ :wait_time_seconds => wait_time
46
+ })
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
@@ -0,0 +1,7 @@
1
+ module Alephant
2
+ module Publisher
3
+ module Queue
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,109 @@
1
+ require 'crimp'
2
+
3
+ require 'alephant/cache'
4
+ require 'alephant/lookup'
5
+ require 'alephant/logger'
6
+ require 'alephant/sequencer'
7
+ require 'alephant/support/parser'
8
+ require 'alephant/renderer'
9
+
10
+ module Alephant
11
+ module Publisher
12
+ module Queue
13
+ class Writer
14
+ include Logger
15
+
16
+ attr_reader :config, :message, :cache, :parser, :renderer
17
+
18
+ def initialize(config, message)
19
+ @config = config
20
+ @message = message
21
+ @renderer = Alephant::Renderer.create(config, data)
22
+ end
23
+
24
+ def cache
25
+ Cache.new(
26
+ config[:s3_bucket_id],
27
+ config[:s3_object_path]
28
+ )
29
+ end
30
+
31
+ def parser
32
+ @parser ||= Support::Parser.new(
33
+ config[:msg_vary_id_path]
34
+ )
35
+ end
36
+
37
+ def run!
38
+ batch? ? batch.sequence(message, &perform) : perform.call
39
+ end
40
+
41
+ protected
42
+
43
+ def perform
44
+ Proc.new { views.each { |id, view| write(id, view) } }
45
+ end
46
+
47
+ def write(id, view)
48
+ seq_for(id).sequence(message) do
49
+ store(id, view, location_for(id))
50
+ end
51
+ end
52
+
53
+ def store(id, view, location)
54
+ cache.put(location, view.render, view.content_type, :msg_id => message.id)
55
+ lookup.write(id, options, seq_id, location)
56
+ end
57
+
58
+ def location_for(id)
59
+ "#{config[:renderer_id]}/#{id}/#{opt_hash}/#{seq_id}"
60
+ end
61
+
62
+ def batch
63
+ @batch ||= (views.count > 1) ? seq_for(config[:renderer_id]) : nil
64
+ end
65
+
66
+ def batch?
67
+ !batch.nil?
68
+ end
69
+
70
+ def seq_for(id)
71
+ Sequencer.create(
72
+ config[:sequencer_table_name],
73
+ seq_key_from(id),
74
+ config[:sequence_id_path],
75
+ config[:keep_all_messages] == 'true'
76
+ )
77
+ end
78
+
79
+ def seq_key_from(id)
80
+ "#{id}/#{opt_hash}"
81
+ end
82
+
83
+ def seq_id
84
+ @seq_id ||= Sequencer::Sequencer.sequence_id_from(message, config[:sequence_id_path])
85
+ end
86
+
87
+ def views
88
+ @views ||= renderer.views
89
+ end
90
+
91
+ def opt_hash
92
+ @opt_hash ||= Crimp.signature(options)
93
+ end
94
+
95
+ def options
96
+ @options ||= data[:options]
97
+ end
98
+
99
+ def data
100
+ @data ||= parser.parse(message)
101
+ end
102
+
103
+ def lookup
104
+ Lookup.create(config[:lookup_table_name])
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Alephant::Publisher::Queue::SQSHelper::Archiver do
4
+ describe "#see" do
5
+ it "calls cache put with the correct params" do
6
+ time_now = DateTime.parse("Feb 24 1981")
7
+ allow(DateTime).to receive(:now).and_return(time_now)
8
+
9
+ q = double("queue").as_null_object
10
+ c = double("cache").as_null_object
11
+
12
+ expect(q).to receive(:url).and_return('url')
13
+
14
+ m = Struct.new(:id, :body, :md5, :queue).new('id', 'body', 'md5', q)
15
+
16
+ expect(c).to receive(:put).with(
17
+ "archive/#{time_now.strftime('%d-%m-%Y_%H')}/id",
18
+ "body",
19
+ {
20
+ :id => "id",
21
+ :md5 => "md5",
22
+ :logged_at => time_now.to_s,
23
+ :queue => "url"
24
+ }
25
+ )
26
+
27
+ instance = Alephant::Publisher::Queue::SQSHelper::Archiver.new(c, false)
28
+
29
+ instance.see(m)
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Alephant::Publisher::Queue::Processor do
4
+
5
+ before(:each) do
6
+ allow_any_instance_of(Alephant::Publisher::Queue::Writer).to receive(:initialize)
7
+ allow_any_instance_of(Alephant::Publisher::Queue::Writer).to receive(:run!)
8
+ end
9
+
10
+ describe "#consume(msg)" do
11
+ it "Consume the message and deletes it" do
12
+
13
+ msg = double('AWS::SQS::ReceivedMessage', :delete => nil)
14
+ expect(msg).to receive(:delete)
15
+ subject.consume(msg)
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe Alephant::Publisher::Queue do
4
+ let(:options) { Alephant::Publisher::Queue::Options.new }
5
+ let(:queue) { double('AWS::SQS::Queue', :url => nil ) }
6
+ let(:queue_double) { double('AWS::SQS::QueueCollection', :[] => queue, :url_for => nil) }
7
+ let(:client_double) { double('AWS::SQS', :queues => queue_double) }
8
+
9
+ before(:each) do
10
+ expect(AWS::SQS).to receive(:new).and_return(client_double)
11
+ end
12
+
13
+ describe ".create" do
14
+ it "sets parser, sequencer, queue and writer" do
15
+ instance = Alephant::Publisher::Queue.create(options)
16
+ expect(instance.queue).to be_a Alephant::Publisher::Queue::SQSHelper::Queue
17
+ end
18
+
19
+ context "with account" do
20
+ it "creates a queue with an account number in the option hash" do
21
+ options = Alephant::Publisher::Queue::Options.new
22
+ options.add_queue({ :sqs_queue_name => 'bar', :aws_account_id => 'foo' })
23
+
24
+ expect(queue_double).to receive(:url_for).with('bar', { :queue_owner_aws_account_id => 'foo' })
25
+
26
+ Alephant::Publisher::Queue.create(options)
27
+ end
28
+ end
29
+
30
+ context "without account" do
31
+ it "creates a queue with an empty option hash" do
32
+ options = Alephant::Publisher::Queue::Options.new
33
+ options.add_queue({ :sqs_queue_name => 'bar' })
34
+
35
+ expect(queue_double).to receive(:url_for).with('bar', {})
36
+
37
+ Alephant::Publisher::Queue.create(options)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Alephant::Publisher::Queue::SQSHelper::Queue do
4
+ describe "#message" do
5
+ it "returns a message" do
6
+ m = double("message").as_null_object
7
+ q = double("queue").as_null_object
8
+
9
+ expect(q).to receive(:receive_message).and_return(m)
10
+
11
+ instance = Alephant::Publisher::Queue::SQSHelper::Queue.new(q)
12
+
13
+ expect(instance.message).to eq(m)
14
+ end
15
+
16
+ it "call see(m) on the handed archiver" do
17
+ a = double("archiver").as_null_object
18
+ m = double("message").as_null_object
19
+ q = double("queue").as_null_object
20
+
21
+ expect(q).to receive(:receive_message).and_return(m)
22
+ expect(a).to receive(:see).with(m)
23
+
24
+ instance = Alephant::Publisher::Queue::SQSHelper::Queue.new(q, a)
25
+
26
+ expect(instance.message).to eq(m)
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,5 @@
1
+ require 'pry'
2
+
3
+ require 'aws-sdk'
4
+ require 'alephant/publisher/queue'
5
+
@@ -0,0 +1,86 @@
1
+ require 'spec_helper'
2
+
3
+ describe Alephant::Publisher::Queue::Writer do
4
+ let(:opts) do
5
+ {
6
+ :lookup_table_name => 'lookup_table_name',
7
+ :msg_vary_id_path => '$.vary',
8
+ :renderer_id => :renderer_id,
9
+ :s3_bucket_id => :s3_bucket_id,
10
+ :s3_object_path => :s3_object_path,
11
+ :sequence_id_path => '$.sequence',
12
+ :sequencer_table_name => :sequencer_table_name,
13
+ :view_path => :view_path
14
+ }
15
+ end
16
+
17
+ before(:each) do
18
+ AWS.stub!
19
+
20
+ allow_any_instance_of(Alephant::Cache).to receive(:initialize)
21
+ .with(
22
+ opts[:s3_bucket_id],
23
+ opts[:s3_object_path]
24
+ )
25
+
26
+ allow_any_instance_of(Alephant::Sequencer::SequenceTable).to receive(:create)
27
+
28
+ allow_any_instance_of(Alephant::Sequencer::Sequencer).to receive(:sequencer_id_from)
29
+ .and_return(1)
30
+
31
+ allow_any_instance_of(Alephant::Sequencer::Sequencer).to receive(:set_last_seen)
32
+
33
+ allow_any_instance_of(Alephant::Sequencer::Sequencer).to receive(:get_last_seen)
34
+
35
+ allow_any_instance_of(Alephant::Lookup::LookupTable).to receive(:create)
36
+
37
+ allow_any_instance_of(Alephant::Lookup::LookupTable).to receive(:table_name)
38
+
39
+ allow_any_instance_of(Alephant::Renderer::Renderer).to receive(:views).and_return({})
40
+ end
41
+
42
+ describe "#run!" do
43
+ let(:msg) do
44
+ data = {
45
+ "sequence" => "1",
46
+ "vary" => "foo"
47
+ }
48
+ Struct.new(:body,:id).new(data.to_json,'id')
49
+ end
50
+
51
+ let(:expected_location) do
52
+ 'renderer_id/component_id/218c835cec343537589dbf1619532e4d/1'
53
+ end
54
+
55
+ let(:renderer) do
56
+ instance_double 'Alephant::Renderer::Renderer'
57
+ end
58
+
59
+ subject do
60
+ Alephant::Publisher::Queue::Writer.new(opts, msg)
61
+ end
62
+
63
+ it "should write the correct lookup location" do
64
+ allow_any_instance_of(Alephant::Cache).to receive(:put)
65
+
66
+ allow_any_instance_of(Alephant::Lookup::LookupHelper).to receive(:write)
67
+ .with(
68
+ "component_id",
69
+ {:variant=>"foo"},
70
+ 1,
71
+ expected_location
72
+ )
73
+ end
74
+
75
+ it "should put the correct location, content to cache" do
76
+ allow_any_instance_of(Alephant::Lookup::LookupHelper).to receive(:write)
77
+
78
+ allow_any_instance_of(Alephant::Cache).to receive(:put)
79
+ .with(expected_location, "content", "foo/bar", :msg_id=>"id")
80
+ end
81
+
82
+ after do
83
+ subject.run!
84
+ end
85
+ end
86
+ end
metadata ADDED
@@ -0,0 +1,325 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alephant-publisher-queue
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - revett
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ version_requirements: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ requirement: !ruby/object:Gem::Requirement
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: '1.5'
25
+ prerelease: false
26
+ type: :development
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ prerelease: false
40
+ type: :development
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-nc
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ prerelease: false
54
+ type: :development
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ prerelease: false
68
+ type: :development
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirement: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ prerelease: false
82
+ type: :development
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ prerelease: false
96
+ type: :development
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry-remote
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirement: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ prerelease: false
110
+ type: :development
111
+ - !ruby/object:Gem::Dependency
112
+ name: pry-nav
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirement: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ prerelease: false
124
+ type: :development
125
+ - !ruby/object:Gem::Dependency
126
+ name: rake-rspec
127
+ version_requirements: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirement: !ruby/object:Gem::Requirement
133
+ requirements:
134
+ - - '>='
135
+ - !ruby/object:Gem::Version
136
+ version: '0'
137
+ prerelease: false
138
+ type: :development
139
+ - !ruby/object:Gem::Dependency
140
+ name: rake
141
+ version_requirements: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirement: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - '>='
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ prerelease: false
152
+ type: :runtime
153
+ - !ruby/object:Gem::Dependency
154
+ name: aws-sdk
155
+ version_requirements: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ~>
158
+ - !ruby/object:Gem::Version
159
+ version: '1.0'
160
+ requirement: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ~>
163
+ - !ruby/object:Gem::Version
164
+ version: '1.0'
165
+ prerelease: false
166
+ type: :runtime
167
+ - !ruby/object:Gem::Dependency
168
+ name: crimp
169
+ version_requirements: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ requirement: !ruby/object:Gem::Requirement
175
+ requirements:
176
+ - - '>='
177
+ - !ruby/object:Gem::Version
178
+ version: '0'
179
+ prerelease: false
180
+ type: :runtime
181
+ - !ruby/object:Gem::Dependency
182
+ name: alephant-support
183
+ version_requirements: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - '>='
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ requirement: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - '>='
191
+ - !ruby/object:Gem::Version
192
+ version: '0'
193
+ prerelease: false
194
+ type: :runtime
195
+ - !ruby/object:Gem::Dependency
196
+ name: alephant-sequencer
197
+ version_requirements: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - '>='
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ requirement: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - '>='
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ prerelease: false
208
+ type: :runtime
209
+ - !ruby/object:Gem::Dependency
210
+ name: alephant-cache
211
+ version_requirements: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - '>='
214
+ - !ruby/object:Gem::Version
215
+ version: '0'
216
+ requirement: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - '>='
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ prerelease: false
222
+ type: :runtime
223
+ - !ruby/object:Gem::Dependency
224
+ name: alephant-logger
225
+ version_requirements: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - '>='
228
+ - !ruby/object:Gem::Version
229
+ version: '0'
230
+ requirement: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - '>='
233
+ - !ruby/object:Gem::Version
234
+ version: '0'
235
+ prerelease: false
236
+ type: :runtime
237
+ - !ruby/object:Gem::Dependency
238
+ name: alephant-lookup
239
+ version_requirements: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - '>='
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ requirement: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - '>='
247
+ - !ruby/object:Gem::Version
248
+ version: '0'
249
+ prerelease: false
250
+ type: :runtime
251
+ - !ruby/object:Gem::Dependency
252
+ name: alephant-renderer
253
+ version_requirements: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ~>
256
+ - !ruby/object:Gem::Version
257
+ version: 0.1.0
258
+ requirement: !ruby/object:Gem::Requirement
259
+ requirements:
260
+ - - ~>
261
+ - !ruby/object:Gem::Version
262
+ version: 0.1.0
263
+ prerelease: false
264
+ type: :runtime
265
+ description: Static publishing to S3 based on SQS messages
266
+ email:
267
+ - charlierevett@gmail.com
268
+ executables: []
269
+ extensions: []
270
+ extra_rdoc_files: []
271
+ files:
272
+ - .gitignore
273
+ - .travis.yml
274
+ - Gemfile
275
+ - Guardfile
276
+ - LICENSE.txt
277
+ - README.md
278
+ - Rakefile
279
+ - alephant-publisher-queue.gemspec
280
+ - lib/alephant/publisher/env.rb
281
+ - lib/alephant/publisher/queue.rb
282
+ - lib/alephant/publisher/queue/options.rb
283
+ - lib/alephant/publisher/queue/processor.rb
284
+ - lib/alephant/publisher/queue/processor/base.rb
285
+ - lib/alephant/publisher/queue/sqs_helper/archiver.rb
286
+ - lib/alephant/publisher/queue/sqs_helper/queue.rb
287
+ - lib/alephant/publisher/queue/version.rb
288
+ - lib/alephant/publisher/queue/writer.rb
289
+ - spec/archiver_spec.rb
290
+ - spec/processor_spec.rb
291
+ - spec/publisher_spec.rb
292
+ - spec/queue_spec.rb
293
+ - spec/spec_helper.rb
294
+ - spec/writer_spec.rb
295
+ homepage: https://github.com/BBC-News/alephant-publisher-queue
296
+ licenses:
297
+ - MIT
298
+ metadata: {}
299
+ post_install_message:
300
+ rdoc_options: []
301
+ require_paths:
302
+ - lib
303
+ required_ruby_version: !ruby/object:Gem::Requirement
304
+ requirements:
305
+ - - '>='
306
+ - !ruby/object:Gem::Version
307
+ version: '0'
308
+ required_rubygems_version: !ruby/object:Gem::Requirement
309
+ requirements:
310
+ - - '>='
311
+ - !ruby/object:Gem::Version
312
+ version: '0'
313
+ requirements: []
314
+ rubyforge_project:
315
+ rubygems_version: 2.1.9
316
+ signing_key:
317
+ specification_version: 4
318
+ summary: Static publishing to S3 based on SQS messages
319
+ test_files:
320
+ - spec/archiver_spec.rb
321
+ - spec/processor_spec.rb
322
+ - spec/publisher_spec.rb
323
+ - spec/queue_spec.rb
324
+ - spec/spec_helper.rb
325
+ - spec/writer_spec.rb