lbrt 0.1.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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +4 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +180 -0
  8. data/Rakefile +6 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +7 -0
  11. data/exe/lbrt +15 -0
  12. data/lbrt.gemspec +31 -0
  13. data/lib/lbrt.rb +46 -0
  14. data/lib/lbrt/alert.rb +91 -0
  15. data/lib/lbrt/alert/dsl.rb +9 -0
  16. data/lib/lbrt/alert/dsl/context.rb +32 -0
  17. data/lib/lbrt/alert/dsl/context/alert.rb +75 -0
  18. data/lib/lbrt/alert/dsl/context/alert/condition.rb +61 -0
  19. data/lib/lbrt/alert/dsl/converter.rb +113 -0
  20. data/lib/lbrt/alert/exporter.rb +38 -0
  21. data/lib/lbrt/cli.rb +2 -0
  22. data/lib/lbrt/cli/alert.rb +26 -0
  23. data/lib/lbrt/cli/app.rb +15 -0
  24. data/lib/lbrt/cli/service.rb +26 -0
  25. data/lib/lbrt/cli/space.rb +27 -0
  26. data/lib/lbrt/driver.rb +240 -0
  27. data/lib/lbrt/ext/string_ext.rb +25 -0
  28. data/lib/lbrt/logger.rb +32 -0
  29. data/lib/lbrt/service.rb +73 -0
  30. data/lib/lbrt/service/dsl.rb +9 -0
  31. data/lib/lbrt/service/dsl/context.rb +33 -0
  32. data/lib/lbrt/service/dsl/context/service.rb +32 -0
  33. data/lib/lbrt/service/dsl/converter.rb +38 -0
  34. data/lib/lbrt/service/exporter.rb +37 -0
  35. data/lib/lbrt/space.rb +126 -0
  36. data/lib/lbrt/space/dsl.rb +9 -0
  37. data/lib/lbrt/space/dsl/context.rb +29 -0
  38. data/lib/lbrt/space/dsl/context/space.rb +19 -0
  39. data/lib/lbrt/space/dsl/context/space/chart.rb +44 -0
  40. data/lib/lbrt/space/dsl/context/space/chart/stream.rb +48 -0
  41. data/lib/lbrt/space/dsl/converter.rb +107 -0
  42. data/lib/lbrt/space/exporter.rb +56 -0
  43. data/lib/lbrt/utils.rb +64 -0
  44. data/lib/lbrt/version.rb +3 -0
  45. metadata +202 -0
