solid_backup 0.1.0 → 0.2.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: ee957011ccc278fb06096635c8b158939f283538bb5ccc25869ba07d3bb7bb5a
4
- data.tar.gz: ea9cdbdbd9e9ae3ae0fdb4b252b9db1c412e80d3288258c8ccf4062e59a70b66
3
+ metadata.gz: ee0e8ba44bfedbddb05671e68d06bdfc8e9e88f1d79ab38678cd15ce8023aba8
4
+ data.tar.gz: 3f1b4ec2ea4fa5b28528e398db334f9f987c34458f572ea3c2a36f5c3ebd45bd
5
5
  SHA512:
6
- metadata.gz: '0802038b0aeac7c0b0bebbf1b8b6d68054b52621ef1c50c9c21f1a640866fb2f1e51ee9a3d98bc6fef80a5b63ab85c4ed9cec500e7ba86ddc24bf45fe65d3c33'
7
- data.tar.gz: 82bec8bad5efe105b5ad7247b6f0754fda99381baafdcaff36704c249a36996c86bf0d1e65e1d4cd81a896a03513cc631a0c4540dc6b501115b5b3134f0936c5
6
+ metadata.gz: 3b0025209e6f305cff67b6d3fb19e86a371541c299cd38ea092a8252ed4635c62b072b64f5e6221affa1028cf222b038c634c3f496b9a18384db34eb52cf37f6
7
+ data.tar.gz: 020ef132f3024278de2e68a440cfd5c613f1f55397525164da15a4fdb2d27307606010f7f06ea830751d1229016432b98fce7d9d0b31119bdd46e6f4cfdf8295
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Solid Backup
2
+
3
+ ## 0.2.0
4
+
5
+ - Support for per-environment backup configuration.
6
+
7
+ ## 0.1.0
8
+
9
+ - Initial release.
data/README.md CHANGED
@@ -44,22 +44,28 @@ mode in practice:
44
44
 
45
45
  ```ruby
46
46
  SolidBackup.configure do |config|
47
- # Back up the primary database using the SQLite backup API.
48
- config.backup "primary",
49
- SolidBackup::Backup::API,
50
- destination: "storage/backups/primary_%Y%m%dT%H%M%S.sqlite3",
51
- interval_in_minutes: 15, # Start the next backup 15 minutes after the previous one.
52
- step: 100, # Back up in 100-page increments.
53
- wait: 0.1 # Wait 0.1 seconds if a lock interrupts a backup step.
54
-
55
- # Don't back up the cache database.
56
- config.backup "cache", SolidBackup::Backup::None
57
-
58
- # Back up the queue database using VACUUM INTO.
59
- config.backup "queue",
60
- SolidBackup::Backup::VacuumInto,
61
- destination: "storage/backups/queue_%Y%m%dT%H%M%S.sqlite3",
62
- interval_in_minutes: 60 # Start the next backup 60 minutes after the previous one.
47
+ config.enabled = true
48
+
49
+ # Configure backups for all production databases. Other environments can have
50
+ # backups configured for them via additional config.environment blocks.
51
+ config.environment("production") do |production|
52
+ # Back up the primary database using the SQLite backup API.
53
+ production.backup "primary",
54
+ 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.
59
+
60
+ # Don't back up the cache database.
61
+ production.backup "cache", SolidBackup::Backup::None
62
+
63
+ # Back up the queue database using VACUUM INTO.
64
+ production.backup "queue",
65
+ 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.
68
+ end
63
69
  end
64
70
  ```
65
71
 
@@ -6,8 +6,8 @@
6
6
  # - perform - to avoid creating lock files and emitting instrumentation events
7
7
  # - destination and interval_in_minutes - to allow nil values
8
8
  class SolidBackup::Backup::None < SolidBackup::Backup
9
- def initialize(name:)
10
- super(name:, destination: nil, interval_in_minutes: 0)
9
+ def initialize(database:)
10
+ super(database:, destination: nil, interval_in_minutes: 0)
11
11
  end
12
12
 
13
13
  def tick
@@ -1,9 +1,9 @@
1
1
  module SolidBackup
2
2
  class Backup
3
- attr_reader :name
3
+ attr_reader :database
4
4
 
5
- def initialize(name:, destination:, interval_in_minutes:)
6
- self.name = name
5
+ def initialize(database:, destination:, interval_in_minutes:)
6
+ self.database = database
7
7
  self.destination = destination
8
8
  self.interval_in_minutes = interval_in_minutes
9
9
 
@@ -22,7 +22,7 @@ module SolidBackup
22
22
  end
23
23
 
24
24
  def perform
25
- ActiveSupport::Notifications.instrument("backup.solid_backup", name:) do
25
+ ActiveSupport::Notifications.instrument("backup.solid_backup", database:) do
26
26
  lock do
