capistrano-mongo-sync 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 933c334709a5c306b67c966dc4568e9b19d08985
4
+ data.tar.gz: 587bbed62e4b4569e138c297b3b7425fe67e8d7a
5
+ SHA512:
6
+ metadata.gz: c48627b33a655b8921edc1874e2c9f48e92ce31cf5693b11ba5f1ca9552c5e3ca35c5c1d02566aef6669c8b870c8647acdaf3925da451e48eaac421d46a47a2a
7
+ data.tar.gz: 720a51a7c10253eebaf6c000c61d9d043f186c5961c768929ceb5381c5b8e478d3477385a091d3f0495271f661a4bfd35a99dda5abf5f632a98937cc44c79ac9
@@ -0,0 +1 @@
1
+ 2.3.0
@@ -0,0 +1,3 @@
1
+ # 0.1.0
2
+
3
+ Initial release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in capistrano-mongo-sync.gemspec
4
+ gemspec
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ capistrano-mongo-sync (0.1.0)
5
+ capistrano (~> 3.1)
6
+ sshkit (~> 1.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ capistrano (3.4.0)
12
+ i18n
13
+ rake (>= 10.0.0)
14
+ sshkit (~> 1.3)
15
+ i18n (0.7.0)
16
+ metaclass (0.0.4)
17
+ minitest (5.8.4)
18
+ mocha (1.1.0)
19
+ metaclass (~> 0.0.1)
20
+ net-scp (1.2.1)
21
+ net-ssh (>= 2.6.5)
22
+ net-ssh (3.0.2)
23
+ rake (11.1.1)
24
+ sshkit (1.9.0)
25
+ net-scp (>= 1.1.2)
26
+ net-ssh (>= 2.8.0)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ capistrano-mongo-sync!
33
+ minitest (~> 5.8)
34
+ mocha (~> 1.1)
35
+
36
+ BUNDLED WITH
37
+ 1.11.2
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Open Listings, Buy a home without a realtor and get a
4
+ 50% commission refund
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
+ the Software, and to permit persons to whom the Software is furnished to do so,
11
+ subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
19
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
20
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,64 @@
1
+ # capistrano-mongo-sync
2
+
3
+ Use capistrano-mongo-sync to sync your local development database from
4
+ your production database using:
5
+
6
+ ```ruby
7
+ cap production mongo:pull
8
+ ```
9
+
10
+ Or sync just one collection from the database:
11
+
12
+ ```ruby
13
+ COLLECTION=users cap production mongo:pull
14
+ ```
15
+
16
+ Or sync your staging database from your production database.
17
+
18
+ ```ruby
19
+ cap production mongo:sync_prod_to_staging
20
+ ```
21
+
22
+ If you've already downloaded a mongo dump with the
23
+ cap task, it will ask you if you'd like to use that local dump.
24
+ If someone has already created a mongo dump recently on the remote server,
25
+ the cap task will ask if you'd like to use that dump. Older mongodumps, both
26
+ remote and local, will be deleted when you run the cap task.
27
+
28
+ ## Usage
29
+ Add to your Gemfile:
30
+
31
+ ```ruby
32
+ gem 'capistrano-mongo-sync'
33
+ ```
34
+
35
+ Require in `Capfile` to use the predefined tasks:
36
+
37
+ ```ruby
38
+ require 'capistrano/mongo-sync'
39
+ ```
40
+
41
+ In deploy.rb, set some variables:
42
+
43
+ ```ruby
44
+ set :production_db, 'PRODUCTION_DB'
45
+ set :development_db, 'DEVELOPMENT_DB'
46
+ ```
47
+
48
+ set some optional variables:
49
+ ```ruby
50
+ set :staging_db, 'STAGING_DB'
51
+ ```
52
+
53
+ enable hipchat notifications (requires hipchat gem):
54
+ ```ruby
55
+ set :hipchat_client, HipChat::Client.new('HIPCHAT_TOKEN')
56
+ ```
57
+
58
+ change where mongodumps are kept remotely and locally:
59
+ ```ruby
60
+ set :remote_dump_base, '/tmp/dumps'
61
+ set :local_dump_base, '/tmp/dumps'
62
+ ```
63
+
64
+ Or write your own tasks using the MongoSync class
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task default: :test
@@ -0,0 +1,22 @@
1
+ Gem::Specification.new do |gem|
2
+ gem.name = 'capistrano-mongo-sync'
3
+ gem.version = '0.1.4'
4
+ gem.date = '2016-06-13'
5
+ gem.summary = "A tool for keeping local mongo in sync with remote"
6
+ gem.description = "A tool for keeping local mongo in sync with remote"
7
+ gem.authors = ["Open Listings Engineering"]
8
+ gem.email = 'engineering@openlistinggem.com'
9
+ gem.homepage = 'https://github.com/openlistings/capistrano-mongo-sync'
10
+ gem.license = 'MIT'
11
+
12
+ gem.files = `git ls-files`.split($/)
13
+ gem.test_files = gem.files.grep(%r{^(test)/})
14
+ gem.require_paths = ['lib']
15
+
16
+ gem.add_dependency 'capistrano', '~> 3.1'
17
+ gem.add_dependency 'sshkit', '~> 1.2'
18
+
19
+ gem.add_development_dependency 'minitest', '~> 5.8'
20
+ gem.add_development_dependency 'mocha', '~> 1.1'
21
+
22
+ end
@@ -0,0 +1,2 @@
1
+ load File.expand_path('../mongo_sync/mongo_sync.rb', __FILE__)
2
+ load File.expand_path('../tasks/mongo-sync.cap', __FILE__)
@@ -0,0 +1,75 @@
1
+ ##
2
+ # Usage:
3
+ # ./bin/cap production mongo:pull
4
+ # COLLECTION=agents ./bin/cap production mongo:pull
5
+ # ./bin/cap production mongo:sync_prod_to_staging
6
+
7
+ namespace :mongo do
8
+ set :remote_dump_base, '/tmp/dumps'
9
+ set :local_dump_base, '/tmp/dumps'
10
+ set :collection, ENV['COLLECTION'] || 'full'
11
+
12
+ task :sync_prod_to_staging do
13
+ set :from_db, fetch(:production_db)
14
+
15
+ on roles(:db) do
16
+ ms_remote = MongoSync.new(self)
17
+ ms_remote.hipchat_notify! 'Engineering', 'capistrano', 'Started syncing prod to staging...', color: 'yellow'
18
+ ms_remote.remote_setup!
19
+ ms_remote.remote_cleanup!
20
+
21
+ remote_dump_dir = ms_remote.last_remote_dump
22
+ remote_dump_dir ||= ms_remote.remote_mongodump!
23
+
24
+ ms_remote.staging_mongorestore! remote_dump_dir
25
+ ms_remote.hipchat_notify! 'Engineering', 'capistrano', 'Finished syncing prod to staging...', color: 'green'
26
+ end
27
+ end
28
+
29
+ task :pull do
30
+ set :from_db, :production == fetch(:stage) ? fetch(:production_db) : fetch(:staging_db)
31
+
32
+ run_locally do
33
+ ms_local = MongoSync.new(self)
34
+ ms_local.local_setup!
35
+ ms_local.local_cleanup!
36
+
37
+ # variable scope
38
+ remote_tgz = nil
39
+ local_tgz = nil
40
+
41
+ if local_tgz = ms_local.last_local_dump
42
+ # use local tgz
43
+ ms_local.local_unarchive! local_tgz
44
+
45
+ local_dump_dir = File.join fetch(:local_dump_base), File.basename(local_tgz, '.tgz')
46
+ ms_local.local_mongorestore!(local_dump_dir)
47
+ else
48
+ # get dump from remote
49
+ on roles(:db) do
50
+ ms_remote = MongoSync.new(self)
51
+ ms_remote.remote_setup!
52
+ ms_remote.remote_cleanup!
53
+
54
+ # find & choose tarfile or dump & archive
55
+ remote_tgz = ms_remote.last_remote_dump_tgz
56
+ unless remote_tgz
57
+ dump_dir = ms_remote.remote_mongodump!
58
+ remote_tgz = ms_remote.remote_archive! dump_dir
59
+ end
60
+
61
+ # download!
62
+ local_tgz = File.join fetch(:local_dump_base), File.basename(remote_tgz)
63
+ download! remote_tgz, local_tgz, method: :scp
64
+ end
65
+
66
+ # unarchive!
67
+ ms_local.local_unarchive!(local_tgz)
68
+
69
+ # restore!
70
+ local_dump_dir = File.join fetch(:local_dump_base), File.basename(remote_tgz, '.tgz')
71
+ ms_local.local_mongorestore!(local_dump_dir)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,183 @@
1
+ class MongoSync
2
+ def initialize(connection)
3
+ @connection = connection
4
+ @remote_dump_base = fetch(:remote_dump_base)
5
+ @local_dump_base = fetch(:local_dump_base)
6
+ @production_db = fetch(:production_db)
7
+ @development_db = fetch(:development_db)
8
+ @staging_db = fetch(:staging_db)
9
+ @from_db = fetch(:from_db)
10
+ @collection = fetch(:collection) || 'full'
11
+ @hipchat_client = fetch(:hipchat_client)
12
+
13
+ fail "Incomplete configuration: missing remote_dump_base" unless @remote_dump_base
14
+ fail "Incomplete configuration: missing local_dump_base" unless @local_dump_base
15
+ fail "Incomplete configuration: missing production_db" unless @production_db
16
+ fail "Incomplete configuration: missing development_db" unless @development_db
17
+ fail "Incomplete configuration: missing from_db" unless @from_db
18
+ end
19
+
20
+ # the first part of the dump dir, without the timestamp... for example "mydatabase-full"
21
+ def dump_dir_part
22
+ [@from_db, @collection].join('-')
23
+ end
24
+
25
+ ## Remote
26
+ def remote_setup!
27
+ @connection.execute :mkdir, '-p', @remote_dump_base
28
+ end
29
+
30
+ def remote_cleanup!
31
+ pattern = File.join(@remote_dump_base, '*')
32
+
33
+ if @connection.test "find #{pattern} -mtime +1"
34
+ @connection.execute :find, pattern, "-mtime +1 -exec rm {} \\;"
35
+ end
36
+ end
37
+
38
+ def remote_mongodump!
39
+ dump_dir = [dump_dir_part, Time.now.strftime('%Y-%m-%d-%H-%M')].join('-')
40
+
41
+ args = ['-d', @from_db, '-o', File.join(@remote_dump_base, dump_dir)]
42
+ args += ['-c', @collection] unless 'full' == @collection
43
+
44
+ @connection.execute :mongodump, *args
45
+
46
+ dump_dir
47
+ end
48
+
49
+ def staging_mongorestore!( remote_dump_dir )
50
+ full_path_to_remote_dump_dir = if remote_dump_dir == File.basename(remote_dump_dir)
51
+ File.join(@remote_dump_base, remote_dump_dir, @production_db)
52
+ else
53
+ remote_dump_dir
54
+ end
55
+
56
+ @connection.execute :mongorestore, '--drop', '-d', @staging_db, full_path_to_remote_dump_dir
57
+ end
58
+
59
+ def last_remote_dump
60
+ previous_remote_dump_dirs_wildcard = File.join @remote_dump_base, '%s*/%s' % [dump_dir_part, @production_db]
61
+
62
+ if @connection.test( "ls -td #{previous_remote_dump_dirs_wildcard}" )
63
+ dump_candidate = @connection.capture(:ls, '-td', previous_remote_dump_dirs_wildcard).split("\n")[0]
64
+
65
+ dump_prompt(:use_remote_dump_dir, dump_candidate)
66
+
67
+ if 'y' == fetch(:use_remote_dump_dir)
68
+ dump_candidate
69
+ else
70
+ nil
71
+ end
72
+ else
73
+ nil
74
+ end
75
+ end
76
+
77
+ def last_remote_dump_tgz
78
+ previous_remote_dump_tgz_wildcard = '%s*.tgz' % File.join( @remote_dump_base, dump_dir_part )
79
+
80
+ if @connection.test "ls -t #{previous_remote_dump_tgz_wildcard}"
81
+ dump_candidate = @connection.capture(:ls, '-t', previous_remote_dump_tgz_wildcard).split("\n")[0]
82
+ dump_prompt(:use_remote_dump_tgz, dump_candidate)
83
+
84
+ if 'y' == fetch(:use_remote_dump_tgz)
85
+ dump_candidate
86
+ else
87
+ nil
88
+ end
89
+ else
90
+ nil
91
+ end
92
+ end
93
+
94
+ def remote_archive!( dump_dir )
95
+ tar_filename = '%s.tgz' % dump_dir
96
+ @connection.within( @remote_dump_base ) do
97
+ @connection.execute :tar, '-czvf', tar_filename, dump_dir
98
+ end
99
+ File.join @remote_dump_base, tar_filename
100
+ end
101
+
102
+ ## Local
103
+ def local_setup!
104
+ @connection.execute :mkdir, '-p', @local_dump_base
105
+ end
106
+
107
+ def local_cleanup!
108
+ pattern = File.join(@local_dump_base, '*')
109
+
110
+ if @connection.test "find #{pattern} -mtime +1"
111
+ @connection.execute :find, pattern, "-mtime +1 -exec rm {} \\;"
112
+ end
113
+ end
114
+
115
+ def last_local_dump
116
+ pattern = File.join(@local_dump_base, '%s*.tgz' % dump_dir_part)
117
+
118
+ if @connection.test "ls #{pattern}"
119
+ local_dump_candidate = @connection.capture(:ls, '-td', pattern).split("\n")[0]
120
+
121
+ dump_prompt(:use_local_dump, local_dump_candidate)
122
+
123
+ if 'y' == fetch(:use_local_dump)
124
+ local_dump_candidate
125
+ else
126
+ nil
127
+ end
128
+ else
129
+ nil
130
+ end
131
+ end
132
+
133
+ def local_unarchive!(local_tgz)
134
+ local_dump_dir = File.join @local_dump_base, File.basename(local_tgz, '.tgz')
135
+ if @connection.test("ls #{local_dump_dir}")
136
+ # warn "Skipping untar and instead using previously unpacked dump_dir #{local_dump_dir}"
137
+ else
138
+ @connection.within( @local_dump_base ) do
139
+ @connection.execute :tar, '-xzvf', local_tgz
140
+ end
141
+ end
142
+ end
143
+
144
+ def local_mongorestore!(local_dump_dir)
145
+ db_dump_path = File.join local_dump_dir, @from_db
146
+ @connection.within( @local_dump_base ) do
147
+ @connection.execute :mongorestore, '--drop', '-d', @development_db, db_dump_path
148
+ end
149
+ end
150
+
151
+ ## Hipchat
152
+ def hipchat_notify!( room, user, msg, opts = {} )
153
+ return unless @hipchat_client
154
+ @hipchat_client[room].send(user, msg, opts)
155
+ end
156
+
157
+ ## Utility
158
+ def dump_prompt_message( gvar, filename )
159
+ dump_tstamp = filename[/(\d{4}-\d{2}-\d{2}-\d{2}-\d{2})/, 1]
160
+ dump_time = Time.new(*dump_tstamp.split('-'))
161
+
162
+ fmt = if dump_time.strftime('%D') == Time.now.strftime('%D')
163
+ 'today at %I:%M %p'
164
+ elsif dump_time.strftime('%D') == (Time.now - 24 * 60 * 60).strftime('%D')
165
+ 'yesterday at %I:%M %p'
166
+ else
167
+ '%B %d, %Y at %I:%M %p'
168
+ end
169
+
170
+ dump_time_human = dump_time.strftime(fmt)
171
+
172
+ local_remote = gvar.to_s =~ /local/ ? 'local' : 'remote'
173
+ 'Use %s dump from %s? "%s"? (y/n)' % [local_remote, dump_time_human, filename]
174
+ end
175
+
176
+ def dump_prompt( gvar, filename )
177
+ return false unless filename
178
+
179
+ until fetch(gvar) =~ /\A[yn]\Z/
180
+ @connection.ask(gvar, dump_prompt_message(gvar, filename))
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,75 @@
1
+ ##
2
+ # Usage:
3
+ # ./bin/cap production mongo:pull
4
+ # COLLECTION=agents ./bin/cap production mongo:pull
5
+ # ./bin/cap production mongo:sync_prod_to_staging
6
+
7
+ namespace :mongo do
8
+ set :remote_dump_base, '/tmp/dumps'
9
+ set :local_dump_base, '/tmp/dumps'
10
+ set :collection, ENV['COLLECTION'] || 'full'
11
+
12
+ task :sync_prod_to_staging do
13
+ set :from_db, fetch(:production_db)
14
+
15
+ on roles(:db) do
16
+ ms_remote = MongoSync.new(self)
17
+ ms_remote.hipchat_notify! 'Engineering', 'capistrano', 'Started syncing prod to staging...', color: 'yellow'
18
+ ms_remote.remote_setup!
19
+ ms_remote.remote_cleanup!
20
+
21
+ remote_dump_dir = ms_remote.last_remote_dump
22
+ remote_dump_dir ||= ms_remote.remote_mongodump!
23
+
24
+ ms_remote.staging_mongorestore! remote_dump_dir
25
+ ms_remote.hipchat_notify! 'Engineering', 'capistrano', 'Finished syncing prod to staging...', color: 'green'
26
+ end
27
+ end
28
+
29
+ task :pull do
30
+ set :from_db, :production == fetch(:stage) ? fetch(:production_db) : fetch(:staging_db)
31
+
32
+ run_locally do
33
+ ms_local = MongoSync.new(self)
34
+ ms_local.local_setup!
35
+ ms_local.local_cleanup!
36
+
37
+ # variable scope
38
+ remote_tgz = nil
39
+ local_tgz = nil
40
+
41
+ if local_tgz = ms_local.last_local_dump
42
+ # use local tgz
43
+ ms_local.local_unarchive! local_tgz
44
+
45
+ local_dump_dir = File.join fetch(:local_dump_base), File.basename(local_tgz, '.tgz')
46
+ ms_local.local_mongorestore!(local_dump_dir)
47
+ else
48
+ # get dump from remote
49
+ on roles(:db) do
50
+ ms_remote = MongoSync.new(self)
51
+ ms_remote.remote_setup!
52
+ ms_remote.remote_cleanup!
53
+
54
+ # find & choose tarfile or dump & archive
55
+ remote_tgz = ms_remote.last_remote_dump_tgz
56
+ unless remote_tgz
57
+ dump_dir = ms_remote.remote_mongodump!
58
+ remote_tgz = ms_remote.remote_archive! dump_dir
59
+ end
60
+
61
+ # download!
62
+ local_tgz = File.join fetch(:local_dump_base), File.basename(remote_tgz)
63
+ download! remote_tgz, local_tgz, method: :scp
64
+ end
65
+
66
+ # unarchive!
67
+ ms_local.local_unarchive!(local_tgz)
68
+
69
+ # restore!
70
+ local_dump_dir = File.join fetch(:local_dump_base), File.basename(remote_tgz, '.tgz')
71
+ ms_local.local_mongorestore!(local_dump_dir)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,174 @@
1
+ require 'minitest/autorun'
2
+ require 'mocha/mini_test'
3
+
4
+ require 'capistrano/mongo_sync/mongo_sync'
5
+
6
+ class MongoSync
7
+ def fetch(x)
8
+ @remote_dump_base = '/mnt/tmp/dumps'.freeze
9
+ @local_dump_base = '/tmp/dumps'.freeze
10
+ @development_db = 'mydb'.freeze
11
+ @production_db = 'mydb'.freeze
12
+ @staging_db = 'mydb_staging'.freeze
13
+ @from_db = 'mydb'.freeze
14
+ @hipchat_client = nil
15
+ @collection = 'full'
16
+
17
+ instance_variable_get("@#{x}")
18
+ end
19
+ end
20
+
21
+ class MongoSyncTest < Minitest::Test
22
+ def setup
23
+ @time_of_run = Time.new(2015, 1, 1, 1, 1)
24
+ Time.stubs(:now).returns(@time_of_run)
25
+
26
+ @connection = mock()
27
+ @mongo_sync = MongoSync.new(@connection)
28
+ end
29
+
30
+ def test_local_setup
31
+ @connection.expects(:execute).with(:mkdir, '-p', '/tmp/dumps').once()
32
+ @mongo_sync.local_setup!
33
+ end
34
+
35
+ def test_local_cleanup
36
+ @connection.expects(:test).with('find /tmp/dumps/* -mtime +1').once().returns(true)
37
+ @connection.expects(:execute).with(:find, '/tmp/dumps/*', '-mtime +1 -exec rm {} \\;').once()
38
+ @mongo_sync.local_cleanup!
39
+ end
40
+
41
+ def test_remote_setup
42
+ @connection.expects(:execute).with(:mkdir, '-p', '/mnt/tmp/dumps').once()
43
+ @mongo_sync.remote_setup!
44
+ end
45
+
46
+ def test_remote_cleanup
47
+ @connection.expects(:test).with('find /mnt/tmp/dumps/* -mtime +1').returns(true).once()
48
+ @connection.expects(:execute).with(:find, '/mnt/tmp/dumps/*', '-mtime +1 -exec rm {} \\;').once()
49
+ @mongo_sync.remote_cleanup!
50
+ end
51
+
52
+ def test_remote_mongodump_full
53
+ @connection.expects(:execute).once().with(:mongodump, '-d', 'mydb', '-o', '/mnt/tmp/dumps/mydb-full-2015-01-01-01-01')
54
+ output_dir = @mongo_sync.remote_mongodump!
55
+ assert_equal 'mydb-full-2015-01-01-01-01', output_dir
56
+ end
57
+
58
+ def test_remote_mongodump_agents_collection
59
+ @mongo_sync.instance_variable_set("@collection", 'agents')
60
+ @connection.expects(:execute).once().with(:mongodump, '-d', 'mydb', '-o', '/mnt/tmp/dumps/mydb-agents-2015-01-01-01-01', '-c', 'agents')
61
+ output_dir = @mongo_sync.remote_mongodump!
62
+ assert_equal 'mydb-agents-2015-01-01-01-01', output_dir
63
+ end
64
+
65
+ def test_dump_prompt_message
66
+ path_to_tgz = '/tmp/dumps/mydb-full-2015-01-01-01-00.tgz'
67
+ expected_msg = "Use local dump from today at 01:00 AM? \"%s\"? (y/n)" % path_to_tgz
68
+ actual_msg = @mongo_sync.dump_prompt_message :use_local_dump, path_to_tgz
69
+ assert_equal expected_msg, actual_msg
70
+ end
71
+
72
+ def test_dump_prompt_accepts_y_n
73
+ path_to_tgz = '/tmp/dumps/mydb-full-2015-01-01-01-00.tgz'
74
+
75
+ @mongo_sync.instance_variable_set '@use_local_dump', 'y'
76
+ @mongo_sync.dump_prompt :use_local_dump, path_to_tgz
77
+
78
+ @mongo_sync.instance_variable_set '@use_local_dump', 'n'
79
+ @mongo_sync.dump_prompt :use_local_dump, path_to_tgz
80
+ end
81
+
82
+ def test_hipchat_notify
83
+ msg = 'Finished syncing prod to staging...'
84
+ opts = { color: 'green' }
85
+
86
+ @hipchat_client = mock()
87
+ @mongo_sync.instance_variable_set("@hipchat_client", @hipchat_client)
88
+ @engineering_room = mock()
89
+ @hipchat_client.expects("[]").once().with('Engineering').returns(@engineering_room)
90
+ @engineering_room.expects('capistrano').with(msg, opts)
91
+ @mongo_sync.hipchat_notify! 'Engineering', 'capistrano', msg, opts
92
+ end
93
+
94
+ def test_staging_mongorestore_full_path
95
+ @connection.expects(:execute).once().with(:mongorestore, '--drop', '-d', 'mydb_staging', '/mnt/tmp/dumps/mydb-agents-2015-01-01-01-01/mydb')
96
+ @mongo_sync.staging_mongorestore! '/mnt/tmp/dumps/mydb-agents-2015-01-01-01-01/mydb'
97
+ end
98
+
99
+ def test_staging_mongorestore_relative_to_dump_base
100
+ @connection.expects(:execute).once().with(:mongorestore, '--drop', '-d', 'mydb_staging', '/mnt/tmp/dumps/mydb-agents-2015-01-01-01-01/mydb')
101
+ @mongo_sync.staging_mongorestore! 'mydb-agents-2015-01-01-01-01'
102
+ end
103
+
104
+ def test_last_remote_dump_no_dumps
105
+ @connection.expects(:test, 'ls -td /mnt/tmp/dumps/*/mydb').returns(false)
106
+ lrd = @mongo_sync.last_remote_dump
107
+ assert_equal nil, lrd
108
+ end
109
+
110
+ def test_last_remote_dump_dumps_y
111
+ dumps = %w( /mnt/tmp/dumps/mydb-full-2015-10-07-09-36/mydb /mnt/tmp/dumps/mydb-full-2015-10-07-08-50/mydb ).join("\n")
112
+
113
+ @connection.expects(:test, 'ls -td /mnt/tmp/dumps/*').returns(true)
114
+ @connection.expects(:capture).with(:ls, '-td', '/mnt/tmp/dumps/mydb-full*/mydb').returns(dumps)
115
+ @mongo_sync.instance_variable_set '@use_remote_dump_dir', 'y'
116
+
117
+ lrd = @mongo_sync.last_remote_dump
118
+ assert_equal '/mnt/tmp/dumps/mydb-full-2015-10-07-09-36/mydb', lrd
119
+ end
120
+
121
+ def test_last_remote_dump_dumps_n
122
+ dumps = %w( /mnt/tmp/dumps/mydb-full-2015-10-07-09-36/mydb /mnt/tmp/dumps/mydb-full-2015-10-07-08-50/mydb ).join("\n")
123
+
124
+ @connection.expects(:test, 'ls -td /mnt/tmp/dumps/*/mydb').returns(true)
125
+ @connection.expects(:capture).with(:ls, '-td', '/mnt/tmp/dumps/mydb-full*/mydb').returns(dumps)
126
+ @mongo_sync.instance_variable_set '@use_remote_dump_dir', 'n'
127
+
128
+ lrd = @mongo_sync.last_remote_dump
129
+ assert_equal nil, lrd
130
+ end
131
+
132
+ def test_last_local_dump_no_dumps
133
+ @connection.expects(:test, 'ls -td /mnt/tmp/dumps/mydb-full*/mydb').returns(false)
134
+ lrd = @mongo_sync.last_remote_dump
135
+ assert_equal nil, lrd
136
+ end
137
+
138
+ def test_last_remote_dump_tgz
139
+ @connection.expects(:test, 'ls -td /mnt/tmp/dumps/mydb-full*/mydb').returns(false)
140
+ lrd = @mongo_sync.last_remote_dump
141
+ assert_equal nil, lrd
142
+ end
143
+
144
+ def test_local_mongorestore
145
+ dump_dir = 'mydb-full-2015-10-07-09-36'
146
+ @connection.expects(:within).with('/tmp/dumps').once.yields
147
+ @connection.expects(:execute).with(:mongorestore, '--drop', '-d', 'mydb', 'mydb-full-2015-10-07-09-36/mydb').once
148
+ @mongo_sync.local_mongorestore! dump_dir
149
+ end
150
+
151
+ def test_local_unarchive_preexisting
152
+ tgz = 'mydb-full-2015-10-07-09-36.tgz'
153
+ local_dump_dir = '/tmp/dumps/mydb-full-2015-10-07-09-36'
154
+ @connection.expects(:test).with("ls #{local_dump_dir}").once.returns(true)
155
+ @mongo_sync.local_unarchive! tgz
156
+ end
157
+
158
+ def test_local_unarchive_need_to_download
159
+ tgz = 'mydb-full-2015-10-07-09-36.tgz'
160
+ local_dump_dir = '/tmp/dumps/mydb-full-2015-10-07-09-36'
161
+ @connection.expects(:test).with("ls #{local_dump_dir}").once.returns(false)
162
+ @connection.expects(:within).with('/tmp/dumps').once.yields
163
+ @connection.expects(:execute).with(:tar, '-xzvf', tgz).once
164
+ @mongo_sync.local_unarchive! tgz
165
+ end
166
+
167
+ def test_remote_archive
168
+ dump_dir = 'mydb-full-2015-10-07-09-36'
169
+ @connection.expects(:within).with('/mnt/tmp/dumps').once.yields
170
+ @connection.expects(:execute).with(:tar, '-czvf', 'mydb-full-2015-10-07-09-36.tgz', 'mydb-full-2015-10-07-09-36').once
171
+ @mongo_sync.remote_archive! dump_dir
172
+ end
173
+
174
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capistrano-mongo-sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Open Listings Engineering
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-06-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capistrano
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sshkit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.8'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.8'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mocha
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.1'
69
+ description: A tool for keeping local mongo in sync with remote
70
+ email: engineering@openlistinggem.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - ".ruby-version"
76
+ - CHANGELOG.md
77
+ - Gemfile
78
+ - Gemfile.lock
79
+ - LICENSE
80
+ - README.md
81
+ - Rakefile
82
+ - capistrano-mongo-sync.gemspec
83
+ - lib/capistrano/mongo-sync.rb
84
+ - lib/capistrano/mongo-sync/mongo-sync.cap
85
+ - lib/capistrano/mongo_sync/mongo_sync.rb
86
+ - lib/capistrano/tasks/mongo-sync.cap
87
+ - test/test_mongo_sync.rb
88
+ homepage: https://github.com/openlistings/capistrano-mongo-sync
89
+ licenses:
90
+ - MIT
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.5.1
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: A tool for keeping local mongo in sync with remote
112
+ test_files:
113
+ - test/test_mongo_sync.rb