themigrator 0.1.0

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: b1f67f74c4b64f07f670d8878a8854e64b6de64e
4
+ data.tar.gz: 45b335ce112247d11d2adda886f635b512cb3f80
5
+ SHA512:
6
+ metadata.gz: a1764195422d095d79055d1e84cd3257672e493fbf1658e8874843ffd4ca7b4f94b0141cc486bbc909a75499bf4e3cc10db62589a715fc27f44c92a6e672ba32
7
+ data.tar.gz: 50a6430052769b67d0e3bb28a400514f427c7ed282c6b7a0ff3cceb541ba6ed659da1a39e33a3c476062e07a685fedb78c3bb5551108869000a82dc11cede8b6
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ test/fixtures/sample_project/logs/
11
+ .byebug_history
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in themigrator.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,29 @@
1
+ Copyright (c) 2016, Wooga GmbH
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+
10
+ 2. Redistributions in binary form must reproduce the above copyright notice,
11
+ this list of conditions and the following disclaimer in the documentation
12
+ and/or other materials provided with the distribution.
13
+
14
+ 3. Neither the name of the copyright holder nor the names of its contributors
15
+ may be used to endorse or promote products derived from this software without
16
+ specific prior written permission.
17
+
18
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
+
29
+
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # Themigrator
2
+
3
+ The migrator is a program that eases and automates data migrations.
4
+
5
+ ## Installation
6
+
7
+
8
+ Install it as a normal gem
9
+
10
+ $ gem install themigrator
11
+
12
+ ## Usage
13
+
14
+
15
+ `themigrator` helps a data migration by automating a pre-defined process.
16
+
17
+ It uses the concepts of _roles_(folders), and _actions_(scripts).
18
+
19
+ Each _role_ can contain any file, but `themigrator` will treat some of them in
20
+ a special way.
21
+
22
+ This is the list of actions and its intended behaviour:
23
+
24
+ * `setup` Provision machines with tooling or opening tunnels.
25
+ * `pre-migrate` Put the site in maintenance mode.
26
+ * `migrate` Copy data and check integrity.
27
+ * `cleanup` In case `migrate` works, it will cleanup, or enable a site.
28
+ * `rollback` In case any previous command fails, `rollback` is called.
29
+
30
+ There are some constrains:
31
+
32
+ * All the _actions_ are called at the same time for all the _roles_.
33
+ * Unless all the _actions_ return a 0, the next _action_ is not executed.
34
+ * In case an _action_ return a non zero code, a SIGINT is sent to the rest of
35
+ the actions still alive.
36
+ * If case an _action_ return a non zero code, the `rollback` script will be
37
+ called only for the roles that run any previous action.
38
+
39
+
40
+ ## Example
41
+
42
+ For example. We have three roles: _mariadb_, _redis_, and _app_.
43
+
44
+ /mariadb/setup # Check maria is still app and running.
45
+ /mariadb/pre-migrate # Disable inserts.
46
+ /mariadb/migrate # Run `ssh oldmaria mysqldump | mysql newmaria`.
47
+ /mariadb/cleanup # Stop mariadb.
48
+ /mariadb/rollback # Enable inserts.
49
+ /redis/migrate # Run `redis-cli oldredis dump | redis-cli newredis bullread`.
50
+ /app/pre-migrate # Set maintenance mode and stop the app servers.
51
+ /app/rollback # Enable the old site
52
+
53
+ Will do the following:
54
+
55
+ 1. Run the `mariadb/setup`. If fails `mariadb/rollback`
56
+ 2. Run the `mariadb/pre-migrate` and `app/pre-migrate`. If fails `mariadb/rollback` and `app/rollback`.
57
+ 3. Run the `mariadb/migrate` and `redis/migrate`. If fails `mariadb/rollback` and `app/rollback`.
58
+ 4. Run the `mariadb/cleanup`. Will output the error.
59
+
60
+
61
+ ## Contributing
62
+
63
+ Bug reports and pull requests are welcome on GitHub at https://github.com/wooga/themigrator.
64
+
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "themigrator"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/themigrator ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "themigrator/cli"
4
+
5
+ Themigrator::CLI.start(ARGV)
6
+
7
+
8
+
9
+
10
+
@@ -0,0 +1,9 @@
1
+ require "themigrator/version"
2
+ require "themigrator/logger"
3
+ require "themigrator/runner"
4
+ require "themigrator/migration"
5
+
6
+ module Themigrator
7
+
8
+
9
+ end
@@ -0,0 +1,21 @@
1
+ require 'thor'
2
+ require 'themigrator/migrator'
3
+
4
+
5
+ module Themigrator
6
+ class CLI < Thor
7
+
8
+ desc "migrate", "Migrate the current project"
9
+ def migrate
10
+ migrator = Themigrator::Migrator.new(Dir.pwd)
11
+ migrator.migrate!
12
+ end
13
+
14
+ desc "roles", "show the list of roles"
15
+ def roles
16
+ migration = Themigrator::Migration.new(Dir.pwd)
17
+ migration.analyze_project!
18
+ puts migration.roles.join("\n")
19
+ end
20
+ end
21
+ end
File without changes
@@ -0,0 +1,21 @@
1
+ require 'fileutils'
2
+
3
+ module Themigrator
4
+ module Logger
5
+ include FileUtils
6
+
7
+
8
+ def log_path(base_dir, run_id, role, action)
9
+ filename = "#{role}-#{action}.log"
10
+ directory = log_dir(base_dir, run_id)
11
+ File.join(directory,filename)
12
+ end
13
+
14
+ def log_dir(base_dir, run_id)
15
+ dir = File.join(base_dir,"logs",run_id)
16
+ mkdir_p(dir, mode: 0700)
17
+ dir
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,47 @@
1
+
2
+
3
+ module Themigrator
4
+
5
+ # Holds the plan of the migration
6
+ class Migration
7
+
8
+ ACTIONS = %w(setup pre-migrate migrate cleanup rollback)
9
+ SCRIPTS = ACTIONS | %w(rollback)
10
+
11
+ attr_reader :roles, :actions
12
+
13
+ def initialize(dir)
14
+ @dir = dir
15
+ @action_and_roles = Hash.new{|hash,key| hash[key] = [] }
16
+ @roles = []
17
+ @actions = []
18
+ end
19
+
20
+ def analyze_project!
21
+ roles = Set.new
22
+ used_actions = [] # Should be in order
23
+
24
+ SCRIPTS.each do |action|
25
+ path_match = File.join(@dir,"*/#{action}")
26
+ Dir[path_match].select { |f|
27
+ File.executable?(f)
28
+ }.each {|f|
29
+ role = File.basename(File.dirname(f))
30
+ roles.add(role)
31
+ used_actions << action
32
+ @action_and_roles[action] << role
33
+
34
+ }
35
+
36
+ end
37
+ @roles = roles.to_a.sort
38
+ @actions = used_actions.uniq
39
+ @actions.delete("rollback")
40
+ end
41
+
42
+ def roles_for_action(action)
43
+ @action_and_roles[action]
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,71 @@
1
+ require 'themigrator/runner'
2
+ require 'themigrator/logger'
3
+ require 'themigrator/migration'
4
+
5
+
6
+ module Themigrator
7
+ class Migrator
8
+ include Runner
9
+ include Logger
10
+
11
+
12
+ def initialize(dir)
13
+ @dir = dir
14
+ @runned_roles = []
15
+ end
16
+
17
+ def migrate!
18
+ @migration = Migration.new(@dir)
19
+ @migration.analyze_project!
20
+
21
+ Migration::ACTIONS.each do |action|
22
+ run_action_and_wait(action)
23
+ end
24
+
25
+ rescue ActionFailedException
26
+ run_rollback_and_wait
27
+ end
28
+
29
+ private
30
+
31
+ def run_rollback_and_wait
32
+ @runned_roles.each do |role|
33
+ run_role_action(role, "rollback")
34
+ end
35
+
36
+ wait_all_processes
37
+ end
38
+
39
+ def run_id
40
+ @run_id ||= Time.now.strftime("%Y-%m-%d-%H:%M:%S")
41
+ end
42
+
43
+ def run_action_and_wait(action)
44
+ run_action(action)
45
+ wait
46
+ end
47
+
48
+ def run_action(action)
49
+ @migration.roles_for_action(action).each do |role|
50
+ @runned_roles << role # To prevent calling rollback on roles that
51
+ # have not being executed
52
+
53
+ start_role_action(role, action)
54
+
55
+ end
56
+ @runned_roles.uniq!
57
+ end
58
+
59
+ def start_role_action(role, action)
60
+ script_path = find_script(role, action)
61
+ log_path = log_path(@dir,run_id,role, action)
62
+
63
+ run(script_path,log_path)
64
+ end
65
+
66
+ def find_script(role, action)
67
+ File.join(@dir, role, action)
68
+ end
69
+ end
70
+
71
+ end
@@ -0,0 +1,53 @@
1
+ require 'themigrator/logger'
2
+
3
+
4
+ module Themigrator
5
+ module Runner
6
+ include Logger
7
+
8
+ class ActionFailedException < Exception; end
9
+
10
+
11
+ class Script
12
+ def initialize(script, log_file)
13
+ @script = script
14
+ @log_file = File.open(log_file, "w", 0600)
15
+ end
16
+
17
+ def close
18
+ @log_file.close
19
+ end
20
+ end
21
+
22
+
23
+ def run(script, log_file)
24
+ wd_dir = File.dirname(script)
25
+ log_fd = File.open(log_file, "w", 0600)
26
+ pid = Process.spawn(script, err: log_fd, out: log_fd, chdir: wd_dir)
27
+ add_process(pid)
28
+ end
29
+
30
+ def wait
31
+ @processes.each do |pid|
32
+ pid, status = Process.wait2(pid)
33
+ return_code = status.exitstatus
34
+ @processes.delete(pid)
35
+
36
+ if return_code != 0
37
+ raise ActionFailedException.new(pid)
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def processes
45
+ @processes || []
46
+ end
47
+ def add_process(pid)
48
+ @processes ||= []
49
+ @processes << pid
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,3 @@
1
+ module Themigrator
2
+ VERSION = "0.1.0"
3
+ end
data/test.sh ADDED
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+
3
+
4
+ set -eu
5
+
6
+
7
+ cat <<-EOF > remote_file
8
+ #!/bin/bash
9
+
10
+ redis-cli redis03 dump everything NOW > my file
11
+ process my file
12
+
13
+
14
+ cat thecooloutput
15
+
16
+ EOF
17
+
18
+ chmod +x remote_file
19
+
20
+ scp remote_file redis:
21
+
22
+ ssh redis remote_file | redis-cli pipe asdf
23
+
24
+ redis-cli PIPE << asdf
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'themigrator/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "themigrator"
8
+ spec.version = Themigrator::VERSION
9
+ spec.authors = ["Guillermo Álvarez"]
10
+ spec.email = ["guillermo@cientifico.net"]
11
+
12
+ spec.summary = %q{tool to ease migration a data migration process.}
13
+ spec.description = %q{The migrator is a program that eases and automates data migrations.}
14
+ spec.homepage = "https://github.com/wooga/themigrator"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.12"
22
+ spec.add_development_dependency "rake", "~> 10.0"
23
+ spec.add_development_dependency "minitest", "~> 5.0"
24
+
25
+ spec.add_dependency "thor", "~> 0.19"
26
+ end
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: themigrator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Guillermo Álvarez
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
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.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: thor
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.19'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.19'
69
+ description: The migrator is a program that eases and automates data migrations.
70
+ email:
71
+ - guillermo@cientifico.net
72
+ executables:
73
+ - themigrator
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".travis.yml"
79
+ - Gemfile
80
+ - LICENSE
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - exe/themigrator
86
+ - lib/themigrator.rb
87
+ - lib/themigrator/cli.rb
88
+ - lib/themigrator/helper.rb
89
+ - lib/themigrator/logger.rb
90
+ - lib/themigrator/migration.rb
91
+ - lib/themigrator/migrator.rb
92
+ - lib/themigrator/runner.rb
93
+ - lib/themigrator/version.rb
94
+ - test.sh
95
+ - themigrator.gemspec
96
+ homepage: https://github.com/wooga/themigrator
97
+ licenses: []
98
+ metadata: {}
99
+ post_install_message:
100
+ rdoc_options: []
101
+ require_paths:
102
+ - lib
103
+ required_ruby_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ requirements: []
114
+ rubyforge_project:
115
+ rubygems_version: 2.5.1
116
+ signing_key:
117
+ specification_version: 4
118
+ summary: tool to ease migration a data migration process.
119
+ test_files: []