synchronised_migration 0.1.0.pre.alpha.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3ab84276b5cbc3b6a059da59e59c414496f72a69
4
+ data.tar.gz: ba49512428c7d3554fbafa2d98b6746204f3d4ad
5
+ SHA512:
6
+ metadata.gz: f18218af70a990a21ccdb7a88944bcf5482d56fc84b9181b52b48ee60339d1b412f27dcf91922849d617e59f0ca4dcc8429b8c5cfea32eef707d4286633c489c
7
+ data.tar.gz: 3dc0653bc15b35736138bacbdb9752e76589f571657966bc0b5ef641e1f67350d1fa4b201690320c54aa00fcc38001e74ce44c3cd8d10c262d21bbfca67c0180
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ /.bundle
2
+ /vendor
3
+ /coverage
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.7
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ ---
2
+ language: ruby
3
+ script: bundle && bundle exec rspec
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Synchronised Migration
2
+
3
+ ## 0.1.0 Unreleased
4
+
5
+ * [DO-70] Use Redis to synchronise migrations across multiple deployments
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,58 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ synchronised_migration (0.1.0)
5
+ redlock (~> 0.2)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ byebug (9.1.0)
11
+ coderay (1.1.2)
12
+ diff-lcs (1.3)
13
+ docile (1.1.5)
14
+ json (2.1.0)
15
+ method_source (0.8.2)
16
+ pry (0.10.4)
17
+ coderay (~> 1.1.0)
18
+ method_source (~> 0.8.1)
19
+ slop (~> 3.4)
20
+ pry-byebug (3.5.0)
21
+ byebug (~> 9.1)
22
+ pry (~> 0.10)
23
+ redis (3.3.3)
24
+ redlock (0.2.0)
25
+ redis (~> 3, >= 3.0.0)
26
+ rspec (3.6.0)
27
+ rspec-core (~> 3.6.0)
28
+ rspec-expectations (~> 3.6.0)
29
+ rspec-mocks (~> 3.6.0)
30
+ rspec-core (3.6.0)
31
+ rspec-support (~> 3.6.0)
32
+ rspec-expectations (3.6.0)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.6.0)
35
+ rspec-mocks (3.6.0)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.6.0)
38
+ rspec-support (3.6.0)
39
+ simplecov (0.15.0)
40
+ docile (~> 1.1.0)
41
+ json (>= 1.8, < 3)
42
+ simplecov-html (~> 0.10.0)
43
+ simplecov-html (0.10.2)
44
+ simplecov-rcov (0.2.3)
45
+ simplecov (>= 0.4.1)
46
+ slop (3.6.0)
47
+
48
+ PLATFORMS
49
+ ruby
50
+
51
+ DEPENDENCIES
52
+ pry-byebug (~> 3.5)
53
+ rspec (~> 3.6)
54
+ simplecov-rcov (~> 0.2)
55
+ synchronised_migration!
56
+
57
+ BUNDLED WITH
58
+ 1.15.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 SeaLink Travel Group Limited
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Synchronised Migration
2
+
3
+ [![Build Status](https://travis-ci.org/sealink/synchronised-migration-rb.svg?branch=master)](https://travis-ci.org/sealink/synchronised-migration-rb)
4
+
5
+ This gem makes it possible to deploy multiple instances with data migration
6
+ simultaneously. It uses Redis to ensure that there will be only one migration
7
+ running.
8
+
9
+ This gem works out of the box with a Rails project. It should work with other
10
+ Ruby projects so long as you load the rake task in Rakefile instead of relying
11
+ on Railtie.
12
+
13
+ This is a Ruby port of the same logic written in PHP in our [Craft
14
+ Docker](https://github.com/sealink/craft-docker) project.
15
+
16
+ ## Usage
17
+
18
+ Class `RedisConfig` needs to be provided as follow.
19
+
20
+ ```
21
+ RedisConfig.get[:host] # example.com
22
+ RedisConfig.get[:port] # 6379
23
+ RedisConfig.get[:db] # 0
24
+ ```
25
+
26
+ You may override these settings through environment variables.
27
+
28
+ ```
29
+ SYNC_RAKE_TASK=launch:migrate
30
+ REDLOCK_TIMEOUT_MS=3600000
31
+ REDLOCK_RETRY_DELAY_MS=200
32
+ REDLOCK_LOCK_KEY=migration-in-progress
33
+ REDLOCK_FAIL_KEY=migration-failed
34
+ ```
35
+
36
+ Run this before you launch the application during deployment.
37
+
38
+ ```
39
+ $ rake synchronised_migrate:execute
40
+ ```
41
+
42
+ ## Testing
43
+
44
+ Please refer to `.travis.yml` for testing.
@@ -0,0 +1,99 @@
1
+ require 'synchronised_migration'
2
+ require 'synchronised_migration/result'
3
+ require 'redis'
4
+ require 'redlock'
5
+ require 'singleton'
6
+
7
+ class SynchronisedMigration::Main
8
+ include Singleton
9
+
10
+ Result = SynchronisedMigration::Result
11
+
12
+ class << self
13
+ extend Forwardable
14
+ def_delegators :instance, :call
15
+ end
16
+
17
+ def call
18
+ lock_and_execute
19
+ end
20
+
21
+ private
22
+
23
+ def lock_and_execute
24
+ redlock.lock! lock_key, timeout do
25
+ execute
26
+ end
27
+ end
28
+
29
+ def execute
30
+ return Result.new 'Halting the script because the previous migration failed.' if previous_failed?
31
+ mark_failed
32
+ migrate
33
+ remove_fail_marker
34
+ Result.new
35
+ end
36
+
37
+ def previous_failed?
38
+ value = redis.get(fail_key)
39
+ not value.nil? and not value.empty?
40
+ end
41
+
42
+ def mark_failed
43
+ redis.set fail_key, 1
44
+ end
45
+
46
+ def remove_fail_marker
47
+ redis.del fail_key
48
+ end
49
+
50
+ def migrate
51
+ Rake::Task[target_rake_task].invoke
52
+ end
53
+
54
+ def target_rake_task
55
+ ENV.fetch 'SYNC_RAKE_TASK', 'launch:migrate'
56
+ end
57
+
58
+ def redis
59
+ @redis ||= Redis.new(url: redis_url)
60
+ end
61
+
62
+ def redlock
63
+ @redlock ||= Redlock::Client.new(
64
+ [ redis_url ], {
65
+ retry_count: retry_count,
66
+ retry_delay: retry_delay
67
+ }
68
+ )
69
+ end
70
+
71
+ def redis_url
72
+ sprintf(
73
+ 'redis:://%s:%s/%s',
74
+ RedisConfig.get[:host],
75
+ RedisConfig.get[:port],
76
+ RedisConfig.get[:db]
77
+ )
78
+ end
79
+
80
+ def timeout
81
+ ENV.fetch('REDLOCK_TIMEOUT_MS', 3_600_000).to_i
82
+ end
83
+
84
+ def retry_delay
85
+ ENV.fetch('REDLOCK_RETRY_DELAY_MS', 200).to_i
86
+ end
87
+
88
+ def retry_count
89
+ timeout / retry_delay
90
+ end
91
+
92
+ def lock_key
93
+ ENV.fetch 'REDLOCK_LOCK_KEY', 'migration-in-progress'
94
+ end
95
+
96
+ def fail_key
97
+ ENV.fetch 'REDLOCK_FAIL_KEY', 'migration-failed'
98
+ end
99
+ end
@@ -0,0 +1,5 @@
1
+ class SynchronisedMigration::Railtie < Rails::Railtie
2
+ rake_tasks do
3
+ load 'tasks/synchronised_migration.rake'
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ require 'synchronised_migration'
2
+
3
+ class SynchronisedMigration::Result
4
+ attr_accessor :error
5
+
6
+ def initialize(error = nil)
7
+ @error = error
8
+ end
9
+
10
+ def success?
11
+ error.nil?
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module SynchronisedMigration
2
+ VERSION = '0.1.0-alpha.1'
3
+ end
@@ -0,0 +1,4 @@
1
+ module SynchronisedMigration
2
+ end
3
+
4
+ require 'synchronised_migration/railtie' if defined?(Rails)
@@ -0,0 +1,9 @@
1
+ require 'synchronised_migration/main'
2
+
3
+ namespace :synchronised_migration do
4
+ task :execute do
5
+ result = SynchronisedMigration::Main.call
6
+ next if result.success?
7
+ fail result.error
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'pry'
2
+ require 'simplecov'
3
+ require 'simplecov-rcov'
4
+
5
+ SimpleCov.formatter = SimpleCov::Formatter::RcovFormatter
6
+ SimpleCov.minimum_coverage 100
7
+ SimpleCov.start do
8
+ add_filter %r{^/vendor}
9
+ end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'synchronised_migration/main'
3
+
4
+ describe SynchronisedMigration::Main do
5
+ subject { described_class }
6
+ let(:result) { subject.call }
7
+
8
+ context 'when the prerequisites are meet' do
9
+ let(:redis) { double }
10
+ let(:redlock) { double }
11
+ let(:fail_marker_value) { nil }
12
+ let(:rake_task) { double }
13
+
14
+ before do
15
+ subject.instance.instance_variable_set :@redis, nil
16
+ subject.instance.instance_variable_set :@redlock, nil
17
+
18
+ allow(Redis).to receive(:new).and_return(redis)
19
+ allow(redis).to receive(:get).and_return(fail_marker_value)
20
+ allow(redis).to receive(:set)
21
+ allow(redis).to receive(:del)
22
+
23
+ allow(Redlock::Client).to receive(:new).and_return(redlock)
24
+ allow(redlock).to receive(:lock!) { |lock_key, timeout, &block| block.call }
25
+
26
+ stub_const 'Rake', Module.new
27
+ stub_const 'Rake::Task', { 'launch:migrate' => rake_task }
28
+
29
+ allow(rake_task).to receive(:invoke)
30
+
31
+ stub_const(
32
+ 'RedisConfig', double(
33
+ get: {
34
+ host: 'example.com',
35
+ port: 6379,
36
+ db: 0
37
+ }
38
+ )
39
+ )
40
+ end
41
+
42
+ context 'in the happy path' do
43
+ it 'executes the migration successfully' do
44
+ expect(result).to be_success
45
+ expect(redlock).to have_received(:lock!)
46
+ expect(redis).to have_received(:get).with('migration-failed')
47
+ expect(redis).to have_received(:set).with('migration-failed', 1)
48
+ expect(rake_task).to have_received(:invoke)
49
+ expect(redis).to have_received(:del).with('migration-failed')
50
+ end
51
+ end
52
+
53
+ context 'after a deployment failed previously' do
54
+ let(:fail_marker_value) { '1' }
55
+
56
+ it "doesn't execute the migration" do
57
+ expect(result).not_to be_success
58
+ expect(rake_task).not_to have_received(:invoke)
59
+ end
60
+ end
61
+
62
+ context 'when the task crashed' do
63
+ before do
64
+ allow(rake_task).to receive(:invoke) do
65
+ fail 'An error message'
66
+ end
67
+ end
68
+
69
+ it 'marks the failure in Redis' do
70
+ expect { result }.to raise_error('An error message')
71
+ expect(redis).to have_received(:set).with('migration-failed', 1)
72
+ expect(redis).not_to have_received(:del)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,23 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'synchronised_migration/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'synchronised_migration'
7
+ spec.version = SynchronisedMigration::VERSION
8
+ spec.authors = ['Alvin Yim']
9
+ spec.email = 'support@travellink.com.au'
10
+ spec.description = 'Use Redis to record the data migration status'
11
+ spec.summary = 'For deploying to multiple instances simultaneously'
12
+ spec.homepage = 'https://github.com/sealink/synchronised-migration-rb'
13
+
14
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_dependency 'redlock', '~> 0.2'
20
+ spec.add_development_dependency 'simplecov-rcov', '~> 0.2'
21
+ spec.add_development_dependency 'rspec', '~> 3.6'
22
+ spec.add_development_dependency 'pry-byebug', '~> 3.5'
23
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: synchronised_migration
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre.alpha.1
5
+ platform: ruby
6
+ authors:
7
+ - Alvin Yim
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redlock
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: simplecov-rcov
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry-byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.5'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.5'
69
+ description: Use Redis to record the data migration status
70
+ email: support@travellink.com.au
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - ".gitignore"
76
+ - ".ruby-version"
77
+ - ".travis.yml"
78
+ - CHANGELOG.md
79
+ - Gemfile
80
+ - Gemfile.lock
81
+ - LICENSE
82
+ - README.md
83
+ - lib/synchronised_migration.rb
84
+ - lib/synchronised_migration/main.rb
85
+ - lib/synchronised_migration/railtie.rb
86
+ - lib/synchronised_migration/result.rb
87
+ - lib/synchronised_migration/version.rb
88
+ - lib/tasks/synchronised_migration.rake
89
+ - spec/spec_helper.rb
90
+ - spec/synchronised_migration/main_spec.rb
91
+ - synchronised_migration.gemspec
92
+ homepage: https://github.com/sealink/synchronised-migration-rb
93
+ licenses: []
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">"
107
+ - !ruby/object:Gem::Version
108
+ version: 1.3.1
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.4.5.2
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: For deploying to multiple instances simultaneously
115
+ test_files:
116
+ - spec/spec_helper.rb
117
+ - spec/synchronised_migration/main_spec.rb