mys3ql 1.0.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 86d0a66ce7dab918b298c22eb4d667df2cb9ac7c
4
+ data.tar.gz: cd9527db20ae5a07349b3456e544a0aa3552f9bd
5
+ SHA512:
6
+ metadata.gz: 25abe9f3cc7daee801c8402dbd07b8827608d139ed4a2ca95ec089ed1a7d4335dfbfaa8fd30f10ea06118159d3513fa90c866ac9fa09976b48dc8d05645ac105
7
+ data.tar.gz: 340d29d1230eda51d4408b8f5fb3597ed7da31276171608469092c10fbe04ceb5f3e0640f3758cbdbe14c2af99804bc5ebb81dd27e6097c661c492d4339d56f4
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Simple backup of your MySQL database onto Amazon S3.
4
4
 
5
+ See [Example: mysqldump + mysqlbinlog for Backup and Restore](https://dev.mysql.com/doc/refman/5.7/en/mysqlbinlog-backup.html#mysqlbinlog-backup-example).
6
+
5
7
 
6
8
  ## Quick start
7
9
 
@@ -19,6 +21,12 @@ To restore from the latest backup (plus binlogs if present):
19
21
 
20
22
  $ mys3ql restore
21
23
 
24
+ To restore a recent subset of binlogs:
25
+
26
+ $ mys3ql restore --after NUMBER
27
+
28
+ – where NUMBER is a 6-digit binlog file number.
29
+
22
30
  By default mys3ql looks for a configuration file at `~/.mys3ql`. You can override this like so:
23
31
 
24
32
  $ mys3ql [command] -c FILE
@@ -79,20 +87,8 @@ N.B. the binary logs contain updates to all the databases on the server. This m
79
87
  Marc-André Cournoyer's [mysql_s3_backup](https://github.com/macournoyer/mysql_s3_backup).
80
88
 
81
89
 
82
- ## To Do
83
-
84
- - tests ;)
85
- - remove old dump files (s3)
86
- - (restore from non-latest dump)
87
-
88
-
89
- ## Questions, Problems, Feedback
90
-
91
- Please use the GitHub [issue tracker](https://github.com/airblade/mys3ql/issues) or email me.
92
-
93
-
94
90
  ## Intellectual property
95
91
 
96
- Copyright 2011 Andy Stewart (boss@airbladesoftware.com).
92
+ Copyright 2011-2021 Andy Stewart (boss@airbladesoftware.com).
97
93
 
98
94
  Released under the MIT licence.
data/bin/mys3ql CHANGED
@@ -1,35 +1,33 @@
1
- #!/usr/bin/env ruby-local-exec
2
-
3
- lib_dir = File.join(File.dirname(__FILE__), '..', 'lib')
4
- $LOAD_PATH.unshift lib_dir if File.directory?(lib_dir)
1
+ #!/usr/bin/env ruby
5
2
 
3
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
6
4
  require 'mys3ql'
7
- require 'main'
5
+ require 'optparse'
8
6
 
9
- Main do
10
- # consider using modes as/when we need command-specific arguments (e.g. restore specific backup)
11
- argument 'command' do
12
- validate { |command| %w[ full incremental restore ].include? command }
13
- description 'specifies the operation to perform [full | incremental | restore]'
14
- end
7
+ params = {}
8
+ op = OptionParser.new do |opts|
9
+ opts.banner = 'Usage: mys3ql <full|incremental|restore> [<options>]'
15
10
 
16
- option 'config', 'c' do
17
- argument :required
18
- description 'load configuration from YAML file'
19
- defaults '~/.mys3ql'
20
- end
11
+ opts.separator ''
12
+ opts.separator 'Common options:'
21
13
 
22
- option 'debug', 'd' do
23
- description 'be verbose'
14
+ opts.on('-c', '--config CONFIG', 'Load configuration from YAML file (default ~/.mys3ql)') { |v| params[:config] = v }
15
+ opts.on('-d', '--debug', 'Be verbose') { |v| params[:debug] = v }
16
+ opts.on '-v', '--version', 'Print version' do
17
+ puts "mys3ql v#{Mys3ql::VERSION}"
18
+ exit
24
19
  end
25
20
 
26
- option 'version', 'v'
21
+ opts.separator ''
22
+ opts.separator 'restore options:'
23
+ opts.on('-a', '--after NUMBER', 'Use only the subset of binary logs after NUMBER') { |v| params[:after] = v }
24
+ end
25
+ op.parse! ARGV
27
26
 
28
- def run
29
- if params[:version].given?
30
- puts "mys3ql v#{Mys3ql::VERSION}"
31
- exit
32
- end
33
- Mys3ql::Conductor.run params[:command].value, params[:config].value, params[:debug].given?
34
- end
27
+ params[:command] = ARGV[0]
28
+ unless %w[full incremental restore].include? params[:command]
29
+ puts op.help
30
+ exit 1
35
31
  end
32
+
33
+ Mys3ql::Conductor.run params
@@ -6,10 +6,16 @@ require 'mys3ql/s3'
6
6
  module Mys3ql
7
7
  class Conductor
8
8
 
9
- def self.run(command, config, debug)
10
- conductor = Conductor.new(config)
11
- conductor.debug = debug
12
- conductor.send command
9
+ def self.run(args)
10
+ conductor = Conductor.new args.fetch(:config, nil)
11
+ conductor.debug = args.fetch(:debug, false)
12
+
13
+ command = args.fetch(:command)
14
+ if command == 'restore'
15
+ conductor.restore args.fetch(:after, nil)
16
+ else
17
+ conductor.send command
18
+ end
13
19
  end
14
20
 
15
21
  def initialize(config_file = nil)
@@ -18,6 +24,10 @@ module Mys3ql
18
24
  @s3 = S3.new @config
19
25
  end
20
26
 
27
+ # Dumps the database and uploads it to S3.
28
+ # Copies the uploaded file to the key :latest.
29
+ # Deletes binary logs from the file system.
30
+ # Deletes binary logs from S3.
21
31
  def full
22
32
  @mysql.dump
23
33
  @s3.store @mysql.dump_file
@@ -25,30 +35,49 @@ module Mys3ql
25
35
  @s3.delete_bin_logs
26
36
  end
27
37
 
38
+ # Uploads mysql's binary logs to S3.
39
+ # The binary logs are left on the file system.
40
+ # Log files already on S3 are not re-uploaded.
28
41
  def incremental
29
42
  @mysql.each_bin_log do |log|
30
43
  @s3.store log, false
31
44
  end
32
45
  end
33
46
 
34
- # for now only restore from latest
35
- def restore
36
- # get latest dump
37
- with_temp_file do |file|
38
- @s3.retrieve :latest, file
39
- @mysql.restore file
47
+ # When 'after' is nil:
48
+ #
49
+ # - downloads the latest dump from S3 and loads it into the database;
50
+ # - downloads each binary log from S3 and loads it into the database.
51
+ #
52
+ # When 'after' is given:
53
+ #
54
+ # - downloads each binary log following 'after' from S3 and loads it into the database.
55
+ #
56
+ # Downloaded files are removed from the file system.
57
+ def restore(after = nil)
58
+ unless after
59
+ # get latest dump
60
+ with_temp_file do |file|
61
+ @s3.retrieve :latest, file
62
+ @mysql.restore file
63
+ end
40
64
  end
41
65
 
42
- # apply subsequent bin logs
43
- @s3.each_bin_log do |log|
44
- with_temp_file do |file|
45
- @s3.retrieve log, file
46
- @mysql.apply_bin_log file
66
+ # apply bin logs
67
+ begin
68
+ tmpfiles = []
69
+ @s3.each_bin_log(after) do |log|
70
+ file = Tempfile.new 'mys3ql'
71
+ tmpfiles << file
72
+ @s3.retrieve log, file.path
47
73
  end
74
+ @mysql.apply_bin_logs tmpfiles.map(&:path)
75
+ ensure
76
+ tmpfiles.each &:close!
48
77
  end
49
78
 
50
79
  # NOTE: not sure about this:
51
- puts "You might want to flush mysql's logs..."
80
+ # puts "You might want to flush mysql's logs..."
52
81
  end
53
82
 
54
83
  def debug=(val)
data/lib/mys3ql/mysql.rb CHANGED
@@ -13,8 +13,10 @@ module Mys3ql
13
13
  #
14
14
 
15
15
  def dump
16
+ # --master-data=2 include the current binary log coordinates in the log file
17
+ # --delete-master-logs delete binary log files
16
18
  cmd = "#{@config.bin_path}mysqldump"
17
- cmd += ' --quick --single-transaction --create-options'
19
+ cmd += ' --quick --single-transaction --create-options --no-tablespaces'
18
20
  cmd += ' --flush-logs --master-data=2 --delete-master-logs' if binary_logging?
19
21
  cmd += cli_options
20
22
  cmd += " | gzip > #{dump_file}"
@@ -36,14 +38,18 @@ module Mys3ql
36
38
 
37
39
  # flushes logs, yields each bar the last to the block
38
40
  def each_bin_log(&block)
41
+ # FLUSH LOGS Closes and reopens any log file, including binary logs,
42
+ # to which the server is writing. For binary logs, the sequence
43
+ # number of the binary log file is incremented by one relative to
44
+ # the previous file.
45
+ # https://dev.mysql.com/doc/refman/5.7/en/flush.html#flush-logs
46
+ # https://dev.mysql.com/doc/refman/5.7/en/flush.html#flush-binary-logs
39
47
  execute 'flush logs'
40
48
  logs = Dir.glob("#{@config.bin_log}.[0-9]*").sort_by { |f| f[/\d+/].to_i }
41
- logs_to_backup = logs[0..-2] # all logs except the last, which is in use
49
+ logs_to_backup = logs[0..-2] # all logs except the last, which is newly created
42
50
  logs_to_backup.each do |log_file|
43
51
  yield log_file
44
52
  end
45
- # delete binlogs from file system
46
- #execute "purge master logs to '#{File.basename(logs[-1])}'"
47
53
  end
48
54
 
49
55
  #
@@ -54,8 +60,8 @@ module Mys3ql
54
60
  run "gunzip -c #{file} | #{@config.bin_path}mysql #{cli_options}"
55
61
  end
56
62
 
57
- def apply_bin_log(file)
58
- cmd = "#{@config.bin_path}mysqlbinlog --database=#{@config.database} #{file}"
63
+ def apply_bin_logs(*files)
64
+ cmd = "#{@config.bin_path}mysqlbinlog --database=#{@config.database} #{files.join ' '}"
59
65
  cmd += " | #{@config.bin_path}mysql -u'#{@config.user}'"
60
66
  cmd += " -p'#{@config.password}'" if @config.password
61
67
  run cmd
data/lib/mys3ql/s3.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  require 'mys3ql/shell'
2
- require 'fog'
2
+ require 'aws-sdk-s3'
3
3
 
4
4
  module Mys3ql
5
5
  class S3
@@ -7,7 +7,6 @@ module Mys3ql
7
7
 
8
8
  def initialize(config)
9
9
  @config = config
10
- Fog::Logger[:warning] = nil
11
10
  end
12
11
 
13
12
  def store(file, dump = true)
@@ -15,20 +14,28 @@ module Mys3ql
15
14
  s3_file = save file, key
16
15
  if dump && s3_file
17
16
  copy_key = key_for :latest
18
- s3_file.copy @config.bucket, copy_key
17
+ s3_file.copy_to key: copy_key
19
18
  log "s3: copied #{key} to #{copy_key}"
20
19
  end
21
20
  end
22
21
 
23
22
  def delete_bin_logs
24
23
  each_bin_log do |file|
25
- file.destroy
24
+ file.delete
26
25
  log "s3: deleted #{file.key}"
27
26
  end
28
27
  end
29
28
 
30
- def each_bin_log(&block)
31
- bucket.files.all(:prefix => "#{bin_logs_prefix}").sort_by { |file| file.key[/\d+/].to_i }.each do |file|
29
+ def each_bin_log(after = nil, &block)
30
+ if after && after !~ /^\d{6}$/
31
+ puts 'Binary log file number must be 6 digits.'
32
+ exit 1
33
+ end
34
+
35
+ bucket.objects(prefix: bin_logs_prefix)
36
+ .sort_by { |file| file.key[/\d+/].to_i }
37
+ .select { |file| after.nil? || (file.key[/\d+/].to_i > after.to_i) }
38
+ .each do |file|
32
39
  yield file
33
40
  end
34
41
  end
@@ -41,25 +48,28 @@ module Mys3ql
41
48
  private
42
49
 
43
50
  def get(s3_key, local_file_name)
44
- s3_file = bucket.files.get s3_key
45
- File.open(local_file_name, 'wb') do |file|
46
- file.write s3_file.body
47
- end
51
+ s3.get_object(
52
+ response_target: local_file_name,
53
+ bucket: @config.bucket,
54
+ key: s3_key
55
+ )
48
56
  log "s3: pulled #{s3_key} to #{local_file_name}"
49
57
  end
50
58
 
51
- # returns Fog::Storage::AWS::File if we pushed, nil otherwise.
52
59
  def save(local_file_name, s3_key)
53
- s3.sync_clock
54
- unless bucket.files.head(s3_key)
55
- s3_file = bucket.files.create(
56
- :key => s3_key,
57
- :body => File.open(local_file_name),
58
- :public => false
59
- )
60
- log "s3: pushed #{local_file_name} to #{s3_key}"
61
- s3_file
60
+ if bucket.object(s3_key).exists?
61
+ log "s3: skipped #{local_file_name} - already exists"
62
+ return
62
63
  end
64
+
65
+ s3_file = bucket.put_object(
66
+ key: s3_key,
67
+ body: File.open(local_file_name),
68
+ storage_class: 'STANDARD_IA',
69
+ acl: 'private'
70
+ )
71
+ log "s3: pushed #{local_file_name} to #{s3_key}"
72
+ s3_file
63
73
  end
64
74
 
65
75
  def key_for(kind, file = nil)
@@ -73,23 +83,22 @@ module Mys3ql
73
83
 
74
84
  def s3
75
85
  @s3 ||= begin
76
- s = Fog::Storage.new(
77
- :provider => 'AWS',
78
- :aws_secret_access_key => @config.secret_access_key,
79
- :aws_access_key_id => @config.access_key_id,
80
- :region => @config.region
86
+ client = Aws::S3::Client.new(
87
+ secret_access_key: @config.secret_access_key,
88
+ access_key_id: @config.access_key_id,
89
+ region: @config.region
81
90
  )
82
91
  log 's3: connected'
83
- s
92
+ client
84
93
  end
85
94
  end
86
95
 
87
96
  def bucket
88
- @directory ||= begin
89
- d = s3.directories.get @config.bucket
90
- raise "S3 bucket #{@config.bucket} not found" unless d # create bucket instead (n.b. region/location)?
97
+ @bucket ||= begin
98
+ b = Aws::S3::Bucket.new @config.bucket, client: s3
99
+ raise "S3 bucket #{@config.bucket} not found" unless b.exists?
91
100
  log "s3: opened bucket #{@config.bucket}"
92
- d
101
+ b
93
102
  end
94
103
  end
95
104
 
@@ -104,6 +113,5 @@ module Mys3ql
104
113
  def bin_logs_exist?
105
114
  @config.bin_log && @config.bin_log.length > 0 && File.exist?(@config.bin_log)
106
115
  end
107
-
108
116
  end
109
117
  end
data/lib/mys3ql/shell.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  module Mys3ql
2
- class ShellCommandError < RuntimeError ; end
2
+ ShellCommandError = Class.new RuntimeError
3
3
 
4
4
  module Shell
5
5
  def run(command)
@@ -1,3 +1,3 @@
1
1
  module Mys3ql
2
- VERSION = '1.0.2'
2
+ VERSION = '1.3.0'
3
3
  end
data/mys3ql.gemspec CHANGED
@@ -11,14 +11,11 @@ Gem::Specification.new do |s|
11
11
  s.summary = 'Simple backup of your MySql database onto Amazon S3.'
12
12
  s.description = s.summary
13
13
 
14
- s.rubyforge_project = "mys3ql"
15
-
16
14
  s.files = `git ls-files`.split("\n")
17
15
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
16
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
17
  s.require_paths = ["lib"]
20
18
 
21
- s.add_dependency 'main', '~> 4.8.0'
22
- s.add_dependency 'fog', '~> 1.19.0'
19
+ s.add_dependency 'aws-sdk-s3', '~> 1'
23
20
  s.add_development_dependency 'rake'
24
21
  end
metadata CHANGED
@@ -1,62 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mys3ql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
5
- prerelease:
4
+ version: 1.3.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Andy Stewart
9
- autorequire:
8
+ autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2014-01-07 00:00:00.000000000 Z
11
+ date: 2021-02-08 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
- name: main
14
+ name: aws-sdk-s3
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
- version: 4.8.0
19
+ version: '1'
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: 4.8.0
30
- - !ruby/object:Gem::Dependency
31
- name: fog
32
- requirement: !ruby/object:Gem::Requirement
33
- none: false
34
- requirements:
35
- - - ~>
36
- - !ruby/object:Gem::Version
37
- version: 1.19.0
38
- type: :runtime
39
- prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ~>
44
- - !ruby/object:Gem::Version
45
- version: 1.19.0
26
+ version: '1'
46
27
  - !ruby/object:Gem::Dependency
47
28
  name: rake
48
29
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
30
  requirements:
51
- - - ! '>='
31
+ - - ">="
52
32
  - !ruby/object:Gem::Version
53
33
  version: '0'
54
34
  type: :development
55
35
  prerelease: false
56
36
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
37
  requirements:
59
- - - ! '>='
38
+ - - ">="
60
39
  - !ruby/object:Gem::Version
61
40
  version: '0'
62
41
  description: Simple backup of your MySql database onto Amazon S3.
@@ -67,8 +46,7 @@ executables:
67
46
  extensions: []
68
47
  extra_rdoc_files: []
69
48
  files:
70
- - .gitignore
71
- - CHANGELOG.md
49
+ - ".gitignore"
72
50
  - Gemfile
73
51
  - README.md
74
52
  - Rakefile
@@ -83,32 +61,25 @@ files:
83
61
  - mys3ql.gemspec
84
62
  homepage: https://github.com/airblade/mys3ql
85
63
  licenses: []
86
- post_install_message:
64
+ metadata: {}
65
+ post_install_message:
87
66
  rdoc_options: []
88
67
  require_paths:
89
68
  - lib
90
69
  required_ruby_version: !ruby/object:Gem::Requirement
91
- none: false
92
70
  requirements:
93
- - - ! '>='
71
+ - - ">="
94
72
  - !ruby/object:Gem::Version
95
73
  version: '0'
96
- segments:
97
- - 0
98
- hash: 271295663072223621
99
74
  required_rubygems_version: !ruby/object:Gem::Requirement
100
- none: false
101
75
  requirements:
102
- - - ! '>='
76
+ - - ">="
103
77
  - !ruby/object:Gem::Version
104
78
  version: '0'
105
- segments:
106
- - 0
107
- hash: 271295663072223621
108
79
  requirements: []
109
- rubyforge_project: mys3ql
110
- rubygems_version: 1.8.23
111
- signing_key:
112
- specification_version: 3
80
+ rubyforge_project:
81
+ rubygems_version: 2.5.2.3
82
+ signing_key:
83
+ specification_version: 4
113
84
  summary: Simple backup of your MySql database onto Amazon S3.
114
85
  test_files: []
data/CHANGELOG.md DELETED
@@ -1,3 +0,0 @@
1
- # Changelog
2
-
3
- [awaiting v1.0.0]