bone 0.2.6 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,441 @@
1
+
2
+ class Bone
3
+ module API
4
+ module InstanceMethods
5
+ attr_accessor :token, :secret
6
+ def initialize(t, s=nil)
7
+ # TODO: Add size command
8
+ @token, @secret = t, s
9
+ end
10
+ def get(name)
11
+ carefully do
12
+ raise_errors
13
+ Bone.api.get token, secret, name
14
+ end
15
+ end
16
+ alias_method :[], :get
17
+ def set(name, value)
18
+ carefully do
19
+ raise_errors
20
+ Bone.api.set token, secret, name, value
21
+ end
22
+ end
23
+ alias_method :[]=, :set
24
+ def keys(filter='*')
25
+ carefully do
26
+ raise_errors
27
+ Bone.api.keys(token, secret, filter) || []
28
+ end
29
+ end
30
+ def key?(name)
31
+ carefully do
32
+ raise_errors
33
+ Bone.api.key? token, secret, name
34
+ end
35
+ end
36
+ def register(this_token, this_secret)
37
+ carefully do
38
+ Bone.api.register this_token, this_secret
39
+ end
40
+ end
41
+ def generate
42
+ carefully do
43
+ Bone.api.generate || []
44
+ end
45
+ end
46
+ def destroy(token)
47
+ carefully do
48
+ Bone.api.destroy token, secret
49
+ end
50
+ end
51
+ def token?(token)
52
+ carefully do
53
+ Bone.api.token? token, secret
54
+ end
55
+ end
56
+ private
57
+ def raise_errors
58
+ raise RuntimeError, "No token" unless token
59
+ #raise RuntimeError, "Invalid token (#{token})" if !Bone.api.token?(token)
60
+ end
61
+ def carefully
62
+ begin
63
+ yield
64
+ rescue Errno::ECONNREFUSED => ex
65
+ Bone.info ex.message
66
+ nil
67
+ rescue => ex
68
+ Bone.ld "#{ex.class}: #{ex.message}", ex.backtrace
69
+ nil
70
+ end
71
+ end
72
+ end
73
+ module ClassMethods
74
+ def get(name)
75
+ new(Bone.token, Bone.secret).get name
76
+ end
77
+ alias_method :[], :get
78
+ def set(name, value)
79
+ new(Bone.token, Bone.secret).set name, value
80
+ end
81
+ alias_method :[]=, :set
82
+ def keys(filter='*')
83
+ new(Bone.token, Bone.secret).keys filter
84
+ end
85
+ def key?(name)
86
+ new(Bone.token, Bone.secret).key? name
87
+ end
88
+ def register(this_token, this_secret)
89
+ new(Bone.token, Bone.secret).register this_token, this_secret
90
+ end
91
+ def generate
92
+ new(Bone.token, Bone.secret).generate
93
+ end
94
+ def destroy(token)
95
+ new(Bone.token, Bone.secret).destroy token
96
+ end
97
+ def token?(token)
98
+ new(Bone.token, Bone.secret).token? token
99
+ end
100
+ end
101
+ module Helpers
102
+ def path(*parts)
103
+ "/#{APIVERSION}/" << parts.flatten.collect { |v| Bone.uri_escape(v) }.join('/')
104
+ end
105
+ def prefix(*parts)
106
+ parts.flatten!
107
+ parts.unshift *[APIVERSION, 'bone']
108
+ parts.join(':')
109
+ end
110
+ end
111
+ extend Bone::API::Helpers
112
+ end
113
+
114
+ module API
115
+
116
+ module HTTP
117
+ SIGVERSION = 'v2'.freeze unless defined?(Bone::API::HTTP::SIGVERSION)
118
+ @token_suffix = 2.freeze
119
+ class << self
120
+ attr_reader :token_suffix
121
+ # /v2/[name]
122
+ def get(token, secret, name)
123
+ path = Bone::API.path(token, 'key', name)
124
+ query = {}
125
+ http_request token, secret, :get, path, query
126
+ end
127
+ def set(token, secret, name, value)
128
+ path = Bone::API.path(token, 'key', name)
129
+ query = {}
130
+ http_request token, secret, :post, path, query, value
131
+ end
132
+ def keys(token, secret, filter='*')
133
+ path = Bone::API.path(token, 'keys')
134
+ ret = http_request token, secret, :get, path, {}
135
+ (ret || '').split($/)
136
+ end
137
+ def key?(token, secret, name)
138
+ !get(token, secret, name).nil?
139
+ end
140
+ def destroy(token, secret)
141
+ query = {}
142
+ path = Bone::API.path('destroy', token)
143
+ ret = http_request token, secret, :delete, path, query
144
+ !ret.nil? # errors return nil
145
+ end
146
+ def secret(token, secret)
147
+ path = Bone::API.path(token, 'secret')
148
+ ret = http_request token, secret, :get, path, {}
149
+ end
150
+ def register(token, secret)
151
+ query = {}
152
+ path = Bone::API.path('register', token)
153
+ http_request token, secret, :post, path, query, secret
154
+ end
155
+ def generate
156
+ path = Bone::API.path('generate')
157
+ ret = http_request '', '', :post, path, {}
158
+ ret.nil? ? nil : ret.split($/)
159
+ end
160
+ def token?(token, secret)
161
+ path = Bone::API.path(token)
162
+ query = {}
163
+ ret = http_request token, secret, :get, path, query
164
+ !ret.nil?
165
+ end
166
+ def connect
167
+ require 'em-http-request' # TODO: catch error, deregister this API
168
+ @external_em = EM.reactor_running?
169
+ #@retry_delay, @redirects, @max_retries, @performed_retries = 2, 1, 2, 0
170
+ end
171
+
172
+ def canonical_time now=Time.now
173
+ now.utc.to_i
174
+ end
175
+
176
+ def canonical_host host
177
+ if URI === host
178
+ host.port ||= 80
179
+ host = [host.host.to_s, host.port.to_s].join(':')
180
+ end
181
+ host.downcase
182
+ end
183
+
184
+ # Based on / stolen from: https://github.com/chneukirchen/rack/blob/master/lib/rack/utils.rb
185
+ # which was based on / stolen from Mongrel
186
+ def parse_query(qs, d = '&;')
187
+ params = {}
188
+ (qs || '').split(/[#{d}] */n).each do |p|
189
+ k, v = p.split('=', 2).map { |x| Bone.uri_unescape(x) }
190
+ if cur = params[k]
191
+ if cur.class == Array
192
+ params[k] << v
193
+ else
194
+ params[k] = [cur, v]
195
+ end
196
+ else
197
+ params[k] = v
198
+ end
199
+ end
200
+ return params
201
+ end
202
+
203
+ # Builds the canonical string for signing requests. This strips out all '&', '?', and '='
204
+ # from the query string to be signed. The parameters in the path passed in must already
205
+ # be sorted in case-insensitive alphabetical order and must not be url encoded.
206
+ #
207
+ # Based on / stolen from: https://github.com/grempe/amazon-ec2/blob/master/lib/AWS.rb
208
+ #
209
+ # See also: http://docs.amazonwebservices.com/AWSEC2/2009-04-04/DeveloperGuide/index.html?using-query-api.html
210
+ #
211
+ def canonical_sig_string host, meth, path, query, body=nil
212
+ # Sort, and encode parameters into a canonical string.
213
+ sorted_params = query.sort {|x,y| x[0].to_s <=> y[0].to_s }
214
+ encoded_params = sorted_params.collect do |p|
215
+ encoded = [Bone.uri_escape(p[0]), Bone.uri_escape(p[1])].join '='
216
+ # Ensure spaces are encoded as '%20', not '+'
217
+ encoded = encoded.gsub '+', '%20'
218
+ # According to RFC3986 (the scheme for values expected
219
+ # by signing requests), '~' should not be encoded
220
+ encoded = encoded.gsub '%7E', '~'
221
+ end
222
+ querystr = encoded_params.join '&'
223
+ parts = [meth.to_s.downcase, canonical_host(host), path, querystr]
224
+ parts << body unless body.to_s.empty?
225
+ parts.join "\n"
226
+ end
227
+
228
+ # Encodes the given string with the secret_access_key by taking the
229
+ # hmac-sha1 sum, and then base64 encoding it. Optionally, it will also
230
+ # url encode the result of that to protect the string if it's going to
231
+ # be used as a query string parameter.
232
+ #
233
+ # Based on / stolen from: https://github.com/grempe/amazon-ec2/blob/master/lib/AWS.rb
234
+ def encode secret, str, escape=true
235
+ digest = OpenSSL::HMAC.digest Bone.digest_type.new, secret.to_s, str.to_s
236
+ b64_hmac = Base64.encode64(digest).tr "\n", ''
237
+ escape ? Bone.uri_escape(b64_hmac) : b64_hmac
238
+ end
239
+
240
+ def prepare_query query={}, token=Bone.token, stamp=canonical_time
241
+ { "sigversion" => Bone::API::HTTP::SIGVERSION,
242
+ "apiversion" => Bone::APIVERSION,
243
+ "token" => token,
244
+ "stamp" => stamp
245
+ }.merge query
246
+ end
247
+
248
+ def sign_query token, secret, meth, path, query, body=nil
249
+ sig = generate_signature secret, Bone.source, meth, path, query, body
250
+ { 'sig' => sig }.merge query
251
+ end
252
+
253
+ # Based on / stolen from: https://github.com/grempe/amazon-ec2/blob/master/lib/AWS.rb
254
+ def generate_signature secret, host, meth, path, query, body=nil
255
+ str = canonical_sig_string host, meth, path, query, body
256
+ sig = encode secret, str
257
+ Bone.ld [sig, str, body].inspect
258
+ sig
259
+ end
260
+
261
+ private
262
+
263
+ # based on: https://github.com/EmmanuelOga/firering/blob/master/lib/firering/connection.rb
264
+ def http_request token, secret, meth, path, query={}, body=nil
265
+ uri = Bone.source.clone
266
+ uri.path = path
267
+ query = prepare_query query, token
268
+ signed_query = sign_query token, secret, meth, path, query, body
269
+ Bone.ld "#{meth} #{uri} (#{query})"
270
+ content, status, headers = nil
271
+ handler = Proc.new do |http|
272
+ content, status, headers = http.response, http.response_header.status, http.response_header
273
+ end
274
+ if @external_em
275
+ em_request meth, uri, signed_query, body, &handler
276
+ else
277
+ EM.run {
278
+ em_request meth, uri, signed_query, body, &handler
279
+ }
280
+ end
281
+ if status >= 400
282
+ Bone.ld "Request failed: #{status} #{content}"
283
+ nil
284
+ else
285
+ content
286
+ end
287
+ end
288
+
289
+ def em_request meth, uri, query, body, &blk
290
+ args = { :query => query, :timeout => 10 }
291
+ args[:head] = {}
292
+ args[:body] = body.to_s unless body.nil?
293
+ http = EventMachine::HttpRequest.new(uri).send(meth, args)
294
+ http.errback do
295
+ #perform_retry(http) do
296
+ # http(method, path, data, &callback)
297
+ #end
298
+ Bone.info "Could not access #{uri}"
299
+ EventMachine.stop @external_em
300
+ end
301
+ http.callback {
302
+ Bone.ld "#{http.response_header.status}: #{http.response_header.inspect}"
303
+ #reset_retries_counter
304
+ blk.call(http) if blk
305
+ EventMachine.stop unless @external_em
306
+ }
307
+ http
308
+ end
309
+
310
+ end
311
+ Bone.register_api :http, self
312
+ Bone.register_api :https, self
313
+ end
314
+ module Redis
315
+ @token_suffix = 1.freeze
316
+ extend self
317
+ attr_reader :token_suffix
318
+ attr_accessor :redis
319
+ def get(token, secret, name)
320
+ Key.new(token, name).value.get # get returns nil if not set
321
+ end
322
+ def set(token, secret, name, value)
323
+ Key.new(token, name).value = value
324
+ Token.new(token).keys.add Time.now.utc.to_f, name
325
+ value.to_s
326
+ end
327
+ def keys(token, secret, filter='*')
328
+ Token.new(token).keys.to_a
329
+ end
330
+ def key?(token, secret, name)
331
+ Key.new(token, name).value.exists?
332
+ end
333
+ def destroy(token, secret)
334
+ Token.tokens.delete token
335
+ Token.new(token).secret.destroy!
336
+ end
337
+ def register(token, secret)
338
+ raise RuntimeError, "Could not generate token" if token.nil? || token?(token)
339
+ Token.tokens.add Time.now.utc.to_i, token
340
+ t = Token.new(token).secret = secret
341
+ token
342
+ end
343
+ def generate
344
+ begin
345
+ token = Bone.random_token
346
+ attempts ||= 10
347
+ end while token?(token) && !(attempts -= 1).zero?
348
+ secret = Bone.random_secret
349
+ raise RuntimeError, "Could not generate token" if token.nil? || token?(token)
350
+ Token.tokens.add Time.now.utc.to_i, token
351
+ t = Token.new(token).secret = secret
352
+ [token, secret]
353
+ end
354
+ def secret token
355
+ Token.new(token).secret.value
356
+ end
357
+ def token?(token, secret=nil)
358
+ Token.tokens.member?(token.to_s)
359
+ end
360
+ def connect
361
+ Familia.uri = Bone.source
362
+ end
363
+ class Key
364
+ include Familia
365
+ prefix Bone::API.prefix(:key)
366
+ string :value
367
+ attr_reader :token, :name, :bucket
368
+ def initialize(token, name, bucket=:global)
369
+ @token, @name, @bucket = token.to_s, name.to_s, bucket.to_s
370
+ initialize_redis_objects
371
+ end
372
+ def index
373
+ [token, bucket, name].join ':'
374
+ end
375
+ end
376
+ class Token
377
+ include Familia
378
+ prefix Bone::API.prefix(:token)
379
+ string :secret
380
+ zset :keys
381
+ class_zset :tokens
382
+ index :token
383
+ attr_reader :token
384
+ def initialize(token)
385
+ @token = token.to_s
386
+ initialize_redis_objects
387
+ end
388
+ end
389
+
390
+ Bone.register_api :redis, self
391
+ end
392
+
393
+ module Memory
394
+ extend self
395
+ @token_suffix = 0.freeze
396
+ attr_reader :token_suffix
397
+ @data, @tokens = {}, {}
398
+ def get(token, secret, name)
399
+ @data[Bone::API.prefix(token, name)]
400
+ end
401
+ def set(token, secret, name, value)
402
+ @data[Bone::API.prefix(token, name)] = value.to_s
403
+ end
404
+ def keys(token, secret, filter='*')
405
+ filter = '.+' if filter == '*'
406
+ filter = Bone::API.prefix(token, filter)
407
+ @data.keys.select { |name| name =~ /#{filter}/ }
408
+ end
409
+ def key?(token, secret, name)
410
+ @data.has_key?(Bone::API.prefix(token, name))
411
+ end
412
+ def destroy(token, secret)
413
+ @tokens.delete token
414
+ end
415
+ def register(token, secret)
416
+ raise RuntimeError, "Could not generate token" if token.nil? || token?(token)
417
+ @tokens[token] = secret
418
+ token
419
+ end
420
+ def secret(token)
421
+ @tokens[token]
422
+ end
423
+ def generate
424
+ begin
425
+ token = Bone.random_token
426
+ attemps ||= 10
427
+ end while token?(token) && !(attempts -= 1).zero?
428
+ secret = Bone.random_secret
429
+ @tokens[token] = secret
430
+ [token, secret]
431
+ end
432
+ def token?(token, secret=nil)
433
+ @tokens.key?(token)
434
+ end
435
+ def connect
436
+ end
437
+ Bone.register_api :memory, self
438
+ end
439
+
440
+ end
441
+ end
@@ -1,39 +1,49 @@
1
1
  require 'bone'
2
- require 'net/http'
2
+
3
+ # TODO: finish this
3
4
 
4
5
  class Bone::CLI < Drydock::Command
5
6
 
6
7
  def check!
7
- @token = @global.t || ENV['BONE_TOKEN']
8
- raise Bone::BadBone, @token unless Bone.valid_token?(@token)
8
+ @token = @global.token || ENV['BONE_TOKEN']
9
+ raise Bone::NoToken, @token unless Bone.token?(@token)
10
+ Bone.token = @token
9
11
  end
10
12
 
11
13
  def get
12
14
  check!
13
15
  @argv.unshift @alias unless @alias == 'get'
14
- raise "No key specified" unless @argv.first
15
- puts Bone.get(@argv.first)
16
- end
17
-
18
- def del
19
- check!
20
- raise "No key specified" unless @argv.first
21
- puts Bone.del(@argv.first)
16
+ ## TODO: handle bone name=value
17
+ ##if @alias.index('=') > 0
18
+ ## a = @alias.gsub(/\s+=\s+/, '=')
19
+ ## name, value = *( ? @argv.first.split('=') : @argv)
20
+ ##end
21
+ raise Bone::Problem, "No key specified" unless @argv.first
22
+ ret = Bone.get(@argv.first)
23
+ puts ret unless ret.nil?
22
24
  end
23
25
 
24
26
  def set
27
+ # TODO: use STDIN instead of @option.string
25
28
  check!
26
- opts = {:token => @token }
27
- keyname, value = *(@argv.size == 1 ? @argv.first.split('=') : @argv)
28
- raise "No key specified" unless keyname
29
- raise "No value specified" unless value
30
- if File.exists?(value) && !@option.string
31
- value = File.readlines(value).join
32
- opts[:file] = true
29
+ name, value = *(@argv.size == 1 ? @argv.first.split('=') : @argv)
30
+ raise Bone::Problem, "No key specified" unless name
31
+ from_stdin = false
32
+ if value.nil? && !stdin.tty? && !stdin.eof?
33
+ from_stdin = true
34
+ value = stdin.read
33
35
  end
34
- puts Bone.set(keyname, value, opts)
36
+ raise Bone::Problem, "Cannot set null value" unless value
37
+ Bone[name] = value
38
+ puts from_stdin ? '<STDIN>' : value
35
39
  end
36
40
 
41
+ #def del
42
+ # check!
43
+ # raise Bone::Problem, "No key specified" unless @argv.first
44
+ # puts Bone.delete(@argv.first)
45
+ #end
46
+
37
47
  def keys
38
48
  check!
39
49
  list = Bone.keys(@argv[0])
@@ -47,23 +57,27 @@ class Bone::CLI < Drydock::Command
47
57
  end
48
58
 
49
59
  def token
50
- check!
51
- if @option.force
52
- generate_token_dialog
53
- else
54
- puts @token
60
+ check!
61
+ puts Bone.token
62
+ end
63
+
64
+ def secret
65
+ check!
66
+ puts Bone.secret
67
+ end
68
+
69
+ def generate
70
+ t, s = *Bone.generate
71
+ unless t.nil?
72
+ puts "# Your token for #{Bone.source}"
73
+ puts "BONE_TOKEN=#{t}"
74
+ puts "BONE_SECRET=#{s}"
55
75
  end
56
- rescue Bone::BadBone => ex
57
- generate_token_dialog
58
- exit 1
76
+ #rescue Bone::NoToken => ex
77
+ # update_token_dialog
78
+ # exit 1
59
79
  end
60
80
 
61
81
  private
62
- def generate_token_dialog
63
- newtoken = Bone.generate_token
64
- puts newtoken and return if @global.quiet
65
- puts "Set the BONE_TOKEN environment variable with the following token"
66
- puts newtoken
67
- end
68
82
 
69
83
  end