Mend incorrectly imported MQTT config entries (#68987)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
This commit is contained in:
Erik Montnemery 2022-04-01 17:11:31 +02:00 committed by GitHub
parent 2c82befc78
commit 9b21a48048
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 114 additions and 13 deletions

View file

@ -131,12 +131,15 @@ DEFAULT_PROTOCOL = PROTOCOL_311
DEFAULT_TLS_PROTOCOL = "auto"
DEFAULT_VALUES = {
CONF_PORT: DEFAULT_PORT,
CONF_WILL_MESSAGE: DEFAULT_WILL,
CONF_BIRTH_MESSAGE: DEFAULT_BIRTH,
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_PAYLOAD_TEMPLATE = "payload_template"
@ -203,9 +206,7 @@ CONFIG_SCHEMA_BASE = vol.Schema(
CONF_CLIENT_CERT, "client_key_auth", msg=CLIENT_KEY_AUTH_MSG
): cv.isfile,
vol.Optional(CONF_TLS_INSECURE): cv.boolean,
vol.Optional(CONF_TLS_VERSION, default=DEFAULT_TLS_PROTOCOL): vol.Any(
"auto", "1.0", "1.1", "1.2"
),
vol.Optional(CONF_TLS_VERSION): vol.Any("auto", "1.0", "1.1", "1.2"),
vol.Optional(CONF_PROTOCOL, default=DEFAULT_PROTOCOL): vol.All(
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(
{
DOMAIN: vol.All(
@ -602,6 +614,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.data[DATA_MQTT_CONFIG] = conf
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.config_entries.flow.async_init(
DOMAIN,
@ -612,18 +627,53 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True
def _merge_config(entry, conf):
"""Merge configuration.yaml config with config entry."""
# Base config on default values
def _merge_basic_config(
hass: HomeAssistant, entry: ConfigEntry, yaml_config: dict[str, Any]
) -> 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}
return {**conf, **entry.data}
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""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:
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):
shared_keys = conf.keys() & entry.data.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,
)
# Merge the configuration values from configuration.yaml
conf = _merge_config(entry, conf)
# Merge advanced configuration values from configuration.yaml
conf = _merge_extended_config(entry, conf)
hass.data[DATA_MQTT] = MQTT(
hass,
@ -870,7 +920,7 @@ class MQTT:
if (conf := hass.data.get(DATA_MQTT_CONFIG)) is None:
conf = CONFIG_SCHEMA_BASE(dict(entry.data))
self.conf = _merge_config(entry, conf)
self.conf = _merge_extended_config(entry, conf)
await self.async_disconnect()
self.init_client()
await self.async_connect()

View file

@ -23,7 +23,7 @@ from homeassistant.const import (
TEMP_CELSIUS,
)
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.helpers import device_registry as dr, template
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
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
async def test_message_callback_exception_gets_logged(hass, caplog, mqtt_mock):
"""Test exception raised by message handler."""