mongolly 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +24 -0
- data/README.md +36 -0
- data/Rakefile +1 -0
- data/bin/mongolly +120 -0
- data/lib/mongolly.rb +6 -0
- data/lib/mongolly/snapshot_manager.rb +35 -0
- data/lib/mongolly/version.rb +3 -0
- data/mongolly.gemspec +25 -0
- metadata +126 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2012 Michael Saffitz
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
* Redistributions of source code must retain the above copyright
|
7
|
+
notice, this list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright
|
9
|
+
notice, this list of conditions and the following disclaimer in the
|
10
|
+
documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of the <organization> nor the
|
12
|
+
names of its contributors may be used to endorse or promote products
|
13
|
+
derived from this software without specific prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
19
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
20
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
21
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
22
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
23
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
24
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# Mongolly
|
2
|
+
|
3
|
+
**Easy backups for EBS-based MongoDB Databases**
|
4
|
+
|
5
|
+
More details coming soon.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'mongolly'
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install mongolly
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
`mongolly help usage`
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
|
27
|
+
1. Fork it
|
28
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
29
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
30
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
+
5. Create new Pull Request
|
32
|
+
|
33
|
+
### Copyright
|
34
|
+
|
35
|
+
Copyright (c) 2012 Michael Saffitz. See LICENSE.txt for
|
36
|
+
further details.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/mongolly
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'thor'
|
3
|
+
require 'yaml'
|
4
|
+
require 'time'
|
5
|
+
require 'mongo'
|
6
|
+
require './lib/mongolly'
|
7
|
+
|
8
|
+
module Mongolly
|
9
|
+
class Runner < Thor
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
super
|
13
|
+
@config = read_config
|
14
|
+
@db_name, @db_config = database_config
|
15
|
+
exit unless valid_config?
|
16
|
+
end
|
17
|
+
|
18
|
+
CONFIG_PATH = File.expand_path '~/.mongolly'
|
19
|
+
|
20
|
+
desc "snapshot", "takes an EBS snapshot of the given volumes"
|
21
|
+
method_option :database, aliases: '-d'
|
22
|
+
def snapshot
|
23
|
+
db = Mongo::Connection.new( @db_config["host"], @db_config["port"] )
|
24
|
+
db['admin'].authenticate( @db_config["user"], @db_config["pass"] )
|
25
|
+
|
26
|
+
SnapshotManager.take_snapshots( db, @config["aws_access_key_id"], @config["aws_secret_asccess_key"], @db_config["volumes"] )
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "clean", "removes snapshots older than the given data"
|
30
|
+
method_option :maximum_age, aliases: '-a', required: true
|
31
|
+
method_option :database, aliases: '-d'
|
32
|
+
def clean
|
33
|
+
max_age = Time.parse(options[:maximum_age])
|
34
|
+
|
35
|
+
puts " ** Cleaning snapshots older than #{max_age}"
|
36
|
+
|
37
|
+
ec2 = AWS::EC2.new(access_key_id: @config["aws_access_key_id"], secret_access_key: @config["aws_secret_asccess_key"])
|
38
|
+
ec2.snapshots.with_owner(:self).each do |snapshot|
|
39
|
+
unless snapshot.tags[:created_at].nil? || snapshot.tags[:backup_key].nil?
|
40
|
+
if Time.parse(snapshot.tags[:created_at]) < max_age
|
41
|
+
puts " ** Deleting #{snapshot.id} created on #{snapshot.tags[:created_at]} with key #{snapshot.tags[:backup_key]}"
|
42
|
+
snapshot.delete
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def seed_config
|
50
|
+
return true if File.exists? CONFIG_PATH
|
51
|
+
|
52
|
+
empty_config = {
|
53
|
+
databases: {
|
54
|
+
dbname: {
|
55
|
+
host: nil,
|
56
|
+
port: nil,
|
57
|
+
user: nil,
|
58
|
+
pass: nil,
|
59
|
+
volumes: [nil]
|
60
|
+
}
|
61
|
+
},
|
62
|
+
aws_access_key_id: nil,
|
63
|
+
aws_secret_asccess_key: nil,
|
64
|
+
}
|
65
|
+
|
66
|
+
File.open( CONFIG_PATH, "w" ) do |f|
|
67
|
+
f.write( empty_config.to_yaml )
|
68
|
+
end
|
69
|
+
|
70
|
+
puts " ** An empty configuration file has been written to #{CONFIG_PATH}."
|
71
|
+
puts " ** you must now edit this configuration file with your AWS Credentials,"
|
72
|
+
puts " ** MongoDB Connection Details, and the array of volume IDs that you wish"
|
73
|
+
puts " ** to snapshot"
|
74
|
+
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
|
78
|
+
def read_config
|
79
|
+
return unless seed_config
|
80
|
+
begin
|
81
|
+
return YAML::load( File.read( CONFIG_PATH ) )
|
82
|
+
rescue e
|
83
|
+
puts " ** Unable to read config at #{CONFIG_PATH}"
|
84
|
+
raise e
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def database_config
|
89
|
+
if options[:database].to_s.strip.empty? && @config["databases"].size > 1
|
90
|
+
raise ArgumentError.new("Database name not provided and more than database specified in the config file")
|
91
|
+
elsif ! @config["databases"].keys.include? options[:database].to_s.strip
|
92
|
+
raise ArgumentError.new("Database #{options[:database]} not defined in config")
|
93
|
+
end
|
94
|
+
|
95
|
+
db_name = options[:database].to_s.strip.empty? ? @config["databases"].keys.first : options[:database]
|
96
|
+
db_config = @config["databases"][db_name]
|
97
|
+
|
98
|
+
%w(host port user pass region).each do |arg|
|
99
|
+
if db_config[arg].to_s.strip.empty?
|
100
|
+
raise ArgumentError.new( "#{arg} for database #{db_name} cannot be empty" )
|
101
|
+
end
|
102
|
+
end
|
103
|
+
if db_config["volumes"].empty? or db_config["volumes"].map { |v| v.to_s.strip.empty? }.include?(true)
|
104
|
+
raise ArgumentError.new("volumes cannot be empty or include an empty string for database #{db_name}")
|
105
|
+
end
|
106
|
+
|
107
|
+
return db_name, db_config
|
108
|
+
end
|
109
|
+
|
110
|
+
def valid_config?
|
111
|
+
%w(aws_access_key_id aws_secret_asccess_key).each do |arg|
|
112
|
+
raise ArgumentError.new("#{arg} cannot be empty") if @config[arg].to_s.strip.empty?
|
113
|
+
end
|
114
|
+
return true
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
Mongolly::Runner.start
|
data/lib/mongolly.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module Mongolly
|
4
|
+
class SnapshotManager
|
5
|
+
|
6
|
+
def self.take_snapshots(db, aws_key_id, aws_secret_key, volume_ids)
|
7
|
+
unless db.locked?
|
8
|
+
puts " ** Locking Database"
|
9
|
+
db.lock!
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
ec2 = AWS::EC2.new(access_key_id: aws_key_id, secret_access_key: aws_secret_key)
|
14
|
+
backup_key = (0...8).map{65.+(rand(25)).chr}.join
|
15
|
+
|
16
|
+
puts " ** Starting Snapshot with key #{backup_key}"
|
17
|
+
|
18
|
+
volume_ids.map{ |v| v.to_s.strip }.each do |volume_id|
|
19
|
+
puts " ** Taking snapshot of volume #{volume_id}"
|
20
|
+
volume = ec2.volumes[volume_id]
|
21
|
+
raise RuntimeError.new("Volume #{volume_id} does not exist") unless volume.exists?
|
22
|
+
|
23
|
+
snapshot = volume.create_snapshot("#{backup_key} #{Time.now} mongo backup")
|
24
|
+
snapshot.add_tag('created_at', value: Time.now)
|
25
|
+
snapshot.add_tag('backup_key', value: backup_key)
|
26
|
+
end
|
27
|
+
ensure
|
28
|
+
if db.locked?
|
29
|
+
puts " ** Unlocking Database"
|
30
|
+
db.unlock!
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/mongolly.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mongolly/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "mongolly"
|
8
|
+
gem.version = Mongolly::VERSION
|
9
|
+
gem.authors = ["Michael Saffitz"]
|
10
|
+
gem.email = ["m@saffitz.com"]
|
11
|
+
gem.description = %q{Easy backups for EBS-based MongoDB Databases}
|
12
|
+
gem.summary = %q{Easy backups for EBS-based MongoDB Databases}
|
13
|
+
gem.homepage = "http://www.github.com/msaffitz/mongolly"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency("thor", ["~> 0.15.4"])
|
21
|
+
gem.add_dependency("mongo", ["~> 1.6.4"])
|
22
|
+
gem.add_dependency("bson_ext", ["~> 1.6.4"])
|
23
|
+
gem.add_dependency("aws-sdk", ["~> 1.5.8"])
|
24
|
+
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongolly
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Michael Saffitz
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: thor
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.15.4
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 0.15.4
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: mongo
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.6.4
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.6.4
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bson_ext
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.6.4
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.6.4
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: aws-sdk
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.5.8
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.5.8
|
78
|
+
description: Easy backups for EBS-based MongoDB Databases
|
79
|
+
email:
|
80
|
+
- m@saffitz.com
|
81
|
+
executables:
|
82
|
+
- mongolly
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files: []
|
85
|
+
files:
|
86
|
+
- .gitignore
|
87
|
+
- Gemfile
|
88
|
+
- LICENSE.txt
|
89
|
+
- README.md
|
90
|
+
- Rakefile
|
91
|
+
- bin/mongolly
|
92
|
+
- lib/mongolly.rb
|
93
|
+
- lib/mongolly/snapshot_manager.rb
|
94
|
+
- lib/mongolly/version.rb
|
95
|
+
- mongolly.gemspec
|
96
|
+
homepage: http://www.github.com/msaffitz/mongolly
|
97
|
+
licenses: []
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ! '>='
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
hash: -4173052183562029100
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
hash: -4173052183562029100
|
120
|
+
requirements: []
|
121
|
+
rubyforge_project:
|
122
|
+
rubygems_version: 1.8.24
|
123
|
+
signing_key:
|
124
|
+
specification_version: 3
|
125
|
+
summary: Easy backups for EBS-based MongoDB Databases
|
126
|
+
test_files: []
|