backupper 0.3.0 → 0.4.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
- SHA1:
3
- metadata.gz: 433774005bc1b3d59b575acf5878d1bb9f202d5f
4
- data.tar.gz: 7efb646234fa7392394ce2980728961b13ae196c
2
+ SHA256:
3
+ metadata.gz: e342e000d1463ab2e5acda75380d51b4234e15c10bc9926de642657ffadbbe02
4
+ data.tar.gz: c5b08c11facbf564daaf375ccff26e0e8ba4aa59b5d03a0981dc8f69ea5d5463
5
5
  SHA512:
6
- metadata.gz: 0f97d4c32a27d19ab7925b6b868acda6325954cfa4870e3a795efd0a96c32930d2ee2258baa95df4fc0ee50aa5a88b9ab4d4d58ad2c9b36e44ed1137c456dbea
7
- data.tar.gz: 3439ec0927796ac074a51b51f68d32787c0992622e9dc9c46f38066f55f497bf59a1a15c257d910f5d1411465ecbd84814a530f6f71869cbbd17d6c5965ccaf3
6
+ metadata.gz: c2c543a27f6f781700acded008b3629a19507da9b10b3851cf513edceac10bcaf8e8efdb771dfee04ae2ee208ba966c5134b03e71002df6d65d3851cbc658a26
7
+ data.tar.gz: caccc5dd3ec837854f29efa9e94f11a02543d67c4f6e659f9a5d037760ffe3e5eed577d39eed86151d9e9fb69e5d7ca93d5aaf416adc58386b82dcc49e280269
data/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  /.bundle/
2
+ /.byebug_history
2
3
  /.yardoc
3
4
  /_yardoc/
4
5
  /coverage/
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
- git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
3
+ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
4
4
 
5
5
  # Specify your gem's dependencies in backupper.gemspec
6
6
  gemspec
@@ -0,0 +1,34 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ backupper (0.4.0)
5
+ mail (~> 2.7)
6
+ sshkit (~> 1.15)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ byebug (11.0.1)
12
+ mail (2.7.1)
13
+ mini_mime (>= 0.1.1)
14
+ mini_mime (1.0.2)
15
+ net-scp (2.0.0)
16
+ net-ssh (>= 2.6.5, < 6.0.0)
17
+ net-ssh (5.2.0)
18
+ rake (10.5.0)
19
+ sshkit (1.20.0)
20
+ net-scp (>= 1.1.2)
21
+ net-ssh (>= 2.8.0)
22
+
23
+ PLATFORMS
24
+ ruby
25
+ x86_64-darwin-18
26
+
27
+ DEPENDENCIES
28
+ backupper!
29
+ bundler
30
+ byebug
31
+ rake (~> 10.0)
32
+
33
+ BUNDLED WITH
34
+ 2.0.2
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Backupper
2
2
 
3
- Backupper is a tool to backup all your databases spread in the world!
3
+ Backupper is a tool to backup all your databases spread all over the world!
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,7 +8,7 @@ Backupper is a tool to backup all your databases spread in the world!
8
8
 
9
9
  ## Usage
10
10
 
11
- $ backupper path/to/config.yml
11
+ $ backupper path/to/config.yml [options]
12
12
 
13
13
  ## Configuration file
14
14
 
