Merge pull request #4360 from tgxworld/support_new_pg_dump_format

Changes to backup and restore
This commit is contained in:
Guo Xiang Tan 2016-08-04 10:58:42 +08:00 committed by GitHub
commit 6827239444
6 changed files with 89 additions and 50 deletions

View File

@ -1885,9 +1885,16 @@ en:
backup_succeeded: backup_succeeded:
subject_template: "Backup completed successfully" subject_template: "Backup completed successfully"
text_body_template: "The backup was successful. text_body_template: |
The backup was successful.
Visit the [admin > backup section](%{base_url}/admin/backups) to download your new backup." Visit the [admin > backup section](%{base_url}/admin/backups) to download your new backup."
Here's the log:
```text
%{logs}
```
backup_failed: backup_failed:
subject_template: "Backup failed" subject_template: "Backup failed"
@ -1896,13 +1903,20 @@ en:
Here's the log: Here's the log:
``` ```text
%{logs} %{logs}
``` ```
restore_succeeded: restore_succeeded:
subject_template: "Restore completed successfully" subject_template: "Restore completed successfully"
text_body_template: "The restore was successful." text_body_template: |
The restore was successful.
Here's the log:
```text
%{logs}
```
restore_failed: restore_failed:
subject_template: "Restore failed" subject_template: "Restore failed"
@ -1911,7 +1925,7 @@ en:
Here's the log: Here's the log:
``` ```text
%{logs} %{logs}
``` ```

View File

@ -9,7 +9,7 @@ require_dependency "permalink_constraint"
# and makes Guard not work properly. # and makes Guard not work properly.
USERNAME_ROUTE_FORMAT = /[\w.\-]+/ unless defined? USERNAME_ROUTE_FORMAT USERNAME_ROUTE_FORMAT = /[\w.\-]+/ unless defined? USERNAME_ROUTE_FORMAT
BACKUP_ROUTE_FORMAT = /.+\.(tar\.gz|tgz)/i unless defined? BACKUP_ROUTE_FORMAT BACKUP_ROUTE_FORMAT = /.+\.(sql\.gz|tar\.gz|tgz)/i unless defined? BACKUP_ROUTE_FORMAT
Discourse::Application.routes.draw do Discourse::Application.routes.draw do

View File

@ -6,6 +6,7 @@ module BackupRestore
class OperationRunningError < RuntimeError; end class OperationRunningError < RuntimeError; end
VERSION_PREFIX = "v".freeze
DUMP_FILE = "dump.sql" DUMP_FILE = "dump.sql"
METADATA_FILE = "meta.json" METADATA_FILE = "meta.json"
LOGS_CHANNEL = "/admin/backups/logs" LOGS_CHANNEL = "/admin/backups/logs"

View File

