synchronised_migration 2.2.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 178cde6a23ffc12e5074df16e0c8b2f5503ce460727607fc3af824c98a92bce1
4
- data.tar.gz: 0e3ebd525d0e55ffeabe3565f811ed3ff57ea272d042a3b72386ae6a627ba03c
3
+ metadata.gz: fdaaef7abd9ca7478b25d2e8ddc330db3aa699eb5a9027d3aac05b9626fc0a1b
4
+ data.tar.gz: ec0d069a1bedf336381d3241b9f869b2c41b8e25f4e6138abda5dc0b34bbdddd
5
5
  SHA512:
6
- metadata.gz: bc7a499baac225c7f3e11467f84c44eae41a2af3ef6c016b3a8b674fdd177c53930efb86ec5951da2ce5b220fb697ff87faa62ba557a3de14767293a10a71371
7
- data.tar.gz: 6d07aad5e40d68ac72c240f39875e2686416c1d2c38e1fb7555fe18626a47dbdc510116d51e6c19776ed5c22a3ef4e3bcc77b20b5c3672ed94098768dffdd35c
6
+ metadata.gz: 66a3e6dca6186f13049eae942c13cca91afa9652b6c3dfc69e3e693e5dd8433023f57f98fa3f94502cfb04edafeb323df2f90230b7b8e38f9cd48490b324744a
7
+ data.tar.gz: ae260978d7cca055fe2b361514482175c921361a4a00b944ba0bf3fecce24885f104b6bd4d3f19e205814481e7621f502f8398ad3bd85a55931b648288c87247
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Synchronised Migration
2
2
 
3
+ ## 3.0.0
4
+
5
+ - [OPS-201] Migrate library to CLI tool
6
+
3
7
  ## 2.2.0
4
8
 
5
9
  - [TT-8617] Update to build with github actions / ruby 3.0
