Back to Netdata

Pipeline

.agents/skills/integrations-lifecycle/pipeline.md

2.11.021.3 KB
Original Source

Pipeline

This document maps the integrations pipeline end to end -- every script, every input, every output, every CI workflow. All path citations are repo-relative; line citations refer to the file at HEAD of master at the time this skill was last updated.

The integration documentation pipeline

[ YAML sources ]
         |
         v
+------------------------+
| gen_integrations.py    |  (orchestrator, validator, renderer)
+------------------------+
         |
         v   reads
+------------------------+
| integrations.js (gitignored)
| integrations.json (gitignored)
+------------------------+

[ collector metadata.yaml + taxonomy.yaml + taxonomy registries ]
         |
         v
+------------------------+
| gen_taxonomy.py        |  (taxonomy validator, resolver)
+------------------------+
         |
         v
+------------------------+
| taxonomy.json (gitignored)
+------------------------+
         |
         v
+--------------------------------+
| gen_docs_integrations.py        |  (per-integration .md files)
+--------------------------------+
         |
         v
+--------------------------------+
| gen_doc_collector_page.py       |  (src/collectors/COLLECTORS.md)
+--------------------------------+
         |
         v
+--------------------------------+
| gen_doc_secrets_page.py         |  (src/collectors/SECRETS.md)
+--------------------------------+
         |
         v
+--------------------------------+
| gen_doc_service_discovery_page.py |  (src/collectors/SERVICE-DISCOVERY.md)
+--------------------------------+   NOT in CI today -- see gotchas.md

All four downstream scripts read the same integrations.js (or its data inside; details below). They run sequentially but do not cross-talk.

Stage 1 -- gen_integrations.py (orchestrator)

Repo path: integrations/gen_integrations.py.

Inputs

  • Categories: integrations/categories.yaml (validated against integrations/schemas/categories.json at gen_integrations.py:344-355).
  • Distros: .github/data/distros.yml (loaded via load_yaml at gen_integrations.py:1330 -- WITHOUT validation; the distros.json schema exists but is not consulted -- see gotchas.md).
  • Per-integration metadata.yaml files matched by METADATA_PATTERN = '*/metadata.yaml' (gen_integrations.py:25) under nine collector source roots:
