wavefront-cli 0.0.2
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 +7 -0
- data/.codeclimate.yml +20 -0
- data/.gitignore +4 -0
- data/.travis.yml +16 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +65 -0
- data/README.md +221 -0
- data/Rakefile +18 -0
- data/bin/wavefront +14 -0
- data/lib/wavefront-cli/alert.rb +60 -0
- data/lib/wavefront-cli/base.rb +320 -0
- data/lib/wavefront-cli/cloudintegration.rb +12 -0
- data/lib/wavefront-cli/commands/alert.rb +38 -0
- data/lib/wavefront-cli/commands/base.rb +105 -0
- data/lib/wavefront-cli/commands/dashboard.rb +29 -0
- data/lib/wavefront-cli/commands/event.rb +44 -0
- data/lib/wavefront-cli/commands/integration.rb +33 -0
- data/lib/wavefront-cli/commands/link.rb +34 -0
- data/lib/wavefront-cli/commands/message.rb +23 -0
- data/lib/wavefront-cli/commands/metric.rb +20 -0
- data/lib/wavefront-cli/commands/proxy.rb +25 -0
- data/lib/wavefront-cli/commands/query.rb +32 -0
- data/lib/wavefront-cli/commands/savedsearch.rb +32 -0
- data/lib/wavefront-cli/commands/source.rb +27 -0
- data/lib/wavefront-cli/commands/user.rb +24 -0
- data/lib/wavefront-cli/commands/webhook.rb +25 -0
- data/lib/wavefront-cli/commands/window.rb +33 -0
- data/lib/wavefront-cli/commands/write.rb +35 -0
- data/lib/wavefront-cli/constants.rb +17 -0
- data/lib/wavefront-cli/controller.rb +134 -0
- data/lib/wavefront-cli/dashboard.rb +27 -0
- data/lib/wavefront-cli/display/alert.rb +44 -0
- data/lib/wavefront-cli/display/base.rb +304 -0
- data/lib/wavefront-cli/display/cloudintegration.rb +18 -0
- data/lib/wavefront-cli/display/dashboard.rb +21 -0
- data/lib/wavefront-cli/display/event.rb +19 -0
- data/lib/wavefront-cli/display/externallink.rb +13 -0
- data/lib/wavefront-cli/display/maintenancewindow.rb +19 -0
- data/lib/wavefront-cli/display/message.rb +8 -0
- data/lib/wavefront-cli/display/metric.rb +22 -0
- data/lib/wavefront-cli/display/proxy.rb +13 -0
- data/lib/wavefront-cli/display/query.rb +69 -0
- data/lib/wavefront-cli/display/savedsearch.rb +17 -0
- data/lib/wavefront-cli/display/source.rb +26 -0
- data/lib/wavefront-cli/display/user.rb +16 -0
- data/lib/wavefront-cli/display/webhook.rb +24 -0
- data/lib/wavefront-cli/display/write.rb +19 -0
- data/lib/wavefront-cli/event.rb +162 -0
- data/lib/wavefront-cli/exception.rb +5 -0
- data/lib/wavefront-cli/externallink.rb +16 -0
- data/lib/wavefront-cli/maintenancewindow.rb +16 -0
- data/lib/wavefront-cli/message.rb +19 -0
- data/lib/wavefront-cli/metric.rb +24 -0
- data/lib/wavefront-cli/opt_handler.rb +62 -0
- data/lib/wavefront-cli/proxy.rb +22 -0
- data/lib/wavefront-cli/query.rb +74 -0
- data/lib/wavefront-cli/savedsearch.rb +24 -0
- data/lib/wavefront-cli/source.rb +20 -0
- data/lib/wavefront-cli/user.rb +25 -0
- data/lib/wavefront-cli/version.rb +1 -0
- data/lib/wavefront-cli/webhook.rb +8 -0
- data/lib/wavefront-cli/write.rb +244 -0
- data/spec/spec_helper.rb +197 -0
- data/spec/wavefront-cli/alert_spec.rb +44 -0
- data/spec/wavefront-cli/base_spec.rb +47 -0
- data/spec/wavefront-cli/cli_help_spec.rb +47 -0
- data/spec/wavefront-cli/cloudintegration_spec.rb +24 -0
- data/spec/wavefront-cli/dashboard_spec.rb +37 -0
- data/spec/wavefront-cli/event_spec.rb +19 -0
- data/spec/wavefront-cli/externallink_spec.rb +18 -0
- data/spec/wavefront-cli/maintanancewindow_spec.rb +19 -0
- data/spec/wavefront-cli/message_spec.rb +28 -0
- data/spec/wavefront-cli/metric_spec.rb +22 -0
- data/spec/wavefront-cli/proxy_spec.rb +26 -0
- data/spec/wavefront-cli/query_spec.rb +63 -0
- data/spec/wavefront-cli/resources/conf.yaml +10 -0
- data/spec/wavefront-cli/savedsearch_spec.rb +18 -0
- data/spec/wavefront-cli/source_spec.rb +18 -0
- data/spec/wavefront-cli/user_spec.rb +31 -0
- data/spec/wavefront-cli/webhook_spec.rb +17 -0
- data/wavefront-cli.gemspec +36 -0
- metadata +279 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'externallink' API.
|
6
|
+
#
|
7
|
+
class ExternalLink < WavefrontCli::Base
|
8
|
+
def validator_method
|
9
|
+
:wf_link_id?
|
10
|
+
end
|
11
|
+
|
12
|
+
def validator_exception
|
13
|
+
Wavefront::Exception::InvalidExternalLinkId
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'maintenancewindow' API.
|
6
|
+
#
|
7
|
+
class MaintenanceWindow < WavefrontCli::Base
|
8
|
+
def validator_method
|
9
|
+
:wf_maintenance_window_id?
|
10
|
+
end
|
11
|
+
|
12
|
+
def validator_exception
|
13
|
+
Wavefront::Exception::InvalidMaintenanceWindowId
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'message' API.
|
6
|
+
#
|
7
|
+
class Message < WavefrontCli::Base
|
8
|
+
#
|
9
|
+
# There's an extra flag to "list" that no other commands have.
|
10
|
+
#
|
11
|
+
def do_list
|
12
|
+
wf.list(options[:offset] || 0, options[:limit] || 100, !options[:all])
|
13
|
+
end
|
14
|
+
|
15
|
+
def do_mark
|
16
|
+
wf.read(options[:'<id>'])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'metric' API.
|
6
|
+
#
|
7
|
+
class Metric < WavefrontCli::Base
|
8
|
+
#
|
9
|
+
# There's an extra describe flag that other classes don't have.
|
10
|
+
#
|
11
|
+
def do_describe
|
12
|
+
wf.detail(options[:'<metric>'], options[:glob] || [], options[:offset])
|
13
|
+
end
|
14
|
+
|
15
|
+
def extra_validation
|
16
|
+
return unless options[:'<metric>']
|
17
|
+
begin
|
18
|
+
wf_metric_name?(options[:'<metric>'])
|
19
|
+
rescue Wavefront::Exception::InvalidMetricName
|
20
|
+
abort "'#{options[:'<metric>']}' is not a valid metric."
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'inifile'
|
2
|
+
require 'pathname'
|
3
|
+
require_relative './constants.rb'
|
4
|
+
|
5
|
+
module WavefrontCli
|
6
|
+
#
|
7
|
+
# Options to commands can come from three sources, with the
|
8
|
+
# following order of precedence: program defaults, a configuration
|
9
|
+
# file, and command-line options. Docopt is not well suited to
|
10
|
+
# this, as it will "fill in" any missing options with defaults,
|
11
|
+
# producing a single hash which must be merged with values from
|
12
|
+
# the config file. Assuming we give the command-line higher
|
13
|
+
# precedence, a default value, not supplied by the user, will
|
14
|
+
# override a value in the config file. The other way round, and
|
15
|
+
# you can't override anything in the config file from the
|
16
|
+
# command-line. I think this behaviour is far from unique to
|
17
|
+
# Docopt.
|
18
|
+
#
|
19
|
+
# So, we have a hash of defaults, and we do the merging ourselves,
|
20
|
+
# in this class. We trick Docopt into not using the defaults by
|
21
|
+
# avoiding the magic string 'default: ' in our options stanzas.
|
22
|
+
#
|
23
|
+
class OptHandler
|
24
|
+
include WavefrontCli::Constants
|
25
|
+
|
26
|
+
attr_reader :opts, :cli_opts, :conf_file
|
27
|
+
|
28
|
+
def initialize(conf_file, cli_opts = {})
|
29
|
+
@conf_file = if cli_opts.key?(:config) && cli_opts[:config]
|
30
|
+
Pathname.new(cli_opts[:config])
|
31
|
+
else
|
32
|
+
conf_file
|
33
|
+
end
|
34
|
+
|
35
|
+
@cli_opts = cli_opts.reject { |_k, v| v.nil? }
|
36
|
+
|
37
|
+
@opts = DEFAULT_OPTS.merge(load_profile).merge(@cli_opts)
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_profile
|
41
|
+
#
|
42
|
+
# Load in configuration options from the (optionally) given
|
43
|
+
# section of an ini-style configuration file. If the file's
|
44
|
+
# not there, we don't consider that an error. Returns a hash
|
45
|
+
# of options which matches what Docopt gives us.
|
46
|
+
#
|
47
|
+
unless conf_file.exist?
|
48
|
+
puts "config file '#{conf_file}' not found. Taking options " \
|
49
|
+
'from command-line.'
|
50
|
+
return {}
|
51
|
+
end
|
52
|
+
|
53
|
+
pf = cli_opts.fetch(:profile, 'default')
|
54
|
+
|
55
|
+
puts "reading '#{pf}' profile from '#{conf_file}'" if cli_opts[:debug]
|
56
|
+
|
57
|
+
IniFile.load(conf_file)[pf].each_with_object({}) do |(k, v), memo|
|
58
|
+
memo[k.to_sym] = v
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'proxy' API.
|
6
|
+
#
|
7
|
+
class Proxy < WavefrontCli::Base
|
8
|
+
def do_rename
|
9
|
+
wf_string?(options[:'<name>'])
|
10
|
+
wf.rename(options[:'<id>'], options[:'<name>'])
|
11
|
+
end
|
12
|
+
|
13
|
+
def extra_validation
|
14
|
+
return unless options[:'<name>']
|
15
|
+
begin
|
16
|
+
wf_string?(options[:'<name>'])
|
17
|
+
rescue Wavefront::Exception::InvalidString
|
18
|
+
abort "'#{options[:'<name>']}' is not a valid proxy name."
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'wavefront-sdk/mixins'
|
2
|
+
require_relative './base'
|
3
|
+
|
4
|
+
module WavefrontCli
|
5
|
+
#
|
6
|
+
# CLI coverage for the v2 'query' API.
|
7
|
+
#
|
8
|
+
class Query < WavefrontCli::Base
|
9
|
+
include Wavefront::Mixins
|
10
|
+
|
11
|
+
def do_default
|
12
|
+
opts = {
|
13
|
+
autoEvents: options[:events],
|
14
|
+
i: options[:inclusive],
|
15
|
+
summarization: options[:summarize] || 'mean',
|
16
|
+
listMode: true,
|
17
|
+
strict: true,
|
18
|
+
includeObsoleteMetrics: options[:obsolete],
|
19
|
+
sorted: true
|
20
|
+
}
|
21
|
+
|
22
|
+
if options[:start]
|
23
|
+
options[:start] = parse_time(options[:start], true)
|
24
|
+
else
|
25
|
+
options[:start] = (Time.now - 600).to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
if options[:end]
|
29
|
+
options[:end] = parse_time(options[:end], true)
|
30
|
+
t_end = options[:end]
|
31
|
+
else
|
32
|
+
t_end = Time.now.to_i
|
33
|
+
end
|
34
|
+
|
35
|
+
options[:granularity] ||= default_granularity((t_end -
|
36
|
+
options[:start]).to_i)
|
37
|
+
|
38
|
+
opts[:n] = options[:name] if options[:name]
|
39
|
+
opts[:p] = options[:points] if options[:points]
|
40
|
+
|
41
|
+
wf.query(options[:'<query>'], options[:granularity],
|
42
|
+
options[:start], options[:end] || nil, opts)
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Work out a sensible granularity based on the time window
|
47
|
+
#
|
48
|
+
def default_granularity(window)
|
49
|
+
if window < 300
|
50
|
+
:s
|
51
|
+
elsif window < 10800
|
52
|
+
:m
|
53
|
+
elsif window < 259200
|
54
|
+
:h
|
55
|
+
else
|
56
|
+
:d
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def extra_validation
|
61
|
+
return unless options[:granularity]
|
62
|
+
begin
|
63
|
+
wf_granularity?(options[:granularity])
|
64
|
+
rescue Wavefront::Exception::InvalidGranularity
|
65
|
+
abort "'#{options[:granularity]}' is not a valid granularity."
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def do_raw
|
70
|
+
wf.raw(options[:'<metric>'], options[:host], options[:start],
|
71
|
+
options[:end])
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'savedsearch' API.
|
6
|
+
#
|
7
|
+
class SavedSearch < WavefrontCli::Base
|
8
|
+
def do_list
|
9
|
+
wf.list(options[:offset] || 0, options[:limit] || 100)
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_describe
|
13
|
+
wf.describe(options[:'<id>'])
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_delete
|
17
|
+
wf.delete(options[:'<id>'])
|
18
|
+
end
|
19
|
+
|
20
|
+
def validator_exception
|
21
|
+
Wavefront::Exception::InvalidSavedSearchId
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'source' API.
|
6
|
+
#
|
7
|
+
class Source < WavefrontCli::Base
|
8
|
+
def do_clear
|
9
|
+
wf.delete(options[:'<id>'])
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_description_set
|
13
|
+
wf.update(options[:'<id>'], description: options[:'<description>'])
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_description_clear
|
17
|
+
wf.update(options[:'<id>'], { description: ''}, false)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_relative './base'
|
2
|
+
|
3
|
+
module WavefrontCli
|
4
|
+
#
|
5
|
+
# CLI coverage for the v2 'user' API.
|
6
|
+
#
|
7
|
+
class User < WavefrontCli::Base
|
8
|
+
def do_list
|
9
|
+
wf.list
|
10
|
+
end
|
11
|
+
|
12
|
+
def do_grant
|
13
|
+
wf.grant(options[:'<id>'], options[:'<privilege>'])
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_revoke
|
17
|
+
wf.revoke(options[:'<id>'], options[:'<privilege>'])
|
18
|
+
end
|
19
|
+
|
20
|
+
def import_to_create(raw)
|
21
|
+
raw['emailAddress'] = raw['identifier']
|
22
|
+
raw.delete_if { |k, _v| k == 'customer' || k == 'identifier' }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
WF_CLI_VERSION = '0.0.2'.freeze
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'wavefront-sdk/mixins'
|
2
|
+
require_relative './base'
|
3
|
+
|
4
|
+
module WavefrontCli
|
5
|
+
#
|
6
|
+
# Send points to a proxy.
|
7
|
+
#
|
8
|
+
class Write < Base
|
9
|
+
attr_reader :fmt
|
10
|
+
include Wavefront::Mixins
|
11
|
+
|
12
|
+
def mk_creds
|
13
|
+
{ proxy: options[:proxy], port: options[:port] || 2878 }
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_point
|
17
|
+
p = { path: options[:'<metric>'],
|
18
|
+
value: options[:'<value>'].to_f,
|
19
|
+
tags: tags_to_hash(options[:tag]) }
|
20
|
+
|
21
|
+
p[:source] = options[:host] if options[:host]
|
22
|
+
p[:ts] = parse_time(options[:time]) if options[:time]
|
23
|
+
|
24
|
+
begin
|
25
|
+
wf.write(p)
|
26
|
+
rescue Wavefront::Exception::InvalidEndpoint
|
27
|
+
abort 'could not speak to proxy ' \
|
28
|
+
"'#{options[:proxy]}:#{options[:port]}'."
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def do_file
|
33
|
+
valid_format?(options[:infileformat])
|
34
|
+
setup_fmt(options[:infileformat] || 'tmv')
|
35
|
+
process_input(options[:'<file>'])
|
36
|
+
end
|
37
|
+
|
38
|
+
# Read the input, from a file or from STDIN, and turn each line
|
39
|
+
# into Wavefront points.
|
40
|
+
#
|
41
|
+
def process_input(file)
|
42
|
+
if file == '-'
|
43
|
+
read_stdin
|
44
|
+
else
|
45
|
+
data = load_data(Pathname.new(file)).split("\n").map do |l|
|
46
|
+
process_line(l)
|
47
|
+
end
|
48
|
+
|
49
|
+
wf.write(data)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Read from standard in and stream points through an open
|
54
|
+
# socket. If the user hits ctrl-c, close the socket and exit
|
55
|
+
# politely.
|
56
|
+
#
|
57
|
+
def read_stdin
|
58
|
+
wf.open
|
59
|
+
STDIN.each_line { |l| wf.write(process_line(l.strip), false) }
|
60
|
+
wf.close
|
61
|
+
rescue SystemExit, Interrupt
|
62
|
+
puts 'ctrl-c. Exiting.'
|
63
|
+
wf.close
|
64
|
+
exit 0
|
65
|
+
end
|
66
|
+
|
67
|
+
# Find and return the value in a chunked line of input
|
68
|
+
#
|
69
|
+
# param chunks [Array] a chunked line of input from #process_line
|
70
|
+
# return [Float] the value
|
71
|
+
# raise TypeError if field does not exist
|
72
|
+
# raise Wavefront::Exception::InvalidValue if it's not a value
|
73
|
+
#
|
74
|
+
def extract_value(chunks)
|
75
|
+
v = chunks[fmt.index('v')]
|
76
|
+
v.to_f
|
77
|
+
end
|
78
|
+
|
79
|
+
# Find and return the source in a chunked line of input.
|
80
|
+
#
|
81
|
+
# param chunks [Array] a chunked line of input from #process_line
|
82
|
+
# return [Float] the timestamp, if it is there, or the current
|
83
|
+
# UTC time if it is not.
|
84
|
+
# raise TypeError if field does not exist
|
85
|
+
#
|
86
|
+
def extract_ts(chunks)
|
87
|
+
ts = chunks[fmt.index('t')]
|
88
|
+
return parse_time(ts) if valid_timestamp?(ts)
|
89
|
+
rescue TypeError
|
90
|
+
Time.now.utc.to_i
|
91
|
+
end
|
92
|
+
|
93
|
+
def extract_tags(chunks)
|
94
|
+
tags_to_hash(chunks.last.split(/\s(?=(?:[^"]|"[^"]*")*$)/))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Find and return the metric path in a chunked line of input.
|
98
|
+
# The path can be in the data, or passed as an option, or both.
|
99
|
+
# If the latter, then we assume the option is a prefix, and
|
100
|
+
# concatenate the value in the data.
|
101
|
+
#
|
102
|
+
# param chunks [Array] a chunked line of input from #process_line
|
103
|
+
# return [String] the metric path
|
104
|
+
# raise TypeError if field does not exist
|
105
|
+
#
|
106
|
+
def extract_path(chunks)
|
107
|
+
m = chunks[fmt.index('m')]
|
108
|
+
return options[:metric] ? [options[:metric], m].join('.') : m
|
109
|
+
rescue TypeError
|
110
|
+
return options[:metric] if options[:metric]
|
111
|
+
raise
|
112
|
+
end
|
113
|
+
|
114
|
+
# Find and return the source in a chunked line of input.
|
115
|
+
#
|
116
|
+
# param chunks [Array] a chunked line of input from #process_line
|
117
|
+
# return [String] the source, if it is there, or if not, the
|
118
|
+
# value passed through by -H, or the local hostname.
|
119
|
+
#
|
120
|
+
def extract_source(chunks)
|
121
|
+
return chunks[fmt.index('s')]
|
122
|
+
rescue TypeError
|
123
|
+
options[:source] || Socket.gethostname
|
124
|
+
end
|
125
|
+
|
126
|
+
# Process a line of input, as described by the format string
|
127
|
+
# held in @fmt. Produces a hash suitable for the SDK to send on.
|
128
|
+
#
|
129
|
+
# We let the user define most of the fields, but anything beyond
|
130
|
+
# what they define is always assumed to be point tags. This is
|
131
|
+
# because you can have arbitrarily many of those for each point.
|
132
|
+
#
|
133
|
+
def process_line(l)
|
134
|
+
return true if l.empty?
|
135
|
+
chunks = l.split(/\s+/, fmt.length)
|
136
|
+
raise 'wrong number of fields' unless enough_fields?(l)
|
137
|
+
|
138
|
+
begin
|
139
|
+
point = { path: extract_path(chunks),
|
140
|
+
value: extract_value(chunks) }
|
141
|
+
point[:ts] = extract_ts(chunks) if fmt.include?('t')
|
142
|
+
point[:source] = extract_source(chunks) if fmt.include?('s')
|
143
|
+
point[:tags] = line_tags(chunks)
|
144
|
+
rescue TypeError
|
145
|
+
raise "could not process #{l}"
|
146
|
+
end
|
147
|
+
|
148
|
+
point
|
149
|
+
end
|
150
|
+
|
151
|
+
# We can get tags from the file, from the -T option, or both.
|
152
|
+
# Merge them, making the -T win if there is a collision.
|
153
|
+
#
|
154
|
+
def line_tags(chunks)
|
155
|
+
file_tags = fmt.last == 'T' ? extract_tags(chunks) : {}
|
156
|
+
opt_tags = tags_to_hash(options[:tag])
|
157
|
+
file_tags.merge(opt_tags)
|
158
|
+
end
|
159
|
+
|
160
|
+
# Takes an array of key=value tags (as produced by docopt) and
|
161
|
+
# turns it into a hash of key: value tags. Anything not of the
|
162
|
+
# form key=val is dropped. If key or value are quoted, we
|
163
|
+
# remove the quotes.
|
164
|
+
#
|
165
|
+
# @param tags [Array]
|
166
|
+
# return Hash
|
167
|
+
#
|
168
|
+
def tags_to_hash(tags)
|
169
|
+
return nil unless tags
|
170
|
+
|
171
|
+
[tags].flatten.each_with_object({}) do |t, ret|
|
172
|
+
k, v = t.split('=', 2)
|
173
|
+
k.gsub!(/^["']|["']$/, '')
|
174
|
+
ret[k] = v.to_s.gsub(/^["']|["']$/, '') if v
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# The format string must contain a 'v'. It must not contain
|
179
|
+
# anything other than 'm', 't', 'T', 's', or 'v', and the 'T',
|
180
|
+
# if there, must be at the end. No letter must appear more than
|
181
|
+
# once.
|
182
|
+
#
|
183
|
+
# @param fmt [String] format of input file
|
184
|
+
#
|
185
|
+
def valid_format?(fmt)
|
186
|
+
if fmt.include?('v') && fmt.match(/^[mstv]+T?$/) &&
|
187
|
+
fmt == fmt.split('').uniq.join
|
188
|
+
return true
|
189
|
+
end
|
190
|
+
|
191
|
+
raise 'Invalid format string.'
|
192
|
+
end
|
193
|
+
|
194
|
+
# Make sure we have the right number of columns, according to
|
195
|
+
# the format string. We want to take every precaution we can to
|
196
|
+
# stop users accidentally polluting their metric namespace with
|
197
|
+
# junk.
|
198
|
+
#
|
199
|
+
# If the format string says we are expecting point tags, we
|
200
|
+
# may have more columns than the length of the format string.
|
201
|
+
#
|
202
|
+
def enough_fields?(l)
|
203
|
+
ncols = l.split.length
|
204
|
+
|
205
|
+
if fmt.include?('T')
|
206
|
+
return false unless ncols >= fmt.length
|
207
|
+
else
|
208
|
+
return false unless ncols == fmt.length
|
209
|
+
end
|
210
|
+
|
211
|
+
true
|
212
|
+
end
|
213
|
+
|
214
|
+
# Although the SDK does value checking, we'll add another layer
|
215
|
+
# of input checing here. See if the time looks valid. We'll
|
216
|
+
# assume anything before 2000/01/01 or after a year from now is
|
217
|
+
# wrong. Arbitrary, but there has to be a cut-off somewhere.
|
218
|
+
#
|
219
|
+
def valid_timestamp?(ts)
|
220
|
+
(ts.is_a?(Integer) || ts.match(/^\d+$/)) &&
|
221
|
+
ts.to_i > 946684800 && ts.to_i < (Time.now.to_i + 31557600)
|
222
|
+
end
|
223
|
+
|
224
|
+
def validate_opts
|
225
|
+
unless options[:metric] || options[:format].include?('m')
|
226
|
+
abort "Supply a metric path in the file or with '-m'."
|
227
|
+
end
|
228
|
+
|
229
|
+
raise 'Please supply a proxy address.' unless options[:proxy]
|
230
|
+
end
|
231
|
+
|
232
|
+
private
|
233
|
+
|
234
|
+
def setup_fmt(fmt)
|
235
|
+
@fmt = fmt.split('')
|
236
|
+
end
|
237
|
+
|
238
|
+
def load_data(file)
|
239
|
+
IO.read(file)
|
240
|
+
rescue
|
241
|
+
raise "Cannot open file '#{file}'." unless file.exist?
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|