db_sucker 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/CHANGELOG.md +45 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +193 -0
- data/Rakefile +1 -0
- data/VERSION +1 -0
- data/bin/db_sucker +12 -0
- data/bin/db_sucker.sh +14 -0
- data/db_sucker.gemspec +29 -0
- data/doc/config_example.rb +53 -0
- data/doc/container_example.yml +150 -0
- data/lib/db_sucker/adapters/mysql2.rb +103 -0
- data/lib/db_sucker/application/colorize.rb +28 -0
- data/lib/db_sucker/application/container/accessors.rb +60 -0
- data/lib/db_sucker/application/container/ssh.rb +225 -0
- data/lib/db_sucker/application/container/validations.rb +53 -0
- data/lib/db_sucker/application/container/variation/accessors.rb +45 -0
- data/lib/db_sucker/application/container/variation/helpers.rb +21 -0
- data/lib/db_sucker/application/container/variation/worker_api.rb +65 -0
- data/lib/db_sucker/application/container/variation.rb +60 -0
- data/lib/db_sucker/application/container.rb +70 -0
- data/lib/db_sucker/application/container_collection.rb +47 -0
- data/lib/db_sucker/application/core.rb +222 -0
- data/lib/db_sucker/application/dispatch.rb +364 -0
- data/lib/db_sucker/application/evented_resultset.rb +149 -0
- data/lib/db_sucker/application/fake_channel.rb +22 -0
- data/lib/db_sucker/application/output_helper.rb +197 -0
- data/lib/db_sucker/application/sklaven_treiber/log_spool.rb +57 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/accessors.rb +105 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/core.rb +168 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/helpers.rb +144 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/base.rb +240 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_copy.rb +81 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_gunzip.rb +58 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_import_sql.rb +80 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/file_shasum.rb +49 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/pv_wrapper.rb +73 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/sftp_download.rb +57 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/io/throughput.rb +219 -0
- data/lib/db_sucker/application/sklaven_treiber/worker/routines.rb +313 -0
- data/lib/db_sucker/application/sklaven_treiber/worker.rb +48 -0
- data/lib/db_sucker/application/sklaven_treiber.rb +281 -0
- data/lib/db_sucker/application/slot_pool.rb +137 -0
- data/lib/db_sucker/application/tie.rb +25 -0
- data/lib/db_sucker/application/window/core.rb +185 -0
- data/lib/db_sucker/application/window/dialog.rb +142 -0
- data/lib/db_sucker/application/window/keypad/core.rb +85 -0
- data/lib/db_sucker/application/window/keypad.rb +174 -0
- data/lib/db_sucker/application/window/prompt.rb +124 -0
- data/lib/db_sucker/application/window.rb +329 -0
- data/lib/db_sucker/application.rb +168 -0
- data/lib/db_sucker/patches/beta-warning.rb +374 -0
- data/lib/db_sucker/patches/developer.rb +29 -0
- data/lib/db_sucker/patches/net-sftp.rb +20 -0
- data/lib/db_sucker/patches/thread-count.rb +30 -0
- data/lib/db_sucker/version.rb +4 -0
- data/lib/db_sucker.rb +81 -0
- metadata +217 -0
@@ -0,0 +1,222 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
module Core
|
4
|
+
def sandboxed &block
|
5
|
+
block.call
|
6
|
+
rescue StandardError => ex
|
7
|
+
e = ["#{ex.class}: #{ex.message}"]
|
8
|
+
ex.backtrace.each{|l| e << "\t#{l}" }
|
9
|
+
error(e.join("\n"))
|
10
|
+
return false
|
11
|
+
end
|
12
|
+
|
13
|
+
def notify_exception label_or_ex, ex = nil
|
14
|
+
error [].tap{|e|
|
15
|
+
e << label_or_ex if ex
|
16
|
+
e << "#{"\t" if ex}#{ex ? ex.class : label_or_ex.class}: #{ex ? ex.message : label_or_ex.message}"
|
17
|
+
(ex ? ex : label_or_ex).backtrace.each{|l| e << "\t#{" " if ex}#{l}" }
|
18
|
+
}.join("\n")
|
19
|
+
end
|
20
|
+
|
21
|
+
def dump_file ctx, open = false, &block
|
22
|
+
"#{core_tmp_path}/#{ctx}-#{Time.current.to_i}-#{uniqid}.log".tap do |df|
|
23
|
+
FileUtils.mkdir_p(File.dirname(df))
|
24
|
+
File.open(df, "wb", &block) if block
|
25
|
+
if block && open && sdf = Shellwords.shellescape(df)
|
26
|
+
fork { exec("#{opts[:core_dump_editor]} #{sdf} ; rm #{sdf}") }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def dump_core
|
32
|
+
dump_file "coredump", true do |f|
|
33
|
+
# thread info
|
34
|
+
f.puts "#{Thread.list.length} threads:\n"
|
35
|
+
Thread.list.each do |thr|
|
36
|
+
f.puts "#{thr.inspect}"
|
37
|
+
f.puts " iType: #{thr == Thread.main ? :main_thread : thr[:itype] || :uncategorized}"
|
38
|
+
f.puts " Aborts?: #{thr.abort_on_exception}"
|
39
|
+
f.puts " Priority: #{thr.priority}"
|
40
|
+
f.puts " T-Vars: #{thr.thread_variables.inspect}"
|
41
|
+
thr.thread_variables.each {|k| f.puts " #{k} => #{thr.thread_variable(k)}" }
|
42
|
+
f.puts " F-Vars: #{thr.keys.inspect}"
|
43
|
+
thr.keys.each {|k| f.puts " #{k} => #{thr[k]}" }
|
44
|
+
f.puts " Backtrace: #{thr.backtrace.length}"
|
45
|
+
thr.backtrace.each {|l| f.puts " #{l}" }
|
46
|
+
#f.puts " Caller: #{thr.backtrace_locations.try(:length) || "unknown"}"
|
47
|
+
#thr.backtrace_locations.each {|l| f.puts " #{l}" } if thr.backtrace_locations
|
48
|
+
f.puts
|
49
|
+
end
|
50
|
+
|
51
|
+
# worker info
|
52
|
+
f.puts "\n\n#{sklaventreiber.workers.length} workers:\n"
|
53
|
+
sklaventreiber.workers.each do |w|
|
54
|
+
f.puts "#{"[SSH] " if w.sshing} #{w.descriptive} #{w.state}".strip
|
55
|
+
end
|
56
|
+
|
57
|
+
# slot pool
|
58
|
+
f.puts "\n\n#{sklaventreiber.slot_pools.length} slot pools:\n"
|
59
|
+
sklaventreiber.slot_pools.each do |name, pool|
|
60
|
+
f.puts "#{name}: #{pool.slots}"
|
61
|
+
pool.active.each do |thr|
|
62
|
+
f.puts "\tactive\t #{thr} [#{thr[:itype]}]"
|
63
|
+
end
|
64
|
+
pool.waiting.each do |wthr, tthr|
|
65
|
+
f.puts "\twaiting\t #{tthr} [#{tthr[:itype]}]"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def sync &block
|
72
|
+
@monitor.synchronize(&block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def outsync &block
|
76
|
+
@output_monitor.synchronize(&block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def uniqid
|
80
|
+
Digest::SHA1.hexdigest(SecureRandom.urlsafe_base64(128))
|
81
|
+
end
|
82
|
+
|
83
|
+
def spawn_thread type, &block
|
84
|
+
waitlock = Queue.new
|
85
|
+
sync do
|
86
|
+
if !@opts[:"tp_#{type}"]
|
87
|
+
warning "Thread type `#{type}' has no priority setting, defaulting to 0..."
|
88
|
+
end
|
89
|
+
|
90
|
+
# spawn thread
|
91
|
+
Thread.new do
|
92
|
+
waitlock.pop
|
93
|
+
block.call(Thread.current)
|
94
|
+
end.tap do |thr|
|
95
|
+
# set type and priority
|
96
|
+
thr[:itype] = type
|
97
|
+
thr.priority = @opts[:"tp_#{type}"]
|
98
|
+
thr.abort_on_exception = true
|
99
|
+
|
100
|
+
# add signal methods
|
101
|
+
signalify_thread(thr)
|
102
|
+
|
103
|
+
# start thread
|
104
|
+
waitlock << true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def signalify_thread thr
|
110
|
+
# set lock and signal
|
111
|
+
thr[:monitor] = Monitor.new
|
112
|
+
thr[:signal] = thr[:monitor].new_cond
|
113
|
+
|
114
|
+
# define helper methods
|
115
|
+
def thr.signal
|
116
|
+
self[:monitor].synchronize{ self[:signal].broadcast } ; self
|
117
|
+
end
|
118
|
+
def thr.wait(*a)
|
119
|
+
self[:monitor].synchronize{ self[:signal].wait(*a) }
|
120
|
+
end
|
121
|
+
def thr.sync(&b)
|
122
|
+
self[:monitor].synchronize(&b)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def channelfy_thread thr
|
127
|
+
def thr.active?
|
128
|
+
alive?
|
129
|
+
end
|
130
|
+
|
131
|
+
def thr.closed?
|
132
|
+
!alive?
|
133
|
+
end
|
134
|
+
|
135
|
+
def thr.closing?
|
136
|
+
false
|
137
|
+
end
|
138
|
+
|
139
|
+
thr
|
140
|
+
end
|
141
|
+
|
142
|
+
def fake_channel &block
|
143
|
+
FakeChannel.new(&block).tap do |ch|
|
144
|
+
channelfy_thread(ch)
|
145
|
+
signalify_thread(ch)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def wakeup_handlers
|
150
|
+
Thread.list.each{|thr| thr.try(:signal) }
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
# =================
|
155
|
+
# = Configuration =
|
156
|
+
# =================
|
157
|
+
def core_cfg_path
|
158
|
+
File.expand_path(ENV["DBS_CFGDIR"].presence || "~/.db_sucker")
|
159
|
+
end
|
160
|
+
|
161
|
+
def core_tmp_path
|
162
|
+
"#{File.expand_path(ENV["DBS_TMPDIR"] || ENV["TMPDIR"] || "/tmp")}/db_sucker_temp"
|
163
|
+
end
|
164
|
+
|
165
|
+
def core_cfg_configfile
|
166
|
+
"#{core_cfg_path}/config.rb"
|
167
|
+
end
|
168
|
+
|
169
|
+
def load_appconfig
|
170
|
+
return unless File.exist?(core_cfg_configfile)
|
171
|
+
instance_eval File.read(core_cfg_configfile, encoding: "utf-8"), core_cfg_configfile
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
# ===================
|
176
|
+
# = Signal trapping =
|
177
|
+
# ===================
|
178
|
+
def trap_signals
|
179
|
+
debug "Trapping signals..."
|
180
|
+
Signal.trap("INT") do
|
181
|
+
$core_runtime_exiting = 1
|
182
|
+
Kernel.puts "Interrupting..."
|
183
|
+
end
|
184
|
+
Signal.trap("TERM") do
|
185
|
+
$core_runtime_exiting = 2
|
186
|
+
Kernel.puts "Terminating..."
|
187
|
+
end
|
188
|
+
Signal.trap("USR1") do
|
189
|
+
dump_core
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def release_signals
|
194
|
+
debug "Releasing signal traps..."
|
195
|
+
Signal.trap("INT", "DEFAULT")
|
196
|
+
Signal.trap("TERM", "DEFAULT")
|
197
|
+
Signal.trap("USR1", "DEFAULT")
|
198
|
+
end
|
199
|
+
|
200
|
+
def haltpoint
|
201
|
+
raise Interrupt if $core_runtime_exiting && !$core_runtime_graceful
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
# ==========
|
206
|
+
# = Events =
|
207
|
+
# ==========
|
208
|
+
def hook *which, &hook_block
|
209
|
+
which.each do |w|
|
210
|
+
@hooks[w.to_sym] ||= []
|
211
|
+
@hooks[w.to_sym] << hook_block
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def fire which, *args
|
216
|
+
return if @disable_event_firing
|
217
|
+
sync { debug "[Event] Firing #{which} (#{@hooks[which].try(:length) || 0} handlers) #{args.map(&:to_s)}", 99 }
|
218
|
+
@hooks[which] && @hooks[which].each{|h| h.call(self, *args) }
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,364 @@
|
|
1
|
+
module DbSucker
|
2
|
+
class Application
|
3
|
+
module Dispatch
|
4
|
+
def dispatch action = (@opts[:dispatch] || :help)
|
5
|
+
if respond_to?("dispatch_#{action}")
|
6
|
+
has_action = true
|
7
|
+
fire(:dispatch_before, action)
|
8
|
+
send("dispatch_#{action}")
|
9
|
+
else
|
10
|
+
fire(:dispatch_before, false)
|
11
|
+
abort("unknown action `#{action}'\nRegistered actions: #{available_actions.join(", ")}")
|
12
|
+
end
|
13
|
+
rescue StandardError => ex
|
14
|
+
fire(:dispatch_after, has_action ? action : false, ex)
|
15
|
+
raise ex
|
16
|
+
else
|
17
|
+
fire(:dispatch_after, has_action ? action : false)
|
18
|
+
end
|
19
|
+
|
20
|
+
def available_actions
|
21
|
+
methods.select{|m| m.to_s.start_with?("dispatch_") }.map{|s| s.to_s.gsub(/\Adispatch_/, "").to_sym }
|
22
|
+
end
|
23
|
+
|
24
|
+
def configful_dispatch identifier, variation, &block
|
25
|
+
log "Using config directory #{c core_cfg_path.to_s, :magenta}"
|
26
|
+
|
27
|
+
all_cfgs = cfg.yml_configs(true)
|
28
|
+
enabled = cfg.yml_configs(false)
|
29
|
+
disabled = all_cfgs - enabled
|
30
|
+
r = c("Found #{c all_cfgs.count, :blue} #{c "config files"}")
|
31
|
+
r << c(" (#{c "#{disabled.count} disabled", :red}#{c ")"}") if disabled.any?
|
32
|
+
log r << c("...")
|
33
|
+
|
34
|
+
cfg.load_all_configs
|
35
|
+
ctn = cfg.get(identifier)
|
36
|
+
|
37
|
+
if identifier.present? && !ctn
|
38
|
+
abort "Identifier `#{identifier}' couldn't be found!", 1
|
39
|
+
end
|
40
|
+
|
41
|
+
if ctn && variation && !(var = ctn.variation(variation))
|
42
|
+
abort "Variation `#{variation}' for identifier `#{identifier}' couldn't be found!", 1
|
43
|
+
end
|
44
|
+
|
45
|
+
begin
|
46
|
+
ctn.try(:ssh_begin)
|
47
|
+
block.call(identifier, ctn, variation, var)
|
48
|
+
ensure
|
49
|
+
ctn.try(:ssh_end)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# ----------------------------------------------------------------------
|
54
|
+
|
55
|
+
# ====================
|
56
|
+
# = Dev/Test actions =
|
57
|
+
# ====================
|
58
|
+
|
59
|
+
# via -a/--action cloop
|
60
|
+
def dispatch_cloop
|
61
|
+
begin
|
62
|
+
trap_signals
|
63
|
+
@sklaventreiber = SklavenTreiber.new(self, uniqid)
|
64
|
+
@sklaventreiber.spooled do
|
65
|
+
@opts[:stdout].disable
|
66
|
+
hook(:prompt_start) { @opts[:stdout].enable }
|
67
|
+
hook(:prompt_stop) { @opts[:stdout].disable(true) }
|
68
|
+
begin
|
69
|
+
@sklaventreiber._init_window
|
70
|
+
loop do
|
71
|
+
break if $core_runtime_exiting
|
72
|
+
sleep 0.1
|
73
|
+
end
|
74
|
+
ensure
|
75
|
+
sandboxed { @sklaventreiber.window.try(:stop) }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
ensure
|
79
|
+
release_signals
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# via -a/--action console
|
84
|
+
def dispatch_console
|
85
|
+
configful_dispatch(ARGV.shift, ARGV.shift) do |identifier, ctn, variation, var|
|
86
|
+
begin ; require "pry" ; rescue LoadError ; end
|
87
|
+
::Kernel.binding.pry
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# via -a/--action threadtest
|
92
|
+
def dispatch_threadtest
|
93
|
+
t = Thread.new { Thread.stop }
|
94
|
+
i, m = 1, [0, nil, nil]
|
95
|
+
while i < 3
|
96
|
+
m[0] += i == 1 ? -1 : 1
|
97
|
+
t.priority = m[0]
|
98
|
+
if t.priority != m[0]
|
99
|
+
m[i] = t.priority
|
100
|
+
m[0] = 0
|
101
|
+
i += 1
|
102
|
+
end
|
103
|
+
end
|
104
|
+
t.kill
|
105
|
+
puts "Thread priority: -#{m[1].abs}..+#{m[2].abs}"
|
106
|
+
end
|
107
|
+
|
108
|
+
# via -a/--action sshdiag
|
109
|
+
def dispatch_sshdiag
|
110
|
+
log c("\nPlease wait while we run some tests...\n", :blue)
|
111
|
+
_identifier, _ctn = false, false, false
|
112
|
+
idstr = ARGV.shift
|
113
|
+
varstr = ARGV.shift
|
114
|
+
|
115
|
+
configful_dispatch(idstr, varstr) do |identifier, ctn, variation, var|
|
116
|
+
_identifier = identifier
|
117
|
+
_ctn = ctn
|
118
|
+
channels = []
|
119
|
+
stop = false
|
120
|
+
maxsessions = :unknown
|
121
|
+
begin
|
122
|
+
t = Thread.new {
|
123
|
+
begin
|
124
|
+
loop do
|
125
|
+
ctn.loop_ssh(0.1)
|
126
|
+
end
|
127
|
+
rescue DbSucker::Application::Container::SSH::ChannelOpenFailedError
|
128
|
+
maxsessions = channels.length - channels.select{|c| c[:open_failed] }.length
|
129
|
+
stop = true
|
130
|
+
retry
|
131
|
+
end
|
132
|
+
}
|
133
|
+
250.times do
|
134
|
+
break if stop
|
135
|
+
c, r = ctn.blocking_channel_result("sleep 60", blocking: false, channel: true, use_sh: true)
|
136
|
+
channels << c
|
137
|
+
sleep 0.1
|
138
|
+
end
|
139
|
+
ensure
|
140
|
+
debug "Stopping sessions (#{channels.length})..."
|
141
|
+
channels.each_with_index do |c, i|
|
142
|
+
debug "Channel ##{i+1} #{c[:pid] ? "with PID #{c[:pid]}" : "has no PID"}"
|
143
|
+
ctn.kill_remote_process(c[:pid]) if c[:pid]
|
144
|
+
end
|
145
|
+
log c("\nSSH MaxSessions: #{c maxsessions, :magenta}", :cyan)
|
146
|
+
log "This value determines how many sessions we can multiplex over a single TCP connection."
|
147
|
+
log "Currently, DbSucker can only utilize one connection, thus this value defines the maxmium concurrency."
|
148
|
+
log "If you get errors you can either"
|
149
|
+
log " * increase the SSHd `MaxSessions' setting on the remote (if you can)"
|
150
|
+
log " * reduce the amount of workers and/or remote slots"
|
151
|
+
log " * fix the mess that is this tool, visit #{c "https://github.com/2called-chaos/db_sucker", :blue}"
|
152
|
+
t.kill
|
153
|
+
end
|
154
|
+
end
|
155
|
+
rescue Net::SSH::AuthenticationFailed => ex
|
156
|
+
notify_exception(ex)
|
157
|
+
log "\nDbSucker can't authenticate with the remote host, see exception."
|
158
|
+
log " * username correct?"
|
159
|
+
log " * password/keyfile correct?"
|
160
|
+
log " * check remote /var/log/auth.log?"
|
161
|
+
rescue SocketError => ex
|
162
|
+
notify_exception(ex)
|
163
|
+
log "\nDbSucker can't establish a connection to the remote host " << c(cfg.get(idstr).source["ssh"]["hostname"], :magenta)
|
164
|
+
log " * typo in hostname / IP?"
|
165
|
+
log " * firewall?"
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
# ================
|
170
|
+
# = Main actions =
|
171
|
+
# ================
|
172
|
+
|
173
|
+
# via -h/--help
|
174
|
+
def dispatch_help
|
175
|
+
colorized_help = @optparse.to_s.split("\n").map do |l|
|
176
|
+
if l.start_with?("Usage:")
|
177
|
+
lc = l.split(" ")
|
178
|
+
"#{c lc[0]} #{c lc[1], :blue} #{c lc[2..-1].join(" "), :cyan}"
|
179
|
+
elsif l.start_with?("#")
|
180
|
+
c(l, :blue)
|
181
|
+
elsif l.strip.start_with?("-")
|
182
|
+
"#{c l.to_s[0...33], :cyan}#{c l[33..-1]}"
|
183
|
+
else
|
184
|
+
c(l)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
puts nil, colorized_help, nil
|
188
|
+
puts c("The current config directory is #{c core_cfg_path.to_s, :magenta}"), nil
|
189
|
+
end
|
190
|
+
|
191
|
+
# via -v/--version
|
192
|
+
def dispatch_info
|
193
|
+
your_version = Gem::Version.new(DbSucker::VERSION)
|
194
|
+
puts c ""
|
195
|
+
puts c(" Your version: ", :blue) << c("#{your_version}", :magenta)
|
196
|
+
|
197
|
+
print c(" Current version: ", :blue)
|
198
|
+
if @opts[:check_for_updates]
|
199
|
+
require "net/http"
|
200
|
+
print c("checking...", :blue)
|
201
|
+
|
202
|
+
begin
|
203
|
+
current_version = Gem::Version.new Net::HTTP.get_response(URI.parse(DbSucker::UPDATE_URL)).body.strip
|
204
|
+
|
205
|
+
if current_version > your_version
|
206
|
+
status = c("#{current_version} (consider update)", :red)
|
207
|
+
elsif current_version < your_version
|
208
|
+
status = c("#{current_version} (ahead, beta)", :green)
|
209
|
+
else
|
210
|
+
status = c("#{current_version} (up2date)", :green)
|
211
|
+
end
|
212
|
+
rescue
|
213
|
+
status = c("failed (#{$!.message})", :red)
|
214
|
+
end
|
215
|
+
|
216
|
+
print "#{"\b" * 11}#{" " * 11}#{"\b" * 11}" # reset line
|
217
|
+
puts status
|
218
|
+
else
|
219
|
+
puts c("check disabled", :red)
|
220
|
+
end
|
221
|
+
|
222
|
+
# more info
|
223
|
+
puts c ""
|
224
|
+
puts c " DbSucker is brought to you by #{c "bmonkeys.net", :green}"
|
225
|
+
puts c " Contribute @ #{c "github.com/2called-chaos/db_sucker", :cyan}"
|
226
|
+
puts c " Eat bananas every day!"
|
227
|
+
puts c ""
|
228
|
+
end
|
229
|
+
|
230
|
+
# via --generate
|
231
|
+
def dispatch_generate_config
|
232
|
+
cfg_name = @opts[:config_name] || "default"
|
233
|
+
cfg_file = "#{core_cfg_path}/#{cfg_name}.yml"
|
234
|
+
puts c("Generating container configuration file `#{cfg_name}'")
|
235
|
+
if File.exist?(cfg_file)
|
236
|
+
abort "Conflict, file already exists: #{cfg_file}", 1
|
237
|
+
else
|
238
|
+
puts c("Writing #{cfg_file}", :green)
|
239
|
+
FileUtils.mkdir_p(core_cfg_path)
|
240
|
+
FileUtils.cp("#{ROOT}/doc/container_example.yml", cfg_file)
|
241
|
+
editor = ENV["EDITOR"].presence
|
242
|
+
exec("#{editor.chomp} #{cfg_file}") if editor
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# helper for #dispatch_stat_tmp
|
247
|
+
def _dispatch_stat_tmp_display files, directories, managed, cleanup = false, sftp = false
|
248
|
+
log "Directories: #{c directories.count, :blue}"
|
249
|
+
log " Files: #{c files.count, :blue} #{c "("}#{c managed.count, :blue}#{c " managed)"}"
|
250
|
+
log " Size: #{c human_bytes(files.map(&:second).sum), :blue} #{c "("}#{c human_bytes(managed.map(&:second).sum), :blue} #{c "managed)"}"
|
251
|
+
|
252
|
+
if cleanup
|
253
|
+
if managed.any?
|
254
|
+
log c("WE ONLY SIMULATE! Nothing will be deleted!", :green) if opts[:simulate]
|
255
|
+
warning "----------- Removing #{managed.count} managed files! Press Ctrl-C to abort -----------"
|
256
|
+
managed.each{|f, s| warning " REMOVE #{c "#{f}", :magenta} #{c human_bytes(s), :cyan}" }
|
257
|
+
warning "----------- Removing #{managed.count} managed files! Press Ctrl-C to abort -----------"
|
258
|
+
sleep 3
|
259
|
+
4.times {|n| log "Cleaning up in #{3 - n}..." ; sleep 1 ; rll }
|
260
|
+
|
261
|
+
managed.each do |f, s|
|
262
|
+
if opts[:simulate]
|
263
|
+
warning "(simulate) Removing #{f}..."
|
264
|
+
else
|
265
|
+
warning "Removing #{f}..."
|
266
|
+
sftp ? sftp.remove!(f) : File.unlink(f)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
else
|
270
|
+
log c("No managed files found, nothing to cleanup.", :green)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# via --stat-tmp
|
276
|
+
def dispatch_stat_tmp cleanup = false
|
277
|
+
configful_dispatch(ARGV.shift, ARGV.shift) do |identifier, ctn, variation, var|
|
278
|
+
if ctn
|
279
|
+
ctn.sftp_begin do |sftp|
|
280
|
+
log c("Analyzing ") << c("remote", :cyan) << c(" temp directory #{c ctn.tmp_path, :magenta}")
|
281
|
+
begin
|
282
|
+
files = sftp.dir.glob("#{ctn.tmp_path}", "**/*")
|
283
|
+
rescue Net::SFTP::StatusException => ex
|
284
|
+
if ex.message["no such file"]
|
285
|
+
abort "Destination directory `#{ctn.tmp_path}' does not exist on the remote side!"
|
286
|
+
else
|
287
|
+
raise
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
d_files = files.select(&:file?).map{|f| ["#{ctn.tmp_path}/#{f.name}", f.attributes.size] }
|
292
|
+
d_directories = files.select(&:directory?).map{|f| ["#{ctn.tmp_path}/#{f.name}"] }
|
293
|
+
d_managed = files.select(&:file?).select{|f| f.name.end_with?(".dbsc", ".dbsc.tmp", ".dbsc.gz") }.map{|f| ["#{ctn.tmp_path}/#{f.name}", f.attributes.size] }
|
294
|
+
_dispatch_stat_tmp_display(d_files, d_directories, d_managed, cleanup, sftp)
|
295
|
+
end
|
296
|
+
else
|
297
|
+
log c("Analyzing ") << c("local", :cyan) << c(" temp directory #{c core_tmp_path, :magenta}")
|
298
|
+
files = Dir.glob("#{core_tmp_path}/**/*")
|
299
|
+
|
300
|
+
managed_files = files
|
301
|
+
d_files = files.select{|f| File.file?(f) }.map{|f| [f, File.size(f)] }
|
302
|
+
d_directories = files.select{|f| File.directory?(f) }
|
303
|
+
d_managed = files.select{|f| File.file?(f) && File.basename(f).end_with?(".dbsc", ".dbsc.tmp", ".dbsc.gz") }.map{|f| [f, File.size(f)] }
|
304
|
+
_dispatch_stat_tmp_display(d_files, d_directories, d_managed, cleanup)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# via --cleanup-tmp
|
310
|
+
def dispatch_cleanup_tmp
|
311
|
+
dispatch_stat_tmp(true)
|
312
|
+
end
|
313
|
+
|
314
|
+
# via -l/--list-databases
|
315
|
+
def _list_databases identifier, ctn, variation, var
|
316
|
+
return unless opts[:list_databases]
|
317
|
+
log "Listing databases for identifier #{c identifier, :magenta}#{c "..."}"
|
318
|
+
dbs = ctn.database_list(opts[:list_tables])
|
319
|
+
print_db_table_list ctn.hostname, dbs
|
320
|
+
throw :dispatch_handled
|
321
|
+
end
|
322
|
+
|
323
|
+
# via -t/--list-tables
|
324
|
+
def _list_tables identifier, ctn, variation, var
|
325
|
+
return if !(opts[:list_tables].present? && opts[:list_tables] != :all)
|
326
|
+
print_db_table_list ctn.hostname, [[opts[:list_tables], ctn.table_list(opts[:list_tables])]]
|
327
|
+
throw :dispatch_handled
|
328
|
+
end
|
329
|
+
|
330
|
+
# default action if variation given
|
331
|
+
def _suck_variation identifier, ctn, variation, var
|
332
|
+
if ctn && (var || (@opts[:suck_only].any? || @opts[:suck_except].any?))
|
333
|
+
var ||= ctn.variation("default")
|
334
|
+
begin
|
335
|
+
trap_signals
|
336
|
+
@sklaventreiber = SklavenTreiber.new(self, uniqid)
|
337
|
+
@sklaventreiber.spooled do
|
338
|
+
@sklaventreiber.whip_it!(ctn, var)
|
339
|
+
end
|
340
|
+
ensure
|
341
|
+
release_signals
|
342
|
+
end
|
343
|
+
throw :dispatch_handled
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# default action if no variation is given
|
348
|
+
def _default_listing identifier, ctn, variation, var
|
349
|
+
db_table_listing(ctn ? [[identifier, ctn]] : cfg)
|
350
|
+
end
|
351
|
+
|
352
|
+
# default actions in order
|
353
|
+
def dispatch_index
|
354
|
+
configful_dispatch(ARGV.shift, ARGV.shift) do |identifier, ctn, variation, var|
|
355
|
+
catch :dispatch_handled do
|
356
|
+
[:_list_databases, :_list_tables, :_suck_variation, :_default_listing].each do |meth|
|
357
|
+
__send__(meth, identifier, ctn, variation, var)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
end
|