solid_backup 0.2.2 → 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
2
  SHA256:
3
- metadata.gz: '09416cd05590f787149f5de85fd0094cb124edeebd4a86a41bc943c1c3ea40bb'
4
- data.tar.gz: 31565779742aef8295c71134d01f0f78eaf578950800ca7d4f62f612512eafc6
3
+ metadata.gz: dd198b0749b8c0ec8bbbf9371c95c95e3bf7967b3c76353b6bc8af310acbd160
4
+ data.tar.gz: 6c22706156961bcf44051d1023f6e5b2c50e4e02607a8b87f0dc6b774053963c
5
5
  SHA512:
6
- metadata.gz: 427730d383e49058a091bc569950f8ea857aa3d135d6b5a2815aad687662a09fa7dcf3a4a3031e0ffaa7975f4a3920c09666244fa7a6a55aa8ae52e01d400469
7
- data.tar.gz: 94306f1e4b34f84e2fa516d905187c6b74290dd33f7476704cdd6d8040514b91aaa49859c0778b2fd7fbeacd5c7fe4671ab22a3f0d9163ef96b88df87a580709
6
+ metadata.gz: 2fdd803df8a709d66657b09bec4b49f27a94cc593a95ad27bb82daa1dd812391fb0e71a755124cdb3833c24fd10086b135b66f26665440c4a3d258c39110973d
7
+ data.tar.gz: 40649b13dfeacc3fc4db7c159ae017b0d2034e7075883ce4ad4bb0f1dc48b51ca4db9b62459898134477dede39c653590f095565aa587f4f3807cd52926aad03
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Solid Backup
2
2
 
3
+ ## 0.4.0
4
+
5
+ - Add backup compression support via the `compressor:` option; currently
6
+ `SolidBackup::Compressor::Gzip` is the only supported value.
7
+
8
+ ## 0.3.0
9
+
10
+ - `destination` is the directory where backup files will be placed, not a
11
+ %-encoded template.
12
+ - New `expiration:` option can be set to determine backup expiration policy;
13
+ currently only `SolidBackup::Expiration::Age.new(maximum: ...)` and
14
+ `SolidBackup::Expiration::None.new` are supported.
15
+
3
16
  ## 0.2.2
4
17
 
5
18
  - Don't call `SolidBackup` too early to avoid errors caused by Rails not being
data/README.md CHANGED
@@ -28,11 +28,14 @@ will lack backups due to omission. There are three database backup modes:
28
28
 
29
29
  Modes other than `SolidBackup::Backup::None` take the following parameters:
30
30
 
31
- - `destination` - a `strftime` template used to generate backup file names; the
32
- directory under which the files are placed must already exist
33
- and be writeable.
31
+ - `destination` - the directory where backup files will be held; it must already
32
+ exist and be writeable.
34
33
  - `interval_in_minutes` - the number of minutes to the beginning of the next
35
34
  backup after the previous one finished.
35
+ - `expiration` - backup expiration policy; expired backups are automatically
36
+ removed.
37
+ - `compressor` - backup compression policy; `nil` be default which turns
38
+ compression off, but can be set to SolidBackup::Compressor::Gzip.
36
39
 
37
40
  Additionally, `SolidBackup::Backup::API` takes the following parameters:
38
41
 
@@ -52,10 +55,22 @@ SolidBackup.configure do |config|
52
55
  # Back up the primary database using the SQLite backup API.
53
56
  production.backup "primary",
54
57
  SolidBackup::Backup::API,
55
- destination: "storage/backups/primary_%Y%m%dT%H%M%S.sqlite3",
56
- interval_in_minutes: 15, # Start the next backup 15 minutes after the previous one.
57
- step: 100, # Back up in 100-page increments.
58
- wait: 0.1 # Wait 0.1 seconds if a lock interrupts a backup step.
58
+ destination: "storage/backups",
59
+
60
+ # Start the next backup 15 minutes after the previous one.
61
+ interval_in_minutes: 15,
62
+
63
+ # Back up in 100-page increments.
64
+ step: 100,
65
+
66
+ # Wait 0.1 seconds if a lock interrupts a backup step.
67
+ wait: 0.1,
68
+
69
+ # Remove backups older than 30 days.
70
+ expiration: SolidBackup::Expiration::Age.new(maximum: 30.days),
71
+
72
+ # Compress backups using gzip.
73
+ compressor: SolidBackup::Compressor::Gzip
59
74
 
60
75
  # Don't back up the cache database.
61
76
  production.backup "cache", SolidBackup::Backup::None
@@ -63,8 +78,16 @@ SolidBackup.configure do |config|
63
78
  # Back up the queue database using VACUUM INTO.
