wavefront-client 3.4.0 → 3.5.1

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 CHANGED
@@ -1,15 +1,7 @@
1
1
  ---
2
- !binary "U0hBMQ==":
3
- metadata.gz: !binary |-
4
- ZDFjYTkzZTY4OGIzM2JlYTJiYmZmMGU0NjQzYjVmYTY1Y2MyZTBlOA==
5
- data.tar.gz: !binary |-
6
- MjBhZjI0MzA5NjdhNDhlZmU4OTBkN2E1MjVmNmYxZDA2MWNhN2YwMA==
2
+ SHA1:
3
+ metadata.gz: eb93397040acca02ee9271182b09bf0d1e34a57e
4
+ data.tar.gz: 324e62b59979579b09b750398d53257142cab2a0
7
5
  SHA512:
8
- metadata.gz: !binary |-
9
- Nzc4MWJhMzNiYjAzZmYzYTYyYWRlNTQ4OTBjNzI2ZDg0YmVkMTdhYjNlYjNj
10
- MjM2MjEzMmQzNzFiYjI3YjQ1Y2FjZTlhYTFlODlhODg4Yjk5NjFhNTI4NzU1
11
- YjBhMWU3ODA4M2EzZTZiNWYyZTMwNWRhMTY4NDM4MzE2OGE1YzY=
12
- data.tar.gz: !binary |-
13
- MjQ3MDIzNzRhZTYwZGMwM2FlMjM3N2FjN2UzODg3OGQxNWYxMGY3ZDM3ZDc5
14
- OGQ0NGRjYjY1NmUxZDYxODJkYjg5NmU3MzQ3NTRkNzY5NzYxY2YyNDdmMzRj
15
- NDlmZGM3YWM5ZmU2YTMxOGFiMzk3MzRkM2E2MmE5MzA4MzZkYjU=
6
+ metadata.gz: b6093581d6aa9403fe3de7cbf683bc2698460093ba623651a3b2e75d243d5ea8745cdd13b8c87d73ee073cd07704f46eaee33f95417d65a1fc13a5f55eea023a
7
+ data.tar.gz: 03fad5a006ac5a881ef03db7dd859a54afa3eb57a25933c2c0ce3a18eb4ff862930ab8a032f6aa50dd93cecdb1647b699fc70dc4618c3f4948bc641d84f08124
data/.gitignore CHANGED
@@ -3,3 +3,5 @@ Gemfile.lock
3
3
  pkg
4
4
  rdoc
5
5
  spec/reports
6
+ .yardoc/
7
+ doc
data/.travis.yml CHANGED
@@ -3,9 +3,9 @@ cache: bundler
3
3
  rvm:
4
4
  - 1.9.3
5
5
  - 2.0.0
6
- - 2.1.0
7
- - 2.2.2
8
- - 2.3.0
6
+ - 2.1.10
7
+ - 2.2.5
8
+ - 2.3.1
9
9
  deploy:
10
10
  provider: rubygems
11
11
  api_key:
data/README-cli.md CHANGED
@@ -357,6 +357,103 @@ second. Plot the parabola in Wavefront.
357
357
  $ parabola.rb | wavefront write file -F tv -m cli.demo.parabola -
