balmora 0.0.1
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.
- 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
|