cli-backup 0.2.0__tar.gz → 0.2.1__tar.gz

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.
Files changed (33) hide show
  1. {cli_backup-0.2.0/src/cli_backup.egg-info → cli_backup-0.2.1}/PKG-INFO +3 -3
  2. {cli_backup-0.2.0 → cli_backup-0.2.1}/README.md +2 -2
  3. {cli_backup-0.2.0 → cli_backup-0.2.1}/pyproject.toml +1 -1
  4. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/scheduled.py +2 -0
  5. {cli_backup-0.2.0 → cli_backup-0.2.1/src/cli_backup.egg-info}/PKG-INFO +3 -3
  6. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_cross_project_restore.py +18 -4
  7. {cli_backup-0.2.0 → cli_backup-0.2.1}/LICENSE +0 -0
  8. {cli_backup-0.2.0 → cli_backup-0.2.1}/setup.cfg +0 -0
  9. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/__init__.py +0 -0
  10. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/cli.py +0 -0
  11. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/config.py +0 -0
  12. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/cpu_priority.py +0 -0
  13. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/files.py +0 -0
  14. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/mysql.py +0 -0
  15. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/redis_backup.py +0 -0
  16. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/rehearsal.py +0 -0
  17. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup/storage.py +0 -0
  18. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup.egg-info/SOURCES.txt +0 -0
  19. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup.egg-info/dependency_links.txt +0 -0
  20. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup.egg-info/entry_points.txt +0 -0
  21. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup.egg-info/requires.txt +0 -0
  22. {cli_backup-0.2.0 → cli_backup-0.2.1}/src/cli_backup.egg-info/top_level.txt +0 -0
  23. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_cli.py +0 -0
  24. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_config.py +0 -0
  25. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_cpu_priority.py +0 -0
  26. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_files.py +0 -0
  27. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_mysql.py +0 -0
  28. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_postgresql.py +0 -0
  29. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_redis.py +0 -0
  30. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_rehearsal.py +0 -0
  31. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_scheduled.py +0 -0
  32. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_sqlite_podman.py +0 -0
  33. {cli_backup-0.2.0 → cli_backup-0.2.1}/tests/test_storage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-backup
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Host backup CLI for database snapshots and directory archives
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/XakkiDev/cli-backup
@@ -76,10 +76,10 @@ make cli-backup@verify path=daily where=local file=2026-01-01.example.sql.gz
76
76
 
77
77
  Additional targets `cli-backup@restore-test` and `cli-backup@restore` exist for MariaDB/MySQL. A live restore is destructive, requires an explicit file and interactive confirmation, and must target the correct stopped-writer environment. A restore rehearsal creates a disposable isolated container and should run only with appropriate authorization. For an unavailable source container, set `CLI_BACKUP_TEST_MYSQL_IMAGE` to an appropriate disposable test image before a rehearsal. Use `db=<name>` to filter when needed. Source-independent `list` and `verify` work with a stopped database container.
78
78
 
79
- To restore a backup from another project into this project's selected MariaDB/MySQL database, explicitly specify the source project and database. Stop writers and run interactively only with authorization; the confirmation names both source and destination. The destination database must already exist. This example reads from S3 without a persistent intermediary dump file:
79
+ To restore a backup from another project into this project's selected MariaDB/MySQL database, explicitly specify the source project and database. Source and destination database names must match: SQL dumps can contain database-qualified statements. Stop writers and run interactively only with authorization; the confirmation names both projects and the database. The destination database must already exist. This example reads from S3 without a persistent intermediary dump file:
80
80
 
81
81
  ```sh
82
- make cli-backup@restore where=s3 path=daily file=2026-01-01.production.sql.gz source_project=production source_db=production db=development
82
+ make cli-backup@restore where=s3 path=daily file=2026-01-01.app.sql.gz source_project=production source_db=app db=app
83
83
  ```
84
84
 
85
85
  ### Storage and schedule
@@ -60,10 +60,10 @@ make cli-backup@verify path=daily where=local file=2026-01-01.example.sql.gz
60
60
 
