snapshotar 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.respec +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +79 -0
- data/Rakefile +7 -0
- data/lib/snapshotar/configuration.rb +25 -0
- data/lib/snapshotar/core.rb +103 -0
- data/lib/snapshotar/storage/file_storage.rb +67 -0
- data/lib/snapshotar/storage/s3_storage.rb +70 -0
- data/lib/snapshotar/storage/storage_template.rb +53 -0
- data/lib/snapshotar/tasks.rb +50 -0
- data/lib/snapshotar/version.rb +3 -0
- data/lib/snapshotar.rb +65 -0
- data/snapshotar.gemspec +30 -0
- data/snapshotar.png +0 -0
- data/spec/carrierwave_spec.rb +67 -0
- data/spec/config/mongoid.yml +14 -0
- data/spec/config_spec.rb +24 -0
- data/spec/core_spec.rb +90 -0
- data/spec/file_storage_spec.rb +49 -0
- data/spec/models/artist.rb +10 -0
- data/spec/models/event.rb +8 -0
- data/spec/models/image_uploader.rb +21 -0
- data/spec/s3_storage_spec.rb +32 -0
- data/spec/spec_helper.rb +31 -0
- metadata +196 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4b7518a6186d705c241f358e230cff6743b69b36
|
4
|
+
data.tar.gz: 319fcc1e61c96ad015507bb9b2da001c59c1ba51
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a312f9567fd4a600df243ac3547d134fbac3a2a4c533f5a302a441bd7c07917dfa1e63cd36703b620b08f5a4411451c1ca41d23595ada61c43cb632996270963
|
7
|
+
data.tar.gz: e19decc5f4d9c7017d7b641053323d68958b5e4e3ae51652825f5bea8b188016f0d10d3d4a3eae06470ec03de94163c7e93690f15244759c0cdfe55c661222c3
|
data/.gitignore
ADDED
data/.respec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- "1.9.3"
|
4
|
+
- "2.0.0"
|
5
|
+
- jruby-19mode # JRuby in 1.9 mode
|
6
|
+
|
7
|
+
# uncomment this line if your project needs to run something other than `rake`:
|
8
|
+
# script: bundle exec rspec spec
|
9
|
+
services:
|
10
|
+
- mongodb
|
11
|
+
env:
|
12
|
+
global:
|
13
|
+
- S3_ENABLED=false
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Benjamin Müller
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/elchbenny/snapshotar.svg?branch=master)](https://travis-ci.org/elchbenny/snapshotar)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/elchbenny/snapshotar.png)](https://codeclimate.com/github/elchbenny/snapshotar)
|
3
|
+
|
4
|
+
|
5
|
+
# Snapshotar
|
6
|
+
|
7
|
+
Make a snapshot of your staging environment and pull back on your dev machine.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'snapshotar'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install snapshotar
|
22
|
+
|
23
|
+
## What snapshotar can...
|
24
|
+
|
25
|
+
1. serializing your rails models to json
|
26
|
+
2. handling *carrierwave* attachments
|
27
|
+
3. storing serialized dataset locally or to amazon s3
|
28
|
+
4. listing available snapshots
|
29
|
+
5. deserialize snapshot and load back to database
|
30
|
+
|
31
|
+
## Why snapshotar?
|
32
|
+
|
33
|
+
instead of...
|
34
|
+
|
35
|
+
- **database backup?** Because image attachments causing trouble!!
|
36
|
+
- **fixtures/factories/fakers?** Because this requires coding. Let the
|
37
|
+
others fill up your app with sample data.
|
38
|
+
|
39
|
+
## When to use snapshotar?
|
40
|
+
|
41
|
+
- For testing and development!!
|
42
|
+
- definitely *NOT* for production backups
|
43
|
+
|
44
|
+
## Requirements
|
45
|
+
|
46
|
+
- Ruby >= 1.9.3
|
47
|
+
- Rails ?
|
48
|
+
|
49
|
+
## Usage
|
50
|
+
|
51
|
+
- one option for snapshotar is **rake**
|
52
|
+
|
53
|
+
- you can also integrate snapshotar into your administration backend and let app
|
54
|
+
users create snapshots.
|
55
|
+
|
56
|
+
## Configuration Options
|
57
|
+
|
58
|
+
## Testing
|
59
|
+
This repository is under continuous integration testing on travis-ci.org.
|
60
|
+
|
61
|
+
Amazon S3 related functionalities are not tested in CI as an official account at
|
62
|
+
amazon would be required. AWS S3 tests can be run locally by providing a *.env* file
|
63
|
+
in the projects root directory with the following keys:
|
64
|
+
|
65
|
+
AWS_ACCESS_KEY_ID=<your id>
|
66
|
+
AWS_SECRET_ACCESS_KEY=<your secret>
|
67
|
+
AWS_SNAPSHOTAR_BUCKET=<a bucket name>
|
68
|
+
|
69
|
+
RACK_ENV=test
|
70
|
+
|
71
|
+
S3_ENABLED=true
|
72
|
+
|
73
|
+
## Contributing
|
74
|
+
|
75
|
+
1. Fork it
|
76
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
77
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
78
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
79
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Snapshotar
|
2
|
+
|
3
|
+
##
|
4
|
+
# Configuration Class
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
##
|
8
|
+
# Where to store your snapshots?
|
9
|
+
# - +:s3+ for amazon s3 service
|
10
|
+
# - +:file+ local directory
|
11
|
+
attr_accessor :storage_type
|
12
|
+
|
13
|
+
##
|
14
|
+
# Provide the models and their attributes to seralize like this:
|
15
|
+
# [[ModelName1, :attribute1, :attribute2,...],[ModelName2, :attribute1]]
|
16
|
+
#
|
17
|
+
attr_accessor :models
|
18
|
+
|
19
|
+
def initialize #:nodoc:
|
20
|
+
@storage_type = :file
|
21
|
+
@models = []
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require "snapshotar/storage/s3_storage"
|
2
|
+
require "snapshotar/storage/file_storage"
|
3
|
+
require "json"
|
4
|
+
require "jbuilder"
|
5
|
+
|
6
|
+
module Snapshotar
|
7
|
+
|
8
|
+
class Core
|
9
|
+
|
10
|
+
def initialize #:nodoc:
|
11
|
+
storage_class = Snapshotar::Storage::S3Storage if Snapshotar.configuration.storage_type == :s3
|
12
|
+
storage_class = Snapshotar::Storage::FileStorage if Snapshotar.configuration.storage_type == :file
|
13
|
+
|
14
|
+
@storage = storage_class.new
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# List all available snapshots.
|
19
|
+
#
|
20
|
+
# returns:: array of filenames
|
21
|
+
#
|
22
|
+
def list
|
23
|
+
@storage.index
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Performs a snapshot
|
28
|
+
#
|
29
|
+
# Params::
|
30
|
+
# +filename+:: filename to create or nil to let snapshotar create something
|
31
|
+
# like +snapshotar_dump_<timestamp>.json+
|
32
|
+
#
|
33
|
+
# returns:: +filename+
|
34
|
+
#
|
35
|
+
def export(filename = nil)
|
36
|
+
filename ||= "snapshotar_dump_#{Time.now.to_i}.json"
|
37
|
+
|
38
|
+
serialized = Jbuilder.encode do |json|
|
39
|
+
Snapshotar.configuration.models.each do |m|
|
40
|
+
model_name = m.first.name
|
41
|
+
json.set! model_name do
|
42
|
+
json.array! m.first.all do |itm|
|
43
|
+
m[1..-1].each do |attr|
|
44
|
+
|
45
|
+
next unless itm.respond_to?(attr.to_sym)
|
46
|
+
|
47
|
+
# replace uploads by their url
|
48
|
+
if itm.send(attr.to_sym).respond_to?(:url)
|
49
|
+
json.set! "#{attr}_url".to_sym, itm.send(attr.to_sym).url
|
50
|
+
else
|
51
|
+
json.set! attr.to_sym, itm[attr]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
@storage.create(filename,serialized)
|
60
|
+
|
61
|
+
return filename
|
62
|
+
end
|
63
|
+
|
64
|
+
# Load a snapshot.
|
65
|
+
#
|
66
|
+
# Params::
|
67
|
+
# +filename+:: name of the snapshot to load
|
68
|
+
#
|
69
|
+
def import(filename)
|
70
|
+
tree = JSON.load @storage.show(filename)
|
71
|
+
|
72
|
+
tree.each do |key,value|
|
73
|
+
clazz = key.constantize
|
74
|
+
|
75
|
+
value.each do |itm|
|
76
|
+
item_params = {}
|
77
|
+
itm.each do |itm_key,itm_value|
|
78
|
+
# handle url paths separatley
|
79
|
+
if itm_key.to_s.end_with?("_url")
|
80
|
+
orig_key = itm_key.to_s[0..-5].to_sym
|
81
|
+
item_params[orig_key] = File.open(itm_value)
|
82
|
+
else
|
83
|
+
item_params[itm_key] = itm_value
|
84
|
+
end
|
85
|
+
end
|
86
|
+
clazz.create(item_params)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
##
|
92
|
+
# delete a snapshot.
|
93
|
+
#
|
94
|
+
# Params::
|
95
|
+
# +filename+:: name of the snapshot to delete
|
96
|
+
#
|
97
|
+
def delete(filename)
|
98
|
+
|
99
|
+
@storage.delete(filename)
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Snapshotar
|
2
|
+
module Storage
|
3
|
+
|
4
|
+
class FileStorage
|
5
|
+
|
6
|
+
##
|
7
|
+
# Use this property to specify the local path where snapshots are stored.
|
8
|
+
# Default:: +tmp/+
|
9
|
+
#
|
10
|
+
attr_accessor :base_path
|
11
|
+
|
12
|
+
def initialize(path = nil) #:nodoc:
|
13
|
+
@base_path = path || "tmp"
|
14
|
+
|
15
|
+
# create tmp dir if it's not existent
|
16
|
+
unless File.directory?(@base_path)
|
17
|
+
FileUtils.mkdir_p(@base_path)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# lists available snapshots in this storage.
|
23
|
+
#
|
24
|
+
# returns:: array of filenames
|
25
|
+
#
|
26
|
+
def index
|
27
|
+
Dir["#{@base_path}/*.json"].map{|p| File.basename(p)}
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# loads a snapshot specified by the given +filename+.
|
32
|
+
#
|
33
|
+
# Params::
|
34
|
+
# +filename+:: name of the snapshot to load
|
35
|
+
#
|
36
|
+
# returns:: still seralized json
|
37
|
+
#
|
38
|
+
def show(filename)
|
39
|
+
File.read File.join(@base_path, filename)
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# creates a snapshot specified by the given +filename+ with data provided
|
44
|
+
#
|
45
|
+
# Params::
|
46
|
+
# +filename+:: name of the snapshot to create
|
47
|
+
# +serialized_tree+:: json serialized data
|
48
|
+
#
|
49
|
+
def create(filename,serialized_tree)
|
50
|
+
File.open(File.join(@base_path, filename),"w") do |f|
|
51
|
+
f.write(serialized_tree)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# deletes a snapshot specified by the given +filename+.
|
57
|
+
#
|
58
|
+
# Params::
|
59
|
+
# +filename+:: name of the snapshot to delete
|
60
|
+
#
|
61
|
+
def delete(filename)
|
62
|
+
File.delete File.join(@base_path, filename)
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "aws-sdk"
|
2
|
+
|
3
|
+
module Snapshotar
|
4
|
+
module Storage
|
5
|
+
|
6
|
+
##
|
7
|
+
# This snapshot storage type connects to amazon s3 via *aws-sdk gem*.
|
8
|
+
class S3Storage
|
9
|
+
|
10
|
+
def initialize #:nodoc:
|
11
|
+
|
12
|
+
raise ArgumentError, "You should set ENV['AWS_ACCESS_KEY_ID'] to a valid value" unless ENV['AWS_ACCESS_KEY_ID']
|
13
|
+
raise ArgumentError, "You should set ENV['AWS_SECRET_ACCESS_KEY'] to a valid value" unless ENV['AWS_SECRET_ACCESS_KEY']
|
14
|
+
raise ArgumentError, "You should set ENV['AWS_SNAPSHOTAR_BUCKET'] to a aws bucket name used only for snapshotting" unless ENV['AWS_SNAPSHOTAR_BUCKET']
|
15
|
+
|
16
|
+
p "Running S3 with key: #{ENV['AWS_ACCESS_KEY_ID']}, secret: #{ENV['AWS_SECRET_ACCESS_KEY']}, bucket: #{ENV['AWS_SNAPSHOTAR_BUCKET']}"
|
17
|
+
|
18
|
+
@s3 = AWS::S3.new(
|
19
|
+
access_key_id: ENV['AWS_ACCESS_KEY_ID'],
|
20
|
+
secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
|
21
|
+
region: ENV['AWS_REGION'])
|
22
|
+
|
23
|
+
@bucket = @s3.buckets[ENV['AWS_SNAPSHOTAR_BUCKET']]
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# lists available snapshots in this storage.
|
28
|
+
#
|
29
|
+
# returns:: array of filenames
|
30
|
+
#
|
31
|
+
def index
|
32
|
+
@bucket.objects.map{|obj| obj.key}
|
33
|
+
end
|
34
|
+
|
35
|
+
##
|
36
|
+
# loads a snapshot specified by the given +filename+.
|
37
|
+
#
|
38
|
+
# Params::
|
39
|
+
# +filename+:: name of the snapshot to load
|
40
|
+
#
|
41
|
+
# returns:: still seralized json
|
42
|
+
#
|
43
|
+
def show(filename)
|
44
|
+
@bucket.objects[filename]
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# creates a snapshot specified by the given +filename+ with data provided
|
49
|
+
#
|
50
|
+
# Params::
|
51
|
+
# +filename+:: name of the snapshot to create
|
52
|
+
# +serialized_tree+:: json serialized data
|
53
|
+
#
|
54
|
+
def create(filename,serialized_tree)
|
55
|
+
@bucket.objects[filename].write(serialized_tree)
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# deletes a snapshot specified by the given +filename+.
|
60
|
+
#
|
61
|
+
# Params::
|
62
|
+
# +filename+:: name of the snapshot to delete
|
63
|
+
#
|
64
|
+
def delete(filename)
|
65
|
+
@bucket.objects[filename].delete
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Snapshotar
|
2
|
+
module Storage
|
3
|
+
|
4
|
+
class StorageTemplate
|
5
|
+
|
6
|
+
def initialize #:nodoc:
|
7
|
+
end
|
8
|
+
|
9
|
+
##
|
10
|
+
# lists available snapshots in this storage.
|
11
|
+
#
|
12
|
+
# returns:: array of filenames
|
13
|
+
#
|
14
|
+
def index
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# loads a snapshot specified by the given +filename+.
|
20
|
+
#
|
21
|
+
# Params::
|
22
|
+
# +filename+:: name of the snapshot to load
|
23
|
+
#
|
24
|
+
# returns:: still seralized json
|
25
|
+
#
|
26
|
+
def show(filename)
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# creates a snapshot specified by the given +filename+ with data provided
|
32
|
+
#
|
33
|
+
# Params::
|
34
|
+
# +filename+:: name of the snapshot to create
|
35
|
+
# +serialized_tree+:: json serialized data
|
36
|
+
#
|
37
|
+
def create(filename,serialized_tree)
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
##
|
42
|
+
# deletes a snapshot specified by the given +filename+.
|
43
|
+
#
|
44
|
+
# Params::
|
45
|
+
# +filename+:: name of the snapshot to delete
|
46
|
+
#
|
47
|
+
def delete(filename)
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "rake"
|
2
|
+
|
3
|
+
namespace :snapshotar do
|
4
|
+
|
5
|
+
desc "list available snapshots"
|
6
|
+
task list: :environment do
|
7
|
+
|
8
|
+
file = ARGV.last
|
9
|
+
task file.to_sym do ; end
|
10
|
+
|
11
|
+
p "# Snapshotar: Listing available snapshots"
|
12
|
+
p Snapshotar.list
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "create a snapshots"
|
16
|
+
task create: :environment do
|
17
|
+
|
18
|
+
file = ARGV.last
|
19
|
+
task file.to_sym do ; end
|
20
|
+
|
21
|
+
file = nil if file.downcase == "snapshotar:create"
|
22
|
+
|
23
|
+
p "# Snapshotar: Creating snapshots #{file}"
|
24
|
+
p Snapshotar.create(file)
|
25
|
+
end
|
26
|
+
|
27
|
+
desc "load a snapshots"
|
28
|
+
task load: :environment do
|
29
|
+
|
30
|
+
file = ARGV.last
|
31
|
+
task file.to_sym do ; end
|
32
|
+
|
33
|
+
file = nil if file.downcase == "snapshotar:load"
|
34
|
+
|
35
|
+
p "# Snapshotar: Loading snapshot #{file}"
|
36
|
+
p Snapshotar.load(file)
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "delete a snapshots"
|
40
|
+
task delete: :environment do
|
41
|
+
|
42
|
+
file = ARGV.last
|
43
|
+
task file.to_sym do ; end
|
44
|
+
|
45
|
+
file = nil if file.downcase == "snapshotar:delete"
|
46
|
+
|
47
|
+
p "# Snapshotar: Listing deleting snapshot #{file}"
|
48
|
+
p Snapshotar.delete(file)
|
49
|
+
end
|
50
|
+
end
|
data/lib/snapshotar.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require "snapshotar/configuration"
|
2
|
+
require "snapshotar/version"
|
3
|
+
require "snapshotar/core"
|
4
|
+
require "snapshotar/tasks"
|
5
|
+
|
6
|
+
##
|
7
|
+
# Make a snapshot of your staging environment and pull back on your dev machine.
|
8
|
+
|
9
|
+
module Snapshotar
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
##
|
14
|
+
# Configuration object. Create a *snapshotar.rb* initializer under
|
15
|
+
# config/initializers and add your configuation inside:
|
16
|
+
#
|
17
|
+
# Snapshotar.configure do |config|
|
18
|
+
# config.storage_type = :file # :s3
|
19
|
+
#
|
20
|
+
# config.models << [Event, :name, :date]
|
21
|
+
# config.models << [Artist, :name]
|
22
|
+
# end
|
23
|
+
attr_accessor :configuration
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.configuration
|
27
|
+
@configuration ||= Configuration.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.configure
|
31
|
+
yield(configuration) if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.core
|
35
|
+
@core ||= Core.new
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# List available snapshots
|
40
|
+
#
|
41
|
+
def self.list
|
42
|
+
self.core.list
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Create a snapshot
|
47
|
+
#
|
48
|
+
def self.create(name = nil)
|
49
|
+
self.core.export(name)
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Load a snapshot
|
54
|
+
#
|
55
|
+
def self.load(name)
|
56
|
+
self.core.import(name)
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Delete a snapshot
|
61
|
+
#
|
62
|
+
def self.delete(name)
|
63
|
+
self.core.delete(name)
|
64
|
+
end
|
65
|
+
end
|
data/snapshotar.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'snapshotar/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "snapshotar"
|
8
|
+
spec.version = Snapshotar::VERSION
|
9
|
+
spec.authors = ["Benjamin Müller"]
|
10
|
+
spec.email = ["benjamin@boxar.de"]
|
11
|
+
spec.description = %q{Make a snapshot of your rails database by serializing all objects.}
|
12
|
+
spec.summary = %q{In contrast to a database backup, snapshotAR is able to manage image references made with paperclip or carrierwave correctly. You are able to save your entire application or only parts of it and e.g. seed your test environments. }
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib","tasks"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "dotenv"
|
25
|
+
spec.add_development_dependency 'mongoid', '~> 4.0.0'
|
26
|
+
spec.add_development_dependency 'carrierwave-mongoid'
|
27
|
+
|
28
|
+
spec.add_dependency 'aws-sdk', '~> 1.0'
|
29
|
+
spec.add_dependency 'jbuilder'
|
30
|
+
end
|
data/snapshotar.png
ADDED
Binary file
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Snapshotar::Storage::FileStorage do
|
4
|
+
|
5
|
+
context "local file storage" do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
Mongoid.purge!
|
9
|
+
|
10
|
+
# setup configuration
|
11
|
+
Snapshotar.configure do |config|
|
12
|
+
|
13
|
+
config.storage_type = :file
|
14
|
+
|
15
|
+
# serialization
|
16
|
+
config.models << [Artist, :name, :avatar]
|
17
|
+
end
|
18
|
+
|
19
|
+
# create models with images attached
|
20
|
+
artist = Artist.create({name: "Artist 3"})
|
21
|
+
artist.avatar.store!(File.open("snapshotar.png"))
|
22
|
+
artist.save
|
23
|
+
|
24
|
+
@snapshotar = Snapshotar::Core.new
|
25
|
+
end
|
26
|
+
|
27
|
+
after(:all) do
|
28
|
+
# clean up files
|
29
|
+
FileUtils.rm_rf(Dir["tmp"])
|
30
|
+
|
31
|
+
Mongoid.purge!
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should create image models" do
|
35
|
+
expect(Artist.count).to eq 1
|
36
|
+
p Artist.first.avatar.path
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should export successfully" do
|
40
|
+
filename = @snapshotar.export
|
41
|
+
|
42
|
+
expect(@snapshotar.list).to include(filename)
|
43
|
+
|
44
|
+
# clean up
|
45
|
+
@snapshotar.delete(filename)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should reimport with images" do
|
49
|
+
# export sample
|
50
|
+
filename = @snapshotar.export
|
51
|
+
|
52
|
+
# clear db
|
53
|
+
Mongoid.purge!
|
54
|
+
|
55
|
+
expect(Artist.count).to eq 0
|
56
|
+
|
57
|
+
# # reimport
|
58
|
+
@snapshotar.import(filename)
|
59
|
+
|
60
|
+
expect(Artist.count).to eq 1
|
61
|
+
expect(Artist.first.avatar.path).not_to be_nil
|
62
|
+
|
63
|
+
# clean up
|
64
|
+
@snapshotar.delete(filename)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
test:
|
2
|
+
sessions:
|
3
|
+
default:
|
4
|
+
database: snapshotar_test
|
5
|
+
hosts:
|
6
|
+
- <%= ENV['WERCKER_MONGODB_HOST'] || 'localhost:27017' %>
|
7
|
+
options:
|
8
|
+
# consistency: :strong
|
9
|
+
# In the test environment we lower the retries and retry interval to
|
10
|
+
# low amounts for fast failures.
|
11
|
+
max_retries: 1
|
12
|
+
retry_interval: 0
|
13
|
+
options:
|
14
|
+
raise_not_found_error: false
|
data/spec/config_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
#:nodoc:
|
4
|
+
describe Snapshotar::Core do
|
5
|
+
|
6
|
+
context "listing snapshots" do
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
Snapshotar.configure do |config|
|
10
|
+
# lets use default values
|
11
|
+
config.storage_type = :file
|
12
|
+
config.models = []
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should load default config" do
|
17
|
+
expect(Snapshotar.configuration).not_to be_nil
|
18
|
+
|
19
|
+
expect(Snapshotar.configuration.storage_type).to eq :file
|
20
|
+
expect(Snapshotar.configuration.models).to be_empty
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
data/spec/core_spec.rb
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'jbuilder'
|
3
|
+
|
4
|
+
#:nodoc:
|
5
|
+
describe Snapshotar::Core do
|
6
|
+
|
7
|
+
context "listing snapshots" do
|
8
|
+
|
9
|
+
before(:all) do
|
10
|
+
|
11
|
+
# clear db
|
12
|
+
Mongoid.purge!
|
13
|
+
|
14
|
+
# setup configuration
|
15
|
+
Snapshotar.configure do |config|
|
16
|
+
|
17
|
+
config.storage_type = :file
|
18
|
+
|
19
|
+
# serialization
|
20
|
+
config.models << [Event, :name, :date]
|
21
|
+
config.models << [Artist, :name]
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
# create sample data
|
26
|
+
Event.create({name: "Event 1", date: Date.new})
|
27
|
+
Event.create({name: "Event 2", date: Date.new})
|
28
|
+
|
29
|
+
Artist.create({name: "Artist 1"})
|
30
|
+
Artist.create({name: "Artist 2"})
|
31
|
+
end
|
32
|
+
|
33
|
+
after(:all) do
|
34
|
+
# clean up
|
35
|
+
FileUtils.rm_rf(Dir["tmp"])
|
36
|
+
Mongoid.purge!
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should list snapshots" do
|
40
|
+
expect(Snapshotar.list).to be_empty
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should have one Event" do
|
44
|
+
expect(Event.count).to eq 2
|
45
|
+
expect(Artist.count).to eq 2
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should correctly read config models" do
|
49
|
+
|
50
|
+
serialized = Jbuilder.encode do |json|
|
51
|
+
Snapshotar.configuration.models.each do |m|
|
52
|
+
model_name = m.first.name
|
53
|
+
json.set! model_name do
|
54
|
+
json.array! m.first.all, *m[1..-1]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# p "serialized: #{serialized}"
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should export models" do
|
63
|
+
filename = Snapshotar.create
|
64
|
+
expect(Snapshotar.list).to include(filename)
|
65
|
+
|
66
|
+
# clean up
|
67
|
+
Snapshotar.delete(filename)
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should import models" do
|
71
|
+
# export sample
|
72
|
+
filename = Snapshotar.create
|
73
|
+
|
74
|
+
# clear db
|
75
|
+
Mongoid.purge!
|
76
|
+
|
77
|
+
expect(Event.count).to eq 0
|
78
|
+
expect(Artist.count).to eq 0
|
79
|
+
|
80
|
+
# reimport
|
81
|
+
Snapshotar.load(filename)
|
82
|
+
|
83
|
+
expect(Event.count).to eq 2
|
84
|
+
expect(Artist.count).to eq 2
|
85
|
+
|
86
|
+
# clean up
|
87
|
+
Snapshotar.delete(filename)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe Snapshotar::Storage::FileStorage do
|
5
|
+
|
6
|
+
TMP_DIR = "tmp"
|
7
|
+
|
8
|
+
context "local file storage" do
|
9
|
+
|
10
|
+
before(:all) do
|
11
|
+
@fileStorage = described_class.new(TMP_DIR)
|
12
|
+
|
13
|
+
# write a test file
|
14
|
+
f = File.open(File.join(TMP_DIR,"test.json"), "w+")
|
15
|
+
f.close
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
after(:all) do
|
20
|
+
|
21
|
+
# clean up
|
22
|
+
File.delete(File.join(TMP_DIR,"test.json"))
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should initialize correctly" do
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should list objects" do
|
29
|
+
|
30
|
+
expect(@fileStorage.index).not_to be_empty
|
31
|
+
expect(@fileStorage.index.count).to eq 1
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should show one element" do
|
35
|
+
|
36
|
+
expect(@fileStorage.show(@fileStorage.index.first)).not_to be_nil
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should create an element" do
|
40
|
+
|
41
|
+
@fileStorage.create("zz_testdump.json",{"test" => "this is a test object"}.to_json)
|
42
|
+
|
43
|
+
expect(JSON.load(@fileStorage.show("zz_testdump.json"))).to have_key("test")
|
44
|
+
|
45
|
+
@fileStorage.delete("zz_testdump.json")
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'carrierwave/mongoid'
|
3
|
+
|
4
|
+
class ImageUploader < CarrierWave::Uploader::Base
|
5
|
+
|
6
|
+
# Include RMagick or MiniMagick support:
|
7
|
+
# include CarrierWave::RMagick
|
8
|
+
# include CarrierWave::MiniMagick
|
9
|
+
|
10
|
+
# Choose what kind of storage to use for this uploader:
|
11
|
+
# storage :fog
|
12
|
+
storage = :file
|
13
|
+
enable_processing = false
|
14
|
+
|
15
|
+
# Override the directory where uploaded files will be stored.
|
16
|
+
# This is a sensible default for uploaders that are meant to be mounted:
|
17
|
+
def store_dir
|
18
|
+
"tmp/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
#:nodoc:
|
4
|
+
describe Snapshotar::Storage::S3Storage do
|
5
|
+
|
6
|
+
context "setup s3 connection", require_s3: true do
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
@s3Storage = described_class.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should initialize correctly" do
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should list objects" do
|
16
|
+
expect(@s3Storage.index).not_to be_empty
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should show one element" do
|
20
|
+
expect(@s3Storage.show(@s3Storage.index.first)).not_to be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should create an element" do
|
24
|
+
@s3Storage.create("zz_testdump.json",{"test" => "this is a test object"}.to_json)
|
25
|
+
|
26
|
+
expect(JSON.load(@s3Storage.show(@s3Storage.index.last))).to have_key("test")
|
27
|
+
|
28
|
+
@s3Storage.delete("zz_testdump.json")
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'dotenv'
|
5
|
+
Dotenv.load
|
6
|
+
|
7
|
+
require 'snapshotar'
|
8
|
+
require 'mongoid'
|
9
|
+
|
10
|
+
Mongoid.load!('spec/config/mongoid.yml')
|
11
|
+
|
12
|
+
require 'carrierwave/mongoid'
|
13
|
+
|
14
|
+
CarrierWave.configure do |config|
|
15
|
+
config.root = Dir.pwd
|
16
|
+
config.asset_host = Dir.pwd
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'models/event'
|
20
|
+
require 'models/artist'
|
21
|
+
|
22
|
+
|
23
|
+
RSpec.configure do |config|
|
24
|
+
|
25
|
+
config.tty = true
|
26
|
+
|
27
|
+
if ENV['S3_ENABLED']=="false"
|
28
|
+
p "Skipping S3 Tests"
|
29
|
+
config.filter_run_excluding require_s3: true
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: snapshotar
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Benjamin Müller
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: dotenv
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mongoid
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 4.0.0
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 4.0.0
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: carrierwave-mongoid
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: aws-sdk
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ~>
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ~>
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: jbuilder
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Make a snapshot of your rails database by serializing all objects.
|
126
|
+
email:
|
127
|
+
- benjamin@boxar.de
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- .gitignore
|
133
|
+
- .respec
|
134
|
+
- .travis.yml
|
135
|
+
- Gemfile
|
136
|
+
- LICENSE.txt
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- lib/snapshotar.rb
|
140
|
+
- lib/snapshotar/configuration.rb
|
141
|
+
- lib/snapshotar/core.rb
|
142
|
+
- lib/snapshotar/storage/file_storage.rb
|
143
|
+
- lib/snapshotar/storage/s3_storage.rb
|
144
|
+
- lib/snapshotar/storage/storage_template.rb
|
145
|
+
- lib/snapshotar/tasks.rb
|
146
|
+
- lib/snapshotar/version.rb
|
147
|
+
- snapshotar.gemspec
|
148
|
+
- snapshotar.png
|
149
|
+
- spec/carrierwave_spec.rb
|
150
|
+
- spec/config/mongoid.yml
|
151
|
+
- spec/config_spec.rb
|
152
|
+
- spec/core_spec.rb
|
153
|
+
- spec/file_storage_spec.rb
|
154
|
+
- spec/models/artist.rb
|
155
|
+
- spec/models/event.rb
|
156
|
+
- spec/models/image_uploader.rb
|
157
|
+
- spec/s3_storage_spec.rb
|
158
|
+
- spec/spec_helper.rb
|
159
|
+
homepage: ''
|
160
|
+
licenses:
|
161
|
+
- MIT
|
162
|
+
metadata: {}
|
163
|
+
post_install_message:
|
164
|
+
rdoc_options: []
|
165
|
+
require_paths:
|
166
|
+
- lib
|
167
|
+
- tasks
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
requirements:
|
170
|
+
- - '>='
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: '0'
|
173
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
174
|
+
requirements:
|
175
|
+
- - '>='
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
version: '0'
|
178
|
+
requirements: []
|
179
|
+
rubyforge_project:
|
180
|
+
rubygems_version: 2.4.1
|
181
|
+
signing_key:
|
182
|
+
specification_version: 4
|
183
|
+
summary: In contrast to a database backup, snapshotAR is able to manage image references
|
184
|
+
made with paperclip or carrierwave correctly. You are able to save your entire application
|
185
|
+
or only parts of it and e.g. seed your test environments.
|
186
|
+
test_files:
|
187
|
+
- spec/carrierwave_spec.rb
|
188
|
+
- spec/config/mongoid.yml
|
189
|
+
- spec/config_spec.rb
|
190
|
+
- spec/core_spec.rb
|
191
|
+
- spec/file_storage_spec.rb
|
192
|
+
- spec/models/artist.rb
|
193
|
+
- spec/models/event.rb
|
194
|
+
- spec/models/image_uploader.rb
|
195
|
+
- spec/s3_storage_spec.rb
|
196
|
+
- spec/spec_helper.rb
|