wavefront-cli 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|