lbrt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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