358
358
  ```
359
359
 
360
+ ## `sources` Mode: Tagging and Describing
361
+
362
+ This command is used to add tags and descriptions to Wavefront
363
+ sources. Note that source tags are not the same as point tags.
364
+
365
+ ```
366
+ Usage:
367
+ wavefront source list [-c file] [-P profile] [-E endpoint] [-t token]
368
+ [-f format] [-T tag ...] [-at] [-s source] [-l limit] <pattern>
369
+ wavefront source show [-c file] [-P profile] [-E endpoint] [-t token]
370
+ [-f format] <host> ...
371
+ wavefront source describe [-c file] [-P profile] [-E endpoint] [-t token]
372
+ [-H host ... ] <description>
373
+ wavefront source undescribe [-c file] [-P profile] [-E endpoint] [-t token]
374
+ [<host>] ...
375
+ wavefront source tag add [-c file] [-P profile] [-E endpoint] [-t token]
376
+ [-H host ... ] <tag> ...
377
+ wavefront source tag delete [-c file] [-P profile] [-E endpoint] [-t token]
378
+ [-H host ... ] <tag> ...
379
+ wavefront source untag [-c file] [-P profile] [-E endpoint] [-t token]
380
+ [<host>] ...
381
+ wavefront source --help
382
+
383
+ Global options:
384
+ -c, --config=FILE path to configuration file [default: /home/rob/.wavefront]
385
+ -P, --profile=NAME profile in configuration file [default: default]
386
+ -D, --debug enable debug mode
387
+ -h, --help show this message
388
+
389
+ Options:
390
+ -a, --all including hidden sources in 'human' output
391
+ -t, --tags show tag counts in 'human' output
392
+ -T, --tagged=STRING only list sources with this tag in 'human' output
393
+ -s, --start=STRING start the list after the named source
394
+ -l, --limit=NUMBER only list NUMBER sources
395
+ -H, --host=STRING source to manipulate
396
+ -f, --format=STRING output format (ruby, json, human)
397
+ [default: human]
398
+ ```
399
+
400
+ Tags and descriptions can be applied to multiple sources by repeated
401
+ `-H` options. If no source name is supplied, `wavefront` will use
402
+ the name of the local machine, as supplied by Ruby's
403
+ `Socket.gethostname` method.
404
+
405
+ The `<pattern>` argument in to the `source list` works as a
406
+ substring match. So `pie` will match `pie`, `pier`, `timepieces`,
407
+ etc. Regular expressions will not work.
408
+
409
+ ### Examples
410
+
411
+ List, in human-readable format, all active (non-hidden) sources whose name
412
+ contains `cassandra`, which are tagged with `prod` and `eu-west-1`.
413
+
414
+ ```
415
+ $ wavefront source list -T prod -T eu-west-1 -f human cassandra
416
+ ```
417
+
418
+ Tag this host with `dev` and the kernel version:
419
+
420
+ ```
421
+ $ wavefront tag add dev $(uname -r)
422
+ ```
423
+
424
+ Remove all the tags from `i-123456` and `i-abcdef`
425
+
426
+ ```
427
+ $ wavefront source untag i-123456 i-abcdef
428
+ ```
429
+
430
+ Get the description and tags for the host `build-001`, in JSON format.
431
+
432
+ ```
433
+ $ wavefront source show -f json build-001 | json
434
+ {
435
+ "hostname": "build-001",
436
+ "userTags": [
437
+ "JPC",
438
+ "SmartOS",
439
+ "dev"
440
+ ],
441
+ "description": "build server"
442
+ }
443
+ ```
444
+
445
+ Get a human-readable summary of all the source tags in Wavefront. This works by giving a source name pattern that won't match anything.
446
+
447
+ ```
448
+ $ wavefront source list -t '^$'
449
+ HOSTNAME DESCRIPTION TAGS
450
+
451
+ TAG COUNT
452
+ hidden 339
453
+ physical 10
454
+ zone 363
455
+ ```
456
+
360
457
  ## Notes on Options
361
458
 
362
459
  ### Times
@@ -406,3 +503,6 @@ endpoint = metrics.wavefront.com
406
503
  The key for each key-value pair can match any long option show in the
407
504
  command `help`, so you can set, for instance, a default output
408
505
  format, as shown above.
506
+
507
+ If an option is defined by a command-line switch, and in the
508
+ configuration file, the config file will win.
data/README.md CHANGED
@@ -4,6 +4,20 @@ Wavefront [![Build Status](https://travis-ci.org/wavefrontHQ/ruby-client.svg?bra
4
4
  This is a ruby gem for speaking to the [Wavefront][1] monitoring and graphing system.
5
5
 
6
6
  ## Usage
7
+
8
+ To build API documentation with [YARD](https://github.com/lsegal/yard)
9
+ run
10
+
11
+ ```
12
+ $ rake yard
13
+ ...
14
+ $ cd doc
15
+ $ yard server
16
+ ```
17
+
18
+ and documentation will be available at
19
+ [http://localhost:8808](http://localhost:8808).
20
+
7
21
  Within your own ruby code:
8
22
 
9
23
  ### Writer
@@ -164,13 +178,13 @@ response.highcharts[0]['data'].first # [1436849460000, 517160277.3333333]
164
178
  ```
165
179
 
166
180
  ### Command-line client
167
- A command line client is included too. Please see [README-cli.md]
168
- for details.
181
+ A command line client is included too. Please see
182
+ [README-cli.md](README-cli.md) for details.
169
183
 
