Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/sep/apps/mysql_backups/binlog_payload
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,11 @@ class Upload(Backup):
log_path = Path(logging_dir) / "upload-binlog.log"
self.log_stream = log_path.open("a")
self.backup_type = BACKUP_TYPES[self.backup_code]
# Fail-safe default: an absent ENCRYPT is treated as "encrypt" so a
# standalone run against hand-authored config never silently produces an
# unencrypted backup on a customer host. This deliberately diverges from
# the form/config model default (False, an unchecked box); do not align
# the two.
self.encrypt = settings.get("ENCRYPT", True)
self.encrypt_using_tmpdir = settings.get("ENCRYPT_USING_TMPDIR", False)
self.post_run_encrypt = settings.get("POST_RUN_ENCRYPT", False)
Expand Down
5 changes: 5 additions & 0 deletions app/sep/apps/mysql_backups/mydumper_payload
Original file line number Diff line number Diff line change
Expand Up @@ -1383,6 +1383,11 @@ class Upload(Backup):
log_path = self.logging_dir / "upload-mydumper.log"
self.log_stream = log_path.open("a")
self.backup_type = BACKUP_TYPE
# Fail-safe default: an absent ENCRYPT is treated as "encrypt" so a
# standalone run against hand-authored config never silently produces an
# unencrypted backup on a customer host. This deliberately diverges from
# the form/config model default (False, an unchecked box); do not align
# the two.
self.encrypt = settings.get("ENCRYPT", True)
self.encrypt_using_tmpdir = settings.get("ENCRYPT_USING_TMPDIR", False)
self.post_run_encrypt = settings.get("POST_RUN_ENCRYPT", False)
Expand Down
5 changes: 5 additions & 0 deletions app/sep/apps/mysql_backups/xtrabackup_payload
Original file line number Diff line number Diff line change
Expand Up @@ -1468,6 +1468,11 @@ class Upload(Backup):
log_path = Path(logging_dir) / "upload-xtrabackup.log"
self.log_stream = log_path.open("a")
self.backup_type = BACKUP_TYPES[self.backup_code]
# Fail-safe default: an absent ENCRYPT is treated as "encrypt" so a
# standalone run against hand-authored config never silently produces an
# unencrypted backup on a customer host. This deliberately diverges from
# the form/config model default (False, an unchecked box); do not align
# the two.
self.encrypt = settings.get("ENCRYPT", True)
self.encrypt_using_tmpdir = settings.get("ENCRYPT_USING_TMPDIR", False)
self.post_run_encrypt = settings.get("POST_RUN_ENCRYPT", False)
Expand Down
54 changes: 54 additions & 0 deletions tests/app/sep/apps/mysql_backups/test_payload_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
compute the same ``Path(__file__).parent`` within the package).
"""

import pytest
import yaml

from app.inventory.models import ServiceTypeEnum
from app.sep.apps.framework.spec import assemble_envelope, ResolvedEntities
from app.sep.apps.mysql_backups.forms import BackupCreate
Expand Down Expand Up @@ -182,3 +185,54 @@ def test_spec_path_payload_matrix_matches_golden():
assert_or_update(
PAYLOAD_DIR / "mysql_backups__spec_path.json", canonical_json(payloads)
)


def _all_servers_config(backup_type: str, encryption: dict) -> dict:
"""Return the ``ALL_SERVERS`` block of the YAML config ``build_backup_spec`` emits.

:param backup_type: The ``BackupType`` code (``M``/``X``/``B``).
:param encryption: The encryption fields to pass to the form, if any.
"""
service = _service()
resolved = ResolvedEntities(
service=service,
entities={"service_id": service},
executor_host=_HOSTNAME,
)
form = BackupCreate(
task_name=_TASK_NAME,
hostname=_HOSTNAME,
service_id=service.id,
backup_type=backup_type,
**encryption,
)
return yaml.safe_load(build_backup_spec(form, resolved).config)["ALL_SERVERS"]


@pytest.mark.parametrize("backup_type", ["M", "X", "B"])
@pytest.mark.parametrize(
"encryption",
[
pytest.param(
{"encrypt": True, "encryption_recipient": "ops@example.com"},
id="encrypt_true",
),
pytest.param({"encrypt": False}, id="encrypt_false"),
pytest.param({}, id="encrypt_omitted"),
],
)
def test_build_backup_spec_always_emits_encrypt_key(backup_type: str, encryption: dict):
"""Pin that every generated config carries an explicit ``ENCRYPT`` key.

Drives ``build_backup_spec`` directly for each backup type and for an
``encrypt=True`` form, an ``encrypt=False`` form, and a form built without any
encryption fields, then asserts the serialised YAML always names ``ENCRYPT``
with the form's value. This is the producer-side invariant the payload scripts
rely on: they read ``settings.get("ENCRYPT", True)``, so a config that ever
omitted the key would flip a stored backup from unencrypted to encrypted. The
assertion fails if ``exclude_unset``/``exclude_defaults`` (which would drop the
``False`` default) is ever introduced into the builder.
"""
all_servers = _all_servers_config(backup_type, encryption)
assert "ENCRYPT" in all_servers
assert all_servers["ENCRYPT"] is encryption.get("encrypt", False)
Loading