Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
49bdb6e
feat(ollama): allow to setup bearer token
ziouf Mar 11, 2026
bff9b0d
fix: use truthiness check
ziouf Mar 11, 2026
46ce198
fix: persist api_key in config
ziouf Mar 11, 2026
c07595a
test: add tests for api_key
ziouf Mar 11, 2026
706bac3
Merge branch 'dev' into feat/ollama-bearer-token
ziouf Mar 11, 2026
30710f8
fix: use ha co CONF_API_KEY constant
ziouf Mar 11, 2026
873394c
fix: use None as default api_key value
ziouf Mar 12, 2026
e2d8a5f
fix: explicit error message on invalid api_key
ziouf Mar 12, 2026
b7a3bc9
fix: default value of optional api_key
ziouf Mar 12, 2026
fcf62eb
fix: default value of optional api_key
ziouf Mar 12, 2026
442d9bd
test: add test case for unauthorized api-key exception
ziouf Mar 12, 2026
5b067aa
test: rename to invalid_auth
ziouf Mar 12, 2026
25d8ab3
fix: always raise invalid_auth error on HTTP 401
ziouf Mar 12, 2026
be20193
fix: use default translation
ziouf Mar 12, 2026
b92d6f0
test: ensure CONF_API_KEY is not persisted if not defined by user
ziouf Mar 12, 2026
8499cb0
test: add setup test
ziouf Mar 12, 2026
29e7ba9
test: add test cases
ziouf Mar 12, 2026
a29ab9d
fix: manage ConfigEntryAuthFailed exception
ziouf Mar 12, 2026
06b466a
fix: enhance management of 401/403 errors
ziouf Mar 12, 2026
742cadf
test: use core CONF_URL and CONF_API_KEY instead of ollama specific ones
ziouf Mar 12, 2026
c42d985
fix: trim api_key value
ziouf Mar 12, 2026
b404210
fix: validate url before duplicate entry check
ziouf Mar 12, 2026
0f90086
fix: trim url user input
ziouf Mar 12, 2026
60f96c8
test: fix config flow minor version
ziouf Mar 12, 2026
fccf115
fix: use ConfigEntryError for others http codes than 401 and 403
ziouf Mar 12, 2026
98d1ba5
test: fix ConfigEntryError
ziouf Mar 12, 2026
18f9ef9
test: rename test
ziouf Mar 12, 2026
87422a8
fix: better management of http 5xx and 429
ziouf Mar 12, 2026
8283a49
fix: defensive trim at runtime
ziouf Mar 12, 2026
c67de69
chore: add comments and test function renaming
ziouf Mar 13, 2026
6587a48
feat: implement async_step_reauth flow
ziouf Mar 13, 2026
c61fd40
test: combine tests via parametrize
ziouf Mar 13, 2026
8ccdb5f
test: combine tests via parametrize
ziouf Mar 13, 2026
642aa8b
test: update reauth_flow test to ensure reauth_flow is completed succ…
ziouf Mar 13, 2026
94d93dd
testadd test to ensure flows recover after an error and completes suc…
ziouf Mar 13, 2026
46062c8
test: trim api_key after ensuring itis not None
ziouf Mar 13, 2026
1ee947a
test: add case for reconfigure without api_key to remove it
ziouf Mar 13, 2026
5174544
Potential fix for pull request finding
ziouf Mar 13, 2026
c20a231
Potential fix for pull request finding
ziouf Mar 13, 2026
505092b
test: add ca for spaces only api_key
ziouf Mar 13, 2026
ed81b2b
Potential fix for pull request finding
ziouf Mar 13, 2026
2a4f3cf
Potential fix for pull request finding
ziouf Mar 14, 2026
8c1babc
Potential fix for pull request finding
ziouf Mar 14, 2026
b86f602
Merge branch 'dev' into feat/ollama-bearer-token
ziouf Mar 14, 2026
cb4802d
Fix
joostlek Mar 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions homeassistant/components/ollama/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import ollama

