Initialize docker stack repo

This commit is contained in:
g4st3r
2026-03-07 17:28:59 +00:00
commit 319d4eeb3e
458 changed files with 67215 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
from dataclasses import dataclass
import logging
from typing import Final, Optional
from urllib.parse import urljoin
import aiohttp
from homeassistant.const import STATE_PROBLEM, STATE_UNKNOWN
_LOGGER: Final = logging.getLogger(__name__)
@dataclass(frozen=True)
class QueryResult:
value: Optional[float] = None
error: Optional[str] = None
class Prometheus:
"""Wrapper for Prometheus API Requests."""
def __init__(self, url: str, session: aiohttp.ClientSession) -> None:
"""Initialize the Prometheus API wrapper."""
self._session = session
self._url = urljoin(f"{url}/", "api/v1/query")
async def query(self, expr: str) -> QueryResult:
"""Query expression response."""
try:
response = await self._session.get(self._url, params={"query": expr})
except aiohttp.ClientError as error:
_LOGGER.error("Error querying %s: %s", self._url, error)
return QueryResult(error=STATE_PROBLEM)
if response.status != 200:
_LOGGER.error(
"Unexpected HTTP status code %s for expression '%s'",
response.status,
expr,
)
return QueryResult(error=STATE_UNKNOWN)
try:
result = (await response.json())["data"]["result"]
except (ValueError, KeyError) as error:
_LOGGER.error("Invalid query response: %s", error)
return QueryResult(error=STATE_UNKNOWN)
if not result:
_LOGGER.error("Expression '%s' yielded no result", expr)
return QueryResult(error=STATE_PROBLEM)
elif len(result) > 1:
_LOGGER.error("Expression '%s' yielded multiple metrics", expr)
return QueryResult(error=STATE_PROBLEM)
value = float(result[0]["value"][1])
_LOGGER.debug("Expression '%s' yields result %f", expr, value)
return QueryResult(value)

View File

@@ -0,0 +1,125 @@
"""Prometheus Binary Sensor component."""
from __future__ import annotations
from typing import TYPE_CHECKING, Final
import voluptuous as vol
from homeassistant.components.binary_sensor import (
PLATFORM_SCHEMA as BINARY_SENSOR_PLATFORM_SCHEMA,
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.const import (
CONF_DEVICE_CLASS,
CONF_NAME,
CONF_UNIQUE_ID,
CONF_URL,
CONF_VALUE_TEMPLATE,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from . import Prometheus
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.template import Template
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import QueryResult
from .const import CONF_EXPR, CONF_QUERIES, DEFAULT_URL, SCAN_INTERVAL as SCAN_INTERVAL
_QUERY_SCHEMA: Final = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Required(CONF_EXPR): cv.string,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_DEVICE_CLASS): vol.Coerce(BinarySensorDeviceClass),
}
)
PLATFORM_SCHEMA: Final = BINARY_SENSOR_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_URL, default=DEFAULT_URL): cv.string,
vol.Required(CONF_QUERIES): [_QUERY_SCHEMA],
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
):
"""Set up the sensor platform."""
session = async_get_clientsession(hass)
url = config[CONF_URL]
prometheus = Prometheus(url, session)
async_add_entities(
new_entities=[
PrometheusBinarySensor(
prometheus=prometheus,
unique_id=query.get(CONF_UNIQUE_ID),
device_name=query[CONF_NAME],
expression=query[CONF_EXPR],
value_template=query.get(CONF_VALUE_TEMPLATE),
device_class=query.get(CONF_DEVICE_CLASS),
)
for query in config[CONF_QUERIES]
],
update_before_add=True,
)
class PrometheusBinarySensor(BinarySensorEntity):
"""Sensor entity representing the result of a PromQL expression."""
def __init__(
self,
*,
prometheus: Prometheus,
unique_id: str | None,
device_name: str,
expression: str,
value_template: Template,
device_class: BinarySensorDeviceClass | None,
) -> None:
"""Initialize the sensor."""
self._prometheus: Prometheus = prometheus
self._expression = expression
self._value_template = value_template
self._attr_device_class = device_class
self._attr_name = device_name
self._attr_unique_id = unique_id
async def async_update(self) -> None:
"""Update state by executing query."""
result: QueryResult = await self._prometheus.query(self._expression)
self._attr_available = result.error is None
# Nuke value if sensor becomes unavailable
if not self._attr_available:
self._attr_is_on = None
# Naive bool cast without template
elif self._value_template is None:
self._attr_is_on = bool(result.value)
# Evaluate template
else:
render_result = self._value_template.async_render(
variables=dict(value=result.value)
)
if render_result is not None:
self._attr_is_on = bool(render_result)
else:
self._attr_is_on = None

