master
md 548 lines 20.9 KB
Rendered Raw
1 # Pipeline
2
3 This document maps the integrations pipeline end to end -- every
4 script, every input, every output, every CI workflow. All path
5 citations are repo-relative; line citations refer to the file at
6 HEAD of `master` at the time this skill was last updated.
7
8 ## The integration documentation pipeline
9
10 ```
11 [ YAML sources ]
12 |
13 v
14 +------------------------+
15 | gen_integrations.py | (orchestrator, validator, renderer)
16 +------------------------+
17 |
18 v reads
19 +------------------------+
20 | integrations.js (gitignored)
21 | integrations.json (gitignored)
22 +------------------------+
23
24 [ collector metadata.yaml + taxonomy.yaml + taxonomy registries ]
25 |
26 v
27 +------------------------+
28 | gen_taxonomy.py | (taxonomy validator, resolver)
29 +------------------------+
30 |
31 v
32 +------------------------+
33 | taxonomy.json (gitignored)
34 +------------------------+
35 |
36 v
37 +--------------------------------+
38 | gen_docs_integrations.py | (per-integration .md files)
39 +--------------------------------+
40 |
41 v
42 +--------------------------------+
43 | gen_doc_collector_page.py | (src/collectors/COLLECTORS.md)
44 +--------------------------------+
45 |
46 v
47 +--------------------------------+
48 | gen_doc_secrets_page.py | (src/collectors/SECRETS.md)
49 +--------------------------------+
50 |
51 v
52 +--------------------------------+
53 | gen_doc_service_discovery_page.py | (src/collectors/SERVICE-DISCOVERY.md)
54 +--------------------------------+ NOT in CI today -- see gotchas.md
55 ```
56
57 All four downstream scripts read the **same** `integrations.js`
58 (or its data inside; details below). They run sequentially but
59 do not cross-talk.
60
61 ## Stage 1 -- `gen_integrations.py` (orchestrator)
62
63 Repo path: `integrations/gen_integrations.py`.
64
65 ### Inputs
66
67 - **Categories**: `integrations/categories.yaml` (validated
68 against `integrations/schemas/categories.json` at
69 `gen_integrations.py:344-355`).
70 - **Distros**: `.github/data/distros.yml` (loaded via
71 `load_yaml` at `gen_integrations.py:1330` -- WITHOUT
72 validation; the `distros.json` schema exists but is not
73 consulted -- see `gotchas.md`).
74 - **Per-integration `metadata.yaml`** files matched by
75 `METADATA_PATTERN = '*/metadata.yaml'`
76 (`gen_integrations.py:25`) under nine collector source roots:
77
78 | Root | Integration types served |
79 |---|---|
80 | `src/collectors` | C plugins (apps, cgroups, diskspace, ebpf, freebsd, idlejitter, macos, proc, slabinfo, statsd, systemd-journal, tc, timex, xenstat, log2journal, charts.d, python.d) |
81 | `src/collectors/charts.d.plugin` | shell-based charts.d collectors |
82 | `src/collectors/python.d.plugin` | Python collectors (am2320, etc.) |
83 | `src/collectors/guides` | tutorial-style content |
84 | `src/go/plugin/go.d/collector` | the Go collector tree (the bulk) |
85 | `src/go/plugin/scripts.d/collector` | scripts.d (shell) |
86 | `src/go/plugin/ibm.d/modules` | ibm.d collectors (db2, mq, etc.) |
87 | `src/go/plugin/ibm.d/modules/websphere` | websphere/{jmx,mp,pmi}/ subcollectors -- listed separately because they are 1 level deeper |
88 | `src/crates/netdata-otel` | the OTEL Rust crate's collector metadata |
89
90 - **Exporters**: `src/exporting/*/metadata.yaml`
91 (`gen_integrations.py:43`).
92 - **Agent notifications**:
93 `src/health/notifications/*/metadata.yaml` (`:47`).
94 - **Cloud notifications**:
95 `integrations/cloud-notifications/metadata.yaml` (`:51`).
96 - **Logs**: `integrations/logs/metadata.yaml` (`:55`).
97 - **Authentication**:
98 `integrations/cloud-authentication/metadata.yaml` (`:59`).
99 - **Secretstore**:
100 `src/go/plugin/agent/secrets/secretstore/backends/*/metadata.yaml`
101 (`:63`).
102 - **Service discovery**:
103 `src/go/plugin/go.d/discovery/sdext/discoverer/*/metadata.yaml`
104 (`:67`).
105 - **Deploy**: `integrations/deploy.yaml` (`:39-41`).
106
107 - **Schemas**: `integrations/schemas/*.json` -- loaded on demand
108 via `Registry(retrieve=retrieve_from_filesystem)`
109 (`gen_integrations.py:163-169`). Each integration type has its
110 own `Draft7Validator` instance (`:171-219`).
111
112 - **Templates**: `integrations/templates/**` -- Jinja env at
113 `gen_integrations.py:230-241`. Custom delimiters: `[[ ]]`
114 for variables and `[% %]` for control statements (so that
115 the template can pass through embedded `{% ... %}` and
116 `{{ ... }}` markers untouched). See `gotchas.md`.
117
118 ### Validation behavior
119
120 For each integration type, `gen_integrations.py` runs a
121 `Draft7Validator.validate(...)` call (e.g. `:350`, `:372`,
122 `:399`, `:437`, `:485`, `:533`, `:581`, `:629`, `:677`, `:725`).
123 On any `ValidationError`, the script calls `warn(...)`.
124 **Warnings are fatal**: `fail_on_warnings()` (`:150-160`)
125 returns 1, causing the CI workflow to fail and abort doc
126 regeneration.
127
128 The validator IS strict about declared properties; it is NOT
129 strict about extra properties (no `additionalProperties: false`
130 on collector.json). Unknown keys (`alternative_monitored_instances`,
131 `most_popular`) pass through silently. They appear in
132 `integrations.js` but no template renders them. See `gotchas.md`.
133
134 ### Rendering behavior
135
136 For each integration type, the script:
137
138 1. Loads the YAML(s).
139 2. Validates each entry against the type's JSON Schema.
140 3. Calls `make_id` (collectors only -- `:766`,
141 `f'{plugin}-{module}-{instance}'`).
142 4. Computes `edit_link` from `_src_path` (`:777`).
143 5. Sorts by id/path/index.
144 6. Calls `dedupe_integrations` (`:789`); duplicate ids yield
145 warnings.
146 7. Renders every section listed in `*_RENDER_KEYS` (`:71-122`)
147 through Jinja, storing the result back on the item under
148 that key. Sections come from the type's schema (e.g.
149 `COLLECTOR_RENDER_KEYS = ['alerts', 'metrics', 'functions',
150 'overview', 'related_resources', 'setup',
151 'troubleshooting']` at `:71`).
152 8. Each section is rendered TWICE -- with `clean=False` (rich
153 variant for the JS / cloud-frontend output) and `clean=True`
154 (clean variant for the JSON / GitHub-rendered `.md`
155 output). Both variants are kept in parallel `clean_*`
156 lists.
157 9. Strips internal-only keys (`_src_path`, `_repo`, `_index`)
158 before serialization.
159
160 ### Two-pass templating with `meta.variables`
161
162 When a metadata entry declares
163 `meta.monitored_instance.variables` (collectors) or
164 `meta.variables` (other types), the FIRST pass produces
165 markdown that may still contain `[[ variables.foo ]]` markers.
166 The renderer detects this with a regex and performs a SECOND
167 Jinja pass over the rendered string with `variables=...` in
168 context (`:930-934`). This lets metadata authors inject
169 runtime-style placeholders into rendered text.
170
171 **Divergence**: collectors look up
172 `monitored_instance.variables`; exporters and notifications
173 look up `meta.variables` directly. Same goal, different lookup
174 path -- a known wart.
175
176 ### Outputs
177
178 Two files are written (`gen_integrations.py:1311-1325`):
179
180 - `integrations/integrations.js` -- assembles the
181 `integrations/templates/integrations.js` Jinja shell with
182 `categories=...` and `integrations=...` JSON, then runs
183 `convert_local_links` to rewrite any `](/...)` in the body to
184 absolute GitHub URLs at `https://github.com/netdata/netdata/blob/master/...`.
185 The first 2 lines are a banner:
186 ```
187 // DO NOT EDIT THIS FILE DIRECTLY
188 // It gets generated by integrations/gen_integrations.py in the Netdata repo
189 ```
190 The body is `export const categories = [...]; export const
191 integrations = [...]`.
192 - `integrations/integrations.json` -- pure JSON with the
193 `clean` variant of `{categories, integrations}`. No banner.
194
195 Both are gitignored (`.gitignore:162-163`). They are produced
196 fresh on every run; in CI, the workflow `rm`s them after the
197 downstream scripts read them so they are NOT included in the
198 auto-PR.
199
200 ## Parallel taxonomy stage -- `gen_taxonomy.py`
201
202 Repo path: `integrations/gen_taxonomy.py`.
203
204 ### Inputs
205
206 - Collector `metadata.yaml` files from the same collector source
207 roots used by `gen_integrations.py`.
208 - Sibling collector `taxonomy.yaml` files discovered as
209 `<collector>/taxonomy.yaml`.
210 - `integrations/taxonomy/sections.yaml` -- the stable section
211 registry. Collector files reference only `section_id:`, never
212 `section_path:`.
213 - `integrations/taxonomy/icons.yaml` -- allowed icon ids.
214 - `integrations/schemas/taxonomy_collector.json`,
215 `taxonomy_sections.json`, and `taxonomy_output.json`.
216
217 ### Validation behavior
218
219 The generator validates closed v1 authoring schemas, checks that
220 literal owned contexts and widget references resolve to real contexts
221 in the owning collector's `metadata.yaml`, and requires dynamic
222 selectors to be declared by the owning collector:
223
224 - `context_prefix:` requires
225 `metrics.dynamic_context_prefixes: [{prefix, reason}]`; taxonomy may
226 use a narrower prefix under the declared namespace.
227 - `collect_plugin:` requires
228 `metrics.dynamic_collect_plugins: [{plugin, reason}]`.
229
230 Findings render as plain text locally and as GitHub Actions
231 annotations in CI. Fatal findings fail the run.
232
233 ### Outputs
234
235 `integrations/taxonomy.json` is written by default and validated
236 against `integrations/schemas/taxonomy_output.json`. The file is
237 gitignored and removed by `generate-integrations.yml` cleanup, just
238 like `integrations/integrations.js` and `integrations/integrations.json`.
239
240 Run validation only:
241
242 ```bash
243 python3 integrations/gen_taxonomy.py --check-only
244 ```
245
246 Seed a collector taxonomy from existing metadata contexts:
247
248 ```bash
249 python3 integrations/gen_taxonomy_seed.py src/go/plugin/go.d/collector/apache/metadata.yaml --module-name apache --section-id applications.apache --placement-id apache --icon apache
250 ```
251
252 The seed helper emits a flat `items:` tree. For collectors with richer
253 dashboard layout needs, convert that flat list into explicit
254 `owned_context`, `group`, `flatten`, `selector`, `context`, `grid`,
255 `first_available`, or `view_switch` items before opening the PR.
256
257 Pull-request coverage is checked by:
258
259 ```bash
260 python3 integrations/check_collector_taxonomy.py --pr-diff origin/master...HEAD
261 ```
262
263 ### Commands a maintainer runs locally
264
265 ```bash
266 cd <repo>
267 ./integrations/pip.sh # installs jsonschema referencing jinja2 ruamel.yaml
268 python3 integrations/gen_integrations.py
269 ```
270
271 Run from the repo root. The script depends on relative paths
272 hard-coded in `gen_integrations.py:11-37`.
273
274 ## Stage 2 -- `gen_docs_integrations.py`
275
276 Repo path: `integrations/gen_docs_integrations.py`.
277
278 ### Inputs
279
280 - `integrations/integrations.js` -- the script parses it by
281 string-splitting on `export const categories = ` and
282 `export const integrations = ` (`:129-140`). It does NOT
283 read `integrations.json`.
284
285 ### Outputs
286
287 For each integration entry, the script writes either a
288 `<plugin-dir>/integrations/<slug>.md` file or a
289 `<plugin-dir>/README.md` file (depending on type). The full
290 mapping per integration type is in `per-type-matrix.md`. Slug
291 rules are in `artifacts-and-banners.md`.
292
293 After writing, the script:
294
295 1. Calls `resolve_related_links()` (`:56-78`) to convert
296 `{% relatedResource id="..." %}name{% /relatedResource %}`
297 markers (left in by `templates/overview/collector.md:42`
298 and `templates/related_resources.md:5`) into
299 `[name](/path)` markdown links. **Two-pass resolution**:
300 the markers are present in pass 1; they get rewritten in
301 pass 2 after every file is written so the id-to-path map
302 is complete. If the id is not found, the marker is
303 replaced with bare `name` text (silent fallback).
304
305 2. Calls `make_symlinks(symlink_dict)` (`:527-544`) to symlink
306 `<plugin-dir>/README.md -> integrations/<sole-file>.md`
307 when the directory holds exactly one integration. Only
308 fires when `len(list(integrations_dir.iterdir())) == 1`
309 (`:466`). Multi-integration directories are NOT
310 symlinked.
311
312 3. Cleans the corresponding `**/integrations` directories
313 BEFORE writing (`:19-41`), so removed integrations vanish
314 from the tree.
315
316 ### Scoped regen
317
318 The script accepts `-c plugin/module` to scope cleanup and
319 regen to one collector (`:578-583`). Useful locally:
320
321 ```bash
322 python3 integrations/gen_docs_integrations.py -c go.d.plugin/snmp
323 ```
324
325 NOT used by CI; CI always runs without `-c` (full regen).
326
327 ## Stage 3 -- `gen_doc_collector_page.py`
328
329 Repo path: `integrations/gen_doc_collector_page.py`.
330
331 Reads `integrations/integrations.js` (`:38-47`). Walks the
332 category tree; the "section-level" categories are normally
333 children of `data-collection`, plus the top-level `flows`
334 category (`:82-86`). `flows` is deliberately included because
335 Network Flows entries cover both flow protocols and enrichment
336 inputs, so the Monitor Anything page must list them together
337 under a `Network Flows` section instead of dropping them into
338 `Other`.
339
340 Writes `src/collectors/COLLECTORS.md` (committed). This is the
341 "Monitor anything with Netdata" umbrella marketing page that
342 lists every collector and Network Flows integration in tabular
343 form, grouped by section. The write path is
344 `generate_collectors_md()` (`:565-584`), which renders the
345 header plus dynamic tables and atomically replaces the file.
346
347 ### Notable behaviors
348
349 - Sort order: "Linux first, Other last" (`:285-301`); Network
350 Flows follows its position in `integrations/categories.yaml`
351 because it is treated as a section.
352 - Description extraction: `extract_description_from_overview`
353 reads `## Overview` body, uses the first sentence (`:143-183`);
354 falls back to `meta.monitored_instance.description`; final
355 fallback `Monitor <name>`. Because this text becomes the
356 Monitor Anything table description, the first sentence of the
357 overview must describe the integration itself, not a setting,
358 variable, default, limit, or troubleshooting detail. See
359 `description-authoring.md`.
360 - Slug for table links: `to_slug(display_name)` -- lowercase,
361 spaces to `_`, `/` to `-`, strips parentheses (`:213-215`).
362 - Hardcoded marketing anchors: `_render_tech_navigation`
363 (`:424-493`) writes `#cloud-provider-managed`, `#kubernetes`,
364 `#search-engines`, `#freebsd`, `#message-brokers`, etc.
365 Several of these category IDs do NOT exist in
366 `categories.yaml` -- some links go to non-existent anchors.
367 See `gotchas.md`. Header literal "850+ integrations" is also
368 baked in.
369
370 ## Stage 4 -- `gen_doc_secrets_page.py`
371
372 Repo path: `integrations/gen_doc_secrets_page.py`.
373
374 Reads `integrations/integrations.js` (`:213-217`), filters
375 entries where `integration_type == 'secretstore'`, builds the
376 "Supported Secretstore Backends" table from each backend's
377 `meta.kind`, `meta.name`, `collector_configs.summary.{operand_format,
378 example_operand}`, and renders via
379 `integrations/templates/secrets.md`. Writes
380 `src/collectors/SECRETS.md` (committed, `:358`).
381
382 The bulk of `SECRETS.md` is **static content baked into the
383 script** (`SECRETS_PAGE` dict, `:20-203`). Only the backends
384 table is dynamic. To change the static prose, edit the script.
385
386 ## Stage 5 -- `gen_doc_service_discovery_page.py`
387
388 Repo path: `integrations/gen_doc_service_discovery_page.py`.
389
390 Mirror of the secrets stage for service discovery. Reads
391 `integrations.js`, filters
392 `integration_type == 'service_discovery'`, renders via
393 `integrations/templates/service_discovery.md`. Writes
394 `src/collectors/SERVICE-DISCOVERY.md` (committed, `:382`).
395 Most content is static (`SD_PAGE` dict, `:21-257`).
396
397 **KNOWN GAP**: this stage is NOT wired into the
398 `generate-integrations.yml` workflow. CI does not run it. The
399 file in tree drifts from metadata.yaml until a developer runs
400 the script manually (or a future PR adds it to CI). See
401 `gotchas.md` and the SOW followups.
402
403 ## CI workflow 1 -- `generate-integrations.yml`
404
405 Repo path: `.github/workflows/generate-integrations.yml`.
406
407 ### Triggers
408
409 - `push` to `master` filtered by paths
410 (`generate-integrations.yml:6-25`):
411 - `**/metadata.yaml` (every collector / exporter / notification
412 metadata)
413 - `integrations/templates/**`
414 - `integrations/schemas/**`
415 - `integrations/categories.yaml`, `integrations/deploy.yaml`
416 - `integrations/cloud-notifications/metadata.yaml`,
417 `integrations/cloud-authentication/metadata.yaml`
418 - the four older Python scripts (NOT
419 `gen_doc_service_discovery_page.py` -- the gap)
420 - `workflow_dispatch` -- manual.
421
422 ### Concurrency
423
424 - `integrations-${{ github.ref }}`, `cancel-in-progress: true`.
425
426 ### Repo gate
427
428 - `if: github.repository == 'netdata/netdata'` -- forks do NOT
429 trigger this workflow.
430
431 ### Steps
432
433 1. `actions/checkout@v6` (depth 1, recursive submodules).
434 2. `apt install python3-venv` + `./integrations/pip.sh` to
435 install Python deps.
436 3. `python3 integrations/gen_integrations.py`.
437 4. `python3 integrations/gen_docs_integrations.py`.
438 5. `python3 integrations/gen_doc_collector_page.py`.
439 6. `python3 integrations/gen_doc_secrets_page.py`.
440 7. **NOT** `gen_doc_service_discovery_page.py` -- gap.
441 8. `rm -rf go.d.plugin virtualenv integrations/integrations.js
442 integrations/integrations.json` -- prevents the auto-PR from
443 committing the runtime artifacts.
444 9. `peter-evans/create-pull-request@v8` -- branch
445 `integrations-regen`, label `integrations-update`, title
446 `Regenerate integrations docs`, token
447 `NETDATABOT_GITHUB_TOKEN`. Reviewed and merged manually.
448 10. Slack failure notification on master failures.
449
450 ## CI workflow 2 -- `check-markdown.yml`
451
452 Repo path: `.github/workflows/check-markdown.yml`.
453
454 ### Triggers
455
456 - `pull_request` filtered by paths:
457 - `**/*.md`, `**/*.mdx`
458 - `docs/**`, `**/metadata.yaml`, `integrations/**`
459
460 ### Steps
461
462 1. Checkout PR branch and the `netdata/learn` repo.
463 2. Install Python deps (`./integrations/pip.sh`).
464 3. Run `gen_integrations.py`, `gen_docs_integrations.py`,
465 `gen_doc_collector_page.py`, `gen_doc_secrets_page.py`
466 (same gap on SD page generator).
467 4. Run `learn/ingest/ingest.py --local-repo netdata:...
468 --ignore-on-prem-repo --fail-links-netdata`
469 (`check-markdown.yml:64-69`) -- validates that all
470 generated markdown links resolve through Learn's ingest
471 pipeline.
472
473 This workflow validates but does NOT auto-commit. It acts as
474 a gate on PRs. A failure here means a PR cannot merge until
475 the metadata or links are fixed.
476
477 ## CMake target -- `render-docs`
478
479 Repo path: `packaging/cmake/Modules/NetdataRenderDocs.cmake`.
480
481 A developer-facing convenience target. When wired up by the
482 build system, it runs the same generator chain (with
483 `gen_integrations` + `gen_docs_integrations` only by default).
484 Useful for local validation. NOT a substitute for running the
485 scripts directly during active development.
486
487 ## End-to-end: a single PR's flow
488
489 1. Developer edits `src/go/plugin/go.d/collector/foo/metadata.yaml`
490 (and any other affected consistency-rule files:
491 `taxonomy.yaml`, `config_schema.json`, stock conf,
492 `health.d/foo.conf`, `README.md`).
493 2. Developer runs locally:
494 ```bash
495 ./integrations/pip.sh
496 python3 integrations/gen_integrations.py
497 python3 integrations/gen_taxonomy.py --check-only
498 python3 integrations/check_collector_taxonomy.py --pr-diff master...HEAD
499 python3 -m unittest integrations.tests.test_taxonomy
500 python3 integrations/gen_docs_integrations.py -c go.d.plugin/foo
501 python3 integrations/gen_doc_collector_page.py
502 python3 integrations/gen_doc_secrets_page.py
503 ```
504 3. Developer commits the regenerated `integrations/foo.md`,
505 the symlinked `README.md` (if applicable), and the updated
506 `src/collectors/COLLECTORS.md` if the collector list
507 changed.
508 4. PR is opened. `check-markdown.yml` runs, regenerates the
509 same files in CI, and validates Learn ingest. If the dev's
510 committed files differ from CI's regen, the PR fails.
511 5. Reviewer checks collector consistency, including taxonomy
512 coverage for changed chart contexts.
513 6. PR merges. `generate-integrations.yml` triggers on master,
514 regenerates everything, and opens an `integrations-regen`
515 PR if anything is now stale (typically nothing, because the
516 dev already committed the regen). Maintainer merges.
517 7. Cloud-frontend's own CI (in
518 `${NETDATA_REPOS_DIR}/dashboard/cloud-frontend/`) runs
519 `gen_integrations.py` against the new master and copies
520 `integrations.js` into its source. See `in-app-contract.md`.
521 8. Learn's ingest pulls the new `integrations/foo.md` on its
522 3-hourly schedule. See the `learn-site-structure` skill.
523
524 ## End-to-end: Monitor Anything / `COLLECTORS.md`
525
526 1. `metadata.yaml` entries declare
527 `meta.monitored_instance.categories`.
528 2. `python3 integrations/gen_integrations.py` validates those
529 categories against `integrations/categories.yaml`, renders the
530 integration content, and writes the runtime
531 `integrations/integrations.js` catalog.
532 3. `python3 integrations/gen_doc_collector_page.py` reads
533 `integrations/integrations.js`, groups integrations by Monitor
534 Anything section, and writes `src/collectors/COLLECTORS.md`.
535 4. `check-markdown.yml` runs the same generator before Learn
536 ingest on PRs, so broken generated `COLLECTORS.md` content
537 (for example, unresolved links) blocks the PR. It does not
538 diff-check that the committed `COLLECTORS.md` file is fresh.
539 5. `generate-integrations.yml` runs the same generator after
540 metadata changes land on `master` and opens the
541 `integrations-regen` PR if committed generated artifacts drift.
542 6. Learn ingests `src/collectors/COLLECTORS.md` as the
543 "Monitor anything with Netdata" page.
544
545 For Network Flows specifically, keep the top-level `flows`
546 category handling in `gen_doc_collector_page.py`. Without it,
547 NetFlow / IPFIX / sFlow and enrichment entries will not appear
548 as a coherent `Network Flows` section on Monitor Anything.