Skip to content

Commit c102adb

Browse files
authored
Merge pull request #167 from github/byodb-to-non-byodb
Add checks for non-BYODB to BYODB restores, and vice versa.
2 parents 2f768c8 + b276b35 commit c102adb

12 files changed

+388
-44
lines changed

bin/ghe-restore

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env bash
2-
#/ Usage: ghe-restore [-fchv] [--version] [-s <snapshot-id>] [<host>]
2+
#/ Usage: ghe-restore [-cfhv] [--version] [--skip-mysql] [-s <snapshot-id>] [<host>]
33
#/
44
#/ Restores a GitHub instance from local backup snapshots.
55
#/
@@ -9,40 +9,54 @@
99
#/ <https://enterprise.github.com/help/articles/adding-an-ssh-key-for-shell-access>
1010
#/
1111
#/ OPTIONS:
12-
#/ -f | --force Don't prompt for confirmation before restoring.
13-
#/ -c | --config Restore appliance settings and license in addition to
14-
#/ datastores. Settings are not restored by default to
15-
#/ prevent overwriting different configuration on the
16-
#/ restore host.
17-
#/ -v | --verbose Enable verbose output.
18-
#/ -h | --help Show this message.
19-
#/ --version Display version information and exit.
20-
#/ -s <snapshot-id> Restore from the snapshot with the given id. Available
21-
#/ snapshots may be listed under the data directory.
22-
#/ <host> The <host> is the hostname or IP of the GitHub Enterprise
23-
#/ instance. The <host> may be omitted when the
24-
#/ GHE_RESTORE_HOST config variable is set in backup.config.
25-
#/ When a <host> argument is provided, it always overrides
26-
#/ the configured restore host.
12+
#/ -c | --config Restore appliance settings and license in addition to
13+
#/ datastores. Settings are not restored by default to
14+
#/ prevent overwriting different configuration on the
15+
#/ restore host.
16+
#/ -f | --force Don't prompt for confirmation before restoring.
17+
#/ -h | --help Show this message.
18+
#/ -v | --verbose Enable verbose output.
19+
#/ --skip-mysql Skip MySQL restore steps. Only applicable to external databases.
20+
#/ --version Display version information and exit.
21+
#/
22+
#/ -s <snapshot-id> Restore from the snapshot with the given id. Available
23+
#/ snapshots may be listed under the data directory.
24+
#/
25+
#/ <host> The <host> is the hostname or IP of the GitHub Enterprise
26+
#/ instance. The <host> may be omitted when the
27+
#/ GHE_RESTORE_HOST config variable is set in backup.config.
28+
#/ When a <host> argument is provided, it always overrides
29+
#/ the configured restore host.
2730
#/
2831

2932
set -e
3033

3134
# Parse arguments
32-
restore_settings=false
33-
force=false
35+
: ${RESTORE_SETTINGS:=false}
36+
export RESTORE_SETTINGS
37+
38+
: ${FORCE:=false}
39+
export FORCE
40+
41+
: ${SKIP_MYSQL:=false}
42+
export SKIP_MYSQL
43+
3444
while true; do
3545
case "$1" in
46+
--skip-mysql)
47+
SKIP_MYSQL=true
48+
shift
49+
;;
3650
-f|--force)
37-
force=true
51+
FORCE=true
3852
shift
3953
;;
4054
-s)
4155
snapshot_id="$(basename "$2")"
4256
shift 2
4357
;;
4458
-c|--config)
45-
restore_settings=true
59+
RESTORE_SETTINGS=true
4660
shift
4761
;;
4862
-h|--help)
@@ -116,10 +130,10 @@ ghe_remote_version_required "$GHE_HOSTNAME"
116130

117131
# Figure out if this instance has been configured or is entirely new.
118132
instance_configured=false
119-
if ghe-ssh "$GHE_HOSTNAME" -- "[ -f '$GHE_REMOTE_ROOT_DIR/etc/github/configured' ]"; then
133+
if is_instance_configured; then
120134
instance_configured=true
121135
else
122-
restore_settings=true
136+
RESTORE_SETTINGS=true
123137
fi
124138

125139
# Figure out if we're restoring into cluster
@@ -137,6 +151,11 @@ if ! $CLUSTER && [ "$GHE_BACKUP_STRATEGY" = "cluster" ]; then
137151
exit 1
138152
fi
139153

