Skip to content

Conversation

@davidrapan
Copy link
Contributor

@davidrapan davidrapan commented May 12, 2025

Proposed change

Hi,
I access my HA instances through nginx and purely using IPv6. But since it is exposed like this to the outside, I have to use the Ban IP feature, which currently doesn't work because nginx communicates with the HA instance using IPv4 and the forward header doesn't work because it refuses to mix these families for some reason (therefore it's banning docker IP of the container). :)

I have this well tested for many months already and there are no side effects to this configuration.

Step I used to reconfigure existing hassio instance (it requires to run the image directly):

for ID in $(docker ps --format '{{ .ID }}'); do docker stop $ID; done
for ID in $(docker ps -a --format '{{ .ID }}'); do docker rm $ID; done
docker network rm hassio
docker network create hassio -d bridge --ipv6 --subnet fd0c::/64 --subnet 172.30.32.0/23 --ip-range 172.30.33.0/24 --gateway 172.30.32.1 --opt com.docker.network.bridge.name=hassio
reboot

After restart, all containers will also receive an IPv6 address that they can use for outgoing connections but the most important thing is that Ban IP now sees the real IPv6 of the connected client.

Link to my FR: https://community.home-assistant.io/t/docker-hassio-network-with-ipv6-enabled-by-default/760739 I made a while ago but didn't had the chance to look into supervisor until now.

Type of change

  • Dependency upgrade
  • Bugfix (non-breaking change which fixes an issue)
  • New feature (which adds functionality to the supervisor)
  • Breaking change (fix/feature causing existing functionality to break)
  • Code quality improvements to existing code or addition of tests

Additional information

Checklist

  • The code change is tested and works locally.
  • Local tests pass. Your PR cannot be merged unless tests pass
  • There is no commented out code in this PR.
  • I have followed the development checklist
  • The code has been formatted using Ruff (ruff format supervisor tests)
  • Tests have been added to verify that the new code works.

If API endpoints or add-on configuration are added/changed:

Summary by CodeRabbit

  • New Features

    • Added support for enabling or disabling IPv6 for Docker networks via the API and configuration.
    • Docker network settings now include an option to toggle IPv6 support.
    • Asynchronous initialization of Docker network with IPv6-aware migration and container attachment improvements.
    • Introduced Docker container naming constants for Supervisor and Observer components.
  • Bug Fixes

    • Standardized naming and usage of network mask constants for improved clarity and consistency.
    • Enhanced error handling in Docker network operations.
  • Documentation

    • Updated docstrings and comments to consistently use correct "IPv4" and "IPv6" capitalization.
  • Tests

    • Introduced new tests to verify Docker network IPv6 enablement and network recreation logic.
    • Updated existing tests to reflect changes in constants and improve documentation clarity.

@davidrapan davidrapan changed the title feat: Enable IPv6 for containers Enable IPv6 for containers May 12, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 12, 2025

📝 Walkthrough

Walkthrough

The changes introduce support for IPv6 in the Supervisor's Docker network management. Constants and configuration schemas are updated to distinguish between IPv4 and IPv6 network masks, and new options for enabling IPv6 are added. Network creation, migration, and API endpoints are modified to handle IPv6 settings, with corresponding tests and documentation updates.

Changes

File(s) Change Summary
supervisor/const.py Added and renamed constants for IPv4/IPv6 network masks, observer/supervisor container names, network driver, and IPv6 flag.
supervisor/docker/network.py Refactored for async network init, IPv6-aware migration, container attach-by-name, and enhanced error handling.
supervisor/docker/manager.py Added enable_ipv6 property to config; async network init now uses IPv6 flag from config.
supervisor/api/docker.py Added schema and API endpoint for Docker IPv6 options; info endpoint now returns IPv6 status.
supervisor/api/init.py Registered new /docker/options route; logs handler uses supervisor name constant.
supervisor/api/network.py Updated to use new IPv4 network mask constant.
supervisor/docker/observer.py Uses imported observer name and IPv4 mask constants; removed local constant.
supervisor/misc/filter.py Updated to use new IPv4 network mask constant.
supervisor/validate.py Added optional IPv6 enable field to Docker config schema; comment capitalization fix.
supervisor/dbus/network/connection.py Standardized docstring capitalization for IPv4/IPv6 setters.
supervisor/dbus/network/setting/init.py Capitalized docstrings for IPv4/IPv6 property methods.
tests/api/test_network.py Updated to use new IPv4 network mask constant in assertions.
tests/api/test_docker.py Added test for Docker network IPv6 enable API behavior.
tests/docker/test_network.py New tests for Docker network recreation with IPv6 support and error scenarios.
tests/dbus/network/test_connection.py Capitalized docstrings for IPv4/IPv6 disconnect tests.
tests/dbus/network/test_ip_configuration.py Capitalized docstrings for IPv4/IPv6 configuration tests.
tests/test_validate.py Updated test function signatures and docstrings for IPv4/IPv6 DNS URL validation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API
    participant DockerConfig
    participant DockerNetwork

    User->>API: POST /docker/options ({"enable_ipv6": true})
    API->>DockerConfig: Set enable_ipv6 = true
    DockerConfig->>API: Save config
    API-->>User: 200 OK

    User->>API: GET /docker/info
    API->>DockerConfig: Get enable_ipv6
    API-->>User: {"enable_ipv6": true, ...}

    API->>DockerNetwork: post_init(enable_ipv6)
    DockerNetwork->>DockerNetwork: _get_network(enable_ipv6)
    DockerNetwork->>DockerNetwork: Create/migrate network with IPv6 as needed
Loading
sequenceDiagram
    participant DockerNetwork
    participant Docker
    participant SupervisorContainer
    participant ObserverContainer

    DockerNetwork->>Docker: networks.get(name)
    alt Network IPv6 setting needs change and only Supervisor/Observer attached
        DockerNetwork->>Docker: networks.disconnect(Supervisor)
        DockerNetwork->>Docker: networks.disconnect(Observer)
        DockerNetwork->>Docker: networks.remove()
        DockerNetwork->>Docker: networks.create(enable_ipv6)
        DockerNetwork->>Docker: networks.connect(Supervisor)
        DockerNetwork->>Docker: networks.connect(Observer)
    else Network has other containers
        DockerNetwork->>DockerNetwork: Log warning, skip migration
    end
Loading

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Pylint (3.3.7)
supervisor/dbus/network/setting/__init__.py

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 089e8b6 and 6105c43.

📒 Files selected for processing (1)
  • supervisor/dbus/network/setting/__init__.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • supervisor/dbus/network/setting/init.py
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: Build amd64 supervisor
  • GitHub Check: Build armhf supervisor
  • GitHub Check: Build i386 supervisor
  • GitHub Check: Build aarch64 supervisor
  • GitHub Check: Build armv7 supervisor
  • GitHub Check: Run tests Python 3.13.4
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
supervisor/docker/network.py (1)

102-130: Consider adding IPv6 address support to the attach_container method.

The current implementation only allows specifying an IPv4 address when attaching a container. For complete IPv6 support, consider enhancing this method to also accept an optional IPv6 address parameter.

def attach_container(
    self,
    container: docker.models.containers.Container,
    alias: list[str] | None = None,
    ipv4: IPv4Address | None = None,
+   ipv6: IPv6Address | None = None,
) -> None:
    """Attach container to Supervisor network.

    Need run inside executor.
    """
    ipv4_address = str(ipv4) if ipv4 else None
+   ipv6_address = str(ipv6) if ipv6 else None

    # Reload Network information
    with suppress(docker.errors.DockerException, requests.RequestException):
        self.network.reload()

    # Check stale Network
    if container.name and container.name in (
        val.get("Name") for val in self.network.attrs.get("Containers", {}).values()
    ):
        self.stale_cleanup(container.name)

    # Attach Network
    try:
-       self.network.connect(container, aliases=alias, ipv4_address=ipv4_address)
+       self.network.connect(container, aliases=alias, ipv4_address=ipv4_address, ipv6_address=ipv6_address)
    except docker.errors.APIError as err:
        raise DockerError(
            f"Can't link container to hassio-net: {err}", _LOGGER.error
        ) from err
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9bbb99 and 6100702.

📒 Files selected for processing (6)
  • supervisor/api/network.py (2 hunks)
  • supervisor/const.py (2 hunks)
  • supervisor/docker/network.py (3 hunks)
  • supervisor/docker/observer.py (2 hunks)
  • supervisor/misc/filter.py (2 hunks)
  • tests/api/test_network.py (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
`*/**(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 t...

*/**(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'
  • supervisor/const.py
`*/**(html|markdown|md)`: - Use bold to mark UI strings. - If "" are used to mark UI strings, replace them by bold.

*/**(html|markdown|md): - Use bold to mark UI strings.

  • If "" are used to mark UI strings, replace them by bold.
  • supervisor/const.py
`*/**(html|markdown|md)`: - Be brief in your replies and don't add fluff like "thank you for..." and "Please let me know if"

*/**(html|markdown|md): - Be brief in your replies and don't add fluff like "thank you for..." and "Please let me know if"

  • supervisor/const.py
`*/**(html|markdown|md)`: - Use sentence-style capitalization also in headings.

*/**(html|markdown|md): - Use sentence-style capitalization also in headings.

  • supervisor/const.py
`*/**(html|markdown|md)`: do not comment on HTML used for icons

*/**(html|markdown|md): do not comment on HTML used for icons

  • supervisor/const.py
`*/**(html|markdown|md)`: Avoid flagging inline HTML for embedding videos in future reviews for this repository.

*/**(html|markdown|md): Avoid flagging inline HTML for embedding videos in future reviews for this repository.

  • supervisor/const.py
🔇 Additional comments (11)
supervisor/misc/filter.py (1)

12-12: Correctly updated import statement for IPv4 specificity.

The import statement has been properly updated to use the newly renamed IPv4-specific constant, which is consistent with the IPv6 enablement changes.

tests/api/test_network.py (2)

9-9: Correctly updated import statement.

The import statement has been properly updated to use the newly renamed IPv4-specific constant.


62-62: Test correctly updated to match implementation changes.

The assertion has been updated to check that the API returns the IPv4 network mask, matching the implementation changes.

supervisor/docker/observer.py (1)

5-5: Correctly updated import statement.

The import statement has been properly updated to use the newly renamed IPv4-specific constant.

supervisor/api/network.py (1)

41-41: Correctly updated import statement.

The import statement has been properly updated to use the newly renamed IPv4-specific constant.

supervisor/const.py (2)

5-5: Addition of IPv6Network import is correct for implementing IPv6 support.

The new import of IPv6Network alongside the existing IPv4Network is necessary to enable IPv6 networking in the supervisor.


44-46: LGTM: Clear naming convention for network constants.

The changes establish a clear naming pattern by:

  1. Adding a new IPv6 network mask constant with the unique IPv6 ULA prefix (fd0c::/64)
  2. Renaming existing IPv4 constants to explicitly include "IPV4" in their names

This approach correctly maintains backward compatibility while adding the new IPv6 functionality.

supervisor/docker/network.py (4)

10-15: LGTM: Updated imports align with the renamed constants.

The imports have been correctly updated to reference the new, more explicit constant names from const.py.


50-50: LGTM: Updated all IPv4 references to use the new constant name.

All network property references now consistently use the explicitly named DOCKER_IPV4_NETWORK_MASK constant.

Also applies to: 55-55, 60-60, 65-65, 70-70, 75-75


84-92: LGTM: Properly configured both IPv6 and IPv4 IPAM pools.

The implementation correctly:

  1. Creates a separate IPv6 IPAM pool using the defined IPv6 network mask
  2. Maintains the existing IPv4 IPAM pool configuration with subnet, gateway and IP range
  3. Combines both pools into a single IPAM configuration

This approach follows Docker networking best practices for dual-stack IPv4/IPv6 configuration.


98-98: LGTM: Enabled IPv6 in network creation.

Setting enable_ipv6=True is essential for the Docker network to accept and route IPv6 traffic.

Copy link
Member

@agners agners left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I access my HA instances through nginx and purely using IPv6.

By nginx you mean the official NGINX proxy add-on?

But since it is exposed like this to the outside, I have to use the Ban IP feature, which currently doesn't work because nginx communicates with the HA instance using IPv4 and the Forward header doesn't work because it refuses to mix these families (which is then banning docker IP of the container). :)

Thanks for explaining your specific use case here! 🙏

I was wondering if X-Forwarded-For indeed doesn't allow mixing IP addresses, but that doesn't seem the problem per-se, even the X-Forwarded-For examples at mdn show headers with mixed address families. I also don't think this is a NGINX problem. This is purely a Docker problem: We expose the outside IP address using Docker, which seemingly makes it look like originating from the Docker network's hassio host IP (172.30.32.1). Presumably this is just because Docker uses docker-proxy by default like so:

/usr/bin/docker-proxy -proto tcp -host-ip :: -host-port 1443 -container-ip 172.30.33.6 -container-port 443 -use-listen-fd

Side quest: Interestingly enough there is also an IPv4 docker-proxy running, so why does it work for IPv4 then? Shouldn't that also lead to a masqueraded IPv4 source IP? It turns out that besides the proxy, there is also a iptables rule:

iptables -t nat -L DOCKER -n -v
...
    8   480 DNAT       tcp  --  !hassio *       0.0.0.0/0            0.0.0.0/0            tcp dpt:1443 to:172.30.33.6:443

So it seem the IPv4 proxy is only a fallback 🤔 🤷

Anyhow, I can see why using IPv6 can help here, even in the NAT case: This probably makes Docker use the same DNAT rules for IPv6 as well, hence the source IP is no longer masqueraded.

That said, I am a bit worried what it does for users without IPv6 connectivity: IP client software running in add-on who try to connect to hosts with IPv6 addresses (where the nameserver resolved AAAA) might try connecting to those hosts using IPv6. Maybe this is not a (big) problem because the add-on use ULA address range and clients will ultimately fallback to IPv4. But there is some risk of things not working as expected, I think. Ideally we make that feature an opt-in first maybe? 🤔

@davidrapan
Copy link
Contributor Author

davidrapan commented May 12, 2025

By nginx you mean the official NGINX proxy add-on?

Yes.

I was wondering if X-Forwarded-For indeed doesn't allow mixing IP addresses..

The thing is, when I initially investigated the problem, I even logged the packet headers and they just weren't there, so something is going wrong. I just thought let's try give the containers IPv6 and let's see.. and it worked so my conclusion was nginx is not handing over v6 (but who knows if nginx even know the source IP, proly not) to a server on v4 (in other words, out-of-spec behavior no matter who's to blame here).

So when is the whole traffic after the changes on v6 it very likely hides some problem somewhere (docker-proxy is a strong candidate) but I wasn't able to find out more.

Anyhow, I can see why using IPv6 can help here, even in the NAT case: This probably makes Docker use the same DNAT rules for IPv6 as well, hence the source IP is no longer masqueraded.

Yes, it's easy to see when observing the traffic there.

That said, I am a bit worried what it does for users without IPv6 connectivity: IP client software running in add-on who try to connect to hosts with IPv6 addresses (where the nameserver resolved AAAA) might try connecting to those hosts using IPv6. Maybe this is not a (big) problem because the add-on use ULA address range and clients will ultimately fallback to IPv4. But there is some risk of things not working as expected, I think. Ideally we make that feature an opt-in first maybe? 🤔

This can't be a problem practically, because containers don't get a default gateway to v6. So there are no attempts to initiate any v6 connections, except when they need to respond, etc. But I thought about it and tried hassio configured like this in a v4-only network and didn't experience any issues whatsoever.

Hmmm, it would be ideal but I guess it would need hassio network bridge re-initialization (like I did manually), right?

@davidrapan davidrapan requested a review from agners May 12, 2025 18:17
@agners
Copy link
Member

agners commented May 12, 2025

This can't be a problem practically, because containers don't get a default gateway to v6. So there are no attempts to initiate any v6 connections, except when they need to respond, etc. But I thought about it and tried hassio configured like this in a v4-only network and didn't experience any issues whatsoever.

Hm, yeah I guess no gateway will make any connection attempt fail immediately, and likely cause to try out the next address (family). But the exact behavior is always a bit tricky, depends on how well the client code is written.

Hmmm, it would be ideal but I guess it would need hassio network bridge re-initialization (like I did manually), right?

Yeah the current code essentially doesn't migrate existing installations. Which maybe is actually not a bad thing, just to see how things work first... 🤔

But ultimately we should migrate existing installations.

@davidrapan
Copy link
Contributor Author

davidrapan commented May 12, 2025

But the exact behavior is always a bit tricky, depends on how well the client code is written.

Yeah, everything around IP stack is a bit tricky sometimes.. heh.

Yeah the current code essentially doesn't migrate existing installations. Which maybe is actually not a bad thing, just to see how things work first... 🤔

I think so too and as you said this change does not affect current users in any way, so far.

But ultimately we should migrate existing installations.

Maybe it's doable during DockerNetwork._get_network call, what do you think?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
tests/docker/test_network.py (4)

10-16: Add docstrings to the mock class and methods.

The MockINetwork class is missing docstrings as flagged by the static analysis. While this is a test-only class, adding documentation would improve maintainability.

-class MockINetwork(dict):
+class MockINetwork(dict):
+    """Mock implementation of Docker network that returns False for all attributes."""
    def __init__(self):
+        """Initialize mock network."""
        pass

    def __getattr__(self, attr: str):
+        """Return False for any attribute access."""
        return False
🧰 Tools
🪛 Ruff (0.8.2)

10-10: Missing docstring in public class

(D101)


11-11: Missing docstring in __init__

(D107)


14-14: Missing docstring in magic method

(D105)


10-12: Consider whether dict inheritance is necessary.

The class inherits from dict but doesn't use any dictionary functionality. Consider removing the inheritance if not needed.

-class MockINetwork(dict):
+class MockINetwork:
🧰 Tools
🪛 Ruff (0.8.2)

10-10: Missing docstring in public class

(D101)


11-11: Missing docstring in __init__

(D107)


27-27: Add descriptive assertion message.

Adding an error message to the assertion would make test failures more informative.

-        mock_create.assert_called_with(enable_ipv6=True)
+        mock_create.assert_called_with(enable_ipv6=True, 
+            msg="Network should be recreated with IPv6 enabled")

18-27:

❓ Verification inconclusive

Consider extending test coverage.

The test only verifies that the network is created with IPv6 enabled. Consider adding assertions for other important parameters (like subnet configurations, gateway settings, etc.) that would be part of an IPv6-enabled network.


🏁 Script executed:

#!/bin/bash
# Search for the create method usage in the network module to understand what other parameters might be important
grep -r "create.*enable_ipv6" --include="*.py" supervisor/docker/

Length of output: 67


🏁 Script executed:

#!/bin/bash
# Locate the NetworkCollection class definition
grep -R -n "class NetworkCollection" --include="*.py" supervisor/docker

echo "----"

# Locate the create method implementations in the docker module
grep -R -n "def create" --include="*.py" supervisor/docker

Length of output: 151


🏁 Script executed:

#!/bin/bash
# Search for NetworkCollection class and its create method across the repo
grep -R -n "class NetworkCollection" --include="*.py" --exclude-dir={tests,node_modules,build,dist} .

echo "----"

grep -R -n "def create" --include="*.py" --exclude-dir={tests,node_modules,build,dist} .

Length of output: 1448


Extend IPv6 network creation test coverage

The current test only verifies that create is called with enable_ipv6=True. To ensure the network is created correctly for IPv6, please update the test to assert any additional parameters your create implementation supports—for example:

• IPAM configuration (subnet, gateway, IP range)
• Driver options or internal flags
• Network name, labels, or other relevant settings

Locations to update:

  • tests/docker/test_network.py, lines 18–27
  • Replace or augment the existing
    mock_create.assert_called_with(enable_ipv6=True)
    with something like
    mock_create.assert_called_with(
        enable_ipv6=True,
        ipam_config={"Subnet": "2001:db8::/64", "Gateway": "2001:db8::1"},
        internal=False,
        driver_opts={…},
        name="my_ipv6_network",
        # etc.
    )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a29c5ec and ede24eb.

📒 Files selected for processing (2)
  • supervisor/docker/network.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • supervisor/docker/network.py
🧰 Additional context used
🪛 Ruff (0.8.2)
tests/docker/test_network.py

10-10: Missing docstring in public class

(D101)


11-11: Missing docstring in __init__

(D107)


14-14: Missing docstring in magic method

(D105)

🔇 Additional comments (1)
tests/docker/test_network.py (1)

1-9: LGTM! Clear imports for testing Docker network functionality.

The imports are appropriate for a test file focusing on the Docker network manager, including the necessary mock utilities and Docker classes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/docker/test_network.py (2)

10-16: Add docstrings to the MockINetwork class.

The MockINetwork class is missing docstrings that would explain its purpose and behavior. Since this is a mock class used for testing, it's important to document what aspects of a real network it's simulating and how it behaves.

 class MockINetwork(dict):
+    """Mock implementation of Docker internal network."""
     def __init__(self):
+        """Initialize the mock network."""
         pass

     def __getattr__(self, attr: str):
+        """Return False for any attribute to simulate a network without IPv6 enabled."""
         return False
🧰 Tools
🪛 Ruff (0.8.2)

10-10: Missing docstring in public class

(D101)


11-11: Missing docstring in __init__

(D107)


14-14: Missing docstring in magic method

(D105)


18-27: Test validates IPv6 enablement but could be more comprehensive.

The test correctly verifies that create is called with enable_ipv6=True when recreating a network, which aligns with the PR objective of enabling IPv6 support. However, it doesn't verify other aspects of network creation such as IPAM configuration for both IPv4 and IPv6 subnets.

You might want to extend the test to verify more aspects of the network creation by checking additional parameters passed to the create method:

def test_network_recreate_as_ipv6_with_full_config(docker: DockerAPI):
    """Test that network recreation includes proper IPv4 and IPv6 configuration."""

    with (
        patch.object(NetworkCollection, "get", return_value=MockINetwork()),
        patch.object(NetworkCollection, "create") as mock_create,
    ):
        docker.network._get_network()

        # Verify IPv6 is enabled
        mock_create.assert_called_once()
        args, kwargs = mock_create.call_args
        assert kwargs["enable_ipv6"] is True
        
        # Verify IPAM configuration for both IPv4 and IPv6
        assert "ipam" in kwargs
        ipam_config = kwargs["ipam"]["Config"]
        assert len(ipam_config) == 2  # Should have both IPv4 and IPv6 pools
        
        # Verify IPv4 subnet
        assert any(config.get("Subnet", "").startswith("172.30.32.") for config in ipam_config)
        
        # Verify IPv6 subnet
        assert any(config.get("Subnet", "").startswith("fd00:dead:beef::") for config in ipam_config)

Note: You should adjust the subnet prefixes to match the actual expected values from your implementation.

🛑 Comments failed to post (1)
tests/docker/test_network.py (1)

18-27: 💡 Verification agent

🧩 Analysis chain

Consider testing the migration path for existing installations.

Based on the PR objectives, there was discussion about how to handle migration for existing installations. This test verifies that when a network exists without IPv6, it will be recreated with IPv6 enabled, which is good. However, it might be beneficial to add tests for other scenarios:

  1. What happens when the network already has IPv6 enabled?
  2. How are existing containers handled during network recreation?
  3. Are there any error conditions during migration that should be gracefully handled?

🏁 Script executed:

#!/bin/bash
# Look for error handling code in the network recreation logic
rg -A 5 -B 5 "_get_network" --glob "*.py" | grep -i "except\|error\|fail"

Length of output: 73


🏁 Script executed:

#!/bin/bash
# Locate and inspect the implementation of the _get_network method to check for any error handling
rg -n "def _get_network" -A 20 -B 5 --glob "*.py"

Length of output: 1885


Add tests for all migration scenarios in tests/docker/test_network.py

Currently only the “existing network without IPv6” path is covered. Please add tests for:

• Existing network already IPv6-enabled
– patch NetworkCollection.get to return a network where EnableIPv6=True
– call docker.network._get_network()
– assert neither network.remove() nor create() is invoked

• Network not found (migration from pre-IPv6 install)
– patch NetworkCollection.get to raise docker.errors.NotFound
– call docker.network._get_network()
– assert create(enable_ipv6=True) is invoked

• Error during migration
– patch NetworkCollection.get to return non-IPv6 network and network.remove() to raise docker.errors.APIError
– call docker.network._get_network()
– assert the exception is propagated or logged as appropriate

These will ensure coverage for all branches in _get_network.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/docker/test_network.py (1)

10-16: Add docstrings to the mock class for better documentation.

The MockINetwork class is missing docstrings, which would improve code readability and maintainability. Consider adding class and method docstrings explaining its purpose as a mock returning False for all attributes.

class MockINetwork(dict):
+    """Mock implementation of a Docker network object for testing.
+    
+    Returns False for all attribute access to simulate a network 
+    without IPv6 or other required features.
+    """
    def __init__(self):
+        """Initialize mock network."""
        pass

    def __getattr__(self, _: str):
+        """Return False for any attribute access."""
        return False
🧰 Tools
🪛 Ruff (0.8.2)

10-10: Missing docstring in public class

(D101)


11-11: Missing docstring in __init__

(D107)


14-14: Missing docstring in magic method

(D105)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 29b4b36 and bcf91f8.

📒 Files selected for processing (2)
  • supervisor/docker/network.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • supervisor/docker/network.py
🧰 Additional context used
🪛 Ruff (0.8.2)
tests/docker/test_network.py

10-10: Missing docstring in public class

(D101)


11-11: Missing docstring in __init__

(D107)


14-14: Missing docstring in magic method

(D105)

🔇 Additional comments (2)
tests/docker/test_network.py (2)

1-8: Good job on setting up the test module!

The module docstring and imports are well-organized, following the Python standard of ordering imports (standard library, third-party, local).


22-23: Good job fixing the patch approach!

The code correctly uses patch.object() for patching the NetworkCollection methods, which is the appropriate approach when patching methods of imported classes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
tests/docker/test_network.py (1)

27-46: 💡 Verification agent

🧩 Analysis chain

The test verifies IPv6 enablement but lacks IPAM configuration assertions.

While the test correctly verifies that create_network is called with enable_ipv6=True, it doesn't verify the IPAM configuration that would be crucial for proper IPv6 addressing. Consider enhancing the test to assert the IPAM configuration as well.


🏁 Script executed:

#!/bin/bash
# Description: Check how IPAM is configured in network creation

# Look for IPAM configuration in the network.py file
rg -A 15 "create_network" supervisor/docker/network.py

Length of output: 1182


🏁 Script executed:

#!/bin/bash
# Locate where ipam_config is defined in the network module
rg -n "ipam_config" supervisor/docker/network.py -A5

Length of output: 567


Assert IPAM configuration in test_network.py

The test currently verifies enable_ipv6 and options but omits the ipam argument passed to create_network. Please extend the assertion to include IPAM.

• File: tests/docker/test_network.py
Location: around the mock_create.assert_called_with(...) call

Suggested change:

 from unittest.mock import ANY

 mock_create.assert_called_with(
     DOCKER_NETWORK,
     driver="bridge",
-    enable_ipv6=True,
-    options={"com.docker.network.bridge.name": DOCKER_NETWORK},
+    ipam=ANY,
+    enable_ipv6=True,
+    options={"com.docker.network.bridge.name": DOCKER_NETWORK},
 )

You can also add more specific assertions on mock_create.call_args.kwargs["ipam"].pool_configs if you need to validate the IPv4/IPv6 subnets.

🧹 Nitpick comments (1)
tests/docker/test_network.py (1)

3-3: Remove unused import MagicMock.

The MagicMock import is not used anywhere in this file. Consider removing it to keep the imports clean.

-from unittest.mock import MagicMock, patch
+from unittest.mock import patch
🧰 Tools
🪛 Ruff (0.8.2)

3-3: unittest.mock.MagicMock imported but unused

Remove unused import: unittest.mock.MagicMock

(F401)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 813d915 and cc33b21.

📒 Files selected for processing (2)
  • supervisor/docker/network.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • supervisor/docker/network.py
🧰 Additional context used
🪛 Ruff (0.8.2)
tests/docker/test_network.py

3-3: unittest.mock.MagicMock imported but unused

Remove unused import: unittest.mock.MagicMock

(F401)

🔇 Additional comments (2)
tests/docker/test_network.py (2)

10-19: LGTM! The mock implementation for IPv6 testing.

The MockINetwork class correctly simulates a network without IPv6 by returning False for any attribute. This is an effective way to test the system's behavior when IPv6 needs to be enabled.


24-25: The test docstring has been improved as suggested in previous review.

The docstring now clearly states the purpose of testing network recreation with IPv6 enabled when needed.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
supervisor/docker/network.py (1)

78-80: Missing docstring in get method.

The new public method is missing a docstring explaining its purpose.

def get(self, network_id: str) -> docker.models.networks.Network:
+    """Get a Docker network by ID or name."""
    return self.docker.networks.get(network_id)
🧰 Tools
🪛 Ruff (0.8.2)

78-78: Missing docstring in public method

(D102)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8cb2f0f and 9f6e121.

📒 Files selected for processing (2)
  • supervisor/docker/network.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/docker/test_network.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
supervisor/docker/network.py (3)
supervisor/docker/manager.py (3)
  • docker (133-137)
  • network (140-144)
  • info (162-166)
tests/docker/test_network.py (1)
  • remove (18-19)
supervisor/api/network.py (1)
  • info (177-192)
🪛 Ruff (0.8.2)
supervisor/docker/network.py

78-78: Missing docstring in public method

(D102)

🔇 Additional comments (6)
supervisor/docker/network.py (6)

6-6: Added Any type import for better type hinting.

This is a good improvement for type safety in the new create method parameters.


11-16: Network constant imports properly refactored for IPv6 support.

The imports now clearly distinguish between IPv4 and IPv6 network masks and ranges, providing better type safety and clarity.


51-51: Properties correctly updated to use IPv4-specific constants.

All IPv4 address properties (gateway, supervisor, dns, audio, cli, observer) now properly reference DOCKER_IPV4_NETWORK_MASK instead of the generic constant, maintaining backward compatibility while enabling IPv6 support.

Also applies to: 56-56, 61-61, 66-66, 71-71, 76-76


81-83: Good addition of the create wrapper method with proper docstring.

This wrapper method provides a clean abstraction for network creation operations and includes proper type annotations.


88-91: Network migration logic handles existing installations properly.

The code now checks if the existing network has IPv6 enabled and recreates it if necessary, with a clear log message about the migration process. This aligns with the discussed approach in the PR comments.

Based on the previous comments on this code, this migration happens during supervisor bootstrap before containers are connected to the network, making it safe to recreate the network at this point.


95-110: Docker network creation properly configured for dual-stack support.

The updated network creation logic now:

  1. Creates an IPv6 pool with the new DOCKER_IPV6_NETWORK_MASK
  2. Maintains the IPv4 pool with subnet, gateway, and range configuration
  3. Explicitly enables IPv6 with enable_ipv6=True

This implementation correctly establishes a dual-stack network that supports both IPv4 and IPv6 communications.

@davidrapan
Copy link
Contributor Author

Can you run the checks so I can see if the coverage is okay?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
tests/docker/test_network.py (1)

37-40: Consider adding more specific assertions about the network object.

The test only asserts that network is truthy, but could be more specific about what's expected.

- assert network
+ assert network is True  # or whatever specific value is expected
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f6e121 and dafa7c3.

📒 Files selected for processing (3)
  • supervisor/const.py (2 hunks)
  • supervisor/docker/network.py (3 hunks)
  • tests/docker/test_network.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • supervisor/docker/network.py
  • supervisor/const.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
tests/docker/test_network.py (1)
supervisor/docker/network.py (5)
  • supervisor (55-57)
  • network (40-42)
  • DockerNetwork (23-173)
  • name (35-37)
  • gateway (50-52)
🔇 Additional comments (5)
tests/docker/test_network.py (5)

1-15: Good job implementing the test for IPv6 network support.

The imports and constants are well organized and include everything needed for the test.


17-22: The MockNetwork class is appropriately minimal.

The implementation correctly simulates a Docker network with just the functionality needed for this test.


24-25: Good test description.

The improved docstring clearly explains what the test is checking, which helps with maintenance and understanding.


27-36: Patching is correctly implemented.

The test correctly patches both the get and create methods with their full import paths, addressing the issue mentioned in the past review.


41-58: The assertions thoroughly verify network recreation with IPv6 enabled.

The test adequately verifies that:

  1. The existing network is retrieved by name
  2. A new network is created with the correct parameters:
    • Name and driver
    • IPv6 subnet configuration
    • IPv4 subnet, gateway, and range
    • IPv6 explicitly enabled
    • Bridge network options

This comprehensively tests the migration functionality to IPv4/IPv6 dual-stack networking.

@davidrapan
Copy link
Contributor Author

davidrapan commented May 15, 2025

Hi @agners, so apparently containers are now getting default v6 gateway? (I could swear that when I did the first tests, they didn't have it):

➜  ~ docker exec -i addon_core_nginx_proxy ip -6 route 
fd0c::/64 dev eth0  metric 256 
fe80::/64 dev eth0  metric 256 
default via fd0c::1 dev eth0  metric 1024 
multicast ff00::/8 dev eth0  metric 256 

v6 ping:

➜  ~ docker exec -i addon_core_nginx_proxy ping -6 google.com
PING google.com (2a00:1450:4014:80a::200e): 56 data bytes
64 bytes from 2a00:1450:4014:80a::200e: seq=0 ttl=116 time=34.882 ms

However, the preferable route is still through v4 (most likely because v6 is a ULA and/or v4 has default metric):

➜  ~ docker exec -i addon_core_nginx_proxy ping google.com 
PING google.com (142.251.36.110): 56 data bytes
64 bytes from 142.251.36.110: seq=0 ttl=116 time=32.297 ms

Edit: BTW,

Error: The head commit for this pull_request event is not ahead of the base commit. Please submit an issue on this action's GitHub repo.

in Build supervisor / Initialize build (pull_request) means something is wrong or is it “normal”?

@agners
Copy link
Member

agners commented May 15, 2025

Hi @agners, so apparently containers are now getting a default v6 gateway? (I could swear that when I did the first tests, they didn't have it):

Hm, ok, yeah good to know. I definitely need to do some testing with different scenarios of broken IPv6 connectivity.

Error: The head commit for this pull_request event is not ahead of the base commit. Please submit an issue on this action's GitHub repo.

in Build supervisor / Initialize build (pull_request) means something is wrong or is it “normal”?

That typically means your PR needs a rebase ontop of current main.

@davidrapan
Copy link
Contributor Author

davidrapan commented May 15, 2025

I definitely need to do some testing with different scenarios of broken IPv6 connectivity.

I did some myself today and so far so good.

But anyway, according to RFC 6724 IPv4 has preference over IPv6 ULA in sourcefilter by default:

Prefix        Precedence Label
::1/128               50     0
::/0                  40     1
::ffff:0:0/96         35     4
2002::/16             30     2
2001::/32              5     5
fc00::/7               3    13
::/96                  1     3
fec0::/10              1    11
3ffe::/16              1    12

v4 (::ffff:0:0/96) has precedence 35 while v6 ULA (fc00::/7) only 3, "..we say that address A has higher precedence than address B, meaning that our algorithm will prefer to sort destination address A before destination address B." And it's the same for source too.

@davidrapan
Copy link
Contributor Author

davidrapan commented May 16, 2025

Hi @agners, finally, I also found confirmation that musl (which is used in Alpine Linux) follows RFC 6724 since version 1.1.3

Edit: Fixed the link.

@agners
Copy link
Member

agners commented May 19, 2025

First of all, thanks a lot for your work and the additional research! 🤩

But anyway, according to RFC 6724 IPv4 has preference over IPv6 ULA in sourcefilter by default:

Prefix        Precedence Label
::1/128               50     0
::/0                  40     1
::ffff:0:0/96         35     4
2002::/16             30     2
2001::/32              5     5
fc00::/7               3    13
::/96                  1     3
fec0::/10              1    11
3ffe::/16              1    12

Interesting, where is that list from/what command prints that?

v4 (::ffff:0:0/96) has precedence 35 while v6 ULA (fc00::/7) only 3, "..we say that address A has higher precedence than address B, meaning that our algorithm will prefer to sort destination address A before destination address B." And it's the same for source too.

Thanks, yeah that make sense.

Hi @agners, finally, I also found confirmation that musl (which is used in Alpine Linux) follows RFC 6724 since version 1.1.3

Hm, this applies for getaddrinfo. Sometimes user space does their own thing, e.g. aiodns uses c-ares, and Home Assistant Core uses aiohttp and aiodns for most of the work. That said, I just checked this particular case, it seems that c-ares also implements sorting according to RFC 6724, so it should behave the same way. Still, I fear that some user space apps do things not correctly. I guess we can consider this a bug of those apps, and make sure it gets fixed, but at first it will appear as a breaking change for users.

I wonder if under certain circumstances (server down) the error will change since with an IPv6 address available we'll attempt connecting to the server using the ULA IPv6 if IPv4 fails, so maybe a socket connection attempt ends with a routing error instead of not responding or similar. This is probably not a big deal, but just things which could be influenced.

Unfortunately, the update concept of Supervisor doesn't allow downgrade. So once this is merged and shipped, the only thing to opt out of it is if we would revert it and ship a new release.

For the reasons above, I'd like to make this an option, which we first set the default to false (make it opt-in), and then then rollout to everyone by changing the default to true. We have the mechanism to do this already, e.g. the diagnostics option works exactly like that: If not set by user, the state is None, where we fall back to default (which is false in that case). These options can be set with ha supervisor options, e.g. for this case we could introduce ha supervisor options --bridge-ipv6=true). From my point of view it's fine to apply the change only on restart (e.g. require a ha supervisor restart after changing the option).

With that, we can get this merged soon and roll out to interested users to test soon. Later, when we enable it by default, we can also easily test potential regressions (e.g. if people report connectivity issue, we can ask them to temporarily disable the feature to see if that was the source of the problem).

I think code wise it won't be all that complex. What is missing then is that we also need to add the revert logic in _get_network, essentially compare to the current setting and if it deviates, recreate the bridge.

@davidrapan
Copy link
Contributor Author

davidrapan commented May 19, 2025

Interesting, where is that list from/what command prints that?

It is from the RFC and the standard path to this configuration is /etc/gai.conf. However, in the case of musl it is hardcoded.

Hm, this applies for getaddrinfo. Sometimes user space does their own thing, e.g. aiodns uses c-ares, and Home Assistant Core uses aiohttp and aiodns for most of the work. That said, I just checked this particular case, it seems that c-ares also implements sorting according to RFC 6724, so it should behave the same way.

And kernel address selection.

Of course, the application is free to bind() to any address it wants.

And I totally agree w/ you. I'm very much in favor of the users being able to choose exactly the configuration they want. Forcing it both ways would be too restrictive. I'll try adding a command to the ha supervisor options namespace. 😉

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 1aa44cc and b80653c.

📒 Files selected for processing (5)
  • supervisor/api/supervisor.py (2 hunks)
  • supervisor/config.py (1 hunks)
  • supervisor/docker/manager.py (2 hunks)
  • supervisor/docker/network.py (3 hunks)
  • tests/docker/test_network.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • supervisor/config.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/docker/test_network.py
  • supervisor/docker/network.py
🧰 Additional context used
🧬 Code Graph Analysis (2)
supervisor/api/supervisor.py (3)
supervisor/coresys.py (1)
  • sys_config (695-697)
supervisor/config.py (1)
  • bridge_ipv6 (122-124)
supervisor/docker/manager.py (1)
  • bridge_ipv6 (97-99)
supervisor/docker/manager.py (2)
supervisor/config.py (1)
  • bridge_ipv6 (122-124)
supervisor/docker/network.py (1)
  • DockerNetwork (40-178)
🔇 Additional comments (3)
supervisor/docker/manager.py (2)

96-99: LGTM: New bridge_ipv6 property properly defined.

The property is correctly defined as a boolean with a sensible default of False. This aligns with the PR objective to enable IPv6 support for containers.


134-134: LGTM: DockerNetwork initialization updated to pass IPv6 configuration.

The DockerNetwork initialization now correctly passes the IPv6 configuration from the system config, enabling IPv6 support when requested by the user. This is needed to allow containers to receive IPv6 addresses.

supervisor/api/supervisor.py (1)

71-71: LGTM: Schema updated to accept bridge_ipv6 parameter.

The SCHEMA_OPTIONS is correctly extended to include the bridge_ipv6 parameter as an optional boolean value, allowing API users to enable or disable IPv6 support.

@davidrapan davidrapan changed the title Enable IPv6 for containers Add opt-in IPv6 for containers May 19, 2025
@davidrapan
Copy link
Contributor Author

davidrapan commented May 19, 2025

I can't find tests for "somewhat individual" configuration items, are they missing?

Edit: Ah okay, I found some.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
tests/api/test_supervisor.py (2)

273-274: Fix the docstring to match the test function's purpose.

The current docstring "Test setting supervisor country via API" is incorrect for this function that tests the bridge_ipv6 option. The docstring should reflect what's actually being tested.

-    """Test setting supervisor country via API."""
+    """Test setting supervisor bridge_ipv6 option via API."""

279-279: Avoid equality comparison with True.

Instead of comparing with True explicitly, use a truth value check for better readability and consistency with Python's style guidelines.

-    assert coresys.config.bridge_ipv6 == True
+    assert coresys.config.bridge_ipv6 is True
🧰 Tools
🪛 Ruff (0.11.9)

279-279: Avoid equality comparisons to True; use if coresys.config.bridge_ipv6: for truth checks

Replace with coresys.config.bridge_ipv6

(E712)

supervisor/docker/network.py (1)

115-116: Consider using logger.info with format arguments instead of f-string.

While f-strings are generally efficient, for logging statements it's recommended to use the logger's built-in formatting capabilities to avoid unnecessary string formatting when the log level is not enabled.

-            _LOGGER.info(
-                f"Migrating Supervisor network to {'IPv4/IPv6 Dual Stack' if enable_ipv6 else 'IPv4-Only'}"
-            )
+            _LOGGER.info(
+                "Migrating Supervisor network to %s",
+                "IPv4/IPv6 Dual Stack" if enable_ipv6 else "IPv4-Only"
+            )
🧰 Tools
🪛 Ruff (0.11.9)

115-115: Logging statement uses f-string

(G004)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 6a8e43a and 648c15b.

📒 Files selected for processing (7)
  • supervisor/api/supervisor.py (4 hunks)
  • supervisor/config.py (2 hunks)
  • supervisor/const.py (3 hunks)
  • supervisor/docker/manager.py (3 hunks)
  • supervisor/docker/network.py (3 hunks)
  • tests/api/test_supervisor.py (1 hunks)
  • tests/docker/test_network.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • supervisor/docker/manager.py
  • supervisor/config.py
  • supervisor/api/supervisor.py
  • supervisor/const.py
🧰 Additional context used
🪛 Ruff (0.11.9)
tests/api/test_supervisor.py

279-279: Avoid equality comparisons to True; use if coresys.config.bridge_ipv6: for truth checks

Replace with coresys.config.bridge_ipv6

(E712)

supervisor/docker/network.py

115-115: Logging statement uses f-string

(G004)

🔇 Additional comments (7)
tests/api/test_supervisor.py (1)

272-284: LGTM! Good addition of tests for the new bridge_ipv6 option.

The test properly verifies that the API correctly handles the bridge_ipv6 option, both for setting and retrieving the value. This ensures the new IPv6 support feature is well-tested through the API interface.

🧰 Tools
🪛 Ruff (0.11.9)

279-279: Avoid equality comparisons to True; use if coresys.config.bridge_ipv6: for truth checks

Replace with coresys.config.bridge_ipv6

(E712)

tests/docker/test_network.py (2)

16-25: Good implementation of the mock network class for testing.

The MockNetwork class provides a clean implementation for testing the IPv6 functionality, with proper initialization and mocked methods.


27-41: Well-structured test with parametrization.

The test is properly parameterized to verify both IPv6 enabled and disabled scenarios, and the mock setup accurately represents the real-world conditions with appropriate return values.

supervisor/docker/network.py (4)

22-39: Good network configuration setup for dual-stack support.

The creation of explicit constants for IPv6 support and the comprehensive network parameters dictionary provides a clean implementation for dual-stack networking.


98-105: Good abstraction with helper methods.

The addition of _get and _create helper methods makes the code more testable and maintains the separation of concerns.


48-52: Good implementation of IPv6 support in the DockerNetwork class.

The addition of the enable_ipv6 parameter with a default of False makes this feature opt-in, aligning with the PR objectives. The implementation correctly passes this parameter to the network creation logic.


69-96: Updated network property implementations.

The refactoring to use the explicit IPv4 network mask constants makes the code clearer and more maintainable, correctly separating IPv4 and IPv6 concerns.

@agners
Copy link
Member

agners commented May 28, 2025

Interesting, could this be solved by reattaching all the "plugins"? Or is it completely unrelated to the migration?

It seems that reattaching if the container is stopped (which is the default for all but Supervisor/Observer on startup) really means re-creating the container. Not sure why exactly, but that seems to be the behavior. It's probably also not a bad idea, this makes sure the container gets created with whatever latest Supervisor code is doing (making sure potentially changed run parameters are actually applied).

But that DNS plug-in got started before attaching is unintentional and addressed with #5922.

@davidrapan
Copy link
Contributor Author

davidrapan commented May 28, 2025

Not sure why exactly, but that seems to be the behavior.

It kinda makes sense because it needs to reconfigure container's IP.

So something like this?

        for name, ip in (
            (ATTR_CLI, self.cli),
            (ATTR_DNS, self.dns),
            (ATTR_AUDIO, self.audio),
        ):
            with suppress(DockerError):
                self.attach_container_by_name(f"{DOCKER_PREFIX}_{name}", [name], ip)

@davidrapan davidrapan marked this pull request as ready for review May 28, 2025 16:05
@home-assistant home-assistant bot requested a review from agners May 28, 2025 16:05
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
supervisor/docker/network.py (1)

163-171: Silent error suppression may hide important failures.

Using suppress(DockerError) for container reattachment could hide important failures during network setup. Consider logging warnings when reattachment fails to aid in debugging.

This issue was mentioned in previous reviews. The current implementation silently suppresses errors which could make troubleshooting difficult when containers fail to attach to the new network.

🧹 Nitpick comments (1)
supervisor/docker/network.py (1)

114-181: Complex migration logic could benefit from refactoring.

The _get_network method handles network migration correctly but is quite complex with nested conditions and multiple responsibilities. Consider extracting the migration logic into a separate method for better maintainability and testability.

Extract the migration logic into a helper method:

+    def _migrate_network(
+        self, network: docker.models.networks.Network, enable_ipv6: bool
+    ) -> tuple[bool, docker.models.networks.Network]:
+        """Migrate existing network to new IPv6 configuration.
+        
+        Returns (success, network) where success indicates if migration completed.
+        """
+        containers = network.containers
+        containers_all = all(
+            container.name in (OBSERVER_DOCKER_NAME, SUPERVISOR_DOCKER_NAME)
+            for container in containers
+        ) if containers else True
+        
+        if containers and containers_all:
+            for container in containers:
+                with suppress(
+                    docker.errors.APIError,
+                    docker.errors.DockerException,
+                    requests.RequestException,
+                ):
+                    network.disconnect(container, force=True)
+        
+        if not containers or containers_all:
+            try:
+                network.remove()
+                return True, network
+            except docker.errors.APIError:
+                _LOGGER.warning("Failed to remove existing Supervisor network")
+                return False, network
+        else:
+            _LOGGER.warning(
+                "System appears to be running, "
+                "not applying Supervisor network change. "
+                "Reboot your system to apply the change."
+            )
+            return False, network

     def _get_network(self, enable_ipv6: bool = False) -> docker.models.networks.Network:
         """Get supervisor network."""
         try:
-            if network := self.docker.networks.get(DOCKER_NETWORK):
-                if network.attrs.get(DOCKER_ENABLEIPV6) == enable_ipv6:
-                    return network
-                _LOGGER.info(
-                    "Migrating Supervisor network to %s",
-                    "IPv4/IPv6 Dual-Stack" if enable_ipv6 else "IPv4-Only",
-                )
-                if (containers := network.containers) and (
-                    containers_all := all(
-                        container.name in (OBSERVER_DOCKER_NAME, SUPERVISOR_DOCKER_NAME)
-                        for container in containers
-                    )
-                ):
-                    for container in containers:
-                        with suppress(
-                            docker.errors.APIError,
-                            docker.errors.DockerException,
-                            requests.RequestException,
-                        ):
-                            network.disconnect(container, force=True)
-                if not containers or containers_all:
-                    try:
-                        network.remove()
-                    except docker.errors.APIError:
-                        _LOGGER.warning("Failed to remove existing Supervisor network")
-                        return network
-                else:
-                    _LOGGER.warning(
-                        "System appears to be running, "
-                        "not applying Supervisor network change. "
-                        "Reboot your system to apply the change."
-                    )
-                    return network
+            if network := self.docker.networks.get(DOCKER_NETWORK):
+                if network.attrs.get(DOCKER_ENABLEIPV6) == enable_ipv6:
+                    return network
+                _LOGGER.info(
+                    "Migrating Supervisor network to %s",
+                    "IPv4/IPv6 Dual-Stack" if enable_ipv6 else "IPv4-Only",
+                )
+                success, network = self._migrate_network(network, enable_ipv6)
+                if not success:
+                    return network
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between a88477c and ced405e.

📒 Files selected for processing (17)
  • supervisor/api/__init__.py (3 hunks)
  • supervisor/api/docker.py (2 hunks)
  • supervisor/api/network.py (2 hunks)
  • supervisor/const.py (4 hunks)
  • supervisor/dbus/network/connection.py (2 hunks)
  • supervisor/dbus/network/setting/__init__.py (1 hunks)
  • supervisor/docker/manager.py (3 hunks)
  • supervisor/docker/network.py (5 hunks)
  • supervisor/docker/observer.py (2 hunks)
  • supervisor/misc/filter.py (2 hunks)
  • supervisor/validate.py (3 hunks)
  • tests/api/test_docker.py (2 hunks)
  • tests/api/test_network.py (2 hunks)
  • tests/dbus/network/test_connection.py (2 hunks)
  • tests/dbus/network/test_ip_configuration.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
  • tests/test_validate.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (15)
  • tests/dbus/network/test_ip_configuration.py
  • supervisor/dbus/network/setting/init.py
  • tests/dbus/network/test_connection.py
  • supervisor/misc/filter.py
  • supervisor/api/network.py
  • tests/api/test_network.py
  • tests/test_validate.py
  • supervisor/validate.py
  • supervisor/dbus/network/connection.py
  • supervisor/docker/observer.py
  • supervisor/api/docker.py
  • supervisor/docker/manager.py
  • tests/api/test_docker.py
  • supervisor/const.py
  • supervisor/api/init.py
🔇 Additional comments (6)
tests/docker/test_network.py (1)

1-113: Well-structured test implementation with comprehensive coverage.

The test file is well-designed with appropriate mock classes and comprehensive parameterized test scenarios. The test logic correctly verifies network recreation behavior under different conditions.

supervisor/docker/network.py (5)

3-3: Good async import additions.

The new imports for asyncio and Self typing are appropriate for the async initialization pattern implemented in this file.

Also applies to: 7-7


32-48: Well-designed network configuration constants.

The DOCKER_NETWORK_PARAMS dictionary provides a clean, centralized configuration for Docker network creation with proper IPAM setup for both IPv4 and IPv6 subnets. The structure supports dual-stack networking correctly.


62-67: Excellent async initialization pattern.

Moving network initialization to post_init() and running blocking Docker API calls in an executor is the correct approach for maintaining event loop compatibility.


219-241: Well-designed container attachment by name.

The new attach_container_by_name method is a good addition that simplifies container management during network migration. The error handling is comprehensive and appropriate.


264-279: Improved error handling in stale cleanup.

The enhanced error handling in stale_cleanup with specific exception types and proper logging is a good improvement over the previous implementation.

sairon pushed a commit that referenced this pull request May 29, 2025
* Avoid early DNS plug-in start

A connectivity check can potentially be triggered before the DNS
plug-in is loaded. Avoid calling restart on the DNS plug-in before
it got initially loaded. This prevents starting before attaching.
The attaching makes sure that the DNS plug-in container is recreated
before the DNS plug-in is initially started, which is e.g. needed
by a potentially hassio network configuration change (e.g. the
migration required to enable/disable IPv6 on the hassio network,
see #5879).

* Mock DNS plug-in running
@davidrapan davidrapan marked this pull request as draft May 29, 2025 11:51
@davidrapan davidrapan marked this pull request as ready for review May 29, 2025 11:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
supervisor/docker/network.py (2)

32-48: IPv6 enabled by default in global parameters may cause confusion.

The DOCKER_NETWORK_PARAMS dictionary has ATTR_ENABLE_IPV6: True by default, but the _get_network method defaults to enable_ipv6=False. This inconsistency could lead to unexpected behavior where the global parameters suggest IPv6 is enabled but the method call disables it.

Consider aligning the defaults or adding a comment explaining this design choice:

 DOCKER_NETWORK_PARAMS = {
     "name": DOCKER_NETWORK,
     "driver": DOCKER_NETWORK_DRIVER,
     "ipam": docker.types.IPAMConfig(
         pool_configs=[
             docker.types.IPAMPool(subnet=str(DOCKER_IPV6_NETWORK_MASK)),
             docker.types.IPAMPool(
                 subnet=str(DOCKER_IPV4_NETWORK_MASK),
                 gateway=str(DOCKER_IPV4_NETWORK_MASK[1]),
                 iprange=str(DOCKER_IPV4_NETWORK_RANGE),
             ),
         ]
     ),
-    ATTR_ENABLE_IPV6: True,
+    ATTR_ENABLE_IPV6: False,  # Will be overridden in _get_network based on config
     "options": {"com.docker.network.bridge.name": DOCKER_NETWORK},
 }

124-149: Complex migration logic correctly handles container attachment edge cases.

The migration logic properly addresses the container attachment issue identified in past reviews by:

  1. Checking if only Supervisor/Observer containers are attached
  2. Disconnecting containers before network removal
  3. Gracefully handling cases where other containers are attached

However, the nested conditional logic is quite complex and could benefit from extraction.

Consider extracting the migration logic into a separate method for better readability:

+    def _can_migrate_network(self, containers: list) -> bool:
+        """Check if network migration is safe to perform."""
+        return not containers or all(
+            container.name in (OBSERVER_DOCKER_NAME, SUPERVISOR_DOCKER_NAME)
+            for container in containers
+        )
+
+    def _disconnect_containers(self, network, containers: list) -> None:
+        """Safely disconnect containers from network."""
+        for container in containers:
+            with suppress(
+                docker.errors.APIError,
+                docker.errors.DockerException,
+                requests.RequestException,
+            ):
+                network.disconnect(container, force=True)

Then simplify the main logic:

-                if (containers := network.containers) and (
-                    containers_all := all(
-                        container.name in (OBSERVER_DOCKER_NAME, SUPERVISOR_DOCKER_NAME)
-                        for container in containers
-                    )
-                ):
-                    for container in containers:
-                        with suppress(
-                            docker.errors.APIError,
-                            docker.errors.DockerException,
-                            requests.RequestException,
-                        ):
-                            network.disconnect(container, force=True)
-                if not containers or containers_all:
+                containers = network.containers
+                if self._can_migrate_network(containers):
+                    self._disconnect_containers(network, containers)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between ced405e and d90d2ff.

📒 Files selected for processing (17)
  • supervisor/api/__init__.py (3 hunks)
  • supervisor/api/docker.py (2 hunks)
  • supervisor/api/network.py (2 hunks)
  • supervisor/const.py (4 hunks)
  • supervisor/dbus/network/connection.py (2 hunks)
  • supervisor/dbus/network/setting/__init__.py (1 hunks)
  • supervisor/docker/manager.py (3 hunks)
  • supervisor/docker/network.py (5 hunks)
  • supervisor/docker/observer.py (2 hunks)
  • supervisor/misc/filter.py (2 hunks)
  • supervisor/validate.py (3 hunks)
  • tests/api/test_docker.py (2 hunks)
  • tests/api/test_network.py (2 hunks)
  • tests/dbus/network/test_connection.py (2 hunks)
  • tests/dbus/network/test_ip_configuration.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
  • tests/test_validate.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/api/test_network.py
🚧 Files skipped from review as they are similar to previous changes (15)
  • tests/dbus/network/test_ip_configuration.py
  • supervisor/misc/filter.py
  • tests/dbus/network/test_connection.py
  • supervisor/api/init.py
  • tests/test_validate.py
  • supervisor/dbus/network/setting/init.py
  • supervisor/api/network.py
  • supervisor/validate.py
  • supervisor/dbus/network/connection.py
  • supervisor/api/docker.py
  • supervisor/docker/observer.py
  • tests/api/test_docker.py
  • supervisor/docker/manager.py
  • tests/docker/test_network.py
  • supervisor/const.py
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Build armv7 supervisor
  • GitHub Check: Run tests Python 3.13.3
🔇 Additional comments (4)
supervisor/docker/network.py (4)

62-67: Excellent async initialization pattern implementation.

The move to async post_init properly addresses the blocking Docker API calls by running them in an executor. This prevents blocking the event loop while maintaining the synchronous Docker client interface.


163-171: Container reattachment uses appropriate error suppression.

The use of suppress(DockerError) for container reattachment is appropriate here since these are optional attachments during network initialization. If these fail, the system can still function, and the containers will be attached later when they start.


219-241: New attach_container_by_name method provides good abstraction.

This method properly abstracts the container lookup and attachment logic, making the migration code cleaner. The error handling is comprehensive and consistent with other methods.


208-217: Enhanced exception handling improves error diagnosis.

The expanded exception handling catches specific Docker and request exceptions, providing better error messages compared to the broad exception handling from previous versions.

@davidrapan davidrapan marked this pull request as draft May 29, 2025 16:02
@davidrapan davidrapan marked this pull request as ready for review May 29, 2025 16:04
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
supervisor/docker/network.py (1)

163-171: Container reattachment silently suppresses errors.

The use of suppress(DockerError) hides potential reattachment failures. Consider logging warnings when reattachment fails to improve debugging visibility:

-        with suppress(DockerError):
-            self.attach_container_by_name(
-                SUPERVISOR_DOCKER_NAME, [ATTR_SUPERVISOR], self.supervisor
-            )
-
-        with suppress(DockerError):
-            self.attach_container_by_name(
-                OBSERVER_DOCKER_NAME, [ATTR_OBSERVER], self.observer
-            )
+        try:
+            self.attach_container_by_name(
+                SUPERVISOR_DOCKER_NAME, [ATTR_SUPERVISOR], self.supervisor
+            )
+        except DockerError as err:
+            _LOGGER.warning("Failed to reattach supervisor container: %s", err)
+
+        try:
+            self.attach_container_by_name(
+                OBSERVER_DOCKER_NAME, [ATTR_OBSERVER], self.observer
+            )
+        except DockerError as err:
+            _LOGGER.warning("Failed to reattach observer container: %s", err)
🧹 Nitpick comments (2)
supervisor/api/docker.py (1)

57-66: Add error handling for configuration save failures.

The options method correctly validates input and updates the IPv6 configuration, but lacks explicit error handling for potential save failures.

     async def options(self, request: web.Request) -> None:
         """Set docker options."""
         body = await api_validate(SCHEMA_OPTIONS, request)

         if ATTR_ENABLE_IPV6 in body:
             self.sys_docker.config.enable_ipv6 = body[ATTR_ENABLE_IPV6]

-        await self.sys_docker.config.save_data()
+        try:
+            await self.sys_docker.config.save_data()
+        except Exception as err:
+            raise APIError("Failed to save Docker configuration") from err
supervisor/docker/network.py (1)

117-149: Complex migration logic handles container attachment edge cases.

The network migration logic correctly addresses the issue from past reviews where containers attached to the network would prevent removal. The implementation checks for only supervisor/observer containers and safely disconnects them before migration.

However, consider extracting this complex logic into a separate method for better maintainability:

+    def _can_migrate_network(self, containers: list) -> bool:
+        """Check if network can be safely migrated."""
+        return not containers or all(
+            container.name in (OBSERVER_DOCKER_NAME, SUPERVISOR_DOCKER_NAME)
+            for container in containers
+        )
+
+    def _disconnect_containers_for_migration(self, network, containers: list) -> None:
+        """Safely disconnect containers for network migration."""
+        for container in containers:
+            with suppress(
+                docker.errors.APIError,
+                docker.errors.DockerException,
+                requests.RequestException,
+            ):
+                network.disconnect(container, force=True)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between d90d2ff and bb8260b.

📒 Files selected for processing (17)
  • supervisor/api/__init__.py (3 hunks)
  • supervisor/api/docker.py (2 hunks)
  • supervisor/api/network.py (2 hunks)
  • supervisor/const.py (4 hunks)
  • supervisor/dbus/network/connection.py (2 hunks)
  • supervisor/dbus/network/setting/__init__.py (1 hunks)
  • supervisor/docker/manager.py (3 hunks)
  • supervisor/docker/network.py (5 hunks)
  • supervisor/docker/observer.py (2 hunks)
  • supervisor/misc/filter.py (2 hunks)
  • supervisor/validate.py (3 hunks)
  • tests/api/test_docker.py (2 hunks)
  • tests/api/test_network.py (2 hunks)
  • tests/dbus/network/test_connection.py (2 hunks)
  • tests/dbus/network/test_ip_configuration.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
  • tests/test_validate.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/api/test_network.py
🚧 Files skipped from review as they are similar to previous changes (14)
  • tests/dbus/network/test_ip_configuration.py
  • supervisor/misc/filter.py
  • tests/dbus/network/test_connection.py
  • supervisor/dbus/network/connection.py
  • tests/test_validate.py
  • supervisor/dbus/network/setting/init.py
  • supervisor/api/network.py
  • supervisor/validate.py
  • supervisor/api/init.py
  • supervisor/docker/observer.py
  • tests/api/test_docker.py
  • tests/docker/test_network.py
  • supervisor/docker/manager.py
  • supervisor/const.py
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: Build amd64 supervisor
  • GitHub Check: Build aarch64 supervisor
  • GitHub Check: Build armv7 supervisor
  • GitHub Check: Build armhf supervisor
  • GitHub Check: Run tests Python 3.13.3
🔇 Additional comments (6)
supervisor/api/docker.py (2)

34-36: Well-structured validation schema for IPv6 options.

The schema correctly validates the optional IPv6 enablement flag as a boolean value, following the established pattern for API validation.


41-55: IPv6 status correctly added to Docker info response.

The info method now includes the IPv6 enablement status from the Docker configuration, providing visibility into the current IPv6 state.

supervisor/docker/network.py (4)

62-67: Async initialization pattern correctly implemented.

The post_init method properly defers network initialization to run in an executor, maintaining event loop compatibility as intended.


33-48: IPv6 network parameters correctly configured.

The DOCKER_NETWORK_PARAMS structure properly includes both IPv4 and IPv6 IPAM pool configurations with appropriate subnet masks and gateway settings.


219-241: New container attachment method enhances functionality.

The attach_container_by_name method provides a cleaner interface for attaching containers by name with comprehensive error handling and proper use of the new container name constants.


205-217: Enhanced exception handling for container attachment.

The expanded exception handling now covers multiple Docker and request exception types, providing better error diagnosis. The inline IPv4 address conversion is also cleaner.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
supervisor/docker/network.py (1)

163-180: Consider logging warnings for container reattachment failures.

The suppress(DockerError) blocks hide potential failures during container reattachment. Based on previous review feedback, consider logging warnings when reattachment fails to aid in debugging.

-        with suppress(DockerError):
-            self.attach_container_by_name(
-                SUPERVISOR_DOCKER_NAME, [ATTR_SUPERVISOR], self.supervisor
-            )
+        try:
+            self.attach_container_by_name(
+                SUPERVISOR_DOCKER_NAME, [ATTR_SUPERVISOR], self.supervisor
+            )
+        except DockerError as err:
+            _LOGGER.warning("Failed to reattach supervisor container: %s", err)

Apply similar changes to observer and other container attachments.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between bb8260b and 6baa9c6.

📒 Files selected for processing (17)
  • supervisor/api/__init__.py (3 hunks)
  • supervisor/api/docker.py (2 hunks)
  • supervisor/api/network.py (2 hunks)
  • supervisor/const.py (4 hunks)
  • supervisor/dbus/network/connection.py (2 hunks)
  • supervisor/dbus/network/setting/__init__.py (1 hunks)
  • supervisor/docker/manager.py (3 hunks)
  • supervisor/docker/network.py (5 hunks)
  • supervisor/docker/observer.py (2 hunks)
  • supervisor/misc/filter.py (2 hunks)
  • supervisor/validate.py (3 hunks)
  • tests/api/test_docker.py (2 hunks)
  • tests/api/test_network.py (2 hunks)
  • tests/dbus/network/test_connection.py (2 hunks)
  • tests/dbus/network/test_ip_configuration.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
  • tests/test_validate.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/api/test_network.py
🚧 Files skipped from review as they are similar to previous changes (15)
  • tests/dbus/network/test_ip_configuration.py
  • supervisor/dbus/network/setting/init.py
  • tests/dbus/network/test_connection.py
  • supervisor/dbus/network/connection.py
  • supervisor/misc/filter.py
  • tests/test_validate.py
  • supervisor/api/network.py
  • supervisor/validate.py
  • supervisor/docker/observer.py
  • tests/api/test_docker.py
  • supervisor/docker/manager.py
  • supervisor/api/docker.py
  • supervisor/api/init.py
  • tests/docker/test_network.py
  • supervisor/const.py
🧰 Additional context used
🧬 Code Graph Analysis (1)
supervisor/docker/network.py (5)
supervisor/docker/manager.py (7)
  • docker (146-150)
  • post_init (127-143)
  • enable_ipv6 (98-100)
  • enable_ipv6 (103-105)
  • network (153-157)
  • info (175-179)
  • containers (165-167)
supervisor/exceptions.py (1)
  • DockerError (539-540)
supervisor/api/network.py (1)
  • info (197-212)
supervisor/docker/observer.py (1)
  • name (27-29)
supervisor/docker/supervisor.py (1)
  • name (25-27)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run tests Python 3.13.3
🔇 Additional comments (7)
supervisor/docker/network.py (7)

3-48: LGTM! Clean import organization and constants definition.

The addition of asyncio import and typing.Self for async initialization is appropriate. The new constants from supervisor.const are well-organized and the DOCKER_NETWORK_PARAMS structure correctly defines dual-stack IPv4/IPv6 configuration with proper IPAM pool configs.


57-67: Excellent async initialization pattern.

Moving network initialization from the constructor to post_init addresses the previous review concern and follows best practices. Using run_in_executor for the blocking Docker API calls ensures the event loop isn't blocked during network setup.


69-112: Properties correctly updated to use new constants.

All IP address properties have been properly updated to use the DOCKER_IPV4_NETWORK_MASK constant with appropriate indexing for different components. This maintains the fixed IP addressing scheme for core Supervisor components.


183-217: Improved container attachment with better exception handling.

The method correctly handles IPv4 address conversion and includes comprehensive exception handling covering Docker API errors and network issues. The error messages are informative and include container names for better debugging.


219-240: Well-designed utility method for container attachment by name.

This new method provides a clean abstraction for attaching containers by name with proper error handling and duplicate attachment prevention. The logic flow is correct and the exception handling is comprehensive.


242-262: Good improvements to default bridge detachment.

Using the DOCKER_NETWORK_DRIVER constant instead of hardcoded "bridge" and expanding exception handling improves maintainability and error coverage. The error messages are clear and include container context.


264-279: Simplified and improved stale cleanup method.

The method correctly handles stale container cleanup with better error handling and appropriate warning-level logging. The name-based approach simplifies the interface and aligns with other methods in the class.

@davidrapan davidrapan marked this pull request as draft June 2, 2025 00:36
@davidrapan davidrapan marked this pull request as ready for review June 2, 2025 00:38
@davidrapan davidrapan marked this pull request as draft June 3, 2025 10:53
@davidrapan davidrapan marked this pull request as ready for review June 3, 2025 10:59
@davidrapan davidrapan marked this pull request as draft June 5, 2025 13:24
@davidrapan davidrapan marked this pull request as ready for review June 5, 2025 13:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
supervisor/docker/network.py (1)

163-181: Consider logging attachment failures for better diagnostics.

The suppress(DockerError) blocks hide attachment failures that could be valuable for debugging network setup issues.

Consider replacing the suppression with explicit exception handling that logs warnings when container attachment fails, as suggested in previous reviews.

🧹 Nitpick comments (1)
tests/docker/test_network.py (1)

66-113: Test logic correctly validates network recreation behavior.

The test effectively verifies that network recreation occurs with the correct IPv6 settings under appropriate conditions. The mocking setup properly isolates the functionality being tested.

Consider simplifying the assertion logic on lines 101-109. The complex conditional logic closely mirrors the implementation, which could make the test brittle to implementation changes. You might consider testing specific scenarios separately for better clarity.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0e0a5cd and 9037bdb.

📒 Files selected for processing (17)
  • supervisor/api/__init__.py (3 hunks)
  • supervisor/api/docker.py (2 hunks)
  • supervisor/api/network.py (2 hunks)
  • supervisor/const.py (4 hunks)
  • supervisor/dbus/network/connection.py (2 hunks)
  • supervisor/dbus/network/setting/__init__.py (1 hunks)
  • supervisor/docker/manager.py (3 hunks)
  • supervisor/docker/network.py (5 hunks)
  • supervisor/docker/observer.py (2 hunks)
  • supervisor/misc/filter.py (2 hunks)
  • supervisor/validate.py (3 hunks)
  • tests/api/test_docker.py (2 hunks)
  • tests/api/test_network.py (2 hunks)
  • tests/dbus/network/test_connection.py (2 hunks)
  • tests/dbus/network/test_ip_configuration.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
  • tests/test_validate.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • tests/api/test_network.py
🚧 Files skipped from review as they are similar to previous changes (14)
  • supervisor/misc/filter.py
  • tests/dbus/network/test_ip_configuration.py
  • supervisor/dbus/network/connection.py
  • tests/dbus/network/test_connection.py
  • supervisor/dbus/network/setting/init.py
  • supervisor/api/network.py
  • supervisor/validate.py
  • supervisor/docker/observer.py
  • supervisor/docker/manager.py
  • tests/test_validate.py
  • tests/api/test_docker.py
  • supervisor/api/init.py
  • supervisor/api/docker.py
  • supervisor/const.py
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: Build armv7 supervisor
  • GitHub Check: Build aarch64 supervisor
  • GitHub Check: Build armhf supervisor
  • GitHub Check: Run tests Python 3.13.3
🔇 Additional comments (10)
tests/docker/test_network.py (3)

1-19: LGTM!

The imports are well-organized and include all necessary components for testing the DockerNetwork functionality.


21-53: LGTM!

The mock classes provide appropriate simulation of Docker API objects with necessary attributes and methods for comprehensive testing.


55-65: LGTM!

The test parametrization covers comprehensive scenarios including error conditions, various container configurations, and both IPv4/IPv6 migration directions.

supervisor/docker/network.py (7)

3-27: LGTM!

The updated imports properly support the new async functionality and IPv6 networking features.


32-48: LGTM!

The network parameters are correctly configured with dual-stack IPv4/IPv6 IPAM pools and appropriate subnet configurations.


62-67: LGTM!

The async post_init pattern properly defers blocking Docker API calls to an executor, preventing event loop blocking.


87-112: LGTM!

The property methods correctly use the updated IPv4 network mask constants while maintaining consistent IP address assignments.


153-161: LGTM!

The network creation logic correctly creates a copy of the parameters dictionary before modification, preventing unintended mutations of global state.


219-279: LGTM!

The new attach_container_by_name method provides a useful abstraction, and the updated methods include comprehensive exception handling with appropriate Docker API error types.


114-149: ⚠️ Potential issue

Fix variable scope issue in migration logic.

There's a variable scope problem where containers_all is defined inside the if condition (line 125) but used later outside its scope (line 137). This will cause a NameError if containers is falsy.

-                if (containers := network.containers) and (
-                    containers_all := all(
-                        container.name in (OBSERVER_DOCKER_NAME, SUPERVISOR_DOCKER_NAME)
-                        for container in containers
-                    )
-                ):
+                containers = network.containers
+                containers_all = (
+                    containers and all(
+                        container.name in (OBSERVER_DOCKER_NAME, SUPERVISOR_DOCKER_NAME)
+                        for container in containers
+                    )
+                )
+                if containers_all:

Likely an incorrect or invalid review comment.

@davidrapan davidrapan marked this pull request as draft June 11, 2025 11:20
Configurable and w/ migrations between IPv4-Only and Dual-Stack

Signed-off-by: David Rapan <[email protected]>
@davidrapan davidrapan marked this pull request as ready for review June 11, 2025 11:27
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
supervisor/docker/network.py (1)

124-138: ⚠️ Potential issue

containers_all may be referenced before assignment

If network.containers is falsy, the assignment expression never executes and
containers_all is referenced uninitialised in the subsequent if not containers or containers_all: branch.

-                if (containers := network.containers) and (
-                    containers_all := all(
+                containers = network.containers
+                containers_all = (
+                    containers
+                    and all(
                         container.name in (OBSERVER_DOCKER_NAME, SUPERVISOR_DOCKER_NAME)
                         for container in containers
-                    )
-                ):
+                    )
+                )
+                if containers_all:
🧹 Nitpick comments (3)
tests/docker/test_network.py (1)

101-105: Prefer == over is for boolean comparison

network.attrs.get(DOCKER_ENABLEIPV6) returns a bool; using identity
comparison (is) works today but is brittle. Equality comparison is clearer and
future-proof:

-assert network.attrs.get(DOCKER_ENABLEIPV6) is (
+assert network.attrs.get(DOCKER_ENABLEIPV6) == (
supervisor/docker/network.py (2)

163-171: Swallowing DockerError hides critical failures

Suppressing all DockerErrors during container (re)attachment provides no
signal when something goes wrong. At minimum log the exception; ideally bubble
it up so the caller can decide whether startup should abort.

-with suppress(DockerError):
-    self.attach_container_by_name(
-        SUPERVISOR_DOCKER_NAME, [ATTR_SUPERVISOR], self.supervisor
-    )
+try:
+    self.attach_container_by_name(
+        SUPERVISOR_DOCKER_NAME, [ATTR_SUPERVISOR], self.supervisor
+    )
+except DockerError as err:
+    _LOGGER.warning("Failed to attach supervisor container: %s", err)

220-238: attach_container_by_name performs blocking I/O on the event-loop thread

docker_client.containers.get() is a synchronous, potentially slow call.
attach_container_by_name is public and the docstring advertises that it “Need
run inside executor”, yet the method itself does not enforce that. Consider
either:

  1. Making the method private and only calling it from _get_network() (already
    inside run_in_executor), or
  2. Adding async def async_attach_container_by_name(...) that offloads the
    blocking call with run_in_executor.

This prevents accidental event-loop blocking elsewhere in the codebase.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 9037bdb and 089e8b6.

📒 Files selected for processing (17)
  • supervisor/api/__init__.py (3 hunks)
  • supervisor/api/docker.py (2 hunks)
  • supervisor/api/network.py (2 hunks)
  • supervisor/const.py (4 hunks)
  • supervisor/dbus/network/connection.py (2 hunks)
  • supervisor/dbus/network/setting/__init__.py (1 hunks)
  • supervisor/docker/manager.py (3 hunks)
  • supervisor/docker/network.py (5 hunks)
  • supervisor/docker/observer.py (2 hunks)
  • supervisor/misc/filter.py (2 hunks)
  • supervisor/validate.py (3 hunks)
  • tests/api/test_docker.py (2 hunks)
  • tests/api/test_network.py (2 hunks)
  • tests/dbus/network/test_connection.py (2 hunks)
  • tests/dbus/network/test_ip_configuration.py (2 hunks)
  • tests/docker/test_network.py (1 hunks)
  • tests/test_validate.py (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • supervisor/validate.py
🚧 Files skipped from review as they are similar to previous changes (14)
  • tests/dbus/network/test_ip_configuration.py
  • tests/dbus/network/test_connection.py
  • supervisor/dbus/network/connection.py
  • supervisor/misc/filter.py
  • supervisor/dbus/network/setting/init.py
  • tests/test_validate.py
  • supervisor/api/network.py
  • tests/api/test_network.py
  • supervisor/api/init.py
  • tests/api/test_docker.py
  • supervisor/api/docker.py
  • supervisor/docker/observer.py
  • supervisor/docker/manager.py
  • supervisor/const.py
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Run tests Python 3.13.3
  • GitHub Check: Check pylint
  • GitHub Check: Build armhf supervisor

Copy link
Member

@agners agners left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The last comment regarding the fact that we rely on the behavior that attach recreates the container is fine by me. With #5922 the DNS plug-in container gets properly recreated on attach as well.

LGTM!

@agners agners merged commit d5b5a32 into home-assistant:main Jun 12, 2025
20 checks passed
@davidrapan
Copy link
Contributor Author

davidrapan commented Jun 12, 2025

Awesome! Thank you. 🎉

@davidrapan davidrapan deleted the dev_enable_ipv6 branch June 12, 2025 14:54
@github-actions github-actions bot locked and limited conversation to collaborators Jun 14, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants