Skip to content

Commit 2283bb8

Browse files
n8n: add container-based config discovery (DataDog#23964)
* n8n: add container-based config discovery support * n8n: pass explicit config to dd_agent_check in regular e2e test Without an explicit config, dd_agent_check relies on conf.d which now includes the mounted auto_conf.yaml (from get_e2e_discovery_metadata). This causes autodiscovery to fire an extra check instance alongside the two static ones, corrupting metric assertions in test_check_n8n_e2e. Passing the instances explicitly forces --config-file, which overrides conf.d and isolates the test from autodiscovery — matching the krakend reference pattern. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Revert "n8n: pass explicit config to dd_agent_check in regular e2e test" This reverts commit 7f26c0f. * n8n: add discovery stub files * n8n: remove ad_identifiers from discovery spec stanza * n8n: update discovery.py to new generated format * n8n: add test_e2e_discovery_all_candidates Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * n8n: fix import sort order in test_e2e.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: restore blank line between import groups in test_e2e.py * Add auto_conf.yaml section to spec.yaml so it is generated from spec. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Regenerate auto_conf.yaml with doc comments from spec template. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Use discovery/openmetrics_from_ports template in n8n spec, consistent with krakend Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Assert both main and worker instances are discovered in test_e2e_discovery. Both containers share the same image and are both discovered: main on port 5678 (via the hint), worker on port 5680 (via the port fallback). Together they cover the full metric set so symmetric inclusion can be enabled. Uses discovery_min_instances=2 as suggested. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Skip test_e2e_discovery in lab mode as auto_conf.yaml is not mounted. Follows the same approach as krakend. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Extract common assertions into helper to align test_e2e_discovery with test_check_n8n_e2e. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Restore worker readiness comment and explain missing n8n_process tags in discovery test. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 0a4bb24 commit 2283bb8

9 files changed

Lines changed: 135 additions & 11 deletions

File tree

n8n/assets/configuration/spec.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
name: n8n
22
files:
33
- name: n8n.yaml
4+
discovery:
5+
strategies:
6+
- template: discovery/openmetrics_from_ports
7+
overrides:
8+
port_hints:
9+
- 5678
410
options:
511
- template: init_config
612
options:
@@ -34,3 +40,11 @@ files:
3440
- type: docker
3541
source: n8n
3642
service: <SERVICE>
43+
44+
- name: auto_conf.yaml
45+
options:
46+
- template: ad_identifiers
47+
overrides:
48+
value.example:
49+
- n8n
50+
- template: auto_conf/discovery

n8n/changelog.d/23964.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
16+
from datadog_checks.n8n.config_models import discovery_overrides
17+
from datadog_checks.n8n.config_models.instance import InstanceConfig
18+
from datadog_checks.n8n.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_ports
26+
for port in candidate_ports(service, [5678]):
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: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
- n8n
8+
9+
## Enables configuration discovery
10+
#
11+
discovery: {}
12+
13+
## Unused init configuration
14+
#
15+
init_config:
16+
17+
## Unused instance configuration
18+
#
19+
instances: []

n8n/tests/common.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,6 @@
169169
}
170170
INSTANCE = MAIN_INSTANCE # back-compat default for unit tests
171171

172-
E2E_METADATA = {'docker_volumes': ['/var/run/docker.sock:/var/run/docker.sock:ro']}
173-
174172

175173
def get_compose_env_vars() -> dict[str, str]:
176174
"""Variables consumed by ``tests/docker/docker-compose.yaml``'s ``${...}`` placeholders.

n8n/tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import pytest
1313
import requests
1414

15-
from datadog_checks.dev import docker_run
15+
from datadog_checks.dev import docker_run, get_e2e_discovery_metadata
1616
from datadog_checks.dev.conditions import CheckEndpoints, WaitFor
1717

1818
from . import common
@@ -195,7 +195,7 @@ def dd_environment() -> Iterator[Any]:
195195
},
196196
)
197197
else:
198-
yield instances, common.E2E_METADATA
198+
yield instances, get_e2e_discovery_metadata()
199199

200200

201201
@pytest.fixture

n8n/tests/test_e2e.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,23 @@
55

66
import pytest
77

8+
from datadog_checks.dev.docker import assert_all_discovery_candidates_stable
89
from datadog_checks.dev.utils import assert_service_checks
10+
from datadog_checks.n8n import N8nCheck
911

1012
from . import common
1113

1214

15+
def _assert_metrics(aggregator):
16+
aggregator.assert_metrics_using_metadata(
17+
common.get_metadata_metrics_for_version(exclude_rare=True),
18+
check_submission_type=True,
19+
check_symmetric_inclusion=True,
20+
exclude=list(common.RARE_EVENT_METRIC_NAMES),
21+
)
22+
assert_service_checks(aggregator)
23+
24+
1325
@pytest.mark.e2e
1426
def test_check_n8n_e2e(
1527
dd_agent_check: Callable[..., Any],
@@ -19,11 +31,19 @@ def test_check_n8n_e2e(
1931
aggregator.assert_metric('n8n.readiness.check', value=1, tags=['status_code:200', 'n8n_process:main'], at_least=1)
2032
# Worker also exposes /healthz/readiness via QUEUE_HEALTH_CHECK_ACTIVE on its own port.
2133
aggregator.assert_metric('n8n.readiness.check', value=1, tags=['status_code:200', 'n8n_process:worker'], at_least=1)
34+
_assert_metrics(aggregator)
2235

23-
aggregator.assert_metrics_using_metadata(
24-
common.get_metadata_metrics_for_version(exclude_rare=True),
25-
check_submission_type=True,
26-
check_symmetric_inclusion=True,
27-
exclude=list(common.RARE_EVENT_METRIC_NAMES),
28-
)
29-
assert_service_checks(aggregator)
36+
37+
@pytest.mark.e2e
38+
def test_e2e_discovery(dd_agent_check_discovery):
39+
if common.IS_LAB:
40+
pytest.skip('lab does not currently support configuration discovery')
41+
42+
aggregator = dd_agent_check_discovery(check_rate=True, discovery_min_instances=2)
43+
# n8n_process:main/worker tags come from instance config; the autodiscovery template only sets openmetrics_endpoint.
44+
_assert_metrics(aggregator)
45+
46+
47+
@pytest.mark.e2e
48+
def test_e2e_discovery_all_candidates(dd_agent_check):
49+
assert_all_discovery_candidates_stable(dd_agent_check, N8nCheck)

0 commit comments

Comments
 (0)