alephant-publisher-queue 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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