View File

@@ -0,0 +1,10 @@
from datetime import timedelta
from typing import Final
# Match the default scrape_interval in Prometheus
SCAN_INTERVAL: Final = timedelta(seconds=15)
DEFAULT_URL: Final = "http://localhost:9090"
CONF_QUERIES: Final = "queries"
CONF_EXPR: Final = "expr"

View File

@@ -0,0 +1,9 @@
{
"domain": "prometheus_sensor",
"name": "Prometheus Sensor",
"codeowners": ["@mweinelt"],
"documentation": "https://github.com/mweinelt/ha-prometheus-sensor",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/mweinelt/ha-prometheus-sensor/issues",
"version": "1.2.1"
}

View File

@@ -0,0 +1,112 @@
"""Prometheus Sensor component."""
from __future__ import annotations
from typing import TYPE_CHECKING, Final
import voluptuous as vol
from homeassistant.components.sensor import (
CONF_STATE_CLASS,
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.const import (
CONF_DEVICE_CLASS,
CONF_NAME,
CONF_UNIQUE_ID,
CONF_UNIT_OF_MEASUREMENT,
CONF_URL,
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from . import Prometheus
if TYPE_CHECKING:
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from . import QueryResult
from .const import CONF_EXPR, CONF_QUERIES, DEFAULT_URL, SCAN_INTERVAL as SCAN_INTERVAL
_QUERY_SCHEMA: Final = vol.Schema(
{
vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_UNIQUE_ID): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
vol.Required(CONF_EXPR): cv.string,
vol.Optional(CONF_DEVICE_CLASS): vol.Coerce(SensorDeviceClass),
vol.Optional(CONF_STATE_CLASS): vol.Coerce(SensorStateClass),
}
)
PLATFORM_SCHEMA: Final = SENSOR_PLATFORM_SCHEMA.extend(
{
vol.Optional(CONF_URL, default=DEFAULT_URL): cv.string,
vol.Required(CONF_QUERIES): [_QUERY_SCHEMA],
}
)
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
):
"""Set up the sensor platform."""
session = async_get_clientsession(hass)
url = config[CONF_URL]
prometheus = Prometheus(url, session)
async_add_entities(
new_entities=[
PrometheusSensor(
prometheus=prometheus,
expression=query[CONF_EXPR],
unique_id=query.get(CONF_UNIQUE_ID),
device_name=query[CONF_NAME],
device_class=query.get(CONF_DEVICE_CLASS),
state_class=query.get(CONF_STATE_CLASS),
unit_of_measurement=query.get(CONF_UNIT_OF_MEASUREMENT),
)
for query in config[CONF_QUERIES]
],
update_before_add=True,
)
class PrometheusSensor(SensorEntity):
"""Sensor entity representing the result of a PromQL expression."""
def __init__(
self,
*,
prometheus: Prometheus,
expression: str,
unique_id: str | None,
device_name: str,
device_class: SensorDeviceClass | None,
state_class: SensorStateClass | None,
unit_of_measurement: str | None,
) -> None:
"""Initialize the sensor."""
self._prometheus: Prometheus = prometheus
self._expression = expression
self._attr_device_class = device_class
self._attr_name = device_name
self._attr_native_unit_of_measurement = unit_of_measurement
self._attr_state_class = state_class
self._attr_unique_id = unique_id
async def async_update(self) -> None:
"""Update state by executing query."""
result: QueryResult = await self._prometheus.query(self._expression)
self._attr_available = result.error is None
self._attr_native_value = result.value