wavefront-client 3.5.4 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env ruby
2
-
3
1
  # Copyright 2015 Wavefront Inc.
4
2
  # Licensed under the Apache License, Version 2.0 (the "License");
5
3
  # you may not use this file except in compliance with the License.
@@ -15,24 +13,38 @@
15
13
 
16
14
  require 'wavefront/alerting'
17
15
  require 'wavefront/cli'
16
+ require 'wavefront/mixins'
18
17
  require 'json'
18
+ require 'yaml'
19
19
  require 'pp'
20
20
  require 'time'
21
21
 
22
22
  class Wavefront::Cli::Alerts < Wavefront::Cli
23
+ include Wavefront::Mixins
23
24
 
24
- attr_accessor :options, :arguments
25
+ attr_accessor :options, :arguments, :wfa
25
26
 
26
27
  def run
27
28
  raise 'Missing token.' if ! @options[:token] || @options[:token].empty?
28
29
  raise 'Missing query.' if arguments.empty?
29
- query = arguments[0].to_sym
30
+ valid_format?(@options[:alertformat].to_sym)
30
31
 
31
- wfa = Wavefront::Alerting.new(@options[:token], @options[:endpoint],
32
+ @wfa = Wavefront::Alerting.new(@options[:token], @options[:endpoint],
32
33
  @options[:debug], {
33
34
  noop: @options[:noop], verbose: @options[:verbose]})
35
+
36
+ if options[:export]
37
+ export_alert(options[:'<timestamp>'])
38
+ return
39
+ end
40
+
41
+ if options[:import]
42
+ import_alert
43
+ return
44
+ end
45
+
46
+ query = arguments[0].to_sym
34
47
  valid_state?(wfa, query)
35
- valid_format?(@options[:alertformat].to_sym)
36
48
  options = { host: @options[:endpoint] }
37
49
 
38
50
  if @options[:shared]
@@ -54,6 +66,46 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
54
66
  exit
55
67
  end
56
68
 
69
+ def import_alert
70
+ raw = load_file(options[:'<file>'])
71
+
72
+ begin
73
+ prepped = wfa.import_to_create(raw)
74
+ rescue => e
75
+ puts e if options[:debug]
76
+ raise 'could not parse input.'
77
+ end
78
+
79
+ begin
80
+ wfa.create_alert(prepped)
81
+ puts 'Alert imported.' unless options[:noop]
82
+ rescue RestClient::BadRequest
83
+ raise '400 error: alert probably exists.'
84
+ end
85
+ end
86
+
87
+ def export_alert(id)
88
+ begin
89
+ resp = wfa.get_alert(id)
90
+ rescue => e
91
+ puts e if @options[:debug]
92
+ raise 'Unable to retrieve alert.'
93
+ end
94
+
95
+ return if options[:noop]
96
+
97
+ case options[:alertformat].to_sym
98
+ when :json
99
+ puts JSON.pretty_generate(resp)
100
+ when :yaml
101
+ puts resp.to_yaml
102
+ when :human
103
+ puts humanize([resp])
104
+ else
105
+ puts 'unknown output format.'
106
+ end
107
+ end
108
+
57
109
  def format_result(result, format)
58
110
  #
59
111
  # Call a suitable method to display the output of the API call,
@@ -66,6 +118,8 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
66
118
  pp result
67
119
  when :json
68
120
  puts JSON.pretty_generate(JSON.parse(result))
121
+ when :yaml
122
+ puts JSON.parse(result).to_yaml
69
123
  when :human
70
124
  puts humanize(JSON.parse(result))
71
125
  else
@@ -137,7 +191,7 @@ class Wavefront::Cli::Alerts < Wavefront::Cli
137
191
  # The 'created' and 'updated' timestamps are in epoch
138
192
  # milliseconds
139
193
  #
140
- human_line(k, Time.at(v / 1000))
194
+ human_line(k, "#{Time.at(v / 1000)} (#{v})")
141
195
  end
142
196
 
143
197
  def human_line_updated(k, v)
@@ -0,0 +1,138 @@
1
+ require 'wavefront/dashboards'
2
+ require 'wavefront/cli'
3
+ require 'pathname'
4
+ require 'json'
5
+ require 'yaml'
6
+
7
+ class Wavefront::Cli::Dashboards < Wavefront::Cli
8
+ attr_accessor :wfd
9
+
10
+ include Wavefront::Constants
11
+ include Wavefront::Mixins
12
+
13
+ def run
14
+ @wfd = Wavefront::Dashboards.new(
15
+ options[:token], options[:endpoint], options[:debug],
16
+ noop: options[:noop], verbose: options[:verbose]
17
+ )
18
+
19
+ list_dashboards if options[:list]
20
+ export_dash if options[:export]
21
+ create_dash if options[:create]
22
+ delete_dash if options[:delete]
23
+ undelete_dash if options[:undelete]
24
+ history_dash if options[:history]
25
+ clone_dash if options[:clone]
26
+ import_dash if options[:import]
27
+ end
28
+
29
+ def import_dash
30
+ wfd.import(load_file(options[:'<file>']).to_json, options[:force])
31
+ puts 'Dashboard imported' unless options[:noop]
32
+ rescue RestClient::BadRequest
33
+ raise '400 error: dashboard probably exists, and force not used'
34
+ end
35
+
36
+ def clone_dash
37
+ wfd.clone(options[:'<source_id>'], options[:'<new_id>'],
38
+ options[:'<new_name>'], options[:version])
39
+ puts 'Dashboard cloned' unless options[:noop]
40
+ rescue RestClient::BadRequest
41
+ raise '400 error: either target exists or source does not'
42
+ end
43
+
44
+ def history_dash
45
+ begin
46
+ resp = wfd.history(options[:'<dashboard_id>'],
47
+ options[:start] || 100,
48
+ options[:limit] || nil)
49
+ rescue RestClient::ResourceNotFound
50
+ raise 'Dashboard does not exist'
51
+ end
52
+
53
+ display_resp(resp, :human_history)
54
+ end
55
+
56
+ def undelete_dash
57
+ wfd.undelete(options[:'<dashboard_id>'])
58
+ puts 'dashboard undeleted' unless options[:noop]
59
+ rescue RestClient::ResourceNotFound
60
+ raise 'Dashboard does not exist'
61
+ end
62
+
63
+ def delete_dash
64
+ wfd.delete(options[:'<dashboard_id>'])
65
+ puts 'dashboard deleted' unless options[:noop]
66
+ rescue RestClient::ResourceNotFound
67
+ raise 'Dashboard does not exist'
68
+ end
69
+
70
+ def create_dash
71
+ wfd.create(options[:'<dashboard_id>'], options[:'<name>'])
72
+ puts 'dashboard created' unless options[:noop]
73
+ rescue RestClient::BadRequest
74
+ raise '400 error: dashboard probably exists'
75
+ end
76
+
77
+ def export_dash
78
+ resp = wfd.export(options[:'<dashboard_id>'], options[:version] || nil)
79
+ options[:dashformat] = :json if options[:dashformat] == :human
80
+ display_resp(resp)
81
+ end
82
+
83
+ def list_dashboards
84
+ resp = wfd.list({ private: options[:privatetag],
85
+ shared: options[:sharedtag] })
86
+ display_resp(resp, :human_list)
87
+ end
88
+
89
+ def display_resp(resp, human_method = nil)
90
+ return if options[:noop]
91
+
92
+ case options[:dashformat].to_sym
93
+ when :json
94
+ if resp.is_a?(String)
95
+ puts resp
96
+ else
97
+ puts resp.to_json
98
+ end
99
+ when :yaml
100
+ puts resp.to_yaml
101
+ when :human
102
+ unless human_method
103
+ raise 'human output format is not supported by this subcommand'
104
+ end
105
+
106
+ send(human_method, JSON.parse(resp))
107
+ else
108
+ raise 'unsupported output format'
109
+ end
110
+ end
111
+
112
+ def human_history(resp)
113
+ resp.each do |rev|
114
+ puts format('%-4s%s (%s)', rev['version'],
115
+ Time.at(rev['update_time'].to_i / 1000),
116
+ rev['update_user'])
117
+
118
+ next unless rev['change_description']
119
+ rev['change_description'].each { |desc| puts ' ' + desc }
120
+ end
121
+ end
122
+
123
+ def human_list(resp)
124
+ #
125
+ # Simply list the dashboards we have. If the user wants more
126
+ #
127
+ max_id_width = resp.map { |s| s['url'].size }.max
128
+
129
+ puts format("%-#{max_id_width + 1}s%s", 'ID', 'NAME')
130
+
131
+ resp.each do |dash|
132
+ next if !options[:all] && dash['isTrash']
133
+ line = format("%-#{max_id_width + 1}s%s", dash['url'], dash['name'])
134
+ line.<< ' (in trash)' if dash['isTrash']
135
+ puts line
136
+ end
137
+ end
138
+ end
@@ -16,6 +16,6 @@ See the License for the specific language governing permissions and
16
16
 
17
17
  module Wavefront
18
18
  class Client
19
- VERSION = "3.5.4"
19
+ VERSION = "3.6.0"
20
20
  end
21
21
  end
@@ -26,10 +26,12 @@ module Wavefront
26
26
  DEFAULT_STRICT = true
27
27
  DEFAULT_OBSOLETE_METRICS = false
28
28
  FORMATS = [ :raw, :ruby, :graphite, :highcharts, :human ]
29
- ALERT_FORMATS = [:ruby, :json, :human]
29
+ ALERT_FORMATS = [:ruby, :json, :human, :yaml]
30
30
  SOURCE_FORMATS = [:ruby, :json, :human]
31
+ DASH_FORMATS = [:json, :human, :yaml]
31
32
  DEFAULT_ALERT_FORMAT = :human
32
33
  DEFAULT_SOURCE_FORMAT = :human
34
+ DEFAULT_DASH_FORMAT = :human
33
35
  GRANULARITIES = %w( s m h d )
34
36
  EVENT_STATE_DIR = Pathname.new('/var/tmp/wavefront/events')
35
37
  EVENT_LEVELS = %w(info smoke warn severe)
@@ -51,7 +53,8 @@ module Wavefront
51
53
  format: DEFAULT_FORMAT, # ts output format
52
54
  alertformat: DEFAULT_ALERT_FORMAT, # alert command output format
53
55
  infileformat: DEFAULT_INFILE_FORMAT, # batch writer file format
54
- sourceformat: DEFAULT_SOURCE_FORMAT, # source more output format
56
+ sourceformat: DEFAULT_SOURCE_FORMAT, # source output format
57
+ dashformat: DEFAULT_DASH_FORMAT, # dashboard output format
55
58
  }.freeze
