Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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:
Comment thread
Copilot marked this conversation as resolved.
Outdated
"""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