From 085f7997a223008114b008ba157b21080b6516c7 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 12 Mar 2014 16:23:47 -0400 Subject: [PATCH] FEATURE: Upload backups to S3 when complete. --- app/models/backup.rb | 38 ++++++++++++++++++++++++++++++++++++ config/locales/server.en.yml | 2 ++ config/site_settings.yml | 5 +++++ lib/export/exporter.rb | 8 ++++++++ spec/models/backup_spec.rb | 29 +++++++++++++++++++++++++++ 5 files changed, 82 insertions(+) diff --git a/app/models/backup.rb b/app/models/backup.rb index e871dded713..e63e03be573 100644 --- a/app/models/backup.rb +++ b/app/models/backup.rb @@ -25,6 +25,25 @@ class Backup def remove File.delete(@path) if File.exists?(path) + after_remove_hook + end + + def after_create_hook + upload_to_s3 if SiteSetting.enable_s3_backups? + end + + def after_remove_hook + remove_from_s3 if SiteSetting.enable_s3_backups? + end + + def upload_to_s3 + return unless fog_directory + fog_directory.files.create(key: @filename, public: false, body: File.read(@path)) + end + + def remove_from_s3 + return unless fog + fog.delete_object(SiteSetting.s3_backup_bucket, @filename) end def self.base_directory @@ -49,4 +68,23 @@ class Backup all_backups[SiteSetting.maximum_backups..-1].each {|b| b.remove} end + private + + def fog + return @fog if @fog + return unless SiteSetting.s3_access_key_id.present? && + SiteSetting.s3_secret_access_key.present? && + SiteSetting.s3_backup_bucket.present? + require 'fog' + @fog = Fog::Storage.new(provider: 'AWS', + aws_access_key_id: SiteSetting.s3_access_key_id, + aws_secret_access_key: SiteSetting.s3_secret_access_key) + end + + def fog_directory + return @fog_directory if @fog_directory + return unless fog + @fog_directory ||= fog.directories.get(SiteSetting.s3_backup_bucket) + end + end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 37618b336f2..352149898c7 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -714,6 +714,8 @@ en: allow_restore: "Allow restore, which can replace ALL site data! Leave false unless you plan to do restore a backup" maximum_backups: "The maximum amount of backups to keep on disk. Older backups are automatically deleted" backup_daily: "Automatically create a site backup once a day" + enable_s3_backups: "Upload backups to S3 when complete. Make sure you have filled in your s3 credentials." + s3_backup_bucket: "The remote bucket to hold backups. WARNING: Make sure it is a private bucket." active_user_rate_limit_secs: "How frequently we update the 'last_seen_at' field, in seconds" previous_visit_timeout_hours: "How long a visit lasts before we consider it the 'previous' visit, in hours" diff --git a/config/site_settings.yml b/config/site_settings.yml index 47ea9e314a7..1b7c0829c0f 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -419,6 +419,11 @@ backups: backup_daily: client: false default: false + enable_s3_backups: + client: false + default: false + s3_backup_bucket: + client: false uncategorized: diff --git a/lib/export/exporter.rb b/lib/export/exporter.rb index e7e471b43ff..122d8407998 100644 --- a/lib/export/exporter.rb +++ b/lib/export/exporter.rb @@ -38,6 +38,8 @@ module Export create_archive + after_create_hook + remove_old notify_user @@ -236,6 +238,12 @@ module Export `gzip --best #{tar_filename}` end + def after_create_hook + log "Executing the after_create_hook for the backup" + backup = Backup.create_from_filename("#{File.basename(@archive_basename)}.tar.gz") + backup.after_create_hook + end + def notify_user log "Notifying '#{@user.username}' of the success of the backup..." # NOTE: will only notify if @user != Discourse.site_contact_user diff --git a/spec/models/backup_spec.rb b/spec/models/backup_spec.rb index 746cb3b3063..19f372cf5a3 100644 --- a/spec/models/backup_spec.rb +++ b/spec/models/backup_spec.rb @@ -27,5 +27,34 @@ describe Backup do Backup.remove_old end end + + context ".after_create_hook" do + it "calls upload_to_s3 if the SiteSetting is true" do + SiteSetting.stubs(:enable_s3_backups?).returns(true) + b1.expects(:upload_to_s3).once + b1.after_create_hook + end + + it "calls upload_to_s3 if the SiteSetting is false" do + SiteSetting.stubs(:enable_s3_backups?).returns(false) + b1.expects(:upload_to_s3).never + b1.after_create_hook + end + end + + context ".after_remove_hook" do + it "calls remove_from_s3 if the SiteSetting is true" do + SiteSetting.stubs(:enable_s3_backups?).returns(true) + b1.expects(:remove_from_s3).once + b1.after_remove_hook + end + + it "calls remove_from_s3 if the SiteSetting is false" do + SiteSetting.stubs(:enable_s3_backups?).returns(false) + b1.expects(:remove_from_s3).never + b1.after_remove_hook + end + end + end