ey_cloud_server 1.4.61 → 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/features/support/env.rb +3 -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/config.yml.ci +11 -0
- data/spec/ey_backup/mysql_engine_spec.rb +346 -0
- data/spec/ey_backup/postgresql_engine_spec.rb +188 -0
- data/spec/helpers.rb +9 -6
- data/spec/spec_helper.rb +3 -0
- metadata +57 -48
- data/spec/config.yml +0 -12
- data/spec/log/ey_flex_postgresql_db_thecae.sizes +0 -1
checksums.yaml
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
|
|
5
|
-
data.tar.gz: !binary |-
|
|
6
|
-
OWI2Nzg4YWI5ZDFhNGZiNDcwOGM3ZGFmYTE5OTE4NzM2YzMyOGY3Zg==
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bfb2b6fa4059f5c796ba0cedbb4784c6a564a22adf1cd91dcc8efac32d44a1f6
|
|
4
|
+
data.tar.gz: e2dab140765f7438ccb743c40169a186e23f6b72d5acd36d5be5aaf759ca50a0
|
|
7
5
|
SHA512:
|
|
8
|
-
metadata.gz:
|
|
9
|
-
|
|
10
|
-
MGVhOGRmNzVjZDVkMGFjYzFkN2UxMjdlODk3MDczNDVhMjI1MjFjOTg2YmUy
|
|
11
|
-
ZDRmY2I3YmU2OTk3N2E1MmU2ODc4NjBiMTQzNWNjNzcyYjNhYjI=
|
|
12
|
-
data.tar.gz: !binary |-
|
|
13
|
-
YTRlZjdjYmE2ODk5NTQ0YzE1ODkwOTAwYWM1OWZhMDRiMDhhZmYxZWYxZmQ3
|
|
14
|
-
ZTkwZWY0ZGRkZTE2MDZlNjU4ZGNlZWRmMzBjNTVmMGUyZmNlOWE2NDZjZjll
|
|
15
|
-
NDZmYTI4N2ZjZTdjNWE5MDA4NjAwMGE4NjUwODdlODZiODgzMzc=
|
|
6
|
+
metadata.gz: e0f3941d55b673e5fbe7c3fea6e41ad72447e556cdfcaa7e29ea7226929042ea8e1806bc566bab07f245ad23302b4ad244c4aedde0780c1e3101cda41c4a39e3
|
|
7
|
+
data.tar.gz: 0f9a2d0689d4ba021eef298a7df7786abe731d13aa8aba68a59b9ea0e0e5d6e8dc568723ed97e886c07d9fd875513260d3a44950c93cd054b84e81e7c66348c1
|
data/bin/eybackup
CHANGED
data/features/support/env.rb
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
|
|
data/spec/config.yml.ci
ADDED
|
@@ -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
|
data/spec/helpers.rb
CHANGED
|
@@ -18,7 +18,7 @@ module Helpers
|
|
|
18
18
|
FakeWeb.register_uri(:post, "http://example.org/api/environments", :body => JSON.generate({
|
|
19
19
|
@mock_environment_name => {:id => 1}
|
|
20
20
|
}))
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
create_or_clean_dir(tmp_dir)
|
|
23
23
|
create_or_clean_dir(log_dir)
|
|
24
24
|
|
|
@@ -29,7 +29,7 @@ module Helpers
|
|
|
29
29
|
# end
|
|
30
30
|
stub_configs
|
|
31
31
|
end
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
def create_or_clean_dir(dir)
|
|
34
34
|
FileUtils.mkdir_p(dir)
|
|
35
35
|
Dir.glob("#{dir}/*").each do |f|
|
|
@@ -48,6 +48,9 @@ module Helpers
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def import_gpg
|
|
51
|
+
# GPG v2.1+ dropped support for the --secret-keyring option (https://lists.gnupg.org/pipermail/gnupg-devel/2014-December/029296.html)
|
|
52
|
+
# This means that we need to import private keys too
|
|
53
|
+
system("gpg --import #{PRIVATE_KEY_PATH}") || raise("Could not import public key")
|
|
51
54
|
system("gpg --import #{PUBLIC_KEY_PATH}") || raise("Could not import public key")
|
|
52
55
|
end
|
|
53
56
|
|
|
@@ -91,7 +94,7 @@ module Helpers
|
|
|
91
94
|
def backup_config_file
|
|
92
95
|
"#{tmp_dir}/spec_backups.yml"
|
|
93
96
|
end
|
|
94
|
-
|
|
97
|
+
|
|
95
98
|
def mk_tmp
|
|
96
99
|
FileUtils.mkdir_p("/tmp")
|
|
97
100
|
end
|
|
@@ -113,7 +116,7 @@ module Helpers
|
|
|
113
116
|
write_database_config('mysql', mysql_user, mysql_password, mysql_host, created_mysql_dbs.values, region)
|
|
114
117
|
db_name
|
|
115
118
|
end
|
|
116
|
-
|
|
119
|
+
|
|
117
120
|
def drop_mysql_database(db_name)
|
|
118
121
|
command = %Q{mysql -u#{mysql_user} -h#{mysql_host} #{mysql_password_option} -e "drop database if exists #{db_name};"}
|
|
119
122
|
puts "*** MySQL Drop Command: #{command}"
|
|
@@ -194,7 +197,7 @@ module Helpers
|
|
|
194
197
|
spec_config['tmp_dir'] || File.dirname("./tmp/")
|
|
195
198
|
end
|
|
196
199
|
end
|
|
197
|
-
|
|
200
|
+
|
|
198
201
|
def log_dir
|
|
199
202
|
if spec_config['log_dir'].nil?
|
|
200
203
|
File.dirname(__FILE__) + "/log/"
|
|
@@ -299,7 +302,7 @@ module Helpers
|
|
|
299
302
|
def stdout
|
|
300
303
|
EY::Backup.logger.stdout.string
|
|
301
304
|
end
|
|
302
|
-
|
|
305
|
+
|
|
303
306
|
def stderr
|
|
304
307
|
EY::Backup.logger.stderr.string
|
|
305
308
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
|
@@ -1,179 +1,173 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ey_cloud_server
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
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
|
-
version:
|
|
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
|
-
version:
|
|
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
|
-
version:
|
|
68
|
-
- - <
|
|
69
|
-
- !ruby/object:Gem::Version
|
|
70
|
-
version: '3.0'
|
|
67
|
+
version: 3.3.1
|
|
71
68
|
type: :runtime
|
|
72
69
|
prerelease: false
|
|
73
70
|
version_requirements: !ruby/object:Gem::Requirement
|
|
74
71
|
requirements:
|
|
75
|
-
- -
|
|
76
|
-
- !ruby/object:Gem::Version
|
|
77
|
-
version: '1.16'
|
|
78
|
-
- - <
|
|
72
|
+
- - ">="
|
|
79
73
|
- !ruby/object:Gem::Version
|
|
80
|
-
version:
|
|
74
|
+
version: 3.3.1
|
|
81
75
|
- !ruby/object:Gem::Dependency
|
|
82
76
|
name: fog-aws
|
|
83
77
|
requirement: !ruby/object:Gem::Requirement
|
|
84
78
|
requirements:
|
|
85
|
-
- -
|
|
79
|
+
- - ">="
|
|
86
80
|
- !ruby/object:Gem::Version
|
|
87
81
|
version: 1.2.1
|
|
88
82
|
type: :runtime
|
|
89
83
|
prerelease: false
|
|
90
84
|
version_requirements: !ruby/object:Gem::Requirement
|
|
91
85
|
requirements:
|
|
92
|
-
- -
|
|
86
|
+
- - ">="
|
|
93
87
|
- !ruby/object:Gem::Version
|
|
94
88
|
version: 1.2.1
|
|
95
89
|
- !ruby/object:Gem::Dependency
|
|
96
90
|
name: ey_enzyme
|
|
97
91
|
requirement: !ruby/object:Gem::Requirement
|
|
98
92
|
requirements:
|
|
99
|
-
- -
|
|
93
|
+
- - "~>"
|
|
100
94
|
- !ruby/object:Gem::Version
|
|
101
|
-
version:
|
|
95
|
+
version: 2.0.7pre1
|
|
102
96
|
type: :runtime
|
|
103
97
|
prerelease: false
|
|
104
98
|
version_requirements: !ruby/object:Gem::Requirement
|
|
105
99
|
requirements:
|
|
106
|
-
- -
|
|
100
|
+
- - "~>"
|
|
107
101
|
- !ruby/object:Gem::Version
|
|
108
|
-
version:
|
|
102
|
+
version: 2.0.7pre1
|
|
109
103
|
- !ruby/object:Gem::Dependency
|
|
110
104
|
name: ey_instance_api_client
|
|
111
105
|
requirement: !ruby/object:Gem::Requirement
|
|
112
106
|
requirements:
|
|
113
|
-
- - ~>
|
|
107
|
+
- - "~>"
|
|
114
108
|
- !ruby/object:Gem::Version
|
|
115
109
|
version: 0.1.11
|
|
116
110
|
type: :runtime
|
|
117
111
|
prerelease: false
|
|
118
112
|
version_requirements: !ruby/object:Gem::Requirement
|
|
119
113
|
requirements:
|
|
120
|
-
- - ~>
|
|
114
|
+
- - "~>"
|
|
121
115
|
- !ruby/object:Gem::Version
|
|
122
116
|
version: 0.1.11
|
|
123
117
|
- !ruby/object:Gem::Dependency
|
|
124
118
|
name: rake
|
|
125
119
|
requirement: !ruby/object:Gem::Requirement
|
|
126
120
|
requirements:
|
|
127
|
-
- - ~>
|
|
121
|
+
- - "~>"
|
|
128
122
|
- !ruby/object:Gem::Version
|
|
129
123
|
version: 10.4.2
|
|
130
124
|
type: :development
|
|
131
125
|
prerelease: false
|
|
132
126
|
version_requirements: !ruby/object:Gem::Requirement
|
|
133
127
|
requirements:
|
|
134
|
-
- - ~>
|
|
128
|
+
- - "~>"
|
|
135
129
|
- !ruby/object:Gem::Version
|
|
136
130
|
version: 10.4.2
|
|
137
131
|
- !ruby/object:Gem::Dependency
|
|
138
132
|
name: rspec
|
|
139
133
|
requirement: !ruby/object:Gem::Requirement
|
|
140
134
|
requirements:
|
|
141
|
-
- - ~>
|
|
135
|
+
- - "~>"
|
|
142
136
|
- !ruby/object:Gem::Version
|
|
143
137
|
version: '2.0'
|
|
144
138
|
type: :development
|
|
145
139
|
prerelease: false
|
|
146
140
|
version_requirements: !ruby/object:Gem::Requirement
|
|
147
141
|
requirements:
|
|
148
|
-
- - ~>
|
|
142
|
+
- - "~>"
|
|
149
143
|
- !ruby/object:Gem::Version
|
|
150
144
|
version: '2.0'
|
|
151
145
|
- !ruby/object:Gem::Dependency
|
|
152
146
|
name: randexp
|
|
153
147
|
requirement: !ruby/object:Gem::Requirement
|
|
154
148
|
requirements:
|
|
155
|
-
- - ~>
|
|
149
|
+
- - "~>"
|
|
156
150
|
- !ruby/object:Gem::Version
|
|
157
151
|
version: 0.1.7
|
|
158
152
|
type: :development
|
|
159
153
|
prerelease: false
|
|
160
154
|
version_requirements: !ruby/object:Gem::Requirement
|
|
161
155
|
requirements:
|
|
162
|
-
- - ~>
|
|
156
|
+
- - "~>"
|
|
163
157
|
- !ruby/object:Gem::Version
|
|
164
158
|
version: 0.1.7
|
|
165
159
|
- !ruby/object:Gem::Dependency
|
|
166
160
|
name: cucumber
|
|
167
161
|
requirement: !ruby/object:Gem::Requirement
|
|
168
162
|
requirements:
|
|
169
|
-
- - ~>
|
|
163
|
+
- - "~>"
|
|
170
164
|
- !ruby/object:Gem::Version
|
|
171
165
|
version: 2.4.0
|
|
172
166
|
type: :development
|
|
173
167
|
prerelease: false
|
|
174
168
|
version_requirements: !ruby/object:Gem::Requirement
|
|
175
169
|
requirements:
|
|
176
|
-
- - ~>
|
|
170
|
+
- - "~>"
|
|
177
171
|
- !ruby/object:Gem::Version
|
|
178
172
|
version: 2.4.0
|
|
179
173
|
- !ruby/object:Gem::Dependency
|
|
@@ -208,16 +202,30 @@ dependencies:
|
|
|
208
202
|
name: bundler
|
|
209
203
|
requirement: !ruby/object:Gem::Requirement
|
|
210
204
|
requirements:
|
|
211
|
-
- - ~>
|
|
205
|
+
- - "~>"
|
|
212
206
|
- !ruby/object:Gem::Version
|
|
213
207
|
version: 1.13.7
|
|
214
208
|
type: :development
|
|
215
209
|
prerelease: false
|
|
216
210
|
version_requirements: !ruby/object:Gem::Requirement
|
|
217
211
|
requirements:
|
|
218
|
-
- - ~>
|
|
212
|
+
- - "~>"
|
|
219
213
|
- !ruby/object:Gem::Version
|
|
220
214
|
version: 1.13.7
|
|
215
|
+
- !ruby/object:Gem::Dependency
|
|
216
|
+
name: simplecov
|
|
217
|
+
requirement: !ruby/object:Gem::Requirement
|
|
218
|
+
requirements:
|
|
219
|
+
- - "~>"
|
|
220
|
+
- !ruby/object:Gem::Version
|
|
221
|
+
version: 0.16.1
|
|
222
|
+
type: :development
|
|
223
|
+
prerelease: false
|
|
224
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
225
|
+
requirements:
|
|
226
|
+
- - "~>"
|
|
227
|
+
- !ruby/object:Gem::Version
|
|
228
|
+
version: 0.16.1
|
|
221
229
|
description: Miscellaneous EY server utilities
|
|
222
230
|
email:
|
|
223
231
|
- engineering@engineyard.com
|
|
@@ -267,19 +275,20 @@ files:
|
|
|
267
275
|
- lib/ey_cloud_server/version.rb
|
|
268
276
|
- spec/bucket_minder_spec.rb
|
|
269
277
|
- spec/config-example.yml
|
|
270
|
-
- spec/config.yml
|
|
278
|
+
- spec/config.yml.ci
|
|
271
279
|
- spec/ey_api_spec.rb
|
|
272
280
|
- spec/ey_backup/backend_spec.rb
|
|
273
281
|
- spec/ey_backup/backup_spec.rb
|
|
274
282
|
- spec/ey_backup/cli_spec.rb
|
|
275
283
|
- spec/ey_backup/mysql_backups_spec.rb
|
|
284
|
+
- spec/ey_backup/mysql_engine_spec.rb
|
|
276
285
|
- spec/ey_backup/postgres_backups_spec.rb
|
|
286
|
+
- spec/ey_backup/postgresql_engine_spec.rb
|
|
277
287
|
- spec/ey_backup/spec_helper.rb
|
|
278
288
|
- spec/fakefs_hax.rb
|
|
279
289
|
- spec/gpg.public
|
|
280
290
|
- spec/gpg.sekrit
|
|
281
291
|
- spec/helpers.rb
|
|
282
|
-
- spec/log/ey_flex_postgresql_db_thecae.sizes
|
|
283
292
|
- spec/snapshot_minder_spec.rb
|
|
284
293
|
- spec/spec_helper.rb
|
|
285
294
|
homepage: http://developer.engineyard.com
|
|
@@ -291,36 +300,36 @@ require_paths:
|
|
|
291
300
|
- lib
|
|
292
301
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
293
302
|
requirements:
|
|
294
|
-
- -
|
|
303
|
+
- - ">="
|
|
295
304
|
- !ruby/object:Gem::Version
|
|
296
305
|
version: '0'
|
|
297
306
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
298
307
|
requirements:
|
|
299
|
-
- -
|
|
308
|
+
- - ">="
|
|
300
309
|
- !ruby/object:Gem::Version
|
|
301
310
|
version: '0'
|
|
302
311
|
requirements: []
|
|
303
|
-
|
|
304
|
-
rubygems_version: 2.4.5
|
|
312
|
+
rubygems_version: 3.2.33
|
|
305
313
|
signing_key:
|
|
306
314
|
specification_version: 4
|
|
307
315
|
summary: Server side components for Engine Yard's cloud
|
|
308
316
|
test_files:
|
|
309
317
|
- spec/bucket_minder_spec.rb
|
|
310
318
|
- spec/config-example.yml
|
|
311
|
-
- spec/config.yml
|
|
319
|
+
- spec/config.yml.ci
|
|
312
320
|
- spec/ey_api_spec.rb
|
|
313
321
|
- spec/ey_backup/backend_spec.rb
|
|
314
322
|
- spec/ey_backup/backup_spec.rb
|
|
315
323
|
- spec/ey_backup/cli_spec.rb
|
|
316
324
|
- spec/ey_backup/mysql_backups_spec.rb
|
|
325
|
+
- spec/ey_backup/mysql_engine_spec.rb
|
|
317
326
|
- spec/ey_backup/postgres_backups_spec.rb
|
|
327
|
+
- spec/ey_backup/postgresql_engine_spec.rb
|
|
318
328
|
- spec/ey_backup/spec_helper.rb
|
|
319
329
|
- spec/fakefs_hax.rb
|
|
320
330
|
- spec/gpg.public
|
|
321
331
|
- spec/gpg.sekrit
|
|
322
332
|
- spec/helpers.rb
|
|
323
|
-
- spec/log/ey_flex_postgresql_db_thecae.sizes
|
|
324
333
|
- spec/snapshot_minder_spec.rb
|
|
325
334
|
- spec/spec_helper.rb
|
|
326
335
|
- features/downloading_a_mysql_backup.feature
|
data/spec/config.yml
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
tmp_dir:
|
|
3
|
-
log_dir:
|
|
4
|
-
data_json:
|
|
5
|
-
mysql_user: root
|
|
6
|
-
mysql_password:
|
|
7
|
-
mysql_host: localhost
|
|
8
|
-
postgresql_user: postgres
|
|
9
|
-
postgresql_password: grew152
|
|
10
|
-
postgresql_host: localhost
|
|
11
|
-
aws_secret_id: 'AKIAIDNXVLT3THIWYMNQ'
|
|
12
|
-
aws_secret_key: 'DZBOcAI1qpYF0V+ZL2SeA7Jzt15boFxIo+BD7vda'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
2017-08-29 15:44:07 -0400 1.7 KB
|