glacier_on_rails 0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +21 -0
  6. data/Gemfile.lock +231 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +98 -0
  9. data/Rakefile +13 -0
  10. data/app/assets/images/glacier_on_rails/.gitkeep +0 -0
  11. data/app/assets/javascripts/glacier_on_rails/application.js +15 -0
  12. data/app/assets/stylesheets/glacier_on_rails/application.css +13 -0
  13. data/app/controllers/glacier_on_rails/application_controller.rb +2 -0
  14. data/app/controllers/glacier_on_rails/application_data_backups_controller.rb +43 -0
  15. data/app/controllers/glacier_on_rails/aws_archive_retrieval_jobs_controller.rb +7 -0
  16. data/app/controllers/glacier_on_rails/aws_sns_subscriptions_controller.rb +40 -0
  17. data/app/helpers/glacier_on_rails/application_helper.rb +4 -0
  18. data/app/models/application_data_backup.rb +84 -0
  19. data/app/models/application_database/base_adapter.rb +7 -0
  20. data/app/models/application_database/mysql_adapter.rb +11 -0
  21. data/app/models/application_database/postgres_adapter.rb +52 -0
  22. data/app/models/application_database.rb +29 -0
  23. data/app/models/application_file.rb +25 -0
  24. data/app/models/aws_backend/sns_subscription.rb +54 -0
  25. data/app/models/aws_backend.rb +113 -0
  26. data/app/models/aws_log.rb +7 -0
  27. data/app/models/glacier_archive.rb +115 -0
  28. data/app/models/glacier_db_archive.rb +27 -0
  29. data/app/models/glacier_file_archive.rb +45 -0
  30. data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_application_data_backup.haml +4 -0
  31. data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_available.haml +3 -0
  32. data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_index.haml +101 -0
  33. data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_local.haml +1 -0
  34. data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_pending.haml +1 -0
  35. data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_ready.haml +2 -0
  36. data/config/initializers/aws_log.rb +1 -0
  37. data/config/initializers/glacier_on_rails.rb +3 -0
  38. data/config/initializers/time_formats.rb +3 -0
  39. data/config/routes.rb +9 -0
  40. data/db/migrate/20170503133854_create_glacier_archives_table.rb +11 -0
  41. data/db/migrate/20170507161533_add_notification_column_to_glacier_archives.rb +5 -0
  42. data/db/migrate/20170509195716_add_archive_retrieval_job_id_to_glacier_archive.rb +5 -0
  43. data/db/migrate/20170602135721_create_application_data_backups.rb +7 -0
  44. data/db/migrate/20170602143524_add_application_data_backup_id_to_glacier_archives.rb +5 -0
  45. data/db/migrate/20170602145526_create_glacier_file_archive_join_table.rb +5 -0
  46. data/db/migrate/20170602150324_add_type_to_glacier_archives.rb +5 -0
  47. data/db/migrate/20170603141909_add_filename_to_glacier_archive.rb +5 -0
  48. data/glacier_on_rails.gemspec +29 -0
  49. data/lib/glacier_on_rails/config.rb +22 -0
  50. data/lib/glacier_on_rails/engine.rb +5 -0
  51. data/lib/glacier_on_rails/version.rb +3 -0
  52. data/lib/glacier_on_rails.rb +5 -0
  53. data/lib/tasks/aws.rake +6 -0
  54. data/script/rails +8 -0
  55. data/spec/dummy/README.rdoc +261 -0
  56. data/spec/dummy/Rakefile +7 -0
  57. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  58. data/spec/dummy/app/assets/javascripts/ractive.js +16621 -0
  59. data/spec/dummy/app/assets/javascripts/underscore.js +5 -0
  60. data/spec/dummy/app/assets/stylesheets/application.css +14 -0
  61. data/spec/dummy/app/controllers/admin_controller.rb +4 -0
  62. data/spec/dummy/app/controllers/application_controller.rb +6 -0
  63. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  64. data/spec/dummy/app/mailers/.gitkeep +0 -0
  65. data/spec/dummy/app/models/.gitkeep +0 -0
  66. data/spec/dummy/app/models/application_record.rb +3 -0
  67. data/spec/dummy/app/models/fake_model.rb +10 -0
  68. data/spec/dummy/app/views/admin/_flash_error.haml +40 -0
  69. data/spec/dummy/app/views/admin/index.haml +3 -0
  70. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  71. data/spec/dummy/config/application.rb +67 -0
  72. data/spec/dummy/config/boot.rb +10 -0
  73. data/spec/dummy/config/database.yml +6 -0
  74. data/spec/dummy/config/environment.rb +5 -0
  75. data/spec/dummy/config/environments/development.rb +40 -0
  76. data/spec/dummy/config/environments/production.rb +70 -0
  77. data/spec/dummy/config/environments/test.rb +40 -0
  78. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  79. data/spec/dummy/config/initializers/file_upload.rb +1 -0
  80. data/spec/dummy/config/initializers/glacier_on_rails.rb +5 -0
  81. data/spec/dummy/config/initializers/inflections.rb +15 -0
  82. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  83. data/spec/dummy/config/initializers/secret_token.rb +8 -0
  84. data/spec/dummy/config/initializers/session_store.rb +8 -0
  85. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  86. data/spec/dummy/config/locales/en.yml +5 -0
  87. data/spec/dummy/config/routes.rb +5 -0
  88. data/spec/dummy/config.ru +4 -0
  89. data/spec/dummy/db/migrate/20170531170208_create_fake_models.rb +8 -0
  90. data/spec/dummy/db/schema.rb +56 -0
  91. data/spec/dummy/lib/assets/.gitkeep +0 -0
  92. data/spec/dummy/lib/constants.rb +2 -0
  93. data/spec/dummy/log/.gitkeep +0 -0
  94. data/spec/dummy/public/404.html +26 -0
  95. data/spec/dummy/public/422.html +26 -0
  96. data/spec/dummy/public/500.html +25 -0
  97. data/spec/dummy/public/favicon.ico +0 -0
  98. data/spec/dummy/script/rails +6 -0
  99. data/spec/features/application_data_backup_spec.rb +166 -0
  100. data/spec/helpers/aws_helper.rb +99 -0
  101. data/spec/helpers/dummy_app_helper.rb +23 -0
  102. data/spec/helpers/http_mock_helpers.rb +137 -0
  103. data/spec/models/application_data_backup_spec.rb +226 -0
  104. data/spec/models/application_file_spec.rb +8 -0
  105. data/spec/models/glacier_db_archive_spec.rb +118 -0
  106. data/spec/models/glacier_file_archive_spec.rb +56 -0
  107. data/spec/models/postgres_adapter_spec.rb +46 -0
  108. data/spec/rails_helper.rb +59 -0
  109. data/spec/spec_helper.rb +110 -0
  110. data/spec/support/wait_for_ajax.rb +22 -0
  111. metadata +308 -0
