-
Notifications
You must be signed in to change notification settings - Fork 722
Add support for cloud backups in Core #5438
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There is a lot of test case work to do still that I'm working on. I put this up so people could provide feedback on the approach as its a good amount of change to support this initiative. |
1e30b83
to
9c858cd
Compare
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
🧹 Outside diff range and nitpick comments (20)
tests/backups/test_backup.py (1)
Line range hint
1-24
: Add test coverage for cloud backup locations.Given that this PR introduces cloud backup support with a new
.cloud_backup
location, consider adding test cases to verify:
- Backup creation in the
.cloud_backup
location- Proper handling of backups across multiple locations
- Edge cases when moving backups between locations
Would you like me to help generate these additional test cases?
tests/resolution/fixup/test_mount_execute_remove.py (2)
49-49
: Document the purpose of multiple active statesThe test now includes multiple state transitions (
["active", "inactive", "active", "inactive"]
) but doesn't document why this sequence is necessary. Consider adding a comment explaining the expected behavior being tested.+ # Test resilience against state flapping by simulating multiple state transitions systemd_unit_service.active_state = ["active", "inactive", "active", "inactive"]
56-57
: Consider validating the order of StopUnit callsThe test expects two different mount paths to be stopped but doesn't validate the order of these operations. Given that this appears to be handling both backup and legacy mount locations, the order might be significant.
Consider adding assertions to validate the sequence:
- assert systemd_service.StopUnit.calls == [ - ("mnt-data-supervisor-backup-test.mount", "fail"), - ("mnt-data-supervisor-mounts-test.mount", "fail"), - ] + calls = systemd_service.StopUnit.calls + assert len(calls) == 2, "Expected exactly two StopUnit calls" + # Verify backup mount is stopped first + assert calls[0] == ("mnt-data-supervisor-backup-test.mount", "fail"), "Backup mount should be stopped first" + # Verify legacy mount is stopped second + assert calls[1] == ("mnt-data-supervisor-mounts-test.mount", "fail"), "Legacy mount should be stopped second"supervisor/api/mounts.py (1)
Line range hint
91-92
: Improve async task handling for backup reloadsThe backup reload tasks are created but not monitored. Consider:
- Adding error handling for the reload tasks
- Awaiting the reload in critical paths where immediate consistency is required
Example improvement:
- self.sys_create_task(self.sys_backups.reload()) + try: + reload_task = self.sys_create_task(self.sys_backups.reload()) + # For critical paths, consider awaiting: + # await reload_task + except Exception as err: + _LOGGER.error("Failed to reload backups: %s", err)Also applies to: 108-109, 127-128, 146-147
tests/docker/test_homeassistant.py (1)
87-92
: Add rslave propagation to cloud backup mount.For consistency with other data directories (like
/backup
,/share
,/media
), consider adding therslave
propagation setting to the cloud backup mount.Mount( type="bind", source=coresys.config.path_extern_core_backup.as_posix(), target="/cloud_backup", read_only=False, + propagation="rslave", ),
supervisor/store/data.py (1)
Line range hint
1-241
: Consider documenting backup-related requirements.While the constant renaming is correct, given that this PR introduces cloud backup support and this file handles add-on data storage, consider documenting any backup-related requirements or constraints for add-on locations. This could help prevent issues when implementing the backup feature, especially since this code runs in an executor and handles file paths.
supervisor/mounts/manager.py (2)
144-151
: Add a comment explaining the concurrent binding approachThe implementation correctly handles concurrent binding of backup mounts. Consider adding a brief comment explaining why concurrent binding is used here, as it might not be immediately obvious to future maintainers.
+ # Bind all backup mounts concurrently to minimize initialization time # Bind all backup mounts to directories in backup if self.backup_mounts: await asyncio.wait(
Line range hint
144-275
: Consider adding test coverage for backup mount functionalityWhile the implementation looks solid, consider adding test cases to verify:
- Backup mount initialization during load
- Backup mount creation and binding
- Error handling for failed backup mounts
- Concurrent binding of multiple backup mounts
This will help ensure the reliability of the cloud backup feature.
Would you like me to help generate test cases for these scenarios?
supervisor/homeassistant/websocket.py (1)
Line range hint
1-300
: Verify WebSocket event handling for backup workflowThe WebSocket implementation looks solid, but we should ensure proper error handling for the backup workflow:
- The code handles WebSocket disconnections and reconnections appropriately, which is crucial for long-running backup operations.
- The message queuing system during startup states will help prevent lost backup notifications.
However, there are a few considerations for the backup workflow:
Consider implementing the following improvements:
- Add timeout handling specific to backup operations, as they might take longer than regular WebSocket operations.
- Implement retry logic for backup-related WebSocket messages to ensure reliable delivery.
- Consider adding a heartbeat mechanism to detect connection issues during long-running backup operations.
Would you like me to provide example implementations for these improvements?
supervisor/config.py (1)
277-286
: Consider documenting the backup path structure.Given that this is part of a larger cloud backup feature, consider documenting the backup path structure and its usage in the class docstring or a separate architecture document. This will help other developers understand:
- The relationship between regular backups and cloud backups
- When to use each backup location
- How these paths integrate with the cloud backup feature
tests/api/test_backups.py (3)
476-549
: LGTM! Comprehensive test coverage for reload functionality.The test cases effectively cover various reload scenarios and input validation. The parametrization ensures testing across different backup locations.
Consider adding these test cases to improve coverage:
- Test reloading a backup that exists in multiple locations
- Test concurrent reload operations to verify thread safety
Example for multiple locations:
async def test_reload_multiple_locations( api_client: TestClient, coresys: CoreSys, tmp_supervisor_data: Path, ): """Test reload of backup present in multiple locations.""" backup_file = get_fixture_path("backup_example.tar") copy(backup_file, tmp_supervisor_data / "backup") copy(backup_file, tmp_supervisor_data / "core/backup") resp = await api_client.post("/backups/reload") assert resp.status == 200 assert len(coresys.backups.list_backups) == 1 assert (backup := coresys.backups.get("7fed74c8")) assert backup.locations == [None, ".cloud_backup"]
551-591
: LGTM! Comprehensive access control tests with a minor improvement needed.The test cases effectively verify that only core can access cloud backup locations across all relevant endpoints.
Consider replacing the direct manipulation of the private
_locations
attribute with a proper mock or test helper method:@pytest.fixture def mock_cloud_backup(mock_full_backup: Backup) -> Backup: """Create a mock backup in cloud location.""" mock_full_backup.locations = {".cloud_backup": None} # Use public interface if available return mock_full_backupThen update the test:
- # pylint: disable-next=protected-access - mock_full_backup._locations = {".cloud_backup": None} - assert mock_full_backup.location == ".cloud_backup" + mock_cloud_backup = mock_cloud_backup(mock_full_backup) + assert mock_cloud_backup.location == ".cloud_backup"
593-602
: LGTM! Error handling test with suggestions for additional coverage.The test case correctly verifies error handling for non-existent files.
Consider adding these error cases to improve coverage:
- Test handling of permission errors
- Test handling of corrupted backup files
Example:
async def test_partial_reload_errors_corrupt_file( api_client: TestClient, tmp_supervisor_data: Path, ): """Test partial reload with corrupted backup file.""" corrupt_file = tmp_supervisor_data / "backup/corrupt.tar" corrupt_file.write_bytes(b"not a valid tar file") resp = await api_client.post( "/backups/reload", json={"location": None, "filename": "corrupt.tar"} ) assert resp.status == 400supervisor/addons/model.py (1)
Line range hint
26-26
: Fix typo in ATTR_DESCRIPTON constant nameWhile reviewing the
ATTR_LOCATION
fix, noticed another typo:ATTR_DESCRIPTON
should beATTR_DESCRIPTION
. This is consistent with the current effort to fix constant naming.Consider fixing this in a similar manner:
- ATTR_DESCRIPTON, + ATTR_DESCRIPTION,And update its usage:
@property def description(self) -> str: """Return description of add-on.""" - return self.data[ATTR_DESCRIPTON] + return self.data[ATTR_DESCRIPTION]Also applies to: 249-249
tests/conftest.py (1)
583-583
: Consider adding test cases for the new parameter.The Backup constructor has been updated to include a new parameter (set to None) consistently across all test fixtures. While this maintains compatibility, consider adding test cases that exercise this parameter with non-None values to ensure proper cloud backup functionality.
@@ -583,1 +583,7 @@ - mock_backup = Backup(coresys, Path(tmp_path, "test_backup"), "test", None) + # Test both None and cloud backup paths + backups = [] + for location in [None, Path(tmp_path, ".cloud_backup")]: + mock_backup = Backup(coresys, Path(tmp_path, "test_backup"), "test", location) + # ... rest of the setup ... + backups.append(mock_backup) + yield backupsAlso applies to: 607-607, 638-638
supervisor/backups/backup.py (2)
74-74
: Consider using a more explicit data structure for locationsWhile using a dictionary with None values as an ordered set is clever, it might not be immediately clear to other developers. Consider using
collections.OrderedDict
or a list with uniqueness checks for better clarity.- self._locations: dict[str | None, Literal[None]] = {location: None} + self._locations: OrderedDict[str | None, None] = OrderedDict([(location, None)])Also applies to: 88-89
Line range hint
1-600
: Consider adding more type hints for better code maintainabilityWhile the code is well-structured, adding more type hints would improve maintainability:
- Add return type hints to methods like
store_dockerconfig
- Add type hints to instance variables in
__init__
- Use
TypeVar
for generic types where applicableExample improvements:
- def store_dockerconfig(self): + def store_dockerconfig(self) -> None: """Store the configuration for Docker.""" - def __init__( - self, - coresys: CoreSys, - tar_file: Path, - slug: str, - location: str | None, - data: dict[str, Any] | None = None, - ): + def __init__( + self, + coresys: CoreSys, + tar_file: Path, + slug: str, + location: str | None, + data: dict[str, Any] | None = None, + ) -> None: """Initialize a backup.""" - self._tarfile: Path = tar_file - self._data: dict[str, Any] = data or {ATTR_SLUG: slug} - self._tmp = None + self._tarfile: Path = tar_file + self._data: dict[str, Any] = data or {ATTR_SLUG: slug} + self._tmp: TemporaryDirectory | None = Nonesupervisor/api/backups.py (1)
180-186
: Consider renaming variablebackup
toreload_args
for clarityIn the
reload
method, the variablebackup
returned from_location_to_mount(body)
may cause confusion, as it contains arguments for the reload operation rather than a backup object.Apply this diff to improve variable naming:
async def reload(self, request: web.Request): """Reload backup list.""" body = await api_validate(SCHEMA_RELOAD, request) self._validate_cloud_backup_location(request, body.get(ATTR_LOCATION)) - backup = self._location_to_mount(body) - return await asyncio.shield(self.sys_backups.reload(**backup)) + reload_args = self._location_to_mount(body) + return await asyncio.shield(self.sys_backups.reload(**reload_args))tests/backups/test_manager.py (2)
1877-1972
: Consider refactoring common setup code in test functionsThe test functions
test_reload_multiple_locations
andtest_partial_reload_multiple_locations
share common setup code for creating mounts and copying backup files. Refactoring this common code into a helper function or a fixture would improve maintainability and reduce code duplication.
1996-2004
: Make tests more robust by avoiding reliance oncall_args_list
indexThe assertion uses
ha_ws_client.async_send_command.call_args_list[-3]
, which assumes the desired call is always the third from last. If the number of calls changes in the future, this could lead to unexpected test failures. Consider filteringcall_args_list
for the specific call or usingunittest.mock.call
to precisely match the expected arguments, enhancing the test's robustness.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
⛔ Files ignored due to path filters (1)
tests/fixtures/backup_example.tar
is excluded by!**/*.tar
📒 Files selected for processing (27)
supervisor/addons/model.py
(2 hunks)supervisor/addons/validate.py
(2 hunks)supervisor/api/backups.py
(16 hunks)supervisor/api/const.py
(2 hunks)supervisor/api/mounts.py
(2 hunks)supervisor/backups/backup.py
(6 hunks)supervisor/backups/const.py
(1 hunks)supervisor/backups/manager.py
(13 hunks)supervisor/bootstrap.py
(1 hunks)supervisor/config.py
(2 hunks)supervisor/const.py
(2 hunks)supervisor/docker/addon.py
(11 hunks)supervisor/docker/audio.py
(2 hunks)supervisor/docker/const.py
(2 hunks)supervisor/docker/homeassistant.py
(3 hunks)supervisor/homeassistant/const.py
(1 hunks)supervisor/homeassistant/websocket.py
(1 hunks)supervisor/mounts/manager.py
(3 hunks)supervisor/mounts/mount.py
(4 hunks)supervisor/store/data.py
(2 hunks)tests/api/test_backups.py
(3 hunks)tests/api/test_mounts.py
(7 hunks)tests/backups/test_backup.py
(1 hunks)tests/backups/test_manager.py
(6 hunks)tests/conftest.py
(4 hunks)tests/docker/test_homeassistant.py
(1 hunks)tests/resolution/fixup/test_mount_execute_remove.py
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- supervisor/addons/validate.py
🧰 Additional context used
📓 Path-based instructions (4)
supervisor/bootstrap.py (6)
Pattern */**(html|markdown|md)
: - For instructional content in documentation, use a direct and authoritative tone. Avoid expressions of politeness such as 'may' or 'please', and ensure the goal of the instruction is fronted.
- Apply the Microsoft Style Guide to ensure documentation maintains clarity and conciseness.
- In step-by-step instructions, front the location phrase in the instructional sentence.
- In step-by-step instructions, front the 'goal' in the instructional sentence.
- In step-by-step instructions, if in doubt what to front, front the 'goal' before the location phrase in the instructional sentence.
- do not hyphenate terms like 'top-right' or 'bottom-left' with 'corner'
Pattern */**(html|markdown|md)
: - Use bold to mark UI strings.
- If "" are used to mark UI strings, replace them by bold.
Pattern */**(html|markdown|md)
: - Be brief in your replies and don't add fluff like "thank you for..." and "Please let me know if"
Pattern */**(html|markdown|md)
: - Use sentence-style capitalization also in headings.
Pattern */**(html|markdown|md)
: do not comment on HTML used for icons
Pattern */**(html|markdown|md)
: Avoid flagging inline HTML for embedding videos in future reviews for this repository.
supervisor/config.py (6)
Pattern */**(html|markdown|md)
: - For instructional content in documentation, use a direct and authoritative tone. Avoid expressions of politeness such as 'may' or 'please', and ensure the goal of the instruction is fronted.
- Apply the Microsoft Style Guide to ensure documentation maintains clarity and conciseness.
- In step-by-step instructions, front the location phrase in the instructional sentence.
- In step-by-step instructions, front the 'goal' in the instructional sentence.
- In step-by-step instructions, if in doubt what to front, front the 'goal' before the location phrase in the instructional sentence.
- do not hyphenate terms like 'top-right' or 'bottom-left' with 'corner'
Pattern */**(html|markdown|md)
: - Use bold to mark UI strings.
- If "" are used to mark UI strings, replace them by bold.
Pattern */**(html|markdown|md)
: - Be brief in your replies and don't add fluff like "thank you for..." and "Please let me know if"
Pattern */**(html|markdown|md)
: - Use sentence-style capitalization also in headings.
Pattern */**(html|markdown|md)
: do not comment on HTML used for icons
Pattern */**(html|markdown|md)
: Avoid flagging inline HTML for embedding videos in future reviews for this repository.
supervisor/const.py (6)
Pattern */**(html|markdown|md)
: - For instructional content in documentation, use a direct and authoritative tone. Avoid expressions of politeness such as 'may' or 'please', and ensure the goal of the instruction is fronted.
- Apply the Microsoft Style Guide to ensure documentation maintains clarity and conciseness.
- In step-by-step instructions, front the location phrase in the instructional sentence.
- In step-by-step instructions, front the 'goal' in the instructional sentence.
- In step-by-step instructions, if in doubt what to front, front the 'goal' before the location phrase in the instructional sentence.
- do not hyphenate terms like 'top-right' or 'bottom-left' with 'corner'
Pattern */**(html|markdown|md)
: - Use bold to mark UI strings.
- If "" are used to mark UI strings, replace them by bold.
Pattern */**(html|markdown|md)
: - Be brief in your replies and don't add fluff like "thank you for..." and "Please let me know if"
Pattern */**(html|markdown|md)
: - Use sentence-style capitalization also in headings.
Pattern */**(html|markdown|md)
: do not comment on HTML used for icons
Pattern */**(html|markdown|md)
: Avoid flagging inline HTML for embedding videos in future reviews for this repository.
tests/conftest.py (6)
Pattern */**(html|markdown|md)
: - For instructional content in documentation, use a direct and authoritative tone. Avoid expressions of politeness such as 'may' or 'please', and ensure the goal of the instruction is fronted.
- Apply the Microsoft Style Guide to ensure documentation maintains clarity and conciseness.
- In step-by-step instructions, front the location phrase in the instructional sentence.
- In step-by-step instructions, front the 'goal' in the instructional sentence.
- In step-by-step instructions, if in doubt what to front, front the 'goal' before the location phrase in the instructional sentence.
- do not hyphenate terms like 'top-right' or 'bottom-left' with 'corner'
Pattern */**(html|markdown|md)
: - Use bold to mark UI strings.
- If "" are used to mark UI strings, replace them by bold.
Pattern */**(html|markdown|md)
: - Be brief in your replies and don't add fluff like "thank you for..." and "Please let me know if"
Pattern */**(html|markdown|md)
: - Use sentence-style capitalization also in headings.
Pattern */**(html|markdown|md)
: do not comment on HTML used for icons
Pattern */**(html|markdown|md)
: Avoid flagging inline HTML for embedding videos in future reviews for this repository.
🔇 Additional comments (51)
tests/backups/test_backup.py (1)
13-13
: LGTM! The change maintains backward compatibility.
The addition of None
as the location parameter allows existing backup functionality to work as before.
supervisor/backups/const.py (2)
7-7
: LGTM! Clear and purposeful constant definition.
The new constant follows the file's organization pattern and its name clearly indicates its purpose for cloud backup storage.
7-7
: Verify access controls for the cloud backup directory.
Since this defines a new backup location that will contain potentially sensitive data, we should verify proper access controls are implemented.
Consider implementing the following security measures if not already in place:
- Restrict access to authorized users/components only
- Implement proper file permissions
- Ensure secure handling during file transfers
✅ Verification successful
Let me try another verification approach to check the security implementations around this backup location.
Cloud backup location has proper access controls implemented
Based on the code analysis, the cloud backup location has several security measures in place:
-
Access is restricted to Home Assistant only:
- The
_validate_cloud_backup_location
method ensures only Home Assistant can access the cloud backup location - Other components (including add-ons) are blocked with a 403 Forbidden response
- The
-
File system security:
- Cloud backups are stored in a dedicated directory separate from regular backups
- The path is mounted as a bind mount with proper container isolation
- The backup manager validates mount points before operations
-
API-level validation:
- All backup operations (create/restore/remove) validate the caller's permissions
- Home Assistant API state is checked before backup operations
The implementation provides adequate access controls for protecting sensitive backup data in the cloud backup location.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for access control implementations around the cloud backup location
# Look for access control checks in API endpoints
echo "Checking API endpoints for access controls:"
rg -A 5 "LOCATION_CLOUD_BACKUP" --type python
# Look for permission/security related code
echo "Checking for permission validations:"
ast-grep --pattern 'def $_(self, $$$):
$$$
if not $security_check:
raise $$$
$$$
$$$LOCATION_CLOUD_BACKUP$$$
$$$'
Length of output: 386
Script:
#!/bin/bash
# Search for all files containing LOCATION_CLOUD_BACKUP
echo "Files referencing LOCATION_CLOUD_BACKUP:"
rg -l "LOCATION_CLOUD_BACKUP"
# Look for security-related code around backup operations
echo -e "\nChecking backup operation implementations:"
rg -B 3 -A 7 "cloud_backup|LOCATION_CLOUD_BACKUP"
# Check for permission/access control related code in backup modules
echo -e "\nChecking for permission checks:"
rg -B 2 -A 5 "permission|access|auth" supervisor/backups/
Length of output: 26723
supervisor/homeassistant/const.py (2)
35-35
: LGTM! Verify version compatibility.
The new BACKUP_COMPLETE
websocket event type aligns well with the cloud backup feature requirements. However, we should verify the version compatibility setup.
✅ Verification successful
Version compatibility is properly configured
The BACKUP_COMPLETE
websocket event type is correctly set up with version compatibility in supervisor/homeassistant/websocket.py
with minimum version "2024.11.99". This aligns with the cloud backup feature timeline and follows the same pattern as other backup-related events (BACKUP_START, BACKUP_END).
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check websocket event version compatibility setup
# Expected: Find MIN_VERSION entry for BACKUP_COMPLETE
# Search for version configuration
rg -A 2 "MIN_VERSION.*=.*{" | rg -A 5 "BACKUP_COMPLETE"
# Check for other backup-related websocket handlers
ast-grep --pattern 'async def $_(self, $$$):
$$$
WSType.BACKUP_COMPLETE
$$$'
Length of output: 244
Script:
#!/bin/bash
# Search for version configuration with fixed regex
rg "MIN_VERSION\s*=\s*\{" -A 5
# Search for BACKUP_COMPLETE usage in handlers
rg "BACKUP_COMPLETE" -B 2 -A 2
# Find all files that might contain version configurations
fd "const.py|version.py"
Length of output: 2295
35-35
: Consider standardizing backup event naming patterns.
The existing backup events use simple paths (backup/start
, backup/end
) while the new event uses a longer path (backup/supervisor/backup_complete
). Consider standardizing the naming pattern:
- BACKUP_COMPLETE = "backup/supervisor/backup_complete"
+ BACKUP_COMPLETE = "backup/complete"
✅ Verification successful
Let me gather more specific information about the backup-related event patterns.
Standardize backup event naming pattern to maintain consistency
The verification confirms that existing backup events follow a simple two-level pattern (backup/start
, backup/end
), while the new event uses a three-level pattern (backup/supervisor/backup_complete
). For consistency, the suggestion to use backup/complete
is valid.
supervisor/homeassistant/const.py
: ChangeBACKUP_COMPLETE = "backup/supervisor/backup_complete"
toBACKUP_COMPLETE = "backup/complete"
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check existing websocket event naming patterns
# Expected: Find consistent patterns across similar events
# Search for all defined websocket event types
rg 'WSType\.' | rg -v "class"
# Look for handler implementations using these events
ast-grep --pattern 'class $_ {
$$$
WSType.$_ = "$_"
$$$
}'
Length of output: 2006
Script:
#!/bin/bash
# Search for backup event type definitions
rg -A 1 'class WSType'
# Search for backup event constants and their values
rg 'BACKUP_\w+\s*=\s*"[^"]+"'
# Search for backup event usage in the codebase
rg '"backup/[^"]+"'
Length of output: 1758
tests/resolution/fixup/test_mount_execute_remove.py (1)
49-57
: Verify mount state transitions
The test simulates multiple state transitions but doesn't verify the intermediate states during the mount cleanup process.
Let's verify if there are similar patterns in other test files:
Consider adding assertions for the intermediate states:
# Add before the mount_execute_remove() call
initial_mounts = len(coresys.mounts.mounts)
assert initial_mounts > 0, "Test should start with active mounts"
# Add after the mount_execute_remove() call but before the final assertions
assert all(state in ["active", "inactive"] for state in systemd_unit_service.active_state), \
"All states should be valid"
supervisor/api/const.py (3)
71-71
: LGTM! Verify usage of the new constant.
The addition of ATTR_USER_PATH
supports the new cloud backup feature by providing a way to track user-specific backup paths.
Let's verify the usage of this new constant:
✅ Verification successful
Constant is properly used in the mount management system
The ATTR_USER_PATH
constant is correctly integrated and used in the mount management system:
- Imported and used in
supervisor/api/mounts.py
to expose the container path information - Used to serialize mount information by converting the container path to POSIX format
- Implementation aligns with its intended purpose for the cloud backup feature
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check where ATTR_USER_PATH is being used
rg "ATTR_USER_PATH" --type python
Length of output: 64
Script:
#!/bin/bash
# Search for ATTR_USER_PATH without file type restriction since rg doesn't recognize python
rg "ATTR_USER_PATH"
Length of output: 239
Script:
#!/bin/bash
# Let's check the context of usage in mounts.py to understand how it's being used
rg "ATTR_USER_PATH" supervisor/api/mounts.py -B 5 -A 5
Length of output: 785
49-49
: LGTM! Verify usage of the new constant.
The addition of ATTR_LOCATIONS
aligns with the PR's objective to support multiple backup locations (local and cloud).
Let's verify the usage of this new constant:
✅ Verification successful
The new constant ATTR_LOCATIONS
is properly integrated and used
The constant is correctly imported and used in supervisor/api/backups.py
to expose backup locations in the API responses, which aligns with the PR's objective of supporting multiple backup locations. The usage appears in both detailed and summary backup information contexts, maintaining consistency across the API.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check where ATTR_LOCATIONS is being used
rg "ATTR_LOCATIONS" --type python
Length of output: 64
Script:
#!/bin/bash
# Search for ATTR_LOCATIONS without file type restriction
rg "ATTR_LOCATIONS"
# Also search for potential imports of this constant
rg "from supervisor.api.const import.*ATTR_LOCATIONS|import.*ATTR_LOCATIONS"
Length of output: 489
Script:
#!/bin/bash
# Let's check the context of usage in backups.py to ensure proper implementation
rg "ATTR_LOCATIONS" supervisor/api/backups.py -B 2 -A 2
Length of output: 743
Line range hint 1-85
: Verify the impact of removing ATTR_JOB_ID.
The AI summary indicates that ATTR_JOB_ID
was removed. This could impact existing functionality that relies on job tracking.
Let's verify if there are any remaining references to the removed constant:
supervisor/docker/const.py (3)
4-4
: LGTM! Good choice using PurePath
Using PurePath
is appropriate here as it provides platform-independent path handling without filesystem operations, which is ideal for constants.
97-97
: Clarify the purpose of duplicate config paths
The constants PATH_HOMEASSISTANT_CONFIG_LEGACY
and PATH_PUBLIC_CONFIG
both point to "/config". This could cause confusion for maintainers. Consider:
- Adding comments to explain the different use cases
- Documenting which one should be used in new code
- If
LEGACY
is truly legacy, consider adding a deprecation notice
Let's check how these paths are used:
Also applies to: 99-99
✅ Verification successful
Paths have distinct usage patterns in the codebase
Based on the search results, these paths serve different purposes:
PATH_HOMEASSISTANT_CONFIG_LEGACY
is used specifically in addon.py for addon configuration mappingPATH_PUBLIC_CONFIG
is used in both homeassistant.py and addon.py for general configuration access
The duplicate paths are intentional and used in different contexts, making the current implementation correct. No changes needed.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for usage of both config paths
echo "=== PATH_HOMEASSISTANT_CONFIG_LEGACY usage ==="
rg "PATH_HOMEASSISTANT_CONFIG_LEGACY" -A 2
echo "=== PATH_PUBLIC_CONFIG usage ==="
rg "PATH_PUBLIC_CONFIG" -A 2
Length of output: 1848
103-103
: Verify backup paths are properly isolated
The new backup paths (PATH_BACKUP
and PATH_CLOUD_BACKUP
) align with the PR objectives for implementing cloud backup support. However, we should verify that these paths are properly isolated and won't conflict with other mount points.
Let's check for potential path conflicts:
Also applies to: 106-106
✅ Verification successful
Let's gather more information about how these backup paths are used in the codebase.
This will help us understand:
- How backup paths are used in mount configurations
- The actual backup implementation's usage of these paths
- Any security measures around these paths
- Test cases that verify isolation
Let's check how these backup paths are used in mount configurations and backup operations to verify proper isolation.
This will help us understand:
- How mount points are configured and managed
- How backup paths are used in the backup system
- Any security checks around these paths
- Any potential path conflicts in mount configurations
Based on the analysis of the code, I can now provide a final response about the backup path isolation:
Backup paths are properly isolated and secured
The implementation shows proper isolation and security of backup paths through multiple layers:
-
Path Constants: The backup paths are defined as constants (
PATH_BACKUP
) and used consistently throughout the system. -
Security Middleware: The security middleware (
security.py
) implements proper access control:- Only users with
ROLE_BACKUP
or higher (ROLE_MANAGER, ROLE_ADMIN) can access/backups.*
endpoints - Path filtering prevents path traversal attacks
- Token validation ensures only authorized access
- Only users with
-
Mount Management:
- Backup mounts are handled through dedicated APIs
- The mount system validates mount points before use
- Default backup mount configuration is properly managed
-
Backup Implementation:
- Backup operations use relative paths within their context (e.g., "./backup.json")
- File operations are properly contained within their designated spaces
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for any mount configurations using these paths
echo "=== Backup path mount configurations ==="
rg -A 5 "Mount\(.*(/backup|/cloud_backup)"
# Search for any existing backup path references
echo "=== Existing backup path references ==="
rg -l "/backup|/cloud_backup" --type py
Length of output: 652
Script:
#!/bin/bash
# Check mount configurations and usage in key files
echo "=== Mount configurations in test files ==="
rg -A 5 "Mount.*backup" tests/
echo "=== Backup path usage in supervisor/backups ==="
rg -A 5 "(/backup|/cloud_backup)" supervisor/backups/
echo "=== Mount configurations in supervisor ==="
rg -A 5 "Mount.*backup" supervisor/
echo "=== Security middleware checks ==="
cat supervisor/api/middleware/security.py
Length of output: 82315
Script:
#!/bin/bash
# Check mount configurations and usage
echo "=== Mount configurations in supervisor ==="
rg -A 5 "Mount.*(/backup|/cloud_backup)" supervisor/
echo "=== Backup path usage in mount manager ==="
rg -A 5 "(/backup|/cloud_backup)" supervisor/mounts/
echo "=== Backup path usage in backup manager ==="
rg -A 5 "(/backup|/cloud_backup)" supervisor/backups/
echo "=== Security checks for backup paths ==="
rg -A 5 "(/backup|/cloud_backup)" supervisor/api/middleware/
Length of output: 12240
supervisor/docker/audio.py (2)
20-20
: LGTM! Good standardization of path constants.
The addition of PATH_PRIVATE_DATA
to the imports aligns with the codebase-wide effort to centralize and standardize path definitions.
52-52
: LGTM! Improved maintainability through constant usage.
Replacing the hardcoded "/data" with PATH_PRIVATE_DATA.as_posix()
enhances maintainability while preserving the existing functionality.
Let's verify this constant is used consistently across the codebase:
✅ Verification successful
Constant usage is consistent, but hardcoded paths still exist
The change to use PATH_PRIVATE_DATA.as_posix()
is correctly implemented in the reviewed file. The constant is defined in supervisor/docker/const.py
and used consistently in the few places where it's needed (audio.py
and addon.py
). However, there are still numerous hardcoded "/data" strings across the codebase, particularly in test files and frontend assets. Since these are primarily in test fixtures and compiled frontend assets, they don't impact the core functionality.
supervisor/docker/const.py
: DefinesPATH_PRIVATE_DATA = PurePath("/data")
- Used correctly in:
supervisor/docker/audio.py
supervisor/docker/addon.py
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for any remaining hardcoded "/data" paths and verify PATH_PRIVATE_DATA usage
# Check for hardcoded "/data" paths in Python files
rg -l '"/data"' '**/*.py'
# Check PATH_PRIVATE_DATA usage
ast-grep --pattern 'PATH_PRIVATE_DATA.as_posix()'
Length of output: 314
Script:
#!/bin/bash
# Let's try a more comprehensive search without file pattern restriction
# Check for hardcoded "/data" paths
rg -l '/data'
# Check for PATH_PRIVATE_DATA usage without .as_posix()
ast-grep --pattern 'PATH_PRIVATE_DATA'
# Get content of files containing PATH_PRIVATE_DATA for better context
rg -A 2 'PATH_PRIVATE_DATA'
Length of output: 10155
supervisor/api/mounts.py (2)
14-14
: LGTM: New constant import aligns with feature requirements
The addition of ATTR_USER_PATH
constant is well-placed and supports the new container path information needed for cloud backup functionality.
35-39
: Verify minimum Python version requirement
The use of the dict union operator (|
) requires Python 3.9+. Please ensure this requirement is documented and enforced in the project's setup.
tests/docker/test_homeassistant.py (2)
80-86
: LGTM: Backup mount configuration is correct.
The mount configuration for /backup
follows the established pattern for data directories with appropriate permissions and propagation settings.
82-83
: Verify mount path initialization.
Ensure that both path_extern_backup
and path_extern_core_backup
are properly initialized in CoreSys configuration and created with appropriate permissions.
Also applies to: 89-90
✅ Verification successful
Based on the gathered information from all the shell script executions, I can now provide a final response:
Mount paths are correctly initialized and managed.
The mount paths are properly handled through:
- Constants
BACKUP_DATA
andCORE_BACKUP_DATA
defined insupervisor/config.py
- Path properties
path_extern_backup
andpath_extern_core_backup
in Config class - Mount management through
MountManager
class with proper initialization and binding - Docker mount configuration with correct source paths and propagation modes
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for mount path initialization in CoreSys configuration
# Search for path initialization in CoreSys
echo "Searching for backup path initialization..."
rg -A 5 "path_extern_backup|path_extern_core_backup"
# Search for path creation
echo "Searching for backup path creation..."
rg -A 5 "mkdir.*backup"
Length of output: 3804
Script:
#!/bin/bash
# Let's check for the initialization of these paths by looking at the CoreSys setup
# Check for BACKUP_DATA and CORE_BACKUP_DATA constants
echo "Checking backup path constants..."
rg -p "BACKUP_DATA.*=|CORE_BACKUP_DATA.*="
# Check for path creation in CoreSys initialization
echo "Checking CoreSys initialization..."
ast-grep --pattern 'class CoreSys:
$$$
def __init__($$$):
$$$'
# Check for path setup in Config class
echo "Checking Config class initialization..."
ast-grep --pattern 'class Config:
$$$
def __init__($$$):
$$$'
Length of output: 31771
Script:
#!/bin/bash
# Let's check the Config class implementation and path setup
# Check Config class implementation
echo "Checking Config class implementation..."
rg -p "class Config.*" -A 10
# Check for path creation or setup in the codebase
echo "Checking path creation and setup..."
rg -p "def setup.*|async def setup.*" -A 10 supervisor/
# Check for mount initialization
echo "Checking mount initialization..."
rg -p "def _init_mount.*|async def _init_mount.*" -A 10
Length of output: 24123
Script:
#!/bin/bash
# Let's check for mount path creation and initialization
# Check for mount manager initialization
echo "Checking mount manager initialization..."
rg -p "class MountManager" -A 20
# Check for directory creation
echo "Checking directory creation..."
rg -p "makedirs.*backup|mkdir.*backup" -A 5
# Check for mount setup in supervisor startup
echo "Checking supervisor startup mount setup..."
rg -p "async def start" -A 20 supervisor/supervisor.py
Length of output: 1175
supervisor/store/data.py (2)
14-14
: LGTM! Consistent with the constant renaming.
The import statement correctly uses the renamed constant ATTR_LOCATION
, which is part of a broader cleanup to fix the spelling across multiple files.
226-226
: LGTM! Verify constant usage across codebase.
The usage of ATTR_LOCATION
is correct here, storing the add-on's parent directory path.
Let's verify the constant usage across the codebase to ensure all occurrences have been updated:
✅ Verification successful
Constant usage is consistent across the codebase
The verification shows that:
- No instances of the old misspelled constant
ATTR_LOCATON
exist - All usages of
ATTR_LOCATION
are consistent, defined insupervisor/const.py
and used appropriately across the codebase - Related constants like
ATTR_LOCATIONS
are properly differentiated
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for any remaining instances of the old constant name and verify the new one's usage
# Check for any remaining instances of the old misspelled constant
echo "Checking for any remaining ATTR_LOCATON instances..."
rg "ATTR_LOCATON"
# Check usage patterns of the corrected constant
echo "Verifying ATTR_LOCATION usage patterns..."
rg "ATTR_LOCATION"
Length of output: 2057
supervisor/docker/homeassistant.py (3)
25-30
: LGTM! Good practice using path constants.
The addition of path constants improves maintainability and reduces the risk of typos in path strings.
105-105
: LGTM! Consistent use of path constants in mount configurations.
The mount configurations have been properly updated to use the new path constants while maintaining the existing mount options and permissions.
Also applies to: 118-118, 124-124, 131-131
137-138
: Verify backup paths consistency across the codebase.
The mount paths look correct, but let's verify they match the paths used in the backup-related code:
Also applies to: 144-145
✅ Verification successful
Let me verify the backup paths consistency by searching for more usages of these paths in the codebase.
Based on the search results, I can verify that the backup paths are consistently defined and used across the codebase:
- The constants are defined in
supervisor/docker/const.py
:
PATH_BACKUP = PurePath("/backup")
PATH_CLOUD_BACKUP = PurePath("/cloud_backup")
- The configuration paths are defined in
supervisor/config.py
:
BACKUP_DATA = PurePath("backup")
CORE_BACKUP_DATA = PurePath("core/backup")
- These paths are consistently used in:
- supervisor/docker/homeassistant.py (mount points)
- supervisor/docker/addon.py (addon mounts)
- supervisor/backups/backup.py (backup operations)
- supervisor/mounts/mount.py (mount handling)
- supervisor/mounts/manager.py (mount management)
The mount paths in the code under review correctly use the defined constants and align with the paths used throughout the backup-related code.
Backup path configuration is consistent across the codebase
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Search for usages of backup paths in the codebase
rg -A 2 "path_extern_backup|path_extern_core_backup"
# Search for potential hardcoded backup paths that should use the new constants
rg -A 2 "/backup/|/\.cloud_backup/"
Length of output: 133257
Script:
#!/bin/bash
# Search for backup path constants and their usages
rg -A 2 "PATH_BACKUP|PATH_CLOUD_BACKUP"
# Search for backup path configurations
rg -A 2 "BACKUP_DATA|CORE_BACKUP_DATA"
Length of output: 24576
supervisor/mounts/manager.py (2)
218-219
: LGTM! Consistent with existing mount handling patterns
The backup mount handling is correctly integrated into the mount creation logic, following the same patterns as media and share mounts.
272-275
: Verify backup directory initialization
While the implementation follows the established pattern, we should verify that path_extern_backup
is properly initialized before binding. This helps prevent potential mounting issues if the directory doesn't exist.
Let's check how the backup directory is initialized:
✅ Verification successful
Let me gather more information about directory initialization.
Let me search for directory verification before mounting.
Verification of backup directory is properly handled
The implementation is correct as directory verification is handled at multiple levels:
- In
supervisor/mounts/mount.py
, the mount system verifies and creates directories before mounting:
if not self.local_where.exists():
self.local_where.mkdir(parents=True)
elif not self.local_where.is_dir():
raise MountInvalidError(...)
-
The backup directory path is derived from
path_extern_backup
which is initialized during system bootstrap along with other core directories, as seen in the test fixtures. -
The mount manager includes additional safety checks before binding mounts.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for backup directory initialization in the codebase
# Look for where path_extern_backup is created/initialized
# Search for path_extern_backup references
rg "path_extern_backup" -B 2 -A 2
# Search for backup directory creation patterns
ast-grep --pattern 'mkdir($$$backup$$$)'
Length of output: 1951
Script:
#!/bin/bash
# Search for directory creation/initialization patterns in the codebase
# Look for mkdir, exists_ok, Path.mkdir, etc.
# Search for directory creation patterns
rg -B 3 -A 3 "mkdir\(|exists_ok|makedirs"
# Search specifically for backup directory setup
rg -B 3 -A 3 "BACKUP_DATA|backup.*directory"
Length of output: 63595
Script:
#!/bin/bash
# Search for mount verification and directory checks
rg -B 3 -A 3 "is_dir|exists|mkdir" supervisor/mounts/
Length of output: 4071
supervisor/config.py (2)
53-53
: LGTM! The constant follows established patterns.
The CORE_BACKUP_DATA
constant is well-defined and consistent with other path constants in the file.
277-286
: Verify path creation during system initialization.
The properties are well-implemented and follow the established patterns. However, ensure that these new paths are properly created during system initialization, similar to other critical paths.
Let's verify the path creation in the bootstrap process:
✅ Verification successful
Path creation is properly handled in the bootstrap process
The verification shows that path_core_backup
is correctly initialized during system bootstrap, with appropriate logging and error handling:
# Core backup folder
if not config.path_core_backup.is_dir():
_LOGGER.debug("Creating Core backup folder at '%s", config.path_core_backup)
config.path_core_backup.mkdir(parents=True)
The implementation follows the same pattern as other critical system paths and includes proper directory creation with the parents=True
flag for reliability.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check if the new backup paths are created during initialization
# Search for path creation patterns in bootstrap-related files
rg -A 5 "mkdir|create_dir|exists" supervisor/bootstrap.py
Length of output: 4164
supervisor/const.py (2)
263-263
: LGTM! Addition aligns with cloud backup feature.
The new ATTR_PATH
constant is well-placed in alphabetical order and supports the cloud backup feature mentioned in the PR objectives.
231-231
: Verify the impact of the spelling correction.
The spelling correction from ATTR_LOCATON
to ATTR_LOCATION
is necessary, but we should verify all references are updated.
✅ Verification successful
The spelling correction is safe and all references are using the correct spelling
The verification shows that:
- No instances of the misspelled
ATTR_LOCATON
exist in the codebase - All references use the correct spelling
ATTR_LOCATION
- The constant is properly imported and used across multiple files in the supervisor module
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check for any remaining instances of the misspelled constant
# and verify all references are updated.
echo "Checking for any remaining instances of the misspelled constant..."
rg "ATTR_LOCATON"
echo "Verifying usage of the corrected constant..."
rg "ATTR_LOCATION"
Length of output: 2091
supervisor/mounts/mount.py (3)
Line range hint 155-165
: LGTM! Good performance optimization.
The addition of @cached_property
is a good optimization as the mount location is static during runtime. The implementation correctly handles path translation and safely checks for path containment.
173-180
: LGTM! Clean implementation of container mount path mapping.
The implementation correctly maps mount locations based on usage types, aligning with the cloud backup support requirements. The code is clean, maintainable, and handles all cases systematically.
✅ Verification successful
The mount path mapping implementation is correctly verified
The verification confirms:
- All PATH constants (PATH_BACKUP, PATH_MEDIA, PATH_SHARE) are properly defined in
supervisor/docker/const.py
- These paths are consistently used across the codebase:
- In mount path resolution (
supervisor/mounts/mount.py
) - In Docker container configurations for Home Assistant (
supervisor/docker/homeassistant.py
) - In addon configurations (
supervisor/docker/addon.py
) - In backup operations (
supervisor/backups/backup.py
)
- In mount path resolution (
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify PATH constants are correctly defined
# Check the imported PATH constants
rg "PATH_(BACKUP|MEDIA|SHARE)\s*=" supervisor/docker/const.py
# Verify these paths are used consistently across the codebase
rg "PATH_(BACKUP|MEDIA|SHARE)" -A 3
Length of output: 4674
167-172
: Verify Python version compatibility.
The implementation uses pattern matching (match
statement) which requires Python 3.10 or newer. Please ensure this is compatible with the project's minimum Python version requirement.
✅ Verification successful
Python version compatibility is verified
The project requires Python 3.12.0 or newer as specified in pyproject.toml. Pattern matching (match
statement) was introduced in Python 3.10, so the implementation is compatible with the project's minimum Python version requirement.
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Check Python version requirement in project configuration
# Check for Python version specification in setup.py or pyproject.toml
fd -e toml -e py -x grep -l "python" {} \; | xargs cat
# Check actual Python version used in CI
fd "workflow|action" .github/workflows -x grep -l "python" {} \; | xargs cat
Length of output: 69670
tests/api/test_backups.py (1)
5-5
: LGTM! Import changes are appropriate.
The new imports are correctly placed and necessary for the added test functionality.
Also applies to: 23-24
supervisor/addons/model.py (1)
50-50
: LGTM!
The addition of ATTR_LOCATION
constant follows the existing naming convention and is correctly placed in the alphabetically sorted import list.
tests/conftest.py (1)
421-421
: Verify the cloud backup directory path.
The addition of path_core_backup
directory creation aligns with the cloud backup feature. However, let's ensure this matches the intended .cloud_backup
location mentioned in the PR objectives.
✅ Verification successful
Path configuration for cloud backup is correctly implemented
The path_core_backup
directory creation aligns perfectly with the cloud backup implementation:
- The path is defined as
core/backup
in the supervisor config - It's properly mapped to
/cloud_backup
in Docker context - The location constant
.cloud_backup
is correctly referenced in the backup manager - The directory creation in the test fixture mirrors the production bootstrap process
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the cloud backup directory path configuration
# Expected: Find the path configuration in supervisor config
ast-grep --pattern 'path_core_backup = $_'
Length of output: 44
Script:
#!/bin/bash
# Let's try a broader search for backup path configurations
rg -l "path.*backup" --type py
# Then look for specific path configurations
rg "path_core_backup" --type py -A 2 -B 2
Length of output: 3302
Script:
#!/bin/bash
# Let's check the cloud backup location constant definition
rg "CORE_BACKUP_DATA|PATH_CLOUD_BACKUP" --type py -A 1 -B 1
# And check for any other related constants
rg "LOCATION_CLOUD_BACKUP.*=" --type py -A 1 -B 1
Length of output: 1889
tests/api/test_mounts.py (2)
84-84
: LGTM! Comprehensive test coverage for the new user_path
field.
The test cases thoroughly validate the user_path
field in mount responses across different scenarios:
- Backup mounts:
/backup/<mount_name>
- Media mounts:
/media/<mount_name>
Also applies to: 261-261, 331-331, 379-379, 836-836, 877-877
297-302
: LGTM! Enhanced error handling test coverage.
The mock data changes improve the test's ability to verify mount update failure scenarios by simulating a more comprehensive sequence of systemd unit states.
supervisor/docker/addon.py (6)
53-62
: LGTM! Path constants improve maintainability.
The addition of path constants centralizes path definitions and reduces the risk of typos or inconsistencies across the codebase.
347-347
: LGTM! Consistent path handling for data mount.
The change properly uses the new PATH_PRIVATE_DATA
constant as a fallback when no explicit path is provided.
358-359
: LGTM! Improved config mount handling with legacy support.
The changes properly handle both legacy and new config paths, ensuring backward compatibility while promoting the use of new path constants.
Also applies to: 372-372, 384-384
440-443
: LGTM! Consistent mount configuration for shared resources.
The implementation correctly uses the new path constant and propagation mode for the share mount.
452-455
: LGTM! Media mount configuration aligns with other mounts.
The media mount follows the same pattern as other mounts, using the new path constant and appropriate propagation mode.
428-431
: Verify the impact of RSLAVE propagation mode for backup mount.
The addition of PropagationMode.RSLAVE
for the backup mount means that mount events will propagate down from the host to the container. Ensure this is intentional and required for cloud backup functionality.
✅ Verification successful
RSLAVE propagation mode for backup mount is consistent with system design
The RSLAVE propagation mode for backup mounts is intentional and consistent across the codebase:
- Home Assistant container uses RSLAVE for all its mounts including backup (
supervisor/docker/homeassistant.py
) - All add-on mounts (backup, share, media) consistently use RSLAVE propagation (
supervisor/docker/addon.py
) - The propagation mode is required to ensure proper mount event propagation between host and containers for backup functionality, especially when dealing with network mounts and cloud backups
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Check if any other add-ons use different propagation modes for backup mounts
ast-grep --pattern 'Mount(
$$$
source=$_,
target=$_,
$$$
propagation=$_
$$$
)'
Length of output: 92
Script:
#!/bin/bash
# Search for mount configurations and propagation modes in the codebase
rg -A 5 "propagation.*=" --type py
# Also check for any Mount class usage patterns
rg -A 10 "class.*Mount" --type py
# Look for backup-related mount configurations
rg -A 5 "PATH_BACKUP|MappingType.BACKUP" --type py
Length of output: 19760
supervisor/backups/backup.py (1)
206-222
: LGTM! Well-implemented container path handling
The implementation is efficient and handles paths comprehensively:
- Uses
cached_property
for performance - Correctly handles relative paths with
is_relative_to
- Clear documentation of the functionality
- Proper handling of edge cases
supervisor/api/backups.py (6)
109-114
: Correct usage of vol.Inclusive
in SCHEMA_RELOAD
The SCHEMA_RELOAD
schema correctly uses vol.Inclusive
to ensure that ATTR_LOCATION
and ATTR_FILENAME
are provided together when reloading backups. The regular expression RE_BACKUP_FILENAME
effectively prevents directory traversal by disallowing path separators in the filename.
235-246
: Ensure proper access control in _validate_cloud_backup_location
The _validate_cloud_backup_location
method appropriately restricts access to the cloud backup location by checking if the request is from Home Assistant. The logic is sound and helps maintain security.
57-57
: Regular expression RE_BACKUP_FILENAME
effectively prevents path traversal
The regular expression RE_BACKUP_FILENAME
is correctly defined to match filenames that do not contain path separators and end with .tar
, mitigating the risk of directory traversal attacks.
17-17
: Imports are correctly updated to support new functionality
The added and corrected import statements, such as importing LOCATION_CLOUD_BACKUP
, ATTR_LOCATION
, and APIForbidden
, are appropriate and necessary for implementing the new cloud backup support and improved error handling.
Also applies to: 26-31, 42-42, 47-47, 51-51
278-281
: Proper validation of cloud backup location in backup and restore methods
The calls to _validate_cloud_backup_location
in the backup_full
, backup_partial
, restore_full
, restore_partial
, and remove
methods ensure that cloud backup locations are accessed only by authorized requests from Home Assistant. This enhances security and access control.
Also applies to: 299-302, 320-323, 338-341, 367-370
356-356
: Method signatures updated with type annotations for request
parameter
The methods freeze
, thaw
, download
, and upload
have updated signatures to include type annotations for the request
parameter. This improves code readability and type checking.
Also applies to: 362-362, 373-373, 386-386
tests/backups/test_manager.py (1)
2024-2031
: Duplicate comment: Make tests more robust by avoiding reliance on call_args_list
index
|
||
@property | ||
def locations(self) -> list[str | None]: | ||
"""Return locations this backup was found in except cloud backup (unless that's the only one).""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems a bit weird to me that we only show the cloud location if it is the only one, isn't that a bit inconsistent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just didn't want this list to be empty ever. If a backup exists exclusively in .cloud_backup
no one will be aware of it except core. They're the only ones who will know its slug to ask more info about it and figured it was fine to put that location in the list in this case. In all other cases I would prefer not to show .cloud_backup
as it will confuse all other clients who have no idea what that location is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just didn't want this list to be empty ever. [...] In all other cases I would prefer not to show .cloud_backup as it will confuse all other clients who have no idea what that location is.
Hm, ok other clients might not know about cloud backup, but maybe they should, so nothing funky happens accidentally (e.g. if something alters the backup file? Then maybe it is better if it has the chance to know?)
I mean, I get your arguments. But I think rather than adding this complexity, and potentially hiding something from other users which might be(come) important, why not just leave it here. If it turns out to be a problem, we can remove it then. 🤷♂️
Mount( | ||
type=MountType.BIND, | ||
source=self.sys_config.path_extern_backup.as_posix(), | ||
target=PATH_BACKUP.as_posix(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the bind mount for regular backup needed? And do we handle backups on external network storage as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because core will only use the location .cloud_backup
for cloud-only backups. If the user wants it stored locally or in a network mount core will use that location instead. And we'll provide core with a path to the file within /backup
to use for further processing in the final WS message.
I want it like this because of network mounts. I want to avoid writing backups to the local disk any time we can. So if the user wants a backup stored on their backup mount and google drive only then Supervisor will write it directly to the backup mount and core will read it out of there to upload it to google drive. No unnecessary writes on the local disk.
@@ -141,6 +141,15 @@ async def load(self) -> None: | |||
] | |||
) | |||
|
|||
# Bind all backup mounts to directories in backup |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see. I am a bit worried, so now we need to maintain another location where network storage are bind mounted 😰
Hm, why didn't we do that in first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly just because we didn't need to. There wasn't any reason to expose backup mounts to others because supervisor was doing all the operations on them. I'm not exactly sure how addons like Google Drive sync, etc. will handle it though, it depends on how they process the /backup
folder.
The only other options I could think of was bind mounting the entire /mounts
folder to supervisor into core's container. This would work but it seemed worse for a few reasons:
- All the share and media mounts are available in multiple places in core's container now
- A user could now muck with this container in an automation/script and really mess up supervisor (like if they deleted the folder)
- I don't know if we'll have other types of mounts in the future we want to keep more hidden
Exposing addons to backup network mounts felt less risky. I couldn't think of any reason we didn't do it to begin with other then "we didn't have to".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not exactly sure how addons like Google Drive sync, etc. will handle it though, it depends on how they process the
/backup
folder.
Hm, I see, that could actually have potential implications, e.g. if they browse often etc. it will lead to network traffic etc.
Instead of going to a separate mount, could we optionally mount without mount propagation for add-ons maybe? 🤔 Or make it opt-in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just for anyone else interested - tried this, did not work as we hoped. When propagation is set to rprivate
the addons can still see everything in the network mounts. It just means changes to backup mounts require a restart of the addon to pick them up. So it really just makes it inconsistent, doesn't help.
Why is the WS message even needed? Since Core triggers the backup, can't it simply wait for the command to complete and then take the backup? 🤔 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally LGTM, we can still tweak things as need until release 👍
Proposed change
This is an initial PR on what cloud backup support would look like in Supervisor after discussion with @emontnemery . Noted the backup and restore processes and the significant changes below for review.
The way a cloud backup will work from Supervisor's perspective is as follows:
/backup
or a backup mount, that will be thelocation
. If not (it is only a cloud backup) then core will use a newlocation
called.cloud_backup
hassio
backup sync agent in core/cloud_backup
then core deletes it. Otherwise it leaves itThe way a cloud restore will work from Supervisor's perspective is as follows:
/cloud_backup
/backups/reload
providing the location.cloud_backup
and the filename so Supervisor picks it upSignificant changes to make this function:
/backup
is now mapped into core's container read-write/backup
like share and media mounts. This will also affect all addons which mapbackup
/mnt/data/supervisor/core/backup
is mapped to Core's container at/cloud_backup
. This is the.cloud_backup
location.cloud_backup
location for backups on reload. However this location is hidden from the info APIs in/backups
as only Core can see it and Supervisor expects backups in it to be transient. Additionally API requests which seek to make backups in this location or interact with backups in this location will be rejected with a 403 Forbidden if not from CoreSmaller detail - backups may exist in multiple locations Supervisor sees. This has always been true. Prior to this PR if we found two backups with the same slug during reload the last one was all we remembered. Now the first one is remembered and we keep track of all the locations we found it to return that info from the API.
Type of change
Additional information
Checklist
ruff format supervisor tests
)If API endpoints or add-on configuration are added/changed:
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests
Documentation