onering-agent 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/bin/onering +68 -0
- data/lib/etc/facter.list +19 -0
- data/lib/onering.rb +32 -0
- data/lib/onering/api.rb +322 -0
- data/lib/onering/cli.rb +92 -0
- data/lib/onering/cli/assets.rb +138 -0
- data/lib/onering/cli/automation.rb +62 -0
- data/lib/onering/cli/call.rb +53 -0
- data/lib/onering/cli/devices.rb +98 -0
- data/lib/onering/cli/fact.rb +22 -0
- data/lib/onering/cli/reporter.rb +121 -0
- data/lib/onering/config.rb +62 -0
- data/lib/onering/logger.rb +141 -0
- data/lib/onering/plugins/assets.rb +54 -0
- data/lib/onering/plugins/authentication.rb +35 -0
- data/lib/onering/plugins/automation.rb +70 -0
- data/lib/onering/plugins/reporter.rb +360 -0
- data/lib/onering/util.rb +150 -0
- data/lib/onering/version.rb +8 -0
- metadata +188 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NTliNjI0MzU5MTllMGFkNDg1OGQxMjBhNzBmZmFhOTIyMWY2ZTBkYg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
YjQ5Y2NiOTlkZjQ5YjE3OTIyZmZiNjZhNjdhOWI3MGJhNjhhMzgxZQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YzFiYjNlMWYyMmY0MzZkM2NhMjBlZGVlYzE4Y2UyZjRjNDlhOGNmNzk1ZDdk
|
10
|
+
M2E2Njg2MDM2ZDg2YzcwYzk3ZjY0YmRlYWMyNzgzMmMyZDNiZGJhMWJjNWZj
|
11
|
+
MGNkNGU1NjNhYmRhMDY4MDA2NWVhYTNhMzk5N2Y4NThiMTI3MjA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjE5MzQ5ZTlhYzc1ZjIxNDkxMmE5NDM5OWRlYjgzNWVlYzQyNmVhN2U4ZTQ4
|
14
|
+
YjM2ZWM2MTZjODEwNWI2ZDM3Mjc5NTNkMjlkMTQzZDk1NWJlOTVmZDIyYmRm
|
15
|
+
OWIzOGU4MTZlZGNiNTQ5MzRhMmRiZGFmNDdkNTc2YTg5ZGUwZGU=
|
data/bin/onering
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.push 'lib'
|
3
|
+
require 'trollop'
|
4
|
+
require 'onering'
|
5
|
+
require 'hashlib'
|
6
|
+
require 'rainbow'
|
7
|
+
require 'pp'
|
8
|
+
|
9
|
+
plugins = Onering::CLI::Plugin.registered_plugins.collect{|i| i.name.split('::').last.downcase }
|
10
|
+
exclude_plugins = %w{devices}
|
11
|
+
|
12
|
+
global = Trollop::options do
|
13
|
+
banner <<-EOS
|
14
|
+
onering command line client utility - version #{Onering::Client::VERSION}
|
15
|
+
|
16
|
+
Usage:
|
17
|
+
onering [global] [plugin] [subcommand] [options]
|
18
|
+
|
19
|
+
Plugins (onering <plugin> --help for usage details):
|
20
|
+
#{(plugins - exclude_plugins).sort.join(', ')}
|
21
|
+
|
22
|
+
where [global] options are:
|
23
|
+
EOS
|
24
|
+
|
25
|
+
opt :url, "The URL of the Onering server to connect to", :short => '-s', :type => :string
|
26
|
+
opt :path, "The base path to prepend to all requests (default: /api)", :type => :string
|
27
|
+
opt :source, "Specify the source IP address to use (i.e. which network interface the request should originate from)", :short => '-I', :type => :string
|
28
|
+
opt :param, "Additional query string parameters to include with the request in the format FIELD=VALUE. Can be specified multiple times.", :short => '-p', :type => :string, :multi => true
|
29
|
+
opt :format, "The output format for return values (i.e.: json, yaml, text)", :short => '-t', :type => :string
|
30
|
+
opt :sslkey, "Location of the SSL client key to use for authentication", :short => '-c', :type => :string
|
31
|
+
opt :nosslverify, "Disable verification of the server SSL certificate", :type => :boolean
|
32
|
+
opt :apikey, "The API token to use for authentication", :short => '-k', :type => :string
|
33
|
+
opt :quiet, "Suppress standard output", :short => '-q'
|
34
|
+
opt :separator, "A string used to separate output values of delimited tabular data", :short => '-S', :default => "\t"
|
35
|
+
opt :verbosity, "Set the log level (fatal, error, warn, info, debug)", :short => '-v', :type => :string, :default => 'warn'
|
36
|
+
|
37
|
+
stop_on plugins
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
plugin = ARGV.shift
|
42
|
+
Trollop::die("plugin argument is requried") if plugin.nil?
|
43
|
+
|
44
|
+
Onering::Logger.setup({
|
45
|
+
:destination => 'stdout',
|
46
|
+
:threshold => global[:verbosity]
|
47
|
+
})
|
48
|
+
|
49
|
+
if plugins.include?(plugin)
|
50
|
+
begin
|
51
|
+
plugin = Onering::CLI.const_get(plugin.capitalize)
|
52
|
+
plugin.configure(global)
|
53
|
+
|
54
|
+
Onering::Logger.debug("Executing plugin #{plugin}\#run()", $0)
|
55
|
+
rv = plugin.run(ARGV)
|
56
|
+
Onering::CLI.output(rv, (global[:format] || plugin.default_format(rv, ARGV) || 'text'))
|
57
|
+
|
58
|
+
rescue Onering::API::Errors::Exception => e
|
59
|
+
Onering::Logger.fatal(e.message, e.class.name.split('::').last) rescue nil
|
60
|
+
exit 1
|
61
|
+
|
62
|
+
rescue Onering::Client::FatalError => e
|
63
|
+
exit 255
|
64
|
+
|
65
|
+
end
|
66
|
+
else
|
67
|
+
Trollop::die("unknown plugin #{plugin}")
|
68
|
+
end
|
data/lib/etc/facter.list
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
serialnumber:serial
|
2
|
+
manufacturer:make
|
3
|
+
productname:model
|
4
|
+
boardmanufacturer:mbmake
|
5
|
+
boardproductname:mbmodel
|
6
|
+
boardserialnumber:mbserial
|
7
|
+
hostname
|
8
|
+
fqdn
|
9
|
+
default_ipaddress:ip
|
10
|
+
default_macaddress:mac
|
11
|
+
default_gateway:gateway
|
12
|
+
kernelrelease:kernel
|
13
|
+
kernelarguments:@kernelarguments
|
14
|
+
architecture:arch
|
15
|
+
operatingsystem:distro
|
16
|
+
operatingsystemrelease:version
|
17
|
+
signature
|
18
|
+
boottime:booted_at
|
19
|
+
onering:@onering
|
data/lib/onering.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
module Onering
|
4
|
+
FULL_CLIENT = true
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'onering/version'
|
8
|
+
require 'onering/logger'
|
9
|
+
|
10
|
+
if not ENV['ONERING_LOGLEVEL'].nil?
|
11
|
+
Onering::Logger.setup({
|
12
|
+
:destination => 'stderr',
|
13
|
+
:threshold => ENV['ONERING_LOGLEVEL']
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'onering/util'
|
18
|
+
require 'onering/api'
|
19
|
+
require 'onering/cli'
|
20
|
+
|
21
|
+
# require plugins
|
22
|
+
Dir[File.join(File.expand_path(File.dirname(__FILE__)),"onering","plugins","*.rb")].each do |i|
|
23
|
+
require i
|
24
|
+
end
|
25
|
+
|
26
|
+
# require cli submodules
|
27
|
+
Dir[File.join(File.expand_path(File.dirname(__FILE__)),"onering","cli","*.rb")].each do |i|
|
28
|
+
require i
|
29
|
+
end
|
30
|
+
|
31
|
+
# you've loaded the library, now load the config
|
32
|
+
Onering::Config.load()
|
data/lib/onering/api.rb
ADDED
@@ -0,0 +1,322 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'yaml'
|
3
|
+
require 'hashlib'
|
4
|
+
require 'deep_merge'
|
5
|
+
require 'addressable/uri'
|
6
|
+
require 'httparty'
|
7
|
+
require 'onering/config'
|
8
|
+
|
9
|
+
module Onering
|
10
|
+
class API
|
11
|
+
module Actions
|
12
|
+
class Retry < ::Exception; end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Errors
|
16
|
+
class Exception < ::Exception; end
|
17
|
+
class NotConnected < Exception; end
|
18
|
+
|
19
|
+
class ClientError < Exception; end
|
20
|
+
class Unauthorized < ClientError; end
|
21
|
+
class Forbidden < ClientError; end
|
22
|
+
class NotFound < ClientError; end
|
23
|
+
|
24
|
+
class ServerError < Exception; end
|
25
|
+
class ConnectionTimeout < Exception; end
|
26
|
+
class AuthenticationMissing < Exception; end
|
27
|
+
end
|
28
|
+
|
29
|
+
include Onering::Util
|
30
|
+
include ::HTTParty
|
31
|
+
|
32
|
+
attr_accessor :url
|
33
|
+
format :json
|
34
|
+
|
35
|
+
DEFAULT_BASE="https://onering"
|
36
|
+
DEFAULT_PATH="/api"
|
37
|
+
DEFAULT_CLIENT_PEM=["~/.onering/client.pem", "/etc/onering/client.pem"]
|
38
|
+
DEFAULT_CLIENT_KEY=["~/.onering/client.key", "/etc/onering/client.key"]
|
39
|
+
DEFAULT_VALIDATION_PEM="/etc/onering/validation.pem"
|
40
|
+
|
41
|
+
|
42
|
+
def initialize(options={})
|
43
|
+
@_plugins = {}
|
44
|
+
options = {} if options.nil?
|
45
|
+
@_connection_options = options
|
46
|
+
|
47
|
+
# load and merge all config file sources
|
48
|
+
Onering::Config.load(@_connection_options[:configfile], @_connection_options.get(:config, {}))
|
49
|
+
|
50
|
+
if options.get('config.nosslverify', false) == true
|
51
|
+
# deliberately break SSL verification
|
52
|
+
Onering::Logger.warn("Disabling SSL peer verification for #{options.get('config.url')}")
|
53
|
+
OpenSSL::SSL.send(:const_set, :OLD_VERIFY_PEER, OpenSSL::SSL::VERIFY_PEER)
|
54
|
+
OpenSSL::SSL.send(:remove_const, :VERIFY_PEER)
|
55
|
+
OpenSSL::SSL.send(:const_set, :VERIFY_PEER, OpenSSL::SSL::VERIFY_NONE)
|
56
|
+
else
|
57
|
+
# restore SSL verification if it's currently broken
|
58
|
+
if defined?(OpenSSL::SSL::OLD_VERIFY_PEER)
|
59
|
+
if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE and OpenSSL::SSL::OLD_VERIFY_PEER != OpenSSL::SSL::VERIFY_NONE
|
60
|
+
OpenSSL::SSL.send(:remove_const, :VERIFY_PEER)
|
61
|
+
OpenSSL::SSL.send(:const_set, :VERIFY_PEER, OpenSSL::SSL::OLD_VERIFY_PEER)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE
|
67
|
+
Onering::Logger.warn("Disabling SSL peer verification for #{options.get('config.url')}")
|
68
|
+
end
|
69
|
+
|
70
|
+
# source interface specified
|
71
|
+
# !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !! HAX !!
|
72
|
+
# Due to certain versions of Ruby's Net::HTTP not allowing you explicitly
|
73
|
+
# specify the source IP/interface to use, this horrific monkey patch is
|
74
|
+
# necessary, if not right.
|
75
|
+
#
|
76
|
+
# If at least some of your code doesn't make you feel bottomless shame
|
77
|
+
# then you aren't coding hard enough.
|
78
|
+
#
|
79
|
+
if options.get('config.source').is_a?(String)
|
80
|
+
if options.get('config.source') =~ /^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/
|
81
|
+
# insert firing pin into the hack
|
82
|
+
TCPSocket.instance_eval do
|
83
|
+
(class << self; self; end).instance_eval do
|
84
|
+
alias_method :_stock_open, :open
|
85
|
+
attr_writer :_hack_local_ip
|
86
|
+
|
87
|
+
define_method(:open) do |conn_address, conn_port|
|
88
|
+
_stock_open(conn_address, conn_port, @_hack_local_ip)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# arm the hack
|
94
|
+
TCPSocket._hack_local_ip = options.get('config.source')
|
95
|
+
|
96
|
+
# sound the siren
|
97
|
+
Onering::Logger.info("Using local interface #{options.get('config.source')} to connect", "Onering::API")
|
98
|
+
|
99
|
+
else
|
100
|
+
raise "Invalid source IP address #{options.get('config.source')}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# set API connectivity details
|
105
|
+
Onering::API.base_uri(options.get('config.url', Onering::Config.get(:url, DEFAULT_BASE)))
|
106
|
+
Onering::Logger.info("Server URL is #{Onering::API.base_uri}", "Onering::API")
|
107
|
+
|
108
|
+
# add default parameters
|
109
|
+
options.get('config.params',{}).each do |k,v|
|
110
|
+
_default_param(k,v)
|
111
|
+
end
|
112
|
+
|
113
|
+
connect(options) if options.get(:autoconnect, true)
|
114
|
+
end
|
115
|
+
|
116
|
+
def connect(options={})
|
117
|
+
# setup authentication
|
118
|
+
_setup_auth()
|
119
|
+
|
120
|
+
Onering::Logger.debug("Connection setup complete", "Onering::API")
|
121
|
+
return self
|
122
|
+
end
|
123
|
+
|
124
|
+
|
125
|
+
def request(method, endpoint, options={})
|
126
|
+
endpoint = [Onering::Config.get(:path, DEFAULT_PATH).strip, endpoint.sub(/^\//,'')].join('/')
|
127
|
+
|
128
|
+
Onering::Logger.debug("#{method.to_s.upcase} #{endpoint}#{(options[:query] || {}).empty? ? '' : '?'+options[:query].join('=', '&')}", "Onering::API")
|
129
|
+
options.get(:headers,[]).each do |name, value|
|
130
|
+
next if name == 'Content-Type' and value == 'application/json'
|
131
|
+
Onering::Logger.debug("+#{name}: #{value}", "Onering::API")
|
132
|
+
end
|
133
|
+
|
134
|
+
begin
|
135
|
+
case (method.to_sym rescue method)
|
136
|
+
when :post
|
137
|
+
rv = Onering::API.post(endpoint, options)
|
138
|
+
when :put
|
139
|
+
rv = Onering::API.put(endpoint, options)
|
140
|
+
when :delete
|
141
|
+
rv = Onering::API.delete(endpoint, options)
|
142
|
+
when :head
|
143
|
+
rv = Onering::API.head(endpoint, options)
|
144
|
+
else
|
145
|
+
rv = Onering::API.get(endpoint, options)
|
146
|
+
end
|
147
|
+
rescue SocketError => e
|
148
|
+
Onering::Logger.fatal!("Unable to connect to #{Onering::API.base_uri}", "Onering::API")
|
149
|
+
end
|
150
|
+
|
151
|
+
if rv.code >= 500
|
152
|
+
raise Errors::ServerError.new("HTTP #{rv.code} - #{Onering::Util.http_status(rv.code)} #{rv.parsed_response.get('error.message','') rescue ''}")
|
153
|
+
elsif rv.code >= 400
|
154
|
+
message = "HTTP #{rv.code} - #{Onering::Util.http_status(rv.code)} #{rv.parsed_response.get('error.message', '') rescue ''}"
|
155
|
+
|
156
|
+
case rv.code
|
157
|
+
when 401
|
158
|
+
raise Errors::Unauthorized.new(message)
|
159
|
+
when 403
|
160
|
+
raise Errors::Forbidden.new(message)
|
161
|
+
when 404
|
162
|
+
raise Errors::NotFound.new(message)
|
163
|
+
else
|
164
|
+
raise Errors::ClientError.new(message)
|
165
|
+
end
|
166
|
+
else
|
167
|
+
rv
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
def get(endpoint, options={})
|
173
|
+
request(:get, endpoint, options)
|
174
|
+
end
|
175
|
+
|
176
|
+
def post(endpoint, options={}, &block)
|
177
|
+
if block_given?
|
178
|
+
request(:post, endpoint, options.merge({
|
179
|
+
:body => yield
|
180
|
+
}))
|
181
|
+
else
|
182
|
+
request(:post, endpoint, options)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def put(endpoint, options={}, &block)
|
187
|
+
if block_given?
|
188
|
+
request(:put, endpoint, options.merge({
|
189
|
+
:body => yield
|
190
|
+
}))
|
191
|
+
else
|
192
|
+
request(:put, endpoint, options)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def delete(endpoint, options={})
|
197
|
+
request(:delete, endpoint, options)
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
# I'm not a huge fan of what's happening here, but metaprogramming is hard...
|
202
|
+
#
|
203
|
+
# "Don't let the perfect be the enemy of the good."
|
204
|
+
#
|
205
|
+
def method_missing(method, *args, &block)
|
206
|
+
modname = method.to_s.split('_').map(&:capitalize).join
|
207
|
+
|
208
|
+
if not (plugin = (Onering::API.const_get(modname) rescue nil)).nil?
|
209
|
+
@_plugins[method] ||= plugin.new.connect(@_connection_options)
|
210
|
+
return @_plugins[method]
|
211
|
+
else
|
212
|
+
super
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def status()
|
217
|
+
Onering::API.new.get("/").parsed_response
|
218
|
+
end
|
219
|
+
|
220
|
+
# -----------------------------------------------------------------------------
|
221
|
+
def _setup_auth()
|
222
|
+
type = Onering::Config.get('authentication.type', :auto)
|
223
|
+
_setup_auth_token()
|
224
|
+
end
|
225
|
+
|
226
|
+
# -----------------------------------------------------------------------------
|
227
|
+
def _default_param(key, value)
|
228
|
+
@_default_params ||= {}
|
229
|
+
@_default_params[key] = value
|
230
|
+
Onering::API.default_params(@_default_params)
|
231
|
+
end
|
232
|
+
|
233
|
+
# -----------------------------------------------------------------------------
|
234
|
+
def _setup_auth_token()
|
235
|
+
Onering::Logger.info("Using token authentication mechanism", "Onering::API")
|
236
|
+
|
237
|
+
# get first keyfile found
|
238
|
+
key = Onering::Config.get('authentication.key', Onering::Config.get('authentication.keyfile'))
|
239
|
+
|
240
|
+
if key.nil?
|
241
|
+
if Onering::Config.get('authentication.bootstrap.enabled', true)
|
242
|
+
Onering::Logger.warn("Authentication token not found, attempting to autoregister client", "Onering::API")
|
243
|
+
|
244
|
+
if not (bootstrap = Onering::Config.get('authentication.bootstrap.key')).nil?
|
245
|
+
if bootstrap.to_s =~ /[0-9a-f]{32,64}/
|
246
|
+
# attempt to create key.yml from least-specific to most, first writable path wins
|
247
|
+
clients = [{
|
248
|
+
:path => "/etc/onering",
|
249
|
+
:name => fact('hardwareid'),
|
250
|
+
:keyname => 'system',
|
251
|
+
:autodelete => true
|
252
|
+
},{
|
253
|
+
:path => "~/.onering",
|
254
|
+
:name => ENV['USER'],
|
255
|
+
:keyname => 'cli',
|
256
|
+
:autodelete => false
|
257
|
+
}]
|
258
|
+
|
259
|
+
# for each client attempt...
|
260
|
+
clients.each do |client|
|
261
|
+
# expand and assemble path
|
262
|
+
client[:path] = (File.expand_path(client[:path]) rescue client[:path])
|
263
|
+
keyfile = File.join(client[:path], 'key.yml')
|
264
|
+
|
265
|
+
# skip this if we can't write to the parent directory
|
266
|
+
next unless File.writable?(client[:path])
|
267
|
+
Dir.mkdir(client[:path]) unless File.directory?(client[:path])
|
268
|
+
next if File.exists?(keyfile)
|
269
|
+
|
270
|
+
self.class.headers({
|
271
|
+
'X-Auth-Bootstrap-Token' => bootstrap
|
272
|
+
})
|
273
|
+
|
274
|
+
# attempt to create/download the keyfile
|
275
|
+
Onering::Logger.debug("Requesting authentication token for #{client[:name].strip}; #{bootstrap}", "Onering::API")
|
276
|
+
response = self.class.get("/api/users/#{client[:name].strip}/tokens/#{client[:keyname]}")
|
277
|
+
|
278
|
+
# if successful, write the file
|
279
|
+
if response.code < 400 and response.body
|
280
|
+
File.open(keyfile, 'w').puts(YAML.dump({
|
281
|
+
'authentication' => {
|
282
|
+
'key' => response.body.strip.chomp
|
283
|
+
}
|
284
|
+
}))
|
285
|
+
|
286
|
+
key = response.body.strip.chomp
|
287
|
+
|
288
|
+
else
|
289
|
+
# all errors are fatal at this stage
|
290
|
+
Onering::Logger.fatal!("Cannot autoregister client: HTTP #{response.code} - #{(response.parsed_response || {}).get('error.message', 'Unknown error')}", "Onering::API")
|
291
|
+
end
|
292
|
+
|
293
|
+
self.class.headers({})
|
294
|
+
|
295
|
+
# we're done here...
|
296
|
+
break
|
297
|
+
end
|
298
|
+
else
|
299
|
+
raise Errors::AuthenticationMissing.new("Autoregistration failed: invalid bootstrap token specified")
|
300
|
+
end
|
301
|
+
|
302
|
+
else
|
303
|
+
raise Errors::AuthenticationMissing.new("Autoregistration failed: no bootstrap token specified")
|
304
|
+
end
|
305
|
+
|
306
|
+
else
|
307
|
+
raise Errors::AuthenticationMissing.new("Authentication token not found, and autoregistration disabled")
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
raise Errors::AuthenticationMissing.new("Token authentication specified, but cannot find a token config or as a command line argument") if key.nil?
|
312
|
+
|
313
|
+
# set auth mechanism
|
314
|
+
Onering::API.headers({
|
315
|
+
'X-Auth-Mechanism' => 'token'
|
316
|
+
})
|
317
|
+
|
318
|
+
# set default parameters
|
319
|
+
_default_param(:token, key)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|