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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e295a934008444dea879a7321b54127bff14fdd4
4
+ data.tar.gz: e05e01e722128b66382cd7fe070c3f3c52b5e131
5
+ SHA512:
6
+ metadata.gz: 73eb06569b54dbd7b05fb855b9c4e4272a3c9db778562098504da9a7bd3ab693d5efbd08bb4a819c73a8b45cbb8a58004908a64454c699ac6f85a2076afef033
7
+ data.tar.gz: b656b92d745483062fb790663a6cd6ea873e0f898dd2025e481fe8833197970ae2e18dfca36838d30b44036e0c23993c548738e3afc7eb9fa36d71f40e88195a
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ .bundle/
2
+ log/*.log
3
+ pkg/
4
+ spec/dummy/db/*.sqlite3
5
+ spec/dummy/log/*.log
6
+ spec/dummy/tmp/
7
+ spec/dummy/.sass-cache
8
+ .DS_Store
9
+ sns_notes.md
10
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.3.1
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Declare your gem's dependencies in glacier_on_rails.gemspec.
4
+ # Bundler will treat runtime dependencies like base dependencies, and
5
+ # development dependencies will be added by default to the :development group.
6
+ gemspec
7
+
8
+ # jquery-rails is used by the dummy application
9
+ gem "jquery-rails"
10
+
11
+ gem "pg"
12
+ # Declare any dependencies that are still in development here instead of in
13
+ # your gemspec. These might include edge Rails or gems from your path or
14
+ # Git. Remember to move these dependencies to your gemspec before releasing
15
+ # your gem to rubygems.org.
16
+
17
+ # To use debugger
18
+ # gem 'debugger'
19
+ gem 'webmock', :git => 'https://github.com/lazylester/webmock.git' # contains a patch... use this fork until the primary sources merges the patch
20
+
21
+ gem "aws-sdk", "~> 2"
data/Gemfile.lock ADDED
@@ -0,0 +1,231 @@
1
+ GIT
2
+ remote: https://github.com/lazylester/webmock.git
3
+ revision: 1e056477da4041174cc1b53f432595fb9e1f565c
4
+ specs:
5
+ webmock (3.0.1)
6
+ addressable (>= 2.3.6)
7
+ crack (>= 0.3.2)
8
+ hashdiff
9
+
10
+ PATH
11
+ remote: .
12
+ specs:
13
+ glacier_on_rails (0.0.1)
14
+ httparty
15
+ rails (~> 5.0.0)
16
+
17
+ GEM
18
+ remote: http://rubygems.org/
19
+ specs:
20
+ actioncable (5.0.2)
21
+ actionpack (= 5.0.2)
22
+ nio4r (>= 1.2, < 3.0)
23
+ websocket-driver (~> 0.6.1)
24
+ actionmailer (5.0.2)
25
+ actionpack (= 5.0.2)
26
+ actionview (= 5.0.2)
27
+ activejob (= 5.0.2)
28
+ mail (~> 2.5, >= 2.5.4)
29
+ rails-dom-testing (~> 2.0)
30
+ actionpack (5.0.2)
31
+ actionview (= 5.0.2)
32
+ activesupport (= 5.0.2)
33
+ rack (~> 2.0)
34
+ rack-test (~> 0.6.3)
35
+ rails-dom-testing (~> 2.0)
36
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
37
+ actionview (5.0.2)
38
+ activesupport (= 5.0.2)
39
+ builder (~> 3.1)
40
+ erubis (~> 2.7.0)
41
+ rails-dom-testing (~> 2.0)
42
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
43
+ activejob (5.0.2)
44
+ activesupport (= 5.0.2)
45
+ globalid (>= 0.3.6)
46
+ activemodel (5.0.2)
47
+ activesupport (= 5.0.2)
48
+ activerecord (5.0.2)
49
+ activemodel (= 5.0.2)
50
+ activesupport (= 5.0.2)
51
+ arel (~> 7.0)
52
+ activesupport (5.0.2)
53
+ concurrent-ruby (~> 1.0, >= 1.0.2)
54
+ i18n (~> 0.7)
55
+ minitest (~> 5.1)
56
+ tzinfo (~> 1.1)
57
+ addressable (2.5.1)
58
+ public_suffix (~> 2.0, >= 2.0.2)
59
+ arel (7.1.4)
60
+ aws-sdk (2.9.14)
61
+ aws-sdk-resources (= 2.9.14)
62
+ aws-sdk-core (2.9.14)
63
+ aws-sigv4 (~> 1.0)
64
+ jmespath (~> 1.0)
65
+ aws-sdk-resources (2.9.14)
66
+ aws-sdk-core (= 2.9.14)
67
+ aws-sigv4 (1.0.0)
68
+ builder (3.2.3)
69
+ byebug (9.0.6)
70
+ capybara (2.4.4)
71
+ mime-types (>= 1.16)
72
+ nokogiri (>= 1.3.3)
73
+ rack (>= 1.0.0)
74
+ rack-test (>= 0.5.4)
75
+ xpath (~> 2.0)
76
+ childprocess (0.6.3)
77
+ ffi (~> 1.0, >= 1.0.11)
78
+ cliver (0.3.2)
79
+ coffee-rails (4.2.1)
80
+ coffee-script (>= 2.2.0)
81
+ railties (>= 4.0.0, < 5.2.x)
82
+ coffee-script (2.4.1)
83
+ coffee-script-source
84
+ execjs
85
+ coffee-script-source (1.12.2)
86
+ concurrent-ruby (1.0.5)
87
+ crack (0.4.3)
88
+ safe_yaml (~> 1.0.0)
89
+ database_cleaner (1.5.3)
90
+ diff-lcs (1.3)
91
+ erubis (2.7.0)
92
+ execjs (2.7.0)
93
+ ffi (1.9.18)
94
+ globalid (0.4.0)
95
+ activesupport (>= 4.2.0)
96
+ haml (4.0.7)
97
+ tilt
98
+ haml-rails (1.0.0)
99
+ actionpack (>= 4.0.1)
100
+ activesupport (>= 4.0.1)
101
+ haml (>= 4.0.6, < 6.0)
102
+ html2haml (>= 1.0.1)
103
+ railties (>= 4.0.1)
104
+ hashdiff (0.3.4)
105
+ html2haml (2.1.0)
106
+ erubis (~> 2.7.0)
107
+ haml (~> 4.0)
108
+ nokogiri (>= 1.6.0)
109
+ ruby_parser (~> 3.5)
110
+ httparty (0.15.5)
111
+ multi_xml (>= 0.5.2)
112
+ i18n (0.8.1)
113
+ jmespath (1.3.1)
114
+ jquery-rails (4.3.1)
115
+ rails-dom-testing (>= 1, < 3)
116
+ railties (>= 4.2.0)
117
+ thor (>= 0.14, < 2.0)
118
+ loofah (2.0.3)
119
+ nokogiri (>= 1.5.9)
120
+ mail (2.6.5)
121
+ mime-types (>= 1.16, < 4)
122
+ method_source (0.8.2)
123
+ mime-types (3.1)
124
+ mime-types-data (~> 3.2015)
125
+ mime-types-data (3.2016.0521)
126
+ mini_portile2 (2.1.0)
127
+ minitest (5.10.1)
128
+ multi_xml (0.6.0)
129
+ mysql2 (0.4.5)
130
+ nio4r (2.0.0)
131
+ nokogiri (1.7.1)
132
+ mini_portile2 (~> 2.1.0)
133
+ pg (0.20.0)
134
+ poltergeist (1.14.0)
135
+ capybara (~> 2.1)
136
+ cliver (~> 0.3.1)
137
+ websocket-driver (>= 0.2.0)
138
+ public_suffix (2.0.5)
139
+ rack (2.0.1)
140
+ rack-test (0.6.3)
141
+ rack (>= 1.0)
142
+ rails (5.0.2)
143
+ actioncable (= 5.0.2)
144
+ actionmailer (= 5.0.2)
145
+ actionpack (= 5.0.2)
146
+ actionview (= 5.0.2)
147
+ activejob (= 5.0.2)
148
+ activemodel (= 5.0.2)
149
+ activerecord (= 5.0.2)
150
+ activesupport (= 5.0.2)
151
+ bundler (>= 1.3.0, < 2.0)
152
+ railties (= 5.0.2)
153
+ sprockets-rails (>= 2.0.0)
154
+ rails-dom-testing (2.0.2)
155
+ activesupport (>= 4.2.0, < 6.0)
156
+ nokogiri (~> 1.6)
157
+ rails-html-sanitizer (1.0.3)
158
+ loofah (~> 2.0)
159
+ railties (5.0.2)
160
+ actionpack (= 5.0.2)
161
+ activesupport (= 5.0.2)
162
+ method_source
163
+ rake (>= 0.8.7)
164
+ thor (>= 0.18.1, < 2.0)
165
+ rake (12.0.0)
166
+ rspec-core (3.5.4)
167
+ rspec-support (~> 3.5.0)
168
+ rspec-expectations (3.5.0)
169
+ diff-lcs (>= 1.2.0, < 2.0)
170
+ rspec-support (~> 3.5.0)
171
+ rspec-mocks (3.5.0)
172
+ diff-lcs (>= 1.2.0, < 2.0)
173
+ rspec-support (~> 3.5.0)
174
+ rspec-rails (3.5.2)
175
+ actionpack (>= 3.0)
176
+ activesupport (>= 3.0)
177
+ railties (>= 3.0)
178
+ rspec-core (~> 3.5.0)
179
+ rspec-expectations (~> 3.5.0)
180
+ rspec-mocks (~> 3.5.0)
181
+ rspec-support (~> 3.5.0)
182
+ rspec-support (3.5.0)
183
+ ruby_parser (3.9.0)
184
+ sexp_processor (~> 4.1)
185
+ rubyzip (1.2.1)
186
+ safe_yaml (1.0.4)
187
+ selenium-webdriver (3.4.0)
188
+ childprocess (~> 0.5)
189
+ rubyzip (~> 1.0)
190
+ websocket (~> 1.0)
191
+ sexp_processor (4.9.0)
192
+ sprockets (3.7.1)
193
+ concurrent-ruby (~> 1.0)
194
+ rack (> 1, < 3)
195
+ sprockets-rails (3.2.0)
196
+ actionpack (>= 4.0)
197
+ activesupport (>= 4.0)
198
+ sprockets (>= 3.0.0)
199
+ thor (0.19.4)
200
+ thread_safe (0.3.6)
201
+ tilt (2.0.7)
202
+ tzinfo (1.2.3)
203
+ thread_safe (~> 0.1)
204
+ websocket (1.2.4)
205
+ websocket-driver (0.6.5)
206
+ websocket-extensions (>= 0.1.0)
207
+ websocket-extensions (0.1.2)
208
+ xpath (2.1.0)
209
+ nokogiri (~> 1.3)
210
+
211
+ PLATFORMS
212
+ ruby
213
+
214
+ DEPENDENCIES
215
+ aws-sdk (~> 2)
216
+ byebug
217
+ capybara (~> 2.4.0)
218
+ coffee-rails
219
+ database_cleaner
220
+ glacier_on_rails!
221
+ haml-rails
222
+ jquery-rails
223
+ mysql2
224
+ pg
225
+ poltergeist
226
+ rspec-rails
227
+ selenium-webdriver
228
+ webmock!
229
+
230
+ BUNDLED WITH
231
+ 1.15.1
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # GlacierOnRails
2
+
3
+ Rails engine with utilities for backup and restore of entire application database to the Amazon AWS Glacier service.
4
+ Includes rake tasks that may be invoked by cron task to archive a daily backup.
5
+ Archives both the database and attached files, where the storage of attached files follows the scheme of the [refile gem](https://github.com/refile/refile). Specifically, attached files are immutable, two files with the same filename are presumed to be identical. If a file exists in the attached files directory, it is not restored from the archive during a restore operation. All files in the attached files directory are archived individually, exactly once.
6
+
7
+ Include this in your application Gemfile:
8
+
9
+ ```
10
+ gem 'glacier_on_rails', :git => 'git://github.com/lazylester/glacier_on_rails.git'
11
+ ```
12
+
13
+ The _index partial is intended for inclusion into a page in the main application.
14
+
15
+ Dependencies required to be present in the main application are:
16
+ * jQuery
17
+ * ractive.js
18
+ * underscore.js
19
+
20
+ Run model test suites with:
21
+ ```
22
+ rspec spec/models
23
+ ```
24
+
25
+ Run feature specs with:
26
+ ```
27
+ rspec spec/features
28
+ ```
29
+
30
+ Run all the tests with:
31
+ ```
32
+ rake
33
+ ```
34
+
35
+ Configure in the main application, in config/initializers/glacier_on_rails.rb:
36
+ ```ruby
37
+ if defined? GlacierOnRails
38
+ require 'glacier_on_rails/config'
39
+ GlacierOnRails::Config.setup do |config|
40
+ config.attached_files_directory = FileUploadLocation.join('store')
41
+ config.aws_region = 'us-east-1'
42
+ end
43
+
44
+ AwsLog.logger.level = 'debug' # possible values are: 'debug', 'info', 'warn', 'error', 'fatal'
45
+ # 'debug' is the most liberal, 'fatal' the most restrictive
46
+ end
47
+ ```
48
+ ## Cron control
49
+ The rake task:
50
+ ```
51
+ rake aws:create_db_archive
52
+ ```
53
+ may be invoked from your cron job in order to create a periodic, automatic backup.
54
+
55
+ ## GlacierArchive model and its lifecycle
56
+
57
+ The database and each of the file attachments are archived and restored individually, this dramatically reduces the resources consumed by backup, including Glacier storage and network bandwidth, since most of the file attachments are likely unchanged between backups, so it is unnecessary to back them up periodically.
58
+
59
+ It is assumed that file attachments to Rails models are handled in the manner of the [refile gem](https://github.com/refile/refile). Each file is given a unique name for storage, and the files are never changed. If a file attached to an ActiveRecord model is replaced, it is given a new unique name. Attached files are individually backed up to AWS Glacier, if an attached file has already been backed up to AWS Glacier, the backup is skipped.
60
+
61
+ Similarly during restoral, if a file exists in the attached files directory, it is not retrieved from AWS Glacier.
62
+
63
+ The ApplicationDataBackup model manages the backup and restoral of the database and all file attachments.
64
+
65
+ The GlacierArchive model has a retrieval_status property, indicating its lifecycle status:
66
+
67
+ Status | Meaning
68
+ ----------|-----------------------------------------------------------------------------------------------------------------------
69
+ exists | The attached file is present in the filesystem, retrieval/restoral presumed unnecessary.
70
+ available | The attached file (or database backup file) has previously been archived to Glacier.
71
+ pending | An archive retrieval job has been initiated at Glacier, notification of completion is awaited (can take a few hours).
72
+ ready | Notification has been received that the archive is ready for download.
73
+ local | The archive has been downloaded and is present locally, available for restoral.
74
+
75
+ ## Database backup and restore
76
+ Database tables application_data_backups and glacier_archives are used to store objects related to this gem. So database restoral is selective and excludes these two tables. This is so that the database can be restored to its version of (say) one week ago, and can subsequently be restored to its version from yesterday, even though the week-ago version did not contain ApplicationDataBackup and GlacierArchiv objects from yesterday.
77
+
78
+ This is achieved using the pg_restore selective restoral facility which first generates a list of objects to be restored, which we edit to remove excluded objects, and then restores the database controlled by the list.
79
+
80
+ ## Restoral of attached files
81
+ If the database is restored to an older version, there may be some attached files in the filesystem that now do not have associated ActiveRecord models. Rather than just delete these, they are moved to a directory called "orphan_files" so that they can be handled manually.
82
+
83
+ ## Logger
84
+ A logger is used, with the log messages appended to in log/aws.log. Consider a rotation strategy for this file.
85
+
86
+ ## AWS credentials
87
+ Stored in a .aws/credentials file in the home directory. (see [AWS sdk documentation](http://docs.aws.amazon.com/sdk-for-ruby/v2/developer-guide/setup-config.html)) With contents:
88
+
89
+ ```markdown
90
+ [default]
91
+ account_id = your_account_id
92
+ aws_access_key_id = your_access_key_id
93
+ aws_secret_access_key = your_aws_secret_access_key
94
+
95
+ ```
96
+
97
+ ## Database access parameters
98
+ The pg_restore command depends on the existence of a file called ~/.pgpass, containing access parameters for the database. (See [Postgres documentation](https://www.postgresql.org/docs/9.3/static/libpq-pgpass.html) for format details).
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "rspec/core/rake_task"
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task :default => :spec
6
+ RSpec::Core::RakeTask.module_eval do
7
+ def pattern
8
+ files = []
9
+ files << File.expand_path( 'spec/features/*_spec.rb', __FILE__ ).to_s
10
+ files << File.expand_path( 'spec/models/*_spec.rb', __FILE__ ).to_s
11
+ end
12
+ end
13
+
File without changes
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,13 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the top of the
9
+ * compiled file, but it's generally better to create a new file per style scope.
10
+ *
11
+ *= require_self
12
+ *= require_tree .
13
+ */
@@ -0,0 +1,2 @@
1
+ class GlacierOnRails::ApplicationController < ApplicationController
2
+ end
@@ -0,0 +1,43 @@
1
+ class GlacierOnRails::ApplicationDataBackupsController < ApplicationController
2
+ before_action do
3
+ @application_data_backup = ApplicationDataBackup.find(params[:application_data_backup_id]) unless params[:application_data_backup_id].nil?
4
+ end
5
+
6
+ def create
7
+ @application_data_backup = ApplicationDataBackup.create
8
+ if @application_data_backup.persisted?
9
+ render :partial => 'glacier_on_rails/aws_archive_retrieval_jobs/application_data_backup',
10
+ :locals => {:application_data_backup => @application_data_backup}
11
+ else
12
+ render :js => "flash.error('failed to create backup');", :status => 500
13
+ end
14
+ end
15
+
16
+ def fetch
17
+ if @application_data_backup.fetch_archive
18
+ render :partial => 'glacier_on_rails/aws_archive_retrieval_jobs/application_data_backup',
19
+ :locals => {:application_data_backup => @application_data_backup}
20
+ else
21
+ render :partial => 'glacier_on_rails/aws_archive_retrieval_jobs/application_data_backup',
22
+ :locals => {:application_data_backup => @application_data_backup, :fetch_fail => true}
23
+ end
24
+ end
25
+
26
+ def restore
27
+ if @application_data_backup.restore
28
+ render :partial => 'glacier_on_rails/aws_archive_retrieval_jobs/application_data_backup',
29
+ :locals => {:application_data_backup => @application_data_backup}
30
+ else
31
+ render :js => "flash.error('Database restore failed');", :status => 500
32
+ end
33
+ end
34
+
35
+ def destroy
36
+ if @application_data_backup.destroy
37
+ head :ok
38
+ else
39
+ render :js => "flash.error('Deletion of archive failed for some reason');", :status => 500
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,7 @@
1
+ class GlacierOnRails::AwsArchiveRetrievalJobsController < ApplicationController
2
+ def create
3
+ @application_data_backup = ApplicationDataBackup.find(params[:application_data_backup_id])
4
+ @application_data_backup.initiate_retrieval
5
+ render :partial => 'application_data_backup', :locals => {:application_data_backup => @application_data_backup}
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ require 'httparty'
2
+
3
+ module GlacierOnRails
4
+ class AwsSnsSubscriptionsController < ApplicationController
5
+ class MessageWasNotAuthentic < StandardError; end
6
+
7
+ skip_before_action :verify_authenticity_token, :check_permissions, :only=>[:create]
8
+ def create
9
+ if request.headers["x-amz-sns-message-type"] == "SubscriptionConfirmation" # AWS sends a post to confirm subscription to notifications
10
+ subscribe_url = JSON.parse(request.raw_post)["SubscribeURL"]
11
+ raise MessageWasNotAuthentic unless subscribe_url =~ /^https.*amazonaws\.com\//
12
+ HTTParty.get subscribe_url # confirms subscription
13
+ AwsLog.info "AWS subscription confirmation request received"
14
+ head :ok
15
+ else # this is the notification from AWS Glacier that the retrieval job is completed
16
+ AwsLog.info "AWS notification received #{request.raw_post}"
17
+ # the notification that the retrieve_archive job has completed
18
+ message = JSON.parse(request.raw_post)["Message"]
19
+ json_message = JSON.parse(message)
20
+ job_id = json_message["JobId"]
21
+ glacier_archive = GlacierArchive.find_by(:archive_retrieval_job_id => job_id)
22
+ glacier_archive.update_attributes(:notification => json_message) if glacier_archive
23
+ head :ok
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ # Notification example, note that message is a string and must be parsed by JSON.parse
30
+ # {
31
+ # "Type" : "Notification",
32
+ # "MessageId" : "b221d7d9-73c3-5e6d-9caf-8649cb198736",
33
+ # "TopicArn" : "arn:aws:sns:us-east-1:918359762546:retrieve_archive",
34
+ # "Message" : "{\"Action\":\"ArchiveRetrieval\",\"ArchiveId\":\"_vs0qWot3GIg7I3bsBomHTheu73qNkCW28_B1hKjXhvOMR5vh7rGQs4Ra_UEYdXCA1N6-F8aF-lN7SMqLl6pRyy7HX6mA0DPwhpXSPoOA7FixG1roXtx7O7QN8gFiz_GAyMR8OeqpQ\",\"ArchiveSHA256TreeHash\":\"0c47aee75a9587d83648fc1ea05d52646a67f704fbd23952c134d89776eccbe6\",\"ArchiveSizeInBytes\":285544,\"Completed\":true,\"CompletionDate\":\"2017-05-13T07:32:42.098Z\",\"CreationDate\":\"2017-05-13T03:40:19.264Z\",\"InventoryRetrievalParameters\":null,\"InventorySizeInBytes\":null,\"JobDescription\":\"put anything here\",\"JobId\":\"krCLWk6m7NJWppiy2SxdhP60f98PdrdaZBfhdDTZufrAkoh-ikrvb_NA0Q1vg2WcAhzZLL92kiwjOijUEDh0U7X09YQK\",\"RetrievalByteRange\":\"0-285543\",\"SHA256TreeHash\":\"0c47aee75a9587d83648fc1ea05d52646a67f704fbd23952c134d89776eccbe6\",\"SNSTopic\":\"arn:aws:sns:us-east-1:918359762546:retrieve_archive\",\"StatusCode\":\"Succeeded\",\"StatusMessage\":\"Succeeded\",\"Tier\":\"Standard\",\"VaultARN\":\"arn:aws:glacier:us-east-1:918359762546:vaults/demo\"}",
35
+ # "Timestamp" : "2017-05-13T07:32:42.229Z",
36
+ # "SignatureVersion" : "1",
37
+ # "Signature" : "i95PuF62vsfsrViAbgeHWP8vxZCzX+wMhUi4c8oveMkKT9PqmCXhQT6Jza86NCYMOOoxs8avUKE0mjuLlcrYqS6Iw3wLrx0P3Op/OJy2OQMelBp7nlWWvJpkRnsvY5EOpZF0auvsbsBLUrxgkoPAfP6/B3rO2BZubsu28fA0Qq4/Gzp2tM2U50NmggvRoD7Trt4usrgh8GCQ4iDr7Ce7jrxRglUgKA6/I4frlJG4l/bBwm6h5VzQgwO2xylPyaOSL2IIjiwcXeEwsYw8rGR2Qf+6/yLh4OiYxyp2X58MJSB4B7cenvziS+R95LC45LJrfmk3lK8x3L9xFPZPNjwzeA==",
38
+ # "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-b95095beb82e8f6a046b3aafc7f4149a.pem",
39
+ # "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:918359762546:retrieve_archive:fb8925ed-cc03-4f60-8301-27a4d5ad36d8"
40
+ # }
@@ -0,0 +1,4 @@
1
+ module GlacierOnRails
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,84 @@
1
+ class ApplicationDataBackup < ActiveRecord::Base
2
+ has_one :glacier_db_archive, :dependent => :destroy
3
+ has_and_belongs_to_many :glacier_file_archives, association_foreign_key: 'glacier_file_archive_id', join_table: 'application_data_backups_glacier_file_archives'
4
+
5
+ after_destroy do |application_data_backup|
6
+ GlacierFileArchive.all.select{|gfa| gfa.application_data_backups.empty?}.each{|gfa| gfa.destroy}
7
+ end
8
+
9
+ before_create do |application_data_backup|
10
+ application_data_backup.create_archive
11
+ if application_data_backup.has_errors?
12
+ logger.debug "Error creating backup #{application_data_backup.errors.full_messages}"
13
+ throw :abort
14
+ end
15
+ end
16
+
17
+ def errors
18
+ unless components(:nil?).any?
19
+ components(:errors).map(&:full_messages).flatten.each do |message|
20
+ super.add(:base, message)
21
+ end
22
+ end
23
+ super
24
+ end
25
+
26
+ def create_archive
27
+ self.glacier_db_archive = GlacierDbArchive.create
28
+ # don't bother creating the file archives if the db archive fails
29
+ self.glacier_file_archives = GlacierFileArchive.all! if glacier_db_archive.persisted?
30
+ # if any of them fail, we have errors
31
+ end
32
+
33
+ def initiate_retrieval
34
+ components(:initiate_retrieve_job)
35
+ end
36
+
37
+ def fetch_archive
38
+ components(:fetch_archive)
39
+ !has_errors?
40
+ end
41
+
42
+ def restore
43
+ rehome_orphans
44
+ components(:restore)
45
+ rescue GlacierArchive::RestoreFail
46
+ return false
47
+ end
48
+
49
+ def retrieval_status
50
+ # if any of the components has errors, just show status as 'available', an error message should be shown
51
+ return 'available' if has_errors?
52
+ # if components have different statuses, it's b/c they're not yet synchronized,
53
+ # so take the 'lowest' status, where rank (high to low) is exists, local, ready, pending, available
54
+ pick_lowest(components(:retrieval_status))
55
+ end
56
+
57
+ def has_errors?
58
+ errors.full_messages.length > 0
59
+ end
60
+
61
+ private
62
+ # files that were added since the backup-being-restored was created
63
+ # no longer have a reference in the database-being-restored.
64
+ # instead of just deleting them, move them to a different location.
65
+ # Any further disposition should be handled manually.
66
+ def rehome_orphans
67
+ files_to_be_restored = glacier_file_archives.map(&:filename)
68
+ existing_files = ApplicationFile.list
69
+ to_be_orphaned = existing_files - files_to_be_restored
70
+ to_be_orphaned.each do |file|
71
+ FileUtils.mv GlacierOnRails::Config.attached_files_directory.join(file), GlacierOnRails::Config.orphan_files_directory
72
+ end
73
+ end
74
+
75
+ def pick_lowest(statuses)
76
+ rank = ["available","pending","ready","local","exists"] # low to high
77
+ statuses.inject("exists"){|ref,stat| rank.index(stat) <= rank.index(ref) ? stat : ref }
78
+ end
79
+
80
+ def components(action)
81
+ db_result = glacier_db_archive.send(action)
82
+ glacier_file_archives.reject(&:exists_status?).collect { |archive| archive.send(action) } << db_result
83
+ end
84
+ end
@@ -0,0 +1,7 @@
1
+ class ApplicationDatabase::BaseAdapter
2
+ attr_accessor :db_config
3
+
4
+ def initialize(db_config)
5
+ @db_config = db_config
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ class ApplicationDatabase::MysqlAdapter < ApplicationDatabase::BaseAdapter
2
+
3
+ def contents
4
+ raise "Not yet implemented"
5
+ end
6
+
7
+ def restore(file)
8
+ raise "Not yet implemented"
9
+ end
10
+
11
+ end