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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9f7d56a33f3e8d7e5e35beeb227e3881023ad7ca
|
4
|
+
data.tar.gz: 0a4e21d622e6a0c78c0ad5956d0b07b15221525e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 766373a8b25c96fc88762099c517f6bbd8c66d1f404c95c4ed8dbdfe2286da69664c1b694d513fd55de58536d090d810a7ae4cd5e2996085af7c366866a8a8d8
|
7
|
+
data.tar.gz: ec88c1d20741eda666acf3083a63293d2b1730915cf0f3674b80bd624d80cb30e90ad3809cdb13287186d7ffb5e56cf1f0771b3df9c377cf82143cd562cdfbac
|
data/bin/balmora
ADDED
data/lib/balmora.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
class Balmora
|
2
|
+
|
3
|
+
class Error < StandardError; end
|
4
|
+
class Restart < StandardError; end
|
5
|
+
class Stop < StandardError
|
6
|
+
|
7
|
+
attr_reader :status
|
8
|
+
|
9
|
+
def initialize(message, status)
|
10
|
+
super(message)
|
11
|
+
@status = status
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.run(task, arguments = {}, options = {})
|
17
|
+
state = Balmora::State.create(options, arguments)
|
18
|
+
state.balmora.run(task, state)
|
19
|
+
end
|
20
|
+
|
21
|
+
# def self.create(options = {}, arguments = {})
|
22
|
+
# state = Balmora::State.create(options, arguments)
|
23
|
+
# return state.balmora
|
24
|
+
# end
|
25
|
+
|
26
|
+
def self.factory(state)
|
27
|
+
return self.new(state.logger, state.extension, state)
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(logger, extension, state)
|
31
|
+
@logger = logger
|
32
|
+
@extension = extension
|
33
|
+
@state = state
|
34
|
+
end
|
35
|
+
|
36
|
+
def run(task, state)
|
37
|
+
restarts = state.config.get([:max_restarts], default: 3)
|
38
|
+
|
39
|
+
restarts.
|
40
|
+
times() { |index|
|
41
|
+
begin
|
42
|
+
dir = state.config.get(:chdir, default: Dir.pwd)
|
43
|
+
dir = state.variables.inject(dir)
|
44
|
+
Dir.chdir(dir) {
|
45
|
+
commands = state.config.get([:tasks, task.to_sym(), :commands])
|
46
|
+
run_commands(state, commands)
|
47
|
+
}
|
48
|
+
return 0
|
49
|
+
rescue Restart
|
50
|
+
@logger.debug("Restarting task (#{restarts - index} attempts left)")
|
51
|
+
state = Balmora::State.create(@state.options, @state.arguments)
|
52
|
+
end
|
53
|
+
}
|
54
|
+
|
55
|
+
|
56
|
+
raise Error.new("Maximal restart attempts count (#{restarts}) reached")
|
57
|
+
rescue Stop => stop
|
58
|
+
@logger.debug("Stop with status #{stop.status} catched")
|
59
|
+
return stop.status
|
60
|
+
end
|
61
|
+
|
62
|
+
def run_commands(state, commands)
|
63
|
+
commands.
|
64
|
+
each() { |command|
|
65
|
+
run_command(state, command)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def run_command(state, command)
|
70
|
+
@extension.
|
71
|
+
create_command(state, command).
|
72
|
+
execute()
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
require 'balmora/require.rb'
|
@@ -0,0 +1,175 @@
|
|
1
|
+
class Balmora::Arguments
|
2
|
+
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
def self.parse(options, argv)
|
6
|
+
shortcuts = _shortcuts(options)
|
7
|
+
|
8
|
+
result = {}
|
9
|
+
index = 0
|
10
|
+
while index < argv.length
|
11
|
+
arg = argv[index]
|
12
|
+
|
13
|
+
if arg.start_with?('--')
|
14
|
+
new_index = _parse_key(result, options, argv, index, arg[2..-1])
|
15
|
+
elsif arg.start_with?('-')
|
16
|
+
new_index = _parse_flags(result, options, argv, index, shortcuts, arg)
|
17
|
+
else
|
18
|
+
new_index = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
if new_index.nil?()
|
22
|
+
return result, argv[index..-1]
|
23
|
+
end
|
24
|
+
|
25
|
+
index = new_index + 1
|
26
|
+
end
|
27
|
+
|
28
|
+
return result, nil
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def self._shortcuts(options)
|
34
|
+
shortcuts = Hash[
|
35
|
+
options.
|
36
|
+
collect() { |key, option|
|
37
|
+
if !option.has_key?(:shortcut)
|
38
|
+
next nil
|
39
|
+
end
|
40
|
+
|
41
|
+
[option[:shortcut].to_sym(), key]
|
42
|
+
}.
|
43
|
+
compact()
|
44
|
+
]
|
45
|
+
|
46
|
+
return shortcuts
|
47
|
+
end
|
48
|
+
|
49
|
+
def self._parse_flags(result, options, argv, index, shortcuts, arg)
|
50
|
+
new_index = nil
|
51
|
+
|
52
|
+
if arg.length > 2
|
53
|
+
arg[1..-1].split('').each() { |flag|
|
54
|
+
new_index = _parse_key(result, options, argv, index,
|
55
|
+
shortcuts[flag.to_sym()], true)
|
56
|
+
|
57
|
+
if new_index.nil?()
|
58
|
+
return nil
|
59
|
+
end
|
60
|
+
}
|
61
|
+
else
|
62
|
+
new_index = _parse_key(result, options, argv, index,
|
63
|
+
shortcuts[arg[1..-1].to_sym()])
|
64
|
+
end
|
65
|
+
|
66
|
+
return new_index
|
67
|
+
end
|
68
|
+
|
69
|
+
def self._parse_key(result, options, argv, index, key, flag_required = false)
|
70
|
+
if key.nil?()
|
71
|
+
return nil
|
72
|
+
end
|
73
|
+
|
74
|
+
key = key.to_sym()
|
75
|
+
|
76
|
+
if !options.has_key?(key)
|
77
|
+
return nil
|
78
|
+
end
|
79
|
+
|
80
|
+
option = options[key]
|
81
|
+
if option[:flag] == true
|
82
|
+
result[key] = true
|
83
|
+
else
|
84
|
+
if flag_required
|
85
|
+
return nil
|
86
|
+
end
|
87
|
+
|
88
|
+
value = argv[index + 1]
|
89
|
+
result[key] = value
|
90
|
+
index += 1
|
91
|
+
end
|
92
|
+
|
93
|
+
return index
|
94
|
+
end
|
95
|
+
|
96
|
+
public
|
97
|
+
|
98
|
+
def self.help(options)
|
99
|
+
lines = []
|
100
|
+
options.each() { |long, option|
|
101
|
+
if option.instance_of?(::String)
|
102
|
+
lines.push(option)
|
103
|
+
next
|
104
|
+
end
|
105
|
+
|
106
|
+
arg =
|
107
|
+
if option[:shortcut].nil?()
|
108
|
+
(' ' * 7) + '--' + long.to_s()
|
109
|
+
else
|
110
|
+
(' ' * 4) + '-' + option[:shortcut] + ', ' + '--' + long.to_s()
|
111
|
+
end
|
112
|
+
|
113
|
+
lines.push([arg, option[:description]])
|
114
|
+
}
|
115
|
+
|
116
|
+
return format(lines)
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.format(lines)
|
120
|
+
indent =
|
121
|
+
lines.
|
122
|
+
inject(0) { |current, line|
|
123
|
+
if line.instance_of?(::String)
|
124
|
+
next current
|
125
|
+
end
|
126
|
+
|
127
|
+
if current < line[0].length
|
128
|
+
current = line[0].length
|
129
|
+
end
|
130
|
+
|
131
|
+
current
|
132
|
+
} +
|
133
|
+
4
|
134
|
+
|
135
|
+
result = []
|
136
|
+
lines.each() { |line|
|
137
|
+
if line.instance_of?(::String)
|
138
|
+
result.push(line)
|
139
|
+
next
|
140
|
+
end
|
141
|
+
|
142
|
+
summary = _wrap(line[1] || '', 60)
|
143
|
+
result.push(line[0] + (' ' * (indent - line[0].length)) +
|
144
|
+
(summary[0] || ''))
|
145
|
+
|
146
|
+
(summary[1..-1] || []).each() { |ln| result.push(' ' * indent + ln) }
|
147
|
+
|
148
|
+
result.push('')
|
149
|
+
}
|
150
|
+
|
151
|
+
return result.join("\n").rstrip()
|
152
|
+
end
|
153
|
+
|
154
|
+
def self._wrap(text, length)
|
155
|
+
line = ''
|
156
|
+
result = []
|
157
|
+
|
158
|
+
text.split("\n").each() { |text_line|
|
159
|
+
text_line.split(' ').each() { |word|
|
160
|
+
if (line + word + ' ').length > length
|
161
|
+
result.push(line.rstrip())
|
162
|
+
line = ''
|
163
|
+
end
|
164
|
+
|
165
|
+
line += word + ' '
|
166
|
+
}
|
167
|
+
|
168
|
+
result.push(line.rstrip())
|
169
|
+
line = ''
|
170
|
+
}
|
171
|
+
|
172
|
+
return result
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
data/lib/balmora/cli.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'balmora'
|
2
|
+
require 'balmora/arguments'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
class Balmora::Cli
|
6
|
+
|
7
|
+
class Error < StandardError; end
|
8
|
+
|
9
|
+
attr_accessor :out, :err, :rescue
|
10
|
+
|
11
|
+
def initialize()
|
12
|
+
_create_options()
|
13
|
+
|
14
|
+
@out = STDOUT
|
15
|
+
@err = STDERR
|
16
|
+
@rescue = true
|
17
|
+
|
18
|
+
@arguments = Balmora::Arguments
|
19
|
+
end
|
20
|
+
|
21
|
+
def _create_options()
|
22
|
+
@options = {
|
23
|
+
config: {
|
24
|
+
shortcut: 'c',
|
25
|
+
description: 'Configuration file; if not specified, balmora will ' +
|
26
|
+
'look configuration file at ~/.config/balmora/balmora.conf and ' +
|
27
|
+
'/etc/balmora/balmora.conf in order',
|
28
|
+
},
|
29
|
+
|
30
|
+
quite: {
|
31
|
+
shortcut: 'q',
|
32
|
+
flag: true,
|
33
|
+
description: 'Quite mode; reports only errors',
|
34
|
+
},
|
35
|
+
|
36
|
+
verbose: {
|
37
|
+
shortcut: 'v',
|
38
|
+
flag: true,
|
39
|
+
description: 'Verbose mode; reports all executed commands',
|
40
|
+
},
|
41
|
+
|
42
|
+
help: {
|
43
|
+
shortcut: 'h',
|
44
|
+
flag: true,
|
45
|
+
description: 'Show help; if --config specified in arguments, shows ' +
|
46
|
+
'config help; if TASK specified, shows task help',
|
47
|
+
},
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def run(argv)
|
52
|
+
args, tail = @arguments.parse(@options, argv)
|
53
|
+
state = Balmora::State.create(args, {})
|
54
|
+
if tail.nil?()
|
55
|
+
if args[:help] == true
|
56
|
+
@out.puts(help(state))
|
57
|
+
return 1
|
58
|
+
end
|
59
|
+
|
60
|
+
@err.puts('No task specified; use --help to see help')
|
61
|
+
return 1
|
62
|
+
end
|
63
|
+
|
64
|
+
if tail[0].start_with?('-')
|
65
|
+
raise Error.new("Unknown option #{tail.first()}; use --help to see " +
|
66
|
+
"options")
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
task_name, *task_argv = tail
|
71
|
+
task = state.config.get([:tasks, task_name.to_sym()], default: nil,
|
72
|
+
variables: false)
|
73
|
+
|
74
|
+
if task.nil?()
|
75
|
+
raise Error.new("Unknown task #{task_name.to_s().inspect()}; use " +
|
76
|
+
"--help to see task list")
|
77
|
+
end
|
78
|
+
|
79
|
+
if args[:help] == true
|
80
|
+
@out.puts(task_help(state, task_name, task))
|
81
|
+
return 1
|
82
|
+
end
|
83
|
+
|
84
|
+
if task.has_key?(:arguments)
|
85
|
+
task_args, task_args_tail = @arguments.parse(task[:arguments], task_argv)
|
86
|
+
if !task_args_tail.nil?()
|
87
|
+
raise Error.new("Unknown task option #{task_args_tail.first().
|
88
|
+
inspect()}; use --help #{task_name} to see task options")
|
89
|
+
end
|
90
|
+
else
|
91
|
+
task_args = {}
|
92
|
+
end
|
93
|
+
|
94
|
+
state.arguments.merge!(task_args)
|
95
|
+
|
96
|
+
STDIN.reopen("/dev/tty") # make sure that input to tty will
|
97
|
+
|
98
|
+
return state.balmora.run(task_name, state)
|
99
|
+
rescue => error
|
100
|
+
if !@rescue || true
|
101
|
+
raise error
|
102
|
+
end
|
103
|
+
|
104
|
+
@err.puts('Error: ' + error.to_s())
|
105
|
+
if args[:debug]
|
106
|
+
@err.puts(error.backtrace)
|
107
|
+
end
|
108
|
+
|
109
|
+
return 1
|
110
|
+
end
|
111
|
+
|
112
|
+
def help(state)
|
113
|
+
tasks =
|
114
|
+
state.
|
115
|
+
config.
|
116
|
+
get([:tasks], variables: false, default: []).
|
117
|
+
collect() { |key, task|
|
118
|
+
[' ' * 4 + key.to_s(), task[:description] || '']
|
119
|
+
}
|
120
|
+
|
121
|
+
help =
|
122
|
+
"Usage: balmora [common-options] task [task-options]\n\n" +
|
123
|
+
"Options\n\n" +
|
124
|
+
@arguments.help(@options) + "\n\n" +
|
125
|
+
"Tasks:\n\n" +
|
126
|
+
@arguments.format(tasks)
|
127
|
+
|
128
|
+
return help
|
129
|
+
end
|
130
|
+
|
131
|
+
def task_help(statetask_name, task)
|
132
|
+
help =
|
133
|
+
"Usage: balmora [common-optinos] #{task_name} [task-options]\n\n" +
|
134
|
+
"Description:\n\n" +
|
135
|
+
(
|
136
|
+
task[:description] &&
|
137
|
+
@arguments.format([[' ', task[:description]]]) ||
|
138
|
+
((' ' * 4) + 'No description provided')
|
139
|
+
) + "\n\n" +
|
140
|
+
"Arguments:\n\n" +
|
141
|
+
(
|
142
|
+
task[:arguments] &&
|
143
|
+
@arguments.help(statetask[:arguments]) ||
|
144
|
+
((' ' * 4) + 'Task has no arguments')
|
145
|
+
)
|
146
|
+
|
147
|
+
return help
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Balmora::Command
|
2
|
+
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
def initialize(state, command)
|
6
|
+
@state = state
|
7
|
+
|
8
|
+
@logger = state.logger
|
9
|
+
@shell = state.shell
|
10
|
+
@config = state.config
|
11
|
+
@contexts = state.contexts
|
12
|
+
@variables = state.variables
|
13
|
+
@balmora = state.balmora
|
14
|
+
|
15
|
+
@command = command
|
16
|
+
end
|
17
|
+
|
18
|
+
def init()
|
19
|
+
if (@command.keys() - [:command] - options()).length != 0
|
20
|
+
raise Error.new("Unknown options #{(@command.keys() - [:command] -
|
21
|
+
options()).inspect()}")
|
22
|
+
end
|
23
|
+
|
24
|
+
options().each() { |key|
|
25
|
+
if self.instance_variable_defined?(:"@#{key}")
|
26
|
+
raise Error.new("Can not use #{key} as option")
|
27
|
+
end
|
28
|
+
|
29
|
+
option = @command.fetch(key, nil)
|
30
|
+
self.instance_variable_set(:"@#{key}", option)
|
31
|
+
}
|
32
|
+
|
33
|
+
verify()
|
34
|
+
end
|
35
|
+
|
36
|
+
def options()
|
37
|
+
return [:ignore_error, :fallback, :extensions, :sudo, :context, :chdir,
|
38
|
+
:require]
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute()
|
42
|
+
if @command.has_key?(:chdir)
|
43
|
+
dir = @command[:chdir]
|
44
|
+
dir = @variables.inject(dir)
|
45
|
+
dir = @shell.expand(dir)
|
46
|
+
Dir.chdir(dir) {
|
47
|
+
_execute()
|
48
|
+
}
|
49
|
+
else
|
50
|
+
_execute()
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def _execute()
|
55
|
+
if @command.instance_of?(::Hash) && !@contexts.check(@command[:context])
|
56
|
+
@logger.debug("Skip: #{@command.inspect()}")
|
57
|
+
return
|
58
|
+
end
|
59
|
+
|
60
|
+
init()
|
61
|
+
|
62
|
+
if !@require.nil?()
|
63
|
+
option(:require).each() { |file|
|
64
|
+
require file
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
@logger.debug("Run: #{@command.inspect()}")
|
69
|
+
|
70
|
+
execute = self.method(:run)
|
71
|
+
|
72
|
+
if !@sudo.nil?()
|
73
|
+
execute_sudo = execute
|
74
|
+
execute = Proc.new() {
|
75
|
+
@shell.sudo!(option(:sudo)) {
|
76
|
+
execute_sudo.call()
|
77
|
+
}
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
execute.call()
|
82
|
+
rescue => error
|
83
|
+
@logger.error("#{error.inspect()}; failed to run " +
|
84
|
+
"command: #{@command.inspect()}")
|
85
|
+
|
86
|
+
if @fallback
|
87
|
+
@logger.debug("Executing fallback for command #{@command.inspect()}")
|
88
|
+
@balmora.execute(@fallback)
|
89
|
+
end
|
90
|
+
|
91
|
+
if option(:ignore_error) == true
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
95
|
+
raise error
|
96
|
+
end
|
97
|
+
|
98
|
+
def run()
|
99
|
+
raise Error.new("Run should be implemented in subclass")
|
100
|
+
end
|
101
|
+
|
102
|
+
def option(option)
|
103
|
+
return @variables.inject(self.instance_variable_get(:"@#{option}"))
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|