27
27
  do_perform(Time.now.utc.strftime(destination.to_s))
28
28
  end
@@ -32,13 +32,13 @@ module SolidBackup
32
32
  private
33
33
 
34
34
  attr_accessor :countdown
35
- attr_writer :name
35
+ attr_writer :database
36
36
  attr_reader :destination, :interval_in_minutes
37
37
 
38
38
  def interval_in_minutes=(value)
39
- if !value.is_a?(Integer) && value <= 0
39
+ if !value.is_a?(Integer) || value <= 0
40
40
  raise TypeError.new(<<~ERROR)
41
- invalid backup configuration for #{name}: interval_in_minutes is set to #{interval_in_minutes.inspect}, but should be set to a positive integer
41
+ invalid backup configuration for #{database}: interval_in_minutes is set to #{value.inspect}, but should be set to a positive integer
42
42
  ERROR
43
43
  end
44
44
 
@@ -50,14 +50,14 @@ module SolidBackup
50
50
  directory = value.dirname
51
51
 
52
52
  if !directory.directory?
53
- raise "invalid backup configuration for #{name}: destination directory #{directory} must exist"
53
+ raise "invalid backup configuration for #{database}: destination directory #{directory} must exist"
54
54
  end
55
55
 
56
56
  test_path = directory / "solid_backup.txt"
57
57
  begin
58
58
  test_path.write("Solid Backup Test")
59
- rescue Errno::EACCESS
60
- raise "invalid backup configuration for #{name}: destination directory #{directory} must be writeable"
59
+ rescue Errno::EACCES
60
+ raise "invalid backup configuration for #{database}: destination directory #{directory} must be writeable"
61
61
  ensure
62
62
  test_path.unlink
63
63
  end
@@ -74,13 +74,13 @@ module SolidBackup
74
74
  end
75
75
 
76
76
  def connection_pool
77
- ActiveRecord::Base.connection_handler.establish_connection(name.to_sym)
77
+ ActiveRecord::Base.connection_handler.establish_connection(database.to_sym)
78
78
  end
79
79
 
80
80
  delegate :with_connection, to: :connection_pool
81
81
 
82
82
  def lock
83
- path = destination.dirname / ".solid_backup.#{name}.lock"
83
+ path = destination.dirname / ".solid_backup.#{database}.lock"
84
84
 
85
85
  File.open(path, File::RDWR | File::CREAT) do |f|
86
86
  if f.flock(File::LOCK_EX | File::LOCK_NB)
@@ -94,7 +94,7 @@ module SolidBackup
94
94
 
95
95
  begin
96
96
  File.unlink(path)
97
- rescue ERRNO::ENOENT
97
+ rescue Errno::ENOENT
98
98
  end
99
99
  end
100
100
  end
@@ -0,0 +1,41 @@
1
+ class SolidBackup::Configuration::Environment
2
+ attr_reader :name, :backups
3
+
4
+ def initialize(name)
5
+ self.name = name
6
+ self.backups = []
7
+
8
+ yield(self)
9
+ end
10
+
11
+ def tick
12
+ backups.each(&:tick)
13
+ end
14
+
15
+ def backup(database, algorithm_class, **algorithm_arguments)
16
+ database = database.to_s
17
+ if backups.any? { it.database == database }
18
+ raise ArgumentError.new("backups for #{database} in #{name} have already been configured")
19
+ end
20
+
21
+ backups << algorithm_class.new(database:, **algorithm_arguments)
22
+ end
23
+
24
+ def validate!
25
+ configured_databases = backups.map(&:database)
26
+ all_databases = ActiveRecord::Base.configurations.configs_for(env_name: name).select do |db_config|
27
+ db_config.adapter == "sqlite3"
28
+ end.map(&:name)
29
+
30
+ if (missing_databases = all_databases - configured_databases).present?
31
+ raise "SQLite databases missing from backup configuration for #{name}: #{missing_databases.join(", ")}"
32
+ end
33
+ if (unknown_databases = configured_databases - all_databases).present?
34
+ raise "unknown SQLite databases present in backup configuration for #{name}: #{unknown_databases.join(", ")}"
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ attr_writer :name, :backups
41
+ end
@@ -1,20 +1,25 @@
1
1
  module SolidBackup
2
2
  class Configuration
3
3
  attr_accessor :enabled
4
- attr_reader :algorithms
4
+ attr_reader :environments
5
5
 
6
6
  def initialize
7
7
  self.enabled = nil
8
- self.algorithms = []
8
+ self.environments = []
9
9
  end
10
10
 
11
- def backup(name, algorithm_class, **algorithm_arguments)
11
+ def environment(name, &)
12
12
  name = name.to_s