56
59
  end
57
60
  end
@@ -0,0 +1,106 @@
1
+ require_relative 'client/version'
2
+ require_relative 'exception'
3
+ require 'rest_client'
4
+ require 'uri'
5
+ require 'logger'
6
+ require 'wavefront/constants'
7
+ require 'wavefront/mixins'
8
+
9
+ module Wavefront
10
+ #
11
+ # Wrappers around the v1 dashboards API
12
+ #
13
+ class Dashboards
14
+ include Wavefront::Constants
15
+ include Wavefront::Mixins
16
+ DEFAULT_PATH = '/api/dashboard/'.freeze
17
+
18
+ attr_reader :headers, :noop, :verbose, :endpoint
19
+
20
+ def initialize(token, host = DEFAULT_HOST, debug = false, options = {})
21
+ #
22
+ # Following existing convention, 'host' is the Wavefront API endpoint.
23
+ #
24
+ @headers = { :'X-AUTH-TOKEN' => token }
25
+ @endpoint = host
26
+ debug(debug)
27
+ @noop = options[:noop]
28
+ @verbose = options[:verbose]
29
+ end
30
+
31
+ def import(schema, force = false)
32
+ #
33
+ # Imports a dashboard described as a JSON string (schema)
34
+ #
35
+ qs = force ? nil : 'rejectIfExists=true'
36
+ call_post(create_uri(qs: qs), schema, 'application/json')
37
+ end
38
+
39
+ def clone(source_id, dest_id, dest_name, source_ver = nil)
40
+ #
41
+ # Clone a dashboard. If source_ver is not truthy, the latest
42
+ # version of the source is used.
43
+ #
44
+ qs = hash_to_qs(name: dest_name, url: dest_id)
45
+ qs.<< "&v=#{source_ver}" if source_ver
46
+
47
+ call_post(create_uri(path: uri_concat(source_id, 'clone')), qs,
48
+ 'application/x-www-form-urlencoded')
49
+ end
50
+
51
+ def history(id, start = 100, limit = nil)
52
+ qs = "start=#{start}"
53
+ qs.<< "&limit=#{limit}" if limit
54
+
55
+ call_get(create_uri(path: uri_concat(id, 'history'), qs: qs))
56
+ end
57
+
58
+ def list(opts = {})
59
+ qs = []
60
+
61
+ opts[:private].map { |t| qs.<< "userTag=#{t}" } if opts[:private]
62
+ opts[:shared].map { |t| qs.<< "customerTag=#{t}" } if opts[:shared]
63
+
64
+ call_get(create_uri(qs: qs.join('&')))
65
+ end
66
+
67
+ def undelete(id)
68
+ call_post(create_uri(path: uri_concat(id, 'undelete')))
69
+ end
70
+
71
+ def delete(id)
72
+ call_post(create_uri(path: uri_concat(id, 'delete')))
73
+ end
74
+
75
+ def export(id, version = nil)
76
+ path = version ? uri_concat(id, version) : id
77
+ resp = call_get(create_uri(path: path)) || '{}'
78
+ JSON.parse(resp)
79
+ end
80
+
81
+ def create(id, name)
82
+ call_post(create_uri(path: uri_concat([id, 'create'])),
83
+ "name=#{URI.encode(name)}",
84
+ 'application/x-www-form-urlencoded')
85
+ end
86
+
87
+ def create_uri(options = {})
88
+ #
89
+ # Build the URI we use to send a 'create' request.
90
+ #
91
+ options[:host] ||= endpoint
92
+ options[:path] ||= ''
93
+ options[:qs] ||= nil
94
+
95
+ URI::HTTPS.build(
96
+ host: options[:host],
97
+ path: uri_concat(DEFAULT_PATH, options[:path]),
98
+ query: options[:qs]
99
+ )
100
+ end
101
+
102
+ def debug(enabled)
103
+ RestClient.log = 'stdout' if enabled
104
+ end
105
+ end
106
+ end
@@ -209,40 +209,6 @@ module Wavefront
209
209
  )