RootIntegration types served
src/collectorsC plugins (apps, cgroups, diskspace, ebpf, freebsd, idlejitter, macos, proc, slabinfo, statsd, systemd-journal, tc, timex, xenstat, log2journal, charts.d, python.d)
src/collectors/charts.d.pluginshell-based charts.d collectors
src/collectors/python.d.pluginPython collectors (am2320, etc.)
src/collectors/guidestutorial-style content
src/go/plugin/go.d/collectorthe Go collector tree (the bulk)
src/go/plugin/scripts.d/collectorscripts.d (shell)
src/go/plugin/ibm.d/modulesibm.d collectors (db2, mq, etc.)
src/go/plugin/ibm.d/modules/webspherewebsphere/{jmx,mp,pmi}/ subcollectors -- listed separately because they are 1 level deeper
src/crates/otel-plugin/metadata.yamlthe OTEL plugin's collector metadata (single-file source)
  • Exporters: src/exporting/*/metadata.yaml (gen_integrations.py:43).

  • Agent notifications: src/health/notifications/*/metadata.yaml (:47).

  • Cloud notifications: integrations/cloud-notifications/metadata.yaml (:51).

  • Logs: integrations/logs/metadata.yaml (:55).

  • Authentication: integrations/cloud-authentication/metadata.yaml (:59).

  • Secretstore: src/go/plugin/agent/secrets/secretstore/backends/*/metadata.yaml (:63).

  • Service discovery: src/go/plugin/go.d/discovery/sdext/discoverer/*/metadata.yaml (:67).

  • Deploy: integrations/deploy.yaml (:39-41).

  • Schemas: integrations/schemas/*.json -- loaded on demand via Registry(retrieve=retrieve_from_filesystem) (gen_integrations.py:163-169). Each integration type has its own Draft7Validator instance (:171-219).

  • Templates: integrations/templates/** -- Jinja env at gen_integrations.py:230-241. Custom delimiters: [[ ]] for variables and [% %] for control statements (so that the template can pass through embedded {% ... %} and {{ ... }} markers untouched). See gotchas.md.

Validation behavior

For each integration type, gen_integrations.py runs a Draft7Validator.validate(...) call (e.g. :350, :372, :399, :437, :485, :533, :581, :629, :677, :725). On any ValidationError, the script calls warn(...). Warnings are fatal: fail_on_warnings() (:150-160) returns 1, causing the CI workflow to fail and abort doc regeneration.

The validator IS strict about declared properties; it is NOT strict about extra properties (no additionalProperties: false on collector.json). Unknown keys (alternative_monitored_instances, most_popular) pass through silently. They appear in integrations.js but no template renders them. See gotchas.md.

Rendering behavior

For each integration type, the script:

  1. Loads the YAML(s).
  2. Validates each entry against the type's JSON Schema.
  3. Calls make_id (collectors only -- :766, f'{plugin}-{module}-{instance}').
  4. Computes edit_link from _src_path (:777).
  5. Sorts by id/path/index.
  6. Calls dedupe_integrations (:789); duplicate ids yield warnings.
  7. Renders every section listed in *_RENDER_KEYS (:71-122) through Jinja, storing the result back on the item under that key. Sections come from the type's schema (e.g. COLLECTOR_RENDER_KEYS = ['alerts', 'metrics', 'functions', 'overview', 'related_resources', 'setup', 'troubleshooting'] at :71).
  8. Each section is rendered TWICE -- with clean=False (rich variant for the JS / cloud-frontend output) and clean=True (clean variant for the JSON / GitHub-rendered .md output). Both variants are kept in parallel clean_* lists.
  9. Strips internal-only keys (_src_path, _repo, _index) before serialization.

Two-pass templating with meta.variables

When a metadata entry declares meta.monitored_instance.variables (collectors) or meta.variables (other types), the FIRST pass produces markdown that may still contain [[ variables.foo ]] markers. The renderer detects this with a regex and performs a SECOND Jinja pass over the rendered string with variables=... in context (:930-934). This lets metadata authors inject runtime-style placeholders into rendered text.

Divergence: collectors look up monitored_instance.variables; exporters and notifications look up meta.variables directly. Same goal, different lookup path -- a known wart.

Outputs

Two files are written (gen_integrations.py:1311-1325):

  • integrations/integrations.js -- assembles the integrations/templates/integrations.js Jinja shell with categories=... and integrations=... JSON, then runs convert_local_links to rewrite any ](/...) in the body to absolute GitHub URLs at https://github.com/netdata/netdata/blob/master/.... The first 2 lines are a banner:
    // DO NOT EDIT THIS FILE DIRECTLY
    // It gets generated by integrations/gen_integrations.py in the Netdata repo
    
    The body is export const categories = [...]; export const integrations = [...].
  • integrations/integrations.json -- pure JSON with the clean variant of {categories, integrations}. No banner.

Both are gitignored (.gitignore:162-163). They are produced fresh on every run; in CI, the workflow rms them after the downstream scripts read them so they are NOT included in the auto-PR.

Parallel taxonomy stage -- gen_taxonomy.py

Repo path: integrations/gen_taxonomy.py.

Inputs

  • Collector metadata.yaml files from the same collector source roots used by gen_integrations.py.
  • Sibling collector taxonomy.yaml files discovered as <collector>/taxonomy.yaml.
  • integrations/taxonomy/sections.yaml -- the stable section registry. Collector files reference only section_id:, never section_path:.
  • integrations/taxonomy/icons.yaml -- allowed icon ids.
  • integrations/schemas/taxonomy_collector.json, taxonomy_sections.json, and taxonomy_output.json.

Validation behavior

The generator validates closed v1 authoring schemas, checks that literal owned contexts and widget references resolve to real contexts in the owning collector's metadata.yaml, and requires dynamic selectors to be declared by the owning collector:

  • context_prefix: requires metrics.dynamic_context_prefixes: [{prefix, reason}]; taxonomy may use a narrower prefix under the declared namespace.
  • collect_plugin: requires metrics.dynamic_collect_plugins: [{plugin, reason}].

Findings render as plain text locally and as GitHub Actions annotations in CI. Fatal findings fail the run.

Outputs

integrations/taxonomy.json is written by default and validated against integrations/schemas/taxonomy_output.json. The file is gitignored and removed by generate-integrations.yml cleanup, just like integrations/integrations.js and integrations/integrations.json.

Run validation only:

bash
python3 integrations/gen_taxonomy.py --check-only

Seed a collector taxonomy from existing metadata contexts:

bash
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

The seed helper emits a flat items: tree. For collectors with richer dashboard layout needs, convert that flat list into explicit owned_context, group, flatten, selector, context, grid, first_available, or view_switch items before opening the PR.

Pull-request coverage is checked by:

bash
python3 integrations/check_collector_taxonomy.py --pr-diff origin/master...HEAD

Commands a maintainer runs locally

bash
cd <repo>
./integrations/pip.sh         # installs jsonschema referencing jinja2 ruamel.yaml
python3 integrations/gen_integrations.py

Run from the repo root. The script depends on relative paths hard-coded in gen_integrations.py:11-37.

Stage 2 -- gen_docs_integrations.py

Repo path: integrations/gen_docs_integrations.py.

Inputs

  • integrations/integrations.js -- the script parses it by string-splitting on export const categories = and export const integrations = (:129-140). It does NOT read integrations.json.

Outputs

For each integration entry, the script writes either a <plugin-dir>/integrations/<slug>.md file or a <plugin-dir>/README.md file (depending on type). The full mapping per integration type is in per-type-matrix.md. Slug rules are in artifacts-and-banners.md.

After writing, the script:

  1. Calls resolve_related_links() (:56-78) to convert {% relatedResource id="..." %}name{% /relatedResource %} markers (left in by templates/overview/collector.md:42 and templates/related_resources.md:5) into [name](/path) markdown links. Two-pass resolution: the markers are present in pass 1; they get rewritten in pass 2 after every file is written so the id-to-path map is complete. If the id is not found, the marker is replaced with bare name text (silent fallback).

  2. Calls make_symlinks(symlink_dict) (:527-544) to symlink <plugin-dir>/README.md -> integrations/<sole-file>.md when the directory holds exactly one integration. Only fires when len(list(integrations_dir.iterdir())) == 1 (:466). Multi-integration directories are NOT symlinked.

  3. Cleans the corresponding **/integrations directories BEFORE writing (:19-41), so removed integrations vanish from the tree.