61
61
  Additional targets `cli-backup@restore-test` and `cli-backup@restore` exist for MariaDB/MySQL. A live restore is destructive, requires an explicit file and interactive confirmation, and must target the correct stopped-writer environment. A restore rehearsal creates a disposable isolated container and should run only with appropriate authorization. For an unavailable source container, set `CLI_BACKUP_TEST_MYSQL_IMAGE` to an appropriate disposable test image before a rehearsal. Use `db=<name>` to filter when needed. Source-independent `list` and `verify` work with a stopped database container.
62
62
 
63
- To restore a backup from another project into this project's selected MariaDB/MySQL database, explicitly specify the source project and database. Stop writers and run interactively only with authorization; the confirmation names both source and destination. The destination database must already exist. This example reads from S3 without a persistent intermediary dump file:
63
+ To restore a backup from another project into this project's selected MariaDB/MySQL database, explicitly specify the source project and database. Source and destination database names must match: SQL dumps can contain database-qualified statements. Stop writers and run interactively only with authorization; the confirmation names both projects and the database. The destination database must already exist. This example reads from S3 without a persistent intermediary dump file:
64
64
 
65
65
  ```sh
66
- make cli-backup@restore where=s3 path=daily file=2026-01-01.production.sql.gz source_project=production source_db=production db=development
66
+ make cli-backup@restore where=s3 path=daily file=2026-01-01.app.sql.gz source_project=production source_db=app db=app
67
67
  ```
68
68
 
69
69
  ### Storage and schedule
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "cli-backup"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Host backup CLI for database snapshots and directory archives"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = ["boto3==1.43.127"]
@@ -668,6 +668,8 @@ def main(argv=None) -> int:
668
668
  target = args.database or (names[0] if len(names) == 1 else selected)
669
669
  if target not in names:
670
670
  raise ValueError('restore target database is not configured')
671
+ if target != selected:
672
+ raise ValueError('restoring to a different database name is not supported safely')
671
673
  prompt = f'Restore {source_project}/{selected} into {settings.project}/{target}? Type yes: '
672
674
  if not sys.stdin.isatty() or input(prompt) != 'yes':
673
675
  raise ValueError('restore not confirmed')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cli-backup
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Host backup CLI for database snapshots and directory archives
5
5
  License-Expression: MIT
6
6
  Project-URL: Homepage, https://github.com/XakkiDev/cli-backup
@@ -76,10 +76,10 @@ make cli-backup@verify path=daily where=local file=2026-01-01.example.sql.gz
76
76
 
77
77
  Additional targets `cli-backup@restore-test` and `cli-backup@restore` exist for MariaDB/MySQL. A live restore is destructive, requires an explicit file and interactive confirmation, and must target the correct stopped-writer environment. A restore rehearsal creates a disposable isolated container and should run only with appropriate authorization. For an unavailable source container, set `CLI_BACKUP_TEST_MYSQL_IMAGE` to an appropriate disposable test image before a rehearsal. Use `db=<name>` to filter when needed. Source-independent `list` and `verify` work with a stopped database container.
78
78
 
79
- To restore a backup from another project into this project's selected MariaDB/MySQL database, explicitly specify the source project and database. Stop writers and run interactively only with authorization; the confirmation names both source and destination. The destination database must already exist. This example reads from S3 without a persistent intermediary dump file:
79
+ To restore a backup from another project into this project's selected MariaDB/MySQL database, explicitly specify the source project and database. Source and destination database names must match: SQL dumps can contain database-qualified statements. Stop writers and run interactively only with authorization; the confirmation names both projects and the database. The destination database must already exist. This example reads from S3 without a persistent intermediary dump file:
80
80
 
81
81
  ```sh
82
- make cli-backup@restore where=s3 path=daily file=2026-01-01.production.sql.gz source_project=production source_db=production db=development
82
+ make cli-backup@restore where=s3 path=daily file=2026-01-01.app.sql.gz source_project=production source_db=app db=app
83
83
  ```
84
84
 
85
85
  ### Storage and schedule
@@ -20,7 +20,7 @@ class CrossProjectRestoreTests(unittest.TestCase):
20
20
  artifact = root / 'dump.gz'
21
21
  with gzip.open(artifact, 'wb') as stream:
22
22
  stream.write(b'SELECT 1;')
23
- filename = '2026-11-11.student.sql.gz'
23
+ filename = '2026-11-11.student_dev.sql.gz'
24
24
  store.put(artifact, make_manifest(artifact, 'sa-prod', 'mariadb', filename, '2026-11-11T00:00:00+00:00'))
