wavefront-client 3.5.3 → 3.5.4
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 +4 -4
- data/.travis.yml +4 -2
- data/README-cli.md +84 -60
- data/bin/wavefront +84 -69
- data/lib/wavefront/alerting.rb +14 -3
- data/lib/wavefront/batch_writer.rb +7 -0
- data/lib/wavefront/cli.rb +15 -17
- data/lib/wavefront/cli/alerts.rb +15 -10
- data/lib/wavefront/cli/batch_write.rb +12 -3
- data/lib/wavefront/cli/events.rb +19 -3
- data/lib/wavefront/cli/sources.rb +22 -12
- data/lib/wavefront/cli/ts.rb +9 -1
- data/lib/wavefront/cli/write.rb +7 -0
- data/lib/wavefront/client.rb +9 -5
- data/lib/wavefront/client/version.rb +1 -1
- data/lib/wavefront/constants.rb +20 -0
- data/lib/wavefront/events.rb +26 -4
- data/lib/wavefront/metadata.rb +23 -6
- data/lib/wavefront/opt_handler.rb +61 -0
- data/spec/cli_spec.rb +584 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/wavefront/alerting_spec.rb +8 -9
- data/spec/wavefront/batch_writer_spec.rb +1 -1
- data/spec/wavefront/cli/alerts_spec.rb +5 -4
- data/spec/wavefront/cli/batch_write_spec.rb +4 -2
- data/spec/wavefront/cli/events_spec.rb +3 -2
- data/spec/wavefront/cli/sources_spec.rb +3 -2
- data/spec/wavefront/cli/write_spec.rb +4 -2
- data/spec/wavefront/cli_spec.rb +11 -43
- data/spec/wavefront/client_spec.rb +2 -2
- data/spec/wavefront/events_spec.rb +1 -1
- data/spec/wavefront/metadata_spec.rb +1 -1
- data/spec/wavefront/mixins_spec.rb +1 -1
- data/spec/wavefront/opt_handler_spec.rb +89 -0
- data/spec/wavefront/resources/conf.yaml +10 -0
- data/spec/wavefront/response_spec.rb +3 -3
- data/spec/wavefront/validators_spec.rb +1 -1
- data/spec/wavefront/writer_spec.rb +1 -1
- metadata +9 -3
- data/.ruby-version +0 -1
data/lib/wavefront/alerting.rb
CHANGED
@@ -17,6 +17,7 @@ See the License for the specific language governing permissions and
|
|
17
17
|
require "wavefront/client/version"
|
18
18
|
require "wavefront/constants"
|
19
19
|
require 'wavefront/mixins'
|
20
|
+
require 'wavefront/validators'
|
20
21
|
require 'rest_client'
|
21
22
|
require 'uri'
|
22
23
|
require 'logger'
|
@@ -28,11 +29,18 @@ module Wavefront
|
|
28
29
|
include Wavefront::Mixins
|
29
30
|
DEFAULT_PATH = '/api/alert/'
|
30
31
|
|
31
|
-
attr_reader :token
|
32
|
+
attr_reader :token, :noop, :verbose, :endpoint
|
32
33
|
|
33
|
-
def initialize(token, debug=false)
|
34
|
+
def initialize(token, host = DEFAULT_HOST, debug=false, options = {})
|
35
|
+
#
|
36
|
+
# Following existing convention, 'host' is the Wavefront API endpoint.
|
37
|
+
#
|
38
|
+
@headers = { :'X-AUTH-TOKEN' => token }
|
39
|
+
@endpoint = host
|
34
40
|
@token = token
|
35
41
|
debug(debug)
|
42
|
+
@noop = options[:noop]
|
43
|
+
@verbose = options[:verbose]
|
36
44
|
end
|
37
45
|
|
38
46
|
def active(options={})
|
@@ -76,7 +84,7 @@ module Wavefront
|
|
76
84
|
end
|
77
85
|
|
78
86
|
def get_alerts(path, options={})
|
79
|
-
options[:host] ||=
|
87
|
+
options[:host] ||= endpoint
|
80
88
|
options[:path] ||= DEFAULT_PATH
|
81
89
|
|
82
90
|
uri = URI::HTTPS.build(
|
@@ -85,6 +93,9 @@ module Wavefront
|
|
85
93
|
query: mk_qs(options),
|
86
94
|
)
|
87
95
|
|
96
|
+
puts "GET #{uri.to_s}" if (verbose || noop)
|
97
|
+
return if noop
|
98
|
+
|
88
99
|
RestClient.get(uri.to_s)
|
89
100
|
end
|
90
101
|
|
@@ -66,12 +66,15 @@ module Wavefront
|
|
66
66
|
rejected: 0,
|
67
67
|
unsent: 0,
|
68
68
|
}
|
69
|
+
|
69
70
|
@opts = setup_options(options, defaults)
|
70
71
|
|
71
72
|
if opts[:tags]
|
72
73
|
valid_tags?(opts[:tags])
|
73
74
|
@global_tags = opts[:tags]
|
74
75
|
end
|
76
|
+
|
77
|
+
debug(options[:debug])
|
75
78
|
end
|
76
79
|
|
77
80
|
def setup_options(user, defaults)
|
@@ -210,5 +213,9 @@ module Wavefront
|
|
210
213
|
puts 'Closing connection to proxy.' if opts[:verbose]
|
211
214
|
sock.close
|
212
215
|
end
|
216
|
+
|
217
|
+
def debug(enabled)
|
218
|
+
RestClient.log = 'stdout' if enabled
|
219
|
+
end
|
213
220
|
end
|
214
221
|
end
|
data/lib/wavefront/cli.rb
CHANGED
@@ -14,16 +14,19 @@ See the License for the specific language governing permissions and
|
|
14
14
|
|
15
15
|
=end
|
16
16
|
|
17
|
-
require '
|
17
|
+
require 'wavefront/constants'
|
18
18
|
|
19
19
|
module Wavefront
|
20
|
+
#
|
21
|
+
# Parent of all the CLI classes.
|
22
|
+
#
|
20
23
|
class Cli
|
21
|
-
|
22
|
-
attr_accessor :options, :arguments
|
24
|
+
attr_accessor :options, :arguments, :noop
|
23
25
|
|
24
26
|
def initialize(options, arguments)
|
25
27
|
@options = options
|
26
28
|
@arguments = arguments
|
29
|
+
@noop = options[:noop]
|
27
30
|
|
28
31
|
if options.include?(:help) && options[:help]
|
29
32
|
puts options
|
@@ -31,22 +34,17 @@ module Wavefront
|
|
31
34
|
end
|
32
35
|
end
|
33
36
|
|
34
|
-
def
|
37
|
+
def validate_opts
|
35
38
|
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
+
# There are things we need to have. If we don't have them,
|
40
|
+
# stop the user right now. Also, if we're in debug mode, print
|
41
|
+
# out a hash of options, which can be very useful when doing
|
42
|
+
# actual debugging. Some classes may have to override this
|
43
|
+
# method. The writer, for instance, uses a proxy and has no
|
44
|
+
# token.
|
39
45
|
#
|
40
|
-
|
41
|
-
|
42
|
-
return unless cf.exist?
|
43
|
-
|
44
|
-
pf = options[:profile] || 'default'
|
45
|
-
puts "using #{pf} profile from #{cf}" if options[:debug]
|
46
|
-
|
47
|
-
IniFile.load(cf)[pf].each_with_object({}) do |(k, v), memo|
|
48
|
-
memo[k.to_sym] = v
|
49
|
-
end
|
46
|
+
raise 'Please supply an API token.' unless options[:token]
|
47
|
+
raise 'Please supply an API endpoint.' unless options[:endpoint]
|
50
48
|
end
|
51
49
|
end
|
52
50
|
end
|
data/lib/wavefront/cli/alerts.rb
CHANGED
@@ -28,9 +28,11 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
|
|
28
28
|
raise 'Missing query.' if arguments.empty?
|
29
29
|
query = arguments[0].to_sym
|
30
30
|
|
31
|
-
wfa = Wavefront::Alerting.new(@options[:token]
|
31
|
+
wfa = Wavefront::Alerting.new(@options[:token], @options[:endpoint],
|
32
|
+
@options[:debug], {
|
33
|
+
noop: @options[:noop], verbose: @options[:verbose]})
|
32
34
|
valid_state?(wfa, query)
|
33
|
-
valid_format?(@options[:
|
35
|
+
valid_format?(@options[:alertformat].to_sym)
|
34
36
|
options = { host: @options[:endpoint] }
|
35
37
|
|
36
38
|
if @options[:shared]
|
@@ -43,11 +45,12 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
|
|
43
45
|
|
44
46
|
begin
|
45
47
|
result = wfa.send(query, options)
|
46
|
-
rescue
|
48
|
+
rescue => e
|
49
|
+
puts e if @options[:debug]
|
47
50
|
raise 'Unable to execute query.'
|
48
51
|
end
|
49
52
|
|
50
|
-
format_result(result, @options[:
|
53
|
+
format_result(result, @options[:alertformat].to_sym)
|
51
54
|
exit
|
52
55
|
end
|
53
56
|
|
@@ -56,6 +59,8 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
|
|
56
59
|
# Call a suitable method to display the output of the API call,
|
57
60
|
# which is JSON.
|
58
61
|
#
|
62
|
+
return if noop
|
63
|
+
|
59
64
|
case format
|
60
65
|
when :ruby
|
61
66
|
pp result
|
@@ -83,11 +88,10 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
|
|
83
88
|
# Check the alert type we've been given is valid. There needs to
|
84
89
|
# be a public method in the 'alerting' class for every one.
|
85
90
|
#
|
86
|
-
|
87
|
-
|
88
|
-
unless
|
89
|
-
raise
|
90
|
-
'.'
|
91
|
+
states = %w(active affected_by_maintenance all invalid snoozed)
|
92
|
+
|
93
|
+
unless states.include?(state.to_s)
|
94
|
+
raise "State must be one of: #{states.join(', ')}."
|
91
95
|
end
|
92
96
|
true
|
93
97
|
end
|
@@ -120,7 +124,7 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
|
|
120
124
|
else
|
121
125
|
human_line(key, alert[key])
|
122
126
|
end
|
123
|
-
end
|
127
|
+
end.<< ''
|
124
128
|
end
|
125
129
|
end
|
126
130
|
|
@@ -166,6 +170,7 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
|
|
166
170
|
#
|
167
171
|
# hanging indent long lines to fit in an 80-column terminal
|
168
172
|
#
|
173
|
+
return unless line
|
169
174
|
line.gsub(/(.{1,#{cols - offset}})(\s+|\Z)/, "\\1\n#{' ' *
|
170
175
|
offset}").rstrip
|
171
176
|
end
|
@@ -12,14 +12,23 @@ class Wavefront::Cli::BatchWrite < Wavefront::Cli
|
|
12
12
|
include Wavefront::Constants
|
13
13
|
include Wavefront::Mixins
|
14
14
|
|
15
|
+
def validate_opts
|
16
|
+
#
|
17
|
+
# Unlike all the API methods, we don't need a token here
|
18
|
+
#
|
19
|
+
abort 'Please supply a proxy endpoint.' unless options[:proxy]
|
20
|
+
end
|
21
|
+
|
15
22
|
def run
|
16
|
-
|
23
|
+
unless valid_format?(options[:infileformat])
|
24
|
+
raise 'Invalid format string.'
|
25
|
+
end
|
17
26
|
|
18
27
|
file = options[:'<file>']
|
19
28
|
setup_opts(options)
|
20
29
|
|
21
|
-
if options.key?(:
|
22
|
-
setup_fmt(options[:
|
30
|
+
if options.key?(:infileformat)
|
31
|
+
setup_fmt(options[:infileformat])
|
23
32
|
else
|
24
33
|
setup_fmt
|
25
34
|
end
|
data/lib/wavefront/cli/events.rb
CHANGED
@@ -27,7 +27,8 @@ require 'socket'
|
|
27
27
|
# in a timeseries API call.
|
28
28
|
#
|
29
29
|
class Wavefront::Cli::Events < Wavefront::Cli
|
30
|
-
attr_accessor :state_dir, :hosts, :hostname, :t_start, :t_end,
|
30
|
+
attr_accessor :state_dir, :hosts, :hostname, :t_start, :t_end,
|
31
|
+
:wf_event
|
31
32
|
|
32
33
|
include Wavefront::Constants
|
33
34
|
include Wavefront::Mixins
|
@@ -43,8 +44,11 @@ class Wavefront::Cli::Events < Wavefront::Cli
|
|
43
44
|
@hosts = prep_hosts(options[:host])
|
44
45
|
@t_start = prep_time(:start)
|
45
46
|
@t_end = prep_time(:end)
|
47
|
+
@noop = options[:noop]
|
46
48
|
|
47
|
-
@wf_event = Wavefront::Events.new(
|
49
|
+
@wf_event = Wavefront::Events.new(
|
50
|
+
options[:token], options[:endpoint], options[:debug],
|
51
|
+
{ verbose: options[:verbose], noop: options[:noop]})
|
48
52
|
|
49
53
|
if options[:create]
|
50
54
|
create_event_handler
|
@@ -77,7 +81,7 @@ class Wavefront::Cli::Events < Wavefront::Cli
|
|
77
81
|
raise 'Cannot delete event.'
|
78
82
|
end
|
79
83
|
|
80
|
-
puts 'Deleted event.'
|
84
|
+
puts 'Deleted event.' unless noop
|
81
85
|
end
|
82
86
|
|
83
87
|
def prep_time(t)
|
@@ -104,6 +108,8 @@ class Wavefront::Cli::Events < Wavefront::Cli
|
|
104
108
|
#
|
105
109
|
output = create_event
|
106
110
|
|
111
|
+
return if noop
|
112
|
+
|
107
113
|
unless options[:end] || options[:instant]
|
108
114
|
create_state_dir
|
109
115
|
create_state_file(output) unless options[:nostate]
|
@@ -211,6 +217,7 @@ class Wavefront::Cli::Events < Wavefront::Cli
|
|
211
217
|
#
|
212
218
|
# Returns an array of [timestamp, event_name]
|
213
219
|
#
|
220
|
+
return false unless state_dir.exist?
|
214
221
|
list = state_dir.children
|
215
222
|
list.select! { |f| f.basename.to_s.split('::').last == name } if name
|
216
223
|
return false if list.length == 0
|
@@ -249,4 +256,13 @@ class Wavefront::Cli::Events < Wavefront::Cli
|
|
249
256
|
|
250
257
|
puts "Event state recorded at #{fname}."
|
251
258
|
end
|
259
|
+
|
260
|
+
def validate_opts
|
261
|
+
#
|
262
|
+
# the 'show' sub-command does not make an API call
|
263
|
+
#
|
264
|
+
return true if options[:show]
|
265
|
+
abort 'Please supply an API token.' unless options[:token]
|
266
|
+
abort 'Please supply an API endpoint.' unless options[:endpoint]
|
267
|
+
end
|
252
268
|
end
|
@@ -7,18 +7,21 @@ require 'pp'
|
|
7
7
|
# Turn CLI input, from the 'sources' command, into metadata API calls
|
8
8
|
#
|
9
9
|
class Wavefront::Cli::Sources < Wavefront::Cli
|
10
|
-
attr_accessor :wf, :out_format, :show_hidden, :show_tags
|
10
|
+
attr_accessor :wf, :out_format, :show_hidden, :show_tags, :verbose
|
11
11
|
|
12
12
|
def setup_wf
|
13
13
|
@wf = Wavefront::Metadata.new(options[:token], options[:endpoint],
|
14
|
-
|
14
|
+
options[:debug],
|
15
|
+
{ verbose: options[:verbose],
|
16
|
+
noop: options[:noop]})
|
15
17
|
end
|
16
18
|
|
17
19
|
def run
|
18
20
|
setup_wf
|
19
|
-
@out_format = options[:
|
21
|
+
@out_format = options[:sourceformat].to_s
|
20
22
|
@show_hidden = options[:all]
|
21
23
|
@show_tags = options[:tags]
|
24
|
+
@verbose = options[:verbose]
|
22
25
|
|
23
26
|
begin
|
24
27
|
if options[:list]
|
@@ -49,17 +52,20 @@ class Wavefront::Cli::Sources < Wavefront::Cli
|
|
49
52
|
|
50
53
|
q = {
|
51
54
|
desc: false,
|
52
|
-
limit: limit,
|
55
|
+
limit: limit.to_i,
|
53
56
|
pattern: pattern
|
54
57
|
}
|
55
58
|
|
56
59
|
q[:lastEntityId] = start if start
|
57
60
|
|
58
|
-
|
61
|
+
res = wf.show_sources(q)
|
62
|
+
return if noop
|
63
|
+
display_data(res, 'list_source')
|
59
64
|
end
|
60
65
|
|
61
66
|
def describe_handler(hosts, desc)
|
62
67
|
hosts = [Socket.gethostname] if hosts.empty?
|
68
|
+
hosts = [hosts] if hosts.is_a?(String)
|
63
69
|
|
64
70
|
hosts.each do |h|
|
65
71
|
if desc.empty?
|
@@ -77,20 +83,22 @@ class Wavefront::Cli::Sources < Wavefront::Cli
|
|
77
83
|
end
|
78
84
|
|
79
85
|
def untag_handler(hosts)
|
80
|
-
hosts ||=
|
86
|
+
hosts ||= Socket.gethostname
|
87
|
+
hosts = [hosts] if hosts.is_a?(String)
|
81
88
|
|
82
89
|
hosts.each do |h|
|
83
|
-
puts "Removing all tags from '#{h}'"
|
90
|
+
puts "Removing all tags from '#{h}'" if verbose
|
84
91
|
wf.delete_tags(h)
|
85
92
|
end
|
86
93
|
end
|
87
94
|
|
88
95
|
def add_tag_handler(hosts, tags)
|
89
|
-
hosts ||=
|
96
|
+
hosts ||= Socket.gethostname
|
97
|
+
hosts = [hosts] if hosts.is_a?(String)
|
90
98
|
|
91
99
|
hosts.each do |h|
|
92
100
|
tags.each do |t|
|
93
|
-
puts "Tagging '#{h}' with '#{t}'"
|
101
|
+
puts "Tagging '#{h}' with '#{t}'" if verbose
|
94
102
|
begin
|
95
103
|
wf.set_tag(h, t)
|
96
104
|
rescue Wavefront::Exception::InvalidString
|
@@ -101,11 +109,12 @@ class Wavefront::Cli::Sources < Wavefront::Cli
|
|
101
109
|
end
|
102
110
|
|
103
111
|
def delete_tag_handler(hosts, tags)
|
104
|
-
hosts ||=
|
112
|
+
hosts ||= Socket.gethostname
|
113
|
+
hosts = [hosts] if hosts.is_a?(String)
|
105
114
|
|
106
115
|
hosts.each do |h|
|
107
116
|
tags.each do |t|
|
108
|
-
puts "Removing tag '#{t}' from '#{h}'"
|
117
|
+
puts "Removing tag '#{t}' from '#{h}'" if verbose
|
109
118
|
wf.delete_tag(h, t)
|
110
119
|
end
|
111
120
|
end
|
@@ -125,6 +134,7 @@ class Wavefront::Cli::Sources < Wavefront::Cli
|
|
125
134
|
end
|
126
135
|
|
127
136
|
def display_data(result, method)
|
137
|
+
return if noop
|
128
138
|
if out_format == 'human'
|
129
139
|
puts public_send('humanize_' + method, result)
|
130
140
|
elsif out_format == 'json'
|
@@ -146,7 +156,7 @@ class Wavefront::Cli::Sources < Wavefront::Cli
|
|
146
156
|
if options[:tagged]
|
147
157
|
skip = false
|
148
158
|
options[:tagged].each do |t|
|
149
|
-
unless s['userTags'].include?(t)
|
159
|
+
unless s.key?('userTags') && s['userTags'].include?(t)
|
150
160
|
skip = true
|
151
161
|
break
|
152
162
|
end
|
data/lib/wavefront/cli/ts.rb
CHANGED
@@ -55,8 +55,16 @@ class Wavefront::Cli::Ts < Wavefront::Cli
|
|
55
55
|
options[:end_time] = Time.at(parse_time(@options[:end]))
|
56
56
|
end
|
57
57
|
|
58
|
-
wave = Wavefront::Client.new(@options[:token], @options[:endpoint], @options[:debug])
|
58
|
+
wave = Wavefront::Client.new(@options[:token], @options[:endpoint], @options[:debug], { noop: @options[:noop], verbose: @options[:verbose]})
|
59
|
+
|
60
|
+
if noop
|
61
|
+
wave.query(query, granularity, options)
|
62
|
+
return
|
63
|
+
end
|
64
|
+
|
59
65
|
case options[:response_format]
|
66
|
+
when :json
|
67
|
+
pp wave.query(query, granularity, options)
|
60
68
|
when :raw
|
61
69
|
puts wave.query(query, granularity, options)
|
62
70
|
when :graphite
|
data/lib/wavefront/cli/write.rb
CHANGED
@@ -12,6 +12,13 @@ class Wavefront::Cli::Write < Wavefront::Cli
|
|
12
12
|
include Wavefront::Constants
|
13
13
|
include Wavefront::Mixins
|
14
14
|
|
15
|
+
def validate_opts
|
16
|
+
#
|
17
|
+
# Unlike all the API methods, we don't need a token here
|
18
|
+
#
|
19
|
+
abort 'Please supply a proxy endpoint.' unless options[:proxy]
|
20
|
+
end
|
21
|
+
|
15
22
|
def run
|
16
23
|
valid_value?(options[:'<value>'])
|
17
24
|
valid_metric?(options[:'<metric>'])
|
data/lib/wavefront/client.rb
CHANGED
@@ -28,16 +28,17 @@ module Wavefront
|
|
28
28
|
include Wavefront::Constants
|
29
29
|
DEFAULT_PATH = '/chart/api'
|
30
30
|
|
31
|
-
attr_reader :headers, :base_uri
|
31
|
+
attr_reader :headers, :base_uri, :noop, :verbose
|
32
32
|
|
33
|
-
def initialize(token, host=DEFAULT_HOST, debug=false)
|
33
|
+
def initialize(token, host=DEFAULT_HOST, debug=false, options = {})
|
34
|
+
@verbose = options[:verbose]
|
35
|
+
@noop = options[:noop]
|
34
36
|
@headers = {'X-AUTH-TOKEN' => token}
|
35
37
|
@base_uri = URI::HTTPS.build(:host => host, :path => DEFAULT_PATH)
|
36
38
|
debug(debug)
|
37
39
|
end
|
38
40
|
|
39
41
|
def query(query, granularity='m', options={})
|
40
|
-
|
41
42
|
options[:end_time] ||= Time.now.utc
|
42
43
|
options[:start_time] ||= options[:end_time] - DEFAULT_PERIOD_SECONDS
|
43
44
|
options[:response_format] ||= DEFAULT_FORMAT
|
@@ -48,7 +49,7 @@ module Wavefront
|
|
48
49
|
[ options[:start_time], options[:end_time] ].each { |o| raise Wavefront::Exception::InvalidTimeFormat unless o.is_a?(Time) }
|
49
50
|
raise Wavefront::Exception::InvalidGranularity unless GRANULARITIES.include?(granularity)
|
50
51
|
raise Wavefront::Exception::InvalidResponseFormat unless FORMATS.include?(options[:response_format])
|
51
|
-
raise InvalidPrefixLength unless options[:prefix_length].is_a?(
|
52
|
+
raise InvalidPrefixLength unless options[:prefix_length].is_a?(Integer)
|
52
53
|
|
53
54
|
args = {:params =>
|
54
55
|
{:q => query, :g => granularity, :n => 'Unknown',
|
@@ -62,11 +63,14 @@ module Wavefront
|
|
62
63
|
args[:params].merge!(options[:passthru])
|
63
64
|
end
|
64
65
|
|
66
|
+
puts "GET #{@base_uri.to_s}\nPARAMS #{args.to_s}" if (verbose || noop)
|
67
|
+
|
68
|
+
return if noop
|
69
|
+
|
65
70
|
response = RestClient.get @base_uri.to_s, args
|
66
71
|
|
67
72
|
klass = Object.const_get('Wavefront').const_get('Response').const_get(options[:response_format].to_s.capitalize)
|
68
73
|
return klass.new(response, options)
|
69
|
-
|
70
74
|
end
|
71
75
|
|
72
76
|
private
|