Scoped regen

The script accepts -c plugin/module to scope cleanup and regen to one collector (:578-583). Useful locally:

bash
python3 integrations/gen_docs_integrations.py -c go.d.plugin/snmp

NOT used by CI; CI always runs without -c (full regen).

Stage 3 -- gen_doc_collector_page.py

Repo path: integrations/gen_doc_collector_page.py.

Reads integrations/integrations.js (:38-47). Walks the category tree; the "section-level" categories are normally children of data-collection, plus the top-level flows category (:82-86). flows is deliberately included because Network Flows entries cover both flow protocols and enrichment inputs, so the Monitor Anything page must list them together under a Network Flows section instead of dropping them into Other.

Writes src/collectors/COLLECTORS.md (committed). This is the "Monitor anything with Netdata" umbrella marketing page that lists every collector and Network Flows integration in tabular form, grouped by section. The write path is generate_collectors_md() (:565-584), which renders the header plus dynamic tables and atomically replaces the file.

Notable behaviors

  • Sort order: "Linux first, Other last" (:285-301); Network Flows follows its position in integrations/categories.yaml because it is treated as a section.
  • Description extraction: extract_description_from_overview reads ## Overview body, uses the first sentence (:143-183); falls back to meta.monitored_instance.description; final fallback Monitor <name>. Because this text becomes the Monitor Anything table description, the first sentence of the overview must describe the integration itself, not a setting, variable, default, limit, or troubleshooting detail. See description-authoring.md.
  • Slug for table links: to_slug(display_name) -- lowercase, spaces to _, / to -, strips parentheses (:213-215).
  • Hardcoded marketing anchors: _render_tech_navigation (:424-493) writes #cloud-provider-managed, #kubernetes, #search-engines, #freebsd, #message-brokers, etc. Several of these category IDs do NOT exist in categories.yaml -- some links go to non-existent anchors. See gotchas.md. Header literal "850+ integrations" is also baked in.