13
- if algorithms.find { it.name == name }
14
- raise ArgumentError.new("#{name} has had backups configured already")
13
+ if environments.any? { it.name == name }
14
+ raise ArgumentError.new("environment #{name} has already been configured")
15
15
  end
16
16
 
17
- algorithms << algorithm_class.new(name:, **algorithm_arguments)
17
+ environments << SolidBackup::Configuration::Environment.new(name, &)
18
+ end
19
+
20
+ def tick(env_name)
21
+ env_name = env_name.to_s
22
+ environments.find { it.name == env_name }&.tick
18
23
  end
19
24
 
20
25
  def validate!
@@ -22,17 +27,7 @@ module SolidBackup
22
27
  raise TypeError.new("enabled is set to #{enabled.inspect}, but allowed values are true or false")
23
28
  end
24
29
 
25
- configured_database_names = algorithms.map(&:name)
26
- all_database_names = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env).select do |db_config|
27
- db_config.adapter == "sqlite3"
28
- end.map(&:name)
29
-
30
- if (missing_databases = all_database_names - configured_database_names).present?
31
- raise "SQLite databases missing from backup configuration: #{missing_databases.join(", ")}"
32
- end
33
- if (unknown_databases = configured_database_names - all_database_names).present?
34
- raise "unknown SQLite databases present in backup configuration: #{unknown_databases.join(", ")}"
35
- end
30
+ environments.each(&:validate!)
36
31
  end
37
32
 
38
33
  def enabled?
@@ -45,6 +40,6 @@ module SolidBackup
45
40
 
46
41
  private
47
42
 
48
- attr_writer :algorithms
43
+ attr_writer :environments
49
44
  end
50
45
  end
@@ -13,15 +13,15 @@ module SolidBackup
13
13
  ActiveRecord::Base.configurations.configs_for(env_name: "production").each do |db_config|
14
14
  backups << if yes?("Back up #{db_config.name} in production?")
15
15
  <<~CODE.chomp
16
- config.backup #{db_config.name.inspect},
17
- SolidBackup::Backup::API,
18
- destination: "storage/backups/#{db_config.name}_%Y%m%dT%H%M%S.sqlite3",
19
- interval_in_minutes: 60,
20
- step: 100,
21
- wait: 0.1
16
+ production.backup #{db_config.name.inspect},
17
+ SolidBackup::Backup::API,
18
+ destination: "storage/backups/#{db_config.name}_%Y%m%dT%H%M%S.sqlite3",
19
+ interval_in_minutes: 60,
20
+ step: 100,
21
+ wait: 0.1
22
22
  CODE
23
23
  else
24
- "config.backup #{db_config.name.inspect}, SolidBackup::Backup::None"
24
+ "production.backup #{db_config.name.inspect}, SolidBackup::Backup::None"
25
25
  end
26
26
  end
27
27
 
@@ -34,7 +34,9 @@ module SolidBackup
34
34
  #
35
35
  # IMPORTANT: all databases must be explicitly configured, even if they're not
36
36
  # backed up, to avoid accidentally excluding new databases from backups.
37
- #{backups.join("\n").indent(2)}
37
+ config.environment("production") do |production|
38
+ #{backups.join("\n").indent(4)}
39
+ end
38
40
  end
39
41
  CODE
40
42
 
@@ -1,3 +1,3 @@
1
1
  module SolidBackup
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/solid_backup.rb CHANGED
@@ -3,6 +3,7 @@ require "solid_backup/backup/none"
3
3
  require "solid_backup/backup/api"
4
4
  require "solid_backup/backup/vacuum_into"
5
5
  require "solid_backup/configuration"
6
+ require "solid_backup/configuration/environment"
6
7
  require "solid_backup/version"
7
8
  require "solid_backup/railtie"
8
9
 
@@ -18,7 +19,7 @@ module SolidBackup
18
19
  end
19
20
 
20
21
  def tick
21
- configuration.algorithms.each(&:tick)
22
+ configuration.tick(Rails.env)
22
23
  end
23
24
 
24
25
  private
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Greg Navis
@@ -99,6 +99,7 @@ executables: []
99
99
  extensions: []
100
100
  extra_rdoc_files: []
101
101
  files:
102
+ - CHANGELOG.md
102
103
  - MIT-LICENSE.txt
103
104
  - README.md
104
105
  - lib/puma/plugin/solid_backup.rb
@@ -108,6 +109,7 @@ files:
108
109
  - lib/solid_backup/backup/none.rb
109
110
  - lib/solid_backup/backup/vacuum_into.rb
110
111
  - lib/solid_backup/configuration.rb
112
+ - lib/solid_backup/configuration/environment.rb
111
113
  - lib/solid_backup/generators/install_generator.rb
112
114
  - lib/solid_backup/railtie.rb
113
115
  - lib/solid_backup/version.rb