capistrano-mongo-sync 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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