Stage 4 -- gen_doc_secrets_page.py

Repo path: integrations/gen_doc_secrets_page.py.

Reads integrations/integrations.js (:213-217), filters entries where integration_type == 'secretstore', builds the "Supported Secretstore Backends" table from each backend's meta.kind, meta.name, collector_configs.summary.{operand_format, example_operand}, and renders via integrations/templates/secrets.md. Writes src/collectors/SECRETS.md (committed, :358).

The bulk of SECRETS.md is static content baked into the script (SECRETS_PAGE dict, :20-203). Only the backends table is dynamic. To change the static prose, edit the script.

Stage 5 -- gen_doc_service_discovery_page.py

Repo path: integrations/gen_doc_service_discovery_page.py.

Mirror of the secrets stage for service discovery. Reads integrations.js, filters integration_type == 'service_discovery', renders via integrations/templates/service_discovery.md. Writes src/collectors/SERVICE-DISCOVERY.md (committed, :382). Most content is static (SD_PAGE dict, :21-257).

KNOWN GAP: this stage is NOT wired into the generate-integrations.yml workflow. CI does not run it. The file in tree drifts from metadata.yaml until a developer runs the script manually (or a future PR adds it to CI). See gotchas.md and the SOW followups.

CI workflow 1 -- generate-integrations.yml

Repo path: .github/workflows/generate-integrations.yml.