25
25
  cfg = MySQLConfig('exec', 'student_dev', 'root', 'secret', service='container')
26
26
  env = {'CLI_BACKUP_PROJECT_NAME': 'sa-dev', 'CLI_BACKUP_DB_CONTAINER': 'mariadb',
@@ -29,7 +29,7 @@ class CrossProjectRestoreTests(unittest.TestCase):
29
29
  def fake_restore(database, stream):
30
30
  captured.append((database.database, stream.read()))
31
31
  args = ['restore', '--storage', 's3', '--file', filename,
32
- '--source-project-name', 'sa-prod', '--source-database', 'student']
32
+ '--source-project-name', 'sa-prod', '--source-database', 'student_dev']
33
33
  with patch.dict(os.environ, env, clear=True), \
34
34
  patch('cli_backup.scheduled.connection', return_value=(cfg, ['student_dev'], 'image')), \
35
35
  patch('cli_backup.scheduled.stores', return_value=[('s3', store)]), \
@@ -37,13 +37,27 @@ class CrossProjectRestoreTests(unittest.TestCase):
37
37
  patch('sys.stdin.isatty', return_value=True), patch('builtins.input', return_value='yes') as prompt:
38
38
  self.assertEqual(main(args), 0)
39
39
  self.assertEqual(captured, [('student_dev', b'SELECT 1;')])
40
- self.assertIn('sa-prod/student into sa-dev/student_dev', prompt.call_args.args[0])
40
+ self.assertIn('sa-prod/student_dev into sa-dev/student_dev', prompt.call_args.args[0])
41
+
42
+ def test_different_database_name_fails_closed(self):
43
+ with tempfile.TemporaryDirectory() as tmp:
44
+ root = Path(tmp); store = LocalStorage(root / 'daily'); artifact = root / 'dump.gz'
45
+ with gzip.open(artifact, 'wb') as stream: stream.write(b'ALTER DATABASE `student` CHARACTER SET utf8;')
46
+ filename = '2026-11-11.student.sql.gz'
47
+ store.put(artifact, make_manifest(artifact, 'sa-prod', 'mariadb', filename, '2026-11-11T00:00:00+00:00'))
48
+ cfg = MySQLConfig('exec', 'student_dev', 'root', 'secret', service='container')
49
+ env = {'CLI_BACKUP_PROJECT_NAME': 'sa-dev', 'CLI_BACKUP_DB_CONTAINER': 'mariadb', 'CLI_BACKUP_LOCAL_ROOT': str(root)}
50
+ with patch.dict(os.environ, env, clear=True), patch('cli_backup.scheduled.connection', return_value=(cfg, ['student_dev'], 'image')), \
51
+ patch('cli_backup.scheduled.stores', return_value=[('s3', store)]), patch('cli_backup.scheduled.restore_mysql') as restore, \
52
+ patch('sys.stdin.isatty', return_value=True), patch('builtins.input', return_value='yes'), contextlib.redirect_stderr(io.StringIO()):
53
+ self.assertEqual(main(['restore', '--storage', 's3', '--file', filename, '--source-project-name', 'sa-prod']), 2)
54
+ restore.assert_not_called()
41
55
 
42
56
  def test_confirmation_is_required_even_for_cross_project_restore(self):
43
57
  with tempfile.TemporaryDirectory() as tmp:
44
58
  root = Path(tmp); store = LocalStorage(root / 'daily'); artifact = root / 'dump.gz'
45
59
  with gzip.open(artifact, 'wb') as stream: stream.write(b'SELECT 1;')
46
- filename = '2026-11-11.student.sql.gz'
60
+ filename = '2026-11-11.student_dev.sql.gz'
47
61
  store.put(artifact, make_manifest(artifact, 'sa-prod', 'mariadb', filename, '2026-11-11T00:00:00+00:00'))
48
62
  cfg = MySQLConfig('exec', 'student_dev', 'root', 'secret', service='container')
49
63
  env = {'CLI_BACKUP_PROJECT_NAME': 'sa-dev', 'CLI_BACKUP_DB_CONTAINER': 'mariadb', 'CLI_BACKUP_LOCAL_ROOT': str(root)}
File without changes
File without changes
File without changes