@ -28,8 +28,6 @@ module BackupRestore
ensure_directory_exists(@tmp_directory) ensure_directory_exists(@tmp_directory)
ensure_directory_exists(@archive_directory) ensure_directory_exists(@archive_directory)
write_metadata
### READ-ONLY / START ### ### READ-ONLY / START ###
enable_readonly_mode enable_readonly_mode
@ -43,7 +41,7 @@ module BackupRestore
log "Finalizing backup..." log "Finalizing backup..."
create_archive @with_uploads ? create_archive : move_dump_backup
after_create_hook after_create_hook
rescue SystemExit rescue SystemExit
@ -54,7 +52,7 @@ module BackupRestore
@success = false @success = false
else else
@success = true @success = true
"#{@archive_basename}.tar.gz" @backup_filename
ensure ensure
begin begin
notify_user notify_user
@ -84,9 +82,16 @@ module BackupRestore
@timestamp = Time.now.strftime("%Y-%m-%d-%H%M%S") @timestamp = Time.now.strftime("%Y-%m-%d-%H%M%S")
@tmp_directory = File.join(Rails.root, "tmp", "backups", @current_db, @timestamp) @tmp_directory = File.join(Rails.root, "tmp", "backups", @current_db, @timestamp)
@dump_filename = "#{File.join(@tmp_directory, BackupRestore::DUMP_FILE)}.gz" @dump_filename = "#{File.join(@tmp_directory, BackupRestore::DUMP_FILE)}.gz"
@meta_filename = File.join(@tmp_directory, BackupRestore::METADATA_FILE)
@archive_directory = File.join(Rails.root, "public", "backups", @current_db) @archive_directory = File.join(Rails.root, "public", "backups", @current_db)
@archive_basename = File.join(@archive_directory, "#{SiteSetting.title.parameterize}-#{@timestamp}") @archive_basename = File.join(@archive_directory, "#{SiteSetting.title.parameterize}-#{@timestamp}-#{BackupRestore::VERSION_PREFIX}#{BackupRestore.current_version}")
@backup_filename =
if @with_uploads
"#{File.basename(@archive_basename)}.tar.gz"
else
"#{File.basename(@archive_basename)}.sql.gz"
end
@logs = [] @logs = []
@readonly_mode_was_enabled = Discourse.readonly_mode? || !SiteSetting.readonly_mode_during_backup @readonly_mode_was_enabled = Discourse.readonly_mode? || !SiteSetting.readonly_mode_during_backup
end end
@ -137,15 +142,6 @@ module BackupRestore
false false
end end
def write_metadata
log "Writing metadata to '#{@meta_filename}'..."
metadata = {
source: "discourse",
version: BackupRestore.current_version
}
File.write(@meta_filename, metadata.to_json)
end
def dump_public_schema def dump_public_schema
log "Dumping the public schema of the database..." log "Dumping the public schema of the database..."
@ -199,8 +195,19 @@ module BackupRestore
].join(" ") ].join(" ")
end end
def move_dump_backup
log "Finalizing database dump file: #{@backup_filename}"
execute_command(
"mv #{@dump_filename} #{File.join(@archive_directory, @backup_filename)}",
"Failed to move database dump file."
)
remove_tmp_directory
end
def create_archive def create_archive
log "Creating archive: #{File.basename(@archive_basename)}.tar.gz" log "Creating archive: #{@backup_filename}"
tar_filename = "#{@archive_basename}.tar" tar_filename = "#{@archive_basename}.tar"
@ -219,23 +226,17 @@ module BackupRestore
) )
end end
log "Archiving metadata..." upload_directory = "uploads/" + @current_db
FileUtils.cd(File.dirname(@meta_filename)) do
execute_command(
"tar --append --dereference --file #{tar_filename} #{File.basename(@meta_filename)}",
"Failed to archive metadata."
)
end
if @with_uploads log "Archiving uploads..."
upload_directory = "uploads/" + @current_db FileUtils.cd(File.join(Rails.root, "public")) do
if File.directory?(upload_directory)
log "Archiving uploads..."
FileUtils.cd(File.join(Rails.root, "public")) do
execute_command( execute_command(
"tar --append --dereference --file #{tar_filename} #{upload_directory}", "tar --append --dereference --file #{tar_filename} #{upload_directory}",
"Failed to archive uploads." "Failed to archive uploads."
) )
else
log "No uploads found, skipping archiving uploads..."
end end
end end
@ -247,7 +248,7 @@ module BackupRestore
def after_create_hook def after_create_hook
log "Executing the after_create_hook for the backup..." log "Executing the after_create_hook for the backup..."
backup = Backup.create_from_filename("#{File.basename(@archive_basename)}.tar.gz") backup = Backup.create_from_filename(@backup_filename)
backup.after_create_hook backup.after_create_hook
end end
@ -259,9 +260,9 @@ module BackupRestore
def notify_user def notify_user
log "Notifying '#{@user.username}' of the end of the backup..." log "Notifying '#{@user.username}' of the end of the backup..."
if @success if @success
SystemMessage.create_from_system_user(@user, :backup_succeeded) SystemMessage.create_from_system_user(@user, :backup_succeeded, logs: pretty_logs(@logs))
else else
SystemMessage.create_from_system_user(@user, :backup_failed, logs: @logs.join("\n")) SystemMessage.create_from_system_user(@user, :backup_failed, logs: pretty_logs(@logs))
end end
end end

View File