@@ -0,0 +1,52 @@
1
+ class ApplicationDatabase::PostgresAdapter < ApplicationDatabase::BaseAdapter
2
+ class PgDumpCmdMissing < StandardError; end
3
+ class PgRestoreCmdMissing < StandardError; end
4
+ class PgRestoreFileMissing < StandardError; end
5
+
6
+ RestoreExclusions = %w{ application_data_backups
7
+ glacier_archives
8
+ SCHEMA }
9
+
10
+ RestoreList = GlacierArchive::BackupFileDir.join('restore.list')
11
+
12
+ def contents
13
+ `#{pg_dump} -w -Fc -U #{db_config['username']} #{db_config['database']}`
14
+ end
15
+
16
+ def restore(file)
17
+ raise PgRestoreFileMissing unless File.exists? file
18
+ restore_from_list(file)
19
+ end
20
+
21
+ private
22
+ def generate_object_restoral_list(file)
23
+ # generate the raw list of all objects
24
+ system("#{pg_restore} --clean -n public -l #{file} > #{RestoreList}")
25
+ # remove objects from the list that are enumerated in RestoreExclusions
26
+ retained_lines = File.readlines(RestoreList).select{|line| !line.match(/#{RestoreExclusions.join("|")}/)}
27
+ File.open RestoreList, 'w' do |file|
28
+ file.write(retained_lines.join)
29
+ end
30
+ end
31
+
32
+ def restore_from_list(file)
33
+ generate_object_restoral_list(file)
34
+ result = `#{pg_restore} --clean -n public -U #{db_config['username']} --dbname=#{db_config['database']} -L #{RestoreList} #{file} 2>&1`
35
+ `rm #{RestoreList} && rm #{file}` if $?.exitstatus.zero?
36
+ AwsLog.error result unless $?.exitstatus.zero?
37
+ $?.exitstatus.zero?
38
+ end
39
+
40
+ def pg_dump
41
+ dump_cmd = `which pg_dump`.strip
42
+ raise PgDumpCmdMissing if dump_cmd.blank?
43
+ dump_cmd
44
+ end
45
+
46
+ def pg_restore
47
+ restore_cmd = `which pg_restore`.strip
48
+ raise PgRestoreCmdMissing if restore_cmd.blank?
49
+ restore_cmd
50
+ end
51
+
52
+ end
@@ -0,0 +1,29 @@
1
+ class ApplicationDatabase
2
+ class MissingConfigurationKeys < StandardError
3
+ def initialize(missing_keys)
4
+ msg = "#{missing_keys.join(' and ')} must be specified in config/database.yml"
5
+ super(msg)
6
+ end
7
+ end
8
+
9
+ attr_accessor :adapter
10
+ RequiredKeys = %w{username database adapter}
11
+
12
+ def initialize
13
+ @adapter = case db_config["adapter"]
14
+ when "postgresql"
15
+ PostgresAdapter.new(db_config)
16
+ when "mysql"
17
+ MysqlAdapter.new(db_config)
18
+ end
19
+ end
20
+
21
+ def db_config
22
+ config = ActiveRecord::Base.configurations[Rails.env]
23
+ missing_keys = RequiredKeys - config.keys
24
+ raise MissingConfigurationKeys.new(missing_keys) unless missing_keys.empty?
25
+ config
26
+ end
27
+
28
+ delegate :contents, :restore, :to => :adapter
29
+ end
@@ -0,0 +1,25 @@
1
+ require 'glacier_on_rails/config'
2
+ class ApplicationFile
3
+ attr_accessor :file
4
+ def initialize(file)
5
+ # file may be a Pathname instance or a string
6
+ @file = file.to_s
7
+ end
8
+
9
+ def contents
10
+ ActiveSupport::Gzip.compress(File.read(file))
11
+ end
12
+
13
+ def filename
14
+ File.basename(file)
15
+ end
16
+
17
+ def self.list
18
+ files.map{|f| new(f) }.map(&:filename)
19
+ end
20
+
21
+ def self.files
22
+ Dir.glob(GlacierOnRails::Config.attached_files_directory.join('*'))
23
+ end
24
+
25
+ end
@@ -0,0 +1,54 @@
1
+ class AwsBackend
2
+ class SnsSubscription
3
+ attr_accessor :resp
4
+ # format is: "arn:aws:sns:region:account-id:topicname"
5
+ # see http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#arn-syntax-glacier
6
+ Topic_ARN = "arn:aws:sns:#{AwsBackend::Config.aws_region}:#{::AWS_ACCOUNT_ID}:retrieve_archive"
7
+ #Resource_ARN = ""
8
+
9
+ def initialize
10
+ client = Aws::SNS::Client.new(:region => AwsBackend::Config.aws_region, :credentials => credentials)
11
+ sns = Aws::SNS::Resource.new(:region => AwsBackend::Config.aws_region)
12
+
13
+ topic = sns.create_topic(name: 'retrieve_archive')
14
+ topic.set_attributes({
15
+ attribute_name: "Policy",
16
+ attribute_value: policy
17
+ })
18
+
19
+ @resp = client.subscribe({
20
+ topic_arn: Topic_ARN,
21
+ protocol: "https", # required
22
+ endpoint: GlacierOnRails::Engine.routes.url_helpers.aws_subscription_notify_url
23
+ })
24
+ rescue Exception => e
25
+ puts "AWS Error: #{e.message}"
26
+ end
27
+
28
+ private
29
+ def credentials
30
+ Aws::SharedCredentials.new(:profile_name => AwsBackend::Config.profile_name)
31
+ end
32
+
33
+ def policy
34
+ # see http://docs.aws.amazon.com/sdk-for-ruby/v2/developer-guide/sns-example-enable-resource.html
35
+ "{
36
+ 'Version':'2014-05-06',
37
+ 'Id':'__default_policy_ID',
38
+ 'Statement':[{
39
+ 'Sid':'__default_statement_ID',
40
+ 'Effect':'Allow',
41
+ 'Principal':{
42
+ 'AWS':'*'
43
+ },
44
+ 'Action':['SNS:Publish'],
45
+ 'Resource':'#{Topic_ARN}',
46
+ 'Condition':{
47
+ 'ArnEquals':{
48
+ 'AWS:SourceArn': #{Resource_ARN}}
49
+ }
50
+ }]
51
+ }"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,113 @@
1
+ require 'glacier_on_rails/config'
2
+
3
+ class AwsBackend
4
+ class ArchiveRetrievalNotReady < StandardError; end
5
+ class Config < GlacierOnRails::Config; end
6
+ include Singleton
7
+
8
+ attr_accessor :client, :error_message
9
+
10
+ def initialize
11
+ @client = Aws::Glacier::Client.new(:region => Config.aws_region, :credentials => AwsBackend.credentials)
12
+ unless vault_list.map(&:vault_name).include?(::SITE_NAME)
13
+ @client.create_vault({:account_id => "-", :vault_name => ::SITE_NAME})
14
+ end
15
+ end
16
+
17
+ def self.credentials
18
+ Aws::SharedCredentials.new(:profile_name => Config.profile_name)
19
+ end
20
+
21
+ def vault_list
22
+ vault_list_info = @client.list_vaults({:account_id => "-"})
23
+ vault_list_info.vault_list
24
+ end
25
+
26
+ def delete_archive(archive)
27
+ begin
28
+ response = client.delete_archive({
29
+ account_id: "-",
30
+ archive_id: archive.archive_id,
31
+ vault_name: ::SITE_NAME
32
+ })
33
+ AwsLog.info "Delete archive response: #{response}"
34
+ response
35
+ rescue Aws::Glacier::Errors::ServiceError => e
36
+ AwsLog.error "Failed to delete archive with: #{e.class}: #{e.message}"
37
+ end
38
+ end
39
+
40
+ def create_archive(archive_contents)
41
+ description = "backup of postgres database"
42
+ begin
43
+ resp = client.upload_archive({
44
+ account_id: "-",
45
+ archive_description: description,
46
+ body: archive_contents,
47
+ checksum: checksum(archive_contents),
48
+ vault_name: ::SITE_NAME
49
+ })
50
+ AwsLog.info "Create archive response: #{resp}"
51
+ resp
52
+ rescue Aws::Glacier::Errors::ServiceError => e
53
+ self.error_message = "Failed to create archive with: #{e.class}: #{e.message}"
54
+ AwsLog.error error_message
55
+ false
56
+ end
57
+ end
58
+
59
+ # archive is a GlacierArchive instance from the database
60
+ # response looks like this:
61
+ # <struct Aws::Glacier::Types::InitiateJobOutput location="/918359762546/vaults/demo/jobs/krCLWk6m7NJWppiy2SxdhP60f98PdrdaZBfhdDTZufrAkoh-ikrvb_NA0Q1vg2WcAhzZLL92kiwjOijUEDh0U7X09YQK", job_id="krCLWk6m7NJWppiy2SxdhP60f98PdrdaZBfhdDTZufrAkoh-ikrvb_NA0Q1vg2WcAhzZLL92kiwjOijUEDh0U7X09YQK">
62
+ def retrieve_archive(archive)
63
+ response = client.initiate_job({ account_id: "-", # required
64
+ vault_name: ::SITE_NAME, # required
65
+ job_parameters: {
66
+ type: "archive-retrieval", # valid types are "archive-retrieval" and "inventory-retrieval"
67
+ archive_id: archive.archive_id,
68
+ description: "put anything here",
69
+ sns_topic: SnsSubscription::Topic_ARN,
70
+ tier: "Standard"# it's the default, but put it here to be explicit
71
+ }
72
+ })
73
+ AwsLog.info "Retrieve archive response: #{response}"
74
+ response
75
+ rescue Aws::Glacier::Errors::ServiceError => e
76
+ self.error_message = "Failed to initiate archive retrieval with: #{e.class}: #{e.message}"
77
+ AwsLog.error error_message
78
+ false
79
+ end
80
+
81
+ # archive is a GlacierArchive instance from the database
82
+ def get_job_output(archive)
83
+ raise ArchiveRetrievalNotReady unless archive.retrieval_status == 'ready'
84
+ params = {
85
+ :response_target => filepath(archive),
86
+ :account_id => '-',
87
+ :vault_name => ::SITE_NAME,
88
+ :job_id => archive.archive_retrieval_job_id
89
+ }
90
+ AwsLog.info("AWS Backend get job output request with: #{params}")
91
+ resp = client.get_job_output(params)
92
+ AwsLog.info("AWS Backend response to get job output request: #{resp}")
93
+ resp
94
+ rescue ArchiveRetrievalNotReady
95
+ AwsLog.error "Get job output failed, archive status is not 'ready'"
96
+ false
97
+ end
98
+
99
+ private
100
+
101
+ def checksum(contents)
102
+ tree_hash = Aws::TreeHash.new
103
+ tree_hash.update(contents)
104
+ tree_hash.digest
105
+ end
106
+
107
+ def filepath(archive)
108
+ path = archive.backup_file
109
+ FileUtils.makedirs(File.dirname(path))
110
+ path
111
+ end
112
+
113
+ end
@@ -0,0 +1,7 @@
1
+ class AwsLog
2
+ LogFile = File.join(Rails.root, 'log', 'aws.log')
3
+ class << self
4
+ cattr_accessor :logger
5
+ delegate :debug, :info, :warn, :error, :fatal, :to => :logger
6
+ end
7
+ end
@@ -0,0 +1,115 @@
1
+ # GlacierArchive lifecycle:
2
+ # ACTION | IMPLEMENTED BY | PARAMETER | STATUS INDICATION | STATUS
3
+ # create | AwsBackend#create_archive | | default attributes | available
4
+ # initiate retrieval job | AwsBackend#retrieve_archive | archive_id | archive_retrieval_job_id | pending
5
+ # SNS notification received | AwsSnsSubscriptionsController | archive_retrieval_job_id | notification attribute | ready
6
+ # fetch archive | AwsArchiveController#fetch | archive_retrieval_job_id | retrieved file exists locally | local
7
+ # | | | original file exists locally | exists
8
+ class GlacierArchive < ActiveRecord::Base
9
+ class RestoreFail < StandardError; end
10
+
11
+ default_scope ->{ order("created_at asc") }
12
+
13
+ BackupFileDir = Rails.root.join('tmp','aws')
14
+
15
+ before_create do |archive|
16
+ # create the archive at AWS Glacier
17
+ # and save the metadata in the GlacierArchive instance
18
+ # archive_contents provided by subclass (GlacierDbArchive or GlacierFileArchive)
19
+ if archive_contents && (resp = aws.create_archive(archive_contents))
20
+ archive.attributes = resp.to_h
21
+ else
22
+ archive.errors.add(:base, aws.error_message)
23
+ throw :abort
24
+ end
25
+ end
26
+
27
+ after_destroy do |archive|
28
+ archive.remove_local_backup_copy
29
+ end
30
+
31
+ before_destroy do |archive|
32
+ archive.destroy_archive
33
+ end
34
+
35
+ def destroy_archive
36
+ begin
37
+ response = aws.delete_archive(self)
38
+ AwsLog.info(response)
39
+ true
40
+ rescue Aws::Glacier::Errors::ServiceError => e
41
+ AwsLog.error("Delete archived failed with :#{e.class}: #{e.message}")
42
+ false
43
+ end
44
+ end
45
+
46
+ def initiate_retrieve_job
47
+ if resp = aws.retrieve_archive(self)
48
+ update_attribute(:archive_retrieval_job_id, resp[:job_id])
49
+ else
50
+ errors.add(:base, aws.error_message)
51
+ end
52
+ end
53
+
54
+ def fetch_archive
55
+ begin
56
+ response = aws.get_job_output(self)
57
+ AwsLog.info(response.to_h) if response
58
+ true
59
+ rescue Aws::Glacier::Errors::ServiceError => e
60
+ self.errors.add(:base, e.message)
61
+ AwsLog.error("Fetch archive failed with: #{e.class}: #{e.message}")
62
+ false
63
+ ensure
64
+ reset_retrieval_status
65
+ end
66
+ end
67
+
68
+ def retrieval_status
69
+ exists_status || local_status || ready_status || pending_status || 'available'
70
+ end
71
+
72
+ def backup_file
73
+ BackupFileDir.join(filename)
74
+ end
75
+
76
+ protected
77
+
78
+ def remove_local_backup_copy
79
+ FileUtils.rm(backup_file) if local_status?
80
+ end
81
+
82
+ private
83
+
84
+ def aws
85
+ AwsBackend.instance
86
+ end
87
+
88
+ def reset_retrieval_status
89
+ # because error messages will be reset when update_attributes is used
90
+ update_columns(:archive_retrieval_job_id => nil, :notification => nil)
91
+ end
92
+
93
+ def exists_status
94
+ 'exists' if exists_status?
95
+ end
96
+
97
+ def local_status?
98
+ File.exists? backup_file
99
+ end
100
+
101
+ # archive_retrieval job output has been retrieved
102
+ def local_status
103
+ 'local' if local_status?
104
+ end
105
+
106
+ # ready to retrieve archive_retrieve job output
107
+ def ready_status
108
+ 'ready' if notification
109
+ end
110
+
111
+ # archive_retrieve job has been initiated, but notification not yet received
112
+ def pending_status
113
+ 'pending' if archive_retrieval_job_id
114
+ end
115
+ end
@@ -0,0 +1,27 @@
1
+ class GlacierDbArchive < GlacierArchive
2
+ belongs_to :application_data_backup
3
+
4
+ before_create do |db_archive|
5
+ db_archive.filename = GlacierDbArchive.filename_from_time
6
+ end
7
+
8
+ def self.filename_from_time
9
+ Time.now.strftime("%Y_%m_%d_%H_%M_%S_%L.sql").to_s
10
+ end
11
+
12
+ def archive_contents
13
+ ApplicationDatabase.new.contents
14
+ rescue ApplicationDatabase::MissingConfigurationKeys
15
+ nil
16
+ end
17
+
18
+ # for file archive, true inhibits fetching archive from AWS
19
+ # however we always want to fetch the db archive
20
+ def exists_status?
21
+ false
22
+ end
23
+
24
+ def restore
25
+ raise RestoreFail unless ApplicationDatabase.new.restore(backup_file)
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ require 'glacier_on_rails/config'
2
+
3
+ class GlacierFileArchive < GlacierArchive
4
+ has_and_belongs_to_many :application_data_backups, join_table: 'application_data_backups_glacier_file_archives'
5
+
6
+ def file=(file)
7
+ self.filename = File.basename(file)
8
+ end
9
+
10
+ def file
11
+ GlacierOnRails::Config.attached_files_directory.join(filename)
12
+ end
13
+
14
+ def initiate_retrieve_job
15
+ # don't retrieve the file if it's in the filesystem already
16
+ # based on the assumption that files are immutable
17
+ super unless File.exists?(file)
18
+ end
19
+
20
+ # the bang method creates the instances if they didn't already exist
21
+ def self.all!
22
+ in_filesystem.collect do |file|
23
+ find_or_create_by(:filename => file)
24
+ end
25
+ end
26
+
27
+ def archive_contents
28
+ ApplicationFile.new(file).contents
29
+ end
30
+
31
+ def exists_status?
32
+ File.exists? file
33
+ end
34
+
35
+ def restore
36
+ FileUtils.mv(backup_file, file)
37
+ rescue Errno::ENOENT # usually b/c backup_file not found
38
+ raise RestoreFail
39
+ end
40
+
41
+ private
42
+ def self.in_filesystem
43
+ ApplicationFile.list
44
+ end
45
+ end
@@ -0,0 +1,4 @@
1
+ %tr.application_data_backup{:outro => :fade}
2
+ %td.date{:style => 'width:120px;'}= application_data_backup.created_at.to_date.to_s(:default)
3
+ = render :partial => "glacier_on_rails/aws_archive_retrieval_jobs/#{application_data_backup.retrieval_status}", :locals => {:application_data_backup => application_data_backup, :fetch_fail => local_assigns[:fetch_fail] || false}
4
+ %td.action_link#delete_archive{:onclick => "delete_archive(#{application_data_backup.id})", :style => 'padding-left: 20px;'} delete
@@ -0,0 +1,3 @@
1
+ %td#initiate_retrieval.action_link{:onclick=>"initiate_retrieval(#{application_data_backup.id})"} initiate retrieval
2
+ - if local_assigns[:fetch_fail]
3
+ %td#fetch_fail_message{:style => 'padding-left:25px;'} Fetch archive failed. The job probably expired. Re-initiate archive retrieval.
@@ -0,0 +1,101 @@
1
+ :css
2
+ .disabled {
3
+ pointer-events : none;
4
+ }
5
+ #scrim {
6
+ background-color : black;
7
+ opacity : 0.2;
8
+ position : absolute;
9
+ top : 0;
10
+ width : 100%;
11
+ height : 100%;
12
+ }
13
+ #spinner {
14
+ position:absolute;
15
+ color: white;
16
+ top:50%;
17
+ left:50%;
18
+ transform:translate(-50%,-50%)
19
+ }
20
+ %h2 AWS Glacier Archives
21
+ %table#application_data_backups
22
+ - if application_data_backups.empty?
23
+ %p None have been created
24
+ - else
25
+ = render :partial => 'glacier_on_rails/aws_archive_retrieval_jobs/application_data_backup', :collection => application_data_backups, :as => :application_data_backup
26
+
27
+ %div{:style => 'display:flex; margin-top:12px;'}
28
+ %div.action_link#backup_now{:onclick => 'backup_now()'} backup now
29
+
30
+ :coffeescript
31
+ @backup_now = ->
32
+ url = "#{glacier_on_rails.aws_create_archive_path}"
33
+ $.ajax
34
+ beforeSend : show_spinner
35
+ method : 'post'
36
+ url : url
37
+ success : add
38
+ complete : hide_spinner
39
+ show_spinner = (jqxhr, settings)->
40
+ $('a, .action_link').addClass('disabled')
41
+ $('body').append("<div id='scrim'></div><i id='spinner' class='fa fa-pulse fa-spinner fa-3x' />")
42
+ true
43
+ hide_spinner = ->
44
+ $('a').removeClass('disabled')
45
+ $('.action_link').removeClass('disabled')
46
+ $('#scrim').remove()
47
+ $('#spinner').remove()
48
+ add = (data, status, jqxhr)->
49
+ $('#application_data_backups').append(data)
50
+ @initiate_retrieval = (id)->
51
+ data = {application_data_backup_id : id}
52
+ url = "#{glacier_on_rails.aws_archive_retrieval_job_create_path}"
53
+ $.ajax
54
+ beforeSend : show_spinner
55
+ complete : hide_spinner
56
+ url : url
57
+ method : 'post'
58
+ data : data
59
+ context : $(event.target).closest('tr')
60
+ success : update
61
+ @fetch_archive = (id)->
62
+ url = "#{glacier_on_rails.aws_fetch_archive_path}"
63
+ data = {application_data_backup_id : id}
64
+ context = $(event.target).closest('tr')
65
+ $.ajax
66
+ beforeSend : show_spinner
67
+ complete : hide_spinner
68
+ method : 'post'
69
+ url : url
70
+ data : data
71
+ context : $(event.target).closest('tr')
72
+ success : update
73
+ update = (data, status, jqxhr)->
74
+ @replaceWith(data)
75
+ @restore = (id)->
76
+ url = "#{glacier_on_rails.aws_restore_archive_path}"
77
+ $.ajax
78
+ beforeSend : show_spinner
79
+ complete : hide_spinner
80
+ method : 'post'
81
+ url : url
82
+ data : {application_data_backup_id : id}
83
+ context : $(event.target).closest('tr')
84
+ success : restore_success #jquery handles js for error response
85
+ restore_success = (data, status, jqxhr)->
86
+ @replaceWith(data)
87
+ backup_date = $(data).find('.date').text()
88
+ flash.confirm('Database restored with the '+backup_date+' backup')
89
+ @delete_archive = (id)->
90
+ url = "#{glacier_on_rails.aws_destroy_archive_path}"
91
+ context = $(event.target).closest('tr')
92
+ $.ajax
93
+ beforeSend : show_spinner
94
+ complete : hide_spinner
95
+ method : 'delete'
96
+ url : url
97
+ data : {application_data_backup_id : id}
98
+ context : context
99
+ success : remove_archive
100
+ remove_archive = ->
101
+ @remove()
@@ -0,0 +1 @@
1
+ %td.restore_database.action_link{:onclick=>"restore(#{application_data_backup.id})"} restore
@@ -0,0 +1 @@
1
+ %td.retrieval_pending retrieval pending
@@ -0,0 +1,2 @@
1
+ %td retrieval ready
2
+ %td#fetch_archive.action_link{:style=>'padding-left:25px;',:onclick => "fetch_archive(#{application_data_backup.id})"} fetch archive
@@ -0,0 +1 @@
1
+ AwsLog.logger = Logger.new(AwsLog::LogFile)
@@ -0,0 +1,3 @@
1
+ GlacierOnRails::Engine.config.paths["db/migrate"].expanded.each do |expanded_path|
2
+ Rails.application.config.paths["db/migrate"] << expanded_path
3
+ end
@@ -0,0 +1,3 @@
1
+ Time::DATE_FORMATS[:short_date_with_year] = "%b %d %Y"
2
+ Time::DATE_FORMATS[:short_time] = "%I:%M %p"
3
+
data/config/routes.rb ADDED
@@ -0,0 +1,9 @@
1
+ #Rails.application.routes.draw do
2
+ GlacierOnRails::Engine.routes.draw do
3
+ post :aws_subscription_notify, :to => 'aws_sns_subscriptions#create'
4
+ post :aws_archive_retrieval_job_create, :to => 'aws_archive_retrieval_jobs#create'
5
+ post :aws_fetch_archive, :to => 'application_data_backups#fetch'
6
+ delete :aws_destroy_archive, :to => 'application_data_backups#destroy'
7
+ post :aws_restore_archive, :to => 'application_data_backups#restore'
8
+ post :aws_create_archive, :to => 'application_data_backups#create'
9
+ end
@@ -0,0 +1,11 @@
1
+ class CreateGlacierArchivesTable < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :glacier_archives do |t|
4
+ t.text :description
5
+ t.text :archive_id
6
+ t.text :checksum
7
+ t.text :location
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ class AddNotificationColumnToGlacierArchives < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :glacier_archives, :notification, :json
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddArchiveRetrievalJobIdToGlacierArchive < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :glacier_archives, :archive_retrieval_job_id, :string
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ class CreateApplicationDataBackups < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_table :application_data_backups do |t|
4
+ t.timestamps
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class AddApplicationDataBackupIdToGlacierArchives < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :glacier_archives, :application_data_backup_id, :integer
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class CreateGlacierFileArchiveJoinTable < ActiveRecord::Migration[5.0]
2
+ def change
3
+ create_join_table :application_data_backups, :glacier_file_archives
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddTypeToGlacierArchives < ActiveRecord::Migration[5.0]
2
+ def change
3
+ add_column :glacier_archives, :type, :string
4
+ end
5
+ end