-
Notifications
You must be signed in to change notification settings - Fork 0
/
pg_tools.rb
79 lines (67 loc) · 2.35 KB
/
pg_tools.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# pg_tools.rb
# from: https://www.michalmlozniak.com/notes/checking-postgres-availability-with-pure-ruby.html
#
# provides a way to use the PostgreSQL wire protocol to check a new instance
# of the RDBMS as it starts up. The idea is to wait for full db operations
# until the RDBMS has reached it startup completed state. This is important
# for automated tasks which spin up and shut down the RDBMS on demand.
#
require "digest"
require "socket"
module PgTools
class Status
def self.ready?(host:, port:, user:, password: nil, database: nil)
socket = TCPSocket.new(host, port)
socket.write(build_startup_message(user: user, database: database))
while true
char_tag = socket.read(1)
data = socket.read(4)
length = data.unpack("L>").first - 4
payload = socket.read(length)
case char_tag
when "E"
# Received **ErrorResponse**
break
when "R"
# Received **AuthenticationRequest** message
decoded = payload.unpack("L>").first
case decoded
when 3
# Cleartext password
packet = [112, password.size + 5, password].pack("CL>Z*")
socket.write(packet)
when 5
# MD5 password
salt = payload[4..-1]
hashed_pass = Digest::MD5.hexdigest([password, user].join)
encoded_password = "md5" + Digest::MD5.hexdigest([hashed_pass, salt].join)
socket.write([112, encoded_password.size + 5, encoded_password].pack("CL>Z*"))
end
when "Z"
# Received **Ready for query** message
return true
end
end
false
rescue Errno::ECONNREFUSED
false
end # def self.ready?(host:, port:, user:, password: nil, database: nil)
def self.build_startup_message(user:, database: nil)
message = [0, 196608]
message_size = 4 + 4
pack_string = "L>L>"
params = ["user", user]
params.concat(["database", database]) if database
params.each do |value|
message << value
message_size += value.size + 1
pack_string << "Z*"
end
message << 0
message_size += 1
pack_string << "C"
message[0] = message_size
message.pack(pack_string)
end # def self.build_startup_message(user:, database: nil)
end # class Status
end # module PgTools