ey_cloud_server 1.5.0 → 1.5.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 +5 -13
- data/bin/eybackup +1 -0
- data/lib/ey_backup/engines/mysql_engine.rb +98 -12
- data/lib/ey_backup/engines/postgresql_engine.rb +17 -0
- data/lib/ey_cloud_server/version.rb +1 -1
- data/spec/ey_backup/mysql_engine_spec.rb +346 -0
- data/spec/ey_backup/postgresql_engine_spec.rb +188 -0
- metadata +37 -34
checksums.yaml
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
|
|
5
|
-
data.tar.gz: !binary |-
|
|
6
|
-
YTA0NTBmMTAxYzk5ZmE5YjQ0MzViYjE4NWE2YjRjZGM5YWYxZDNiNQ==
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bfb2b6fa4059f5c796ba0cedbb4784c6a564a22adf1cd91dcc8efac32d44a1f6
|
|
4
|
+
data.tar.gz: e2dab140765f7438ccb743c40169a186e23f6b72d5acd36d5be5aaf759ca50a0
|
|
7
5
|
SHA512:
|
|
8
|
-
metadata.gz:
|
|
9
|
-
|
|
10
|
-
NzM5MDc4OGZmNTMxNzM1OGI1ZTk4NDgxZjY5MTIwYmZmMTI1NmNjNjc0NmFl
|
|
11
|
-
OTI3OWFmMjdiZGE0N2QwZmEyNjljYzQ0NTg0MjBmZWI3NTBiOWU=
|
|
12
|
-
data.tar.gz: !binary |-
|
|
13
|
-
MTdlZDkwYmQ1ZmNiYTJmY2U0YWMzMmQ2YjZmNzA5NWEzYTVlNTA4ODM1ZGZj
|
|
14
|
-
ZTBkN2EwZmVlODczYTAxMjYwNzA3OTk3ODcxODlkOTQwZjYwZDNhNjE4Mjk2
|
|
15
|
-
YThjMTZiNjBmMmY3NTE5Yjg0NjI2YjRkOGQwMDg0YmQ4OTY3ZGM=
|
|
6
|
+
metadata.gz: e0f3941d55b673e5fbe7c3fea6e41ad72447e556cdfcaa7e29ea7226929042ea8e1806bc566bab07f245ad23302b4ad244c4aedde0780c1e3101cda41c4a39e3
|
|
7
|
+
data.tar.gz: 0f9a2d0689d4ba021eef298a7df7786abe731d13aa8aba68a59b9ea0e0e5d6e8dc568723ed97e886c07d9fd875513260d3a44950c93cd054b84e81e7c66348c1
|
data/bin/eybackup
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'shellwords'
|
|
2
|
+
|
|
1
3
|
module EY
|
|
2
4
|
module Backup
|
|
3
5
|
class MysqlEngine < Engine
|
|
@@ -45,10 +47,10 @@ module EY
|
|
|
45
47
|
|
|
46
48
|
def check_if_replica
|
|
47
49
|
err_msg="ERROR: Target host: '#{host}' is currently: "
|
|
48
|
-
err_msg=err_msg + "a replica
|
|
50
|
+
err_msg=err_msg + "a replica, " if db_replicating?
|
|
49
51
|
err_msg=err_msg + "in read_only mode, " if read_only_on?
|
|
50
52
|
err_msg=err_msg + "restores should be processed against the master."
|
|
51
|
-
|
|
53
|
+
|
|
52
54
|
EY::Backup.logger.fatal(%Q{#{err_msg}}) if db_replicating? or read_only_on?
|
|
53
55
|
end
|
|
54
56
|
|
|
@@ -90,10 +92,50 @@ module EY
|
|
|
90
92
|
end
|
|
91
93
|
|
|
92
94
|
def db_replicating?
|
|
93
|
-
|
|
94
|
-
stdout
|
|
95
|
+
statement = replica_status_statement
|
|
96
|
+
stdout = %x(mysql #{username_option} #{host_option} -BN -e #{Shellwords.escape(statement)})
|
|
97
|
+
probe_status = $?
|
|
98
|
+
|
|
99
|
+
unless probe_status.success?
|
|
100
|
+
# The replication-status probe itself failed -- e.g. the statement
|
|
101
|
+
# is unsupported on this server version, or a connection/permission
|
|
102
|
+
# problem. We cannot prove the host is NOT a replica, so fail
|
|
103
|
+
# closed and treat it as a possible replica rather than silently
|
|
104
|
+
# letting the restore proceed (this is how MySQL 8.4's removal of
|
|
105
|
+
# `SHOW SLAVE STATUS` used to defeat this check entirely).
|
|
106
|
+
warn("Unable to determine replication status on '#{host}' via #{statement.inspect}: #{stdout.strip}; treating host as a possible replica.")
|
|
107
|
+
return true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
!stdout.strip.empty?
|
|
95
111
|
end
|
|
96
|
-
|
|
112
|
+
|
|
113
|
+
# `SHOW SLAVE STATUS` was deprecated in MySQL 8.0.22 and removed in
|
|
114
|
+
# 8.4, replaced by `SHOW REPLICA STATUS`.
|
|
115
|
+
def replica_status_statement
|
|
116
|
+
mysql_8_4_or_newer? ? "show replica status" : "show slave status"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def mysql_8_4_or_newer?
|
|
120
|
+
stdout = %x(mysql #{username_option} #{host_option} -BN -e #{Shellwords.escape('select @@version')})
|
|
121
|
+
return false unless $?.success?
|
|
122
|
+
|
|
123
|
+
version = stdout.strip
|
|
124
|
+
# MariaDB keeps its own version numbering (10.x, 11.x) and retains
|
|
125
|
+
# `SHOW SLAVE STATUS` across all versions, so we must not compare
|
|
126
|
+
# its major.minor against MySQL's 8.4 boundary.
|
|
127
|
+
return false if version =~ /mariadb/i
|
|
128
|
+
|
|
129
|
+
# Anchor at the start of the string so we only inspect the leading
|
|
130
|
+
# version tuple (e.g. "8.4.11-11" → "8.4"), not any later digit-dot-digit
|
|
131
|
+
# pattern in suffixes or build metadata.
|
|
132
|
+
numeric = version[/\A\d+\.\d+/]
|
|
133
|
+
return false unless numeric
|
|
134
|
+
|
|
135
|
+
major, minor = numeric.split('.').map(&:to_i)
|
|
136
|
+
major > 8 || (major == 8 && minor >= 4)
|
|
137
|
+
end
|
|
138
|
+
|
|
97
139
|
def read_only_on?
|
|
98
140
|
stdout = %x{mysql #{username_option} #{host_option} -BN -e"select @@global.read_only"}
|
|
99
141
|
stdout.to_i == 1
|
|
@@ -105,13 +147,57 @@ module EY
|
|
|
105
147
|
end
|
|
106
148
|
|
|
107
149
|
def cycle_database(database_name)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
create_cmd
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
150
|
+
create_cmd = capture_create_database_statement(database_name)
|
|
151
|
+
|
|
152
|
+
unless create_cmd =~ /\ACREATE\s+DATABASE\b/i
|
|
153
|
+
raise "Refusing to restore '#{database_name}': could not obtain a valid CREATE DATABASE statement (got #{create_cmd.inspect}). The existing database has NOT been touched."
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
drop_database!(database_name)
|
|
157
|
+
recreate_database!(create_cmd, database_name)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def capture_create_database_statement(database_name)
|
|
161
|
+
query = "show create database `#{database_name}` \\G"
|
|
162
|
+
raw = %x(mysql #{username_option} #{host_option} -NB -e #{Shellwords.escape(query)})
|
|
163
|
+
capture_status = $?
|
|
164
|
+
|
|
165
|
+
# `-N` strips the field-name labels that `\G` would otherwise print,
|
|
166
|
+
# so the last non-blank line is the CREATE DATABASE statement text.
|
|
167
|
+
create_cmd = raw.to_s.split("\n").map(&:strip).reject(&:empty?).last.to_s
|
|
168
|
+
|
|
169
|
+
if create_cmd.empty? || !capture_status.success?
|
|
170
|
+
# No existing database to read from (e.g. this is the first-ever
|
|
171
|
+
# restore against a fresh target), or the probe itself failed.
|
|
172
|
+
# Fall back to a bare CREATE DATABASE so the restore can still
|
|
173
|
+
# proceed.
|
|
174
|
+
"CREATE DATABASE `#{database_name}`"
|
|
175
|
+
else
|
|
176
|
+
create_cmd
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def drop_database!(database_name)
|
|
181
|
+
%x(mysql #{username_option} #{host_option} -e #{Shellwords.escape("DROP DATABASE IF EXISTS `#{database_name}`")})
|
|
182
|
+
raise "Failed to drop database '#{database_name}' prior to restore; the existing database was left intact." unless $?.success?
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def recreate_database!(create_cmd, database_name)
|
|
186
|
+
# Feed the CREATE DATABASE statement to `mysql` on STDIN rather than
|
|
187
|
+
# interpolating it into a quoted shell `-e` argument. From MySQL
|
|
188
|
+
# 8.0.16 onward, `SHOW CREATE DATABASE` output can contain a
|
|
189
|
+
# single-quoted clause (e.g. `/*!80016 DEFAULT ENCRYPTION='N' */`)
|
|
190
|
+
# whose quote would otherwise terminate a single-quoted shell string
|
|
191
|
+
# and truncate the statement mid-SQL -- which is exactly what turned
|
|
192
|
+
# a syntax error into a dropped, never-recreated database.
|
|
193
|
+
IO.popen(['mysql', username_option, host_option], 'w') do |io|
|
|
194
|
+
io.write(create_cmd)
|
|
195
|
+
io.write(";\n")
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
unless $?.success?
|
|
199
|
+
raise "Failed to recreate database '#{database_name}' after DROP -- the database has been dropped and NOT recreated. Statement attempted: #{create_cmd.inspect}"
|
|
200
|
+
end
|
|
115
201
|
end
|
|
116
202
|
|
|
117
203
|
def suffix
|
|
@@ -127,10 +127,27 @@ module EY
|
|
|
127
127
|
create_cmd = create_command(database_name).chomp
|
|
128
128
|
if create_cmd == ''
|
|
129
129
|
create_database(database_name)
|
|
130
|
+
unless $?.success?
|
|
131
|
+
raise "Failed to create database '#{database_name}' before restore."
|
|
132
|
+
end
|
|
130
133
|
else
|
|
131
134
|
check_connections(database_name)
|
|
132
135
|
drop_database(database_name)
|
|
136
|
+
unless $?.success?
|
|
137
|
+
raise "Failed to drop database '#{database_name}' prior to restore; the existing database was left intact."
|
|
138
|
+
end
|
|
139
|
+
# Note: create_cmd is embedded inside a double-quoted shell argument
|
|
140
|
+
# rather than single-quoted, so the single-quote chars that appear
|
|
141
|
+
# in PostgreSQL CREATE DATABASE output (e.g. ENCODING 'UTF8') are
|
|
142
|
+
# not shell-special within double quotes and do not cause the
|
|
143
|
+
# statement-termination break seen in the MySQL path. The risk class
|
|
144
|
+
# is different but we still check the exit status so that a failed
|
|
145
|
+
# create (permissions, malformed content, etc.) is caught before
|
|
146
|
+
# the missing database causes a load failure.
|
|
133
147
|
%x{PGPASSWORD='#{password}' psql -U#{username} -h #{host} -t postgres -c "#{create_cmd}"}
|
|
148
|
+
unless $?.success?
|
|
149
|
+
raise "Failed to recreate database '#{database_name}' after DROP -- the database has been dropped and NOT recreated."
|
|
150
|
+
end
|
|
134
151
|
end
|
|
135
152
|
end
|
|
136
153
|
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Unit tests for the two defects fixed in mysql_engine.rb (ey-all#204):
|
|
2
|
+
#
|
|
3
|
+
# A — cycle_database drops the database and never recreates it when MySQL
|
|
4
|
+
# 8.0.16+ injects a `DEFAULT ENCRYPTION='N'` single-quoted clause into
|
|
5
|
+
# `SHOW CREATE DATABASE` output, which broke out of the original
|
|
6
|
+
# single-quoted `-e` shell argument and truncated the SQL mid-statement.
|
|
7
|
+
#
|
|
8
|
+
# B — db_replicating? silently returned false on MySQL 8.4+ because
|
|
9
|
+
# `SHOW SLAVE STATUS` was removed there; it must fail closed (return
|
|
10
|
+
# true) whenever the probe itself errors, on any MySQL version.
|
|
11
|
+
#
|
|
12
|
+
# (Defect C — eybackup exiting 0 after a failed restore — is a one-line fix
|
|
13
|
+
# in bin/eybackup's top-level rescue block; the CLI entrypoint requires the
|
|
14
|
+
# full ey-flex / ey_enzyme dependency chain from the private NextGem gem
|
|
15
|
+
# server, which isn't available in this environment, so it isn't covered by
|
|
16
|
+
# an executable spec here. See the PR description.)
|
|
17
|
+
#
|
|
18
|
+
# Strategy: rather than re-implementing the engine's logic inline (which
|
|
19
|
+
# would test the test, not the code), these specs put a small fake `mysql`
|
|
20
|
+
# executable on PATH and let the REAL, unmodified engine methods run against
|
|
21
|
+
# it. The fake script's behavior (output, exit status) is controlled per
|
|
22
|
+
# example via environment variables, and it echoes back what it was given so
|
|
23
|
+
# we can assert on exactly what the engine sent it (in particular: whether
|
|
24
|
+
# the single-quote-containing CREATE DATABASE statement survives intact).
|
|
25
|
+
#
|
|
26
|
+
# This file intentionally does NOT require lib/ey_backup.rb (which pulls in
|
|
27
|
+
# ey-flex / ey_enzyme from NextGem); it loads only the engine class and its
|
|
28
|
+
# stdlib dependencies, so it runs on any modern Ruby + rspec (verified here
|
|
29
|
+
# under Ruby 3 / rspec 3 via Docker, since the project's pinned Ruby 1.9.3
|
|
30
|
+
# cannot run on this host). It therefore uses modern `expect`/`allow` syntax
|
|
31
|
+
# rather than the legacy suite's `.should`/`stub`, since it is not part of
|
|
32
|
+
# the (currently non-executable-in-this-environment) bundled spec run.
|
|
33
|
+
|
|
34
|
+
require 'forwardable'
|
|
35
|
+
require 'stringio'
|
|
36
|
+
require 'shellwords'
|
|
37
|
+
require 'fileutils'
|
|
38
|
+
require 'tmpdir'
|
|
39
|
+
|
|
40
|
+
# ---- Minimal EY::Backup bootstrap (avoids requiring ey-flex) -------------
|
|
41
|
+
|
|
42
|
+
module EY
|
|
43
|
+
module Backup
|
|
44
|
+
class << self
|
|
45
|
+
attr_accessor :logger, :tmp_dir, :log_dir
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Logger
|
|
49
|
+
extend Forwardable
|
|
50
|
+
|
|
51
|
+
attr_reader :stdout, :stderr
|
|
52
|
+
|
|
53
|
+
alias_method :fatal, :abort
|
|
54
|
+
public :fatal
|
|
55
|
+
|
|
56
|
+
def_delegator :stdout, :puts, :puts
|
|
57
|
+
|
|
58
|
+
def initialize(stdout = $stdout, stderr = $stderr)
|
|
59
|
+
@stdout, @stderr = stdout, stderr
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def info(msg); stdout.puts("#{Time.now} #{msg}"); end
|
|
63
|
+
def warn(msg, db = nil); stdout.puts("#{Time.now} WARNING: #{msg}"); end
|
|
64
|
+
def error(msg, db = nil); stdout.puts("#{Time.now} ERROR: #{msg}"); end
|
|
65
|
+
def verbose(msg); end
|
|
66
|
+
def debug(msg); stdout.puts("#{Time.now} DEBUG: #{msg}"); end
|
|
67
|
+
def say(msg, newline = true); newline ? info(msg) : stdout.print(msg); end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
module Logging
|
|
71
|
+
extend Forwardable
|
|
72
|
+
def_delegator EY::Backup, :logger
|
|
73
|
+
def_delegators :logger, :fatal, :error, :warn, :info, :verbose, :debug, :say
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
EY::Backup.logger = EY::Backup::Logger.new(StringIO.new, StringIO.new)
|
|
79
|
+
|
|
80
|
+
lib_dir = File.expand_path(File.dirname(__FILE__) + '/../../lib/ey_backup')
|
|
81
|
+
require lib_dir + '/spawner'
|
|
82
|
+
require lib_dir + '/base'
|
|
83
|
+
require lib_dir + '/engine'
|
|
84
|
+
require lib_dir + '/engines/mysql_engine'
|
|
85
|
+
|
|
86
|
+
# ---- Fake `mysql` binary --------------------------------------------------
|
|
87
|
+
#
|
|
88
|
+
# Routes on the `-e` query text (or, absent `-e`, treats the invocation as
|
|
89
|
+
# stdin-fed — exactly what recreate_database! does). Controlled entirely via
|
|
90
|
+
# environment variables so each example can script a distinct scenario.
|
|
91
|
+
|
|
92
|
+
FAKE_MYSQL_SCRIPT = <<~'SCRIPT'
|
|
93
|
+
#!/usr/bin/env bash
|
|
94
|
+
if [ -n "$FAKE_MYSQL_CALL_LOG" ]; then
|
|
95
|
+
printf '%s\n' "$*" >> "$FAKE_MYSQL_CALL_LOG"
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
query=""
|
|
99
|
+
has_e=0
|
|
100
|
+
for arg in "$@"; do
|
|
101
|
+
if [ "$has_e" = "1" ]; then
|
|
102
|
+
query="$arg"
|
|
103
|
+
break
|
|
104
|
+
fi
|
|
105
|
+
if [ "$arg" = "-e" ]; then
|
|
106
|
+
has_e=1
|
|
107
|
+
fi
|
|
108
|
+
done
|
|
109
|
+
|
|
110
|
+
if [ "$has_e" = "0" ]; then
|
|
111
|
+
# No -e flag => stdin-fed invocation (recreate_database!).
|
|
112
|
+
if [ -n "$FAKE_MYSQL_STDIN_CAPTURE_FILE" ]; then
|
|
113
|
+
cat > "$FAKE_MYSQL_STDIN_CAPTURE_FILE"
|
|
114
|
+
else
|
|
115
|
+
cat > /dev/null
|
|
116
|
+
fi
|
|
117
|
+
exit "${FAKE_MYSQL_STDIN_EXIT:-0}"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
case "$query" in
|
|
121
|
+
"select @@version")
|
|
122
|
+
printf '%s' "$FAKE_MYSQL_VERSION"
|
|
123
|
+
exit "${FAKE_MYSQL_VERSION_EXIT:-0}"
|
|
124
|
+
;;
|
|
125
|
+
"show replica status")
|
|
126
|
+
printf '%s' "$FAKE_MYSQL_REPLICA_OUTPUT"
|
|
127
|
+
exit "${FAKE_MYSQL_REPLICA_EXIT:-0}"
|
|
128
|
+
;;
|
|
129
|
+
"show slave status")
|
|
130
|
+
printf '%s' "$FAKE_MYSQL_SLAVE_OUTPUT"
|
|
131
|
+
exit "${FAKE_MYSQL_SLAVE_EXIT:-0}"
|
|
132
|
+
;;
|
|
133
|
+
show\ create\ database*)
|
|
134
|
+
printf '%s' "$FAKE_MYSQL_CREATE_OUTPUT"
|
|
135
|
+
exit "${FAKE_MYSQL_CREATE_EXIT:-0}"
|
|
136
|
+
;;
|
|
137
|
+
DROP\ DATABASE*)
|
|
138
|
+
exit "${FAKE_MYSQL_DROP_EXIT:-0}"
|
|
139
|
+
;;
|
|
140
|
+
*)
|
|
141
|
+
exit 0
|
|
142
|
+
;;
|
|
143
|
+
esac
|
|
144
|
+
SCRIPT
|
|
145
|
+
|
|
146
|
+
RSpec.describe EY::Backup::MysqlEngine do
|
|
147
|
+
around(:each) do |example|
|
|
148
|
+
@fake_bin_dir = Dir.mktmpdir('fake-mysql-bin')
|
|
149
|
+
script_path = File.join(@fake_bin_dir, 'mysql')
|
|
150
|
+
File.write(script_path, FAKE_MYSQL_SCRIPT)
|
|
151
|
+
FileUtils.chmod('+x', script_path)
|
|
152
|
+
|
|
153
|
+
@call_log = File.join(@fake_bin_dir, 'calls.log')
|
|
154
|
+
ENV['FAKE_MYSQL_CALL_LOG'] = @call_log
|
|
155
|
+
|
|
156
|
+
original_path = ENV['PATH']
|
|
157
|
+
ENV['PATH'] = "#{@fake_bin_dir}:#{original_path}"
|
|
158
|
+
begin
|
|
159
|
+
example.run
|
|
160
|
+
ensure
|
|
161
|
+
ENV['PATH'] = original_path
|
|
162
|
+
%w[
|
|
163
|
+
FAKE_MYSQL_CALL_LOG FAKE_MYSQL_VERSION FAKE_MYSQL_VERSION_EXIT
|
|
164
|
+
FAKE_MYSQL_REPLICA_OUTPUT FAKE_MYSQL_REPLICA_EXIT
|
|
165
|
+
FAKE_MYSQL_SLAVE_OUTPUT FAKE_MYSQL_SLAVE_EXIT
|
|
166
|
+
FAKE_MYSQL_CREATE_OUTPUT FAKE_MYSQL_CREATE_EXIT
|
|
167
|
+
FAKE_MYSQL_DROP_EXIT FAKE_MYSQL_STDIN_EXIT
|
|
168
|
+
FAKE_MYSQL_STDIN_CAPTURE_FILE
|
|
169
|
+
].each { |k| ENV.delete(k) }
|
|
170
|
+
FileUtils.remove_entry(@fake_bin_dir)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
let(:engine) do
|
|
175
|
+
described_class.new('root', '', 'localhost', nil, true, false, false, false)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def call_log
|
|
179
|
+
File.exist?(@call_log) ? File.readlines(@call_log) : []
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# =========================================================================
|
|
183
|
+
# B — replication-status detection on MySQL 8.4+ (and fail-closed on error)
|
|
184
|
+
# =========================================================================
|
|
185
|
+
|
|
186
|
+
describe '#db_replicating?' do
|
|
187
|
+
context 'on MySQL 8.4+, host is a replica' do
|
|
188
|
+
it 'uses SHOW REPLICA STATUS and returns true' do
|
|
189
|
+
ENV['FAKE_MYSQL_VERSION'] = '8.4.11-11'
|
|
190
|
+
ENV['FAKE_MYSQL_REPLICA_OUTPUT'] = "Source_Host\treplica-source\tSource_Port\t3306\n"
|
|
191
|
+
ENV['FAKE_MYSQL_REPLICA_EXIT'] = '0'
|
|
192
|
+
|
|
193
|
+
expect(engine.db_replicating?).to eq(true)
|
|
194
|
+
expect(call_log.any? { |l| l.include?('show replica status') }).to eq(true)
|
|
195
|
+
expect(call_log.any? { |l| l.include?('show slave status') }).to eq(false)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
context 'on MySQL 8.4+, host is NOT a replica (plain master)' do
|
|
200
|
+
it 'uses SHOW REPLICA STATUS and returns false' do
|
|
201
|
+
ENV['FAKE_MYSQL_VERSION'] = '8.4.11-11'
|
|
202
|
+
ENV['FAKE_MYSQL_REPLICA_OUTPUT'] = ''
|
|
203
|
+
ENV['FAKE_MYSQL_REPLICA_EXIT'] = '0'
|
|
204
|
+
|
|
205
|
+
expect(engine.db_replicating?).to eq(false)
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
context 'on MySQL 8.0 (below the 8.4 cutover), host is a replica' do
|
|
210
|
+
it 'uses SHOW SLAVE STATUS and returns true' do
|
|
211
|
+
ENV['FAKE_MYSQL_VERSION'] = '8.0.16'
|
|
212
|
+
ENV['FAKE_MYSQL_SLAVE_OUTPUT'] = "Slave_IO_State\tWaiting\n"
|
|
213
|
+
ENV['FAKE_MYSQL_SLAVE_EXIT'] = '0'
|
|
214
|
+
|
|
215
|
+
expect(engine.db_replicating?).to eq(true)
|
|
216
|
+
expect(call_log.any? { |l| l.include?('show slave status') }).to eq(true)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
context 'the pre-fix bug scenario: MySQL 8.4 but the probe uses the removed statement' do
|
|
221
|
+
it 'fails closed (treats as a possible replica) rather than silently returning false' do
|
|
222
|
+
ENV['FAKE_MYSQL_VERSION'] = '8.4.11-11'
|
|
223
|
+
# Simulate SHOW REPLICA STATUS itself erroring (e.g. permissions, or
|
|
224
|
+
# some future removal) -- the probe fails outright.
|
|
225
|
+
ENV['FAKE_MYSQL_REPLICA_OUTPUT'] = "ERROR 1064 (42000): syntax error"
|
|
226
|
+
ENV['FAKE_MYSQL_REPLICA_EXIT'] = '1'
|
|
227
|
+
|
|
228
|
+
expect(engine.db_replicating?).to eq(true)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
context 'probe fails for a connectivity reason' do
|
|
233
|
+
it 'fails closed (treats as a possible replica)' do
|
|
234
|
+
ENV['FAKE_MYSQL_VERSION'] = '8.4.0'
|
|
235
|
+
ENV['FAKE_MYSQL_REPLICA_OUTPUT'] = "ERROR 2003 (HY000): Can't connect"
|
|
236
|
+
ENV['FAKE_MYSQL_REPLICA_EXIT'] = '1'
|
|
237
|
+
|
|
238
|
+
expect(engine.db_replicating?).to eq(true)
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
describe '#mysql_8_4_or_newer?' do
|
|
244
|
+
[
|
|
245
|
+
['8.4.0', true],
|
|
246
|
+
['8.4.11-11', true],
|
|
247
|
+
['9.0.0', true],
|
|
248
|
+
['8.3.9', false],
|
|
249
|
+
['8.0.16', false],
|
|
250
|
+
['5.7.36', false],
|
|
251
|
+
['10.6.12-MariaDB', false],
|
|
252
|
+
].each do |version, expected|
|
|
253
|
+
it "returns #{expected} for server version #{version}" do
|
|
254
|
+
ENV['FAKE_MYSQL_VERSION'] = version
|
|
255
|
+
ENV['FAKE_MYSQL_VERSION_EXIT'] = '0'
|
|
256
|
+
expect(engine.mysql_8_4_or_newer?).to eq(expected)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
it 'returns false (safe default) when the version probe itself fails' do
|
|
261
|
+
ENV['FAKE_MYSQL_VERSION'] = ''
|
|
262
|
+
ENV['FAKE_MYSQL_VERSION_EXIT'] = '1'
|
|
263
|
+
expect(engine.mysql_8_4_or_newer?).to eq(false)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# =========================================================================
|
|
268
|
+
# A — cycle_database no longer destroys the database on a quoting break
|
|
269
|
+
# =========================================================================
|
|
270
|
+
|
|
271
|
+
describe '#capture_create_database_statement' do
|
|
272
|
+
it 'returns the SHOW CREATE DATABASE output verbatim, including an embedded single quote' do
|
|
273
|
+
stmt = "CREATE DATABASE `myapp` /*!40100 DEFAULT CHARACTER SET utf8mb3 */ /*!80016 DEFAULT ENCRYPTION='N' */"
|
|
274
|
+
ENV['FAKE_MYSQL_CREATE_OUTPUT'] = stmt
|
|
275
|
+
ENV['FAKE_MYSQL_CREATE_EXIT'] = '0'
|
|
276
|
+
|
|
277
|
+
result = engine.capture_create_database_statement('myapp')
|
|
278
|
+
expect(result).to eq(stmt)
|
|
279
|
+
expect(result).to include("ENCRYPTION='N'")
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
it 'falls back to a bare CREATE DATABASE when the probe fails (fresh target, no existing db)' do
|
|
283
|
+
ENV['FAKE_MYSQL_CREATE_OUTPUT'] = ''
|
|
284
|
+
ENV['FAKE_MYSQL_CREATE_EXIT'] = '1'
|
|
285
|
+
|
|
286
|
+
expect(engine.capture_create_database_statement('newdb')).to eq('CREATE DATABASE `newdb`')
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
it 'falls back to a bare CREATE DATABASE when the probe succeeds but returns nothing' do
|
|
290
|
+
ENV['FAKE_MYSQL_CREATE_OUTPUT'] = ''
|
|
291
|
+
ENV['FAKE_MYSQL_CREATE_EXIT'] = '0'
|
|
292
|
+
|
|
293
|
+
expect(engine.capture_create_database_statement('newdb')).to eq('CREATE DATABASE `newdb`')
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
describe '#cycle_database' do
|
|
298
|
+
it 'drops then recreates, delivering the FULL statement (with embedded quote) to mysql via stdin intact' do
|
|
299
|
+
stmt = "CREATE DATABASE `myapp` /*!80016 DEFAULT ENCRYPTION='N' */"
|
|
300
|
+
ENV['FAKE_MYSQL_CREATE_OUTPUT'] = stmt
|
|
301
|
+
ENV['FAKE_MYSQL_CREATE_EXIT'] = '0'
|
|
302
|
+
ENV['FAKE_MYSQL_DROP_EXIT'] = '0'
|
|
303
|
+
ENV['FAKE_MYSQL_STDIN_EXIT'] = '0'
|
|
304
|
+
stdin_capture = File.join(@fake_bin_dir, 'stdin_capture.sql')
|
|
305
|
+
ENV['FAKE_MYSQL_STDIN_CAPTURE_FILE'] = stdin_capture
|
|
306
|
+
|
|
307
|
+
expect { engine.cycle_database('myapp') }.not_to raise_error
|
|
308
|
+
|
|
309
|
+
# The database WAS dropped...
|
|
310
|
+
expect(call_log.any? { |l| l.start_with?('DROP DATABASE') || l.include?('DROP DATABASE') }).to eq(true)
|
|
311
|
+
# ...and the recreate step received the statement UNTRUNCATED, quote and all.
|
|
312
|
+
# This is the crux of the fix: under the old `-e '#{create_cmd}'` code,
|
|
313
|
+
# this content would never have reached mysql intact (the shell would
|
|
314
|
+
# have truncated it at the embedded quote).
|
|
315
|
+
received = File.read(stdin_capture)
|
|
316
|
+
expect(received).to include(stmt)
|
|
317
|
+
expect(received).to include("ENCRYPTION='N'")
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
it 'never drops the database if the captured statement is not a valid CREATE DATABASE' do
|
|
321
|
+
ENV['FAKE_MYSQL_CREATE_OUTPUT'] = 'garbage: mysql client misconfiguration warning text'
|
|
322
|
+
ENV['FAKE_MYSQL_CREATE_EXIT'] = '0'
|
|
323
|
+
ENV['FAKE_MYSQL_DROP_EXIT'] = '0'
|
|
324
|
+
|
|
325
|
+
expect { engine.cycle_database('myapp') }.to raise_error(/Refusing to restore/)
|
|
326
|
+
expect(call_log.any? { |l| l.include?('DROP DATABASE') }).to eq(false)
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
it 'raises (does not swallow the failure) if DROP DATABASE itself fails' do
|
|
330
|
+
ENV['FAKE_MYSQL_CREATE_OUTPUT'] = 'CREATE DATABASE `myapp`'
|
|
331
|
+
ENV['FAKE_MYSQL_CREATE_EXIT'] = '0'
|
|
332
|
+
ENV['FAKE_MYSQL_DROP_EXIT'] = '1'
|
|
333
|
+
|
|
334
|
+
expect { engine.cycle_database('myapp') }.to raise_error(/Failed to drop database/)
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
it 'raises clearly (dropped-but-not-recreated) if the recreate step fails after a successful DROP' do
|
|
338
|
+
ENV['FAKE_MYSQL_CREATE_OUTPUT'] = 'CREATE DATABASE `myapp`'
|
|
339
|
+
ENV['FAKE_MYSQL_CREATE_EXIT'] = '0'
|
|
340
|
+
ENV['FAKE_MYSQL_DROP_EXIT'] = '0'
|
|
341
|
+
ENV['FAKE_MYSQL_STDIN_EXIT'] = '1'
|
|
342
|
+
|
|
343
|
+
expect { engine.cycle_database('myapp') }.to raise_error(/dropped and NOT recreated/)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Regression coverage for postgresql_engine.rb's #cycle_database, added
|
|
2
|
+
# alongside the ey-all#204 MySQL fix (see mysql_engine_spec.rb).
|
|
3
|
+
#
|
|
4
|
+
# postgresql_engine.rb's #cycle_database was audited for the same shell-
|
|
5
|
+
# quoting break as MySQL's (defect A): it also captures a statement
|
|
6
|
+
# (create_command) and interpolates it into a shell -c argument. It is NOT
|
|
7
|
+
# vulnerable to the same trigger, because the captured statement is embedded
|
|
8
|
+
# in a DOUBLE-quoted shell argument while its own internal quoting uses
|
|
9
|
+
# single quotes (`ENCODING 'UTF8'`) -- single quotes are not shell-special
|
|
10
|
+
# inside double quotes, so they cannot terminate the argument early the way
|
|
11
|
+
# they did for MySQL's single-quoted `-e` argument. See the PR description
|
|
12
|
+
# for the full analysis.
|
|
13
|
+
#
|
|
14
|
+
# It DID share the other half of defect A's pattern, though: neither
|
|
15
|
+
# drop_database nor the final recreate call checked $? at all, so a failed
|
|
16
|
+
# recreate after a successful drop would silently leave the database
|
|
17
|
+
# dropped. These specs cover the exit-status hardening added for that.
|
|
18
|
+
#
|
|
19
|
+
# Same loading strategy as mysql_engine_spec.rb: avoids lib/ey_backup.rb's
|
|
20
|
+
# ey-flex/NextGem dependency chain, uses a fake `psql`/`dropdb`/`createdb`
|
|
21
|
+
# on PATH instead of a real PostgreSQL server, and uses modern rspec syntax.
|
|
22
|
+
|
|
23
|
+
require 'forwardable'
|
|
24
|
+
require 'stringio'
|
|
25
|
+
require 'fileutils'
|
|
26
|
+
require 'tmpdir'
|
|
27
|
+
|
|
28
|
+
module EY
|
|
29
|
+
module Backup
|
|
30
|
+
class << self
|
|
31
|
+
attr_accessor :logger, :tmp_dir, :log_dir
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class Logger
|
|
35
|
+
extend Forwardable
|
|
36
|
+
attr_reader :stdout, :stderr
|
|
37
|
+
alias_method :fatal, :abort
|
|
38
|
+
public :fatal
|
|
39
|
+
def_delegator :stdout, :puts, :puts
|
|
40
|
+
|
|
41
|
+
def initialize(stdout = $stdout, stderr = $stderr)
|
|
42
|
+
@stdout, @stderr = stdout, stderr
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def info(msg); stdout.puts("#{Time.now} #{msg}"); end
|
|
46
|
+
def warn(msg, db = nil); stdout.puts("#{Time.now} WARNING: #{msg}"); end
|
|
47
|
+
def error(msg, db = nil); stdout.puts("#{Time.now} ERROR: #{msg}"); end
|
|
48
|
+
def verbose(msg); end
|
|
49
|
+
def debug(msg); stdout.puts("#{Time.now} DEBUG: #{msg}"); end
|
|
50
|
+
def say(msg, newline = true); newline ? info(msg) : stdout.print(msg); end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module Logging
|
|
54
|
+
extend Forwardable
|
|
55
|
+
def_delegator EY::Backup, :logger
|
|
56
|
+
def_delegators :logger, :fatal, :error, :warn, :info, :verbose, :debug, :say
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
EY::Backup.logger = EY::Backup::Logger.new(StringIO.new, StringIO.new)
|
|
62
|
+
|
|
63
|
+
lib_dir = File.expand_path(File.dirname(__FILE__) + '/../../lib/ey_backup')
|
|
64
|
+
require lib_dir + '/spawner'
|
|
65
|
+
require lib_dir + '/base'
|
|
66
|
+
require lib_dir + '/engine'
|
|
67
|
+
require lib_dir + '/engines/postgresql_engine'
|
|
68
|
+
|
|
69
|
+
# ---- Fake psql / dropdb / createdb ---------------------------------------
|
|
70
|
+
|
|
71
|
+
FAKE_PSQL_SCRIPT = <<~'SCRIPT'
|
|
72
|
+
#!/usr/bin/env bash
|
|
73
|
+
# Route by the -c argument content:
|
|
74
|
+
# - contains "SELECT 'CREATE DATABASE" => the create_command probe
|
|
75
|
+
# - contains "select count(*)" => check_connections probe
|
|
76
|
+
# - otherwise => the final recreate DDL execution
|
|
77
|
+
cflag=""
|
|
78
|
+
has_c=0
|
|
79
|
+
for arg in "$@"; do
|
|
80
|
+
if [ "$has_c" = "1" ]; then
|
|
81
|
+
cflag="$arg"
|
|
82
|
+
break
|
|
83
|
+
fi
|
|
84
|
+
if [ "$arg" = "-c" ]; then
|
|
85
|
+
has_c=1
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
|
|
89
|
+
case "$cflag" in
|
|
90
|
+
*"select count(*)"*)
|
|
91
|
+
printf '%s' "${FAKE_PSQL_CONN_COUNT:-0}"
|
|
92
|
+
exit 0
|
|
93
|
+
;;
|
|
94
|
+
*"SELECT 'CREATE DATABASE"*)
|
|
95
|
+
printf '%s' "$FAKE_PSQL_CREATE_COMMAND_OUTPUT"
|
|
96
|
+
exit "${FAKE_PSQL_CREATE_COMMAND_EXIT:-0}"
|
|
97
|
+
;;
|
|
98
|
+
*)
|
|
99
|
+
# Final CREATE DATABASE execution
|
|
100
|
+
exit "${FAKE_PSQL_RECREATE_EXIT:-0}"
|
|
101
|
+
;;
|
|
102
|
+
esac
|
|
103
|
+
SCRIPT
|
|
104
|
+
|
|
105
|
+
FAKE_DROPDB_SCRIPT = <<~'SCRIPT'
|
|
106
|
+
#!/usr/bin/env bash
|
|
107
|
+
exit "${FAKE_DROPDB_EXIT:-0}"
|
|
108
|
+
SCRIPT
|
|
109
|
+
|
|
110
|
+
FAKE_CREATEDB_SCRIPT = <<~'SCRIPT'
|
|
111
|
+
#!/usr/bin/env bash
|
|
112
|
+
exit "${FAKE_CREATEDB_EXIT:-0}"
|
|
113
|
+
SCRIPT
|
|
114
|
+
|
|
115
|
+
RSpec.describe EY::Backup::Postgresql do
|
|
116
|
+
around(:each) do |example|
|
|
117
|
+
@fake_bin_dir = Dir.mktmpdir('fake-psql-bin')
|
|
118
|
+
{ 'psql' => FAKE_PSQL_SCRIPT, 'dropdb' => FAKE_DROPDB_SCRIPT, 'createdb' => FAKE_CREATEDB_SCRIPT }.each do |name, script|
|
|
119
|
+
path = File.join(@fake_bin_dir, name)
|
|
120
|
+
File.write(path, script)
|
|
121
|
+
FileUtils.chmod('+x', path)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
original_path = ENV['PATH']
|
|
125
|
+
ENV['PATH'] = "#{@fake_bin_dir}:#{original_path}"
|
|
126
|
+
begin
|
|
127
|
+
example.run
|
|
128
|
+
ensure
|
|
129
|
+
ENV['PATH'] = original_path
|
|
130
|
+
%w[
|
|
131
|
+
FAKE_PSQL_CONN_COUNT FAKE_PSQL_CREATE_COMMAND_OUTPUT
|
|
132
|
+
FAKE_PSQL_CREATE_COMMAND_EXIT FAKE_PSQL_RECREATE_EXIT
|
|
133
|
+
FAKE_DROPDB_EXIT FAKE_CREATEDB_EXIT
|
|
134
|
+
].each { |k| ENV.delete(k) }
|
|
135
|
+
FileUtils.remove_entry(@fake_bin_dir)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
let(:engine) do
|
|
140
|
+
described_class.new('postgres', 'pw', 'localhost', nil, true, false, false, false)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
describe '#cycle_database' do
|
|
144
|
+
context 'database does not yet exist (create_command returns empty)' do
|
|
145
|
+
it 'creates it and does not raise when createdb succeeds' do
|
|
146
|
+
ENV['FAKE_PSQL_CREATE_COMMAND_OUTPUT'] = ''
|
|
147
|
+
ENV['FAKE_CREATEDB_EXIT'] = '0'
|
|
148
|
+
|
|
149
|
+
expect { engine.cycle_database('newdb') }.not_to raise_error
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
it 'raises if createdb fails' do
|
|
153
|
+
ENV['FAKE_PSQL_CREATE_COMMAND_OUTPUT'] = ''
|
|
154
|
+
ENV['FAKE_CREATEDB_EXIT'] = '1'
|
|
155
|
+
|
|
156
|
+
expect { engine.cycle_database('newdb') }.to raise_error(/Failed to create database/)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
context 'database exists (normal restore path)' do
|
|
161
|
+
it 'drops then recreates without raising on success' do
|
|
162
|
+
ENV['FAKE_PSQL_CONN_COUNT'] = '0'
|
|
163
|
+
ENV['FAKE_PSQL_CREATE_COMMAND_OUTPUT'] = "CREATE DATABASE myapp WITH OWNER deploy ENCODING 'UTF8' LC_COLLATE 'en_US.UTF-8' LC_CTYPE 'en_US.UTF-8' CONNECTION LIMIT -1;"
|
|
164
|
+
ENV['FAKE_DROPDB_EXIT'] = '0'
|
|
165
|
+
ENV['FAKE_PSQL_RECREATE_EXIT'] = '0'
|
|
166
|
+
|
|
167
|
+
expect { engine.cycle_database('myapp') }.not_to raise_error
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'raises and does not attempt to recreate if dropdb fails' do
|
|
171
|
+
ENV['FAKE_PSQL_CONN_COUNT'] = '0'
|
|
172
|
+
ENV['FAKE_PSQL_CREATE_COMMAND_OUTPUT'] = "CREATE DATABASE myapp WITH OWNER deploy ENCODING 'UTF8';"
|
|
173
|
+
ENV['FAKE_DROPDB_EXIT'] = '1'
|
|
174
|
+
|
|
175
|
+
expect { engine.cycle_database('myapp') }.to raise_error(/Failed to drop database/)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it 'raises clearly (dropped-but-not-recreated) if the final recreate fails after a successful drop' do
|
|
179
|
+
ENV['FAKE_PSQL_CONN_COUNT'] = '0'
|
|
180
|
+
ENV['FAKE_PSQL_CREATE_COMMAND_OUTPUT'] = "CREATE DATABASE myapp WITH OWNER deploy ENCODING 'UTF8';"
|
|
181
|
+
ENV['FAKE_DROPDB_EXIT'] = '0'
|
|
182
|
+
ENV['FAKE_PSQL_RECREATE_EXIT'] = '1'
|
|
183
|
+
|
|
184
|
+
expect { engine.cycle_database('myapp') }.to raise_error(/dropped and NOT recreated/)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
metadata
CHANGED
|
@@ -1,173 +1,173 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ey_cloud_server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.5.
|
|
4
|
+
version: 1.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- EngineYard
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: json
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - ~>
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: 2.6.1
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - ~>
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: 2.6.1
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: open4
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - ~>
|
|
31
|
+
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: 1.3.0
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - ~>
|
|
38
|
+
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
40
|
version: 1.3.0
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: nokogiri
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- -
|
|
45
|
+
- - ">="
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
47
|
version: 1.6.6.2
|
|
48
|
-
- - <=
|
|
48
|
+
- - "<="
|
|
49
49
|
- !ruby/object:Gem::Version
|
|
50
50
|
version: 1.6.8.1
|
|
51
51
|
type: :runtime
|
|
52
52
|
prerelease: false
|
|
53
53
|
version_requirements: !ruby/object:Gem::Requirement
|
|
54
54
|
requirements:
|
|
55
|
-
- -
|
|
55
|
+
- - ">="
|
|
56
56
|
- !ruby/object:Gem::Version
|
|
57
57
|
version: 1.6.6.2
|
|
58
|
-
- - <=
|
|
58
|
+
- - "<="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
60
|
version: 1.6.8.1
|
|
61
61
|
- !ruby/object:Gem::Dependency
|
|
62
62
|
name: mime-types
|
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
|
-
- -
|
|
65
|
+
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
67
|
version: 3.3.1
|
|
68
68
|
type: :runtime
|
|
69
69
|
prerelease: false
|
|
70
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
71
71
|
requirements:
|
|
72
|
-
- -
|
|
72
|
+
- - ">="
|
|
73
73
|
- !ruby/object:Gem::Version
|
|
74
74
|
version: 3.3.1
|
|
75
75
|
- !ruby/object:Gem::Dependency
|
|
76
76
|
name: fog-aws
|
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
|
78
78
|
requirements:
|
|
79
|
-
- -
|
|
79
|
+
- - ">="
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: 1.2.1
|
|
82
82
|
type: :runtime
|
|
83
83
|
prerelease: false
|
|
84
84
|
version_requirements: !ruby/object:Gem::Requirement
|
|
85
85
|
requirements:
|
|
86
|
-
- -
|
|
86
|
+
- - ">="
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
88
|
version: 1.2.1
|
|
89
89
|
- !ruby/object:Gem::Dependency
|
|
90
90
|
name: ey_enzyme
|
|
91
91
|
requirement: !ruby/object:Gem::Requirement
|
|
92
92
|
requirements:
|
|
93
|
-
- - ~>
|
|
93
|
+
- - "~>"
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
95
|
version: 2.0.7pre1
|
|
96
96
|
type: :runtime
|
|
97
97
|
prerelease: false
|
|
98
98
|
version_requirements: !ruby/object:Gem::Requirement
|
|
99
99
|
requirements:
|
|
100
|
-
- - ~>
|
|
100
|
+
- - "~>"
|
|
101
101
|
- !ruby/object:Gem::Version
|
|
102
102
|
version: 2.0.7pre1
|
|
103
103
|
- !ruby/object:Gem::Dependency
|
|
104
104
|
name: ey_instance_api_client
|
|
105
105
|
requirement: !ruby/object:Gem::Requirement
|
|
106
106
|
requirements:
|
|
107
|
-
- - ~>
|
|
107
|
+
- - "~>"
|
|
108
108
|
- !ruby/object:Gem::Version
|
|
109
109
|
version: 0.1.11
|
|
110
110
|
type: :runtime
|
|
111
111
|
prerelease: false
|
|
112
112
|
version_requirements: !ruby/object:Gem::Requirement
|
|
113
113
|
requirements:
|
|
114
|
-
- - ~>
|
|
114
|
+
- - "~>"
|
|
115
115
|
- !ruby/object:Gem::Version
|
|
116
116
|
version: 0.1.11
|
|
117
117
|
- !ruby/object:Gem::Dependency
|
|
118
118
|
name: rake
|
|
119
119
|
requirement: !ruby/object:Gem::Requirement
|
|
120
120
|
requirements:
|
|
121
|
-
- - ~>
|
|
121
|
+
- - "~>"
|
|
122
122
|
- !ruby/object:Gem::Version
|
|
123
123
|
version: 10.4.2
|
|
124
124
|
type: :development
|
|
125
125
|
prerelease: false
|
|
126
126
|
version_requirements: !ruby/object:Gem::Requirement
|
|
127
127
|
requirements:
|
|
128
|
-
- - ~>
|
|
128
|
+
- - "~>"
|
|
129
129
|
- !ruby/object:Gem::Version
|
|
130
130
|
version: 10.4.2
|
|
131
131
|
- !ruby/object:Gem::Dependency
|
|
132
132
|
name: rspec
|
|
133
133
|
requirement: !ruby/object:Gem::Requirement
|
|
134
134
|
requirements:
|
|
135
|
-
- - ~>
|
|
135
|
+
- - "~>"
|
|
136
136
|
- !ruby/object:Gem::Version
|
|
137
137
|
version: '2.0'
|
|
138
138
|
type: :development
|
|
139
139
|
prerelease: false
|
|
140
140
|
version_requirements: !ruby/object:Gem::Requirement
|
|
141
141
|
requirements:
|
|
142
|
-
- - ~>
|
|
142
|
+
- - "~>"
|
|
143
143
|
- !ruby/object:Gem::Version
|
|
144
144
|
version: '2.0'
|
|
145
145
|
- !ruby/object:Gem::Dependency
|
|
146
146
|
name: randexp
|
|
147
147
|
requirement: !ruby/object:Gem::Requirement
|
|
148
148
|
requirements:
|
|
149
|
-
- - ~>
|
|
149
|
+
- - "~>"
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
151
|
version: 0.1.7
|
|
152
152
|
type: :development
|
|
153
153
|
prerelease: false
|
|
154
154
|
version_requirements: !ruby/object:Gem::Requirement
|
|
155
155
|
requirements:
|
|
156
|
-
- - ~>
|
|
156
|
+
- - "~>"
|
|
157
157
|
- !ruby/object:Gem::Version
|
|
158
158
|
version: 0.1.7
|
|
159
159
|
- !ruby/object:Gem::Dependency
|
|
160
160
|
name: cucumber
|
|
161
161
|
requirement: !ruby/object:Gem::Requirement
|
|
162
162
|
requirements:
|
|
163
|
-
- - ~>
|
|
163
|
+
- - "~>"
|
|
164
164
|
- !ruby/object:Gem::Version
|
|
165
165
|
version: 2.4.0
|
|
166
166
|
type: :development
|
|
167
167
|
prerelease: false
|
|
168
168
|
version_requirements: !ruby/object:Gem::Requirement
|
|
169
169
|
requirements:
|
|
170
|
-
- - ~>
|
|
170
|
+
- - "~>"
|
|
171
171
|
- !ruby/object:Gem::Version
|
|
172
172
|
version: 2.4.0
|
|
173
173
|
- !ruby/object:Gem::Dependency
|
|
@@ -202,28 +202,28 @@ dependencies:
|
|
|
202
202
|
name: bundler
|
|
203
203
|
requirement: !ruby/object:Gem::Requirement
|
|
204
204
|
requirements:
|
|
205
|
-
- - ~>
|
|
205
|
+
- - "~>"
|
|
206
206
|
- !ruby/object:Gem::Version
|
|
207
207
|
version: 1.13.7
|
|
208
208
|
type: :development
|
|
209
209
|
prerelease: false
|
|
210
210
|
version_requirements: !ruby/object:Gem::Requirement
|
|
211
211
|
requirements:
|
|
212
|
-
- - ~>
|
|
212
|
+
- - "~>"
|
|
213
213
|
- !ruby/object:Gem::Version
|
|
214
214
|
version: 1.13.7
|
|
215
215
|
- !ruby/object:Gem::Dependency
|
|
216
216
|
name: simplecov
|
|
217
217
|
requirement: !ruby/object:Gem::Requirement
|
|
218
218
|
requirements:
|
|
219
|
-
- - ~>
|
|
219
|
+
- - "~>"
|
|
220
220
|
- !ruby/object:Gem::Version
|
|
221
221
|
version: 0.16.1
|
|
222
222
|
type: :development
|
|
223
223
|
prerelease: false
|
|
224
224
|
version_requirements: !ruby/object:Gem::Requirement
|
|
225
225
|
requirements:
|
|
226
|
-
- - ~>
|
|
226
|
+
- - "~>"
|
|
227
227
|
- !ruby/object:Gem::Version
|
|
228
228
|
version: 0.16.1
|
|
229
229
|
description: Miscellaneous EY server utilities
|
|
@@ -281,7 +281,9 @@ files:
|
|
|
281
281
|
- spec/ey_backup/backup_spec.rb
|
|
282
282
|
- spec/ey_backup/cli_spec.rb
|
|
283
283
|
- spec/ey_backup/mysql_backups_spec.rb
|
|
284
|
+
- spec/ey_backup/mysql_engine_spec.rb
|
|
284
285
|
- spec/ey_backup/postgres_backups_spec.rb
|
|
286
|
+
- spec/ey_backup/postgresql_engine_spec.rb
|
|
285
287
|
- spec/ey_backup/spec_helper.rb
|
|
286
288
|
- spec/fakefs_hax.rb
|
|
287
289
|
- spec/gpg.public
|
|
@@ -298,17 +300,16 @@ require_paths:
|
|
|
298
300
|
- lib
|
|
299
301
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
300
302
|
requirements:
|
|
301
|
-
- -
|
|
303
|
+
- - ">="
|
|
302
304
|
- !ruby/object:Gem::Version
|
|
303
305
|
version: '0'
|
|
304
306
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
305
307
|
requirements:
|
|
306
|
-
- -
|
|
308
|
+
- - ">="
|
|
307
309
|
- !ruby/object:Gem::Version
|
|
308
310
|
version: '0'
|
|
309
311
|
requirements: []
|
|
310
|
-
|
|
311
|
-
rubygems_version: 2.6.14
|
|
312
|
+
rubygems_version: 3.2.33
|
|
312
313
|
signing_key:
|
|
313
314
|
specification_version: 4
|
|
314
315
|
summary: Server side components for Engine Yard's cloud
|
|
@@ -321,7 +322,9 @@ test_files:
|
|
|
321
322
|
- spec/ey_backup/backup_spec.rb
|
|
322
323
|
- spec/ey_backup/cli_spec.rb
|
|
323
324
|
- spec/ey_backup/mysql_backups_spec.rb
|
|
325
|
+
- spec/ey_backup/mysql_engine_spec.rb
|
|
324
326
|
- spec/ey_backup/postgres_backups_spec.rb
|
|
327
|
+
- spec/ey_backup/postgresql_engine_spec.rb
|
|
325
328
|
- spec/ey_backup/spec_helper.rb
|
|
326
329
|
- spec/fakefs_hax.rb
|
|
327
330
|
- spec/gpg.public
|