wavefront-client 3.5.4 → 3.6.0

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.
@@ -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