diff --git a/keda/assets/configuration/spec.yaml b/keda/assets/configuration/spec.yaml index 8f950e6c38232..a95d6d68735db 100644 --- a/keda/assets/configuration/spec.yaml +++ b/keda/assets/configuration/spec.yaml @@ -2,6 +2,9 @@ name: Keda fleet_configurable: true files: - name: keda.yaml + discovery: + strategies: + - template: discovery/openmetrics_from_named_ports options: - template: init_config options: @@ -13,4 +16,13 @@ files: openmetrics_endpoint.value.example: http://localhost:8080/metrics openmetrics_endpoint.description: | Endpoint exposing the Keda's Prometheus metrics. For more information refer to: - https://keda.sh/docs/2.16/integrations/prometheus/ \ No newline at end of file + https://keda.sh/docs/2.16/integrations/prometheus/ +- name: auto_conf.yaml + options: + - template: ad_identifiers + overrides: + value.example: + - keda + - keda-admission-webhooks + - keda-metrics-apiserver + - template: auto_conf/discovery diff --git a/keda/changelog.d/24459.added b/keda/changelog.d/24459.added new file mode 100644 index 0000000000000..1455667b0b73f --- /dev/null +++ b/keda/changelog.d/24459.added @@ -0,0 +1 @@ +Add container-based config discovery support. diff --git a/keda/datadog_checks/keda/config_models/__init__.py b/keda/datadog_checks/keda/config_models/__init__.py index 106fff2032f68..f678b7e73d91a 100644 --- a/keda/datadog_checks/keda/config_models/__init__.py +++ b/keda/datadog_checks/keda/config_models/__init__.py @@ -1,4 +1,4 @@ -# (C) Datadog, Inc. 2024-present +# (C) Datadog, Inc. 2026-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) diff --git a/keda/datadog_checks/keda/config_models/defaults.py b/keda/datadog_checks/keda/config_models/defaults.py index 71591bead832f..b4fef9c9d9a57 100644 --- a/keda/datadog_checks/keda/config_models/defaults.py +++ b/keda/datadog_checks/keda/config_models/defaults.py @@ -1,4 +1,4 @@ -# (C) Datadog, Inc. 2024-present +# (C) Datadog, Inc. 2026-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) diff --git a/keda/datadog_checks/keda/config_models/discovery.py b/keda/datadog_checks/keda/config_models/discovery.py new file mode 100644 index 0000000000000..2374c55b15303 --- /dev/null +++ b/keda/datadog_checks/keda/config_models/discovery.py @@ -0,0 +1,42 @@ +# (C) Datadog, Inc. 2026-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# This file is autogenerated. +# To change this file you should edit assets/configuration/spec.yaml and then run the following commands: +# ddev -x validate config -s +# ddev -x validate models -s + +from __future__ import annotations + +from collections.abc import Iterator +from typing import Any + +from datadog_checks.base.utils.discovery import Service, candidate_ports_by_name +from datadog_checks.keda.config_models import discovery_overrides +from datadog_checks.keda.config_models.instance import InstanceConfig +from datadog_checks.keda.config_models.shared import SharedConfig + + +def _generated_candidates(service: Service) -> Iterator[dict[str, Any]]: + shared = SharedConfig.model_validate({}, context={'configured_fields': frozenset()}).model_dump( + by_alias=True, mode='json', exclude_none=True + ) + # discovery[0]: from_named_ports + for port in candidate_ports_by_name(service, ['metrics']): + ctx = {'port': port} + instance_data = { + 'openmetrics_endpoint': 'http://{service.host}:{port.number}/metrics'.format(service=service, **ctx), + } + instance = InstanceConfig.model_validate( + instance_data, context={'configured_fields': frozenset(instance_data)} + ).model_dump(by_alias=True, mode='json', exclude_none=True) + yield {'init_config': shared, 'instances': [instance]} + + +def candidates(service: Service) -> Iterator[dict[str, Any]]: + override = getattr(discovery_overrides, 'candidates', None) + if override is None: + yield from _generated_candidates(service) + else: + yield from override(service, default=_generated_candidates) diff --git a/keda/datadog_checks/keda/config_models/discovery_overrides.py b/keda/datadog_checks/keda/config_models/discovery_overrides.py new file mode 100644 index 0000000000000..66af68809dd4c --- /dev/null +++ b/keda/datadog_checks/keda/config_models/discovery_overrides.py @@ -0,0 +1,12 @@ +# (C) Datadog, Inc. 2026-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# Override the generated discovery candidates() for this integration. +# +# Define a candidates(service, default) function to wrap or replace the generated +# candidate generation. `default` is the generated generator; call it to reuse +# the spec-driven candidates, or ignore it to replace them entirely. +# +# def candidates(service, default): +# yield from default(service) diff --git a/keda/datadog_checks/keda/config_models/discovery_strategies.py b/keda/datadog_checks/keda/config_models/discovery_strategies.py new file mode 100644 index 0000000000000..5ac036ddb4684 --- /dev/null +++ b/keda/datadog_checks/keda/config_models/discovery_strategies.py @@ -0,0 +1,18 @@ +# (C) Datadog, Inc. 2026-present +# All rights reserved +# Licensed under a 3-clause BSD style license (see LICENSE) + +# Here you can define custom (local:) discovery strategies for this integration. +# +# Decorate a generator with @discovery_strategy (imported from +# datadog_checks.base.utils.discovery) and reference it from the spec discovery +# stanza as `strategy: local:`. The function receives the +# discovered Service plus the inputs declared in the spec and yields one context +# (ctx) mapping per candidate, exposing the keys listed in `provides`. +# +# from datadog_checks.base.utils.discovery import discovery_strategy +# +# @discovery_strategy(provides=('svc',)) +# def from_some_config(service, config_path): +# ... +# yield {'svc': ...} diff --git a/keda/datadog_checks/keda/config_models/instance.py b/keda/datadog_checks/keda/config_models/instance.py index 66d45e34bf01d..055f5f7934057 100644 --- a/keda/datadog_checks/keda/config_models/instance.py +++ b/keda/datadog_checks/keda/config_models/instance.py @@ -1,4 +1,4 @@ -# (C) Datadog, Inc. 2024-present +# (C) Datadog, Inc. 2026-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) diff --git a/keda/datadog_checks/keda/config_models/shared.py b/keda/datadog_checks/keda/config_models/shared.py index e39d447dfc4b9..10cab800f6c1e 100644 --- a/keda/datadog_checks/keda/config_models/shared.py +++ b/keda/datadog_checks/keda/config_models/shared.py @@ -1,4 +1,4 @@ -# (C) Datadog, Inc. 2024-present +# (C) Datadog, Inc. 2026-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) diff --git a/keda/datadog_checks/keda/data/auto_conf.yaml b/keda/datadog_checks/keda/data/auto_conf.yaml new file mode 100644 index 0000000000000..2faa7b8339ee9 --- /dev/null +++ b/keda/datadog_checks/keda/data/auto_conf.yaml @@ -0,0 +1,21 @@ +## @param ad_identifiers - list of strings - required +## A list of container identifiers that are used by Autodiscovery to identify +## which container the check should be run against. For more information, see: +## https://docs.datadoghq.com/agent/guide/ad_identifiers/ +# +ad_identifiers: + - keda + - keda-admission-webhooks + - keda-metrics-apiserver + +## Enables configuration discovery +# +discovery: {} + +## Unused init configuration +# +init_config: + +## Unused instance configuration +# +instances: [] diff --git a/keda/tests/conftest.py b/keda/tests/conftest.py index 44f8cb07eb0d4..3e118e9029223 100644 --- a/keda/tests/conftest.py +++ b/keda/tests/conftest.py @@ -8,6 +8,7 @@ import pytest from datadog_checks.dev.kind import kind_run +from datadog_checks.dev.kube_discovery import setup_discovery_agent from datadog_checks.dev.kube_port_forward import port_forward from datadog_checks.dev.subprocess import run_command @@ -28,6 +29,7 @@ def setup_ked(): @pytest.fixture(scope='session') def dd_environment(): with kind_run(conditions=[setup_ked], sleep=30) as kubeconfig, ExitStack() as stack: + setup_discovery_agent(kubeconfig) keda_host, keda_port = stack.enter_context( port_forward(kubeconfig, 'keda', 8080, 'deployment', 'keda-operator-metrics-apiserver') ) diff --git a/keda/tests/test_e2e.py b/keda/tests/test_e2e.py index 088d4ae7cda7e..2fb4d8bb1be38 100644 --- a/keda/tests/test_e2e.py +++ b/keda/tests/test_e2e.py @@ -1,8 +1,21 @@ # (C) Datadog, Inc. 2024-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) +import pytest + from datadog_checks.base.constants import ServiceCheck +from datadog_checks.dev.kube_discovery import ( + assert_all_discovery_candidates_stable_kubernetes, + run_discovery_check_kubernetes, +) from datadog_checks.dev.utils import assert_service_checks +from datadog_checks.keda import KedaCheck + +KEDA_POD_SELECTORS = ( + 'app=keda-operator', + 'app=keda-operator-metrics-apiserver', + 'app=keda-admission-webhooks', +) def test_e2e_openmetrics_v2(dd_agent_check): @@ -10,3 +23,23 @@ def test_e2e_openmetrics_v2(dd_agent_check): aggregator.assert_service_check('keda.openmetrics.health', ServiceCheck.OK, count=1) assert_service_checks(aggregator) + + +@pytest.mark.e2e +def test_e2e_discovery(aggregator, datadog_agent): + run_discovery_check_kubernetes(aggregator, datadog_agent, discovery_min_instances=len(KEDA_POD_SELECTORS)) + + aggregator.assert_service_check('keda.openmetrics.health', ServiceCheck.OK, count=len(KEDA_POD_SELECTORS)) + assert_service_checks(aggregator) + + +@pytest.mark.e2e +@pytest.mark.parametrize('pod_selector', KEDA_POD_SELECTORS) +def test_e2e_discovery_all_candidates(aggregator, datadog_agent, pod_selector): + assert_all_discovery_candidates_stable_kubernetes( + KedaCheck, + aggregator, + datadog_agent, + namespace='keda', + pod_selector=pod_selector, + )