percona_migrator 1.0.0 → 1.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 +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 [](https://travis-ci.org/redbooth/percona_migrator) [](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
|