from homeassistant.config_entries import ConfigEntry, ConfigSubentry
from homeassistant.const import CONF_URL, Platform
from homeassistant.const import CONF_API_KEY, CONF_URL, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import (
Expand Down Expand Up @@ -62,7 +62,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: OllamaConfigEntry) -> bool:
"""Set up Ollama from a config entry."""
settings = {**entry.data, **entry.options}
client = ollama.AsyncClient(host=settings[CONF_URL], verify=get_default_context())
api_key = settings.get(CONF_API_KEY) or ""
client = ollama.AsyncClient(
host=settings[CONF_URL],
headers={"Authorization": f"Bearer {api_key}"} if api_key else None,
verify=get_default_context(),
)
try:
async with asyncio.timeout(DEFAULT_TIMEOUT):
await client.list()
Expand Down
17 changes: 13 additions & 4 deletions homeassistant/components/ollama/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
ConfigSubentryFlow,
SubentryFlowResult,
)
from homeassistant.const import CONF_LLM_HASS_API, CONF_NAME, CONF_URL
from homeassistant.const import CONF_API_KEY, CONF_LLM_HASS_API, CONF_NAME, CONF_URL
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, llm
from homeassistant.helpers.selector import (
Expand Down Expand Up @@ -68,7 +68,10 @@
vol.Required(CONF_URL): TextSelector(
TextSelectorConfig(type=TextSelectorType.URL)
),
}
vol.Optional(CONF_API_KEY, default=""): TextSelector(
TextSelectorConfig(type=TextSelectorType.PASSWORD)
),
},
)


Expand All @@ -93,6 +96,7 @@ async def async_step_user(

errors = {}
url = user_input[CONF_URL]
api_key = user_input.get(CONF_API_KEY) or ""

self._async_abort_entries_match({CONF_URL: url})

Expand All @@ -109,7 +113,12 @@ async def async_step_user(
)

try:
client = ollama.AsyncClient(host=url, verify=get_default_context())
client = ollama.AsyncClient(
host=url,
headers={"Authorization": f"Bearer {api_key}"} if api_key else None,
verify=get_default_context(),
)

async with asyncio.timeout(DEFAULT_TIMEOUT):
await client.list()
except TimeoutError, httpx.ConnectError:
Expand All @@ -129,7 +138,7 @@ async def async_step_user(

return self.async_create_entry(
title=url,
data={CONF_URL: url},
data={CONF_URL: url, CONF_API_KEY: api_key},
)

@classmethod
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/ollama/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"step": {
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
"url": "[%key:common::config_flow::data::url%]"
}
}
Expand Down
53 changes: 51 additions & 2 deletions tests/components/ollama/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
"""Test the Ollama config flow."""

import asyncio
from unittest.mock import patch
from unittest.mock import ANY, AsyncMock, patch

from httpx import ConnectError
import pytest

from homeassistant import config_entries
from homeassistant.components import ollama
from homeassistant.const import CONF_LLM_HASS_API, CONF_NAME
from homeassistant.components.ollama.const import DOMAIN
from homeassistant.const import CONF_API_KEY, CONF_LLM_HASS_API, CONF_NAME, CONF_URL
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType

Expand Down Expand Up @@ -50,6 +51,7 @@ async def test_form(hass: HomeAssistant) -> None:
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["data"] == {
ollama.CONF_URL: "http://localhost:11434",
ollama.CONF_API_KEY: "", # Default API key should be empty string
}
# No subentries created by default
assert len(result2.get("subentries", [])) == 0
Expand Down Expand Up @@ -557,3 +559,50 @@ async def test_ai_task_subentry_not_loaded(

assert result.get("type") is FlowResultType.ABORT
assert result.get("reason") == "entry_not_loaded"


@pytest.mark.parametrize(
("user_input", "expected_headers"),
[
(
{CONF_URL: "http://localhost:11434", CONF_API_KEY: "my-secret-token"},
{"Authorization": "Bearer my-secret-token"},
),
(
{CONF_URL: "http://localhost:11434", CONF_API_KEY: ""},
None,
),
(
{CONF_URL: "http://localhost:11434"},
None,
),
],
)
async def test_user_step_async_client_headers(
hass: HomeAssistant,
user_input: dict[str, str],
expected_headers: dict[str, str] | None,
) -> None:
"""Test Authorization header passed to AsyncClient with/without api_key."""
with patch(
"homeassistant.components.ollama.config_flow.ollama.AsyncClient",
) as mock_async_client:
mock_async_client.return_value.list = AsyncMock(return_value={"models": []})

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "user"}
)
assert result["type"] is FlowResultType.FORM

result = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input=user_input,
)
await hass.async_block_till_done()

assert result["type"] is FlowResultType.CREATE_ENTRY
mock_async_client.assert_called_with(
host="http://localhost:11434",
headers=expected_headers,
verify=ANY,
)
Loading