154+
# Ensure target appliance and restore snapshot are a compatible combination with respect to BYODB
155+
if ! ghe-restore-external-database-compatibility-check; then
156+
exit 1
157+
fi
158+
140159
# Figure out if this appliance is in a replication pair
141160
if ghe-ssh "$GHE_HOSTNAME" -- \
142161
"[ -f '$GHE_REMOTE_ROOT_DIR/etc/github/repl-state' ]"; then
@@ -149,7 +168,7 @@ fi
149168
# important data on the destination appliance that cannot be recovered. This is
150169
# mostly to prevent accidents where the backup host is given to restore instead
151170
# of a separate restore host since they're used in such close proximity.
152-
if $instance_configured && ! $force; then
171+
if $instance_configured && ! $FORCE; then
153172
echo
154173
echo "WARNING: All data on GitHub Enterprise appliance $hostname ($GHE_REMOTE_VERSION)"
155174
echo " will be overwritten with data from snapshot ${GHE_RESTORE_SNAPSHOT}."
@@ -234,7 +253,7 @@ fi
234253

235254
# Restore settings and license if restoring to an unconfigured appliance or when
236255
# specified manually.
237-
if $restore_settings; then
256+
if $RESTORE_SETTINGS; then
238257
ghe-restore-settings "$GHE_HOSTNAME"
239258
fi
240259

@@ -255,10 +274,10 @@ if [ -s "$GHE_RESTORE_SNAPSHOT_PATH/uuid" ] && ! $CLUSTER; then
255274
ghe-ssh "$GHE_HOSTNAME" -- "sudo rm -rf /data/user/consul/raft"
256275
fi
257276

258-
if is_service_external 'mysql' "$GHE_RESTORE_SNAPSHOT_PATH/settings.json"; then
277+
if is_external_database_snapshot; then
259278
appliance_strategy="external"
260279
backup_snapshot_strategy="external"
261-
else
280+
else
262281
if is_binary_backup_feature_on; then
263282
appliance_strategy="binary"
264283
else
@@ -272,8 +291,12 @@ else
272291
fi
273292
fi
274293

275-
echo "Restoring MySQL database from ${backup_snapshot_strategy} backup snapshot on an appliance configured for ${appliance_strategy} backups ..."
276-
ghe-restore-mysql "$GHE_HOSTNAME" 1>&3
294+
if is_external_database_target_or_snapshot && $SKIP_MYSQL; then
295+
echo "Skipping MySQL restore."
296+
else
297+
echo "Restoring MySQL database from ${backup_snapshot_strategy} backup snapshot on an appliance configured for ${appliance_strategy} backups ..."
298+
ghe-restore-mysql "$GHE_HOSTNAME" 1>&3
299+
fi
277300

