Remove Splunk YAML import (#183678)

This commit is contained in:
Brett Adams
2026-09-30 08:50:22 +02:00
committed by GitHub
parent 637873ee59
commit 86d28c29b8
7 changed files with 84 additions and 475 deletions
+15 -91
View File
@@ -10,7 +10,7 @@ from aiohttp import ClientConnectionError, ClientResponseError
from hass_splunk import SplunkPayloadError, hass_splunk
import probatio
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
@@ -20,33 +20,16 @@ from homeassistant.const import (
CONF_VERIFY_SSL,
EVENT_STATE_CHANGED,
)
from homeassistant.core import (
DOMAIN as HOMEASSISTANT_DOMAIN,
Event,
EventStateChangedData,
HomeAssistant,
)
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.core import Event, EventStateChangedData, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import (
config_validation as cv,
issue_registry as ir,
state as state_helper,
)
from homeassistant.helpers import config_validation as cv, state as state_helper
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entityfilter import FILTER_SCHEMA, EntityFilter
from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
from .const import (
CONF_FILTER,
DEFAULT_HOST,
DEFAULT_NAME,
DEFAULT_PORT,
DEFAULT_SSL,
DOMAIN,
)
from .const import CONF_FILTER, DOMAIN
_LOGGER = logging.getLogger(__name__)
@@ -54,16 +37,16 @@ DATA_FILTER: HassKey[EntityFilter] = HassKey(DOMAIN)
CONFIG_SCHEMA = probatio.Schema(
{
DOMAIN: probatio.Schema(
{
probatio.Optional(CONF_TOKEN): cv.string,
probatio.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string,
probatio.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
probatio.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean,
probatio.Optional(CONF_VERIFY_SSL, default=True): cv.boolean,
probatio.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
probatio.Optional(CONF_FILTER, default={}): FILTER_SCHEMA,
}
DOMAIN: probatio.All(
cv.removed(CONF_TOKEN, raise_if_present=False),
cv.removed(CONF_HOST, raise_if_present=False),
cv.removed(CONF_PORT, raise_if_present=False),
cv.removed(CONF_SSL, raise_if_present=False),
cv.removed(CONF_VERIFY_SSL, raise_if_present=False),
cv.removed(CONF_NAME, raise_if_present=False),
probatio.Schema(
{probatio.Optional(CONF_FILTER, default={}): FILTER_SCHEMA}
),
)
},
extra=probatio.ALLOW_EXTRA,
@@ -74,76 +57,17 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Splunk component from YAML.
Stores the entity filter in hass.data for use by config entry setup.
Triggers config entry import for connection settings (with deprecation warning).
Filter-only YAML configs are allowed without deprecation.
"""
if DOMAIN not in config:
# No YAML config - store empty filter for config entry to use
# Use setdefault to avoid overwriting a filter set for testing
hass.data.setdefault(DATA_FILTER, FILTER_SCHEMA({}))
return True
conf = config[DOMAIN]
# Store the entity filter in hass.data for async_setup_entry to use
hass.data[DATA_FILTER] = conf.pop(CONF_FILTER)
# Check if YAML has connection settings (anything beyond filter)
# If only filter is configured, no deprecation warning is needed
if CONF_TOKEN in conf:
# Trigger import of connection settings to config entry
hass.async_create_task(_async_import_yaml(hass, conf))
# If only filter, no import needed - filter is stored and will be used
hass.data[DATA_FILTER] = config[DOMAIN][CONF_FILTER]
return True
async def _async_import_yaml(hass: HomeAssistant, conf: dict[str, Any]) -> None:
"""Import YAML config and create deprecation issues."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data=conf,
)
if result.get("type") is FlowResultType.ABORT and result.get("reason") not in (
"already_configured",
"single_instance_allowed",
):
# Import failed with error - create error-specific issue
ir.async_create_issue(
hass,
DOMAIN,
f"deprecated_yaml_import_issue_{result.get('reason')}",
breaks_in_ha_version="2026.9.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": "Splunk",
},
)
return
# Import succeeded or already configured - create standard deprecation issue
ir.async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2026.9.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
"domain": DOMAIN,
"integration_title": "Splunk",
},
)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Splunk from a config entry."""
host = entry.data[CONF_HOST]
@@ -64,27 +64,6 @@ class SplunkConfigFlow(ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_import(
self, import_config: dict[str, Any]
) -> ConfigFlowResult:
"""Handle import from YAML configuration."""
# Single instance integration - manifest prevents duplicates
# Validate the imported configuration
errors = await self._async_validate_input(import_config)
if errors:
# Map error keys to abort reasons for issue creation
error_key = errors.get("base", "unknown")
_LOGGER.error("Failed to import Splunk configuration from YAML: %s", errors)
return self.async_abort(reason=error_key)
host = import_config.get(CONF_HOST, DEFAULT_HOST)
port = import_config.get(CONF_PORT, DEFAULT_PORT)
return self.async_create_entry(
title=f"{host}:{port}",
data=import_config,
)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
-1
View File
@@ -7,4 +7,3 @@ CONF_FILTER = "filter"
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 8088
DEFAULT_SSL = True
DEFAULT_NAME = "HASS"
@@ -143,7 +143,11 @@ rules:
comment: |
Integration does not create entities.
reconfiguration-flow: done
repair-issues: done
repair-issues:
status: exempt
comment: |
Connection failures are retried via ConfigEntryNotReady and an invalid
token starts the reauth flow, so there is nothing for a repair to fix.
stale-devices:
status: exempt
comment: |
@@ -1,16 +1,8 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"invalid_config": "The YAML configuration is invalid and cannot be imported. Please check your configuration.yaml file.",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
@@ -78,19 +70,5 @@
"unexpected_connect_error": {
"message": "Unexpected error while connecting to Splunk."
}
},
"issues": {
"deprecated_yaml_import_issue_cannot_connect": {
"description": "Configuring {integration_title} via YAML is deprecated and will be removed in a future release.\n\nWhile importing your configuration, a connection error occurred. Please correct your YAML configuration and restart Home Assistant, or remove the connection settings from your `{domain}:` configuration and configure the integration via the UI.\n\nNote: Entity filtering via YAML (`filter:`) will continue to work.",
"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.\n\nWhile importing your configuration, the authentication token was rejected. Please correct your YAML configuration and restart Home Assistant, or remove the connection settings from your `{domain}:` configuration and configure the integration via the UI.\n\nNote: Entity filtering via YAML (`filter:`) will continue to work.",
"title": "[%key:component::splunk::issues::deprecated_yaml_import_issue_cannot_connect::title%]"
},
"deprecated_yaml_import_issue_unknown": {
"description": "Configuring {integration_title} via YAML is deprecated and will be removed in a future release.\n\nWhile importing your configuration, an unexpected error occurred. Please check your YAML configuration and restart Home Assistant, or remove the connection settings from your `{domain}:` configuration and configure the integration via the UI.\n\nNote: Entity filtering via YAML (`filter:`) will continue to work.",
"title": "[%key:component::splunk::issues::deprecated_yaml_import_issue_cannot_connect::title%]"
}
}
}
+2 -100
View File
@@ -4,8 +4,8 @@ from unittest.mock import AsyncMock
import pytest
from homeassistant.components.splunk.const import DEFAULT_HOST, DEFAULT_PORT, DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.components.splunk.const import DOMAIN
from homeassistant.config_entries import SOURCE_USER
from homeassistant.const import (
CONF_HOST,
CONF_NAME,
@@ -147,104 +147,6 @@ async def test_user_flow_already_configured(
assert result["reason"] == "single_instance_allowed"
async def test_import_flow_success(
hass: HomeAssistant, mock_hass_splunk: AsyncMock
) -> None:
"""Test successful import flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_TOKEN: "test-token-123",
CONF_HOST: "splunk.example.com",
CONF_PORT: 8088,
CONF_SSL: False,
CONF_NAME: "Imported Splunk",
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "splunk.example.com:8088"
assert result["data"] == {
CONF_TOKEN: "test-token-123",
CONF_HOST: "splunk.example.com",
CONF_PORT: 8088,
CONF_SSL: False,
CONF_NAME: "Imported Splunk",
}
@pytest.mark.parametrize(
("side_effect", "reason"),
[
([False, True], "cannot_connect"),
([True, False], "invalid_auth"),
(Exception("Unexpected error"), "unknown"),
],
)
async def test_import_flow_error_and_recovery(
hass: HomeAssistant,
mock_hass_splunk: AsyncMock,
side_effect: list[bool] | Exception,
reason: str,
) -> None:
"""Test import flow errors and recovery."""
mock_hass_splunk.check.side_effect = side_effect
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_TOKEN: "test-token-123",
CONF_HOST: "splunk.example.com",
CONF_PORT: 8088,
CONF_SSL: False,
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == reason
# Test recovery by resetting mock and importing again
mock_hass_splunk.check.side_effect = None
mock_hass_splunk.check.return_value = True
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_TOKEN: "test-token-123",
CONF_HOST: "splunk.example.com",
CONF_PORT: 8088,
CONF_SSL: False,
},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
async def test_import_flow_already_configured(
hass: HomeAssistant, mock_hass_splunk: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
"""Test import flow when entry is already configured (single instance)."""
mock_config_entry.add_to_hass(hass)
# With single_config_entry in manifest, import should abort immediately
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_IMPORT},
data={
CONF_TOKEN: "test-token-123",
CONF_HOST: DEFAULT_HOST,
CONF_PORT: DEFAULT_PORT,
CONF_SSL: False,
},
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "single_instance_allowed"
async def test_reconfigure_flow_success(
hass: HomeAssistant, mock_hass_splunk: AsyncMock, mock_config_entry: MockConfigEntry
) -> None:
+62 -239
View File
@@ -9,14 +9,16 @@ from hass_splunk import SplunkPayloadError
import pytest
from homeassistant.components.splunk.const import CONF_FILTER, DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntryState
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_SSL, CONF_TOKEN
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.helpers import issue_registry as ir
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
YAML_FILTER = {"include_domains": ["sensor"]}
async def test_setup_entry_success(
hass: HomeAssistant, mock_hass_splunk: AsyncMock, mock_config_entry: MockConfigEntry
@@ -95,80 +97,6 @@ async def test_unload_entry(
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
@pytest.mark.usefixtures("mock_setup_entry")
async def test_yaml_import_without_filter(
hass: HomeAssistant, mock_hass_splunk: AsyncMock
) -> None:
"""Test YAML configuration without filter triggers import."""
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
CONF_TOKEN: "test-token",
CONF_HOST: "localhost",
CONF_PORT: 8088,
CONF_SSL: False,
}
},
)
await hass.async_block_till_done()
# Verify import flow was triggered
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].source == SOURCE_IMPORT
@pytest.mark.usefixtures("mock_setup_entry", "mock_hass_splunk")
async def test_yaml_import_defaults_ssl_on(hass: HomeAssistant) -> None:
"""Test YAML import defaults to SSL enabled when the field is omitted."""
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
CONF_TOKEN: "test-token",
CONF_HOST: "localhost",
CONF_PORT: 8088,
}
},
)
await hass.async_block_till_done()
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].data[CONF_SSL] is True
@pytest.mark.usefixtures("mock_setup_entry")
async def test_yaml_with_filter(
hass: HomeAssistant, mock_hass_splunk: AsyncMock
) -> None:
"""Test YAML configuration with filter triggers import."""
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
CONF_TOKEN: "test-token",
CONF_HOST: "localhost",
CONF_PORT: 8088,
CONF_SSL: False,
CONF_FILTER: {
"include_domains": ["sensor"],
},
}
},
)
await hass.async_block_till_done()
# Verify import flow was triggered
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].source == SOURCE_IMPORT
async def test_setup_without_yaml(
hass: HomeAssistant, mock_hass_splunk: AsyncMock
) -> None:
@@ -177,52 +105,77 @@ async def test_setup_without_yaml(
await hass.async_block_till_done()
@pytest.mark.parametrize(
"yaml_config",
[
pytest.param({CONF_FILTER: YAML_FILTER}, id="filter_only"),
pytest.param(
{
CONF_TOKEN: "yaml-token",
CONF_HOST: "yaml-host",
CONF_PORT: 8089,
CONF_SSL: False,
CONF_FILTER: YAML_FILTER,
},
id="with_removed_connection_settings",
),
],
)
async def test_event_listener_with_filter(
hass: HomeAssistant, mock_hass_splunk: AsyncMock
hass: HomeAssistant,
mock_hass_splunk: AsyncMock,
mock_config_entry: MockConfigEntry,
yaml_config: ConfigType,
) -> None:
"""Test event listener respects entity filter from YAML."""
# Set up via YAML with a filter that only allows sensor entities
mock_config_entry.add_to_hass(hass)
assert await async_setup_component(hass, DOMAIN, {DOMAIN: yaml_config})
await hass.async_block_till_done()
assert hass.config_entries.async_entries(DOMAIN) == [mock_config_entry]
assert mock_config_entry.state is ConfigEntryState.LOADED
# Reset queue call count after startup event
mock_hass_splunk.queue.reset_mock()
hass.states.async_set("sensor.test", "123")
await hass.async_block_till_done()
assert mock_hass_splunk.queue.call_count == 1
mock_hass_splunk.queue.reset_mock()
hass.states.async_set("light.test", "on")
await hass.async_block_till_done()
assert mock_hass_splunk.queue.call_count == 0
async def test_yaml_connection_settings_not_imported(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test YAML connection settings are ignored and no config entry is created."""
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
CONF_TOKEN: "test-token",
CONF_HOST: "localhost",
CONF_PORT: 8088,
CONF_TOKEN: "yaml-token",
CONF_HOST: "yaml-host",
CONF_PORT: 8089,
CONF_SSL: False,
CONF_FILTER: {
"include_domains": ["sensor"],
},
}
},
)
await hass.async_block_till_done()
# Verify config entry was created
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].state is ConfigEntryState.LOADED
# Reset queue call count after startup event
mock_hass_splunk.queue.reset_mock()
# Create a sensor state (should be sent)
hass.states.async_set("sensor.test", "123")
await hass.async_block_till_done()
# Verify event was sent for sensor
assert mock_hass_splunk.queue.call_count == 1
# Reset
mock_hass_splunk.queue.reset_mock()
# Create a light state (should be filtered out)
hass.states.async_set("light.test", "on")
await hass.async_block_till_done()
# Verify no event was sent for light (filtered out)
assert mock_hass_splunk.queue.call_count == 0
assert hass.config_entries.async_entries(DOMAIN) == []
assert (
"The 'token' option has been removed, please remove it from your configuration"
in caplog.text
)
async def test_event_listener_unauthorized(
@@ -309,133 +262,3 @@ async def test_event_listener_error_handling(
record.levelno == expected_log_level and expected_message in record.message
for record in caplog.records
)
async def test_yaml_filter_only_no_deprecation_issue(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_hass_splunk: AsyncMock,
) -> None:
"""Test YAML with only filter does not create deprecation issue."""
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
# Only filter, no connection settings (no token)
CONF_FILTER: {
"include_domains": ["sensor"],
},
}
},
)
await hass.async_block_till_done()
# Verify no config entry was created (no import)
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 0
# Verify no deprecation issue was created
issues = issue_registry.issues
assert not any(
issue_id[0] == DOMAIN and "deprecated" in issue_id[1] for issue_id in issues
)
assert not any(
issue_id[0] == HOMEASSISTANT_DOMAIN and DOMAIN in issue_id[1]
for issue_id in issues
)
@pytest.mark.usefixtures("mock_setup_entry")
async def test_yaml_with_connection_creates_deprecation_issue(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_hass_splunk: AsyncMock,
) -> None:
"""Test YAML with connection settings creates deprecation issue."""
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
CONF_TOKEN: "test-token",
CONF_HOST: "localhost",
CONF_PORT: 8088,
CONF_SSL: False,
}
},
)
await hass.async_block_till_done()
# Verify import flow was triggered
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 1
assert entries[0].source == SOURCE_IMPORT
# Verify deprecation issue was created in homeassistant domain
assert (HOMEASSISTANT_DOMAIN, f"deprecated_yaml_{DOMAIN}") in issue_registry.issues
async def test_yaml_import_error_creates_specific_issue(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_hass_splunk: AsyncMock,
) -> None:
"""Test YAML import with connection error creates specific issue."""
# Config flow client fails connectivity check
mock_hass_splunk.check.return_value = False
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
CONF_TOKEN: "test-token",
CONF_HOST: "invalid-host",
CONF_PORT: 8088,
CONF_SSL: False,
}
},
)
await hass.async_block_till_done()
# Verify no config entry was created (import failed)
entries = hass.config_entries.async_entries(DOMAIN)
assert len(entries) == 0
# Verify error-specific issue was created
assert (
DOMAIN,
"deprecated_yaml_import_issue_cannot_connect",
) in issue_registry.issues
@pytest.mark.usefixtures("mock_setup_entry")
async def test_yaml_import_already_configured_creates_deprecation_issue(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
mock_hass_splunk: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test YAML import when already configured still creates deprecation issue."""
# Add existing config entry before YAML import
mock_config_entry.add_to_hass(hass)
# Set up component with YAML - should see existing entry and
# abort with single_instance_allowed
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: {
CONF_TOKEN: "test-token",
CONF_HOST: "localhost",
CONF_PORT: 8088,
CONF_SSL: False,
}
},
)
await hass.async_block_till_done()
# Verify deprecation issue was still created (single_instance_allowed is ok)
assert (HOMEASSISTANT_DOMAIN, f"deprecated_yaml_{DOMAIN}") in issue_registry.issues