percona_migrator 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/CHANGELOG.md +16 -0
- data/README.md +70 -5
- data/bin/rspec +17 -0
- data/lib/active_record/connection_adapters/percona_adapter.rb +6 -2
- data/lib/percona_migrator/cli_generator.rb +34 -88
- data/lib/percona_migrator/command.rb +102 -0
- data/lib/percona_migrator/configuration.rb +2 -1
- data/lib/percona_migrator/connection_details.rb +70 -0
- data/lib/percona_migrator/dsn.rb +26 -0
- data/lib/percona_migrator/errors.rb +4 -0
- data/lib/percona_migrator/log_sanitizers/password_sanitizer.rb +21 -0
- data/lib/percona_migrator/logger.rb +14 -2
- data/lib/percona_migrator/logger_factory.rb +2 -2
- data/lib/percona_migrator/option.rb +63 -0
- data/lib/percona_migrator/runner.rb +7 -73
- data/lib/percona_migrator/user_options.rb +44 -0
- data/lib/percona_migrator/version.rb +1 -1
- data/lib/percona_migrator.rb +20 -4
- data/percona_migrator.gemspec +9 -2
- metadata +30 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df19cbdeb9b3e36074ff5e95ec745afafb5db5f6
|
4
|
+
data.tar.gz: df627f8393513e5699c63d37c84e46237596aea4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6a37c51ca128523a8ab16f9271a76e0612355ccba8e9406557634ccaf77d4e62db25fa29ccc9edf0fc5e7ebf952a6bb7074e870f7fa545c5714b5235212e6d51
|
7
|
+
data.tar.gz: 80a4e55d6136515ee6f742b9053a7dca9d1e11570bdc29d1f1b662e135f210bb88e54d6a09dceb9b3b88d4fee4056464e527dce22dadf6fd10d665b6d698586c
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,22 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
5
5
|
Please follow the format in [Keep a Changelog](http://keepachangelog.com/)
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
|
+
## [1.1.0] - 2017-04-07
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- Allow passing any `pt-online-schema-change`'s arguments through the
|
13
|
+
`PERCONA_ARGS` env var when executing a migration with `rake db:migrate:up`
|
14
|
+
or `db:migrate:down`.
|
15
|
+
- Allow setting global percona arguments via gem configuration
|
16
|
+
- Filter MySQL's password from logs
|
17
|
+
|
18
|
+
### Changed
|
19
|
+
|
20
|
+
- Enable default pt-online-schema-change replicas discovering mechanism.
|
21
|
+
So far, this was purposely set to `none`. To keep this same behaviour
|
22
|
+
provide the `PERCONA_ARGS=--recursion-method=none` env var when running the
|
23
|
+
migration.
|
8
24
|
|
9
25
|
## [1.0.0] - 2016-11-30
|
10
26
|
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# Departure [![Build Status](https://travis-ci.org/redbooth/departure.svg?branch=master)](https://travis-ci.org/redbooth/percona_migrator) [![Code Climate](https://codeclimate.com/github/redbooth/percona_migrator/badges/gpa.svg)](https://codeclimate.com/github/redbooth/percona_migrator)
|
2
2
|
|
3
|
-
|
3
|
+
Departure is an **ActiveRecord connection adapter** that allows running
|
4
4
|
**MySQL online and non-blocking DDL** through `ActiveRecord::Migration` without needing
|
5
5
|
to use a different DSL other than Rails' migrations DSL.
|
6
6
|
|
@@ -9,15 +9,38 @@ It uses `pt-online-schema-change` command-line tool of
|
|
9
9
|
Toolkit](https://www.percona.com/doc/percona-toolkit/2.0/pt-online-schema-change.html)
|
10
10
|
which runs MySQL alter table statements without downtime.
|
11
11
|
|
12
|
+
## Rename from "Percona Migrator"
|
13
|
+
|
14
|
+
This project was formerly known as "Percona Migrator", but this incurs in an
|
15
|
+
infringement of Percona's trade mark policy and thus has to be renamed. Said
|
16
|
+
name is likely to cause confusion as to the source of the wrapper.
|
17
|
+
|
18
|
+
The next major versions will use "Departure" as gem name.
|
19
|
+
|
12
20
|
## Installation
|
13
21
|
|
14
|
-
|
22
|
+
Departure relies on `pt-online-schema-change` from [Percona
|
15
23
|
Toolkit](https://www.percona.com/doc/percona-toolkit/2.0/pt-online-schema-change.html)
|
16
24
|
|
17
25
|
### Mac
|
18
26
|
|
19
27
|
`brew install percona-toolkit`
|
20
28
|
|
29
|
+
If when running a migration you see an error like:
|
30
|
+
|
31
|
+
```
|
32
|
+
PerconaMigrator::Error: Cannot connect to MySQL: Cannot connect to MySQL because
|
33
|
+
the Perl DBI module is not installed or not found.
|
34
|
+
```
|
35
|
+
|
36
|
+
You also need to install the DBI and DBD::MySQL modules from `cpan`.
|
37
|
+
|
38
|
+
```
|
39
|
+
$ sudo cpan
|
40
|
+
cpan> install DBI
|
41
|
+
cpan> install DBD::mysql
|
42
|
+
```
|
43
|
+
|
21
44
|
### Linux
|
22
45
|
|
23
46
|
#### Ubuntu/Debian based
|
@@ -59,10 +82,52 @@ All the `ALTER TABLE` statements will be executed with
|
|
59
82
|
`pt-online-schema-change`, which will provide additional output to the
|
60
83
|
migration.
|
61
84
|
|
85
|
+
### pt-online-schema-change arguments
|
86
|
+
|
87
|
+
#### with environment variable
|
88
|
+
|
89
|
+
You can specify any `pt-online-schema-change` arguments when running the
|
90
|
+
migration. All what you pass in the PERCONA_ARGS env var, will be bypassed to the
|
91
|
+
binary, overwriting any default values. Note the format is the same as in
|
92
|
+
`pt-online-schema-change`. Check the full list in [Percona Toolkit
|
93
|
+
documentation](https://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html#options)
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
$ PERCONA_ARGS='--chunk-time=1' bundle exec rake db:migrate:up VERSION=xxx
|
97
|
+
```
|
98
|
+
|
99
|
+
or even mulitple arguments
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
$ PERCONA_ARGS='--chunk-time=1 --critical-load=55' bundle exec rake db:migrate:up VERSION=xxx
|
103
|
+
```
|
104
|
+
|
105
|
+
This however, only works for `db:migrate:up` or `db:migrate:down` rake tasks and
|
106
|
+
not with `db:migrate`. The settings you provide can't be generalized as these
|
107
|
+
vary depending on the database table and the kind of changes you apply.
|
108
|
+
|
109
|
+
#### with global configuration
|
110
|
+
|
111
|
+
You can specify any `pt-online-schema-change` arguments in global gem configuration
|
112
|
+
using `global_percona_args` option.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
PerconaMigrator.configure do |config|
|
116
|
+
config.global_percona_args = '--chunk-time=1 --critical-load=55'
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
Unlike using `PERCONA_ARGS`, options provided with global configuration will be applied
|
121
|
+
every time sql command is executed via `pt-online-schema-change`.
|
122
|
+
|
123
|
+
Arguments provided in global configuration can be overwritten with `PERCONA_ARGS` env variable.
|
124
|
+
|
125
|
+
We recommend using this option with caution and only when you understand the consequences.
|
126
|
+
|
62
127
|
### LHM support
|
63
128
|
|
64
129
|
If you moved to Soundcloud's [Lhm](https://github.com/soundcloud/lhm) already,
|
65
|
-
we got you covered.
|
130
|
+
we got you covered. Departure overrides Lhm's DSL so that all the alter
|
66
131
|
statements also go through `pt-online-schema-change` as well.
|
67
132
|
|
68
133
|
You can keep your Lhm migrations and start using Rails migration's DSL back
|
@@ -83,7 +148,7 @@ It's strongly recommended to name it after this gems name, such as
|
|
83
148
|
|
84
149
|
## How it works
|
85
150
|
|
86
|
-
When booting your Rails app,
|
151
|
+
When booting your Rails app, Departure extends the
|
87
152
|
`ActiveRecord::Migration#migrate` method to reset the connection and reestablish
|
88
153
|
it using the `PerconaAdapter` instead of the one you defined in your
|
89
154
|
`config/database.yml`.
|
data/bin/rspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
#
|
4
|
+
# This file was generated by Bundler.
|
5
|
+
#
|
6
|
+
# The application 'rspec' is installed as part of a gem, and
|
7
|
+
# this file is here to facilitate running it.
|
8
|
+
#
|
9
|
+
|
10
|
+
require "pathname"
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
12
|
+
Pathname.new(__FILE__).realpath)
|
13
|
+
|
14
|
+
require "rubygems"
|
15
|
+
require "bundler/setup"
|
16
|
+
|
17
|
+
load Gem.bin_path("rspec-core", "rspec")
|
@@ -13,9 +13,13 @@ module ActiveRecord
|
|
13
13
|
|
14
14
|
config[:username] = 'root' if config[:username].nil?
|
15
15
|
|
16
|
+
connection_details = PerconaMigrator::ConnectionDetails.new(config)
|
16
17
|
verbose = ActiveRecord::Migration.verbose
|
17
|
-
|
18
|
-
|
18
|
+
sanitizers = [
|
19
|
+
PerconaMigrator::LogSanitizers::PasswordSanitizer.new(connection_details)
|
20
|
+
]
|
21
|
+
percona_logger = PerconaMigrator::LoggerFactory.build(sanitizers: sanitizers, verbose: verbose)
|
22
|
+
cli_generator = PerconaMigrator::CliGenerator.new(connection_details)
|
19
23
|
|
20
24
|
runner = PerconaMigrator::Runner.new(
|
21
25
|
percona_logger,
|
@@ -1,54 +1,36 @@
|
|
1
|
+
require 'percona_migrator/dsn'
|
2
|
+
require 'percona_migrator/option'
|
1
3
|
require 'percona_migrator/alter_argument'
|
4
|
+
require 'percona_migrator/connection_details'
|
5
|
+
require 'percona_migrator/user_options'
|
2
6
|
|
3
7
|
module PerconaMigrator
|
4
8
|
|
5
|
-
# Represents the 'DSN' argument of Percona's pt-online-schema-change
|
6
|
-
# See https://www.percona.com/doc/percona-toolkit/2.0/pt-online-schema-change.html#dsn-options
|
7
|
-
class DSN
|
8
|
-
|
9
|
-
# Constructor
|
10
|
-
#
|
11
|
-
# @param database [String, Symbol]
|
12
|
-
# @param table_name [String, Symbol]
|
13
|
-
def initialize(database, table_name)
|
14
|
-
@database = database
|
15
|
-
@table_name = table_name
|
16
|
-
end
|
17
|
-
|
18
|
-
# Returns the pt-online-schema-change DSN string. See
|
19
|
-
# https://www.percona.com/doc/percona-toolkit/2.0/pt-online-schema-change.html#dsn-options
|
20
|
-
def to_s
|
21
|
-
"D=#{database},t=#{table_name}"
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
attr_reader :table_name, :database
|
27
|
-
end
|
28
|
-
|
29
9
|
# Generates the equivalent Percona's pt-online-schema-change command to the
|
30
10
|
# given SQL statement
|
31
11
|
#
|
32
12
|
# --no-check-alter is used to allow running CHANGE COLUMN statements. For
|
33
13
|
# more details, check: www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html#cmdoption-pt-online-schema-change--[no]check-alter
|
34
14
|
#
|
35
|
-
class CliGenerator
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
15
|
+
class CliGenerator
|
16
|
+
COMMAND_NAME = 'pt-online-schema-change'.freeze
|
17
|
+
DEFAULT_OPTIONS = Set.new(
|
18
|
+
[
|
19
|
+
Option.new('execute'),
|
20
|
+
Option.new('statistics'),
|
21
|
+
Option.new('alter-foreign-keys-method', 'auto'),
|
22
|
+
Option.new('no-check-alter')
|
23
|
+
]
|
24
|
+
).freeze
|
25
|
+
|
26
|
+
# TODO: Better doc.
|
27
|
+
#
|
28
|
+
# Constructor. Specify any arguments to pass to pt-online-schema-change
|
29
|
+
# passing the PERCONA_ARGS env var when executing the migration
|
46
30
|
#
|
47
31
|
# @param connection_data [Hash]
|
48
|
-
def initialize(
|
49
|
-
@
|
50
|
-
init_base_command
|
51
|
-
add_connection_details
|
32
|
+
def initialize(connection_details)
|
33
|
+
@connection_details = connection_details
|
52
34
|
end
|
53
35
|
|
54
36
|
# Generates the percona command. Fills all the connection credentials from
|
@@ -62,9 +44,9 @@ module PerconaMigrator
|
|
62
44
|
# @return [String]
|
63
45
|
def generate(table_name, statement)
|
64
46
|
alter_argument = AlterArgument.new(statement)
|
65
|
-
dsn = DSN.new(database, table_name)
|
47
|
+
dsn = DSN.new(connection_details.database, table_name)
|
66
48
|
|
67
|
-
"#{
|
49
|
+
"#{command} #{all_options} #{dsn} #{alter_argument}"
|
68
50
|
end
|
69
51
|
|
70
52
|
# Generates the percona command for a raw MySQL statement. Fills all the
|
@@ -77,63 +59,27 @@ module PerconaMigrator
|
|
77
59
|
# @return [String]
|
78
60
|
def parse_statement(statement)
|
79
61
|
alter_argument = AlterArgument.new(statement)
|
80
|
-
dsn = DSN.new(database, alter_argument.table_name)
|
62
|
+
dsn = DSN.new(connection_details.database, alter_argument.table_name)
|
81
63
|
|
82
|
-
"#{
|
64
|
+
"#{command} #{all_options} #{dsn} #{alter_argument}"
|
83
65
|
end
|
84
66
|
|
85
67
|
private
|
86
68
|
|
87
|
-
attr_reader :
|
88
|
-
|
89
|
-
# Sets up the command with its options
|
90
|
-
def init_base_command
|
91
|
-
@command = [BASE_COMMAND, BASE_OPTIONS.join(' ')]
|
92
|
-
end
|
93
|
-
|
94
|
-
# Adds the host, user and password, if present, to the command
|
95
|
-
def add_connection_details
|
96
|
-
@command.push("-h #{host}")
|
97
|
-
@command.push("-u #{user}")
|
98
|
-
@command.push("-p #{password}") if password.present?
|
99
|
-
end
|
100
|
-
|
101
|
-
# Returns the command as a string that can be executed in a shell
|
102
|
-
#
|
103
|
-
# @return [String]
|
104
|
-
def to_s
|
105
|
-
@command.join(' ')
|
106
|
-
end
|
107
|
-
|
108
|
-
# Returns the database host name, defaulting to localhost. If PERCONA_DB_HOST
|
109
|
-
# is passed its value will be used instead
|
110
|
-
#
|
111
|
-
# @return [String]
|
112
|
-
def host
|
113
|
-
ENV['PERCONA_DB_HOST'] || connection_data[:host] || 'localhost'
|
114
|
-
end
|
69
|
+
attr_reader :connection_details
|
115
70
|
|
116
|
-
|
117
|
-
|
118
|
-
#
|
119
|
-
# @return [String]
|
120
|
-
def user
|
121
|
-
ENV['PERCONA_DB_USER'] || connection_data[:username]
|
71
|
+
def command
|
72
|
+
"#{COMMAND_NAME} #{connection_details}"
|
122
73
|
end
|
123
74
|
|
124
|
-
# Returns the
|
125
|
-
# value will be used instead
|
75
|
+
# Returns all the arguments to execute pt-online-schema-change with
|
126
76
|
#
|
127
77
|
# @return [String]
|
128
|
-
def
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
# Returns the database name. If PERCONA_DB_NAME is passed its value will be
|
134
|
-
# used instead
|
135
|
-
def database
|
136
|
-
ENV['PERCONA_DB_NAME'] || connection_data[:database]
|
78
|
+
def all_options
|
79
|
+
env_variable_options = UserOptions.new
|
80
|
+
global_configuration_options = UserOptions.new(PerconaMigrator.configuration.global_percona_args)
|
81
|
+
options = env_variable_options.merge(global_configuration_options).merge(DEFAULT_OPTIONS)
|
82
|
+
options.to_a.join(' ')
|
137
83
|
end
|
138
84
|
end
|
139
85
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module PerconaMigrator
|
2
|
+
# Executes the given command returning it's status and errors
|
3
|
+
class Command
|
4
|
+
COMMAND_NOT_FOUND = 127
|
5
|
+
|
6
|
+
# Constructor
|
7
|
+
#
|
8
|
+
# @param command_line [String]
|
9
|
+
# @param error_log_path [String]
|
10
|
+
# @param logger [#write_no_newline]
|
11
|
+
def initialize(command_line, error_log_path, logger)
|
12
|
+
@command_line = command_line
|
13
|
+
@error_log_path = error_log_path
|
14
|
+
@logger = logger
|
15
|
+
end
|
16
|
+
|
17
|
+
# Executes the command returning its status. It also prints its stdout to
|
18
|
+
# the logger and its stderr to the file specified in error_log_path.
|
19
|
+
#
|
20
|
+
# @raise [NoStatusError] if the spawned process' status can't be retrieved
|
21
|
+
# @raise [SignalError] if the spawned process received a signal
|
22
|
+
# @raise [CommandNotFoundError] if pt-online-schema-change can't be found
|
23
|
+
#
|
24
|
+
# @return [Process::Status]
|
25
|
+
def run
|
26
|
+
log_deprecations
|
27
|
+
log_started
|
28
|
+
|
29
|
+
run_in_process
|
30
|
+
|
31
|
+
log_finished
|
32
|
+
|
33
|
+
validate_status!
|
34
|
+
status
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :command_line, :error_log_path, :logger, :status
|
40
|
+
|
41
|
+
# Runs the command in a separate process, capturing its stdout and
|
42
|
+
# execution status
|
43
|
+
def run_in_process
|
44
|
+
Open3.popen3(full_command) do |_stdin, stdout, _stderr, waith_thr|
|
45
|
+
begin
|
46
|
+
loop do
|
47
|
+
IO.select([stdout])
|
48
|
+
data = stdout.read_nonblock(8)
|
49
|
+
logger.write_no_newline(data)
|
50
|
+
end
|
51
|
+
rescue EOFError
|
52
|
+
# noop
|
53
|
+
ensure
|
54
|
+
@status = waith_thr.value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Builds the actual command including stderr redirection to the specified
|
60
|
+
# log file
|
61
|
+
#
|
62
|
+
# @return [String]
|
63
|
+
def full_command
|
64
|
+
"#{command_line} 2> #{error_log_path}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Validates the status of the execution
|
68
|
+
#
|
69
|
+
# @raise [NoStatusError] if the spawned process' status can't be retrieved
|
70
|
+
# @raise [SignalError] if the spawned process received a signal
|
71
|
+
# @raise [CommandNotFoundError] if pt-online-schema-change can't be found
|
72
|
+
def validate_status!
|
73
|
+
raise SignalError.new(status) if status.signaled?
|
74
|
+
raise CommandNotFoundError if status.exitstatus == COMMAND_NOT_FOUND
|
75
|
+
raise Error, error_message unless status.success?
|
76
|
+
end
|
77
|
+
|
78
|
+
# Returns the error message that appeared in the process' stderr
|
79
|
+
#
|
80
|
+
# @return [String]
|
81
|
+
def error_message
|
82
|
+
File.read(error_log_path)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Logs when the execution started
|
86
|
+
def log_started
|
87
|
+
logger.write("\n")
|
88
|
+
logger.say("Running #{command_line}\n\n", true)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Prints a line break to keep the logs separate from the execution time
|
92
|
+
# print by the migration
|
93
|
+
def log_finished
|
94
|
+
logger.write("\n")
|
95
|
+
end
|
96
|
+
|
97
|
+
def log_deprecations
|
98
|
+
logger.write("\n")
|
99
|
+
logger.write("[DEPRECATION] This gem has been renamed to Departure and will no longer be supported. Please switch to Departure as soon as possible.")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
module PerconaMigrator
|
2
2
|
class Configuration
|
3
|
-
attr_accessor :tmp_path
|
3
|
+
attr_accessor :tmp_path, :global_percona_args
|
4
4
|
|
5
5
|
def initialize
|
6
6
|
@tmp_path = '.'.freeze
|
7
7
|
@error_log_filename = 'percona_migrator_error.log'.freeze
|
8
|
+
@global_percona_args = nil
|
8
9
|
end
|
9
10
|
|
10
11
|
def error_log_path
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module PerconaMigrator
|
2
|
+
# Holds the parameters of the DB connection and formats them to string
|
3
|
+
class ConnectionDetails
|
4
|
+
|
5
|
+
# Constructor
|
6
|
+
#
|
7
|
+
# @param [Hash] connection parametes as used in #establish_conneciton
|
8
|
+
def initialize(connection_data)
|
9
|
+
@connection_data = connection_data
|
10
|
+
end
|
11
|
+
|
12
|
+
# Returns the details formatted as an string to be used with
|
13
|
+
# pt-online-schema-change. It follows the mysql client's format.
|
14
|
+
#
|
15
|
+
# @return [String]
|
16
|
+
def to_s
|
17
|
+
@to_s ||= "-h #{host} -u #{user} #{password_argument}"
|
18
|
+
end
|
19
|
+
|
20
|
+
# TODO: Doesn't the abstract adapter already handle this somehow?
|
21
|
+
# Returns the database name. If PERCONA_DB_NAME is passed its value will be
|
22
|
+
# used instead
|
23
|
+
#
|
24
|
+
# Returns the database name
|
25
|
+
#
|
26
|
+
# @return [String]
|
27
|
+
def database
|
28
|
+
ENV.fetch('PERCONA_DB_NAME', connection_data[:database])
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns the password fragment of the details string if a password is passed
|
32
|
+
#
|
33
|
+
# @return [String]
|
34
|
+
def password_argument
|
35
|
+
if password.present?
|
36
|
+
"-p #{password}"
|
37
|
+
else
|
38
|
+
''
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :connection_data
|
45
|
+
|
46
|
+
# Returns the database host name, defaulting to localhost. If PERCONA_DB_HOST
|
47
|
+
# is passed its value will be used instead
|
48
|
+
#
|
49
|
+
# @return [String]
|
50
|
+
def host
|
51
|
+
ENV.fetch('PERCONA_DB_HOST', connection_data[:host]) || 'localhost'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns the database user. If PERCONA_DB_USER is passed its value will be
|
55
|
+
# used instead
|
56
|
+
#
|
57
|
+
# @return [String]
|
58
|
+
def user
|
59
|
+
ENV.fetch('PERCONA_DB_USER', connection_data[:username])
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the database user's password. If PERCONA_DB_PASSWORD is passed its
|
63
|
+
# value will be used instead
|
64
|
+
#
|
65
|
+
# @return [String]
|
66
|
+
def password
|
67
|
+
ENV.fetch('PERCONA_DB_PASSWORD', connection_data[:password])
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module PerconaMigrator
|
2
|
+
|
3
|
+
# Represents the 'DSN' argument of Percona's pt-online-schema-change
|
4
|
+
# See https://www.percona.com/doc/percona-toolkit/2.0/pt-online-schema-change.html#dsn-options
|
5
|
+
class DSN
|
6
|
+
|
7
|
+
# Constructor
|
8
|
+
#
|
9
|
+
# @param database [String, Symbol]
|
10
|
+
# @param table_name [String, Symbol]
|
11
|
+
def initialize(database, table_name)
|
12
|
+
@database = database
|
13
|
+
@table_name = table_name
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the pt-online-schema-change DSN string. See
|
17
|
+
# https://www.percona.com/doc/percona-toolkit/2.0/pt-online-schema-change.html#dsn-options
|
18
|
+
def to_s
|
19
|
+
"D=#{database},t=#{table_name}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :table_name, :database
|
25
|
+
end
|
26
|
+
end
|
@@ -32,4 +32,8 @@ module PerconaMigrator
|
|
32
32
|
'Please install pt-online-schema-change. Check: https://www.percona.com/doc/percona-toolkit for further details'
|
33
33
|
end
|
34
34
|
end
|
35
|
+
|
36
|
+
# Used to prevent running the db:migrate rake task when providing arguments
|
37
|
+
# through PERCONA_ARGS env var
|
38
|
+
class ArgumentsNotSupported < Error; end
|
35
39
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module PerconaMigrator
|
2
|
+
module LogSanitizers
|
3
|
+
class PasswordSanitizer
|
4
|
+
PASSWORD_REPLACEMENT = '[filtered_password]'
|
5
|
+
|
6
|
+
delegate :password_argument, to: :connection_details
|
7
|
+
|
8
|
+
def initialize(connection_details)
|
9
|
+
@connection_details = connection_details
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute(log_statement)
|
13
|
+
return log_statement if password_argument.blank?
|
14
|
+
log_statement.gsub(password_argument, PASSWORD_REPLACEMENT)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
attr_accessor :connection_details
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -5,6 +5,10 @@ module PerconaMigrator
|
|
5
5
|
# seen from the connection adapter.
|
6
6
|
class Logger
|
7
7
|
|
8
|
+
def initialize(sanitizers)
|
9
|
+
@sanitizers = sanitizers
|
10
|
+
end
|
11
|
+
|
8
12
|
# Outputs the message through the stdout, following the
|
9
13
|
# ActiveRecord::Migration log format
|
10
14
|
#
|
@@ -18,14 +22,22 @@ module PerconaMigrator
|
|
18
22
|
#
|
19
23
|
# @param text [String]
|
20
24
|
def write(text = '')
|
21
|
-
puts(text)
|
25
|
+
puts(sanitize(text))
|
22
26
|
end
|
23
27
|
|
24
28
|
# Outputs the text through the stdout without adding a new line at the end
|
25
29
|
#
|
26
30
|
# @param text [String]
|
27
31
|
def write_no_newline(text)
|
28
|
-
print(text)
|
32
|
+
print(sanitize(text))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_accessor :sanitizers
|
38
|
+
|
39
|
+
def sanitize(text)
|
40
|
+
sanitizers.inject(text) { |memo, sanitizer| sanitizer.execute(memo) }
|
29
41
|
end
|
30
42
|
end
|
31
43
|
end
|
@@ -6,9 +6,9 @@ module PerconaMigrator
|
|
6
6
|
#
|
7
7
|
# @param verbose [Boolean]
|
8
8
|
# @return [#say, #write]
|
9
|
-
def self.build(verbose: true)
|
9
|
+
def self.build(sanitizers: [], verbose: true)
|
10
10
|
if verbose
|
11
|
-
PerconaMigrator::Logger.new
|
11
|
+
PerconaMigrator::Logger.new(sanitizers)
|
12
12
|
else
|
13
13
|
PerconaMigrator::NullLogger.new
|
14
14
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module PerconaMigrator
|
2
|
+
class Option
|
3
|
+
attr_reader :name, :value
|
4
|
+
|
5
|
+
# Builds an instance by parsing its name and value out of the given string.
|
6
|
+
# Note the string must conform to "--<arg>=<value>" format.
|
7
|
+
#
|
8
|
+
# @param string [String]
|
9
|
+
# @return [Option]
|
10
|
+
def self.from_string(string)
|
11
|
+
pair = string.split('=')
|
12
|
+
name = pair[0][2..-1]
|
13
|
+
value = pair[1]
|
14
|
+
|
15
|
+
new(name, value)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Constructor
|
19
|
+
#
|
20
|
+
# @param name [String]
|
21
|
+
# @param optional value [String]
|
22
|
+
def initialize(name, value = nil)
|
23
|
+
@name = name
|
24
|
+
@value = value
|
25
|
+
end
|
26
|
+
|
27
|
+
# Compares two options
|
28
|
+
#
|
29
|
+
# @param [Option]
|
30
|
+
# @return [Boolean]
|
31
|
+
def ==(another_option)
|
32
|
+
name == another_option.name
|
33
|
+
end
|
34
|
+
alias :eql? :==
|
35
|
+
|
36
|
+
# Returns the option's hash
|
37
|
+
#
|
38
|
+
# @return [Fixnum]
|
39
|
+
def hash
|
40
|
+
name.hash
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the option as string following the "--<name>=<value>" format
|
44
|
+
#
|
45
|
+
# @return [String]
|
46
|
+
def to_s
|
47
|
+
"--#{name}#{value_as_string}"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Returns the value fragment of the option string if any value is specified
|
53
|
+
#
|
54
|
+
# @return [String]
|
55
|
+
def value_as_string
|
56
|
+
if value.nil?
|
57
|
+
''
|
58
|
+
else
|
59
|
+
"=#{value}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -5,7 +5,6 @@ module PerconaMigrator
|
|
5
5
|
# It executes pt-online-schema-change commands in a new process and gets its
|
6
6
|
# output and status
|
7
7
|
class Runner
|
8
|
-
COMMAND_NOT_FOUND = 127
|
9
8
|
|
10
9
|
# Constructor
|
11
10
|
#
|
@@ -17,8 +16,7 @@ module PerconaMigrator
|
|
17
16
|
@logger = logger
|
18
17
|
@cli_generator = cli_generator
|
19
18
|
@mysql_adapter = mysql_adapter
|
20
|
-
@
|
21
|
-
@config = config
|
19
|
+
@error_log_path = config.error_log_path
|
22
20
|
end
|
23
21
|
|
24
22
|
# Executes the passed sql statement using pt-online-schema-change for ALTER
|
@@ -27,8 +25,8 @@ module PerconaMigrator
|
|
27
25
|
# @param sql [String]
|
28
26
|
def query(sql)
|
29
27
|
if alter_statement?(sql)
|
30
|
-
|
31
|
-
execute(
|
28
|
+
command_line = cli_generator.parse_statement(sql)
|
29
|
+
execute(command_line)
|
32
30
|
else
|
33
31
|
mysql_adapter.execute(sql)
|
34
32
|
end
|
@@ -45,18 +43,15 @@ module PerconaMigrator
|
|
45
43
|
# TODO: rename it so we don't confuse it with AR's #execute
|
46
44
|
# Runs and logs the given command
|
47
45
|
#
|
48
|
-
# @param
|
46
|
+
# @param command_line [String]
|
49
47
|
# @return [Boolean]
|
50
|
-
def execute(
|
51
|
-
|
52
|
-
logging { run_command }
|
53
|
-
validate_status
|
54
|
-
status
|
48
|
+
def execute(command_line)
|
49
|
+
Command.new(command_line, error_log_path, logger).run
|
55
50
|
end
|
56
51
|
|
57
52
|
private
|
58
53
|
|
59
|
-
attr_reader :
|
54
|
+
attr_reader :logger, :cli_generator, :mysql_adapter, :error_log_path
|
60
55
|
|
61
56
|
# Checks whether the sql statement is an ALTER TABLE
|
62
57
|
#
|
@@ -65,66 +60,5 @@ module PerconaMigrator
|
|
65
60
|
def alter_statement?(sql)
|
66
61
|
sql =~ /\Aalter table/i
|
67
62
|
end
|
68
|
-
|
69
|
-
# Logs the start and end of the execution
|
70
|
-
#
|
71
|
-
# @yield
|
72
|
-
def logging
|
73
|
-
log_started
|
74
|
-
yield
|
75
|
-
log_finished
|
76
|
-
end
|
77
|
-
|
78
|
-
# Logs when the execution started
|
79
|
-
def log_started
|
80
|
-
logger.write("\n")
|
81
|
-
logger.say("Running #{command}\n\n", true)
|
82
|
-
end
|
83
|
-
|
84
|
-
# Executes the command and prints its output to the stdout
|
85
|
-
def run_command
|
86
|
-
Open3.popen3("#{command} 2> #{error_log_path}") do |_stdin, stdout, _stderr, waith_thr|
|
87
|
-
begin
|
88
|
-
loop do
|
89
|
-
IO.select([stdout])
|
90
|
-
data = stdout.read_nonblock(8)
|
91
|
-
logger.write_no_newline(data)
|
92
|
-
end
|
93
|
-
rescue EOFError
|
94
|
-
# noop
|
95
|
-
ensure
|
96
|
-
@status = waith_thr.value
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
# Validates the status of the execution
|
102
|
-
#
|
103
|
-
# @raise [NoStatusError] if the spawned process' status can't be retrieved
|
104
|
-
# @raise [SignalError] if the spawned process received a signal
|
105
|
-
# @raise [CommandNotFoundError] if pt-online-schema-change can't be found
|
106
|
-
def validate_status
|
107
|
-
raise SignalError.new(status) if status.signaled?
|
108
|
-
raise CommandNotFoundError if status.exitstatus == COMMAND_NOT_FOUND
|
109
|
-
raise Error, error_message unless status.success?
|
110
|
-
end
|
111
|
-
|
112
|
-
# Prints a line break to keep the logs separate from the execution time
|
113
|
-
# print by the migration
|
114
|
-
def log_finished
|
115
|
-
logger.write("\n")
|
116
|
-
end
|
117
|
-
|
118
|
-
# The path where the percona toolkit stderr will be written
|
119
|
-
#
|
120
|
-
# @return [String]
|
121
|
-
def error_log_path
|
122
|
-
config.error_log_path
|
123
|
-
end
|
124
|
-
|
125
|
-
# @return [String]
|
126
|
-
def error_message
|
127
|
-
File.read(error_log_path)
|
128
|
-
end
|
129
63
|
end
|
130
64
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module PerconaMigrator
|
2
|
+
# Encapsulates the pt-online-schema-change options defined by the user
|
3
|
+
class UserOptions
|
4
|
+
delegate :each, :merge, to: :to_set
|
5
|
+
|
6
|
+
# Constructor
|
7
|
+
#
|
8
|
+
# @param arguments [String]
|
9
|
+
def initialize(arguments = ENV['PERCONA_ARGS'])
|
10
|
+
@arguments = arguments
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_reader :arguments
|
16
|
+
|
17
|
+
# Returns the arguments the user defined but without duplicates
|
18
|
+
#
|
19
|
+
# @return [Set]
|
20
|
+
def to_set
|
21
|
+
Set.new(user_options)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns Option instances from the arguments the user specified, if any
|
25
|
+
#
|
26
|
+
# @return [Array]
|
27
|
+
def user_options
|
28
|
+
if arguments
|
29
|
+
build_options
|
30
|
+
else
|
31
|
+
[]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Builds Option instances from the user arguments
|
36
|
+
#
|
37
|
+
# @return [Array<Option>]
|
38
|
+
def build_options
|
39
|
+
arguments.split(' ').map do |argument|
|
40
|
+
Option.from_string(argument)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/percona_migrator.rb
CHANGED
@@ -2,6 +2,7 @@ require 'active_record'
|
|
2
2
|
require 'active_support/all'
|
3
3
|
|
4
4
|
require 'percona_migrator/version'
|
5
|
+
require 'percona_migrator/log_sanitizers/password_sanitizer'
|
5
6
|
require 'percona_migrator/runner'
|
6
7
|
require 'percona_migrator/cli_generator'
|
7
8
|
require 'percona_migrator/logger'
|
@@ -9,6 +10,7 @@ require 'percona_migrator/null_logger'
|
|
9
10
|
require 'percona_migrator/logger_factory'
|
10
11
|
require 'percona_migrator/configuration'
|
11
12
|
require 'percona_migrator/errors'
|
13
|
+
require 'percona_migrator/command'
|
12
14
|
|
13
15
|
require 'percona_migrator/railtie' if defined?(Rails)
|
14
16
|
|
@@ -28,6 +30,21 @@ module PerconaMigrator
|
|
28
30
|
# Hooks Percona Migrator into Rails migrations by replacing the configured
|
29
31
|
# database adapter
|
30
32
|
def self.load
|
33
|
+
ActiveRecord::Migrator.instance_eval do
|
34
|
+
class << self
|
35
|
+
alias_method(:original_migrate, :migrate)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Checks whether arguments are being passed through PERCONA_ARGS when running
|
39
|
+
# the db:migrate rake task
|
40
|
+
#
|
41
|
+
# @raise [ArgumentsNotSupported] if PERCONA_ARGS has any value
|
42
|
+
def migrate(migrations_paths, target_version = nil, &block)
|
43
|
+
raise ArgumentsNotSupported if ENV['PERCONA_ARGS'].present?
|
44
|
+
original_migrate(migrations_paths, target_version, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
31
48
|
ActiveRecord::Migration.class_eval do
|
32
49
|
alias_method :original_migrate, :migrate
|
33
50
|
|
@@ -55,10 +72,9 @@ module PerconaMigrator
|
|
55
72
|
# Make all connections in the connection pool to use PerconaAdapter
|
56
73
|
# instead of the current adapter.
|
57
74
|
def reconnect_with_percona
|
58
|
-
connection_config = ActiveRecord::Base
|
59
|
-
|
60
|
-
|
61
|
-
)
|
75
|
+
connection_config = ActiveRecord::Base
|
76
|
+
.connection_config.merge(adapter: 'percona')
|
77
|
+
ActiveRecord::Base.establish_connection(connection_config)
|
62
78
|
end
|
63
79
|
end
|
64
80
|
end
|
data/percona_migrator.gemspec
CHANGED
@@ -7,14 +7,20 @@ require 'percona_migrator/version'
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = 'percona_migrator'
|
9
9
|
spec.version = PerconaMigrator::VERSION
|
10
|
-
spec.authors = ['Ilya Zayats', 'Pau Pérez', 'Fran Casas', 'Jorge Morante']
|
11
|
-
spec.email = ['ilya.zayats@redbooth.com', 'pau.perez@redbooth.com', 'fran.casas@redbooth.com', 'jorge.morante@redbooth.com']
|
10
|
+
spec.authors = ['Ilya Zayats', 'Pau Pérez', 'Fran Casas', 'Jorge Morante', 'Adrian Serafin']
|
11
|
+
spec.email = ['ilya.zayats@redbooth.com', 'pau.perez@redbooth.com', 'fran.casas@redbooth.com', 'jorge.morante@redbooth.com', 'adrian@softmad.pl']
|
12
12
|
|
13
13
|
spec.summary = %q{pt-online-schema-change runner for ActiveRecord migrations}
|
14
14
|
spec.description = %q{Execute your ActiveRecord migrations with Percona's pt-online-schema-change}
|
15
15
|
spec.homepage = 'http://github.com/redbooth/percona_migrator'
|
16
16
|
spec.license = 'MIT'
|
17
17
|
|
18
|
+
spec.post_install_message = <<-MESSAGE
|
19
|
+
! The Percona_migrator gem has been deprecated and has been replaced by Departure.
|
20
|
+
! See: https://rubygems.org/gems/departure
|
21
|
+
! And: https://github.com/redbooth/departure
|
22
|
+
MESSAGE
|
23
|
+
|
18
24
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
25
|
spec.require_paths = ['lib']
|
20
26
|
|
@@ -26,4 +32,5 @@ Gem::Specification.new do |spec|
|
|
26
32
|
spec.add_development_dependency 'rspec', '~> 3.4', '>= 3.4.0'
|
27
33
|
spec.add_development_dependency 'rspec-its', '~> 1.2'
|
28
34
|
spec.add_development_dependency 'byebug', '~> 8.2', '>= 8.2.1'
|
35
|
+
spec.add_development_dependency 'climate_control', '~> 0.0.3'
|
29
36
|
end
|
metadata
CHANGED
@@ -1,17 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: percona_migrator
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Zayats
|
8
8
|
- Pau Pérez
|
9
9
|
- Fran Casas
|
10
10
|
- Jorge Morante
|
11
|
+
- Adrian Serafin
|
11
12
|
autorequire:
|
12
13
|
bindir: bin
|
13
14
|
cert_chain: []
|
14
|
-
date:
|
15
|
+
date: 2017-04-07 00:00:00.000000000 Z
|
15
16
|
dependencies:
|
16
17
|
- !ruby/object:Gem::Dependency
|
17
18
|
name: rails
|
@@ -129,12 +130,27 @@ dependencies:
|
|
129
130
|
- - ">="
|
130
131
|
- !ruby/object:Gem::Version
|
131
132
|
version: 8.2.1
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
name: climate_control
|
135
|
+
requirement: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - "~>"
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 0.0.3
|
140
|
+
type: :development
|
141
|
+
prerelease: false
|
142
|
+
version_requirements: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - "~>"
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: 0.0.3
|
132
147
|
description: Execute your ActiveRecord migrations with Percona's pt-online-schema-change
|
133
148
|
email:
|
134
149
|
- ilya.zayats@redbooth.com
|
135
150
|
- pau.perez@redbooth.com
|
136
151
|
- fran.casas@redbooth.com
|
137
152
|
- jorge.morante@redbooth.com
|
153
|
+
- adrian@softmad.pl
|
138
154
|
executables: []
|
139
155
|
extensions: []
|
140
156
|
extra_rdoc_files: []
|
@@ -150,6 +166,7 @@ files:
|
|
150
166
|
- RELEASING.md
|
151
167
|
- Rakefile
|
152
168
|
- bin/console
|
169
|
+
- bin/rspec
|
153
170
|
- bin/setup
|
154
171
|
- config.yml
|
155
172
|
- configuration.rb
|
@@ -161,13 +178,19 @@ files:
|
|
161
178
|
- lib/percona_migrator.rb
|
162
179
|
- lib/percona_migrator/alter_argument.rb
|
163
180
|
- lib/percona_migrator/cli_generator.rb
|
181
|
+
- lib/percona_migrator/command.rb
|
164
182
|
- lib/percona_migrator/configuration.rb
|
183
|
+
- lib/percona_migrator/connection_details.rb
|
184
|
+
- lib/percona_migrator/dsn.rb
|
165
185
|
- lib/percona_migrator/errors.rb
|
186
|
+
- lib/percona_migrator/log_sanitizers/password_sanitizer.rb
|
166
187
|
- lib/percona_migrator/logger.rb
|
167
188
|
- lib/percona_migrator/logger_factory.rb
|
168
189
|
- lib/percona_migrator/null_logger.rb
|
190
|
+
- lib/percona_migrator/option.rb
|
169
191
|
- lib/percona_migrator/railtie.rb
|
170
192
|
- lib/percona_migrator/runner.rb
|
193
|
+
- lib/percona_migrator/user_options.rb
|
171
194
|
- lib/percona_migrator/version.rb
|
172
195
|
- percona_migrator.gemspec
|
173
196
|
- test_database.rb
|
@@ -175,7 +198,10 @@ homepage: http://github.com/redbooth/percona_migrator
|
|
175
198
|
licenses:
|
176
199
|
- MIT
|
177
200
|
metadata: {}
|
178
|
-
post_install_message:
|
201
|
+
post_install_message: |2
|
202
|
+
! The Percona_migrator gem has been deprecated and has been replaced by Departure.
|
203
|
+
! See: https://rubygems.org/gems/departure
|
204
|
+
! And: https://github.com/redbooth/departure
|
179
205
|
rdoc_options: []
|
180
206
|
require_paths:
|
181
207
|
- lib
|
@@ -191,7 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
191
217
|
version: '0'
|
192
218
|
requirements: []
|
193
219
|
rubyforge_project:
|
194
|
-
rubygems_version: 2.
|
220
|
+
rubygems_version: 2.6.10
|
195
221
|
signing_key:
|
196
222
|
specification_version: 4
|
197
223
|
summary: pt-online-schema-change runner for ActiveRecord migrations
|