Mend incorrectly imported MQTT config entries (#68987)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
parent
2c82befc78
commit
9b21a48048
2 changed files with 114 additions and 13 deletions
|
@ -131,12 +131,15 @@ DEFAULT_PROTOCOL = PROTOCOL_311
|
||||||
DEFAULT_TLS_PROTOCOL = "auto"
|
DEFAULT_TLS_PROTOCOL = "auto"
|
||||||
|
|
||||||
DEFAULT_VALUES = {
|
DEFAULT_VALUES = {
|
||||||
CONF_PORT: DEFAULT_PORT,
|
|
||||||
CONF_WILL_MESSAGE: DEFAULT_WILL,
|
|
||||||
CONF_BIRTH_MESSAGE: DEFAULT_BIRTH,
|
CONF_BIRTH_MESSAGE: DEFAULT_BIRTH,
|
||||||
CONF_DISCOVERY: DEFAULT_DISCOVERY,
|
CONF_DISCOVERY: DEFAULT_DISCOVERY,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_TLS_VERSION: DEFAULT_TLS_PROTOCOL,
|
||||||
|
CONF_WILL_MESSAGE: DEFAULT_WILL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MANDATORY_DEFAULT_VALUES = (CONF_PORT,)
|
||||||
|
|
||||||
ATTR_TOPIC_TEMPLATE = "topic_template"
|
ATTR_TOPIC_TEMPLATE = "topic_template"
|
||||||
ATTR_PAYLOAD_TEMPLATE = "payload_template"
|
ATTR_PAYLOAD_TEMPLATE = "payload_template"
|
||||||
|
|
||||||
|
@ -203,9 +206,7 @@ CONFIG_SCHEMA_BASE = vol.Schema(
|
||||||
CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
|
CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
|
||||||
): cv.isfile,
|
): cv.isfile,
|
||||||
vol.Optional(CONF_TLS_INSECURE): cv.boolean,
|
vol.Optional(CONF_TLS_INSECURE): cv.boolean,
|
||||||
vol.Optional(CONF_TLS_VERSION, default=DEFAULT_TLS_PROTOCOL): vol.Any(
|
vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"),
|
||||||
"auto", "1.0", "1.1", "1.2"
|
|
||||||
),
|
|
||||||
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
|
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
|
||||||
cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])
|
cv.string, vol.In([PROTOCOL_31, PROTOCOL_311])
|
||||||
),
|
),
|
||||||
|
@ -220,6 +221,17 @@ CONFIG_SCHEMA_BASE = vol.Schema(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DEPRECATED_CONFIG_KEYS = [
|
||||||
|
CONF_BIRTH_MESSAGE,
|
||||||
|
CONF_BROKER,
|
||||||
|
CONF_DISCOVERY,
|
||||||
|
CONF_PASSWORD,
|
||||||
|
CONF_PORT,
|
||||||
|
CONF_TLS_VERSION,
|
||||||
|
CONF_USERNAME,
|
||||||
|
CONF_WILL_MESSAGE,
|
||||||
|
]
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
DOMAIN: vol.All(
|
DOMAIN: vol.All(
|
||||||
|
@ -602,6 +614,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
hass.data[DATA_MQTT_CONFIG] = conf
|
hass.data[DATA_MQTT_CONFIG] = conf
|
||||||
|
|
||||||
if not bool(hass.config_entries.async_entries(DOMAIN)):
|
if not bool(hass.config_entries.async_entries(DOMAIN)):
|
||||||
|
# Create an import flow if the user has yaml configured entities etc.
|
||||||
|
# but no broker configuration. Note: The intention is not for this to
|
||||||
|
# import broker configuration from YAML because that has been deprecated.
|
||||||
hass.async_create_task(
|
hass.async_create_task(
|
||||||
hass.config_entries.flow.async_init(
|
hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
@ -612,18 +627,53 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _merge_config(entry, conf):
|
def _merge_basic_config(
|
||||||
"""Merge configuration.yaml config with config entry."""
|
hass: HomeAssistant, entry: ConfigEntry, yaml_config: dict[str, Any]
|
||||||
# Base config on default values
|
) -> None:
|
||||||
|
"""Merge basic options in configuration.yaml config with config entry.
|
||||||
|
|
||||||
|
This mends incomplete migration from old version of HA Core.
|
||||||
|
"""
|
||||||
|
|
||||||
|
entry_updated = False
|
||||||
|
entry_config = {**entry.data}
|
||||||
|
for key in DEPRECATED_CONFIG_KEYS:
|
||||||
|
if key in yaml_config and key not in entry_config:
|
||||||
|
entry_config[key] = yaml_config[key]
|
||||||
|
entry_updated = True
|
||||||
|
|
||||||
|
for key in MANDATORY_DEFAULT_VALUES:
|
||||||
|
if key not in entry_config:
|
||||||
|
entry_config[key] = DEFAULT_VALUES[key]
|
||||||
|
entry_updated = True
|
||||||
|
|
||||||
|
if entry_updated:
|
||||||
|
hass.config_entries.async_update_entry(entry, data=entry_config)
|
||||||
|
|
||||||
|
|
||||||
|
def _merge_extended_config(entry, conf):
|
||||||
|
"""Merge advanced options in configuration.yaml config with config entry."""
|
||||||
|
# Add default values
|
||||||
conf = {**DEFAULT_VALUES, **conf}
|
conf = {**DEFAULT_VALUES, **conf}
|
||||||
return {**conf, **entry.data}
|
return {**conf, **entry.data}
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Load a config entry."""
|
"""Load a config entry."""
|
||||||
# If user didn't have configuration.yaml config, generate defaults
|
# Merge basic configuration, and add missing defaults for basic options
|
||||||
|
_merge_basic_config(hass, entry, hass.data.get(DATA_MQTT_CONFIG, {}))
|
||||||
|
|
||||||
|
# Bail out if broker setting is missing
|
||||||
|
if CONF_BROKER not in entry.data:
|
||||||
|
_LOGGER.error("MQTT broker is not configured, please configure it")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# If user doesn't have configuration.yaml config, generate default values
|
||||||
|
# for options not in config entry data
|
||||||
if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None:
|
if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None:
|
||||||
conf = CONFIG_SCHEMA_BASE(dict(entry.data))
|
conf = CONFIG_SCHEMA_BASE(dict(entry.data))
|
||||||
|
|
||||||
|
# User has configuration.yaml config, warn about config entry overrides
|
||||||
elif any(key in conf for key in entry.data):
|
elif any(key in conf for key in entry.data):
|
||||||
shared_keys = conf.keys() & entry.data.keys()
|
shared_keys = conf.keys() & entry.data.keys()
|
||||||
override = {k: entry.data[k] for k in shared_keys}
|
override = {k: entry.data[k] for k in shared_keys}
|
||||||
|
@ -635,8 +685,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
override,
|
override,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Merge the configuration values from configuration.yaml
|
# Merge advanced configuration values from configuration.yaml
|
||||||
conf = _merge_config(entry, conf)
|
conf = _merge_extended_config(entry, conf)
|
||||||
|
|
||||||
hass.data[DATA_MQTT] = MQTT(
|
hass.data[DATA_MQTT] = MQTT(
|
||||||
hass,
|
hass,
|
||||||
|
@ -870,7 +920,7 @@ class MQTT:
|
||||||
if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None:
|
if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None:
|
||||||
conf = CONFIG_SCHEMA_BASE(dict(entry.data))
|
conf = CONFIG_SCHEMA_BASE(dict(entry.data))
|
||||||
|
|
||||||
self.conf = _merge_config(entry, conf)
|
self.conf = _merge_extended_config(entry, conf)
|
||||||
await self.async_disconnect()
|
await self.async_disconnect()
|
||||||
self.init_client()
|
self.init_client()
|
||||||
await self.async_connect()
|
await self.async_connect()
|
||||||
|
|
|
@ -23,7 +23,7 @@ from homeassistant.const import (
|
||||||
TEMP_CELSIUS,
|
TEMP_CELSIUS,
|
||||||
)
|
)
|
||||||
import homeassistant.core as ha
|
import homeassistant.core as ha
|
||||||
from homeassistant.core import CoreState, callback
|
from homeassistant.core import CoreState, HomeAssistant, callback
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr, template
|
from homeassistant.helpers import device_registry as dr, template
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
@ -1620,6 +1620,57 @@ async def test_setup_entry_with_config_override(hass, device_reg, mqtt_client_mo
|
||||||
assert device_entry is not None
|
assert device_entry is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_update_incomplete_entry(
|
||||||
|
hass: HomeAssistant, device_reg, mqtt_client_mock, caplog
|
||||||
|
):
|
||||||
|
"""Test if the MQTT component loads when config entry data is incomplete."""
|
||||||
|
data = (
|
||||||
|
'{ "device":{"identifiers":["0AFFD2"]},'
|
||||||
|
' "state_topic": "foobar/sensor",'
|
||||||
|
' "unique_id": "unique" }'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Config entry data is incomplete
|
||||||
|
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={"port": 1234})
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
# Mqtt present in yaml config
|
||||||
|
config = {"broker": "yaml_broker"}
|
||||||
|
await async_setup_component(hass, mqtt.DOMAIN, {mqtt.DOMAIN: config})
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
# Config entry data should now be updated
|
||||||
|
assert entry.data == {
|
||||||
|
"port": 1234,
|
||||||
|
"broker": "yaml_broker",
|
||||||
|
}
|
||||||
|
# Warnings about broker deprecated, but not about other keys with default values
|
||||||
|
assert (
|
||||||
|
"The 'broker' option is deprecated, please remove it from your configuration"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
"Deprecated configuration settings found in configuration.yaml. These settings "
|
||||||
|
"from your configuration entry will override: {'broker': 'yaml_broker'}"
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
# Discover a device to verify the entry was setup correctly
|
||||||
|
async_fire_mqtt_message(hass, "homeassistant/sensor/bla/config", data)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
device_entry = device_reg.async_get_device({("mqtt", "0AFFD2")})
|
||||||
|
assert device_entry is not None
|
||||||
|
|
||||||
|
|
||||||
|
async def test_fail_no_broker(hass, device_reg, mqtt_client_mock, caplog):
|
||||||
|
"""Test if the MQTT component loads when broker configuration is missing."""
|
||||||
|
# Config entry data is incomplete
|
||||||
|
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={})
|
||||||
|
entry.add_to_hass(hass)
|
||||||
|
assert not await hass.config_entries.async_setup(entry.entry_id)
|
||||||
|
assert "MQTT broker is not configured, please configure it" in caplog.text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.no_fail_on_log_exception
|
@pytest.mark.no_fail_on_log_exception
|
||||||
async def test_message_callback_exception_gets_logged(hass, caplog, mqtt_mock):
|
async def test_message_callback_exception_gets_logged(hass, caplog, mqtt_mock):
|
||||||
"""Test exception raised by message handler."""
|
"""Test exception raised by message handler."""
|
||||||
|
|
Loading…
Add table
Reference in a new issue