Skip to content

Commit e761168

Browse files
vitkyrkaclaude
andcommitted
Add container-based config discovery support to fluxcd
Adds a from_ports discovery strategy on port 8080 (the shared Prometheus metrics port across all Flux controllers) and an auto_conf.yaml matching the four flux-system controller images (helm-controller, kustomize-controller, notification-controller, source-controller) already exercised by the integration's kind-based E2E test. Wires the new kind-based E2E discovery helpers (setup_discovery_agent/save_kube_discovery_state/ run_discovery_check_kubernetes/assert_all_discovery_candidates_stable_kubernetes) into conftest.py and test_e2e.py. Environment: Datadog workspace Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 9f91003 commit e761168

8 files changed

Lines changed: 178 additions & 18 deletions

File tree

fluxcd/assets/configuration/spec.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ name: fluxcd
22
fleet_configurable: true
33
files:
44
- name: fluxcd.yaml
5+
discovery:
6+
strategies:
7+
- template: discovery/openmetrics_from_named_ports
8+
overrides:
9+
port_names:
10+
- http-prom
511
options:
612
- template: init_config
713
options:
@@ -15,3 +21,20 @@ files:
1521
for Flux custom resources (Flux 2.1+).
1622
value:
1723
type: string
24+
- name: auto_conf.yaml
25+
options:
26+
- template: ad_identifiers
27+
overrides:
28+
value.example:
29+
- helm-controller
30+
- kustomize-controller
31+
- notification-controller
32+
- source-controller
33+
- template: auto_conf/cel_selector
34+
overrides:
35+
cel_selector.value.example:
36+
containers:
37+
# Narrow down matching containers since the controller names are generic
38+
# and don't identify the container as being part of FluxCD.
39+
- container.image.reference.contains("fluxcd/")
40+
- template: auto_conf/discovery

fluxcd/changelog.d/24510.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add container-based config discovery support.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# (C) Datadog, Inc. 2026-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
5+
# This file is autogenerated.
6+
# To change this file you should edit assets/configuration/spec.yaml and then run the following commands:
7+
# ddev -x validate config -s <INTEGRATION_NAME>
8+
# ddev -x validate models -s <INTEGRATION_NAME>
9+
10+
from __future__ import annotations
11+
12+
from collections.abc import Iterator
13+
from typing import Any
14+
15+
from datadog_checks.base.utils.discovery import Service, candidate_ports_by_name
16+
from datadog_checks.fluxcd.config_models import discovery_overrides
17+
from datadog_checks.fluxcd.config_models.instance import InstanceConfig
18+
from datadog_checks.fluxcd.config_models.shared import SharedConfig
19+
20+
21+
def _generated_candidates(service: Service) -> Iterator[dict[str, Any]]:
22+
shared = SharedConfig.model_validate({}, context={'configured_fields': frozenset()}).model_dump(
23+
by_alias=True, mode='json', exclude_none=True
24+
)
25+
# discovery[0]: from_named_ports
26+
for port in candidate_ports_by_name(service, ['http-prom']):
27+
ctx = {'port': port}
28+
instance_data = {
29+
'openmetrics_endpoint': 'http://{service.host}:{port.number}/metrics'.format(service=service, **ctx),
30+
}
31+
instance = InstanceConfig.model_validate(
32+
instance_data, context={'configured_fields': frozenset(instance_data)}
33+
).model_dump(by_alias=True, mode='json', exclude_none=True)
34+
yield {'init_config': shared, 'instances': [instance]}
35+
36+
37+
def candidates(service: Service) -> Iterator[dict[str, Any]]:
38+
override = getattr(discovery_overrides, 'candidates', None)
39+
if override is None:
40+
yield from _generated_candidates(service)
41+
else:
42+
yield from override(service, default=_generated_candidates)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# (C) Datadog, Inc. 2026-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
5+
# Override the generated discovery candidates() for this integration.
6+
#
7+
# Define a candidates(service, default) function to wrap or replace the generated
8+
# candidate generation. `default` is the generated generator; call it to reuse
9+
# the spec-driven candidates, or ignore it to replace them entirely.
10+
#
11+
# def candidates(service, default):
12+
# yield from default(service)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# (C) Datadog, Inc. 2026-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
5+
# Here you can define custom (local:) discovery strategies for this integration.
6+
#
7+
# Decorate a generator with @discovery_strategy (imported from
8+
# datadog_checks.base.utils.discovery) and reference it from the spec discovery
9+
# stanza as `strategy: local:<function_name>`. The function receives the
10+
# discovered Service plus the inputs declared in the spec and yields one context
11+
# (ctx) mapping per candidate, exposing the keys listed in `provides`.
12+
#
13+
# from datadog_checks.base.utils.discovery import discovery_strategy
14+
#
15+
# @discovery_strategy(provides=('svc',))
16+
# def from_some_config(service, config_path):
17+
# ...
18+
# yield {'svc': ...}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
## @param ad_identifiers - list of strings - required
2+
## A list of container identifiers that are used by Autodiscovery to identify
3+
## which container the check should be run against. For more information, see:
4+
## https://docs.datadoghq.com/agent/guide/ad_identifiers/
5+
#
6+
ad_identifiers:
7+
- helm-controller
8+
- kustomize-controller
9+
- notification-controller
10+
- source-controller
11+
12+
## @param cel_selector - mapping - optional
13+
## CEL selector for autodiscovery.
14+
#
15+
cel_selector:
16+
containers:
17+
- container.image.reference.contains("fluxcd/")
18+
19+
## Enables configuration discovery
20+
#
21+
discovery: {}
22+
23+
## Unused init configuration
24+
#
25+
init_config:
26+
27+
## Unused instance configuration
28+
#
29+
instances: []

