1
0
mirror of https://github.com/home-assistant/core.git synced 2026-07-12 17:18:04 +01:00

Proxmox clean up import code (#175557)

This commit is contained in:
Erwin Douna
2026-07-04 22:04:35 +02:00
committed by GitHub
parent f191ca32b4
commit bc2017a9b4
5 changed files with 7 additions and 296 deletions
+3 -127
View File
@@ -2,43 +2,17 @@
import logging
import voluptuous as vol
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_TOKEN,
CONF_USERNAME,
CONF_VERIFY_SSL,
Platform,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import (
config_validation as cv,
entity_registry as er,
issue_registry as ir,
)
from homeassistant.helpers.typing import ConfigType
from homeassistant.const import CONF_TOKEN, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .const import (
AUTH_OTHER,
AUTH_PAM,
AUTH_PVE,
CONF_AUTH_METHOD,
CONF_CONTAINERS,
CONF_NODE,
CONF_NODES,
CONF_REALM,
CONF_TOKEN_ID,
CONF_TOKEN_SECRET,
CONF_VMS,
DEFAULT_PORT,
DEFAULT_REALM,
DEFAULT_VERIFY_SSL,
DOMAIN,
)
from .coordinator import ProxmoxConfigEntry, ProxmoxCoordinator
@@ -49,107 +23,9 @@ PLATFORMS = [
]
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
cv.ensure_list,
[
vol.Schema(
{
vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Required(
CONF_AUTH_METHOD, default=DEFAULT_REALM
): cv.string,
vol.Optional(CONF_REALM, default=DEFAULT_REALM): cv.string,
vol.Optional(CONF_TOKEN, default=False): cv.boolean,
vol.Optional(CONF_TOKEN_ID): cv.string,
vol.Optional(CONF_TOKEN_SECRET): cv.string,
vol.Optional(
CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL
): cv.boolean,
vol.Required(CONF_NODES): vol.All(
cv.ensure_list,
[
vol.Schema(
{
vol.Required(CONF_NODE): cv.string,
vol.Optional(CONF_VMS, default=[]): [
cv.positive_int
],
vol.Optional(CONF_CONTAINERS, default=[]): [
cv.positive_int
],
}
)
],
),
}
)
],
)
},
extra=vol.ALLOW_EXTRA,
)
_LOGGER = logging.getLogger(__name__)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Import the Proxmox configuration from YAML."""
if DOMAIN not in config:
return True
hass.async_create_task(_async_setup(hass, config))
return True
async def _async_setup(hass: HomeAssistant, config: ConfigType) -> None:
for entry_config in config[DOMAIN]:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=entry_config,
)
if (
result.get("type") is FlowResultType.ABORT
and result.get("reason") != "already_configured"
):
ir.async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_import_issue_{result.get('reason')}",
breaks_in_ha_version="2026.8.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key=f"deprecated_yaml_import_issue_{result.get('reason')}",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Proxmox VE",
},
)
return
ir.async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
"deprecated_yaml",
breaks_in_ha_version="2026.8.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Proxmox VE",
},
)
async def async_setup_entry(hass: HomeAssistant, entry: ProxmoxConfigEntry) -> bool:
"""Set up a ProxmoxVE from a config entry."""
coordinator = ProxmoxCoordinator(hass, entry)
@@ -356,30 +356,6 @@ class ProxmoxveConfigFlow(ConfigFlow, domain=DOMAIN):
return proxmox_nodes, errors
async def async_step_import(self, import_data: dict[str, Any]) -> ConfigFlowResult:
"""Handle a flow initiated by configuration file."""
self._async_abort_entries_match({CONF_HOST: import_data[CONF_HOST]})
try:
proxmox_nodes = await self.hass.async_add_executor_job(
_get_nodes_data, import_data
)
except ProxmoxConnectTimeout:
return self.async_abort(reason="connect_timeout")
except ProxmoxAuthenticationError:
return self.async_abort(reason="invalid_auth")
except ProxmoxSSLError:
return self.async_abort(reason="ssl_error")
except ProxmoxNoNodesFound:
return self.async_abort(reason="no_nodes_found")
except ProxmoxConnectionError:
return self.async_abort(reason="cannot_connect")
return self.async_create_entry(
title=import_data[CONF_HOST],
data={**import_data, CONF_NODES: proxmox_nodes},
)
def _get_auth_schema(
self,
data: Mapping[str, Any],
@@ -342,28 +342,6 @@
"message": "A timeout occurred while trying to connect to the Proxmox VE instance."
}
},
"issues": {
"deprecated_yaml_import_issue_cannot_connect": {
"description": "Configuring {integration_title} via YAML is deprecated and will be removed in a future release. While importing your configuration, a connection error occurred. Please correct your YAML configuration and restart Home Assistant, or remove the {domain} key from your configuration and configure the integration via the UI.",
"title": "[%key:component::proxmoxve::issues::deprecated_yaml_import_issue_connect_timeout::title%]"
},
"deprecated_yaml_import_issue_connect_timeout": {
"description": "Configuring {integration_title} via YAML is deprecated and will be removed in a future release. While importing your configuration, a connection timeout occurred. Please correct your YAML configuration and restart Home Assistant, or remove the {domain} key from your configuration and configure the integration via the UI.",
"title": "The {integration_title} YAML configuration is being removed"
},
"deprecated_yaml_import_issue_invalid_auth": {
"description": "Configuring {integration_title} via YAML is deprecated and will be removed in a future release. While importing your configuration, invalid authentication details were found. Please correct your YAML configuration and restart Home Assistant, or remove the {domain} key from your configuration and configure the integration via the UI.",
"title": "[%key:component::proxmoxve::issues::deprecated_yaml_import_issue_connect_timeout::title%]"
},
"deprecated_yaml_import_issue_no_nodes_found": {
"description": "Configuring {integration_title} via YAML is deprecated and will be removed in a future release. While importing your configuration, no active nodes were found on the Proxmox VE server. Please correct your YAML configuration and restart Home Assistant, or remove the {domain} key from your configuration and configure the integration via the UI.",
"title": "[%key:component::proxmoxve::issues::deprecated_yaml_import_issue_connect_timeout::title%]"
},
"deprecated_yaml_import_issue_ssl_error": {
"description": "Configuring {integration_title} via YAML is deprecated and will be removed in a future release. While importing your configuration, an SSL error occurred. Please correct your YAML configuration and restart Home Assistant, or remove the {domain} key from your configuration and configure the integration via the UI.",
"title": "[%key:component::proxmoxve::issues::deprecated_yaml_import_issue_connect_timeout::title%]"
}
},
"selector": {
"auth_method": {
"options": {
+3 -78
View File
@@ -9,7 +9,7 @@ import pytest
import requests
from requests.exceptions import ConnectTimeout, SSLError
from homeassistant.components.proxmoxve import CONF_AUTH_METHOD, CONF_HOST, CONF_REALM
from homeassistant.components.proxmoxve import CONF_AUTH_METHOD, CONF_REALM
from homeassistant.components.proxmoxve.const import (
CONF_NODE,
CONF_NODES,
@@ -17,8 +17,9 @@ from homeassistant.components.proxmoxve.const import (
CONF_TOKEN_SECRET,
DOMAIN,
)
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER, ConfigEntryState
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
CONF_PORT,
CONF_TOKEN,
@@ -424,82 +425,6 @@ async def test_duplicate_entry(
assert result["reason"] == "already_configured"
async def test_import_flow(
hass: HomeAssistant,
mock_setup_entry: MagicMock,
mock_proxmox_client: MagicMock,
) -> None:
"""Test importing from YAML creates a config entry and sets it up."""
MOCK_IMPORT_CONFIG = {
DOMAIN: {
**MOCK_USER_STEP,
**MOCK_USER_AUTH_STEP_PASSWORD,
**MOCK_USER_SETUP,
}
}
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=MOCK_IMPORT_CONFIG[DOMAIN]
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "127.0.0.1"
assert result["data"][CONF_HOST] == "127.0.0.1"
assert len(mock_setup_entry.mock_calls) == 1
assert result["result"].state is ConfigEntryState.LOADED
@pytest.mark.parametrize(
("exception", "reason"),
[
(
AuthenticationError("Invalid credentials"),
"invalid_auth",
),
(
SSLError("SSL handshake failed"),
"ssl_error",
),
(
ConnectTimeout("Connection timed out"),
"connect_timeout",
),
(
ResourceException("404", "status_message", "content"),
"no_nodes_found",
),
(
requests.exceptions.ConnectionError("Connection error"),
"cannot_connect",
),
],
)
async def test_import_flow_exceptions(
hass: HomeAssistant,
mock_setup_entry: MagicMock,
mock_proxmox_client: MagicMock,
exception: Exception,
reason: str,
) -> None:
"""Test importing from YAML creates a config entry and sets it up."""
MOCK_IMPORT_CONFIG = {
DOMAIN: {
**MOCK_USER_STEP,
**MOCK_USER_AUTH_STEP_PASSWORD,
**MOCK_USER_SETUP,
}
}
mock_proxmox_client.nodes.get.side_effect = exception
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=MOCK_IMPORT_CONFIG[DOMAIN]
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == reason
assert len(mock_setup_entry.mock_calls) == 0
assert len(hass.config_entries.async_entries(DOMAIN)) == 0
def sanitize_config_entry(data: dict[str, Any]) -> dict[str, Any]:
"""Sanitize config entry data by removing unused auth keys."""
# Ignore unused keys (i.e. when switching from password to token or vice versa)
+1 -45
View File
@@ -11,11 +11,7 @@ from requests.exceptions import ConnectTimeout, SSLError
from homeassistant.components.proxmoxve.const import (
AUTH_PAM,
CONF_AUTH_METHOD,
CONF_CONTAINERS,
CONF_NODE,
CONF_NODES,
CONF_REALM,
CONF_VMS,
DOMAIN,
)
from homeassistant.components.proxmoxve.coordinator import (
@@ -32,54 +28,14 @@ from homeassistant.const import (
STATE_OFF,
STATE_ON,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
import homeassistant.helpers.issue_registry as ir
from homeassistant.setup import async_setup_component
from . import setup_integration
from tests.common import MockConfigEntry, async_load_json_array_fixture
@pytest.mark.usefixtures("mock_setup_entry")
async def test_config_import(
hass: HomeAssistant,
mock_proxmox_client: MagicMock,
issue_registry: ir.IssueRegistry,
) -> None:
"""Test sensor initialization."""
await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
CONF_HOST: "127.0.0.1",
CONF_PORT: 8006,
CONF_REALM: "pam",
CONF_USERNAME: "test_user@pam",
CONF_PASSWORD: "test_password",
CONF_VERIFY_SSL: True,
CONF_NODES: [
{
CONF_NODE: "pve1",
CONF_VMS: [100, 101],
CONF_CONTAINERS: [200, 201],
},
],
}
]
},
)
await hass.async_block_till_done()
assert len(issue_registry.issues) == 1
assert (HOMEASSISTANT_DOMAIN, "deprecated_yaml") in issue_registry.issues
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
@pytest.mark.parametrize(
("exception", "expected_state", "target"),
[