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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 686c3a5b5ab858a587451fba1c6aa0b934f1cf9c
4
- data.tar.gz: d01ece64414481d9b43a143a16f748ce134afc7a
3
+ metadata.gz: df19cbdeb9b3e36074ff5e95ec745afafb5db5f6
4
+ data.tar.gz: df627f8393513e5699c63d37c84e46237596aea4
5
5
  SHA512:
6
- metadata.gz: 104039977e6a970cad93bcb93e7ff628a2a00c0a10715543daaf9915fca08a1bf6e1643dc2e2e34e947f494f43abc9662c62a0a3e53ae9c98a0500abf9a9576e
7
- data.tar.gz: f7e205f53c0cfd327dd293d0357b8e1e85b64ddf0a7a2cca14679a22607e3d5c8d1731757cb0ed1a25d83c38f31795fe23a74fc9cfd82af5cad1f42e67ce4e84
6
+ metadata.gz: 6a37c51ca128523a8ab16f9271a76e0612355ccba8e9406557634ccaf77d4e62db25fa29ccc9edf0fc5e7ebf952a6bb7074e870f7fa545c5714b5235212e6d51
7
+ data.tar.gz: 80a4e55d6136515ee6f742b9053a7dca9d1e11570bdc29d1f1b662e135f210bb88e54d6a09dceb9b3b88d4fee4056464e527dce22dadf6fd10d665b6d698586c
data/.travis.yml CHANGED
@@ -7,7 +7,7 @@ before_install:
7
7
  - sudo apt-get update -qq
8
8
  - sudo apt-get install percona-toolkit
9
9
  - gem update bundler
10
- - bin/setup
10
+ install: bin/setup
11
11
  after_success:
12
12
  - codeclimate-test-reporter
13
13
 
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
- # Percona Migrator [![Build Status](https://travis-ci.org/redbooth/percona_migrator.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)
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
- Percona Migrator is an **ActiveRecord connection adapter** that allows running
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
- Percona Migrator relies on `pt-online-schema-change` from [Percona
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. Percona Migrator overrides Lhm's DSL so that all the alter
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, Percona Migrator extends the
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
- percona_logger = PerconaMigrator::LoggerFactory.build(verbose: verbose)
18
- cli_generator = PerconaMigrator::CliGenerator.new(config)
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 # Command
36
- BASE_COMMAND = 'pt-online-schema-change'
37
- BASE_OPTIONS = %w(
38
- --execute
39
- --statistics
40
- --recursion-method=none
41
- --alter-foreign-keys-method=auto
42
- --no-check-alter
43
- )
44
-
45
- # Constructor
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(connection_data)
49
- @connection_data = connection_data
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
- "#{self} #{dsn} #{alter_argument}"
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
- "#{self} #{dsn} #{alter_argument}"
64
+ "#{command} #{all_options} #{dsn} #{alter_argument}"
83
65
  end
84
66
 
85
67
  private
86
68
 
87
- attr_reader :connection_data
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
- # Returns the database user. If PERCONA_DB_USER is passed its value will be
117
- # used instead
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 database user's password. If PERCONA_DB_PASSWORD is passed its
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 password
129
- ENV['PERCONA_DB_PASSWORD'] || connection_data[:password]
130
- end
131
-
132
- # TODO: Doesn't the abstract adapter already handle this somehow?
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
- @status = nil
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
- command = cli_generator.parse_statement(sql)
31
- execute(command)
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 command [String]
46
+ # @param command_line [String]
49
47
  # @return [Boolean]
50
- def execute(command)
51
- @command = command
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 :command, :logger, :status, :cli_generator, :mysql_adapter, :config
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
@@ -1,3 +1,3 @@
1
1
  module PerconaMigrator
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '1.1.0'.freeze
3
3
  end
@@ -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.connection_config
59
- ActiveRecord::Base.establish_connection(
60
- connection_config.merge(adapter: 'percona')
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
@@ -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.0.0
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: 2016-11-30 00:00:00.000000000 Z
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.4.5.1
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