pebblebed 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Rakefile +1 -0
- data/lib/pebblebed/clients/abstract_client.rb +21 -0
- data/lib/pebblebed/clients/checkpoint_client.rb +45 -0
- data/lib/pebblebed/clients/generic_client.rb +33 -0
- data/lib/pebblebed/clients/quorum_client.rb +30 -0
- data/lib/pebblebed/config.rb +57 -0
- data/lib/pebblebed/connector.rb +38 -0
- data/lib/pebblebed/http.rb +98 -0
- data/lib/pebblebed/parts.rb +124 -0
- data/lib/pebblebed/sinatra.rb +79 -0
- data/lib/pebblebed/uid.rb +76 -0
- data/lib/pebblebed/version.rb +3 -0
- data/lib/pebblebed.rb +10 -0
- data/pebblebed.gemspec +36 -0
- data/spec/checkpoint_client_spec.rb +116 -0
- data/spec/config_spec.rb +27 -0
- data/spec/connector_spec.rb +23 -0
- data/spec/generic_client_spec.rb +38 -0
- data/spec/http_spec.rb +20 -0
- data/spec/mockcached.rb +25 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/uid_spec.rb +76 -0
- metadata +212 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'deepstruct'
|
2
|
+
|
3
|
+
module Pebblebed
|
4
|
+
class AbstractClient
|
5
|
+
def perform(method, url = '', params = {}, &block)
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
8
|
+
|
9
|
+
def get(*args, &block)
|
10
|
+
perform(:get, *args, &block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def post(*args, &block)
|
14
|
+
perform(:post, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(*args, &block)
|
18
|
+
perform(:delete, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Pebblebed
|
2
|
+
class CheckpointClient < Pebblebed::GenericClient
|
3
|
+
def me
|
4
|
+
return @identity if @identity_checked
|
5
|
+
@identity_checked = true
|
6
|
+
@identity = get("/identities/me")[:identity]
|
7
|
+
end
|
8
|
+
|
9
|
+
def cache_key_for_identity_id(id)
|
10
|
+
"identity:#{id}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# Given a list of identity IDs it returns each identity or an empty hash for identities that doesnt exists.
|
14
|
+
# If pebbles are configured with memcached, results will be cached.
|
15
|
+
# Params: ids a list of identities
|
16
|
+
def find_identities(ids)
|
17
|
+
|
18
|
+
result = {}
|
19
|
+
uncached = ids
|
20
|
+
|
21
|
+
if Pebblebed.memcached
|
22
|
+
cache_keys = ids.collect {|id| cache_key_for_identity_id(id) }
|
23
|
+
result = Hash[Pebblebed.memcached.get_multi(*cache_keys).map do |key, identity|
|
24
|
+
/identity:(?<id>\d+)/ =~ key # yup, this is ugly, but an easy hack to get the actual identity id we are trying to retrieve
|
25
|
+
[id.to_i, identity]
|
26
|
+
end]
|
27
|
+
uncached = ids-result.keys
|
28
|
+
end
|
29
|
+
|
30
|
+
if uncached.size > 0
|
31
|
+
request = get("/identities/#{uncached.join(',')},")
|
32
|
+
uncached.each_with_index do |id, i|
|
33
|
+
identity = request.identities[i].identity.unwrap
|
34
|
+
result[id] = identity
|
35
|
+
Pebblebed.memcached.set(cache_key_for_identity_id(id), identity, ttl=60*15) if Pebblebed.memcached
|
36
|
+
end
|
37
|
+
end
|
38
|
+
return DeepStruct.wrap(ids.collect {|id| result[id]})
|
39
|
+
end
|
40
|
+
|
41
|
+
def god?
|
42
|
+
me.god if me
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'deepstruct'
|
2
|
+
|
3
|
+
module Pebblebed
|
4
|
+
class GenericClient < AbstractClient
|
5
|
+
def initialize(session_key, root_url)
|
6
|
+
@root_url = root_url
|
7
|
+
@root_url = URI(@root_url) unless @root_url.is_a?(URI::HTTP)
|
8
|
+
@session_key = session_key
|
9
|
+
end
|
10
|
+
|
11
|
+
def perform(method, url = '', params = {}, &block)
|
12
|
+
begin
|
13
|
+
result = Pebblebed::Http.send(method, service_url(url), service_params(params), &block)
|
14
|
+
return DeepStruct.wrap(Yajl::Parser.parse(result.body))
|
15
|
+
rescue Yajl::ParseError
|
16
|
+
return result.body
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def service_url(url)
|
21
|
+
result = @root_url.dup
|
22
|
+
result.path = result.path.sub(/\/+$/, "") + url
|
23
|
+
result
|
24
|
+
end
|
25
|
+
|
26
|
+
def service_params(params)
|
27
|
+
params ||= {}
|
28
|
+
params['session'] = @session_key if @session_key
|
29
|
+
params
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'deepstruct'
|
2
|
+
require 'futurevalue'
|
3
|
+
|
4
|
+
# A client that talks to a number of clients all at on
|
5
|
+
module Pebblebed
|
6
|
+
class QuorumClient < AbstractClient
|
7
|
+
def initialize(services, session_key)
|
8
|
+
@clients = Hash[services.map do |service|
|
9
|
+
[service, Pebblebed::GenericClient.new(session_key, Pebblebed.root_url_for(service))]
|
10
|
+
end]
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform(method, url = '', params = {}, &block)
|
14
|
+
# Using Future::Value perform the full quorum in parallel
|
15
|
+
results = @clients.map do |service, client|
|
16
|
+
response = [service]
|
17
|
+
response << Future::Value.new do
|
18
|
+
begin
|
19
|
+
client.perform(method, url, params, &block)
|
20
|
+
rescue HttpError => e
|
21
|
+
e
|
22
|
+
end
|
23
|
+
end
|
24
|
+
response
|
25
|
+
end
|
26
|
+
# Unwrap future values and thereby joining all threads
|
27
|
+
Hash[results.map{|service, response| [service, response.value]}]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Pebblebed
|
2
|
+
class Builder
|
3
|
+
def host(value)
|
4
|
+
Pebblebed.host = value
|
5
|
+
end
|
6
|
+
|
7
|
+
def memcached(value)
|
8
|
+
Pebblebed.memcached = value
|
9
|
+
end
|
10
|
+
|
11
|
+
def service(name, options = {})
|
12
|
+
Pebblebed.require_service(name, options)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.config(&block)
|
17
|
+
Builder.new.send(:instance_eval, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.require_service(name, options = {})
|
21
|
+
(@services ||= {})[name.to_sym] = options
|
22
|
+
Pebblebed::Connector.class_eval <<-END
|
23
|
+
def #{name}
|
24
|
+
self["#{name}"]
|
25
|
+
end
|
26
|
+
END
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.host
|
30
|
+
@host
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.host=(value)
|
34
|
+
@host = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.memcached
|
38
|
+
@memcached
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.memcached=(value)
|
42
|
+
@memcached = value
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.services
|
46
|
+
@services.keys
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.version_of(service)
|
50
|
+
return 1 unless @services && @services[service.to_sym]
|
51
|
+
@services[service.to_sym][:version] || 1
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.root_url_for(service, url_opts={})
|
55
|
+
URI("http://#{url_opts[:host] || self.host}/api/#{service}/v#{version_of(service)}/")
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
3
|
+
module Pebblebed
|
4
|
+
class Connector
|
5
|
+
def initialize(key = nil, url_opts = {})
|
6
|
+
@key = key
|
7
|
+
@clients = {}
|
8
|
+
@url_opts = url_opts
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](service)
|
12
|
+
client_class = self.class.client_class_for(service)
|
13
|
+
(@clients[service.to_sym] ||= client_class.new(@key, Pebblebed.root_url_for(service.to_s, @url_opts)))
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns a quorum client that talks to the provided list of
|
17
|
+
# pebbles all at once. The result is a hash of services and their
|
18
|
+
# responses. If any service returned an error, their entry
|
19
|
+
# in the hash will be an HttpError object.
|
20
|
+
def quorum(services = nil, session_key = nil)
|
21
|
+
QuorumClient.new(services || Pebblebed.services, session_key)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def parts
|
26
|
+
@parts ||= Pebblebed::Parts.new(self)
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.client_class_for(service)
|
30
|
+
class_name = ActiveSupport::Inflector.classify(service)+'Client'
|
31
|
+
begin
|
32
|
+
Pebblebed.const_get(class_name)
|
33
|
+
rescue NameError
|
34
|
+
GenericClient
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# A wrapper for all low level http client stuff
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'curl'
|
5
|
+
require 'yajl'
|
6
|
+
require 'queryparams'
|
7
|
+
require 'nokogiri'
|
8
|
+
require 'pathbuilder'
|
9
|
+
require 'active_support'
|
10
|
+
|
11
|
+
module Pebblebed
|
12
|
+
class HttpError < Exception
|
13
|
+
attr_reader :status, :message
|
14
|
+
def initialize(message, status = nil)
|
15
|
+
@message = message
|
16
|
+
@status = status
|
17
|
+
end
|
18
|
+
|
19
|
+
def not_found?
|
20
|
+
@status_code == 404
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Http
|
25
|
+
class CurlResult
|
26
|
+
def initialize(curl_result)
|
27
|
+
@curl_result = curl_result
|
28
|
+
end
|
29
|
+
|
30
|
+
def status
|
31
|
+
@curl_result.response_code
|
32
|
+
end
|
33
|
+
|
34
|
+
def url
|
35
|
+
@curl_result.url
|
36
|
+
end
|
37
|
+
|
38
|
+
def body
|
39
|
+
@curl_result.body_str
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.get(url = nil, params = nil, &block)
|
44
|
+
url, params = url_and_params_from_args(url, params, &block)
|
45
|
+
handle_curl_response(Curl::Easy.perform(url_with_params(url, params)))
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.post(url, params, &block)
|
49
|
+
url, params = url_and_params_from_args(url, params, &block)
|
50
|
+
handle_curl_response(Curl::Easy.http_post(url, *(QueryParams.encode(params).split('&'))))
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.delete(url, params, &block)
|
54
|
+
url, params = url_and_params_from_args(url, params, &block)
|
55
|
+
handle_curl_response(Curl::Easy.http_delete(url_with_params(url, params)))
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def self.handle_http_errors(result)
|
61
|
+
if result.status >= 400
|
62
|
+
errmsg = "Service request to '#{result.url}' failed (#{result.status}):"
|
63
|
+
errmsg << extract_error_summary(result.body)
|
64
|
+
raise HttpError.new(result.status, ActiveSupport::SafeBuffer.new(errmsg))
|
65
|
+
# ActiveSupport::SafeBuffer.new is the same as errmsg.html_safe in rails
|
66
|
+
end
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.handle_curl_response(curl_response)
|
71
|
+
handle_http_errors(CurlResult.new(curl_response))
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.url_with_params(url, params)
|
75
|
+
url.query = QueryParams.encode(params || {})
|
76
|
+
url.to_s
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.url_and_params_from_args(url, params = nil, &block)
|
80
|
+
if block_given?
|
81
|
+
pathbuilder = PathBuilder.new.send(:instance_eval, &block)
|
82
|
+
url = url.dup
|
83
|
+
url.path = url.path.chomp("/")+pathbuilder.path
|
84
|
+
(params ||= {}).merge!(pathbuilder.params)
|
85
|
+
end
|
86
|
+
[url, params]
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.extract_error_summary(body)
|
90
|
+
# Supports Sinatra error pages
|
91
|
+
extract = Nokogiri::HTML(body).css('#summary').text.gsub(/\s+/, ' ').strip
|
92
|
+
# TODO: Rails?
|
93
|
+
return body if extract == ''
|
94
|
+
extract
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# A class to help consumers of layout parts embed parts
|
2
|
+
|
3
|
+
class Pebblebed::Parts
|
4
|
+
|
5
|
+
attr_reader :composition_strategy
|
6
|
+
|
7
|
+
# A composition strategy is a way to get from an url and a params hash to markup that will
|
8
|
+
# render the part into the page. Pluggable strategies to support different production
|
9
|
+
# environments. SSI is provided by Nginx, ESI by Varnish and :direct is a fallback
|
10
|
+
# for the development environment.
|
11
|
+
|
12
|
+
@composition_strategies = {}
|
13
|
+
|
14
|
+
def initialize(connector)
|
15
|
+
@composition_strategy = :ssi # <- It's the best! Use Nginx with SSI
|
16
|
+
@preloadable = {} # <- A cache to remember which parts are preloadable
|
17
|
+
@connector = connector
|
18
|
+
end
|
19
|
+
|
20
|
+
# All part manifests for all configured pebbles as a hash of DeepStructs
|
21
|
+
def manifests
|
22
|
+
@manifests ||= @connector.quorum.get("/parts")
|
23
|
+
@manifests.each { |k,v| @manifests.delete(k) if v.is_a?(Pebblebed::HttpError) }
|
24
|
+
@manifests
|
25
|
+
end
|
26
|
+
|
27
|
+
def reload_manifest
|
28
|
+
@manifests = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# The strategy for composing multiple parts into a page. Default: `:ssi`
|
32
|
+
def composition_strategy=(value)
|
33
|
+
raise ArgumentError, "Unknown composition strategy '#{value}'" unless composition_strategies.keys.include?(value)
|
34
|
+
@composition_strategy = value
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generates the markup for the part according to the composition strategy
|
38
|
+
def markup(partspec, params = nil)
|
39
|
+
["<div data-pebbles-component=\"#{partspec}\" #{self.class.data_attributes(params || {})}>",
|
40
|
+
composition_markup_from_partspec(partspec, params),
|
41
|
+
"</div>"].join
|
42
|
+
end
|
43
|
+
|
44
|
+
def stylesheet_urls
|
45
|
+
manifests.keys.map do |service|
|
46
|
+
@connector[service].service_url("/parts/assets/parts.css")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def javascript_urls
|
51
|
+
manifests.keys.map do |service|
|
52
|
+
@connector[service].service_url("/parts/assets/parts.js")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Register a new composition strategy handler. The block must accept two parameters:
|
57
|
+
# |url, params| and is expected to return whatever markup is needed to make it happen.
|
58
|
+
def self.register_composition_strategy(name, &block)
|
59
|
+
@composition_strategies[name.to_sym] = block
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Check the manifests to see if the part has a server side action implemented.
|
65
|
+
def preloadable?(partspec)
|
66
|
+
@preloadable[partspec] ||= raw_part_is_preloadable?(partspec)
|
67
|
+
end
|
68
|
+
|
69
|
+
def raw_part_is_preloadable?(partspec)
|
70
|
+
service, part = self.class.parse_partspec(partspec)
|
71
|
+
return false unless service = manifests[service.to_sym]
|
72
|
+
return false unless part_record = service[part]
|
73
|
+
return false if part_record.is_a?(::Pebblebed::HttpError)
|
74
|
+
return part_record.part.preloadable
|
75
|
+
end
|
76
|
+
|
77
|
+
def composition_markup_from_partspec(partspec, params)
|
78
|
+
return '' unless preloadable?(partspec)
|
79
|
+
service, part = self.class.parse_partspec(partspec)
|
80
|
+
composition_markup(
|
81
|
+
@connector[service].service_url("/parts/#{part}"), params)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.composition_strategies
|
85
|
+
@composition_strategies
|
86
|
+
end
|
87
|
+
|
88
|
+
def composition_markup(url, params)
|
89
|
+
self.class.composition_strategies[@composition_strategy].call(url, params)
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.parse_partspec(partspec)
|
93
|
+
/^(?<service>[^\.]+)\.(?<part>.*)$/ =~ partspec
|
94
|
+
[service, part]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Create a string of data-attributes from a hash
|
98
|
+
def self.data_attributes(hash)
|
99
|
+
hash = hash.dup
|
100
|
+
hash.select{ |k| k != :session }.
|
101
|
+
map { |k,v| "data-#{k.to_s}=\"#{v}\"" }.join(' ')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# -------------------------------------------------------------------------------
|
106
|
+
|
107
|
+
# SSI (Nginx): http://wiki.nginx.org/HttpSsiModule
|
108
|
+
Pebblebed::Parts.register_composition_strategy :ssi do |url, params|
|
109
|
+
"<!--# include virtual=\"#{URI.parse(url.to_s).path}?#{QueryParams.encode(params || {})}\" -->"
|
110
|
+
end
|
111
|
+
|
112
|
+
# ESI (Varnish): https://www.varnish-cache.org/trac/wiki/ESIfeatures
|
113
|
+
Pebblebed::Parts.register_composition_strategy :esi do |url, params|
|
114
|
+
"<esi:include src=\"#{URI.parse(url.to_s).path}?#{QueryParams.encode(params || {})}\"\/>"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Just fetches the content and returns it. ONLY FOR DEVELOPMENT
|
118
|
+
Pebblebed::Parts.register_composition_strategy :direct do |url, params|
|
119
|
+
begin
|
120
|
+
Pebblebed::Connector.new.get(url, params)
|
121
|
+
rescue HttpError => e
|
122
|
+
"<span class='pebbles_error'>'#{url}' with parameters #{params.to_json} failed (#{e.status}): #{e.message}</span>"
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Extends Sinatra for maximum pebble pleasure
|
2
|
+
require 'pebblebed'
|
3
|
+
|
4
|
+
module Sinatra
|
5
|
+
module Pebblebed
|
6
|
+
module Helpers
|
7
|
+
# Render the markup for a part. A partspec takes the form
|
8
|
+
# "<kit>.<partname>", e.g. "base.post"
|
9
|
+
def part(partspec, params = {})
|
10
|
+
params[:session] ||= current_session
|
11
|
+
pebbles.parts.markup(partspec, params)
|
12
|
+
end
|
13
|
+
|
14
|
+
def parts_script_include_tags
|
15
|
+
@script_include_tags ||= pebbles.parts.javascript_urls.map do |url|
|
16
|
+
"<script src=\"#{url.to_s}\"></script>"
|
17
|
+
end.join
|
18
|
+
end
|
19
|
+
|
20
|
+
def parts_stylesheet_include_tags
|
21
|
+
@stylesheet_include_tags ||= pebbles.parts.stylesheet_urls.map do |url|
|
22
|
+
"<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"#{url.to_s}\">"
|
23
|
+
end.join
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_session
|
27
|
+
params[:session] || request.cookies['checkpoint.session']
|
28
|
+
end
|
29
|
+
alias :checkpoint_session :current_session
|
30
|
+
|
31
|
+
def pebbles
|
32
|
+
@pebbles ||= ::Pebblebed::Connector.new(checkpoint_session, :host => request.host)
|
33
|
+
end
|
34
|
+
|
35
|
+
def current_identity
|
36
|
+
pebbles.checkpoint.me
|
37
|
+
end
|
38
|
+
|
39
|
+
def require_identity
|
40
|
+
unless current_identity.respond_to?(:id)
|
41
|
+
halt 403, "No current identity."
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def current_identity_is?(identity_id)
|
46
|
+
require_identity
|
47
|
+
unless (current_identity.id == identity_id || current_identity.god)
|
48
|
+
halt 403, "Private resource"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def require_god
|
53
|
+
require_identity
|
54
|
+
halt 403, "Current identity #{current_identity.id} is not god" unless current_identity.god
|
55
|
+
end
|
56
|
+
|
57
|
+
def require_parameters(parameters, *keys)
|
58
|
+
missing = keys.map(&:to_s) - (parameters ? parameters.keys : [])
|
59
|
+
halt 409, "missing parameters: #{missing.join(', ')}" unless missing.empty?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.registered(app)
|
64
|
+
app.helpers(Sinatra::Pebblebed::Helpers)
|
65
|
+
app.get "/ping" do
|
66
|
+
"{\"name\":#{(app.service_name || 'undefined').to_s.inspect}}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def declare_pebbles(&block)
|
71
|
+
::Pebblebed.config(&block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def i_am(service_name)
|
75
|
+
set :service_name, service_name
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Pebblebed
|
2
|
+
class InvalidUid < StandardError; end
|
3
|
+
class Uid
|
4
|
+
def initialize(uid)
|
5
|
+
self.klass, self.path, self.oid = self.class.raw_parse(uid)
|
6
|
+
raise InvalidUid, "Missing klass in uid" unless self.klass
|
7
|
+
raise InvalidUid, "A valid uid must specify either path or oid" unless self.path || self.oid
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :klass, :path, :oid
|
11
|
+
def klass=(value)
|
12
|
+
return @klass = nil if value == '' || value.nil?
|
13
|
+
raise InvalidUid, "Invalid klass '#{value}'" unless self.class.valid_klass?(value)
|
14
|
+
@klass = value
|
15
|
+
end
|
16
|
+
def path=(value)
|
17
|
+
return @path = nil if value == '' || value.nil?
|
18
|
+
raise InvalidUid, "Invalid path '#{value}'" unless self.class.valid_path?(value)
|
19
|
+
@path = (value.strip != "") ? value : nil
|
20
|
+
end
|
21
|
+
def oid=(value)
|
22
|
+
return @oid = nil if value == '' || value.nil?
|
23
|
+
raise InvalidUid, "Invalid oid '#{value}'" unless self.class.valid_oid?(value)
|
24
|
+
@oid = (value.strip != "") ? value : nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.raw_parse(string)
|
28
|
+
/(?<klass>^[^:]+)\:(?<path>[^\$]*)?\$?(?<oid>.*$)?/ =~ string
|
29
|
+
[klass, path, oid]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.valid?(string)
|
33
|
+
begin
|
34
|
+
true if new(string)
|
35
|
+
rescue InvalidUid
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.parse(string)
|
41
|
+
uid = new(string)
|
42
|
+
[uid.klass, uid.path, uid.oid]
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.valid_label?(value)
|
46
|
+
!!(value =~ /^[a-zA-Z0-9_]+$/)
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.valid_klass?(value)
|
50
|
+
self.valid_label?(value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.valid_path?(value)
|
54
|
+
# catches a stupid edge case in ruby where "..".split('.') == [] instead of ["", "", ""]
|
55
|
+
return false if value =~ /^\.+$/
|
56
|
+
value.split('.').each do |label|
|
57
|
+
return false unless self.valid_label?(label)
|
58
|
+
end
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.valid_oid?(value)
|
63
|
+
self.valid_label?(value)
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect
|
67
|
+
"#<Pebblebed::Uid '#{to_s}'>"
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_s
|
71
|
+
"#{@klass}:#{@path}$#{@oid}".chomp("$")
|
72
|
+
end
|
73
|
+
alias_method :to_uid, :to_s
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|
data/lib/pebblebed.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require "pebblebed/version"
|
2
|
+
require 'pebblebed/http'
|
3
|
+
require 'pebblebed/connector'
|
4
|
+
require 'pebblebed/config'
|
5
|
+
require 'pebblebed/uid'
|
6
|
+
require 'pebblebed/clients/abstract_client'
|
7
|
+
require 'pebblebed/clients/generic_client'
|
8
|
+
require 'pebblebed/clients/checkpoint_client'
|
9
|
+
require 'pebblebed/clients/quorum_client'
|
10
|
+
require 'pebblebed/parts'
|
data/pebblebed.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "pebblebed/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "pebblebed"
|
7
|
+
s.version = Pebblebed::VERSION
|
8
|
+
s.authors = ["Katrina Owen", "Simen Svale Skogsrud"]
|
9
|
+
s.email = ["katrina@bengler.no", "simen@bengler.no"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Development tools for working with Pebblebed}
|
12
|
+
s.description = %q{Development tools for working with Pebblebed}
|
13
|
+
|
14
|
+
s.rubyforge_project = "pebblebed"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "simplecov"
|
25
|
+
|
26
|
+
s.add_runtime_dependency "deepstruct"
|
27
|
+
s.add_runtime_dependency "curb"
|
28
|
+
s.add_runtime_dependency "yajl-ruby"
|
29
|
+
s.add_runtime_dependency "queryparams"
|
30
|
+
s.add_runtime_dependency "futurevalue"
|
31
|
+
s.add_runtime_dependency "pathbuilder"
|
32
|
+
s.add_runtime_dependency "nokogiri"
|
33
|
+
s.add_runtime_dependency "i18n"
|
34
|
+
s.add_runtime_dependency "activesupport"
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe Pebblebed::CheckpointClient do
|
5
|
+
|
6
|
+
let(:checkpoint_client) { Pebblebed::Connector.new('session_key')[:checkpoint] }
|
7
|
+
|
8
|
+
describe "me" do
|
9
|
+
let(:canned_response_for_me) {
|
10
|
+
DeepStruct.wrap({:body=>{:identity => {:id => 1, :god => true}}.to_json})
|
11
|
+
}
|
12
|
+
|
13
|
+
it "returns current user identity upon request and caches it as an instance variable" do
|
14
|
+
checkpoint_client = Pebblebed::Connector.new('session_key')[:checkpoint]
|
15
|
+
|
16
|
+
Pebblebed::Http.should_receive(:get) { |url|
|
17
|
+
url.path.should match("/identities/me")
|
18
|
+
canned_response_for_me
|
19
|
+
}.once
|
20
|
+
checkpoint_client.me
|
21
|
+
checkpoint_client.me
|
22
|
+
end
|
23
|
+
|
24
|
+
it "tells us whether we are dealing with god allmighty himself or just another average joe" do
|
25
|
+
checkpoint_client = Pebblebed::Connector.new('session_key')[:checkpoint]
|
26
|
+
Pebblebed::Http.should_receive(:get) { |url|
|
27
|
+
url.path.should match("/identities/me")
|
28
|
+
canned_response_for_me
|
29
|
+
}.once
|
30
|
+
checkpoint_client.god?.should eq true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "cache_key_for_identity_id" do
|
35
|
+
it "creates a nice looking cache key for memcache" do
|
36
|
+
checkpoint_client = Pebblebed::Connector.new('session_key')[:checkpoint]
|
37
|
+
checkpoint_client.cache_key_for_identity_id(2).should eq "identity:2"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe "find_identities" do
|
42
|
+
let(:canned_response) {
|
43
|
+
DeepStruct.wrap({:body=>
|
44
|
+
{:identities =>
|
45
|
+
[{:identity => {:id => 1}}, {:identity => {}}, {:identity => {:id => 3}}, {:identity => {}}]
|
46
|
+
}.to_json
|
47
|
+
})
|
48
|
+
}
|
49
|
+
|
50
|
+
describe "without memcache configured" do
|
51
|
+
before(:each) do
|
52
|
+
Pebblebed.config do
|
53
|
+
host "checkpoint.dev"
|
54
|
+
service :checkpoint
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "issues an http request every time" do
|
59
|
+
Pebblebed::Http.should_receive(:get).twice.and_return canned_response
|
60
|
+
checkpoint_client.find_identities([1, 2])
|
61
|
+
checkpoint_client.find_identities([1, 2])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "with memcached configured" do
|
66
|
+
before(:each) do
|
67
|
+
Pebblebed.config do
|
68
|
+
host "checkpoint.dev"
|
69
|
+
memcached $memcached
|
70
|
+
service :checkpoint
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "issues an http request and caches it" do
|
75
|
+
Pebblebed::Http.should_receive(:get).once.and_return(canned_response)
|
76
|
+
checkpoint_client.find_identities([1, 2])
|
77
|
+
checkpoint_client.find_identities([1, 2])
|
78
|
+
|
79
|
+
$memcached.get(checkpoint_client.cache_key_for_identity_id(1)).should eq({"id"=>1})
|
80
|
+
$memcached.get(checkpoint_client.cache_key_for_identity_id(2)).should eq({})
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns exactly the same data no matter if it is cached or originating from a request" do
|
84
|
+
Pebblebed::Http.should_receive(:get).once.and_return(canned_response)
|
85
|
+
|
86
|
+
http_requested_result = checkpoint_client.find_identities([1, 2, 3])
|
87
|
+
cached_result = checkpoint_client.find_identities([1, 2, 3])
|
88
|
+
|
89
|
+
http_requested_result.unwrap.should eq cached_result.unwrap
|
90
|
+
end
|
91
|
+
|
92
|
+
it "issues a request only for not previously cached identities" do
|
93
|
+
Pebblebed::Http.should_receive(:get) { |url|
|
94
|
+
url.path.should match("/identities/1,2,4")
|
95
|
+
canned_response
|
96
|
+
}.once
|
97
|
+
checkpoint_client.find_identities([1, 2, 4])
|
98
|
+
|
99
|
+
Pebblebed::Http.should_receive(:get) { |url|
|
100
|
+
url.path.should match("/identities/3,") # Note the extra comma. Will ensure that a list is returned
|
101
|
+
canned_response
|
102
|
+
}.once
|
103
|
+
checkpoint_client.find_identities([1, 2, 3, 4])
|
104
|
+
end
|
105
|
+
|
106
|
+
it "will always return identities in the order they are requested" do
|
107
|
+
Pebblebed::Http.should_receive(:get).once.and_return(canned_response)
|
108
|
+
|
109
|
+
checkpoint_client.find_identities([1, 2, 3, 4])
|
110
|
+
identities = checkpoint_client.find_identities([4, 3, 2, 1])
|
111
|
+
identities[1].id.should eq 3
|
112
|
+
identities[3].id.should eq 1
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pebblebed do
|
4
|
+
it "has a nice dsl that configures stuff" do
|
5
|
+
Pebblebed.config do
|
6
|
+
host "example.org"
|
7
|
+
memcached $memcached
|
8
|
+
service :checkpoint
|
9
|
+
end
|
10
|
+
|
11
|
+
Pebblebed.host.should eq "example.org"
|
12
|
+
Pebblebed.memcached.should eq $memcached
|
13
|
+
Pebblebed::Connector.instance_methods.should include :checkpoint
|
14
|
+
end
|
15
|
+
|
16
|
+
it "can calculate the root uri of any pebble" do
|
17
|
+
Pebblebed.config do
|
18
|
+
service :checkpoint
|
19
|
+
service :foobar, :version => 2
|
20
|
+
end
|
21
|
+
Pebblebed.host = "example.org"
|
22
|
+
Pebblebed.root_url_for(:checkpoint).to_s.should eq "http://example.org/api/checkpoint/v1/"
|
23
|
+
Pebblebed.root_url_for(:checkpoint, :host => 'checkpoint.dev').to_s.should eq "http://checkpoint.dev/api/checkpoint/v1/"
|
24
|
+
Pebblebed.root_url_for(:foobar).to_s.should eq "http://example.org/api/foobar/v2/"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Pebblebed::Connector" do
|
4
|
+
it "can configure clients for any service" do
|
5
|
+
connector = Pebblebed::Connector.new("session_key")
|
6
|
+
client = connector['foobar']
|
7
|
+
client.class.name.should eq "Pebblebed::GenericClient"
|
8
|
+
client.instance_variable_get(:@session_key).should eq "session_key"
|
9
|
+
client.instance_variable_get(:@root_url).to_s.should =~ /api\/foobar/
|
10
|
+
end
|
11
|
+
|
12
|
+
it "caches any given client" do
|
13
|
+
connector = Pebblebed::Connector.new("session_key")
|
14
|
+
connector['foobar'].should eq connector['foobar']
|
15
|
+
end
|
16
|
+
|
17
|
+
it "fetches specific client implementations if one is provided" do
|
18
|
+
connector = Pebblebed::Connector.new("session_key")
|
19
|
+
connector['checkpoint'].class.name.should eq "Pebblebed::CheckpointClient"
|
20
|
+
connector['foobar'].class.name.should eq "Pebblebed::GenericClient"
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pebblebed::GenericClient do
|
4
|
+
it "always forwards the session key" do
|
5
|
+
client = Pebblebed::GenericClient.new("session_key", "http://example.org/")
|
6
|
+
client.service_params({})['session'].should eq "session_key"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "always converts urls to URI-objects" do
|
10
|
+
client = Pebblebed::GenericClient.new("session_key", "http://example.org/")
|
11
|
+
client.instance_variable_get(:@root_url).class.name.should eq ("URI::HTTP")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "knows how to generate a service specific url" do
|
15
|
+
client = Pebblebed::GenericClient.new("session_key", "http://example.org/")
|
16
|
+
client.service_url("/test").to_s.should eq "http://example.org/test"
|
17
|
+
client.service_url("").to_s.should eq "http://example.org"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "wraps JSON-results in a deep struct" do
|
21
|
+
curl_result = DeepStruct.wrap({status:200, body:'{"hello":"world"}'})
|
22
|
+
Pebblebed::Http.stub(:get).and_return(curl_result)
|
23
|
+
client = Pebblebed::GenericClient.new("session_key", "http://example.org/")
|
24
|
+
result = client.get "/"
|
25
|
+
result.class.name.should eq "DeepStruct::HashWrapper"
|
26
|
+
result.hello.should eq "world"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "does not wrap non-json results" do
|
30
|
+
curl_result = DeepStruct.wrap({status:200, body:'Ok'})
|
31
|
+
Pebblebed::Http.stub(:get).and_return(curl_result)
|
32
|
+
client = Pebblebed::GenericClient.new("session_key", "http://example.org/")
|
33
|
+
result = client.get "/"
|
34
|
+
result.class.name.should eq "String"
|
35
|
+
result.should eq "Ok"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/spec/http_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pebblebed::Http do
|
4
|
+
it "knows how to pack params into a http query string" do
|
5
|
+
Pebblebed::Http.send(:url_with_params, URI("/dingo/"), {a:1}).should eq "/dingo/?a=1"
|
6
|
+
end
|
7
|
+
|
8
|
+
it "knows how to combine url and parmas with results of pathbuilder" do
|
9
|
+
url, params = Pebblebed::Http.send(:url_and_params_from_args, URI("http://example.org/api"), {a:1}) do
|
10
|
+
foo.bar(:b => 2)
|
11
|
+
end
|
12
|
+
params.should eq(:a => 1, :b => 2)
|
13
|
+
url.to_s.should eq "http://example.org/api/foo/bar"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "raises an exception if there is a http-error" do
|
17
|
+
-> { Pebblebed::Http.send(:handle_http_errors, DeepStruct.wrap(status:400, url:"/foobar", body:"Oh noes")) }.should raise_error Pebblebed::HttpError
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
data/spec/mockcached.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class Mockcached
|
2
|
+
def initialize
|
3
|
+
@store = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
def set(*args)
|
7
|
+
@store[args[0]] = args[1]
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(*args)
|
11
|
+
@store[args[0]]
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_multi(*keys)
|
15
|
+
result = {}
|
16
|
+
keys.each do |key|
|
17
|
+
result[key] = get(key) if @store.has_key?(key)
|
18
|
+
end
|
19
|
+
result
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(*args)
|
23
|
+
@store.delete(args[0])
|
24
|
+
end
|
25
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require './spec/mockcached'
|
3
|
+
require 'bundler'
|
4
|
+
require 'rspec'
|
5
|
+
|
6
|
+
SimpleCov.add_filter 'spec'
|
7
|
+
SimpleCov.add_filter 'config'
|
8
|
+
SimpleCov.start
|
9
|
+
|
10
|
+
Bundler.require
|
11
|
+
|
12
|
+
RSpec.configure do |c|
|
13
|
+
c.mock_with :rspec
|
14
|
+
c.around(:each) do |example|
|
15
|
+
clear_cookies if respond_to?(:clear_cookies)
|
16
|
+
$memcached = Mockcached.new
|
17
|
+
example.run
|
18
|
+
end
|
19
|
+
end
|
data/spec/uid_spec.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Pebblebed::Uid do
|
4
|
+
it "parses a full uid correctly" do
|
5
|
+
uid = Pebblebed::Uid.new("klass:path$oid")
|
6
|
+
uid.klass.should eq "klass"
|
7
|
+
uid.path.should eq "path"
|
8
|
+
uid.oid.should eq "oid"
|
9
|
+
uid.to_s.should eq "klass:path$oid"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "parses an uid with no oid correctly" do
|
13
|
+
uid = Pebblebed::Uid.new("klass:path")
|
14
|
+
uid.klass.should eq "klass"
|
15
|
+
uid.path.should eq "path"
|
16
|
+
uid.oid.should be_nil
|
17
|
+
uid.to_s.should eq "klass:path"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "parses an uid with no path correctly" do
|
21
|
+
uid = Pebblebed::Uid.new("klass:$oid")
|
22
|
+
uid.klass.should eq "klass"
|
23
|
+
uid.path.should be_nil
|
24
|
+
uid.oid.should eq "oid"
|
25
|
+
uid.to_s.should eq "klass:$oid"
|
26
|
+
end
|
27
|
+
|
28
|
+
it "raises an exception when you try to create an invalid uid" do
|
29
|
+
-> { Pebblebed::Uid.new("!:$298") }.should raise_error Pebblebed::InvalidUid
|
30
|
+
end
|
31
|
+
|
32
|
+
it "raises an exception when you modify an uid with an invalid value" do
|
33
|
+
uid = Pebblebed::Uid.new("klass:path$oid")
|
34
|
+
-> { uid.klass = "!" }.should raise_error Pebblebed::InvalidUid
|
35
|
+
-> { uid.path = "..." }.should raise_error Pebblebed::InvalidUid
|
36
|
+
-> { uid.oid = "(/&%$" }.should raise_error Pebblebed::InvalidUid
|
37
|
+
end
|
38
|
+
|
39
|
+
it "rejects invalid labels for klass and oid" do
|
40
|
+
Pebblebed::Uid.valid_klass?("abc123").should be_true
|
41
|
+
Pebblebed::Uid.valid_klass?("abc123!").should be_false
|
42
|
+
Pebblebed::Uid.valid_klass?("").should be_false
|
43
|
+
Pebblebed::Uid.valid_oid?("abc123").should be_true
|
44
|
+
Pebblebed::Uid.valid_oid?("abc123!").should be_false
|
45
|
+
Pebblebed::Uid.valid_oid?("abc 123").should be_false
|
46
|
+
Pebblebed::Uid.valid_oid?("").should be_false
|
47
|
+
end
|
48
|
+
|
49
|
+
it "rejects invalid paths" do
|
50
|
+
Pebblebed::Uid.valid_path?("abc123").should be_true
|
51
|
+
Pebblebed::Uid.valid_path?("abc.123").should be_true
|
52
|
+
Pebblebed::Uid.valid_path?("").should be_true
|
53
|
+
Pebblebed::Uid.valid_path?("abc!.").should be_false
|
54
|
+
Pebblebed::Uid.valid_path?(".").should be_false
|
55
|
+
Pebblebed::Uid.valid_path?("ab. 123").should be_false
|
56
|
+
end
|
57
|
+
|
58
|
+
it "knows how to parse in place" do
|
59
|
+
Pebblebed::Uid.parse("klass:path$oid").should eq ['klass', 'path', 'oid']
|
60
|
+
Pebblebed::Uid.parse("post:this.is.a.path.to$object_id").should eq ['post', 'this.is.a.path.to', 'object_id']
|
61
|
+
Pebblebed::Uid.parse("post:$object_id").should eq ['post', nil, 'object_id']
|
62
|
+
end
|
63
|
+
|
64
|
+
it "knows the valid uids from the invalid ones" do
|
65
|
+
Pebblebed::Uid.valid?("F**ing H%$#!!!").should be_false
|
66
|
+
Pebblebed::Uid.valid?("").should be_false
|
67
|
+
Pebblebed::Uid.valid?("bang:").should be_false
|
68
|
+
Pebblebed::Uid.valid?(":bang").should be_false
|
69
|
+
Pebblebed::Uid.valid?(":bang$paff").should be_false
|
70
|
+
Pebblebed::Uid.valid?("$paff").should be_false
|
71
|
+
Pebblebed::Uid.valid?("a:b.c.d$e").should be_true
|
72
|
+
Pebblebed::Uid.valid?("a:$e").should be_true
|
73
|
+
Pebblebed::Uid.valid?("a:b.c.d").should be_true
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pebblebed
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Katrina Owen
|
9
|
+
- Simen Svale Skogsrud
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2011-12-18 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rspec
|
17
|
+
requirement: &70362516223620 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *70362516223620
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rake
|
28
|
+
requirement: &70362516220480 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *70362516220480
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: simplecov
|
39
|
+
requirement: &70362516217900 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *70362516217900
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: deepstruct
|
50
|
+
requirement: &70362516215200 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :runtime
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *70362516215200
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: curb
|
61
|
+
requirement: &70362516213740 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :runtime
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *70362516213740
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: yajl-ruby
|
72
|
+
requirement: &70362516211060 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *70362516211060
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: queryparams
|
83
|
+
requirement: &70362516207240 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: *70362516207240
|
92
|
+
- !ruby/object:Gem::Dependency
|
93
|
+
name: futurevalue
|
94
|
+
requirement: &70362516206260 !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
type: :runtime
|
101
|
+
prerelease: false
|
102
|
+
version_requirements: *70362516206260
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: pathbuilder
|
105
|
+
requirement: &70362516175980 !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
type: :runtime
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: *70362516175980
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: nokogiri
|
116
|
+
requirement: &70362516172200 !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
type: :runtime
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: *70362516172200
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: i18n
|
127
|
+
requirement: &70362516170520 !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ! '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
type: :runtime
|
134
|
+
prerelease: false
|
135
|
+
version_requirements: *70362516170520
|
136
|
+
- !ruby/object:Gem::Dependency
|
137
|
+
name: activesupport
|
138
|
+
requirement: &70362516163800 !ruby/object:Gem::Requirement
|
139
|
+
none: false
|
140
|
+
requirements:
|
141
|
+
- - ! '>='
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
type: :runtime
|
145
|
+
prerelease: false
|
146
|
+
version_requirements: *70362516163800
|
147
|
+
description: Development tools for working with Pebblebed
|
148
|
+
email:
|
149
|
+
- katrina@bengler.no
|
150
|
+
- simen@bengler.no
|
151
|
+
executables: []
|
152
|
+
extensions: []
|
153
|
+
extra_rdoc_files: []
|
154
|
+
files:
|
155
|
+
- .gitignore
|
156
|
+
- .rspec
|
157
|
+
- Gemfile
|
158
|
+
- Rakefile
|
159
|
+
- lib/pebblebed.rb
|
160
|
+
- lib/pebblebed/clients/abstract_client.rb
|
161
|
+
- lib/pebblebed/clients/checkpoint_client.rb
|
162
|
+
- lib/pebblebed/clients/generic_client.rb
|
163
|
+
- lib/pebblebed/clients/quorum_client.rb
|
164
|
+
- lib/pebblebed/config.rb
|
165
|
+
- lib/pebblebed/connector.rb
|
166
|
+
- lib/pebblebed/http.rb
|
167
|
+
- lib/pebblebed/parts.rb
|
168
|
+
- lib/pebblebed/sinatra.rb
|
169
|
+
- lib/pebblebed/uid.rb
|
170
|
+
- lib/pebblebed/version.rb
|
171
|
+
- pebblebed.gemspec
|
172
|
+
- spec/checkpoint_client_spec.rb
|
173
|
+
- spec/config_spec.rb
|
174
|
+
- spec/connector_spec.rb
|
175
|
+
- spec/generic_client_spec.rb
|
176
|
+
- spec/http_spec.rb
|
177
|
+
- spec/mockcached.rb
|
178
|
+
- spec/spec_helper.rb
|
179
|
+
- spec/uid_spec.rb
|
180
|
+
homepage: ''
|
181
|
+
licenses: []
|
182
|
+
post_install_message:
|
183
|
+
rdoc_options: []
|
184
|
+
require_paths:
|
185
|
+
- lib
|
186
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
187
|
+
none: false
|
188
|
+
requirements:
|
189
|
+
- - ! '>='
|
190
|
+
- !ruby/object:Gem::Version
|
191
|
+
version: '0'
|
192
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubyforge_project: pebblebed
|
200
|
+
rubygems_version: 1.8.10
|
201
|
+
signing_key:
|
202
|
+
specification_version: 3
|
203
|
+
summary: Development tools for working with Pebblebed
|
204
|
+
test_files:
|
205
|
+
- spec/checkpoint_client_spec.rb
|
206
|
+
- spec/config_spec.rb
|
207
|
+
- spec/connector_spec.rb
|
208
|
+
- spec/generic_client_spec.rb
|
209
|
+
- spec/http_spec.rb
|
210
|
+
- spec/mockcached.rb
|
211
|
+
- spec/spec_helper.rb
|
212
|
+
- spec/uid_spec.rb
|