fluxcd/tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from datadog_checks.dev import get_here
1111
from datadog_checks.dev.kind import kind_run
12+
from datadog_checks.dev.kube_discovery import setup_discovery_agent
1213
from datadog_checks.dev.kube_port_forward import port_forward
1314
from datadog_checks.dev.subprocess import run_command
1415
from datadog_checks.fluxcd import FluxcdCheck
@@ -38,6 +39,7 @@ def dd_environment():
3839
with kind_run(conditions=[setup_fluxcd]) as kubeconfig:
3940
instances = []
4041
with ExitStack() as stack:
42+
setup_discovery_agent(kubeconfig)
4143
for controller in (
4244
'source-controller',
4345
'helm-controller',

fluxcd/tests/test_e2e.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,37 @@
22
# All rights reserved
33
# Licensed under a 3-clause BSD style license (see LICENSE)
44

5+
import pytest
6+
7+
from datadog_checks.dev.kube_discovery import (
8+
assert_all_discovery_candidates_stable_kubernetes,
9+
run_discovery_check_kubernetes,
10+
)
511
from datadog_checks.dev.utils import get_metadata_metrics
12+
from datadog_checks.fluxcd import FluxcdCheck
613

714
from .common import EXPECTED_METRICS
815

16+
# These require an actual reconcile/workqueue event to fire, which doesn't happen against an
17+
# idle test cluster with no GitRepository/Kustomization/HelmRelease objects configured.
18+
IGNORED_METRICS = {
19+
'fluxcd.controller.runtime.reconcile.count',
20+
'fluxcd.controller.runtime.reconcile.errors.count',
21+
'fluxcd.controller.runtime.reconcile.time.seconds.bucket',
22+
'fluxcd.controller.runtime.reconcile.time.seconds.count',
23+
'fluxcd.controller.runtime.reconcile.time.seconds.sum',
24+
'fluxcd.gotk.reconcile.condition',
25+
'fluxcd.gotk.reconcile.duration.seconds.bucket',
26+
'fluxcd.gotk.reconcile.duration.seconds.count',
27+
'fluxcd.gotk.reconcile.duration.seconds.sum',
28+
# Emitted by kube-state-metrics (Flux 2.1+), not Flux controller /metrics endpoints.
29+
'fluxcd.gotk.resource.info',
30+
'fluxcd.gotk.suspend.status',
31+
'fluxcd.process.cpu_seconds.count',
32+
'fluxcd.workqueue.adds.count',
33+
'fluxcd.workqueue.retries.count',
34+
}
35+
936

1037
def test_source_controller_metrics(dd_agent_check):
1138
"""
@@ -14,24 +41,30 @@ def test_source_controller_metrics(dd_agent_check):
1441
Version 1 is in maintenance mode, all our users are on version 2.
1542
"""
1643
aggregator = dd_agent_check()
17-
ignore = {
18-
'fluxcd.controller.runtime.reconcile.count',
19-
'fluxcd.controller.runtime.reconcile.errors.count',
20-
'fluxcd.controller.runtime.reconcile.time.seconds.bucket',
21-
'fluxcd.controller.runtime.reconcile.time.seconds.count',
22-
'fluxcd.controller.runtime.reconcile.time.seconds.sum',
23-
'fluxcd.gotk.reconcile.condition',
24-
'fluxcd.gotk.reconcile.duration.seconds.bucket',
25-
'fluxcd.gotk.reconcile.duration.seconds.count',
26-
'fluxcd.gotk.reconcile.duration.seconds.sum',
27-
# Emitted by kube-state-metrics (Flux 2.1+), not Flux controller /metrics endpoints.
28-
'fluxcd.gotk.resource.info',
29-
'fluxcd.gotk.suspend.status',
30-
'fluxcd.process.cpu_seconds.count',
31-
'fluxcd.workqueue.adds.count',
32-
'fluxcd.workqueue.retries.count',
33-
}
34-
for metric_name in set(EXPECTED_METRICS['v2']) - ignore:
44+
for metric_name in set(EXPECTED_METRICS['v2']) - IGNORED_METRICS:
45+
aggregator.assert_metric(metric_name)
46+
aggregator.assert_all_metrics_covered()
47+
aggregator.assert_metrics_using_metadata(get_metadata_metrics())
48+
49+
50+
@pytest.mark.e2e
51+
def test_e2e_discovery(aggregator, datadog_agent):
52+
# Kubelet Autodiscovery is expected to find all four flux-system controller pods exercised by
53+
# the non-discovery E2E test above (source, helm, kustomize, notification-controller).
54+
run_discovery_check_kubernetes(aggregator, datadog_agent, discovery_min_instances=4)
55+
56+
for metric_name in set(EXPECTED_METRICS['v2']) - IGNORED_METRICS:
3557
aggregator.assert_metric(metric_name)
3658
aggregator.assert_all_metrics_covered()
3759
aggregator.assert_metrics_using_metadata(get_metadata_metrics())
60+
61+
62+
@pytest.mark.e2e
63+
def test_e2e_discovery_all_candidates(aggregator, datadog_agent):
64+
assert_all_discovery_candidates_stable_kubernetes(
65+
FluxcdCheck,
66+
aggregator,
67+
datadog_agent,
68+
namespace='flux-system',
69+
pod_selector='app=source-controller',
70+
)

0 commit comments

Comments
 (0)