data/README.md CHANGED
@@ -4,56 +4,67 @@
4
4
  [![Build Status](https://github.com/sealink/synchronised-migration-rb/workflows/Build%20and%20Test/badge.svg?branch=master)](https://github.com/sealink/synchronised-migration-rb/actions)
5
5
 
6
6
  This gem makes it possible to deploy multiple instances with data migration
7
- simultaneously. It uses Redis to ensure that there will be only one migration
7
+ simultaneously. It uses Redis to ensure that there will be only one migration
8
8
  running.
9
9
 
10
- This gem works out of the box with a Rails project. It should work with other
11
- Ruby projects so long as you load the rake task in Rakefile instead of relying
12
- on Railtie.
10
+ ## Usage
13
11
 
14
- This is a Ruby port of the same logic written in PHP in our [Craft
15
- Docker](https://github.com/sealink/craft-docker) project.
12
+ ### Migrating an application
16
13
 
17
- ## Usage
14
+ ```
15
+ bundle exec synchronised-migration migrate --config=migration.yml --version=1.2.3
16
+ ```
18
17
 
19
- Module `SynchronisedMigration` needs to be configured as below.
18
+ ### Checking lock stats
20
19
 
21
- ```ruby
22
- SynchronisedMigration.configure do |config|
23
- config.host = 'example.com'
24
- config.port = 6379
25
- config.db = 0
26
- end
20
+ ```
21
+ bundle exec synchronised-migration status --config=migration.yml --version=1.2.3
27
22
  ```
28
23
 
29
- Configuration can be called by using
30
- ```SynchronisedMigration.redis_config.host``` or similar.
24
+ ### Clearing locks
31
25
 
32
- You may override these settings through environment variables.
26
+ If a previous migration has failed a new one will not be allowed to start
27
+ unless it is first cleared.
33
28
 
34
29
  ```
35
- SYNCHRONISED_COMMAND=bin/launch/migrate
36
- WITH_CLEAN_BUNDLER_ENV=1 # Non-empty for true
37
- REDLOCK_TIMEOUT_MS=3600000
38
- REDLOCK_RETRY_DELAY_MS=200
39
- REDLOCK_LOCK_KEY=migration-in-progress
40
- REDLOCK_FAIL_KEY=migration-failed
30
+ bundle exec synchronised-migration clear --config=migration.yml
41
31
  ```
42
32
 
43
- Run this before you launch the application during deployment.
33
+ If you wish to clear all locks, \*including the running lock then you must also provide version
44
34
 
45
35
  ```
46
- $ rake synchronised_migrate:execute
36
+ bundle exec synchronised-migration clear --config=migration.yml --version=1.2.3 --all
37
+ ```
38
+
39
+ ### Configuration
40
+
41
+ | Key | Description |
42
+ | ------------------------ | --------------------------------------- |
43
+ | redis_uri | Redis server to use for locking |
44
+ | application | Application being migrated |
45
+ | version | Version being migrated too |
46
+ | debug | Enable additional debug information |
47
+ | command | Command to execute for the migration |
48
+ | timeout_milliseconds | Timeout to wait for the lock |
49
+ | retry_delay_milliseconds | Retry for the lock every x milliseconds |
50
+
51
+ ### Overriding config file options
52
+
53
+ You may override certain config options such as the `redis-uri` and the ```debug``` flag
54
+ by providing them via cli options.
55
+
56
+ ```
57
+ bundle exec synchronised-migration migrate --config=migration.yml --version=1.2.3 --redis_uri=redis://127.0.0.1:6379/0
47
58
  ```
48
59
 
49
60
  ## Release
50
61
 
51
62
  To publish a new version of this gem the following steps must be taken.
52
63
 
53
- * Update the version in the following files
64
+ - Update the version in the following files
54
65
  ```
55
66
  CHANGELOG.md
56
67
  lib/synchronised_migration/version.rb
57
- ````
58
- * Create a tag using the format v0.1.0
59
- * Follow build progress in GitHub actions
68
+ ```
69
+ - Create a tag using the format v0.1.0
70
+ - Follow build progress in GitHub actions
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "synchronised_migration"
6
+
7
+ Dry::CLI.new(SynchronisedMigration::Commands).call
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SynchronisedMigration
4
+ module Commands
5
+ extend Dry::CLI::Registry
6
+
7
+ class Migrate < Dry::CLI::Command
8
+ desc "Run the provided command in a migration lock"
9
+
10
+ option :config, type: :string, required: true, desc: "Configuration file location"
11
+ option :version, type: :string, required: true, desc: "Version we are migrating to"
12
+ option :application, type: :string, desc: "Application we are migrating"
13
+ option :redis_uri, type: :string, desc: "Redis Server URI"
14
+ option :debug, type: :boolean, desc: "Enable additional debugging output"
15
+
16
+ def call(**options)
17
+ abort "Config location must be provided" if options[:config].nil?
18
+ abort "Version must be provided" if options[:version].nil?
19
+
20
+ config = SynchronisedMigration::Configuration.from_cli(options)
21
+ result = SynchronisedMigration::Main.new(config).call
22
+
23
+ abort(result.error_msg) if result.failure?
24
+ $stdout.puts "Complete!"
25
+ end
26
+ end
27
+
28
+ class Clear < Dry::CLI::Command
29
+ desc "Clear the migration keys"
30
+
31
+ option :config, type: :string, required: true, desc: "Configuration file"
32
+ option :version, type: :string, required: true, desc: "Version we are migrating to"
33
+ option :application, type: :string, desc: "Application we are migrating"
34
+ option :redis_uri, type: :string, desc: "Redis Server URI"
35
+ option :debug, type: :boolean, desc: "Enable additional debugging output"
36
+ option :all, type: :boolean, default: false, desc: "Clear all keys including success and lock"
37
+
38
+ def call(**options)
39
+ abort "Config location must be provided" if options[:config].nil?
40
+
41
+ config = SynchronisedMigration::Configuration.from_cli(options)
42
+
43
+ redis_opts = {url: config.redis_uri}
44
+ redis_opts[:logger] = Logger.new($stdout) if config.debug?
45
+
46
+ redis = Redis.new(redis_opts)
47
+ redis.del(config.fail_key)
48
+
49
+ if options[:all]
50
+ abort "Version must be provided" if options[:version].nil?
51
+ redis.del(config.success_key)
52
+ redis.del(config.lock_key)
53
+ end
54
+
55
+ $stdout.puts "Complete!"
56
+ end
57
+ end
58
+
59
+ class Status < Dry::CLI::Command
60
+ desc "Shows the current status the redis failure if any"
61
+
62
+ option :config, type: :string, required: true, desc: "Configuration file"
63
+ option :version, type: :string, required: true, desc: "Version we are migrating to"
64
+ option :application, type: :string, desc: "Application we are migrating"
65
+ option :redis_uri, type: :string, desc: "Redis Server URI"
66
+ option :debug, type: :boolean, desc: "Enable additional debugging output"
67
+
68
+ def call(**options)
69
+ abort "Config location must be provided" if options[:config].nil?
70
+ abort "Version must be provided" if options[:version].nil?
71
+
72
+ config = SynchronisedMigration::Configuration.from_cli(options)
73
+
74
+ redis_opts = {url: config.redis_uri}
75
+ redis_opts[:logger] = Logger.new($stdout) if config.debug?
76
+ redis = Redis.new(redis_opts)
77
+
78
+ rows = []
79
+ fail_key = config.fail_key
80
+ rows << if redis.exists?(fail_key)
81
+ [fail_key, "true", "Failed Migration"]
82
+ else
83
+ [fail_key, "false", nil]
84
+ end
85
+
86
+ success_key = config.success_key
87
+ rows << if redis.exists?(success_key)
88
+ [success_key, "true", "successfully Migrated"]
89
+ else
90
+ [success_key, "false", nil]
91
+ end
92
+
93
+ lock_key = config.lock_key
94
+ rows << if redis.exists?(lock_key)
95
+ [lock_key, "true", "Running Migration"]
96
+ else
97
+ [lock_key, "false", nil]
98
+ end
99
+
100
+ table = TTY::Table.new(["key", "status", "message"], rows)
101
+ $stdout.puts table.render(:ascii)
102
+ end
103
+ end
104
+
105
+ register "migrate", Migrate
106
+ register "clear", Clear
107
+ register "status", Status
108
+ end
109
+ end
@@ -0,0 +1,50 @@
1
+ module SynchronisedMigration
2
+ class Configuration
3
+ attr_accessor :redis_uri
4
+ attr_accessor :command
5
+ attr_accessor :application
6
+ attr_accessor :debug
7
+ attr_accessor :version
8
+
9
+ def self.from_cli(options)
10
+ config_yml = YAML.load_file(options[:config]).transform_keys(&:to_sym).to_h
11
+ new(config_yml.merge(options.compact))
12
+ end
13
+
14
+ def initialize(options = {})
15
+ @application = options.fetch(:application, nil)
16
+ @version = options.fetch(:version, nil)
17
+ @redis_uri = options.fetch(:redis_uri, "redis://127.0.0.1:6379/0")
18
+ @debug = options.fetch(:debug, false)
19
+ @command = options.fetch(:command, "bin/launch/migrate")
20
+ @timeout_milliseconds = options.fetch(:timeout_milliseconds, 3_600_000).to_i
21
+ @retry_delay_milliseconds = options.fetch(:retry_delay_milliseconds, 3000).to_i
22
+
23
+ raise OptionParser::InvalidArgument, "Application must be configured" if @application.nil?
24
+ end
25
+
26
+ def timeout_milliseconds
27
+ @timeout_milliseconds.to_i
28
+ end
29
+
30
+ def retry_delay_milliseconds
31
+ @retry_delay_milliseconds.to_i
32
+ end
33
+
34
+ def debug?
35
+ debug
36
+ end
37
+
38
+ def success_key
39
+ "migration-success-#{application}-#{version}"
40
+ end
41
+
42
+ def fail_key
43
+ "migration-failed-#{application}"
44
+ end
45
+
46
+ def lock_key
47
+ "migration-lock-#{application}"
48
+ end
49
+ end
50
+ end
@@ -1,144 +1,84 @@
1
- require 'synchronised_migration'
2
- require 'synchronised_migration/result'
3
- require 'redis'
4
- require 'redlock'
5
- require 'singleton'
1
+ module SynchronisedMigration
2
+ class Main
3
+ FAIL_TIMEOUT = 3600 # 1 Hour
4
+ SUCCESS_TIMEOUT = 3600 * 24 * 30 # 30 dyas
6
5
 
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
- done_or_execute
19
- end
20
-
21
- private
22
-
23
- def done_or_execute
24
- return Result.ok if migration_already_completed?
25
- lock_and_execute
26
- end
27
-
28
- def lock_and_execute
29
- redlock.lock! lock_key, timeout do
30
- execute
6
+ def initialize(config)
7
+ @config = config
31
8
  end
32
- end
33
9
 
34
- def execute
35
- return Result.ok if migration_already_completed?
36
- return Result.fail 'Halting the script because the previous migration failed.' if previous_failed?
37
- mark_failed
38
- migration_success = migrate
39
- return Result.fail 'Migration failed.' unless migration_success
40
- mark_successful
41
- remove_fail_marker
42
- return Result.ok
43
- end
10
+ def call
11
+ return Result.migration_already_completed if migration_already_completed?
44
12
 
45
- def migration_already_completed?
46
- return false if !success_key
47
- value = redis.get(success_key)
48
- not value.nil? and not value.empty?
49
- end
50
-
51
- def mark_successful
52
- if success_key
53
- redis.set success_key, timestamp, ex: 3600*24*30
13
+ lock_and_execute
54
14
  end
55
- end
56
-
57
- def previous_failed?
58
- value = redis.get(fail_key)
59
- not value.nil? and not value.empty?
60
- end
61
-
62
- def mark_failed
63
- redis.set fail_key, timestamp, ex: 3600
64
- end
65
15
 
66
- def remove_fail_marker
67
- redis.del fail_key
68
- end
69
-
70
- def migrate
71
- if with_clean_env?
72
- Bundler.with_original_env do
73
- Kernel.system target_command
16
+ private
17
+
18
+ def lock_and_execute
19
+ client = Redlock::Client.new(
20
+ [@config.redis_uri], {
21
+ retry_count: retry_count,
22
+ retry_delay: @config.retry_delay_milliseconds
23
+ }
24
+ )
25
+ client.lock!(@config.lock_key, @config.timeout_milliseconds) do
26
+ execute_within_lock
74
27
  end
75
- else
76
- Kernel.system target_command
77
28
  end
78
- $?.success?
79
- end
80
-
81
- def with_clean_env?
82
- not ENV.fetch('WITH_CLEAN_BUNDLER_ENV', '').empty?
83
- end
84
29
 
85
- def target_command
86
- ENV.fetch 'SYNCHRONISED_COMMAND', 'bin/launch/migrate'
87
- end
88
-
89
- def redis
90
- @redis ||= Redis.new(url: redis_url)
91
- end
30
+ def execute_within_lock
31
+ return Result.ok if migration_already_completed?
32
+ return Result.previous_migration_failed if previous_failed?
33
+ mark_failed
34
+ migration_success = migrate
35
+ return Result.migration_failed unless migration_success
36
+ mark_successful
37
+ remove_fail_marker
38
+ Result.ok
39
+ end
92
40
 
93
- def redlock
94
- @redlock ||= Redlock::Client.new(
95
- [ redis_url ], {
96
- retry_count: retry_count,
97
- retry_delay: retry_delay
98
- }
99
- )
100
- end
41
+ def migration_already_completed?
42
+ redis.exists?(@config.success_key)
43
+ end
101
44
 
102
- def redis_url
103
- sprintf(
104
- 'redis://%s:%s/%s',
105
- SynchronisedMigration.redis_config.host,
106
- SynchronisedMigration.redis_config.port,
107
- SynchronisedMigration.redis_config.db
108
- )
109
- end
45
+ def mark_successful
46
+ success_obj = {application: @config.application, version: @config.version, command: @config.command, timestamp: timestamp}.to_json
47
+ redis.set(@config.success_key, success_obj, ex: SUCCESS_TIMEOUT)
48
+ end
110
49
 
111
- def timestamp
112
- Time.now.to_i
113
- end
50
+ def previous_failed?
51
+ redis.exists?(@config.fail_key)
52
+ end
114
53
 
115
- def timeout
116
- ENV.fetch('REDLOCK_TIMEOUT_MS', 3_600_000).to_i
117
- end
54
+ def mark_failed
55
+ redis.set(@config.fail_key, timestamp, ex: FAIL_TIMEOUT)
56
+ end
118
57
 
119
- def retry_delay
120
- ENV.fetch('REDLOCK_RETRY_DELAY_MS', 3000).to_i
121
- end
58
+ def remove_fail_marker
59
+ redis.del(@config.fail_key)
60
+ end
122
61
 
123
- def retry_count
124
- timeout / retry_delay
125
- end
62
+ def migrate
63
+ Kernel.system @config.command
64
+ $?.success?
65
+ end
126
66
 
127
- def lock_key
128
- ENV.fetch 'REDLOCK_LOCK_KEY', 'migration-in-progress'
129
- end
67
+ def redis
68
+ @redis ||= begin
69
+ redis_opts = {url: @config.redis_uri}
70
+ redis_opts[:logger] = Logger.new($stdout) if @config.debug?
130
71
 
131
- def fail_key
132
- ENV.fetch 'REDLOCK_FAIL_KEY', 'migration-failed' + version_suffix
133
- end
72
+ Redis.new(redis_opts)
73
+ end
74
+ end
134
75
 
135
- def success_key
136
- return false if version_suffix.empty?
137
- 'migration-success' + version_suffix
138
- end
76
+ def timestamp
77
+ Time.now.to_i
78
+ end
139
79
 
140
- def version_suffix
141
- suffix = ENV.fetch 'REDLOCK_VERSION_SUFFIX', false
142
- suffix ? '-' + suffix : ''
80
+ def retry_count
81
+ @config.timeout_milliseconds / @config.retry_delay_milliseconds
82
+ end
143
83
  end
144
84
  end
@@ -1,21 +1,52 @@
1
- require 'synchronised_migration'
1
+ require "synchronised_migration"
2
2
 
3
- class SynchronisedMigration::Result
4
- attr_accessor :error
3
+ module SynchronisedMigration
4
+ MIGRATION_SUCCESS = 0
5
+ PREVIOUS_SUCCESS = 1
6
+ PREVIOUS_FAILED = 2
7
+ MIGRATION_FAILED = 3
5
8
 
6
- def initialize(error = nil)
7
- @error = error
8
- end
9
+ PREVIOUS_FAILED_MSG = "Halting the script because the previous migration failed."
10
+ MIGRATION_FAILED_MSG = "Migration command failed."
9
11
 
10
- def success?
11
- error.nil?
12
- end
12
+ class Result
13
+ attr_reader :code
13
14
 
14
- def self.ok
15
- self.new
16
- end
15
+ def initialize(code)
16
+ @code = code
17
+ end
18
+
19
+ def successful?
20
+ [MIGRATION_SUCCESS, PREVIOUS_SUCCESS].include?(code)
21
+ end
22
+
23
+ def failure?
24
+ !successful?
25
+ end
26
+
27
+ def error_msg
28
+ case code
29
+ when MIGRATION_FAILED
30
+ MIGRATION_FAILED_MSG
31
+ when PREVIOUS_FAILED
32
+ PREVIOUS_FAILED_MSG
33
+ end
34
+ end
35
+
36
+ def self.ok
37
+ new(MIGRATION_SUCCESS)
38
+ end
39
+
40
+ def self.migration_already_completed
41
+ new(PREVIOUS_SUCCESS)
42
+ end
43
+
44
+ def self.previous_migration_failed
45
+ new(PREVIOUS_FAILED)
46
+ end
17
47
 
18
- def self.fail(error)
19
- self.new error
48
+ def self.migration_failed
49
+ new(MIGRATION_FAILED)
50
+ end
20
51
  end
21
52
  end
@@ -1,3 +1,3 @@
1
1
  module SynchronisedMigration
2
- VERSION = '2.2.0'
2
+ VERSION = "3.0.0"
3
3
  end
@@ -1,22 +1,14 @@
1
1
  module SynchronisedMigration
2
- class << self
3
- attr_accessor :redis_config
4
- end
5
-
6
- def self.configure
7
- self.redis_config ||= Configuration.new
8
- yield(redis_config)
9
- end
10
-
11
- class Configuration
12
- attr_accessor :host, :port, :db
13
-
14
- def initialize
15
- @host = ''
16
- @port = 0
17
- @db = 0
18
- end
19
- end
20
2
  end
21
3
 
22
- require 'synchronised_migration/railtie' if defined?(Rails)
4
+ require "redis"
5
+ require "logger"
6
+ require "json"
7
+ require "redlock"
8
+ require "yaml"
9
+ require "dry/cli"
10
+ require "tty/table"
11
+ require "synchronised_migration/commands"
12
+ require "synchronised_migration/configuration"
13
+ require "synchronised_migration/main"
14
+ require "synchronised_migration/result"
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: synchronised_migration
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alvin Yim
8
8
  - Stefan Cooper
9
9
  autorequire:
10
- bindir: bin
10
+ bindir: exe
11
11
  cert_chain: []
12
- date: 2021-01-06 00:00:00.000000000 Z
12
+ date: 2021-09-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redlock
@@ -27,6 +27,34 @@ dependencies:
27
27
  version: '0.2'
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: redis
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 4.2.1
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 4.2.1
42
+ - !ruby/object:Gem::Dependency
43
+ name: dry-cli
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: tty-table
30
58
  requirement: !ruby/object:Gem::Requirement
31
59
  requirements:
32
60
  - - ">="
@@ -95,31 +123,50 @@ dependencies:
95
123
  - - ">="
96
124
  - !ruby/object:Gem::Version
97
125
  version: '3.5'
126
+ - !ruby/object:Gem::Dependency
127
+ name: standardrb
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: timecop
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
98
154
  description: Use Redis to record the data migration status
99
155
  email: support@travellink.com.au
100
- executables: []
156
+ executables:
157
+ - synchronised-migration
101
158
  extensions: []
102
159
  extra_rdoc_files: []
103
160
  files:
104
- - ".github/dependabot.yml"
105
- - ".github/workflows/release.yml"
106
- - ".github/workflows/ruby.yml"
107
- - ".gitignore"
108
- - ".ruby-version"
109
161
  - CHANGELOG.md
110
- - Gemfile
111
- - LICENSE
112
162
  - README.md
113
- - Rakefile
163
+ - exe/synchronised-migration
114
164
  - lib/synchronised_migration.rb
165
+ - lib/synchronised_migration/commands.rb
166
+ - lib/synchronised_migration/configuration.rb
115
167
  - lib/synchronised_migration/main.rb
116
- - lib/synchronised_migration/railtie.rb
117
168
  - lib/synchronised_migration/result.rb
118
169
  - lib/synchronised_migration/version.rb
119
- - lib/tasks/synchronised_migration.rake
120
- - spec/spec_helper.rb
121
- - spec/synchronised_migration/main_spec.rb
122
- - synchronised_migration.gemspec
123
170
  homepage: https://github.com/sealink/synchronised-migration-rb
124
171
  licenses: []
125
172
  metadata: {}
@@ -138,10 +185,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
185
  - !ruby/object:Gem::Version
139
186
  version: '0'
140
187
  requirements: []
141
- rubygems_version: 3.2.3
188
+ rubygems_version: 3.2.22
142
189
  signing_key:
143
190
  specification_version: 4
144
191
  summary: For deploying to multiple instances simultaneously
145
- test_files:
146
- - spec/spec_helper.rb
147
- - spec/synchronised_migration/main_spec.rb
192
+ test_files: []
@@ -1,6 +0,0 @@
1
- version: 2
2
- updates:
3
- - package-ecosystem: "bundler"
4
- directory: "/"
5
- schedule:
6
- interval: "daily"
@@ -1,59 +0,0 @@
1
- name: Release
2
-
3
- on:
4
- push:
5
- tags:
6
- - "v*"
7
-
8
- jobs:
9
- build:
10
- name: Build
11
- runs-on: ubuntu-latest
12
- steps:
13
- - name: Checkout
14
- uses: actions/checkout@v2
15
- - uses: ruby/setup-ruby@v1
16
- with:
17
- bundler-cache: true
18
- - run: bundle exec rake
19
-
20
- release:
21
- needs: build
22
- name: Release
23
- runs-on: ubuntu-latest
24
- steps:
25
- - name: Checkout
26
- uses: actions/checkout@v2
27
-
28
- - name: Generate Changelog
29
- run: |
30
- # Get version from github ref (remove 'refs/tags/' and prefix 'v')
31
- version="${GITHUB_REF#refs/tags/v}"
32
- npx changelog-parser CHANGELOG.md | jq -cr ".versions | .[] | select(.version == \"$version\") | .body" > ${{ github.workflow }}-CHANGELOG.txt
33
-
34
- - name: Release
35
- uses: softprops/action-gh-release@v1
36
- with:
37
- body_path: ${{ github.workflow }}-CHANGELOG.txt
38
- env:
39
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40
-
41
- publish:
42
- needs: [build, release]
43
- name: Publish
44
- runs-on: ubuntu-latest
45
-
46
- steps:
47
- - uses: actions/checkout@v2
48
- - uses: ruby/setup-ruby@v1
49
-
50
- - name: Publish to RubyGems
51
- run: |
52
- mkdir -p $HOME/.gem
53
- touch $HOME/.gem/credentials
54
- chmod 0600 $HOME/.gem/credentials
55
- printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
56
- gem build *.gemspec
57
- gem push *.gem
58
- env:
59
- GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
@@ -1,16 +0,0 @@
1
- name: Build and Test
2
- on: [push, pull_request]
3
- jobs:
4
- test:
5
- strategy:
6
- fail-fast: false
7
- matrix:
8
- ruby: ["2.6", "2.7", "3.0"]
9
- runs-on: ubuntu-latest
10
- steps:
11
- - uses: actions/checkout@v2
12
- - uses: ruby/setup-ruby@v1
13
- with:
14
- ruby-version: ${{ matrix.ruby }}
15
- bundler-cache: true
16
- - run: bundle exec rake
data/.gitignore DELETED
@@ -1,5 +0,0 @@
1
- /.bundle
2
- /vendor
3
- /coverage
4
- /*.gem
5
- Gemfile.lock
data/.ruby-version DELETED
@@ -1 +0,0 @@
1
- 3.0.0
data/Gemfile DELETED
@@ -1,2 +0,0 @@
1
- source 'https://rubygems.org'
2
- gemspec
data/LICENSE DELETED
@@ -1,21 +0,0 @@
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/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- require "bundler/gem_tasks"
2
-
3
- desc 'Default: run specs.'
4
- task :default => :spec
5
-
6
- require 'rspec/core/rake_task'
7
-
8
- desc "Run specs"
9
- RSpec::Core::RakeTask.new do |t|
10
- t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
11
- # Put spec opts in a file named .rspec in root
12
- end
@@ -1,5 +0,0 @@
1
- class SynchronisedMigration::Railtie < Rails::Railtie
2
- rake_tasks do
3
- load 'tasks/synchronised_migration.rake'
4
- end
5
- end
@@ -1,9 +0,0 @@
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
data/spec/spec_helper.rb DELETED
@@ -1,9 +0,0 @@
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
@@ -1,165 +0,0 @@
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(:success_marker_value) { nil }
13
- let(:version_suffix) { 'bork' }
14
- let(:set_version_suffix) { ENV['REDLOCK_VERSION_SUFFIX'] = version_suffix }
15
- let(:time_value) { double(to_i: 123456789) }
16
-
17
- before do
18
- set_version_suffix
19
-
20
- subject.instance.instance_variable_set :@redis, nil
21
- subject.instance.instance_variable_set :@redlock, nil
22
-
23
- allow(subject.instance).to receive(:execute).and_call_original
24
- allow(subject.instance).to receive(:migrate).and_call_original
25
-
26
- allow(Redis).to receive(:new).and_return(redis)
27
- allow(redis).to receive(:get) { |key|
28
- case key
29
- when "migration-failed-#{version_suffix}"
30
- fail_marker_value
31
- when "migration-failed"
32
- fail_marker_value
33
- when "migration-success-#{version_suffix}"
34
- success_marker_value
35
- when "migration-success"
36
- success_marker_value
37
- else
38
- raise "invalid key for redis get: #{key}"
39
- end
40
- }
41
- allow(redis).to receive(:set)
42
- allow(redis).to receive(:del)
43
-
44
- allow(Redlock::Client).to receive(:new).and_return(redlock)
45
- allow(redlock).to receive(:lock!) { |lock_key, timeout, &block| block.call }
46
-
47
- allow(Time).to receive(:now).and_return(time_value)
48
-
49
- allow(Kernel).to receive(:system).and_wrap_original { |method, *args|
50
- next if args == [ 'bin/launch/migrate' ]
51
- method.call *args
52
- }
53
-
54
- allow(Bundler).to receive(:with_original_env).and_call_original
55
-
56
- SynchronisedMigration.configure do |config|
57
- config.host = 'example.com'
58
- config.port = 6379
59
- config.db = 0
60
- end
61
- end
62
-
63
- context 'in the happy path' do
64
- it 'executes the migration successfully' do
65
- expect(result).to be_success
66
- expect(redlock).to have_received(:lock!)
67
- expect(redis).to have_received(:get).with('migration-failed-bork')
68
- expect(redis).to have_received(:set).with('migration-failed-bork', 123456789, ex: 3600)
69
- expect(redis).to have_received(:set).with('migration-success-bork', 123456789, ex: 3600*24*30)
70
- expect(Kernel).to have_received(:system)
71
- expect(Bundler).not_to have_received(:with_original_env)
72
- expect(redis).to have_received(:del).with('migration-failed-bork')
73
- end
74
-
75
- context 'and migration completed previously' do
76
- let(:success_marker_value) { '1' }
77
- it 'contines without executing' do
78
- expect(result).to be_success
79
- expect(redlock).not_to have_received(:lock!)
80
- end
81
- end
82
-
83
- context 'executing in lock waiter' do
84
- let(:result2) { subject.call }
85
-
86
- before do
87
- # Note: bypasses the first success flag check so it can enter lock.
88
- expect(result).to be_success
89
- allow(redis).to receive(:get).and_return(nil, '1')
90
- end
91
-
92
- it 'early exits and does not execute again', :aggregate_failures do
93
- expect(result2).to be_success
94
- expect(redlock).to have_received(:lock!).exactly(2).times
95
- expect(Kernel).to have_received(:system).exactly(1).times
96
- expect(subject.instance).to have_received(:execute).exactly(2).times
97
- expect(subject.instance).to have_received(:migrate).exactly(1).times
98
- end
99
- end
100
- end
101
-
102
- context 'when require a clean Bundler environment' do
103
- before do
104
- allow(ENV).to receive(:fetch).and_call_original
105
- allow(ENV).to receive(:fetch).with('WITH_CLEAN_BUNDLER_ENV', '').and_return('1')
106
- end
107
-
108
- it 'executes it with a clean Bundler environment' do
109
- expect(result).to be_success
110
- expect(Kernel).to have_received(:system)
111
- expect(Bundler).to have_received(:with_original_env)
112
- end
113
- end
114
-
115
- context 'after a deployment failed previously' do
116
- let(:fail_marker_value) { '1' }
117
-
118
- it "doesn't execute the migration" do
119
- expect(result).not_to be_success
120
- expect(Kernel).not_to have_received(:system)
121
- end
122
- end
123
-
124
- context 'when the task crashed' do
125
- it 'marks the failure in Redis' do
126
- fork { exit 1 }
127
- Process.wait
128
-
129
- expect(result).not_to be_success
130
- expect(redis).to have_received(:set).with('migration-failed-bork', 123456789, ex: 3600)
131
- expect(redis).not_to have_received(:del)
132
- end
133
- end
134
-
135
- context 'without version suffix' do
136
- let(:set_version_suffix) { ENV.delete 'REDLOCK_VERSION_SUFFIX' }
137
-
138
- context 'in the happy path' do
139
- it 'executes the migration successfully' do
140
- fork { exit 0 }
141
- Process.wait
142
-
143
- expect(result).to be_success
144
- expect(redlock).to have_received(:lock!)
145
- expect(redis).to have_received(:get).with('migration-failed')
146
- expect(redis).to have_received(:set).with('migration-failed', 123456789, ex: 3600)
147
- expect(Kernel).to have_received(:system)
148
- expect(Bundler).not_to have_received(:with_original_env)
149
- expect(redis).to have_received(:del).with('migration-failed')
150
- end
151
- end
152
-
153
- context 'when the task crashed' do
154
- it 'marks the failure in Redis' do
155
- fork { exit 1 }
156
- Process.wait
157
-
158
- expect(result).not_to be_success
159
- expect(redis).to have_received(:set).with('migration-failed', 123456789, ex: 3600)
160
- expect(redis).not_to have_received(:del)
161
- end
162
- end
163
- end
164
- end
165
- end
@@ -1,26 +0,0 @@
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', 'Stefan Cooper']
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
- spec.required_ruby_version = '>= 2.6.0'
19
-
20
- spec.add_dependency 'redlock', '>= 0.2'
21
- spec.add_dependency 'redis'
22
- spec.add_development_dependency 'rake'
23
- spec.add_development_dependency 'simplecov-rcov', '>= 0.2'
24
- spec.add_development_dependency 'rspec', '>= 3.6'
25
- spec.add_development_dependency 'pry-byebug', '>= 3.5'
26
- end