210
210
  end
211
211
 
212
- def call_get(uri)
213
- if (verbose || noop)
214
- puts 'GET ' + uri.to_s
215
- puts 'HEADERS ' + headers.to_s
216
- end
217
- return if noop
218
- RestClient.get(uri.to_s, headers)
219
- end
220
-
221
- def call_delete(uri)
222
- if (verbose || noop)
223
- puts 'DELETE ' + uri.to_s
224
- puts 'HEADERS ' + headers.to_s
225
- end
226
- return if noop
227
- RestClient.delete(uri.to_s, headers)
228
- end
229
-
230
- def call_post(uri, query = nil)
231
- h = headers
232
- if (verbose || noop)
233
- puts 'POST ' + uri.to_s
234
- puts 'QUERY ' + query if query
235
- puts 'HEADERS ' + h.to_s
236
- end
237
- return if noop
238
-
239
- RestClient.post(uri.to_s, query,
240
- h.merge(:'Content-Type' => 'text/plain',
241
- :Accept => 'application/json'
242
- )
243
- )
244
- end
245
-
246
212
  def debug(enabled)
247
213
  RestClient.log = 'stdout' if enabled
248
214
  end
@@ -33,14 +33,12 @@ module Wavefront
33
33
  #
34
34
  # Return a time as an integer, however it might come in.
