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,25 @@
1
+ class String
2
+ @@colorize = false
3
+
4
+ class << self
5
+ def colorize=(value)
6
+ @@colorize = value
7
+ end
8
+
9
+ def colorize
10
+ @@colorize
11
+ end
12
+ end # of class methods
13
+
14
+ Term::ANSIColor::Attribute.named_attributes.map do |attribute|
15
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
16
+ def #{attribute.name}
17
+ if @@colorize
18
+ Term::ANSIColor.send(#{attribute.name.inspect}, self)
19
+ else
20
+ self
21
+ end
22
+ end
23
+ EOS
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ class Lbrt::Logger < ::Logger
2
+ include Singleton
3
+
4
+ def initialize
5
+ super($stdout)
6
+
7
+ self.formatter = proc do |severity, datetime, progname, msg|
8
+ "#{msg}\n"
9
+ end
10
+
11
+ self.level = INFO
12
+ end
13
+
14
+ def set_debug(value)
15
+ self.level = value ? DEBUG : INFO
16
+ end
17
+
18
+ module Helper
19
+ def log(level, message, opts = {})
20
+ global_options = (@options || {}).dup
21
+ global_options.delete(:color)
22
+ opts = global_options.merge(opts)
23
+
24
+ message = "[#{level.to_s.upcase}] #{message}" unless level == :info
25
+ message << ' (dry-run)' if opts[:dry_run]
26
+ message = message.send(opts[:color]) if opts[:color]
27
+
28
+ logger = opts[:logger] || Lbrt::Logger.instance
29
+ logger.send(level, message)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,73 @@
1
+ class Lbrt::Service
2
+ include Lbrt::Logger::Helper
3
+
4
+ def initialize(client, options = {})
5
+ @client = client
6
+ @options = options
7
+ @driver = Lbrt::Driver.new(@client, @options)
8
+ end
9
+
10
+ def export(export_options = {})
11
+ exported = Lbrt::Service::Exporter.export(@client, @options)
12
+ Lbrt::Service::DSL.convert(exported, @options)
13
+ end
14
+
15
+ def apply(file)
16
+ walk(file)
17
+ end
18
+
19
+ private
20
+
21
+ def walk(file)
22
+ expected = load_file(file)
23
+ actual = Lbrt::Service::Exporter.export(@client, @options)
24
+ walk_services(expected, actual)
25
+ end
26
+
27
+ def walk_services(expected, actual)
28
+ updated = false
29
+
30
+ expected.each do |key, expected_service|
31
+ next unless key.any? {|i| Lbrt::Utils.matched?(i, @options[:target]) }
32
+ actual_service = actual.delete(key)
33
+
34
+ if actual_service
35
+ updated = walk_service(key, expected_service, actual_service) || updated
36
+ else
37
+ updated = @driver.create_service(key, expected_service) || updated
38
+ end
39
+ end
40
+
41
+ actual.each do |key, actual_service|
42
+ next unless key.any? {|i| Lbrt::Utils.matched?(i, @options[:target]) }
43
+ updated = @driver.delete_service(key, actual_service) || updated
44
+ end
45
+
46
+ updated
47
+ end
48
+
49
+ def walk_service(key, expected, actual)
50
+ updated = false
51
+
52
+ actual_without_id = actual.dup
53
+ service_id = actual_without_id.delete('id')
54
+
55
+ if expected != actual_without_id
56
+ updated = @driver.update_service(key, expected.merge('id' => service_id), actual) || updated
57
+ end
58
+
59
+ updated
60
+ end
61
+
62
+ def load_file(file)
63
+ if file.kind_of?(String)
64
+ open(file) do |f|
65
+ Lbrt::Service::DSL.parse(f.read, file)
66
+ end
67
+ elsif [File, Tempfile].any? {|i| file.kind_of?(i) }
68
+ Lbrt::Service::DSL.parse(file.read, file.path)
69
+ else
70
+ raise TypeError, "can't convert #{file} into File"
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,9 @@
1
+ class Lbrt::Service::DSL
2
+ def self.convert(exported, options = {})
3
+ Lbrt::Service::DSL::Converter.convert(exported, options)
4
+ end
5
+
6
+ def self.parse(dsl, path, options = {})
7
+ Lbrt::Service::DSL::Context.eval(dsl, path, options).result
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ class Lbrt::Service::DSL::Context
2
+ include Lbrt::Utils::ContextHelper
3
+
4
+ def self.eval(dsl, path, options = {})
5
+ self.new(path, options) {
6
+ eval(dsl, binding, path)
7
+ }
8
+ end
9
+
10
+ attr_reader :result
11
+
12
+ def initialize(path, options = {}, &block)
13
+ @path = path
14
+ @options = options
15
+ @result = {}
16
+ instance_eval(&block)
17
+ end
18
+
19
+ private
20
+
21
+ def service(type, title, &block)
22
+ type = type.to_s
23
+ title = title.to_s
24
+ key = [type, title]
25
+
26
+ if @result[key]
27
+ raise "Service `#{type}/#{title}` is already defined"
28
+ end
29
+
30
+ srvc = Lbrt::Service::DSL::Context::Service.new(type, title, &block).result
31
+ @result[key] = srvc
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ class Lbrt::Service::DSL::Context::Service
2
+ REQUIRED_ATTRIBUTES = %w(
3
+ settings
4
+ )
5
+
6
+ def initialize(type, title, &block)
7
+ @type = type
8
+ @title = title
9
+ @result = {}
10
+ instance_eval(&block)
11
+ end
12
+
13
+ def result
14
+ REQUIRED_ATTRIBUTES.each do |name|
15
+ unless @result.has_key?(name)
16
+ raise "Service `#{@type}/#{@title}`: #{name} is not defined"
17
+ end
18
+ end
19
+
20
+ @result
21
+ end
22
+
23
+ private
24
+
25
+ def settings(value)
26
+ unless value.is_a?(Hash)
27
+ raise TypeError, "wrong argument type #{value.class}: #{value.inspect} (expected Hash)"
28
+ end
29
+
30
+ @result['settings'] = value
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ class Lbrt::Service::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_services(@exported)
13
+ end
14
+
15
+ private
16
+
17
+ def output_services(service_by_key)
18
+ services = []
19
+
20
+ service_by_key.sort_by(&:first).map do |key, attrs|
21
+ next unless key.any? {|i| Lbrt::Utils.matched?(i, @options[:target]) }
22
+ services << output_service(key, attrs)
23
+ end
24
+
25
+ services.join("\n")
26
+ end
27
+
28
+ def output_service(key, attrs)
29
+ type, title = key
30
+ settings = attrs.fetch('settings')
31
+
32
+ <<-EOS
33
+ service #{type.inspect}, #{title.inspect} do
34
+ settings #{Lbrt::Utils.unbrace(settings.inspect)}
35
+ end
36
+ EOS
37
+ end
38
+ end
@@ -0,0 +1,37 @@
1
+ class Lbrt::Service::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
+ services = @client.services.get
15
+ normalize(services)
16
+ end
17
+
18
+ private
19
+
20
+ def normalize(services)
21
+ service_by_key = {}
22
+
23
+ services.each do |srvc|
24
+ type = srvc.delete('type')
25
+ title = srvc.delete('title')
26
+ service_key = [type, title]
27
+
28
+ if service_by_key[service_key]
29
+ raise "Duplicate service type/title exists: #{type}/#{title}"
30
+ end
31
+
32
+ service_by_key[service_key] = srvc
33
+ end
34
+
35
+ service_by_key
36
+ end
37
+ end
@@ -0,0 +1,126 @@
1
+ class Lbrt::Space
2
+ include Lbrt::Logger::Helper
3
+
4
+ def initialize(client, options = {})
5
+ @client = client
6
+ @options = options
7
+ @driver = Lbrt::Driver.new(@client, @options)
8
+ end
9
+
10
+ def export(export_options = {})
11
+ exported = Lbrt::Space::Exporter.export(@client, @options)
12
+ Lbrt::Space::DSL.convert(exported, @options)
13
+ end
14
+
15
+ def apply(file)
16
+ walk(file)
17
+ end
18
+
19
+ private
20
+
21
+ def walk(file)
22
+ expected = load_file(file)
23
+ actual = Lbrt::Space::Exporter.export(@client, @options)
24
+ walk_spaces(expected, actual)
25
+ end
26
+
27
+ def walk_spaces(expected, actual)
28
+ updated = false
29
+
30
+ expected.each do |name_or_id, expected_space|
31
+ actual_space = actual.delete(name_or_id)
32
+
33
+ if not actual_space and name_or_id.is_a?(Integer)
34
+ actual_space = actual.values.find {|i| i['id'] == name_or_id }
35
+ end
36
+
37
+ unless actual_space
38
+ updated = @driver.create_space(name_or_id, expected_space) || updated
39
+ actual_space = expected_space.merge('charts' => {})
40
+
41
+ # Set dummy id for dry-run
42
+ actual_space['id'] ||= -1
43
+ end
44
+
45
+ updated = walk_space(name_or_id, expected_space, actual_space) || updated
46
+ end
47
+
48
+ actual.each do |name_or_id, actual_space|
49
+ updated = @driver.delete_space(name_or_id, actual_space) || updated
50
+ end
51
+
52
+ updated
53
+ end
54
+
55
+ def walk_space(name_or_id, expected, actual)
56
+ updated = false
57
+ space_id = actual.fetch('id')
58
+ expected_charts = expected.fetch('charts')
59
+ actual_charts = actual.fetch('charts')
60
+ updated = walk_charts(name_or_id, space_id, expected_charts, actual_charts) || updated
61
+ updated
62
+ end
63
+
64
+ def walk_charts(space_name_or_id, space_id, expected, actual)
65
+ updated = false
66
+
67
+ expected.each do |name_or_id, expected_chart|
68
+ actual_chart = actual.delete(name_or_id)
69
+
70
+ if not actual_chart and name_or_id.is_a?(Integer)
71
+ actual_chart = actual.values.find {|i| i['id'] == name_or_id }
72
+ end
73
+
74
+ if actual_chart
75
+ updated = walk_chart(space_name_or_id, space_id, name_or_id, expected_chart, actual_chart) || updated
76
+ else
77
+ updated = @driver.create_chart(space_name_or_id, space_id, name_or_id, expected_chart) || updated
78
+ end
79
+ end
80
+
81
+ actual.each do |name_or_id, actual_chart|
82
+ updated = @driver.delete_chart(space_name_or_id, space_id, name_or_id, actual_chart) || updated
83
+ end
84
+
85
+ updated
86
+ end
87
+
88
+ def walk_chart(space_name_or_id, space_id, name_or_id, expected, actual)
89
+ updated = false
90
+
91
+ actual_without_id = actual.dup
92
+ alert_id = actual_without_id.delete('id')
93
+
94
+ if differ_chart?(expected, actual_without_id)
95
+ updated = @driver.update_chart(space_name_or_id, space_id, name_or_id, expected.merge('id' => alert_id), actual) || updated
96
+ end
97
+
98
+ updated
99
+ end
100
+
101
+ def differ_chart?(chart1, chart2)
102
+ chart1 = normalize_chart(chart1)
103
+ chart2 = normalize_chart(chart2)
104
+ chart1 != chart2
105
+ end
106
+
107
+ def normalize_chart(chart)
108
+ chart = chart.dup
109
+ chart_streams = chart['streams'].dup
110
+ chart_streams.each {|i| i.delete('id') }
111
+ chart['streams'] = chart_streams
112
+ chart
113
+ end
114
+
115
+ def load_file(file)
116
+ if file.kind_of?(String)
117
+ open(file) do |f|
118
+ Lbrt::Space::DSL.parse(f.read, file)
119
+ end
120
+ elsif [File, Tempfile].any? {|i| file.kind_of?(i) }
121
+ Lbrt::Space::DSL.parse(file.read, file.path)
122
+ else
123
+ raise TypeError, "can't convert #{file} into File"
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,9 @@
1
+ class Lbrt::Space::DSL
2
+ def self.convert(exported, options = {})
3
+ Lbrt::Space::DSL::Converter.convert(exported, options)
4
+ end
5
+
6
+ def self.parse(dsl, path, options = {})
7
+ Lbrt::Space::DSL::Context.eval(dsl, path, options).result
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ class Lbrt::Space::DSL::Context
2
+ include Lbrt::Utils::ContextHelper
3
+
4
+ def self.eval(dsl, path, options = {})
5
+ self.new(path, options) {
6
+ eval(dsl, binding, path)
7
+ }
8
+ end
9
+
10
+ attr_reader :result
11
+
12
+ def initialize(path, options = {}, &block)
13
+ @path = path
14
+ @options = options
15
+ @result = {}
16
+ instance_eval(&block)
17
+ end
18
+
19
+ private
20
+
21
+ def space(name_or_id, &block)
22
+ if @result[name_or_id]
23
+ raise "Space `#{name_or_id}` is already defined"
24
+ end
25
+
26
+ spc = Lbrt::Space::DSL::Context::Space.new(name_or_id, &block).result
27
+ @result[name_or_id] = spc
28
+ end
29
+ end