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.
- data/Gemfile +9 -0
- data/Gemfile.lock +26 -0
- data/LICENSE.txt +60 -19
- data/README.md +45 -22
- data/Rakefile +44 -39
- data/VERSION.yml +4 -0
- data/bin/bone +22 -14
- data/bone.gemspec +78 -44
- data/config/redis-server.conf +195 -0
- data/lib/bone.rb +154 -163
- data/lib/bone/api.rb +441 -0
- data/lib/bone/cli.rb +47 -33
- data/try/10_generate_try.rb +17 -0
- data/try/16_http_try.rb +73 -0
- data/try/20_instance_try.rb +29 -0
- data/try/25_key_token_object_try.rb +67 -0
- data/try/30_api_memory_try.rb +74 -0
- data/try/31_api_redis_try.rb +73 -0
- data/try/32_api_http_try.rb +68 -0
- data/try/manual_cli.rb +24 -0
- metadata +67 -19
- data/try/bone.rb +0 -9
data/lib/bone/api.rb
ADDED
@@ -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
|
data/lib/bone/cli.rb
CHANGED
@@ -1,39 +1,49 @@
|
|
1
1
|
require 'bone'
|
2
|
-
|
2
|
+
|
3
|
+
# TODO: finish this
|
3
4
|
|
4
5
|
class Bone::CLI < Drydock::Command
|
5
6
|
|
6
7
|
def check!
|
7
|
-
@token = @global.
|
8
|
-
raise Bone::
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
puts
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
value =
|
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
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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::
|
57
|
-
|
58
|
-
|
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
|