35
35
  #
36
- begin
37
- return t if t.is_a?(Integer)
38
- return t.to_i if t.is_a?(Time)
39
- return t.to_i if t.is_a?(String) && t.match(/^\d+$/)
40
- DateTime.parse("#{t} #{Time.now.getlocal.zone}").to_time.utc.to_i
41
- rescue
42
- raise "cannot parse timestamp '#{t}'."
43
- end
36
+ return t if t.is_a?(Integer)
37
+ return t.to_i if t.is_a?(Time)
38
+ return t.to_i if t.is_a?(String) && t.match(/^\d+$/)
39
+ DateTime.parse("#{t} #{Time.now.getlocal.zone}").to_time.utc.to_i
40
+ rescue
41
+ raise "cannot parse timestamp '#{t}'."
44
42
  end
45
43
 
46
44
  def time_to_ms(t)
@@ -62,5 +60,56 @@ module Wavefront
62
60
  def uri_concat(*args)
63
61
  args.join('/').squeeze('/')
64
62
  end
63
+
64
+ def call_get(uri)
65
+ if verbose || noop
66
+ puts 'GET ' + uri.to_s
67
+ puts 'HEADERS ' + headers.to_s
68
+ end
69
+ return if noop
70
+ RestClient.get(uri.to_s, headers)
71
+ end
72
+
73
+ def call_post(uri, query = nil, ctype = 'text/plain')
74
+ h = headers
75
+ if verbose || noop
76
+ puts 'POST ' + uri.to_s
77
+ puts 'QUERY ' + query if query
78
+ puts 'HEADERS ' + h.to_s
79
+ end
80
+ return if noop
81
+
82
+ RestClient.post(uri.to_s, query,
83
+ h.merge(:'Content-Type' => ctype,
84
+ :Accept => 'application/json'))
85
+ end
86
+
87
+ def call_delete(uri)
88
+ if verbose || noop
89
+ puts 'DELETE ' + uri.to_s
90
+ puts 'HEADERS ' + headers.to_s
91
+ end
92
+ return if noop
93
+ RestClient.delete(uri.to_s, headers)
94
+ end
95
+
96
+ def load_file(path)
97
+ #
98
+ # Give it a path to a file (as a string) and it will return the
99
+ # contents of that file as a Ruby object. Automatically detects
100
+ # JSON and YAML. Raises an exception if it doesn't look like
101
+ # either.
102
+ #
103
+ file = Pathname.new(path)
104
+ raise 'Import file does not exist.' unless file.exist?
105
+
106
+ if file.extname == '.json'
107
+ JSON.parse(IO.read(file))
108
+ elsif file.extname == '.yaml' || file.extname == '.yml'
109
+ YAML.load(IO.read(file))
110
+ else
111
+ raise 'Unsupported file format.'
112
+ end
113
+ end
65
114
  end
66
115
  end