glacier_on_rails 0.9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +21 -0
- data/Gemfile.lock +231 -0
- data/MIT-LICENSE +20 -0
- data/README.md +98 -0
- data/Rakefile +13 -0
- data/app/assets/images/glacier_on_rails/.gitkeep +0 -0
- data/app/assets/javascripts/glacier_on_rails/application.js +15 -0
- data/app/assets/stylesheets/glacier_on_rails/application.css +13 -0
- data/app/controllers/glacier_on_rails/application_controller.rb +2 -0
- data/app/controllers/glacier_on_rails/application_data_backups_controller.rb +43 -0
- data/app/controllers/glacier_on_rails/aws_archive_retrieval_jobs_controller.rb +7 -0
- data/app/controllers/glacier_on_rails/aws_sns_subscriptions_controller.rb +40 -0
- data/app/helpers/glacier_on_rails/application_helper.rb +4 -0
- data/app/models/application_data_backup.rb +84 -0
- data/app/models/application_database/base_adapter.rb +7 -0
- data/app/models/application_database/mysql_adapter.rb +11 -0
- data/app/models/application_database/postgres_adapter.rb +52 -0
- data/app/models/application_database.rb +29 -0
- data/app/models/application_file.rb +25 -0
- data/app/models/aws_backend/sns_subscription.rb +54 -0
- data/app/models/aws_backend.rb +113 -0
- data/app/models/aws_log.rb +7 -0
- data/app/models/glacier_archive.rb +115 -0
- data/app/models/glacier_db_archive.rb +27 -0
- data/app/models/glacier_file_archive.rb +45 -0
- data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_application_data_backup.haml +4 -0
- data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_available.haml +3 -0
- data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_index.haml +101 -0
- data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_local.haml +1 -0
- data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_pending.haml +1 -0
- data/app/views/glacier_on_rails/aws_archive_retrieval_jobs/_ready.haml +2 -0
- data/config/initializers/aws_log.rb +1 -0
- data/config/initializers/glacier_on_rails.rb +3 -0
- data/config/initializers/time_formats.rb +3 -0
- data/config/routes.rb +9 -0
- data/db/migrate/20170503133854_create_glacier_archives_table.rb +11 -0
- data/db/migrate/20170507161533_add_notification_column_to_glacier_archives.rb +5 -0
- data/db/migrate/20170509195716_add_archive_retrieval_job_id_to_glacier_archive.rb +5 -0
- data/db/migrate/20170602135721_create_application_data_backups.rb +7 -0
- data/db/migrate/20170602143524_add_application_data_backup_id_to_glacier_archives.rb +5 -0
- data/db/migrate/20170602145526_create_glacier_file_archive_join_table.rb +5 -0
- data/db/migrate/20170602150324_add_type_to_glacier_archives.rb +5 -0
- data/db/migrate/20170603141909_add_filename_to_glacier_archive.rb +5 -0
- data/glacier_on_rails.gemspec +29 -0
- data/lib/glacier_on_rails/config.rb +22 -0
- data/lib/glacier_on_rails/engine.rb +5 -0
- data/lib/glacier_on_rails/version.rb +3 -0
- data/lib/glacier_on_rails.rb +5 -0
- data/lib/tasks/aws.rake +6 -0
- data/script/rails +8 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/javascripts/ractive.js +16621 -0
- data/spec/dummy/app/assets/javascripts/underscore.js +5 -0
- data/spec/dummy/app/assets/stylesheets/application.css +14 -0
- data/spec/dummy/app/controllers/admin_controller.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +6 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/fake_model.rb +10 -0
- data/spec/dummy/app/views/admin/_flash_error.haml +40 -0
- data/spec/dummy/app/views/admin/index.haml +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +67 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +6 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +40 -0
- data/spec/dummy/config/environments/production.rb +70 -0
- data/spec/dummy/config/environments/test.rb +40 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/file_upload.rb +1 -0
- data/spec/dummy/config/initializers/glacier_on_rails.rb +5 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20170531170208_create_fake_models.rb +8 -0
- data/spec/dummy/db/schema.rb +56 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/lib/constants.rb +2 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/features/application_data_backup_spec.rb +166 -0
- data/spec/helpers/aws_helper.rb +99 -0
- data/spec/helpers/dummy_app_helper.rb +23 -0
- data/spec/helpers/http_mock_helpers.rb +137 -0
- data/spec/models/application_data_backup_spec.rb +226 -0
- data/spec/models/application_file_spec.rb +8 -0
- data/spec/models/glacier_db_archive_spec.rb +118 -0
- data/spec/models/glacier_file_archive_spec.rb +56 -0
- data/spec/models/postgres_adapter_spec.rb +46 -0
- data/spec/rails_helper.rb +59 -0
- data/spec/spec_helper.rb +110 -0
- data/spec/support/wait_for_ajax.rb +22 -0
- 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
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,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,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
|