hotswap 0.2.0 → 0.2.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 +4 -4
- data/lib/hotswap/database.rb +23 -0
- data/lib/hotswap/railtie.rb +4 -0
- data/lib/hotswap/socket_server.rb +10 -0
- data/lib/hotswap/version.rb +1 -1
- data/lib/hotswap.rb +6 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2313b39ed36d4fd0d236192de9c34f9ac7546984f8a076d474f035abddef947
|
|
4
|
+
data.tar.gz: a7458d108f6d2051a08c89aa3a8357c3cb72ccb892c7669b69d3deeaf1f5abe3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53ea80ca58d9f342fc04bc51385674ee5b5f8d3dd64a63a18869b5d4fdc81f8ac74c80a6901991d8709d65aeb565d19a5ca3cbeaa19e1a0408ab4ca9ce0dd154
|
|
7
|
+
data.tar.gz: dfbb24c4a439443db7e43d19f65b9964c86505eda6479fa6ec4ca75cba93b25cc619a6d845aa6ca16a822bc187c2f53f8cabdbf66d4ffae98dc9e93682324fbb
|
data/lib/hotswap/database.rb
CHANGED
|
@@ -12,6 +12,9 @@ module Hotswap
|
|
|
12
12
|
|
|
13
13
|
# Push a new database from an IO stream or file path
|
|
14
14
|
def push(source, stdout: $stdout, stderr: $stderr)
|
|
15
|
+
source_label = source.is_a?(String) ? source : "stdin"
|
|
16
|
+
logger.info "push started: #{source_label} → #{@path}"
|
|
17
|
+
|
|
15
18
|
input = source.is_a?(String) ? File.open(source, "rb") : source
|
|
16
19
|
|
|
17
20
|
dir = File.dirname(@path)
|
|
@@ -19,31 +22,39 @@ module Hotswap
|
|
|
19
22
|
begin
|
|
20
23
|
IO.copy_stream(input, temp)
|
|
21
24
|
temp.close
|
|
25
|
+
logger.info "received #{File.size(temp.path)} bytes, running integrity check"
|
|
22
26
|
|
|
23
27
|
db = SQLite3::Database.new(temp.path)
|
|
24
28
|
result = db.execute("PRAGMA integrity_check")
|
|
25
29
|
db.close
|
|
26
30
|
unless result == [["ok"]]
|
|
31
|
+
logger.error "integrity check failed for #{source_label}"
|
|
27
32
|
stderr.write("ERROR: integrity check failed\n")
|
|
28
33
|
return false
|
|
29
34
|
end
|
|
30
35
|
|
|
36
|
+
logger.info "integrity check passed, acquiring swap lock"
|
|
31
37
|
stderr.write("Swapping database...\n")
|
|
32
38
|
|
|
33
39
|
Middleware::SWAP_LOCK.synchronize do
|
|
34
40
|
if defined?(ActiveRecord::Base)
|
|
35
41
|
ActiveRecord::Base.connection_handler.clear_all_connections!
|
|
42
|
+
logger.info "disconnected ActiveRecord"
|
|
36
43
|
end
|
|
37
44
|
File.rename(temp.path, @path)
|
|
45
|
+
logger.info "renamed #{temp.path} → #{@path}"
|
|
38
46
|
end
|
|
39
47
|
|
|
40
48
|
if defined?(ActiveRecord::Base)
|
|
41
49
|
ActiveRecord::Base.establish_connection
|
|
50
|
+
logger.info "reconnected ActiveRecord"
|
|
42
51
|
end
|
|
43
52
|
|
|
53
|
+
logger.info "push complete: #{@path}"
|
|
44
54
|
stdout.write("OK\n")
|
|
45
55
|
true
|
|
46
56
|
rescue => e
|
|
57
|
+
logger.error "push failed: #{e.message}"
|
|
47
58
|
stderr.write("ERROR: #{e.message}\n")
|
|
48
59
|
false
|
|
49
60
|
ensure
|
|
@@ -54,7 +65,11 @@ module Hotswap
|
|
|
54
65
|
|
|
55
66
|
# Pull the database to an IO stream or file path
|
|
56
67
|
def pull(destination, stderr: $stderr)
|
|
68
|
+
dest_label = destination.is_a?(String) ? destination : "stdout"
|
|
69
|
+
logger.info "pull started: #{@path} → #{dest_label}"
|
|
70
|
+
|
|
57
71
|
unless File.exist?(@path)
|
|
72
|
+
logger.error "database file not found at #{@path}"
|
|
58
73
|
stderr.write("ERROR: database file not found at #{@path}\n")
|
|
59
74
|
return false
|
|
60
75
|
end
|
|
@@ -69,20 +84,28 @@ module Hotswap
|
|
|
69
84
|
b.finish
|
|
70
85
|
dst_db.close
|
|
71
86
|
src_db.close
|
|
87
|
+
logger.info "backup complete: #{File.size(temp.path)} bytes"
|
|
72
88
|
|
|
73
89
|
if destination.is_a?(String)
|
|
74
90
|
FileUtils.cp(temp.path, destination)
|
|
91
|
+
logger.info "pull complete: #{@path} → #{destination}"
|
|
75
92
|
stderr.write("OK\n")
|
|
76
93
|
else
|
|
77
94
|
File.open(temp.path, "rb") { |f| IO.copy_stream(f, destination) }
|
|
95
|
+
logger.info "pull complete: #{@path} → stdout"
|
|
78
96
|
end
|
|
79
97
|
true
|
|
80
98
|
rescue => e
|
|
99
|
+
logger.error "pull failed: #{e.message}"
|
|
81
100
|
stderr.write("ERROR: #{e.message}\n")
|
|
82
101
|
false
|
|
83
102
|
ensure
|
|
84
103
|
temp.unlink if temp && File.exist?(temp.path)
|
|
85
104
|
end
|
|
86
105
|
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def logger = Hotswap.logger
|
|
87
110
|
end
|
|
88
111
|
end
|
data/lib/hotswap/railtie.rb
CHANGED
|
@@ -2,6 +2,10 @@ module Hotswap
|
|
|
2
2
|
class Railtie < Rails::Railtie
|
|
3
3
|
config.hotswap = ActiveSupport::OrderedOptions.new
|
|
4
4
|
|
|
5
|
+
initializer "hotswap.logger" do
|
|
6
|
+
Hotswap.logger = Rails.logger
|
|
7
|
+
end
|
|
8
|
+
|
|
5
9
|
initializer "hotswap.configure" do |app|
|
|
6
10
|
# Discover all SQLite databases from Rails config
|
|
7
11
|
if app.config.hotswap.database_paths
|
|
@@ -27,10 +27,15 @@ module Hotswap
|
|
|
27
27
|
|
|
28
28
|
@thread = Thread.new { accept_loop }
|
|
29
29
|
@thread.report_on_exception = false
|
|
30
|
+
|
|
31
|
+
logger.info "listening on #{@socket_path}"
|
|
32
|
+
logger.info "stderr socket on #{@stderr_socket_path}"
|
|
33
|
+
logger.info "managing #{Hotswap.databases.size} database(s): #{Hotswap.databases.map(&:path).join(', ')}"
|
|
30
34
|
self
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
def stop
|
|
38
|
+
logger.info "shutting down"
|
|
34
39
|
@server&.close
|
|
35
40
|
@stderr_server&.close
|
|
36
41
|
@thread&.kill
|
|
@@ -45,6 +50,8 @@ module Hotswap
|
|
|
45
50
|
|
|
46
51
|
private
|
|
47
52
|
|
|
53
|
+
def logger = Hotswap.logger
|
|
54
|
+
|
|
48
55
|
def accept_loop
|
|
49
56
|
ios = [@server, @stderr_server]
|
|
50
57
|
loop do
|
|
@@ -75,6 +82,7 @@ module Hotswap
|
|
|
75
82
|
|
|
76
83
|
def handle_connection(socket)
|
|
77
84
|
unless IO.select([socket], nil, nil, CONNECTION_TIMEOUT)
|
|
85
|
+
logger.warn "connection timed out"
|
|
78
86
|
socket.write("ERROR: connection timeout\n") rescue nil
|
|
79
87
|
return
|
|
80
88
|
end
|
|
@@ -83,6 +91,7 @@ module Hotswap
|
|
|
83
91
|
return unless line
|
|
84
92
|
|
|
85
93
|
parts = Shellwords.split(line.strip)
|
|
94
|
+
logger.info "command: #{parts.join(' ')}" unless parts.empty?
|
|
86
95
|
|
|
87
96
|
# Grab the stderr socket if one is waiting
|
|
88
97
|
stderr_io = take_stderr_client
|
|
@@ -94,6 +103,7 @@ module Hotswap
|
|
|
94
103
|
stderr: stderr_io || $stderr
|
|
95
104
|
)
|
|
96
105
|
rescue => e
|
|
106
|
+
logger.error "connection error: #{e.message}"
|
|
97
107
|
socket.write("ERROR: #{e.message}\n") rescue nil
|
|
98
108
|
ensure
|
|
99
109
|
stderr_io&.close rescue nil
|
data/lib/hotswap/version.rb
CHANGED
data/lib/hotswap.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "logger"
|
|
1
2
|
require_relative "hotswap/version"
|
|
2
3
|
require_relative "hotswap/middleware"
|
|
3
4
|
require_relative "hotswap/database"
|
|
@@ -10,6 +11,11 @@ module Hotswap
|
|
|
10
11
|
|
|
11
12
|
class << self
|
|
12
13
|
attr_accessor :socket_path, :stderr_socket_path
|
|
14
|
+
attr_writer :logger
|
|
15
|
+
|
|
16
|
+
def logger
|
|
17
|
+
@logger ||= Logger.new($stdout, progname: "hotswap")
|
|
18
|
+
end
|
|
13
19
|
|
|
14
20
|
def configure
|
|
15
21
|
yield self
|