kamal-backup 0.5.0 → 0.5.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: aa8276219db8c5d8aa820e85e0f289f87715889cb5ea18ae2c99e3e12bbfe6b9
4
- data.tar.gz: 626824ed56029bf3b713676d3376ab38d571942ee3809e7cf18abbfde9c24a45
3
+ metadata.gz: 0ba0b661680e0b8885935b4697cc40052f1584a2c39b0646d2e939d5874f043a
4
+ data.tar.gz: 3ac2499e70f17ac20b30eaba0bc507e6558fbf1406d8423e32fb1f44c642e3b8
5
5
  SHA512:
6
- metadata.gz: 75d362d35409c0447c29994a7bf4d23ac47d287fc88621eb46f32a56296537217cee76a28799983f45154361ecd61e4f553b609144969bed2b10016bf0ecc09c
7
- data.tar.gz: e5f664a15123066441b940d945caed2335d8b50356acaa64d9ae6f49d87a7705894234e55db42ec6508aa188f727b01d4e4d4c71688ecd93b4dbf4c12d53ef5d
6
+ metadata.gz: 61ff158f87e67167f446b540af94a4193657bf02f8a435d7ffb9465092c32cde6a9b82b4a616fb8571454ddde085915a051be312748ef0d1e2c8647d80f159ee
7
+ data.tar.gz: e872f59bb07fa089fcf4477c6b2d1f315d8a6d1fed164366d52a8475753c90a951caf46b929ff71ec4b5056160b3f8f8661a833ce0c8ecfaa2a0269d2c03b253
@@ -480,6 +480,10 @@ module KamalBackup
480
480
  config.validate_restic
481
481
  config.validate_database_backup
482
482
  config.validate_backup_paths
483
+ # Check writability up front. This used to be discovered only after
484
+ # restic had pulled the entire file snapshot, so a read-only mount cost a
485
+ # full download before failing — the wrong moment to learn about it.
486
+ config.backup_paths.each { |path| ensure_writable_restore_target(path) }
483
487
  end
484
488
 
485
489
  def validate_production_drill(file_target, database_name, sqlite_path)
@@ -34,6 +34,28 @@ module KamalBackup
34
34
  CommandSpec.new(argv: argv, env: current_connection)
35
35
  end
36
36
 
37
+ # Replace the target schema before restoring.
38
+ #
39
+ # pg_restore --clean emits DROP TABLE for each object in the dump, but
40
+ # PostgreSQL refuses to drop a table another table still references, and
41
+ # the dump's ordering cannot account for constraints that only exist in
42
+ # the target. Restoring over a schema Rails already created — which is
43
+ # every restore onto a freshly deployed host, since Kamal runs db:prepare
44
+ # on boot — leaves the drops failing, the CREATEs failing as "already
45
+ # exists", the COPYs never running, and the foreign keys rejected because
46
+ # the tables they point at are still empty.
47
+ #
48
+ # Dropping the schema first sidesteps the ordering problem entirely. This
49
+ # only runs for restore-to-current, which already means "replace this
50
+ # database"; scratch restores target a separate database and are
51
+ # untouched.
52
+ def restore_to_current(restic, snapshot, filename)
53
+ reset_current_schema
54
+ result = super
55
+ ensure_restore_reported_no_errors(result)
56
+ result
57
+ end
58
+
37
59
  def current_restore_command
38
60
  connection = current_connection
39
61
  database = connection.fetch('PGDATABASE')
@@ -61,6 +83,38 @@ module KamalBackup
61
83
 
62
84
  private
63
85
 
86
+ # pg_restore exits 0 even when individual objects fail, reporting only
87
+ # "errors ignored on restore: N" on stderr. A restore that half-applied
88
+ # and claimed success is worse than one that failed, because the damage
89
+ # is only discovered later, from the data.
90
+ def ensure_restore_reported_no_errors(result)
91
+ stderr = result.respond_to?(:stderr) ? result.stderr.to_s : ''
92
+ match = stderr.match(/errors ignored on restore:\s*(\d+)/i)
93
+ return if match.nil? || match[1].to_i.zero?
94
+
95
+ raise CommandError.new(
96
+ "pg_restore reported #{match[1]} ignored error(s); the database is only partially restored. " \
97
+ 'Inspect the output above, resolve the cause, and restore again.',
98
+ command: current_restore_command,
99
+ stderr: stderr
100
+ )
101
+ end
102
+
103
+ def reset_current_schema
104
+ connection = current_connection
105
+ argv = ['psql', '--quiet', '--no-psqlrc', '--set', 'ON_ERROR_STOP=1', '--command', RESET_SCHEMA_SQL]
106
+ Command.capture(CommandSpec.new(argv: argv, env: connection), redactor: redactor)
107
+ rescue CommandError => e
108
+ raise CommandError.new(
109
+ "failed to reset the public schema before restoring: #{e.message}",
110
+ command: e.command,
111
+ stderr: e.stderr
112
+ )
113
+ end
114
+
115
+ RESET_SCHEMA_SQL = 'DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;'
116
+ private_constant :RESET_SCHEMA_SQL
117
+
64
118
  def validate_scratch_restore_target(target)
65
119
  raise ConfigurationError, 'scratch database must differ from the current PostgreSQL database' if current_connection.fetch('PGDATABASE') == target
66
120
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module KamalBackup
4
- VERSION = '0.5.0'
4
+ VERSION = '0.5.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kamal-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - crmne