lbrt 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +180 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/lbrt +15 -0
- data/lbrt.gemspec +31 -0
- data/lib/lbrt.rb +46 -0
- data/lib/lbrt/alert.rb +91 -0
- data/lib/lbrt/alert/dsl.rb +9 -0
- data/lib/lbrt/alert/dsl/context.rb +32 -0
- data/lib/lbrt/alert/dsl/context/alert.rb +75 -0
- data/lib/lbrt/alert/dsl/context/alert/condition.rb +61 -0
- data/lib/lbrt/alert/dsl/converter.rb +113 -0
- data/lib/lbrt/alert/exporter.rb +38 -0
- data/lib/lbrt/cli.rb +2 -0
- data/lib/lbrt/cli/alert.rb +26 -0
- data/lib/lbrt/cli/app.rb +15 -0
- data/lib/lbrt/cli/service.rb +26 -0
- data/lib/lbrt/cli/space.rb +27 -0
- data/lib/lbrt/driver.rb +240 -0
- data/lib/lbrt/ext/string_ext.rb +25 -0
- data/lib/lbrt/logger.rb +32 -0
- data/lib/lbrt/service.rb +73 -0
- data/lib/lbrt/service/dsl.rb +9 -0
- data/lib/lbrt/service/dsl/context.rb +33 -0
- data/lib/lbrt/service/dsl/context/service.rb +32 -0
- data/lib/lbrt/service/dsl/converter.rb +38 -0
- data/lib/lbrt/service/exporter.rb +37 -0
- data/lib/lbrt/space.rb +126 -0
- data/lib/lbrt/space/dsl.rb +9 -0
- data/lib/lbrt/space/dsl/context.rb +29 -0
- data/lib/lbrt/space/dsl/context/space.rb +19 -0
- data/lib/lbrt/space/dsl/context/space/chart.rb +44 -0
- data/lib/lbrt/space/dsl/context/space/chart/stream.rb +48 -0
- data/lib/lbrt/space/dsl/converter.rb +107 -0
- data/lib/lbrt/space/exporter.rb +56 -0
- data/lib/lbrt/utils.rb +64 -0
- data/lib/lbrt/version.rb +3 -0
- 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
|
data/lib/lbrt/logger.rb
ADDED
@@ -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
|
data/lib/lbrt/service.rb
ADDED
@@ -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,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
|
data/lib/lbrt/space.rb
ADDED
@@ -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,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
|