64
79
  production.backup "queue",
65
80
  SolidBackup::Backup::VacuumInto,
66
- destination: "storage/backups/queue_%Y%m%dT%H%M%S.sqlite3",
67
- interval_in_minutes: 60 # Start the next backup 60 minutes after the previous one.
81
+ destination: "storage/backups",
82
+
83
+ # Start the next backup 60 minutes after the previous one.
84
+ interval_in_minutes: 60,
85
+
86
+ # Remove backups older than 12 hours.
87
+ expiration: SolidBackup::Expiration::Age.new(maximum: 12.hours),
88
+
89
+ # Don't compress backups. This is the default setting.
90
+ compressor: nil
68
91
  end
69
92
  end
70
93
  ```
@@ -13,9 +13,9 @@ class SolidBackup::Backup::API < SolidBackup::Backup
13
13
  def do_perform(backup_path)
14
14
  with_connection do |connection|
15
15
  source = connection.raw_connection
16
- destination = SQLite3::Database.new(backup_path)
16
+ target = SQLite3::Database.new(backup_path)
17
17
 
18
- backup = SQLite3::Backup.new(destination, "main", source, "main")
18
+ backup = SQLite3::Backup.new(target, "main", source, "main")
19
19
 
20
20
  loop do
21
21
  result = backup.step(step)
@@ -32,7 +32,7 @@ class SolidBackup::Backup::API < SolidBackup::Backup
32
32
  end
33
33
 
34
34
  backup.finish
35
- destination.close
35
+ target.close
36
36
  end
37
37
  end
38
38
  end
@@ -7,19 +7,19 @@
7
7
  # - destination and interval_in_minutes - to allow nil values
8
8
  class SolidBackup::Backup::None < SolidBackup::Backup
9
9
  def initialize(database:)
10
- super(database:, destination: nil, interval_in_minutes: 0)
10
+ super(database:, destination: nil, interval_in_minutes: 0, expiration: nil, compressor: nil)
11
11
  end
12
12
 
13
13
  def tick
14
14
  end
15
15
 
16
- def perform
17
- end
18
-
19
16
  private
20
17
 
21
18
  attr_writer :destination, :interval_in_minutes
22
19
 
20
+ def perform
21
+ end
22
+
23
23
  def do_perform(backup_path)
24
24
  end
25
25
  end
@@ -4,10 +4,12 @@ module SolidBackup
4
4
  class Backup
5
5
  attr_reader :database
6
6
 
7
- def initialize(database:, destination:, interval_in_minutes:)
7
+ def initialize(database:, destination:, interval_in_minutes:, expiration:, compressor:)
8
8
  self.database = database
9
9
  self.destination = destination
10
10
  self.interval_in_minutes = interval_in_minutes
11
+ self.expiration = expiration
12
+ self.compressor = compressor
11
13
 
12
14
  reset_countdown
13
15
  end
@@ -19,23 +21,40 @@ module SolidBackup
19
21
 
20
22
  if countdown <= 0
21
23
  perform
24
+ remove_expired_backups
22
25
  reset_countdown
23
26
  end
24
27
  end
25
28
 
29
+ private
30
+
31
+ attr_accessor :countdown, :expiration, :compressor
32
+ attr_writer :database
33
+ attr_reader :destination, :interval_in_minutes
34
+
26
35
  def perform
27
36
  ActiveSupport::Notifications.instrument("backup.solid_backup", database:) do
28
37
  lock do
29
- do_perform(Time.now.utc.strftime(destination.to_s))
38
+ backup_path = Pathname(Time.now.utc.strftime((destination / file_name_pattern).to_s))
39
+ do_perform(backup_path)
40
+
41
+ next if compressor.nil?
42
+
43
+ compressor.compress(backup_path)
44
+ backup_path.unlink
30
45
  end
31
46
  end
32
47
  end
33
48
 
34
- private
35
-
36
- attr_accessor :countdown
37
- attr_writer :database
38
- attr_reader :destination, :interval_in_minutes
49
+ def remove_expired_backups
50
+ files = destination.children.select(&:file?)
51
+ expiration.select_expired(files) do |pathname|
52
+ Time.strptime(pathname.basename.to_s, file_name_pattern)
53
+ rescue ArgumentError
54
+ end.each do |pathname|
55
+ pathname.unlink
56
+ end
57
+ end
39
58
 
40
59
  def interval_in_minutes=(value)
41
60
  if !value.is_a?(Integer) || value <= 0
@@ -49,17 +68,16 @@ module SolidBackup
49
68
 
50
69
  def destination=(value)
51
70
  value = Pathname(value)
52
- directory = value.dirname
53
71
 
54
- if !directory.directory?
55
- raise "invalid backup configuration for #{database}: destination directory #{directory} must exist"
72
+ if !value.directory?
73
+ raise "invalid backup configuration for #{database}: destination directory #{value} must exist"
56
74
  end
57
75
 
58
- test_path = directory / "solid_backup.txt"
76
+ test_path = value / "solid_backup.txt"
59
77
  begin
60
78
  test_path.write("Solid Backup Test")
61
79
  rescue Errno::EACCES
62
- raise "invalid backup configuration for #{database}: destination directory #{directory} must be writeable"
80
+ raise "invalid backup configuration for #{database}: destination directory #{value} must be writeable"
63
81
  ensure
64
82
  test_path.unlink
65
83
  end
@@ -79,10 +97,14 @@ module SolidBackup
79
97
  ActiveRecord::Base.connection_handler.establish_connection(database.to_sym)
80
98
  end
81
99
 
100
+ def file_name_pattern
101
+ "#{database}_%Y%m%dT%H%M%S%Z.sqlite3"
102
+ end
103
+
82
104
  delegate :with_connection, to: :connection_pool
83
105
 
84
106
  def lock
85
- path = destination.dirname / ".solid_backup.#{database}.lock"
107
+ path = destination / ".solid_backup.#{database}.lock"
86
108
 
87
109
  File.open(path, File::RDWR | File::CREAT) do |f|
88
110
  if f.flock(File::LOCK_EX | File::LOCK_NB)
@@ -0,0 +1,14 @@
1
+ module SolidBackup::Compressor
2
+ module Gzip
3
+ def self.compress(source_path)
4
+ target_path = "#{source_path}.gz"
5
+ source_path = Pathname(source_path)
6
+
7
+ Zlib::GzipWriter.open(target_path) do |gzip|
8
+ gzip.mtime = source_path.mtime
9
+ gzip.orig_name = source_path.basename.to_s
10
+ gzip.write(source_path.binread)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,25 @@
1
+ module SolidBackup::Expiration
2
+ class None
3
+ def select_expired(pathnames)
4
+ []
5
+ end
6
+ end
7
+
8
+ class Age
9
+ def initialize(maximum:)
10
+ self.maximum = maximum
11
+ end
12
+
13
+ def select_expired(pathnames)
14
+ threshold = maximum.ago.utc
15
+ pathnames.select do |pathname|
16
+ timestamp = yield(pathname)
17
+ timestamp && timestamp < threshold
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_accessor :maximum
24
+ end
25
+ end
@@ -15,8 +15,10 @@ module SolidBackup
15
15
  <<~CODE.chomp
16
16
  production.backup #{db_config.name.inspect},
17
17
  SolidBackup::Backup::API,
18
- destination: "storage/backups/#{db_config.name}_%Y%m%dT%H%M%S.sqlite3",
18
+ destination: "storage/backups",
19
19
  interval_in_minutes: 60,
20
+ expiration: SolidBackup::Expiration::Age.new(maximum: 24.hours),
21
+ compressor: SolidBackup::Compressor::Gzip,
20
22
  step: 100,
21
23
  wait: 0.1
22
24
  CODE
@@ -1,3 +1,3 @@
1
1
  module SolidBackup
2
- VERSION = "0.2.2"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/solid_backup.rb CHANGED
@@ -6,6 +6,8 @@ require "solid_backup/backup/api"
6
6
  require "solid_backup/backup/vacuum_into"
7
7
  require "solid_backup/configuration"
8
8
  require "solid_backup/configuration/environment"
9
+ require "solid_backup/expiration"
10
+ require "solid_backup/compressor"
9
11
  require "solid_backup/version"
10
12
  require "solid_backup/railtie"
11
13
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Navis
@@ -108,8 +108,10 @@ files:
108
108
  - lib/solid_backup/backup/api.rb
109
109
  - lib/solid_backup/backup/none.rb
110
110
  - lib/solid_backup/backup/vacuum_into.rb
111
+ - lib/solid_backup/compressor.rb
111
112
  - lib/solid_backup/configuration.rb
112
113
  - lib/solid_backup/configuration/environment.rb
114
+ - lib/solid_backup/expiration.rb
113
115
  - lib/solid_backup/generators/install_generator.rb
114
116
  - lib/solid_backup/railtie.rb
115
117
  - lib/solid_backup/version.rb
@@ -134,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
136
  - !ruby/object:Gem::Version
135
137
  version: '0'
136
138
  requirements: []
137
- rubygems_version: 4.0.10
139
+ rubygems_version: 4.0.20
138
140
  specification_version: 4
139
141
  summary: Solid Backup provides simple backups for SQLite databases in Ruby on Rails
140
142
  apps.