wavefront-cli 2.3.1 → 2.4.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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/lib/wavefront-cli/base.rb +26 -10
- data/lib/wavefront-cli/base_write.rb +231 -0
- data/lib/wavefront-cli/commands/report.rb +35 -0
- data/lib/wavefront-cli/commands/write.rb +5 -2
- data/lib/wavefront-cli/controller.rb +8 -4
- data/lib/wavefront-cli/display/report.rb +17 -0
- data/lib/wavefront-cli/display/write.rb +7 -4
- data/lib/wavefront-cli/output/base.rb +10 -0
- data/lib/wavefront-cli/output/hcl.rb +20 -0
- data/lib/wavefront-cli/output/hcl/alert.rb +14 -0
- data/lib/wavefront-cli/output/hcl/base.rb +92 -0
- data/lib/wavefront-cli/output/hcl/dashboard.rb +114 -0
- data/lib/wavefront-cli/output/hcl/notificant.rb +30 -0
- data/lib/wavefront-cli/output/json.rb +12 -0
- data/lib/wavefront-cli/output/ruby.rb +12 -0
- data/lib/wavefront-cli/output/wavefront.tf +257 -0
- data/lib/wavefront-cli/output/yaml.rb +15 -0
- data/lib/wavefront-cli/report.rb +15 -0
- data/lib/wavefront-cli/string.rb +12 -1
- data/lib/wavefront-cli/version.rb +1 -1
- data/lib/wavefront-cli/write.rb +9 -221
- data/spec/wavefront-cli/string_spec.rb +24 -0
- data/wavefront-cli.gemspec +1 -1
- metadata +20 -6
@@ -0,0 +1,20 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module WavefrontOutput
|
4
|
+
#
|
5
|
+
# Display objects in an HCL-compatible way, for use with the
|
6
|
+
# Wavefront Terraform provider. We farm everything out, as
|
7
|
+
# different resource types need various amounts of massaging. Args
|
8
|
+
# are passed through to the child class.
|
9
|
+
#
|
10
|
+
class Hcl < Base
|
11
|
+
def run
|
12
|
+
require_relative File.join('hcl', options[:class])
|
13
|
+
oclass = Object.const_get(format('WavefrontHclOutput::%s',
|
14
|
+
options[:class].to_s.capitalize))
|
15
|
+
oclass.new(resp, options).run
|
16
|
+
rescue LoadError
|
17
|
+
abort "no HCL output for #{options[:class]}."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module WavefrontHclOutput
|
4
|
+
#
|
5
|
+
# Define alerts which can be understood by the Wavefront Terraform
|
6
|
+
# provider.
|
7
|
+
#
|
8
|
+
class Alert < Base
|
9
|
+
def hcl_fields
|
10
|
+
%w[name target condition additional_information display_expression
|
11
|
+
minutes resolve_after_minutes severity tags]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module WavefrontHclOutput
|
5
|
+
class Base
|
6
|
+
attr_reader :resp, :options
|
7
|
+
|
8
|
+
def initialize(resp, options)
|
9
|
+
@resp = resp
|
10
|
+
@options = options
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
puts open_output
|
15
|
+
required_fields.each { |k, v| puts handler(k, v) }
|
16
|
+
puts close_output
|
17
|
+
end
|
18
|
+
|
19
|
+
# Fields which the provider requires.
|
20
|
+
# @return [Array] of strings
|
21
|
+
#
|
22
|
+
def hcl_fields
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
|
26
|
+
def open_output
|
27
|
+
format('resource "wavefront_%s" "%s" {', resource_name,
|
28
|
+
SecureRandom.uuid)
|
29
|
+
end
|
30
|
+
|
31
|
+
def close_output
|
32
|
+
'}'
|
33
|
+
end
|
34
|
+
|
35
|
+
# Override this if the provider calls a resource something other
|
36
|
+
# than the name of the inheriting class
|
37
|
+
#
|
38
|
+
def resource_name
|
39
|
+
options[:class]
|
40
|
+
end
|
41
|
+
|
42
|
+
# The provider can only handle certain keys. Each class should
|
43
|
+
# provide a list of things it knows the provider requires. If it
|
44
|
+
# does not, we display everything
|
45
|
+
#
|
46
|
+
def required_fields
|
47
|
+
return resp if hcl_fields.empty?
|
48
|
+
resp.select { |k, _v| hcl_fields.include?(k) }
|
49
|
+
end
|
50
|
+
|
51
|
+
# Format each key-value pair
|
52
|
+
# @param k [String] key
|
53
|
+
# @param v [Any] value
|
54
|
+
# @return [String]
|
55
|
+
#
|
56
|
+
def handler(k, v)
|
57
|
+
key_handler = "khandle_#{k}".to_sym
|
58
|
+
value_handler = "vhandle_#{k}".to_sym
|
59
|
+
quote_handler = "qhandle_#{k}".to_sym
|
60
|
+
k = send(key_handler) if respond_to?(key_handler)
|
61
|
+
v = send(value_handler, v) if respond_to?(value_handler)
|
62
|
+
|
63
|
+
quote_handler = :quote_value unless respond_to?(quote_handler)
|
64
|
+
|
65
|
+
format(' %s = %s', k.to_snake, send(quote_handler, v))
|
66
|
+
end
|
67
|
+
|
68
|
+
# Tags need to be in an array. They aren't always called "tags"
|
69
|
+
# by the API.
|
70
|
+
# @param v [Array,Hash,String] tags
|
71
|
+
# @return [Array] of soft-quoted tags
|
72
|
+
#
|
73
|
+
def vhandle_tags(v)
|
74
|
+
v = v.values if v.is_a?(Hash)
|
75
|
+
Array(v).flatten
|
76
|
+
end
|
77
|
+
|
78
|
+
# Some values need to be quoted, some need to be escaped etc
|
79
|
+
# etc.
|
80
|
+
# @param v [Object] value
|
81
|
+
# @return [String]
|
82
|
+
#
|
83
|
+
def quote_value(v)
|
84
|
+
case v.class.to_s.to_sym
|
85
|
+
when :String
|
86
|
+
format('"%s"', v.gsub(/\"/, '\"'))
|
87
|
+
else
|
88
|
+
v
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module WavefrontHclOutput
|
4
|
+
#
|
5
|
+
# This is a rather kludgy class which generates HCL output
|
6
|
+
# suitable for the Wavefront Terraform provider. It has to work
|
7
|
+
# round a number of inconsistencies and omissions in said
|
8
|
+
# provider, and will have to change as the provider improves.
|
9
|
+
#
|
10
|
+
# It works, manually, down the hierarchy described
|
11
|
+
# in https://github.com/spaceapegames/terraform-provider-wavefront/blob/master/wavefront/resource_dashboard.go
|
12
|
+
#
|
13
|
+
class Dashboard < Base
|
14
|
+
|
15
|
+
# Top-level fields
|
16
|
+
#
|
17
|
+
def hcl_fields
|
18
|
+
%w[name description url sections parameter_details tags]
|
19
|
+
end
|
20
|
+
|
21
|
+
def khandle_sections
|
22
|
+
'section'
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param vals [Array] an array of objects
|
26
|
+
# @param fn [Symbol] a method which knows how to deal with one
|
27
|
+
# of the objects in vals
|
28
|
+
# @return [String] HCL list of vals
|
29
|
+
#
|
30
|
+
def listmaker(vals, fn)
|
31
|
+
vals.each_with_object([]) { |v, a| a.<< send(fn, v) }.to_hcl_list
|
32
|
+
end
|
33
|
+
|
34
|
+
def vhandle_sections(v)
|
35
|
+
v.each_with_object([]) do |section, a|
|
36
|
+
a.<< ("name = \"#{section[:name]}\"\n row = " +
|
37
|
+
handle_rows(section[:rows])).braced(4)
|
38
|
+
end.to_hcl_list
|
39
|
+
end
|
40
|
+
|
41
|
+
def handle_rows(rows)
|
42
|
+
rows.each_with_object([]) do |row, a|
|
43
|
+
a.<< ("chart = " + handle_charts(row[:charts]).to_s).braced(8)
|
44
|
+
end.to_hcl_list
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_charts(charts)
|
48
|
+
listmaker(charts, :handle_chart)
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle_chart(chart)
|
52
|
+
fields = %w[units name description]
|
53
|
+
|
54
|
+
lines = chart.each_with_object([]) do |(k, v), a|
|
55
|
+
a.<< format('%s = %s', k, quote_value(v)) if fields.include?(k)
|
56
|
+
end
|
57
|
+
|
58
|
+
lines.<< "source = #{handle_sources(chart[:sources])}"
|
59
|
+
lines.to_hcl_obj(10)
|
60
|
+
end
|
61
|
+
|
62
|
+
def handle_sources(sources)
|
63
|
+
listmaker(sources, :handle_source)
|
64
|
+
end
|
65
|
+
|
66
|
+
def handle_source(source)
|
67
|
+
fields = %w[name query disabled scatterPlotSource querybuilderEnabled
|
68
|
+
sourceDescription]
|
69
|
+
|
70
|
+
source.each_with_object([]) do |(k, v), a|
|
71
|
+
if fields.include?(k)
|
72
|
+
k = 'queryBuilderEnabled' if k == 'querybuilderEnabled'
|
73
|
+
a.<< format('%s = %s', k.to_snake, quote_value(v))
|
74
|
+
end
|
75
|
+
end.to_hcl_obj(14)
|
76
|
+
end
|
77
|
+
|
78
|
+
def qhandle_sections(v)
|
79
|
+
v
|
80
|
+
end
|
81
|
+
|
82
|
+
def quote_value(v)
|
83
|
+
v.gsub!(/\$/, '$$') if v.is_a?(String)
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class String
|
90
|
+
def braced(indent = 0)
|
91
|
+
pad = ' ' * indent
|
92
|
+
"\n#{pad}{#{self}\n#{pad}}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
class Array
|
97
|
+
#
|
98
|
+
# Turn an array into a string which represents an HCL list
|
99
|
+
# @return [String]
|
100
|
+
#
|
101
|
+
def to_hcl_list
|
102
|
+
'[' + self.join(',') + ']'
|
103
|
+
end
|
104
|
+
|
105
|
+
# Turn an array into a string which represents an HCL object
|
106
|
+
# @return [String]
|
107
|
+
#
|
108
|
+
def to_hcl_obj(indent = 0)
|
109
|
+
outpad = ' ' * indent
|
110
|
+
inpad = ' ' * (indent + 2)
|
111
|
+
|
112
|
+
"\n#{outpad}{\n#{inpad}" + self.join("\n#{inpad}") + "\n#{outpad}}"
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module WavefrontHclOutput
|
4
|
+
#
|
5
|
+
# Munge notificant output into something compatible with the
|
6
|
+
# Wavefront Terraform provider
|
7
|
+
#
|
8
|
+
class Notificant < Base
|
9
|
+
def hcl_fields
|
10
|
+
%w[title description triggers template method recipient emailSubject
|
11
|
+
contentType customHttpHeaders]
|
12
|
+
end
|
13
|
+
|
14
|
+
def vhandle_template(v)
|
15
|
+
v.gsub(/\s*\n/, '')
|
16
|
+
end
|
17
|
+
|
18
|
+
def resource_name
|
19
|
+
'alert_target'
|
20
|
+
end
|
21
|
+
|
22
|
+
def khandle_title
|
23
|
+
'name'
|
24
|
+
end
|
25
|
+
|
26
|
+
def khandle_customHttpHeaders
|
27
|
+
'custom_headers'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
MERP MERP
|
2
|
+
resource "wavefront_dashboard" "9f8d3f29-d965-4f7e-8473-43571e431b99" {
|
3
|
+
name = "JPC Website Host"
|
4
|
+
description = "overview of SmartOS zones"
|
5
|
+
url = "jpc-webhost"
|
6
|
+
section = [
|
7
|
+
{ name = "Memory"
|
8
|
+
row = [
|
9
|
+
{ chart = [
|
10
|
+
{
|
11
|
+
units = "B"
|
12
|
+
name = "Swap Free"
|
13
|
+
description = "Remember, on Solaris, \"swap\" means backing store, so this is all memory."
|
14
|
+
source = [
|
15
|
+
{
|
16
|
+
scatter_plot_source = "Y"
|
17
|
+
query_builder_enabled = false
|
18
|
+
source_description = ""
|
19
|
+
name = "New Query"
|
20
|
+
query = "ts(\"tenant.swapresv.value\", env=${env}) - ts(\"tenant.swapresv.usage\", env=${env})"
|
21
|
+
}]
|
22
|
+
},
|
23
|
+
{
|
24
|
+
units = ""
|
25
|
+
name = "Free Physical Memory"
|
26
|
+
source = [
|
27
|
+
{
|
28
|
+
scatter_plot_source = "Y"
|
29
|
+
query_builder_enabled = false
|
30
|
+
source_description = ""
|
31
|
+
name = "physical memory"
|
32
|
+
query = "ts(\"tenant.physicalmem.value\", env=${env}) - ts(\"tenant.physicalmem.usage\", env=${env})"
|
33
|
+
},
|
34
|
+
{
|
35
|
+
scatter_plot_source = "Y"
|
36
|
+
query_builder_enabled = true
|
37
|
+
source_description = ""
|
38
|
+
name = "out of memory"
|
39
|
+
query = "0"
|
40
|
+
}]
|
41
|
+
},
|
42
|
+
{
|
43
|
+
units = ""
|
44
|
+
name = "times over memory"
|
45
|
+
source = [
|
46
|
+
{
|
47
|
+
scatter_plot_source = "Y"
|
48
|
+
query_builder_enabled = false
|
49
|
+
source_description = ""
|
50
|
+
name = "times over memory"
|
51
|
+
query = "deriv(ts(\"tenant.memory_cap.nover\", env=${env}))"
|
52
|
+
}]
|
53
|
+
}]
|
54
|
+
},
|
55
|
+
{ chart = [
|
56
|
+
{
|
57
|
+
units = "B"
|
58
|
+
name = "Sinatra application memory"
|
59
|
+
source = [
|
60
|
+
{
|
61
|
+
scatter_plot_source = "Y"
|
62
|
+
query_builder_enabled = false
|
63
|
+
source_description = ""
|
64
|
+
name = "New Query"
|
65
|
+
query = "sum(ts(\"process.memory.pr_rssize.*\", svc=\"svc:/application/sinatra/*\" and env=${env}), svc)"
|
66
|
+
}]
|
67
|
+
},
|
68
|
+
{
|
69
|
+
units = ""
|
70
|
+
name = "top memory consumers"
|
71
|
+
source = [
|
72
|
+
{
|
73
|
+
scatter_plot_source = "Y"
|
74
|
+
query_builder_enabled = false
|
75
|
+
source_description = ""
|
76
|
+
name = "by process name"
|
77
|
+
query = "sum(ts(\"process.memory.pr_rssize.*\", env=${env}), metrics)"
|
78
|
+
}]
|
79
|
+
}]
|
80
|
+
}]
|
81
|
+
},
|
82
|
+
{ name = "nginx"
|
83
|
+
row = [
|
84
|
+
{ chart = [
|
85
|
+
{
|
86
|
+
units = "req/s"
|
87
|
+
name = "\"200\" HTTP requests"
|
88
|
+
source = [
|
89
|
+
{
|
90
|
+
scatter_plot_source = "Y"
|
91
|
+
query_builder_enabled = false
|
92
|
+
source_description = ""
|
93
|
+
name = "Query"
|
94
|
+
query = "rate(ts(\"nginx.code.200\"))"
|
95
|
+
}]
|
96
|
+
},
|
97
|
+
{
|
98
|
+
units = "ms"
|
99
|
+
name = "request latency"
|
100
|
+
source = [
|
101
|
+
{
|
102
|
+
scatter_plot_source = "Y"
|
103
|
+
query_builder_enabled = false
|
104
|
+
source_description = ""
|
105
|
+
name = "total response time"
|
106
|
+
query = "ts(\"nginx.time.response.*\")"
|
107
|
+
},
|
108
|
+
{
|
109
|
+
scatter_plot_source = "Y"
|
110
|
+
query_builder_enabled = false
|
111
|
+
source_description = ""
|
112
|
+
name = "upstream response time"
|
113
|
+
query = "ts(\"nginx.time.upstream_response.*\")"
|
114
|
+
}]
|
115
|
+
},
|
116
|
+
{
|
117
|
+
units = "req/s"
|
118
|
+
name = "HTTP Errors"
|
119
|
+
source = [
|
120
|
+
{
|
121
|
+
scatter_plot_source = "Y"
|
122
|
+
query_builder_enabled = false
|
123
|
+
source_description = ""
|
124
|
+
name = "40x"
|
125
|
+
query = "ceil(rate(ts(\"nginx.code.4*\")))"
|
126
|
+
},
|
127
|
+
{
|
128
|
+
scatter_plot_source = "Y"
|
129
|
+
query_builder_enabled = false
|
130
|
+
source_description = ""
|
131
|
+
name = "50x"
|
132
|
+
query = "ceil(rate(ts(\"nginx.code.5*\")))"
|
133
|
+
}]
|
134
|
+
}]
|
135
|
+
}]
|
136
|
+
},
|
137
|
+
{ name = "Processes and Services"
|
138
|
+
row = [
|
139
|
+
{ chart = [
|
140
|
+
{
|
141
|
+
units = "processes"
|
142
|
+
name = "processes"
|
143
|
+
description = "number active processes in zone"
|
144
|
+
source = [
|
145
|
+
{
|
146
|
+
scatter_plot_source = "Y"
|
147
|
+
query_builder_enabled = true
|
148
|
+
source_description = ""
|
149
|
+
name = "running processes"
|
150
|
+
query = "ts(\"tenant.nprocs.usage\", env=${env})"
|
151
|
+
}]
|
152
|
+
},
|
153
|
+
{
|
154
|
+
units = "services"
|
155
|
+
name = "Service States"
|
156
|
+
description = "count of services in each possible state. Disabled service are not shown,"
|
157
|
+
source = [
|
158
|
+
{
|
159
|
+
scatter_plot_source = "Y"
|
160
|
+
query_builder_enabled = false
|
161
|
+
source_description = ""
|
162
|
+
name = "New Query"
|
163
|
+
query = "ts(\"smf.svcs.*\", env=${env})"
|
164
|
+
}]
|
165
|
+
}]
|
166
|
+
}]
|
167
|
+
},
|
168
|
+
{ name = "Network"
|
169
|
+
row = [
|
170
|
+
{ chart = [
|
171
|
+
{
|
172
|
+
units = "bytes"
|
173
|
+
name = "Network In"
|
174
|
+
source = [
|
175
|
+
{
|
176
|
+
scatter_plot_source = "Y"
|
177
|
+
query_builder_enabled = false
|
178
|
+
source_description = ""
|
179
|
+
name = "New Query"
|
180
|
+
query = "deriv(ts(\"network.*.net0.rbytes64\", env=${env}))"
|
181
|
+
}]
|
182
|
+
},
|
183
|
+
{
|
184
|
+
units = "bytes"
|
185
|
+
name = "Network Out"
|
186
|
+
source = [
|
187
|
+
{
|
188
|
+
scatter_plot_source = "Y"
|
189
|
+
query_builder_enabled = false
|
190
|
+
source_description = ""
|
191
|
+
name = "network out"
|
192
|
+
query = "deriv(ts(\"network.*.net0.obytes64\", env=${env}))"
|
193
|
+
}]
|
194
|
+
}]
|
195
|
+
},
|
196
|
+
{ chart = [
|
197
|
+
{
|
198
|
+
units = "s"
|
199
|
+
name = "Puppet Run Time"
|
200
|
+
source = [
|
201
|
+
{
|
202
|
+
scatter_plot_source = "Y"
|
203
|
+
query_builder_enabled = false
|
204
|
+
source_description = ""
|
205
|
+
name = "time"
|
206
|
+
query = "ts(\"puppet.time.*\", env=${env})"
|
207
|
+
}]
|
208
|
+
},
|
209
|
+
{
|
210
|
+
units = "Units"
|
211
|
+
name = "Puppet Changes"
|
212
|
+
description = ""
|
213
|
+
source = [
|
214
|
+
{
|
215
|
+
scatter_plot_source = "Y"
|
216
|
+
query_builder_enabled = false
|
217
|
+
source_description = ""
|
218
|
+
name = "New Query"
|
219
|
+
query = "ts(\"puppet.changes.total\")"
|
220
|
+
}]
|
221
|
+
}]
|
222
|
+
}]
|
223
|
+
},
|
224
|
+
{ name = "Page Impressions"
|
225
|
+
row = [
|
226
|
+
{ chart = [
|
227
|
+
{
|
228
|
+
units = ""
|
229
|
+
name = "Most read posts"
|
230
|
+
source = [
|
231
|
+
{
|
232
|
+
scatter_plot_source = "Y"
|
233
|
+
query_builder_enabled = false
|
234
|
+
source_description = ""
|
235
|
+
name = "Query"
|
236
|
+
query = "sum(ts(\"nginx.code.200\", vhost=sysdef.xyz and path=\"/post/*\"), pointTags)"
|
237
|
+
}]
|
238
|
+
}]
|
239
|
+
},
|
240
|
+
{ chart = [
|
241
|
+
{
|
242
|
+
units = ""
|
243
|
+
name = "most viewed pieces"
|
244
|
+
description = ""
|
245
|
+
source = [
|
246
|
+
{
|
247
|
+
scatter_plot_source = "Y"
|
248
|
+
query_builder_enabled = false
|
249
|
+
source_description = ""
|
250
|
+
name = "Query"
|
251
|
+
query = "sum(ts(\"nginx.code.200\", vhost=\"rdfisher.co.uk\" and path=\"/piece/*\"), pointTags)"
|
252
|
+
}]
|
253
|
+
}]
|
254
|
+
}]
|
255
|
+
}]
|
256
|
+
tags = []
|
257
|
+
}
|