278301
commands=("
279302
echo \"Restoring Redis database ...\"

docs/usage.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ You can supply your own configuration file or use the example configuration file
1616

1717
An example configuration file with documentation on possible settings can found in [backup.config-example](../backup.config-example).
1818

19+
There are a number of command line options that can also be passed to the `ghe-restore` command. Of particular note, if you use an external MySQL service but are restoring from a snapshot prior to enabling this, or vice versa, you must migrate the MySQL data outside of the context of backup-utils first, then pass the `--skip-mysql` flag to `ghe-restore`.
20+
1921
## Example backup and restore usage
2022

2123
The following assumes that `GHE_HOSTNAME` is set to "github.example.com" in

share/github-backup-utils/ghe-backup-config

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,9 @@ is_binary_backup(){
392392
test -f "$1/mysql-binary-backup-sentinel"
393393
}
394394

395+
# Check if a given service is managed externally on the appliance or in a snapshot.
396+
# Usage: is_service_external $service [$config_file]
397+
# Pass in the config file to check if the service is managed externally in the snapshot.
395398
is_service_external(){
396399
service=$1
397400
config_file=$2
@@ -401,11 +404,48 @@ is_service_external(){
401404
enabled=$(GIT_CONFIG="$config_file" git config mysql.external.enabled)
402405
[ "$enabled" == "true" ];
403406
else
404-
ghe-ssh "$GHE_HOSTNAME" ghe-config --true "mysql.external.enabled"
407+
ghe-ssh "$GHE_HOSTNAME" -- ghe-config --true "mysql.external.enabled"
405408
fi
406409
;;
407410
*)
408411
return 1
409412
;;
410413
esac
411414
}
415+
416+
is_instance_configured(){
417+
ghe-ssh "$GHE_HOSTNAME" -- "[ -f '$GHE_REMOTE_ROOT_DIR/etc/github/configured' ]"
418+
}
419+
420+
# Helper method that returns true if:
421+
# - the target appliance uses the internal MySQL database (aka NOT BYODB), and
422+
# - the snapshot being restored is from an appliance using an external MySQL database (BYODB)
423+
external_database_snapshot_to_internal_database(){
424+
! is_external_database_target && is_external_database_snapshot
425+
}
426+
427+
# Helper method that returns true if:
428+
# - the target appliance uses an external MySQL database (BYODB), and
429+
# - the snapshot being restored is from an appliance using an internal MySQL database (aka NOT BYODB)
430+
internal_database_snapshot_to_external_database(){
431+
is_external_database_target && ! is_external_database_snapshot
432+
}
433+
434+
is_external_database_target_or_snapshot(){
435+
# If restoring settings, only check if the snapshot being restored was from an appliance with external DB configured.
436+
if $RESTORE_SETTINGS; then
437+
is_external_database_snapshot
438+
else
439+
# Check if restoring a snapshot with an external database configured, or restoring
440+
# to an appliance with an external database configured.
441+
is_external_database_snapshot || is_external_database_target
442+
fi
443+
}
444+
445+
is_external_database_target(){
446+
is_service_external "mysql"
447+
}
448+
449+
is_external_database_snapshot(){
450+
is_service_external "mysql" "$GHE_DATA_DIR/$GHE_RESTORE_SNAPSHOT/settings.json"
451+
}

share/github-backup-utils/ghe-backup-mysql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ bm_start "$(basename $0)"
1515
# Perform a host-check and establish the remote version in GHE_REMOTE_VERSION.
1616
ghe_remote_version_required "$GHE_HOSTNAME"
1717

18-
if is_service_external 'mysql'; then
18+
if is_external_database_target; then
1919
echo "Backing up external MySQL database using customer-provided script..."
2020
if [ -n "$EXTERNAL_DATABASE_BACKUP_SCRIPT" ]; then
2121
$EXTERNAL_DATABASE_BACKUP_SCRIPT

share/github-backup-utils/ghe-restore-es-audit-log

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ last_month=$(printf "audit_log(-[0-9]+)?-%4d-%02d(-[0-9]+)?" $last_yr $last_mth)
3636
current_month=$(printf "audit_log(-[0-9]+)?-%4d-%02d(-[0-9]+)?" $this_yr $this_mth)
3737

3838
tmp_list="$(mktemp -t backup-utils-restore-XXXXXX)"
39-
if ghe-ssh "$GHE_HOSTNAME" -- "[ -f '$GHE_REMOTE_ROOT_DIR/etc/github/configured' ]"; then
39+
if is_instance_configured; then
4040
configured=true
4141
fi
4242

share/github-backup-utils/ghe-restore-es-hookshot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ last_index=$(ghe-ssh "$GHE_HOSTNAME" 'curl -s "localhost:9201/_cat/indices/hooks
2424

2525
indices=$(find $GHE_DATA_DIR/$GHE_RESTORE_SNAPSHOT/hookshot/*.gz -print0 2>/dev/null | xargs -0 -I{} -n1 basename {} .gz)
2626
tmp_list="$(mktemp -t backup-utils-restore-XXXXXX)"
27-
if ghe-ssh "$GHE_HOSTNAME" -- "[ -f '$GHE_REMOTE_ROOT_DIR/etc/github/configured' ]"; then
27+
if is_instance_configured; then
2828
configured=true
2929
fi
3030

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#!/usr/bin/env bash
2+
# Usage: ghe-restore-external-database-compatibility-check
3+
# GitHub Enterprise checks for external-database related restores.
4+
5+
# Bring in the backup configuration
6+
# shellcheck source=share/github-backup-utils/ghe-backup-config
7+
. "$( dirname "${BASH_SOURCE[0]}" )/ghe-backup-config"
8+
9+
set -e
10+
11+
# Always allow restoring to unconfigured appliances.
12+
# Additional checks are required if the instance is configured.
13+
if is_instance_configured; then
14+
15+
if internal_database_snapshot_to_external_database; then
16+
17+
# Restoring settings in this scenario would change BYODB state, which is not supported via backup-utils.
18+
if $RESTORE_SETTINGS; then
19+
echo "Restoring the settings of a snapshot from an appliance using the bundled MySQL service to an appliance using an externally-managed MySQL service is not supported."
20+
echo "Please reconfigure the appliance first, then run ghe-restore again."
21+
exit 1
22+
fi
23+
24+
# Restoring interal DB snapshot to BYODB appliance without passing in --skip-mysql is not supported.
25+
if ! $SKIP_MYSQL; then
26+
echo "Restoring a snapshot from an appliance using the bundled MySQL service to an appliance using an externally-managed MySQL service is not supported."
27+
echo "Please migrate the MySQL data beforehand, then run ghe-restore again, passing in the --skip-mysql flag."
28+
exit 1
29+
fi
30+
fi
31+
32+
if external_database_snapshot_to_internal_database; then
33+
34+
# Restoring settings in this scenario would change BYODB state, which is not supported via backup-utils.
35+
if $RESTORE_SETTINGS; then
36+
echo "Restoring the settings of a snapshot from an appliance using an externally-managed MySQL service to an appliance using the bundled MySQL service is not supported."
37+
echo "Please reconfigure the appliance first, then run ghe-restore again."
38+
exit 1
39+
fi
40+
41+
# Restoring BYODB snapshot to internal DB appliance without passing in --skip-mysql is not supported.
42+
if ! $SKIP_MYSQL; then
43+
echo "Restoring a snapshot from an appliance using an externally-managed MySQL service to an appliance using the bundled MySQL service is not supported."
44+
echo "Please migrate the MySQL data beforehand, then run ghe-restore again, passing in the --skip-mysql flag."
45+
exit 1
46+
fi
47+
fi
48+
fi

share/github-backup-utils/ghe-restore-mysql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ export GHE_RESTORE_SNAPSHOT
2929
# The directory holding the snapshot to restore
3030
snapshot_dir="$GHE_DATA_DIR/$GHE_RESTORE_SNAPSHOT"
3131

32-
# When customer uses external database, we rely on customer to implement the restore process
33-
if is_service_external 'mysql' "$snapshot_dir/settings.json"; then
32+
if is_external_database_snapshot; then
3433
if [ -n "$EXTERNAL_DATABASE_RESTORE_SCRIPT" ]; then
3534
$EXTERNAL_DATABASE_RESTORE_SCRIPT
3635
else

test/bin/ghe-cluster-each

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,37 @@
33
# Emulates the remote GitHub ghe-config command. Tests use this
44
# to assert that the command was executed.
55
set -e
6-
if [[ "$*" =~ "-u" ]]; then
7-
# Mimic `ghe-cluster-each $role -u`
8-
echo "fake-uuid
9-
fake-uuid1
10-
fake-uuid2
11-
"
6+
7+
for _ in "$@"; do
8+
case "$1" in
9+
-u)
10+
SHOW_UUID=true
11+
shift
12+
;;
13+
-r|--role)
14+
# fake change last save timestamp every 1s
15+
ROLE=$2
16+
shift
17+
shift
18+
;;
19+
esac
20+
done
21+
22+
if $SHOW_UUID; then
23+
CONFIG="$GHE_REMOTE_DATA_USER_DIR/common/cluster.conf"
24+
25+
hosts=$(git config -f $CONFIG --get-regexp cluster.*.hostname | cut -d ' ' -f2)
26+
27+
if [ -z "$hosts" ]; then
28+
# Mimic `ghe-cluster-each $role -u`
29+
echo "fake-uuid
30+
fake-uuid1
31+
fake-uuid2
32+
"
33+
else
34+
for hostname in $hosts; do
35+
[ -n "$ROLE" ] && [ "$(git config -f $CONFIG cluster.$hostname.$ROLE-server)" != "true" ] && continue
36+
echo $hostname
37+
done
38+
fi
1239
fi

test/bin/ghe-config

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,10 @@ if [ "$1" = "--true" ]; then
99
[[ $($0 "$@") = true ]] && exit 0 || exit 1
1010
fi
1111

12-
git config -f "$GHE_REMOTE_DATA_USER_DIR/common/secrets.conf" "$@"
12+
if [[ "$1" =~ secrets\..+ ]]; then
13+
CONFIG="$GHE_REMOTE_DATA_USER_DIR/common/secrets.conf"
14+
else
15+
CONFIG="$GHE_REMOTE_DATA_USER_DIR/common/github.conf"
16+
fi
17+
18+
git config -f $CONFIG "$@"

0 commit comments

Comments
 (0)