@@ -0,0 +1,9 @@
1
+ class Lbrt::Alert::DSL
2
+ def self.convert(exported, options = {})
3
+ Lbrt::Alert::DSL::Converter.convert(exported, options)
4
+ end
5
+
6
+ def self.parse(client, dsl, path, options = {})
7
+ Lbrt::Alert::DSL::Context.eval(client, dsl, path, options).result
8
+ end
9
+ end
@@ -0,0 +1,32 @@
1
+ class Lbrt::Alert::DSL::Context
2
+ include Lbrt::Utils::ContextHelper
3
+
4
+ def self.eval(client, dsl, path, options = {})
5
+ self.new(client, path, options) {
6
+ eval(dsl, binding, path)
7
+ }
8
+ end
9
+
10
+ attr_reader :result
11
+
12
+ def initialize(client, path, options = {}, &block)
13
+ @path = path
14
+ @options = options
15
+ @result = {}
16
+ @services = Lbrt::Service::Exporter.export(client, options)
17
+ instance_eval(&block)
18
+ end
19
+
20
+ private
21
+
22
+ def alert(name, &block)
23
+ name = name.to_s
24
+
25
+ if @result[name]
26
+ raise "Alert `#{name}` is already defined"
27
+ end
28
+
29
+ alrt = Lbrt::Alert::DSL::Context::Alert.new(name, @services, &block).result
30
+ @result[name] = alrt
31
+ end
32
+ end
@@ -0,0 +1,75 @@
1
+ class Lbrt::Alert::DSL::Context::Alert
2
+ REQUIRED_ATTRIBUTES = %w(
3
+ description
4
+ attributes
5
+ active
6
+ rearm_seconds
7
+ rearm_per_signal
8
+ )
9
+
10
+ def initialize(name, services, &block)
11
+ @name = name
12
+ @services = services
13
+
14
+ @result = {
15
+ 'attributes' => {},
16
+ 'conditions' => [],
17
+ 'services' => [],
18
+ }
19
+
20
+ instance_eval(&block)
21
+ end
22
+
23
+ def result
24
+ REQUIRED_ATTRIBUTES.each do |name|
25
+ unless @result.has_key?(name)
26
+ raise "Alert `#{@name}`: #{name} is not defined"
27
+ end
28
+ end
29
+
30
+ @result
31
+ end
32
+
33
+ private
34
+
35
+ def description(value)
36
+ @result['description'] = value.to_s
37
+ end
38
+
39
+ def attributes(value)
40
+ unless value.is_a?(Hash)
41
+ raise TypeError, "wrong argument type #{value.class}: #{value.inspect} (expected Hash)"
42
+ end
43
+
44
+ @result['attributes'] = value
45
+ end
46
+
47
+ def active(value)
48
+ @result['active'] = !!value
49
+ end
50
+
51
+ def rearm_seconds(value)
52
+ @result['rearm_seconds'] = value.to_i
53
+ end
54
+
55
+ def rearm_per_signal(value)
56
+ @result['rearm_per_signal'] = !!value
57
+ end
58
+
59
+ def condition(&block)
60
+ @result['conditions'] << Lbrt::Alert::DSL::Context::Alert::Condition.new(@name, &block).result
61
+ end
62
+
63
+ def service(type, title)
64
+ service_key = [type, title]
65
+ service = @services[service_key]
66
+
67
+ unless service
68
+ raise "Service `#{type}/#{title}` is not found"
69
+ end
70
+
71
+ service['title'] = title
72
+ service['type'] = type
73
+ @result['services'] << service
74
+ end
75
+ end
@@ -0,0 +1,61 @@
1
+ class Lbrt::Alert::DSL::Context::Alert::Condition
2
+ REQUIRED_ATTRIBUTES = %w(
3
+ type
4
+ metric_name
5
+ source
6
+ )
7
+
8
+ REQUIRED_ATTRIBUTES_BY_TYPE = {
9
+ 'above' => %W(threshold summary_function),
10
+ 'below' => %W(threshold summary_function),
11
+ 'active' => %W(duration),
12
+ }
13
+
14
+ def initialize(alert_name, &block)
15
+ @alert_name = alert_name
16
+ @result = {}
17
+ instance_eval(&block)
18
+ end
19
+
20
+ def result
21
+ REQUIRED_ATTRIBUTES.each do |name|
22
+ unless @result.has_key?(name)
23
+ raise "Alert `#{@alert_name}` > Condition > `#{name}` is not defined"
24
+ end
25
+ end
26
+
27
+ REQUIRED_ATTRIBUTES_BY_TYPE.fetch(@result['type'], {}).each do |name|
28
+ unless @result.has_key?(name)
29
+ raise "Alert `#{@alert_name}` > Condition > `#{name}` is not defined"
30
+ end
31
+ end
32
+
33
+ @result
34
+ end
35
+
36
+ private
37
+
38
+ def type(value)
39
+ @result['type'] = value.to_s
40
+ end
41
+
42
+ def metric_name(value)
43
+ @result['metric_name'] = value.to_s
44
+ end
45
+
46
+ def source(value)
47
+ @result['source'] = value.nil? ? nil : value.to_s
48
+ end
49
+
50
+ def threshold(value)
51
+ @result['threshold'] = value.to_f
52
+ end
53
+
54
+ def summary_function(value)
55
+ @result['summary_function'] = value.to_s
56
+ end
57
+
58
+ def duration(value)
59
+ @result['duration'] = value.to_i
60
+ end
61
+ end
@@ -0,0 +1,113 @@
1
+ class Lbrt::Alert::DSL::Converter
2
+ def self.convert(exported, options = {})
3
+ self.new(exported, options).convert
4
+ end
5
+
6
+ def initialize(exported, options = {})
7
+ @exported = exported
8
+ @options = options
9
+ end
10
+
11
+ def convert
12
+ output_alerts(@exported)
13
+ end
14
+
15
+ private
16
+
17
+ def output_alerts(alert_by_name)
18
+ alerts = []
19
+
20
+ alert_by_name.sort_by(&:first).map do |name, attrs|
21
+ next unless Lbrt::Utils.matched?(name, @options[:target])
22
+ alerts << output_alert(name, attrs)
23
+ end
24
+
25
+ alerts.join("\n")
26
+ end
27
+
28
+ def output_alert(name, attrs)
29
+ description = attrs.fetch('description')
30
+ attributes = attrs.fetch('attributes')
31
+ active = attrs.fetch('active')
32
+ rearm_seconds = attrs.fetch('rearm_seconds')
33
+ rearm_per_signal = attrs.fetch('rearm_per_signal')
34
+
35
+ conditions = attrs.fetch('conditions')
36
+ services = attrs.fetch('services')
37
+
38
+ if attributes.empty?
39
+ attributes = "\n" + <<-EOS.chomp
40
+ #attributes "runbook_url"=>"http://example.com"
41
+ EOS
42
+ else
43
+ attributes = "\n" + <<-EOS.chomp
44
+ attributes #{Lbrt::Utils.unbrace(attributes.inspect)}
45
+ EOS
46
+ end
47
+
48
+ <<-EOS
49
+ alert #{name.inspect} do
50
+ description #{description.inspect}#{
51
+ attributes}
52
+ active #{active.inspect}
53
+ rearm_seconds #{rearm_seconds.inspect}
54
+ rearm_per_signal #{rearm_per_signal.inspect}
55
+
56
+ #{output_conditions(conditions)}
57
+
58
+ #{output_services(services)}
59
+ end
60
+ EOS
61
+ end
62
+
63
+ def output_conditions(conditions)
64
+ if conditions.empty?
65
+ '# no condition'
66
+ else
67
+ conditions.map {|i| output_condition(i) }.join("\n").strip
68
+ end
69
+ end
70
+
71
+ def output_condition(condition)
72
+ type = condition.fetch('type')
73
+ metric_name = condition.fetch('metric_name')
74
+ source = condition.fetch('source')
75
+
76
+ out = <<-EOS
77
+ condition do
78
+ type #{type.inspect}
79
+ metric_name #{metric_name.inspect}
80
+ source #{source.inspect}
81
+ EOS
82
+
83
+ %w(threshold summary_function duration).each do |key|
84
+ next unless condition.has_key?(key)
85
+ value = condition[key]
86
+
87
+ out << <<-EOS
88
+ #{key} #{value.inspect}
89
+ EOS
90
+ end
91
+
92
+ out << <<-EOS
93
+ end
94
+ EOS
95
+ end
96
+
97
+ def output_services(services)
98
+ if services.empty?
99
+ '# no service'
100
+ else
101
+ services.map {|i| output_service(i) }.join.strip
102
+ end
103
+ end
104
+
105
+ def output_service(service)
106
+ type = service.fetch('type')
107
+ title = service.fetch('title')
108
+
109
+ <<-EOS
110
+ service #{type.inspect}, #{title.inspect}
111
+ EOS
112
+ end
113
+ end
@@ -0,0 +1,38 @@
1
+ class Lbrt::Alert::Exporter
2
+ class << self
3
+ def export(client, options = {})
4
+ self.new(client, options).export
5
+ end
6
+ end # of class methods
7
+
8
+ def initialize(client, options = {})
9
+ @client = client
10
+ @options = options
11
+ end
12
+
13
+ def export
14
+ alerts = @client.alerts.get
15
+ normalize(alerts)
16
+ end
17
+
18
+ def normalize(alerts)
19
+ alert_by_name = {}
20
+
21
+ alerts.each do |alrt|
22
+ name = alrt.delete('name')
23
+
24
+ if alert_by_name[name]
25
+ raise "Duplicate alert name exists: #{name}"
26
+ end
27
+
28
+ %w(created_at updated_at version).each do |key|
29
+ alrt.delete(key)
30
+ end
31
+
32
+ alrt['attributes'] ||= {}
33
+ alert_by_name[name] = alrt
34
+ end
35
+
36
+ alert_by_name
37
+ end
38
+ end
@@ -0,0 +1,2 @@
1
+ module Lbrt::CLI
2
+ end
@@ -0,0 +1,26 @@
1
+ class Lbrt::CLI::Alert < Thor
2
+ include Lbrt::Utils::CLIHelper
3
+
4
+ class_option :target
5
+
6
+ desc 'apply FILE', 'Apply alerts'
7
+ option :'dry-run', :type => :boolean, :default => false
8
+ def apply(file)
9
+ updated = client(Lbrt::Alert).apply(file)
10
+
11
+ unless updated
12
+ Lbrt::Logger.instance.info('No change'.intense_blue)
13
+ end
14
+ end
15
+
16
+ desc 'export [FILE]', 'Export alerts'
17
+ def export(file = nil)
18
+ dsl = client(Lbrt::Alert).export
19
+
20
+ if file.nil? or file == '-'
21
+ puts dsl
22
+ else
23
+ open(file, 'wb') {|f| f.puts dsl }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,15 @@
1
+ class Lbrt::CLI::App < Thor
2
+ class_option :user, :default => ENV['LIBRATO_USER']
3
+ class_option :token, :default => ENV['LIBRATO_TOKEN']
4
+ class_option :color, :type => :boolean, :default => true
5
+ class_option :debug, :type => :boolean, :default => false
6
+
7
+ desc 'alert SUBCOMMAND', 'Manage alerts'
8
+ subcommand :alert, Lbrt::CLI::Alert
9
+
10
+ desc 'service SUBCOMMAND', 'Manage services'
11
+ subcommand :service, Lbrt::CLI::Service
12
+
13
+ desc 'space SUBCOMMAND', 'Manage spaces'
14
+ subcommand :space, Lbrt::CLI::Space
15
+ end
@@ -0,0 +1,26 @@
1
+ class Lbrt::CLI::Service < Thor
2
+ include Lbrt::Utils::CLIHelper
3
+
4
+ class_option :target
5
+
6
+ desc 'apply FILE', 'Apply services'
7
+ option :'dry-run', :type => :boolean, :default => false
8
+ def apply(file)
9
+ updated = client(Lbrt::Service).apply(file)
10
+
11
+ unless updated
12
+ Lbrt::Logger.instance.info('No change'.intense_blue)
13
+ end
14
+ end
15
+
16
+ desc 'export [FILE]', 'Export services'
17
+ def export(file = nil)
18
+ dsl = client(Lbrt::Service).export
19
+
20
+ if file.nil? or file == '-'
21
+ puts dsl
22
+ else
23
+ open(file, 'wb') {|f| f.puts dsl }
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ class Lbrt::CLI::Space < Thor
2
+ include Lbrt::Utils::CLIHelper
3
+
4
+ class_option :target
5
+ class_option :'export-concurrency', :type => :numeric, :default => 32
6
+
7
+ desc 'apply FILE', 'Apply spaces'
8
+ option :'dry-run', :type => :boolean, :default => false
9
+ def apply(file)
10
+ updated = client(Lbrt::Space).apply(file)
11
+
12
+ unless updated
13
+ Lbrt::Logger.instance.info('No change'.intense_blue)
14
+ end
15
+ end
16
+
17
+ desc 'export [FILE]', 'Export spaces'
18
+ def export(file = nil)
19
+ dsl = client(Lbrt::Space).export
20
+
21
+ if file.nil? or file == '-'
22
+ puts dsl
23
+ else
24
+ open(file, 'wb') {|f| f.puts dsl }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,240 @@
1
+ class Lbrt::Driver
2
+ include Lbrt::Logger::Helper
3
+
4
+ def initialize(client, options = {})
5
+ @client = client
6
+ @options = options
7
+ end
8
+
9
+ # Space
10
+
11
+ def create_space(name, expected)
12
+ updated = false
13
+
14
+ log(:info, "Create Space `#{name}`", :color => :cyan)
15
+
16
+ unless name.is_a?(String)
17
+ raise TypeError, "wrong argument type #{name.class}: #{name.inspect} (expected String)"
18
+ end
19
+
20
+ unless @options[:dry_run]
21
+ response = @client.spaces.post('name' => name)
22
+ expected['id'] = response.fetch('id')
23
+ updated = true
24
+ end
25
+
26
+ updated
27
+ end
28
+
29
+ def delete_space(name_or_id, actual)
30
+ updated = false
31
+
32
+ log(:info, "Delete Space `#{name_or_id}`", :color => :red)
33
+
34
+ space_id = actual.fetch('id')
35
+
36
+ unless @options[:dry_run]
37
+ @client.spaces(space_id).delete
38
+ updated = true
39
+ end
40
+
41
+ updated
42
+ end
43
+
44
+ # Chart
45
+
46
+ def create_chart(space_name_or_id, space_id, name, expected)
47
+ updated = false
48
+
49
+ log(:info, "Create Space `#{space_name_or_id}` > Chart `#{name}`", :color => :cyan)
50
+
51
+ unless name.is_a?(String)
52
+ raise TypeError, "wrong argument type #{name.class}: #{name.inspect} (expected String)"
53
+ end
54
+
55
+ unless @options[:dry_run]
56
+ response = @client.spaces(space_id).charts.post(expected.merge('name' => name))
57
+ expected['id'] = response.fetch('id')
58
+ updated = true
59
+ end
60
+
61
+ updated
62
+ end
63
+
64
+ def delete_chart(space_name_or_id, space_id, name_or_id, actual)
65
+ updated = false
66
+
67
+ log(:info, "Delete Space `#{space_name_or_id}` > `#{name_or_id}`", :color => :red)
68
+
69
+ chart_id = actual.fetch('id')
70
+
71
+ unless @options[:dry_run]
72
+ @client.spaces(space_id).charts(chart_id).delete
73
+ updated = true
74
+ end
75
+
76
+ updated
77
+ end
78
+
79
+ def update_chart(space_name_or_id, space_id, name_or_id, expected, actual)
80
+ updated = false
81
+
82
+ log(:info, "Update Space `#{space_name_or_id}` > Chart: #{name_or_id}", :color => :green)
83
+
84
+ delta = diff(Lbrt::Space::DSL::Converter,
85
+ {space_name_or_id => {'charts' => {name_or_id => actual}}},
86
+ {space_name_or_id => {'charts' => {name_or_id => expected}}}
87
+ )
88
+
89
+ log(:info, delta)
90
+
91
+ chart_id = actual.fetch('id')
92
+
93
+ unless @options[:dry_run]
94
+ params = chart_to_update_params(expected)
95
+ @client.spaces(space_id).charts(chart_id).put(params)
96
+ updated = true
97
+ end
98
+
99
+ updated
100
+ end
101
+
102
+ # Alert
103
+
104
+ def create_alert(name, expected)
105
+ updated = false
106
+
107
+ log(:info, "Create Alert `#{name}`", :color => :cyan)
108
+
109
+ unless @options[:dry_run]
110
+ params = alert_to_params(name, expected)
111
+ response = @client.alerts.post(params)
112
+ expected['id'] = response.fetch('id')
113
+ updated = true
114
+ end
115
+
116
+ updated
117
+ end
118
+
119
+ def delete_alert(name, actual)
120
+ updated = false
121
+
122
+ log(:info, "Delete Alert `#{name}`", :color => :red)
123
+
124
+ alert_id = actual.fetch('id')
125
+
126
+ unless @options[:dry_run]
127
+ @client.alerts(alert_id).delete
128
+ updated = true
129
+ end
130
+
131
+ updated
132
+ end
133
+
134
+ def update_alert(name, expected, actual)
135
+ updated = false
136
+
137
+ log(:info, "Update Alert `#{name}`", :color => :green)
138
+ log(:info, diff(Lbrt::Alert::DSL::Converter, {name => actual}, {name => expected}))
139
+
140
+ alert_id = actual.fetch('id')
141
+
142
+ unless @options[:dry_run]
143
+ params = alert_to_params(name, expected)
144
+ @client.alerts(alert_id).put(params)
145
+ updated = true
146
+ end
147
+
148
+ updated
149
+ end
150
+
151
+ # Service
152
+
153
+ def create_service(key, expected)
154
+ updated = false
155
+ type, title = key
156
+
157
+ log(:info, "Create Service `#{type}/#{title}`", :color => :cyan)
158
+
159
+ settings = expected.fetch('settings')
160
+
161
+ unless @options[:dry_run]
162
+ response = @client.services.post(
163
+ 'title' => title,
164
+ 'type' => type,
165
+ 'settings' => settings
166
+ )
167
+
168
+ expected['id'] = response.fetch('id')
169
+ updated = true
170
+ end
171
+
172
+ updated
173
+ end
174
+
175
+ def delete_service(key, actual)
176
+ updated = false
177
+ type, title = key
178
+
179
+ log(:info, "Delete Service `#{type}/#{title}`", :color => :red)
180
+
181
+ service_id = actual.fetch('id')
182
+
183
+ unless @options[:dry_run]
184
+ @client.services(service_id).delete
185
+ updated = true
186
+ end
187
+
188
+ updated
189
+ end
190
+
191
+ def update_service(key, expected, actual)
192
+ updated = false
193
+ type, title = key
194
+
195
+ log(:info, "Update Service `#{type}/#{title}`", :color => :green)
196
+ log(:info, diff(Lbrt::Service::DSL::Converter, {key => actual}, {key => expected}))
197
+
198
+ service_id = actual.fetch('id')
199
+ settings = expected.fetch('settings')
200
+
201
+ unless @options[:dry_run]
202
+ @client.services(service_id).put(
203
+ 'settings' => settings
204
+ )
205
+
206
+ updated = true
207
+ end
208
+
209
+ updated
210
+ end
211
+
212
+ private
213
+
214
+ def diff(converter, obj1, obj2)
215
+ diffy = Diffy::Diff.new(
216
+ converter.convert(obj1),
217
+ converter.convert(obj2),
218
+ :diff => '-u'
219
+ )
220
+
221
+ diffy.to_s(@options[:color] ? :color : :text).gsub(/\s+\z/m, '')
222
+ end
223
+
224
+ def alert_to_params(name, alert)
225
+ params = alert.dup
226
+ params['name'] = name
227
+ params['services'] = params.fetch('services').map {|i| i.fetch('id') }
228
+ params
229
+ end
230
+
231
+ def chart_to_update_params(chat)
232
+ params = chat.dup
233
+ # XXX: Correct?
234
+ params['chart_type'] = params.delete('type')
235
+ params['max'] ||= nil
236
+ params['min'] ||= nil
237
+ params['label'] ||= nil
238
+ params
239
+ end
240
+ end