solid_backup 0.1.0 → 0.2.1

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: 64041a4fe1924fce518b5fdebf659f64e56838958cffc85d3c91a73eabd8fce3
4
+ data.tar.gz: 123c5f3123eae959527fb48282154fc5eab4ca2d61d0898e63a908591db360fb
5
5
  SHA512:
6
- metadata.gz: '0802038b0aeac7c0b0bebbf1b8b6d68054b52621ef1c50c9c21f1a640866fb2f1e51ee9a3d98bc6fef80a5b63ab85c4ed9cec500e7ba86ddc24bf45fe65d3c33'
7
- data.tar.gz: 82bec8bad5efe105b5ad7247b6f0754fda99381baafdcaff36704c249a36996c86bf0d1e65e1d4cd81a896a03513cc631a0c4540dc6b501115b5b3134f0936c5
6
+ metadata.gz: e1796921c4775f3c580dea38644c695a9fc1317f9ca1c7cd4a5998bddab2e2b97157d488eba5f2ad07ba81aff9df369912ee9d9d473ee5947e9ef40a1e507182
7
+ data.tar.gz: e69599caa609128c443b52dfd4af35b825f43a35347c1feb47b68ef3ed91b53dfd98196920e79de34b95b805bf679426071ae77042663af31a59d68cca7bc0d3
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Solid Backup
2
+
3
+ ## 0.2.1
4
+
5
+ - Require Active Support libraries to ensure `delegate` is defined.
6
+
7
+ ## 0.2.0
8
+
9
+ - Support for per-environment backup configuration.
10
+
11
+ ## 0.1.0
12
+
13
+ - 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,11 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
1
3
  module SolidBackup
2
4
  class Backup
3
- attr_reader :name
5
+ attr_reader :database
4
6
 
5
- def initialize(name:, destination:, interval_in_minutes:)
6
- self.name = name
7
+ def initialize(database:, destination:, interval_in_minutes:)
8
+ self.database = database
7
9
  self.destination = destination
8
10
  self.interval_in_minutes = interval_in_minutes
9
11
 
@@ -22,7 +24,7 @@ module SolidBackup
22
24
  end
23
25
 
24
26
  def perform
25
- ActiveSupport::Notifications.instrument("backup.solid_backup", name:) do
27
+ ActiveSupport::Notifications.instrument("backup.solid_backup", database:) do
26
28
  lock do
27
29
  do_perform(Time.now.utc.strftime(destination.to_s))
28
30
  end
@@ -32,13 +34,13 @@ module SolidBackup
32
34
  private
33
35
 
34
36
  attr_accessor :countdown
35
- attr_writer :name
37
+ attr_writer :database
36
38
  attr_reader :destination, :interval_in_minutes
37
39
 
38
40
  def interval_in_minutes=(value)
39
- if !value.is_a?(Integer) && value <= 0
41
+ if !value.is_a?(Integer) || value <= 0
40
42
  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
43
+ invalid backup configuration for #{database}: interval_in_minutes is set to #{value.inspect}, but should be set to a positive integer
42
44
  ERROR
43
45
  end
44
46
 
@@ -50,14 +52,14 @@ module SolidBackup
50
52
  directory = value.dirname
51
53
 
52
54
  if !directory.directory?
53
- raise "invalid backup configuration for #{name}: destination directory #{directory} must exist"
55
+ raise "invalid backup configuration for #{database}: destination directory #{directory} must exist"
54
56
  end
55
57
 
56
58
  test_path = directory / "solid_backup.txt"
57
59
  begin
58
60
  test_path.write("Solid Backup Test")
59
- rescue Errno::EACCESS
60
- raise "invalid backup configuration for #{name}: destination directory #{directory} must be writeable"
61
+ rescue Errno::EACCES
62
+ raise "invalid backup configuration for #{database}: destination directory #{directory} must be writeable"
61
63
  ensure
62
64
  test_path.unlink
63
65
  end
@@ -74,13 +76,13 @@ module SolidBackup
74
76
  end
75
77
 
76
78
  def connection_pool
77
- ActiveRecord::Base.connection_handler.establish_connection(name.to_sym)
79
+ ActiveRecord::Base.connection_handler.establish_connection(database.to_sym)
78
80
  end
79
81
 
80
82
  delegate :with_connection, to: :connection_pool
81
83
 
82
84
  def lock
83
- path = destination.dirname / ".solid_backup.#{name}.lock"
85
+ path = destination.dirname / ".solid_backup.#{database}.lock"
84
86
 
85
87
  File.open(path, File::RDWR | File::CREAT) do |f|
86
88
  if f.flock(File::LOCK_EX | File::LOCK_NB)
@@ -94,7 +96,7 @@ module SolidBackup
94
96
 
95
97
  begin
96
98
  File.unlink(path)
97
- rescue ERRNO::ENOENT
99
+ rescue Errno::ENOENT
98
100
  end
99
101
  end
100
102
  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.1"
3
3
  end
data/lib/solid_backup.rb CHANGED
@@ -1,8 +1,11 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
1
3
  require "solid_backup/backup"
2
4
  require "solid_backup/backup/none"
3
5
  require "solid_backup/backup/api"
4
6
  require "solid_backup/backup/vacuum_into"
5
7
  require "solid_backup/configuration"
8
+ require "solid_backup/configuration/environment"
6
9
  require "solid_backup/version"
7
10
  require "solid_backup/railtie"
8
11
 
@@ -18,7 +21,7 @@ module SolidBackup
18
21
  end
19
22
 
20
23
  def tick
21
- configuration.algorithms.each(&:tick)
24
+ configuration.tick(Rails.env)
22
25
  end
23
26
 
24
27
  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.1
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