@ -110,13 +110,18 @@ module BackupRestore
@archive_filename = File.join(@tmp_directory, @filename) @archive_filename = File.join(@tmp_directory, @filename)
@tar_filename = @archive_filename[0...-3] @tar_filename = @archive_filename[0...-3]
@meta_filename = File.join(@tmp_directory, BackupRestore::METADATA_FILE) @meta_filename = File.join(@tmp_directory, BackupRestore::METADATA_FILE)
@is_archive = !(@filename =~ /.sql.gz$/)
# For backwards compatibility # For backwards compatibility
@dump_filename = @dump_filename =
if system("tar --list --file #{@source_filename} #{BackupRestore::DUMP_FILE}") if @is_archive
File.join(@tmp_directory, BackupRestore::DUMP_FILE) if system("tar --list --file #{@source_filename} #{BackupRestore::DUMP_FILE}")
File.join(@tmp_directory, BackupRestore::DUMP_FILE)
else
File.join(@tmp_directory, "#{BackupRestore::DUMP_FILE}.gz")
end
else else
File.join(@tmp_directory, "#{BackupRestore::DUMP_FILE}.gz") File.join(@tmp_directory, @filename)
end end
@logs = [] @logs = []
@ -175,7 +180,10 @@ module BackupRestore
end end
def unzip_archive def unzip_archive
return unless @is_archive
log "Unzipping archive, this may take a while..." log "Unzipping archive, this may take a while..."
FileUtils.cd(@tmp_directory) do FileUtils.cd(@tmp_directory) do
execute_command("gzip --decompress '#{@archive_filename}'", "Failed to unzip archive.") execute_command("gzip --decompress '#{@archive_filename}'", "Failed to unzip archive.")
end end
@ -184,14 +192,23 @@ module BackupRestore
def extract_metadata def extract_metadata
log "Extracting metadata file..." log "Extracting metadata file..."
FileUtils.cd(@tmp_directory) do @metadata =
execute_command( if system("tar --list --file #{@source_filename} #{BackupRestore::METADATA_FILE}")
"tar --extract --file '#{@tar_filename}' #{BackupRestore::METADATA_FILE}", FileUtils.cd(@tmp_directory) do
"Failed to extract metadata file." execute_command(
) "tar --extract --file '#{@tar_filename}' #{BackupRestore::METADATA_FILE}",
end "Failed to extract metadata file."
)
end
@metadata = Oj.load_file(@meta_filename) Oj.load_file(@meta_filename)
else
if @filename =~ /-#{BackupRestore::VERSION_PREFIX}(\d{14})/
{ "version" => Regexp.last_match[1].to_i }
else
raise "Migration version is missing from the filename."
end
end
end end
def validate_metadata def validate_metadata
@ -204,6 +221,8 @@ module BackupRestore
end end
def extract_dump def extract_dump
return unless @is_archive
log "Extracting dump file..." log "Extracting dump file..."
FileUtils.cd(@tmp_directory) do FileUtils.cd(@tmp_directory) do
@ -334,7 +353,7 @@ module BackupRestore
end end
def extract_uploads def extract_uploads
if `tar --list --file '#{@tar_filename}' | grep 'uploads/'`.present? if system("tar --list --file '#{@tar_filename}' 'uploads'")
log "Extracting uploads..." log "Extracting uploads..."
FileUtils.cd(File.join(Rails.root, "public")) do FileUtils.cd(File.join(Rails.root, "public")) do
execute_command( execute_command(
@ -359,9 +378,9 @@ module BackupRestore
if user = User.find_by(email: @user_info[:email]) if user = User.find_by(email: @user_info[:email])
log "Notifying '#{user.username}' of the end of the restore..." log "Notifying '#{user.username}' of the end of the restore..."
if @success if @success
SystemMessage.create_from_system_user(user, :restore_succeeded) SystemMessage.create_from_system_user(user, :restore_succeeded, logs: pretty_logs(@logs))
else else
SystemMessage.create_from_system_user(user, :restore_failed, logs: @logs.join("\n")) SystemMessage.create_from_system_user(user, :restore_failed, logs: pretty_logs(@logs))
end end
else else
log "Could not send notification to '#{@user_info[:username]}' (#{@user_info[:email]}), because the user does not exists..." log "Could not send notification to '#{@user_info[:username]}' (#{@user_info[:email]}), because the user does not exists..."

View File

@ -10,5 +10,9 @@ module BackupRestore
output output
end end
def pretty_logs(logs)
logs.join("\n")
end
end end
end end