diff --git a/app/jobs/scheduled/drop_backup_schema.rb b/app/jobs/scheduled/drop_backup_schema.rb new file mode 100644 index 00000000000..1908c72bf7a --- /dev/null +++ b/app/jobs/scheduled/drop_backup_schema.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Jobs + class DropBackupSchema < ::Jobs::Scheduled + every 1.day + + def execute(_) + BackupRestore::DatabaseRestorer.drop_backup_schema + end + end +end diff --git a/app/models/backup_metadata.rb b/app/models/backup_metadata.rb index db4bf7e9ad4..b7f3a1c4c62 100644 --- a/app/models/backup_metadata.rb +++ b/app/models/backup_metadata.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class BackupMetadata < ActiveRecord::Base + LAST_RESTORE_DATE = "last_restore_date" + def self.value_for(name) where(name: name).pluck_first(:value).presence end diff --git a/lib/backup_restore/database_restorer.rb b/lib/backup_restore/database_restorer.rb index 1e2c65504ac..b3cb9ab0c8b 100644 --- a/lib/backup_restore/database_restorer.rb +++ b/lib/backup_restore/database_restorer.rb @@ -8,6 +8,7 @@ module BackupRestore MAIN_SCHEMA = "public" BACKUP_SCHEMA = "backup" + DROP_BACKUP_SCHEMA_AFTER_DAYS = 7 def initialize(logger, current_db) @logger = logger @@ -25,6 +26,8 @@ module BackupRestore restore_dump migrate_database reconnect_database + + self.class.update_last_restore_date end def rollback @@ -42,6 +45,20 @@ module BackupRestore drop_created_discourse_functions end + def self.drop_backup_schema + if backup_schema_dropable? + ActiveRecord::Base.connection.drop_schema(BACKUP_SCHEMA) + end + end + + def self.update_last_restore_date + BackupMetadata.where(name: BackupMetadata::LAST_RESTORE_DATE).delete_all + BackupMetadata.create!( + name: BackupMetadata::LAST_RESTORE_DATE, + value: Time.zone.now.iso8601 + ) + end + protected def restore_dump @@ -178,5 +195,20 @@ module BackupRestore rescue => ex log "Something went wrong while dropping functions from the discourse_functions schema", ex end + + def self.backup_schema_dropable? + return false unless ActiveRecord::Base.connection.schema_exists?(BACKUP_SCHEMA) + + last_restore_date = BackupMetadata.value_for(BackupMetadata::LAST_RESTORE_DATE) + + if last_restore_date.present? + last_restore_date = Time.zone.parse(last_restore_date) + return last_restore_date + DROP_BACKUP_SCHEMA_AFTER_DAYS.days < Time.zone.now + end + + update_last_restore_date + false + end + private_class_method :backup_schema_dropable? end end diff --git a/spec/lib/backup_restore/database_restorer_spec.rb b/spec/lib/backup_restore/database_restorer_spec.rb index 5f595522d94..6d79dff9ef8 100644 --- a/spec/lib/backup_restore/database_restorer_spec.rb +++ b/spec/lib/backup_restore/database_restorer_spec.rb @@ -67,6 +67,14 @@ describe BackupRestore::DatabaseRestorer do subject.restore("foo.sql") end + it "stores the date of the last restore" do + date_string = "2020-01-10T17:38:27Z" + freeze_time(Time.parse(date_string)) + execute_stubbed_restore + + expect(BackupMetadata.value_for(BackupMetadata::LAST_RESTORE_DATE)).to eq(date_string) + end + context "with real psql" do after do psql = BackupRestore::DatabaseRestorer.psql_command @@ -176,4 +184,53 @@ describe BackupRestore::DatabaseRestorer do subject.clean_up end end + + describe ".drop_backup_schema" do + subject { BackupRestore::DatabaseRestorer } + + context "when no backup schema exists" do + it "doesn't do anything" do + ActiveRecord::Base.connection.expects(:schema_exists?).with("backup").returns(false) + ActiveRecord::Base.connection.expects(:drop_schema).never + + subject.drop_backup_schema + end + end + + context "when a backup schema exists" do + before do + ActiveRecord::Base.connection.expects(:schema_exists?).with("backup").returns(true) + end + + it "drops the schema when the last restore was long ago" do + ActiveRecord::Base.connection.expects(:drop_schema).with("backup") + + freeze_time(8.days.ago) do + subject.update_last_restore_date + end + + subject.drop_backup_schema + end + + it "doesn't drop the schema when the last restore was recently" do + ActiveRecord::Base.connection.expects(:drop_schema).with("backup").never + + freeze_time(6.days.ago) do + subject.update_last_restore_date + end + + subject.drop_backup_schema + end + + it "stores the current date when there is no record of the last restore" do + ActiveRecord::Base.connection.expects(:drop_schema).with("backup").never + + date_string = "2020-01-08T17:38:27Z" + freeze_time(Time.parse(date_string)) + + subject.drop_backup_schema + expect(BackupMetadata.value_for(BackupMetadata::LAST_RESTORE_DATE)).to eq(date_string) + end + end + end end