170
184
  ## Building and installing
171
185
 
172
186
  ```bash
173
- gem build ./wavefront-client.gemspec && gem install ./wavefront*.gem --no-rdoc --no-ri
187
+ rake build
174
188
  ```
175
189
 
176
190
  or
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- =begin
1
+ =begin
2
2
  Copyright 2015 Wavefront Inc.
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@ See the License for the specific language governing permissions and
16
16
 
17
17
  require "bundler/gem_tasks"
18
18
  require "rspec/core/rake_task"
19
+ require 'yard'
19
20
 
20
21
  RSpec::Core::RakeTask.new(:spec)
21
22
 
@@ -26,3 +27,7 @@ task :install do
26
27
  sh 'gem install wavefront-client-*.gem --no-rdoc --no-ri'
27
28
  sh 'rm wavefront-client-*.gem'
28
29
  end
30
+
31
+ YARD::Rake::YardocTask.new do |t|
32
+ t.files = ['lib/**/*.rb']
33
+ end
data/bin/wavefront CHANGED
@@ -39,7 +39,6 @@ DEF_CF = if ENV['HOME']
39
39
  Pathname.new('/etc/wavefront/client.conf')
40
40
  end
41
41
 
42
-
43
42
  # The global_opts are available in every command.
44
43
  #
45
44
  global_opts = %(
@@ -146,6 +145,34 @@ Files are whitespace separated, and fields can be defined with the -F
146
145
  option. Use 't' for timestamp; 'm' for metric name; 'v' for value
147
146
  and 'T' for tags. Put 'T' last.
148
147
  ),