@@ -52,6 +52,12 @@ After done all backups a report email is sent to you using gmail smtp service (r
52
52
 
53
53
  ⚠️ __WARNING__: the backupper configuration file contains many important passwords, so be careful to lock it and protect it with care!
54
54
 
55
+ To backup only specific databases you can pass the option `-o db1,db3`. In this way only databases under the key `db1` and `db3` will be backupped,
56
+ ignoring the others configuration keys.
57
+
58
+ ## Resources
59
+ * [Blog post about Backupper](https://tech.uqido.com/2018/07/16/backupper-tool-backup-database/) (italian only).
60
+
55
61
  ## Contributing
56
62
 
57
63
  Bug reports and pull requests are welcome on GitHub at https://github.com/uqido/backupper.
@@ -67,4 +73,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
67
73
  Backupper is maintained and funded by [Uqido](https://uqido.com).
68
74
  The names and logos for Uqido are trademarks of Uqido s.r.l.
69
75
 
70
- The [Uqido team](https://www.uqido.com/en/about-us/).
76
+ The [Uqido team](https://www.uqido.com/en/about-us/).
data/Rakefile CHANGED
@@ -1,2 +1,2 @@
1
- require "bundler/gem_tasks"
2
- task :default => :spec
1
+ require 'bundler/gem_tasks'
2
+ task default: :spec
@@ -1,5 +1,4 @@
1
-
2
- lib = File.expand_path('../lib', __FILE__)
1
+ lib = File.expand_path('lib', __dir__)
3
2
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
3
  require 'backupper/version'
5
4
 
@@ -9,8 +8,8 @@ Gem::Specification.new do |spec|
9
8
  spec.authors = ['pioz']
10
9
  spec.email = ['enrico.pilotto@uqido.com']
11
10
 
12
- spec.summary = %q{Tool to backup databases}
13
- spec.description = %q{Backupper is a tool to backup all your databases spread in the world}
11
+ spec.summary = 'Tool to backup databases'
12
+ spec.description = 'Backupper is a tool to backup all your databases spread all over the world'
14
13
  spec.homepage = 'https://github.com/uqido/backupper'
15
14
  spec.license = 'MIT'
16
15
 
@@ -22,16 +21,17 @@ Gem::Specification.new do |spec|
22
21
  raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
23
22
  end
24
23
 
25
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
- f.match(%r{^(test|spec|features)/})
24
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match(/^(test|spec|features)\//)
27
26
  end
28
- spec.bindir = "bin"
29
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
30
- spec.require_paths = ["lib"]
27
+ spec.bindir = 'bin'
28
+ spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
31
30
 
32
- spec.add_runtime_dependency 'sshkit', '~> 1.15'
33
31
  spec.add_runtime_dependency 'mail', '~> 2.7'
32
+ spec.add_runtime_dependency 'sshkit', '~> 1.15'
34
33
 
35
- spec.add_development_dependency 'bundler', '~> 1.16'
34
+ spec.add_development_dependency 'bundler'
35
+ spec.add_development_dependency 'byebug'
36
36
  spec.add_development_dependency 'rake', '~> 10.0'
37
37
  end
@@ -1,11 +1,34 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'backupper'
3
+ require 'optparse'
4
4
 
5
- if ARGV.size != 1
6
- puts "usage: #{$0} path/to/config.yml"
5
+ require_relative '../lib/backupper'
6
+
7
+ options = {}
8
+ parser = OptionParser.new do |opts|
9
+ opts.banner = "usage: #{$PROGRAM_NAME} path/to/config.yml [options]"
10
+
11
+ opts.on('-v', 'Print version') do |v|
12
+ options[:version] = v
13
+ end
14
+
15
+ opts.on('-o', '--only=conf-key1,conf-key2', Array, 'Backup only specific database configurations') do |keys|
16
+ options[:only] = keys
17
+ end
18
+ end
19
+
20
+ begin
21
+ parser.parse!
22
+ raise OptionParser::InvalidOption if ARGV.size != 1
23
+ rescue OptionParser::InvalidOption
24
+ puts parser.banner
7
25
  exit 1
8
26
  end
9
27
 
28
+ if options[:version]
29
+ puts Backupper::VERSION
30
+ exit 0
31
+ end
32
+
10
33
  b = Backupper.new(ARGV.first)
11
- b.backup!
34
+ b.backup!(options[:only])
@@ -1,2 +1,2 @@
1
- require 'backupper/backupper'
2
- require 'backupper/version'
1
+ require_relative 'backupper/backupper'
2
+ require_relative 'backupper/version'
@@ -1,17 +1,18 @@
1
- require 'yaml'
2
1
  require 'fileutils'
3
2
  require 'sshkit'
4
3
  require 'sshkit/dsl'
5
- require 'backupper/dump_command'
6
- require 'backupper/mailer'
7
- include SSHKit::DSL
4
+ require 'yaml'
5
+
6
+ require_relative 'dump_command'
7
+ require_relative 'mailer'
8
8
 
9
9
  class Backupper
10
+ include SSHKit::DSL
10
11
 
11
12
  SSHKit::Backend::Netssh.configure do |ssh|
12
13
  ssh.connection_timeout = 30
13
14
  ssh.ssh_options = {
14
- auth_methods: %w(publickey password)
15
+ auth_methods: %w[publickey password]
15
16
  }
16
17
  end
17
18
 
@@ -19,17 +20,20 @@ class Backupper
19
20
  conf = YAML.load_file(conf_file_path)
20
21
  @default = conf['default'] || {}
21
22
  @mailer = conf['mailer'] || {}
22
- @conf = conf.select{|k, v| !%w(default mailer).include?(k) && (v['disabled'].nil? || v['disabled'] == false)}
23
+ @conf = conf.select { |k, v| !%w[default mailer].include?(k) && (v['disabled'].nil? || v['disabled'] == false) }
23
24
  @report = {}
25
+ @logger = SSHKit::Formatter::Pretty.new($stdout)
24
26
  end
25
27
 
26
- def backup!
27
- @conf.each do |k, options|
28
- puts "⬇️ backing up #{k}..."
28
+ def backup!(only_these_keys = nil)
29
+ filtered_conf = @conf
30
+ filtered_conf = @conf.slice(*only_these_keys) if only_these_keys
31
+ filtered_conf.each do |k, options|
32
+ @logger.info "[#{Time.now}] ⬇️ backing up #{k}..."
29
33
  o, err = setup_options(options)
30
34
  if err
31
35
  error(k, err)
32
- puts err
36
+ @logger.error err
33
37
  next
34
38
  end
35
39
  begin
@@ -47,7 +51,7 @@ class Backupper
47
51
  )
48
52
  rescue SSHKit::Runner::ExecuteError => e
49
53
  error(k, e.to_s)
50
- puts e
54
+ @logger.error e
51
55
  end
52
56
  end
53
57
  send_report_email!
@@ -73,58 +77,55 @@ class Backupper
73
77
  size: (File.size(path).to_f / 2**20).round(2),
74
78
  time: (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t).round(2)
75
79
  }
76
- @report[key].merge!({extra_copy: File.absolute_path(File.join(extra_copy, dumpname))}) if extra_copy
80
+ @report[key].merge!({ extra_copy: File.absolute_path(File.join(extra_copy, dumpname)) }) if extra_copy
77
81
  end
78
82
 
79
83
  private
80
84
 
81
- def setup_options(options)
82
- o = @default.merge(options)
83
- o['outdir'] = check_dir(o['dump'].to_s)
84
- unless o['outdir']
85
- return nil, 'Invalid directory where to save database dump'
86
- end
87
- unless o['database']
88
- return nil, 'Please specify the database name!'
89
- end
90
- unless o['host']
91
- return nil, 'Please specify the host!'
92
- end
93
- o['url'] = o['host']
94
- o['url'] = "#{o['username']}@#{o['url']}" if o['username']
95
- o['url'] = "#{o['url']}:#{o['port']}" if o['port']
96
- o['adapter'] ||= 'mysql'
97
- unless DumpCommand.respond_to?(o['adapter'])
98
- return nil, "Cannot handle adapter '#{o['adapter']}'"
85
+ def setup_options(options)
86
+ o = @default.merge(options)
87
+ o['outdir'] = check_dir(o['dump'].to_s)
88
+ return nil, 'Invalid directory where to save database dump' unless o['outdir']
89
+ return nil, 'Please specify the database name!' unless o['database']
90
+ return nil, 'Please specify the host!' unless o['host']
91
+
92
+ o['url'] = o['host']
93
+ o['url'] = "#{o['username']}@#{o['url']}" if o['username']
94
+ o['url'] = "#{o['url']}:#{o['port']}" if o['port']
95
+ o['adapter'] ||= 'mysql'
96
+ unless DumpCommand.respond_to?(o['adapter'])
97
+ return nil, "Cannot handle adapter '#{o['adapter']}'"
98
+ end
99
+
100
+ return o, nil
99
101
  end
100
- return o, nil
101
- end
102
102
 
103
- def send_report_email!
104
- if @report.any? && @mailer['from'] && @mailer['to'] && @mailer['password']
105
- begin
106
- Mailer.send(from: @mailer['from'], to: @mailer['to'], password: @mailer['password'], report: @report)
107
- rescue Net::SMTPAuthenticationError => e
108
- puts e
103
+ def send_report_email!
104
+ if @report.any? && @mailer['from'] && @mailer['to'] && @mailer['password']
105
+ begin
106
+ Mailer.send(from: @mailer['from'], to: @mailer['to'], password: @mailer['password'], report: @report)
107
+ rescue Net::SMTPAuthenticationError => e
108
+ @logger.error e
109
+ end
109
110
  end
110
111
  end
111
- end
112
112
 
113
- def error(key, error)
114
- @report[key] = {error: error}
115
- end
113
+ def error(key, error)
114
+ @report[key] = { error: error }
115
+ end
116
116
 
117
- def check_dir(dirpath)
118
- return nil if dirpath.nil?
119
- unless File.exists?(dirpath)
120
- begin
121
- FileUtils::mkdir_p(dirpath)
122
- rescue => e
123
- return nil
117
+ def check_dir(dirpath)
118
+ return nil if dirpath.nil?
119
+
120
+ unless File.exist?(dirpath)
121
+ begin
122
+ FileUtils.mkdir_p(dirpath)
123
+ rescue StandardError => _e
124
+ return nil
125
+ end
124
126
  end
125
- end
126
- return File.dirname(dirpath) unless File.directory?(dirpath)
127
- return dirpath
128
- end
127
+ return File.dirname(dirpath) unless File.directory?(dirpath)
129
128
 
130
- end
129
+ return dirpath
130
+ end
131
+ end
@@ -1,5 +1,4 @@
1
1
  module DumpCommand
2
-
3
2
  def self.mysql(database:, username: 'root', password: nil, dump_options: nil, outfile:)
4
3
  params = []
5
4
  params << "--databases '#{database}'"
@@ -16,5 +15,4 @@ module DumpCommand
16
15
  params << dump_options if dump_options
17
16
  return "PGPASSWORD=#{password} pg_dump #{params.join(' ')} | bzip2 > '#{outfile}'"
18
17
  end
19
-
20
- end
18
+ end
@@ -1,7 +1,6 @@
1
1
  require 'mail'
2
2
 
3
3
  class Mailer
4
-
5
4
  def self.send(from:, to:, password:, report:)
6
5
  options = {
7
6
  address: 'smtp.gmail.com',
@@ -17,47 +16,47 @@ class Mailer
17
16
  Mail.deliver do
18
17
  to to
19
18
  from from
20
- subject Mailer.subject(report)
21
- body Mailer.body(report)
19
+ subject self.subject(report)
20
+ body self.body(report)
22
21
  end
23
22
  end
24
23
 
25
- private
24
+ class << self
25
+ private
26
26
 
27
- def self.body(report)
28
- b = []
29
- report.each do |k, data|
30
- s = ''
31
- if data[:error]
32
- s << "❌ #{k}\n"
33
- s << '=' * 80 << "\n"
34
- s << "Backup FAILED!\n"
35
- s << " error: #{data[:error]}\n"
36
- b << s
37
- else
38
- s << "️✅ #{k}\n"
39
- s << '=' * 80 << "\n"
40
- s << "Backup SUCCESS!\n"
41
- s << " dump size: #{data[:size]} MB\n"
42
- s << " time: #{data[:time]} seconds\n"
43
- s << " dump saved in: #{data[:path]}\n"
44
- if data[:extra_copy]
45
- s << " extra copy in: #{data[:extra_copy]}\n"
46
- else
47
- s << " no extra copy has been made\n"
27
+ def body(report)
28
+ b = []
29
+ report.each do |k, data|
30
+ s = ''
31
+ if data[:error]
32
+ s << "❌ #{k}\n"
33
+ s << '=' * 80 << "\n"
34
+ s << "Backup FAILED!\n"
35
+ s << " error: #{data[:error]}\n"
36
+ else
37
+ s << "️✅ #{k}\n"
38
+ s << '=' * 80 << "\n"
39
+ s << "Backup SUCCESS!\n"
40
+ s << " dump size: #{data[:size]} MB\n"
41
+ s << " time: #{data[:time]} seconds\n"
42
+ s << " dump saved in: #{data[:path]}\n"
43
+ if data[:extra_copy]
44
+ s << " extra copy in: #{data[:extra_copy]}\n"
45
+ else
46
+ s << " no extra copy has been made\n"
47
+ end
48
+ end
49
+ b << s
48
50
  end
49
- b << s
50
- end
51
- end
52
- return "Report for backups (#{Time.now})\n\n#{b.join("\n\n")}"
53
- end
51
+ return "Report for backups (#{Time.now})\n\n#{b.join("\n\n")}"
52
+ end
54
53
 
55
- def self.subject(report)
56
- errors = report.select{|k, v| v[:error]}.size
57
- icon = '✅'
58
- icon = '⚠️' if errors > 0
59
- icon = '❌' if errors == report.size
60
- return "[Backupper] #{report.size-errors}/#{report.size} backups successfully completed #{icon}"
54
+ def subject(report)
55
+ errors = report.select { |_k, v| v[:error] }.size
56
+ icon = '✅'
57
+ icon = '⚠️' if errors > 0
58
+ icon = '❌' if errors == report.size
59
+ return "[Backupper] #{report.size - errors}/#{report.size} backups successfully completed #{icon}"
60
+ end
61
61
  end
62
-
63
- end
62
+ end
@@ -1,3 +1,3 @@
1
1
  class Backupper
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,57 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backupper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - pioz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-04 00:00:00.000000000 Z
11
+ date: 2019-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: sshkit
14
+ name: mail
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.15'
19
+ version: '2.7'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '1.15'
26
+ version: '2.7'
27
27
  - !ruby/object:Gem::Dependency
28
- name: mail
28
+ name: sshkit
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.7'
33
+ version: '1.15'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '2.7'
40
+ version: '1.15'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '1.16'
47
+ version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
53
67
  - !ruby/object:Gem::Version
54
- version: '1.16'
68
+ version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
70
  name: rake
57
71
  requirement: !ruby/object:Gem::Requirement
@@ -66,7 +80,8 @@ dependencies:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
82
  version: '10.0'
69
- description: Backupper is a tool to backup all your databases spread in the world
83
+ description: Backupper is a tool to backup all your databases spread all over the
84
+ world
70
85
  email:
71
86
  - enrico.pilotto@uqido.com
72
87
  executables:
@@ -76,6 +91,7 @@ extra_rdoc_files: []
76
91
  files:
77
92
  - ".gitignore"
78
93
  - Gemfile
94
+ - Gemfile.lock
79
95
  - LICENSE.txt
80
96
  - README.md
81
97
  - Rakefile
@@ -106,8 +122,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
122
  - !ruby/object:Gem::Version
107
123
  version: '0'
108
124
  requirements: []
109
- rubyforge_project:
110
- rubygems_version: 2.6.13
125
+ rubygems_version: 3.0.2
111
126
  signing_key:
112
127
  specification_version: 4
113
128
  summary: Tool to backup databases