balmora 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/balmora +6 -0
- data/lib/balmora.rb +77 -0
- data/lib/balmora/arguments.rb +175 -0
- data/lib/balmora/cli.rb +150 -0
- data/lib/balmora/command.rb +106 -0
- data/lib/balmora/command/commands.rb +21 -0
- data/lib/balmora/command/exec.rb +24 -0
- data/lib/balmora/command/file.rb +168 -0
- data/lib/balmora/command/files.rb +122 -0
- data/lib/balmora/command/pacman.rb +71 -0
- data/lib/balmora/command/reload_config.rb +13 -0
- data/lib/balmora/command/restart.rb +13 -0
- data/lib/balmora/command/set_variable.rb +41 -0
- data/lib/balmora/command/stop.rb +17 -0
- data/lib/balmora/command/yaourt.rb +8 -0
- data/lib/balmora/config.rb +190 -0
- data/lib/balmora/context.rb +98 -0
- data/lib/balmora/context/config_changed.rb +11 -0
- data/lib/balmora/context/exec.rb +26 -0
- data/lib/balmora/context/exec_result.rb +7 -0
- data/lib/balmora/contexts.rb +76 -0
- data/lib/balmora/extension.rb +58 -0
- data/lib/balmora/extension/file_secret.rb +45 -0
- data/lib/balmora/logger.rb +25 -0
- data/lib/balmora/require.rb +25 -0
- data/lib/balmora/shell.rb +118 -0
- data/lib/balmora/state.rb +82 -0
- data/lib/balmora/variables.rb +110 -0
- data/lib/balmora/variables/config.rb +7 -0
- data/lib/balmora/variables/variables.rb +7 -0
- metadata +74 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
class Balmora::Command::Stop < Balmora::Command
|
2
|
+
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
def options()
|
6
|
+
return super().concat([:status])
|
7
|
+
end
|
8
|
+
|
9
|
+
def verify()
|
10
|
+
return true
|
11
|
+
end
|
12
|
+
|
13
|
+
def run()
|
14
|
+
raise Balmora::Stop.new(nil, @status)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Balmora::Config
|
4
|
+
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
attr_accessor :file, :dir, :require, :variables
|
8
|
+
attr_reader :old, :config
|
9
|
+
|
10
|
+
def self.factory(state)
|
11
|
+
return self.create(state.options[:config], state.variables)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.create(config = nil, variables = nil)
|
15
|
+
if config.nil?()
|
16
|
+
config = File.join(Dir.home(), '.config/balmora/balmora.conf')
|
17
|
+
end
|
18
|
+
|
19
|
+
if !File.exist?(config)
|
20
|
+
config = File.join('/etc/balmora/balmora.conf')
|
21
|
+
end
|
22
|
+
|
23
|
+
if !File.exist?(config)
|
24
|
+
raise Error.new('Config not found in ~/.config/balmora/balmora.conf ' +
|
25
|
+
'and /etc/balmora/balmora.conf; you should create config to ' +
|
26
|
+
'continue or use --config switch')
|
27
|
+
end
|
28
|
+
|
29
|
+
return self.new(config || '~/.config/balmora/balmora.conf', variables)
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(path, variables = nil)
|
33
|
+
@file = File
|
34
|
+
@dir = Dir
|
35
|
+
|
36
|
+
@variables = variables
|
37
|
+
@path = path
|
38
|
+
@require = Object.method(:require)
|
39
|
+
|
40
|
+
@config = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
def get(key = [], options = {})
|
44
|
+
if !key.instance_of?(::Array)
|
45
|
+
key = [key]
|
46
|
+
end
|
47
|
+
|
48
|
+
result =
|
49
|
+
key.
|
50
|
+
inject(@config) { |current, part|
|
51
|
+
begin
|
52
|
+
current = current.fetch(part)
|
53
|
+
rescue KeyError
|
54
|
+
if options[:default]
|
55
|
+
return options[:default]
|
56
|
+
end
|
57
|
+
|
58
|
+
raise "Config value #{key.inspect()} should be defined in " +
|
59
|
+
"#{@path.inspect()}"
|
60
|
+
end
|
61
|
+
|
62
|
+
next current
|
63
|
+
}
|
64
|
+
|
65
|
+
result = _deep_clone(result)
|
66
|
+
if options[:variables] != false
|
67
|
+
result = @variables.inject(result, false)
|
68
|
+
end
|
69
|
+
|
70
|
+
return result
|
71
|
+
end
|
72
|
+
|
73
|
+
def load()
|
74
|
+
@old = _deep_clone(@config)
|
75
|
+
|
76
|
+
Dir.chdir(File.dirname(@path)) {
|
77
|
+
@config = _load(@path)
|
78
|
+
}
|
79
|
+
|
80
|
+
@config[:config_dir] = File.dirname(@path)
|
81
|
+
@config[:config_path] = @path
|
82
|
+
|
83
|
+
get(:require, default: [], variables: false).each() { |file|
|
84
|
+
@require.call(file)
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def _load(path)
|
89
|
+
contents = @file.read(@file.expand_path(path))
|
90
|
+
value = JSON.parse(contents, {symbolize_names: true})
|
91
|
+
value = _process(value)
|
92
|
+
return value
|
93
|
+
rescue => error
|
94
|
+
raise Error.new("Failed to read config file #{path}: #{error.to_s()}")
|
95
|
+
end
|
96
|
+
|
97
|
+
def _process(value)
|
98
|
+
result =
|
99
|
+
if value.instance_of?(::Array)
|
100
|
+
_process_array(value)
|
101
|
+
elsif value.instance_of?(::Hash)
|
102
|
+
_process_hash(value)
|
103
|
+
else
|
104
|
+
value
|
105
|
+
end
|
106
|
+
|
107
|
+
return result
|
108
|
+
end
|
109
|
+
|
110
|
+
def _process_array(value)
|
111
|
+
result = []
|
112
|
+
value.each() { |item|
|
113
|
+
if item.instance_of?(::Hash) && item.keys() == [:'extend-file']
|
114
|
+
files = item[:'extend-file']
|
115
|
+
if !files.instance_of?(::Array)
|
116
|
+
files = [files]
|
117
|
+
end
|
118
|
+
|
119
|
+
files.each() { |path|
|
120
|
+
_glob(path).each() { |file|
|
121
|
+
result.concat(_load(file))
|
122
|
+
}
|
123
|
+
}
|
124
|
+
else
|
125
|
+
result.push(_process(item))
|
126
|
+
end
|
127
|
+
}
|
128
|
+
|
129
|
+
return result
|
130
|
+
end
|
131
|
+
|
132
|
+
def _process_hash(value)
|
133
|
+
result = {}
|
134
|
+
value.each() { |key, item|
|
135
|
+
if key == :'include-file'
|
136
|
+
files = item
|
137
|
+
if !files.instance_of?(::Array)
|
138
|
+
files = [files]
|
139
|
+
end
|
140
|
+
|
141
|
+
files.each() { |path|
|
142
|
+
_glob(path).each() { |file|
|
143
|
+
result.merge!(_load(file))
|
144
|
+
}
|
145
|
+
}
|
146
|
+
else
|
147
|
+
result[key] = _process(item)
|
148
|
+
end
|
149
|
+
}
|
150
|
+
|
151
|
+
return result
|
152
|
+
end
|
153
|
+
|
154
|
+
def _glob(file)
|
155
|
+
if !file.include?('*')
|
156
|
+
return [file]
|
157
|
+
end
|
158
|
+
|
159
|
+
return @dir.glob(file)
|
160
|
+
end
|
161
|
+
|
162
|
+
def _deep_clone(value)
|
163
|
+
if value.instance_of?(::Array)
|
164
|
+
value = value.collect() { |item|
|
165
|
+
_deep_clone(item)
|
166
|
+
}
|
167
|
+
elsif value.instance_of?(::Hash)
|
168
|
+
value = Hash[
|
169
|
+
value.collect() { |key, item|
|
170
|
+
[_deep_clone(key), _deep_clone(item)]
|
171
|
+
}
|
172
|
+
]
|
173
|
+
else
|
174
|
+
is_clonable = (
|
175
|
+
!value.is_a?(::Numeric) &&
|
176
|
+
!value.is_a?(::TrueClass) &&
|
177
|
+
!value.is_a?(::FalseClass) &&
|
178
|
+
!value.is_a?(::NilClass) &&
|
179
|
+
!value.is_a?(::Symbol)
|
180
|
+
)
|
181
|
+
|
182
|
+
if is_clonable
|
183
|
+
value = value.clone()
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
return value
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
class Balmora::Context < Balmora::Command
|
2
|
+
|
3
|
+
def initialize(state, context)
|
4
|
+
@state = state
|
5
|
+
|
6
|
+
@logger = state.logger
|
7
|
+
@shell = state.shell
|
8
|
+
@config = state.config
|
9
|
+
@variables = state.variables
|
10
|
+
@balmora = state.balmora
|
11
|
+
|
12
|
+
@context = context
|
13
|
+
end
|
14
|
+
|
15
|
+
def init()
|
16
|
+
if (@context.keys() - [:context] - options()).length != 0
|
17
|
+
raise Error.new("Unknown options #{(@context.keys() - [:context] -
|
18
|
+
options()).inspect()}")
|
19
|
+
end
|
20
|
+
|
21
|
+
options().each() { |key|
|
22
|
+
if self.instance_variable_defined?(:"@#{key}")
|
23
|
+
raise Error.new("Can not use #{key} as option")
|
24
|
+
end
|
25
|
+
|
26
|
+
option = @context.fetch(key, nil)
|
27
|
+
self.instance_variable_set(:"@#{key}", option)
|
28
|
+
}
|
29
|
+
|
30
|
+
verify()
|
31
|
+
end
|
32
|
+
|
33
|
+
def options()
|
34
|
+
return [:operator, :value]
|
35
|
+
end
|
36
|
+
|
37
|
+
def verify()
|
38
|
+
if @operator.nil?()
|
39
|
+
raise Error.new('"operator" should be defined')
|
40
|
+
end
|
41
|
+
|
42
|
+
if @value.nil?()
|
43
|
+
raise Error.new('"value" should be defined')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def execute()
|
48
|
+
result = run()
|
49
|
+
|
50
|
+
operator = @operator
|
51
|
+
|
52
|
+
is_not = false
|
53
|
+
if operator.start_with?('not-')
|
54
|
+
is_not = true
|
55
|
+
operator = operator[4..-1]
|
56
|
+
end
|
57
|
+
|
58
|
+
case operator
|
59
|
+
when 'match'
|
60
|
+
return _not(is_not, result.match(@value) != nil)
|
61
|
+
when 'equal'
|
62
|
+
return _not(is_not, result == @value)
|
63
|
+
when 'greater'
|
64
|
+
return _not(is_not, result > @value)
|
65
|
+
when 'greater-or-equal'
|
66
|
+
return _not(is_not, result >= @value)
|
67
|
+
when 'lesser'
|
68
|
+
return _not(is_not, result < @value)
|
69
|
+
when 'lesser-or-equal'
|
70
|
+
return _not(is_not, result <= @value)
|
71
|
+
end
|
72
|
+
|
73
|
+
raise Error.new("Unknown operator #{operator}")
|
74
|
+
rescue => error
|
75
|
+
@logger.error("#{error.inspect()}; failed to run " +
|
76
|
+
"context: #{@context.inspect()}")
|
77
|
+
|
78
|
+
raise error
|
79
|
+
end
|
80
|
+
|
81
|
+
def _not(is_not, result)
|
82
|
+
if is_not
|
83
|
+
return !result
|
84
|
+
end
|
85
|
+
|
86
|
+
return result
|
87
|
+
end
|
88
|
+
|
89
|
+
def run()
|
90
|
+
raise Error.new("run should be implemented in subclass")
|
91
|
+
end
|
92
|
+
|
93
|
+
def option(option)
|
94
|
+
return @variables.inject(self.instance_variable_get(:"@#{option}"))
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class Balmora::Context::Exec < Balmora::Context
|
2
|
+
|
3
|
+
def options()
|
4
|
+
return super().concat([:exec])
|
5
|
+
end
|
6
|
+
|
7
|
+
def verify()
|
8
|
+
if !@exec
|
9
|
+
raise Error.new('"exec" should be defined')
|
10
|
+
end
|
11
|
+
|
12
|
+
super()
|
13
|
+
end
|
14
|
+
|
15
|
+
def run()
|
16
|
+
return @shell.run!(_exec(), verbose: false).rstrip()
|
17
|
+
end
|
18
|
+
|
19
|
+
def _exec()
|
20
|
+
exec = option(:exec)
|
21
|
+
if exec.instance_of?(::String)
|
22
|
+
exec = [@shell.expression(exec)]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
class Balmora::Contexts
|
2
|
+
|
3
|
+
def self.factory(state)
|
4
|
+
return self.new(state.logger, state.extension, state.variables, state)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(logger, extension, variables, state)
|
8
|
+
@state = state
|
9
|
+
@logger = logger
|
10
|
+
@extension = extension
|
11
|
+
@variables = variables
|
12
|
+
end
|
13
|
+
|
14
|
+
def check(context)
|
15
|
+
if context.nil?()
|
16
|
+
return true
|
17
|
+
end
|
18
|
+
|
19
|
+
if context.instance_of?(::Array)
|
20
|
+
return _check_array(context)
|
21
|
+
end
|
22
|
+
|
23
|
+
if context.instance_of?(::String)
|
24
|
+
context = {
|
25
|
+
context: 'exec-result',
|
26
|
+
exec: context,
|
27
|
+
operator: 'equal',
|
28
|
+
value: 0,
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
context_class = @extension.get(Balmora::Context, context[:context])
|
34
|
+
context_instance = context_class.new(@state, context)
|
35
|
+
context_instance.init()
|
36
|
+
|
37
|
+
if !(context_class < Balmora::Context)
|
38
|
+
raise Error.new("Context #{context_class} should be subclass of context")
|
39
|
+
end
|
40
|
+
|
41
|
+
context_instance.verify()
|
42
|
+
result = context_instance.execute()
|
43
|
+
return result
|
44
|
+
end
|
45
|
+
|
46
|
+
def _check_array(contexts)
|
47
|
+
operator = :and
|
48
|
+
result = true
|
49
|
+
contexts.each() { |context|
|
50
|
+
if context == 'or'
|
51
|
+
operator = :or
|
52
|
+
next
|
53
|
+
end
|
54
|
+
|
55
|
+
if operator == :or
|
56
|
+
result = result || check(context)
|
57
|
+
operator = :and
|
58
|
+
else
|
59
|
+
if !result
|
60
|
+
next
|
61
|
+
end
|
62
|
+
|
63
|
+
result = result && check(context)
|
64
|
+
end
|
65
|
+
}
|
66
|
+
|
67
|
+
return result
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
# [
|
73
|
+
# {"context": "test -e folder/file1; echo $?", "operator": "equal", "value": "0"},
|
74
|
+
# "or",
|
75
|
+
# {"context": "test -e folder/file2; echo $?", "operator": "equal", "value": "0"},
|
76
|
+
# ]
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Balmora::Extension
|
2
|
+
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
attr_accessor :command_constant, :extension_constant
|
6
|
+
|
7
|
+
def self.factory(state)
|
8
|
+
return self.new(state)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(state)
|
12
|
+
@command_constant = ::Balmora::Command
|
13
|
+
@extension_constant = ::Balmora::Extension
|
14
|
+
@state = state
|
15
|
+
end
|
16
|
+
|
17
|
+
def get(namespace, extension)
|
18
|
+
class_name =
|
19
|
+
extension.
|
20
|
+
to_s().
|
21
|
+
gsub(/(-|^)(\w)/) { |char | (char[1] || char[0]).upcase() }.
|
22
|
+
to_sym()
|
23
|
+
|
24
|
+
if !namespace.const_defined?(class_name, false)
|
25
|
+
raise Error.new("Extension #{class_name.inspect()} not found in " +
|
26
|
+
"namespace " + namespace.inspect())
|
27
|
+
end
|
28
|
+
|
29
|
+
return namespace.const_get(class_name, false)
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_command(state, command)
|
33
|
+
if command.instance_of?(::String)
|
34
|
+
command = {command: 'exec', exec: command}
|
35
|
+
end
|
36
|
+
|
37
|
+
if command[:command].nil?()
|
38
|
+
raise Error.new("\"command\" should be defined in command " +
|
39
|
+
"#{command.inspect()}")
|
40
|
+
end
|
41
|
+
|
42
|
+
command_name = @state.variables.inject(command[:command])
|
43
|
+
command_class = get(@command_constant, command_name)
|
44
|
+
if !(command_class < @command_constant)
|
45
|
+
raise Error.new("Command should be instance of #{@command_constant}")
|
46
|
+
end
|
47
|
+
|
48
|
+
command_instance = command_class.new(state, command)
|
49
|
+
|
50
|
+
(command[:extensions] || []).each() { |extension|
|
51
|
+
extension_class = get(@extension_constant, extension)
|
52
|
+
command_instance.extend(extension_class)
|
53
|
+
}
|
54
|
+
|
55
|
+
return command_instance
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|