Triggers

  • push to master filtered by paths (generate-integrations.yml:6-25):
    • **/metadata.yaml (every collector / exporter / notification metadata)
    • integrations/templates/**
    • integrations/schemas/**
    • integrations/categories.yaml, integrations/deploy.yaml
    • integrations/cloud-notifications/metadata.yaml, integrations/cloud-authentication/metadata.yaml
    • the four older Python scripts (NOT gen_doc_service_discovery_page.py -- the gap)
  • workflow_dispatch -- manual.

Concurrency

  • integrations-${{ github.ref }}, cancel-in-progress: true.

Repo gate

  • if: github.repository == 'netdata/netdata' -- forks do NOT trigger this workflow.

Steps

  1. actions/checkout@v6 (depth 1, recursive submodules).
  2. apt install python3-venv + ./integrations/pip.sh to install Python deps.
  3. python3 integrations/gen_integrations.py.
  4. python3 integrations/gen_docs_integrations.py.
  5. python3 integrations/gen_doc_collector_page.py.
  6. python3 integrations/gen_doc_secrets_page.py.
  7. NOT gen_doc_service_discovery_page.py -- gap.
  8. rm -rf go.d.plugin virtualenv integrations/integrations.js integrations/integrations.json -- prevents the auto-PR from committing the runtime artifacts.
  9. peter-evans/create-pull-request@v8 -- branch integrations-regen, label integrations-update, title Regenerate integrations docs, token NETDATABOT_GITHUB_TOKEN. Reviewed and merged manually.
  10. Slack failure notification on master failures.

CI workflow 2 -- check-markdown.yml

Repo path: .github/workflows/check-markdown.yml.

Triggers

  • pull_request filtered by paths:
    • **/*.md, **/*.mdx
    • docs/**, **/metadata.yaml, integrations/**

Steps

  1. Checkout PR branch and the netdata/learn repo.
  2. Install Python deps (./integrations/pip.sh).
  3. Run gen_integrations.py, gen_docs_integrations.py, gen_doc_collector_page.py, gen_doc_secrets_page.py (same gap on SD page generator).
  4. Run learn/ingest/ingest.py --local-repo netdata:... --ignore-on-prem-repo --fail-links-netdata (check-markdown.yml:64-69) -- validates that all generated markdown links resolve through Learn's ingest pipeline.

This workflow validates but does NOT auto-commit. It acts as a gate on PRs. A failure here means a PR cannot merge until the metadata or links are fixed.

CMake target -- render-docs

Repo path: packaging/cmake/Modules/NetdataRenderDocs.cmake.

A developer-facing convenience target. When wired up by the build system, it runs the same generator chain (with gen_integrations + gen_docs_integrations only by default). Useful for local validation. NOT a substitute for running the scripts directly during active development.

End-to-end: a source PR's flow

  1. Developer edits src/go/plugin/go.d/collector/foo/metadata.yaml (and any other affected consistency-rule files: taxonomy.yaml, config_schema.json, stock conf, health.d/foo.conf, README.md).
  2. Developer runs locally:
    bash
    ./integrations/pip.sh
    python3 integrations/gen_integrations.py
    python3 integrations/gen_taxonomy.py --check-only
    python3 integrations/check_collector_taxonomy.py --pr-diff master...HEAD
    python3 -m unittest integrations.tests.test_taxonomy
    # When committing generated docs in the source PR:
    python3 integrations/gen_docs_integrations.py -c go.d.plugin/foo
    python3 integrations/gen_doc_collector_page.py
    python3 integrations/gen_doc_secrets_page.py
    
  3. Developer records one generated-documentation delivery route:
    • commit the regenerated integrations/foo.md, symlinked README.md (if applicable), and affected umbrella pages in the source PR; or
    • leave existing generated pages unchanged and use the automatic post-merge regeneration PR.
  4. PR is opened. check-markdown.yml runs, regenerates the same files in CI, and validates Learn ingest. It does not assert that regeneration leaves the checkout clean; failures come from generation, taxonomy, or Learn-ingest validation.
  5. Reviewer checks collector consistency, including taxonomy coverage for changed chart contexts.
  6. PR merges. generate-integrations.yml triggers on master, regenerates everything, and opens an integrations-regen PR if anything is now stale. This PR carries the generated pages when the source PR selected the post-merge route and is normally empty when the source PR committed them. Maintainer merges it when it contains changes.
  7. Cloud-frontend's own CI (in ${NETDATA_REPOS_DIR}/dashboard/cloud-frontend/) runs gen_integrations.py against the new master and copies integrations.js into its source. See in-app-contract.md.
  8. Learn's ingest pulls the new integrations/foo.md on its 3-hourly schedule. See the learn-site-structure skill.

End-to-end: Monitor Anything / COLLECTORS.md

  1. metadata.yaml entries declare meta.monitored_instance.categories.
  2. python3 integrations/gen_integrations.py validates those categories against integrations/categories.yaml, renders the integration content, and writes the runtime integrations/integrations.js catalog.
  3. python3 integrations/gen_doc_collector_page.py reads integrations/integrations.js, groups integrations by Monitor Anything section, and writes src/collectors/COLLECTORS.md.
  4. check-markdown.yml runs the same generator before Learn ingest on PRs, so broken generated COLLECTORS.md content (for example, unresolved links) blocks the PR. It does not diff-check that the committed COLLECTORS.md file is fresh.
  5. generate-integrations.yml runs the same generator after metadata changes land on master and opens the integrations-regen PR if committed generated artifacts drift.
  6. Learn ingests src/collectors/COLLECTORS.md as the "Monitor anything with Netdata" page.

For Network Flows specifically, keep the top-level flows category handling in gen_doc_collector_page.py. Without it, NetFlow / IPFIX / sFlow and enrichment entries will not appear as a coherent Network Flows section on Monitor Anything.