148
+ source: %(
149
+ Usage:
150
+ #{ME} source list [-c file] [-P profile] [-E endpoint] [-t token]
151
+ [-f format] [-T tag ...] [-at] [-s source] [-l limit] <pattern>
152
+ #{ME} source show [-c file] [-P profile] [-E endpoint] [-t token]
153
+ [-f format] <host> ...
154
+ #{ME} source describe [-c file] [-P profile] [-E endpoint] [-t token]
155
+ [-H host ... ] <description>
156
+ #{ME} source undescribe [-c file] [-P profile] [-E endpoint] [-t token]
157
+ [<host>] ...
158
+ #{ME} source tag add [-c file] [-P profile] [-E endpoint] [-t token]
159
+ [-H host ... ] <tag> ...
160
+ #{ME} source tag delete [-c file] [-P profile] [-E endpoint] [-t token]
161
+ [-H host ... ] <tag> ...
162
+ #{ME} source untag [-c file] [-P profile] [-E endpoint] [-t token]
163
+ [<host>] ...
164
+ #{ME} source --help
165
+ #{global_opts}
166
+ Options:
167
+ -a, --all including hidden sources in 'human' output
168
+ -t, --tags show tag counts in 'human' output
169
+ -T, --tagged=STRING only list sources with this tag in 'human' output
170
+ -s, --start=STRING start the list after the named source
171
+ -l, --limit=NUMBER only list NUMBER sources
172
+ -H, --host=STRING source to manipulate
173
+ -f, --format=STRING output format (#{ALERT_FORMATS.join(', ')})
174
+ [default: #{DEFAULT_ALERT_FORMAT}]
175
+ ),
149
176
  default: %(
150
177
  Wavefront CLI
151
178
 
@@ -158,6 +185,7 @@ Commands:
158
185
  ts view timeseries data
159
186
  alerts view alerts
160
187
  event open and close events
188
+ source view and manage source tags and descriptions
161
189
  write send data points to a Wavefront proxy
162
190
 
163
191
  Use '#{ME} <command> --help' for further information.)
@@ -182,8 +210,8 @@ rescue Docopt::Exit => e
182
210
  end
183
211
  end
184
212
 
185
- # Load the config file. Values in there take priority. Probably
186
- # should be the other way round.
213
+ # Load the config file. Values in there take priority, otherwise
214
+ # default (unset) options will win.
187
215
  #
188
216
  opts.merge!(Wavefront::Cli.new(opts, nil).load_profile || {})
189
217
 
@@ -197,6 +225,9 @@ when :event
197
225
  when :alerts
198
226
  require 'wavefront/cli/alerts'
199
227
  cli = Wavefront::Cli::Alerts.new(opts, [opts[:'<state>']])
228
+ when :source
229
+ require 'wavefront/cli/sources'
230
+ cli = Wavefront::Cli::Sources.new(opts, [opts[:'<state>']])
200
231
  when :write
201
232
  if opts[:file]
202
233
  require 'wavefront/cli/batch_write'
@@ -1,4 +1,4 @@
1
- =begin
1
+ =begin
2
2
  Copyright 2015 Wavefront Inc.
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
@@ -15,8 +15,8 @@ See the License for the specific language governing permissions and
15
15
  =end
16
16
 
17
17
  require "wavefront/client/version"
18
- require "wavefront/exception"
19
18
  require "wavefront/constants"
19
+ require 'wavefront/mixins'
20
20
  require 'rest_client'
21
21
  require 'uri'
22
22
  require 'logger'
@@ -24,6 +24,8 @@ require 'logger'
24
24
  module Wavefront
25
25
  class Alerting
26
26
  include Wavefront::Constants
27
+ include Wavefront::Validators
28
+ include Wavefront::Mixins
27
29
  DEFAULT_PATH = '/api/alert/'
28
30
 
29
31
  attr_reader :token
@@ -55,35 +57,39 @@ module Wavefront
55
57
 
56
58
  private
57
59
 
58
- def get_alerts(path, options={})
59
- options[:host] ||= DEFAULT_HOST
60
- options[:path] ||= DEFAULT_PATH
60
+ def list_of_tags(t)
61
+ t.is_a?(Array) ? t : [t]
62
+ end
63
+
64
+ def mk_qs(options)
65
+ query = "t=#{token}"
66
+
67
+ query += '&' + list_of_tags(options[:shared_tags]).map do |t|
68
+ "customerTag=#{t}"
69
+ end.join('&') if options[:shared_tags]
61
70
 
62
- query = "t=#{@token}"
71
+ query += '&' + list_of_tags(options[:private_tags]).map do |t|
72
+ "userTag=#{t}"
73
+ end.join('&') if options[:private_tags]
63
74
 
64
- if options[:shared_tags]
65
- tags = options[:shared_tags].class == Array ? options[:shared_tags] : [ options[:shared_tags] ] # Force an array, even if string given
66
- query += "&#{tags.map{|t| "customerTag=#{t}"}.join('&')}"
67
- end
75
+ query
76
+ end
68
77
 
69
- if options[:private_tags]
70
- tags = options[:private_tags].class == Array ? options[:private_tags] : [ options[:private_tags] ] # Force an array, even if string given
71
- query += "&#{tags.map{|t| "userTag=#{t}"}.join('&')}"
72
- end
78
+ def get_alerts(path, options={})
79
+ options[:host] ||= DEFAULT_HOST
80
+ options[:path] ||= DEFAULT_PATH
73
81
 
74
82
  uri = URI::HTTPS.build(
75
- host: options[:host],
76
- path: File.join(options[:path], path),
77
- query: query
83
+ host: options[:host],
84
+ path: uri_concat(options[:path], path),
85
+ query: mk_qs(options),
78
86
  )
87
+
79
88
  RestClient.get(uri.to_s)
80
89
  end
81
90
 
82
91
  def debug(enabled)
83
- if enabled
84
- RestClient.log = 'stdout'
85
- end
92
+ RestClient.log = 'stdout' if enabled
86
93
  end
87
-
88
94
  end
89
95
  end
@@ -1,6 +1,7 @@
1
1
  require 'wavefront/client/version'
2
2
  require 'wavefront/exception'
3
3
  require 'wavefront/constants'
4
+ require 'wavefront/validators'
4
5
  require 'uri'
5
6
  require 'socket'
6
7
 
@@ -27,6 +28,7 @@ module Wavefront
27
28
  class BatchWriter
28
29
  attr_reader :sock, :opts, :summary
29
30
  include Wavefront::Constants
31
+ include Wavefront::Validators
30
32
 
31
33
  def initialize(options = {})
32
34
  #
@@ -117,7 +119,7 @@ module Wavefront
117
119
 
118
120
  send_point(hash_to_wf(p))
119
121
  end
120
- return summary[:rejected] == 0 ? true : false
122
+ summary[:rejected] == 0 ? true : false
121
123
  end
122
124
 
123
125
  def valid_point?(point)
@@ -134,41 +136,6 @@ module Wavefront
134
136
  true
135
137
  end
136
138
 
137
- def valid_path?(path)
138
- fail Wavefront::Exception::InvalidMetricName unless \
139
- path.is_a?(String) && path.match(/^[a-z0-9\-_\.]+$/) &&
140
- path.length < 1024
141
- true
142
- end
143
-
144
- def valid_value?(value)
145
- fail Wavefront::Exception::InvalidMetricValue unless value.is_a?(Numeric)
146
- true
147
- end
148
-
149
- def valid_ts?(ts)
150
- unless ts.is_a?(Time) || ts.is_a?(Date)
151
- fail Wavefront::Exception::InvalidTimestamp
152
- end
153
- true
154
- end
155
-
156
- def valid_source?(path)
157
- unless path.is_a?(String) && path.match(/^[a-z0-9\-_\.]+$/) &&
158
- path.length < 1024
159
- fail Wavefront::Exception::InvalidSource
160
- end
161
- true
162
- end
163
-
164
- def valid_tags?(tags)
165
- tags.each do |k, v|
166
- fail Wavefront::Exception::InvalidTag unless (k.length +
167
- v.length < 254) && k.match(/^[a-z0-9\-_\.]+$/)
168
- end
169
- true
170
- end
171
-
172
139
  def hash_to_wf(p)
173
140
  #
174
141
  # Convert the hash received by the write() method to a string
@@ -65,10 +65,9 @@ class Wavefront::Cli::Events < Wavefront::Cli
65
65
  end
66
66
 
67
67
  begin
68
- response = wf_event.delete({
69
- startTime: options[:'<timestamp>'],
70
- name: options[:'<event>'],
71
- })
68
+ wf_event.delete(startTime: options[:'<timestamp>'],
69
+ name: options[:'<event>']
70
+ )
72
71
  rescue RestClient::Unauthorized
73
72
  raise 'Cannot connect to Wavefront API.'
74
73
  rescue RestClient::ResourceNotFound
@@ -78,7 +77,7 @@ class Wavefront::Cli::Events < Wavefront::Cli
78
77
  raise 'Cannot delete event.'
79
78
  end
80
79
 
81
- puts "Deleted event."
80
+ puts 'Deleted event.'
82
81
  end
83
82
 
84
83
  def prep_time(t)
@@ -0,0 +1,193 @@
1
+ require 'wavefront/cli'
2
+ require 'wavefront/metadata'
3
+ require 'json'
4
+ require 'pp'
5
+
6
+ #
7
+ # Turn CLI input, from the 'sources' command, into metadata API calls
8
+ #
9
+ class Wavefront::Cli::Sources < Wavefront::Cli
10
+ attr_accessor :wf, :out_format, :show_hidden, :show_tags
11
+
12
+ def setup_wf
13
+ @wf = Wavefront::Metadata.new(options[:token])
14
+ end
15
+
16
+ def run
17
+ setup_wf
18
+ @out_format = options[:format]
19
+ @show_hidden = options[:all]
20
+ @show_tags = options[:tags]
21
+
22
+ begin
23
+ if options[:list]
24
+ list_source_handler(options[:'<pattern>'], options[:start],
25
+ options[:limit])
26
+ elsif options[:show]
27
+ show_source_handler(options[:'<host>'])
28
+ elsif options[:tag] && options[:add]
29
+ add_tag_handler(options[:host], options[:'<tag>'])
30
+ elsif options[:tag] && options[:delete]
31
+ delete_tag_handler(options[:host], options[:'<tag>'])
32
+ elsif options[:describe]
33
+ describe_handler(options[:host], options[:'<description>'])
34
+ elsif options[:undescribe]
35
+ describe_handler(options[:'<host>'], '')
36
+ elsif options[:untag]
37
+ untag_handler(options[:'<host>'])
38
+ else
39
+ fail 'undefined sources error'
40
+ end
41
+ rescue Wavefront::Exception::InvalidSource
42
+ abort 'ERROR: invalid source name.'
43
+ end
44
+ end
45
+
46
+ def list_source_handler(pattern, start = false, limit = false)
47
+ limit ||= 100
48
+
49
+ q = {
50
+ desc: false,
51
+ limit: limit,
52
+ pattern: pattern
53
+ }
54
+
55
+ q[:lastEntityId] = start if start
56
+
57
+ display_data(JSON.parse(wf.show_sources(q)), 'list_source')
58
+ end
59
+
60
+ def describe_handler(hosts, desc)
61
+ hosts = [Socket.gethostname] if hosts.empty?
62
+
63
+ hosts.each do |h|
64
+ if desc.empty?
65
+ puts "clearing description of '#{h}'"
66
+ else
67
+ puts "setting '#{h}' description to '#{desc}'"
68
+ end
69
+
70
+ begin
71
+ wf.set_description(h, desc)
72
+ rescue Wavefront::Exception::InvalidString
73
+ puts 'ERROR: description contains invalid characters.'
74
+ end
75
+ end
76
+ end
77
+
78
+ def untag_handler(hosts)
79
+ hosts ||= [Socket.gethostname]
80
+
81
+ hosts.each do |h|
82
+ puts "Removing all tags from '#{h}'"
83
+ wf.delete_tags(h)
84
+ end
85
+ end
86
+
87
+ def add_tag_handler(hosts, tags)
88
+ hosts ||= [Socket.gethostname]
89
+
90
+ hosts.each do |h|
91
+ tags.each do |t|
92
+ puts "Tagging '#{h}' with '#{t}'"
93
+ begin
94
+ wf.set_tag(h, t)
95
+ rescue Wavefront::Exception::InvalidString
96
+ puts 'ERROR: tag contains invalid characters.'
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def delete_tag_handler(hosts, tags)
103
+ hosts ||= [Socket.gethostname]
104
+
105
+ hosts.each do |h|
106
+ tags.each do |t|
107
+ puts "Removing tag '#{t}' from '#{h}'"
108
+ wf.delete_tag(h, t)
109
+ end
110
+ end
111
+ end
112
+
113
+ def show_source_handler(sources)
114
+ sources.each do |s|
115
+ begin
116
+ result = JSON.parse(wf.show_source(s))
117
+ rescue RestClient::ResourceNotFound
118
+ puts "Source '#{s}' not found."
119
+ next
120
+ end
121
+
122
+ display_data(result, 'show_source')
123
+ end
124
+ end
125
+
126
+ def display_data(result, method)
127
+ if out_format == 'human'
128
+ puts public_send('humanize_' + method, result)
129
+ elsif out_format == 'json'
130
+ puts result.to_json
131
+ else
132
+ pp result
133
+ end
134
+ end
135
+
136
+ def humanize_list_source(result)
137
+ hdr = format('%-25s %-30s %s', 'HOSTNAME', 'DESCRIPTION', 'TAGS')
138
+
139
+ ret = result['sources'].each_with_object([hdr]) do |s, aggr|
140
+ if s.include?('userTags') && s['userTags'].include?('hidden') &&
141
+ !show_hidden
142
+ next
143
+ end
144
+
145
+ if options[:tagged]
146
+ skip = false
147
+ options[:tagged].each do |t|
148
+ unless s['userTags'].include?(t)
149
+ skip = true
150
+ break
151
+ end
152
+ end
153
+ next if skip
154
+ end
155
+
156
+ if s['description']
157
+ desc = s['description']
158
+ desc = desc[0..27] + '...' if desc.length > 30
159
+ else
160
+ desc = ''
161
+ end
162
+
163
+ tags = s['userTags'] ? s['userTags'].join(', ') : ''
164
+
165
+ aggr.<< format('%-25s %-30s %s', s['hostname'], desc, tags)
166
+ end
167
+
168
+ if show_tags
169
+ ret.<< ['', format('%-25s%s', 'TAG', 'COUNT')]
170
+
171
+ result['counts'].each do |tag, count|
172
+ ret.<< format('%-25s%s', tag, count)
173
+ end
174
+ end
175
+
176
+ ret.join("\n")
177
+ end
178
+
179
+ def humanize_show_source(data)
180
+ ret = [data['hostname']]
181
+
182
+ if data['description']
183
+ ret.<< format(' %-15s%s', 'description', data['description'])
184
+ end
185
+
186
+ if data['userTags']
187
+ ret.<< format(' %-15s%s', 'tags', data['userTags'].shift)
188
+ data['userTags'].each { |t| ret.<< format(' %-15s%s', '', t) }
189
+ end
190
+
191
+ ret.join("\n")
192
+ end
193
+ end