From f84fcb0d0da978d121e1869a036214721edfae09 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Fri, 17 Jul 2026 14:26:29 +0100 Subject: [PATCH 01/25] feat: add config example validation script and workflow Signed-off-by: Patrick Stephens --- .github/workflows/pr-example-validation.yaml | 69 +++++++++++ scripts/extract-config.sh | 113 +++++++++++++++++++ scripts/test-config.sh | 108 ++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 .github/workflows/pr-example-validation.yaml create mode 100755 scripts/extract-config.sh create mode 100755 scripts/test-config.sh diff --git a/.github/workflows/pr-example-validation.yaml b/.github/workflows/pr-example-validation.yaml new file mode 100644 index 000000000..9d371f6db --- /dev/null +++ b/.github/workflows/pr-example-validation.yaml @@ -0,0 +1,69 @@ +name: Validate example configurations in PRs + +on: + pull_request: + paths: + - '**/*.md' + +permissions: {} + +# Avoid multiple commits on the same PR racing to update the comment. +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + validate-configs: + name: Validate example configurations in changed Markdown files + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out code + uses: actions/checkout@v7 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Get changed Markdown files + # We do not use the usual changed files action because of previous security issues. + id: changed_files + # This complex command finds files changed in the PR, filtering for `.md` and also ensuring they are in the `docs` directory. + # It ensures we only process files that are part of the PR and match our pattern. + # Note: The `github.event.pull_request.base.sha` is used to get the base commit of the PR, which is important for accurate diffing. + # + # On pull requests, HEAD refers to the merge commit. + # We want to compare against the base of the PR. + # Use git diff-tree to get files changed between the PR base and the current commit. + # --name-only lists only the names of the files. + # --diff-filter=AMCR will only show Added, Modified, Copied, Renamed files. + # We grep for *.md files you are interested in. + # The 'docs/' prefix is a common convention but adjust as necessary. + run: | + changed_md=$(git diff-tree --no-commit-id --name-only --diff-filter=AMCR ${{ github.event.pull_request.base.sha }}...HEAD | grep './.*\.md$' || true) + echo "Changed Markdown files: $changed_md" + echo "list=${changed_md}" >> $GITHUB_OUTPUT + shell: bash + + - name: Validate example configurations provided in any changed files + run: | + if [ -z "${CHANGED_MD_FILES:-}" ]; then + echo "No Markdown files changed in this PR. Skipping validation." + exit 0 + fi + + error_count=0 + + # Loop through each changed file + for FILE in $CHANGED_MD_FILES; do + echo "Processing changed file: $FILE" + ./scripts/test-config.sh "$FILE" || error_count=$((error_count + 1)) + done + + if [ $error_count -ne 0 ]; then + echo "ERROR: Validation failed for $error_count file(s)." + exit 1 + fi + shell: bash + env: + CHANGED_MD_FILES: ${{ steps.changed_files.outputs.list }} diff --git a/scripts/extract-config.sh b/scripts/extract-config.sh new file mode 100755 index 000000000..f2a80f33d --- /dev/null +++ b/scripts/extract-config.sh @@ -0,0 +1,113 @@ +#!/usr/bin/env sh +set -eu + +# This script extracts the contents of a code fence from a specific tab in a markdown file. +# The intention is to extract code snippets from the documentation to then validate them in CI. +# We are looking for a tab with a specific title and then a code fence with a specific language inside that tab. +# For example, given the following markdown snippet: + +# The following example captures Fluent Bit internal logs and writes them to standard output: +# {% tabs %} +# {% tab title="fluent-bit.yaml" %} +# ```yaml +# service: +# flush: 1 +# log_level: info +# pipeline: +# inputs: +# - name: fluentbit_logs +# tag: internal.logs +# outputs: +# - name: stdout +# match: 'internal.logs' +# ``` +# {% endtab %} +# {% tab title="fluent-bit.conf" %} +# ```text +# [SERVICE] +# Flush 1 +# Log_Level info +# [INPUT] +# Name fluentbit_logs +# Tag internal.logs +# [OUTPUT] +# Name stdout +# Match internal.logs +# ``` +# {% endtab %} +# {% endtabs %} +# To forward ... +# END + +# We want to pull out the contents of the code fence with language "yaml"/"text" from the tab with title "fluent-bit.yaml"/"fluent-bit.conf". +# Usage: ./extract-config.sh +# e.g. +# ./extract-config.sh README.md "fluent-bit.yaml" yaml +# + +FILE=${1:?"Usage: $0 FILE TAB_TITLE LANGUAGE"} +TITLE=${2:?"Usage: $0 FILE TAB_TITLE LANGUAGE"} +LANGUAGE=${3:?"Usage: $0 FILE TAB_TITLE LANGUAGE"} + +# We only use AWK to keep it simple and portable. +# We could use a more powerful parser, but that would require additional dependencies. +# For macOS ensure AWK is GNU awk (gawk) and not the default BSD awk. You can install gawk via Homebrew: `brew install gawk`. + +awk -v wanted_title="$TITLE" -v wanted_language="$LANGUAGE" ' +BEGIN { + tab_marker = "{% tab title=\"" wanted_title "\" %}" + fence_marker = "```" wanted_language +} + +{ + # Remove a trailing carriage return from CRLF input. + line = $0 + sub(/\r$/, "", line) + + # Ignore whitespace surrounding directives and fences. + marker = line + sub(/^[[:space:]]+/, "", marker) + sub(/[[:space:]]+$/, "", marker) + + if (!in_tab) { + if (marker == tab_marker) { + found_tab = 1 + in_tab = 1 + } + next + } + + if (!in_fence) { + if (marker == "{% endtab %}") { + reached_endtab = 1 + exit + } + + if (marker == fence_marker) { + found_fence = 1 + in_fence = 1 + } + + next + } + + if (marker == "```") { + closed_fence = 1 + exit + } + + print line +} + +END { + if (found_tab) { + if (!found_fence) { + printf "ERROR: %s code fence not found in tab: %s\n", wanted_language, wanted_title > "/dev/stderr" + exit 1 + } else if (!closed_fence) { + printf "ERROR: code fence was not closed in tab: %s\n", wanted_title > "/dev/stderr" + exit 1 + } + } +} +' "$FILE" \ No newline at end of file diff --git a/scripts/test-config.sh b/scripts/test-config.sh new file mode 100755 index 000000000..b831c8b77 --- /dev/null +++ b/scripts/test-config.sh @@ -0,0 +1,108 @@ +#!/usr/bin/env bash +set -euo pipefail + +# This script is used to test the configuration snippets extracted from the documentation. +# It will run the extracted configuration through Fluent Bit's dry-run mode to validate it. +# Errors are on stderr and the script will exit with a non-zero status if any validation fails. + +# To run for all Markdown files in the repository, you can use the following command: +# find . -type f -iname "*.md" | while read -r file; do +# if ! ./scripts/test-config.sh "$file"; then +# echo "FAILED: $file" | tee -a "$LOG_FILE" +# fi +# done + +SOURCE=${BASH_SOURCE[0]} +while [ -L "$SOURCE" ]; do + SCRIPT_DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd ) + SOURCE=$(readlink "$SOURCE") + [[ $SOURCE != /* ]] && SOURCE=$SCRIPT_DIR/$SOURCE +done +SCRIPT_DIR=$( cd -P "$( dirname "$SOURCE" )" >/dev/null 2>&1 && pwd ) + +FILE=${1:?"Usage: $0 FILE"} +if [ ! -f "${FILE}" ]; then + echo "ERROR: File $FILE does not exist or is not readable" >&2 + exit 1 +fi + +# Check if the file is listed in the suppression list. If it is, we skip validation for this file. +# We just use an array of suppressed files for now - add any new files to suppress to this list. +SUPPRESSED_FILES=( + # Skip the examples for contribution. + "CONTRIBUTING.md" + # Requires the WASM filter to be built and loaded. + "pipeline/filters/wasm.md" + "development/wasm-filter-plugins.md" + # Requires loading the actual LUA scripts as well. + "pipeline/filters/lua.md" + # Exec is not supported in the container image. + "pipeline/inputs/exec.md" + # Requires custom golang plugins to be built and loaded. + "development/golang-output-plugins.md" + # Not currently supported in the container image. + "pipeline/filters/tensorflow.md" + "pipeline/inputs/ebpf.md" + # Windows plugins are not available in the Linux image. + "installation/downloads/windows.md" + "pipeline/inputs/windows-event-log-winevtlog.md" + "pipeline/inputs/windows-system-statistics.md" + "pipeline/inputs/windows-exporter-metrics.md" + "pipeline/inputs/windows-event-log.md" +) +for suppressed_file in "${SUPPRESSED_FILES[@]}"; do + if [[ "$FILE" == "$suppressed_file" ]]; then + echo "INFO: Skipping validation for suppressed file $FILE" + exit 0 + fi + # Use a wildcard match to check if the file path matches any of the suppressed files. This allows for more flexible matching. + if [[ "$FILE" == *"$suppressed_file"* ]]; then + echo "INFO: Skipping validation for suppressed file $FILE" + exit 0 + fi + # Check if the file is in a subdirectory of any suppressed file. This allows for suppressing entire directories of files if needed. + if [[ "$FILE" == */"$suppressed_file" || "$FILE" == */"$suppressed_file"/* ]]; then + echo "INFO: Skipping validation for suppressed file $FILE" + exit 0 + fi +done + +CONTAINER_RUNTIME=${CONTAINER_RUNTIME:-docker} +# Always pull the latest Fluent Bit image to ensure we are validating against the most recent version. +if ! $CONTAINER_RUNTIME pull fluent/fluent-bit:latest &>/dev/null; then + echo "ERROR: Failed to pull Fluent Bit container image" >&2 + exit 1 +fi + +# Loop over YAML and legacy .conf configurations +for LANGUAGE in "yaml" "text"; do + if [ "$LANGUAGE" = "yaml" ]; then + TAB_TITLE="fluent-bit.yaml" + OUTPUT_FILE="/tmp/fluent-bit.yaml" + else + TAB_TITLE="fluent-bit.conf" + OUTPUT_FILE="/tmp/fluent-bit.conf" + fi + + # Extract the configuration snippet from the Markdown file + if ! "$SCRIPT_DIR/extract-config.sh" "$FILE" "$TAB_TITLE" "$LANGUAGE" > "$OUTPUT_FILE"; then + echo "ERROR: Failed to extract $LANGUAGE config from $FILE" >&2 + exit 1 + fi + + # If the output file is empty, skip validation + if [ ! -s "$OUTPUT_FILE" ]; then + continue + fi + + # Use the Fluent Bit container to validate the configuration snippet + if ! $CONTAINER_RUNTIME run --rm -t -v "$OUTPUT_FILE":"$OUTPUT_FILE":ro fluent/fluent-bit:latest fluent-bit --dry-run --config="$OUTPUT_FILE" &>/dev/null; then + echo "ERROR: Validation failed for $LANGUAGE config in $FILE" >&2 + # Provide the configuration and failure output for debugging purposes on stderr + cat "$OUTPUT_FILE" >&2 + $CONTAINER_RUNTIME run --rm -t -v "$OUTPUT_FILE":"$OUTPUT_FILE":ro fluent/fluent-bit:latest fluent-bit --dry-run --config="$OUTPUT_FILE" >&2 + exit 1 + fi +done + +echo "INFO: All checks passed for $FILE" From d7e030bac7e9432deabefb437c2d952863ea180c Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 15:40:07 +0100 Subject: [PATCH 02/25] fix: suppress known failures due to unrelated Fluent Bit issue 12113 Signed-off-by: Patrick Stephens --- scripts/test-config.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/test-config.sh b/scripts/test-config.sh index b831c8b77..3e19add20 100755 --- a/scripts/test-config.sh +++ b/scripts/test-config.sh @@ -29,6 +29,10 @@ fi # Check if the file is listed in the suppression list. If it is, we skip validation for this file. # We just use an array of suppressed files for now - add any new files to suppress to this list. SUPPRESSED_FILES=( + # These currently fail due to a known issue so skip them for now. See https://github.com/fluent/fluent-bit/issues/12113 and remove suppressions once this is in place. + "pipeline/outputs/azure_blob.md" + "pipeline/outputs/nats.md" + "pipeline/outputs/kafka-rest-proxy.md" # Skip the examples for contribution. "CONTRIBUTING.md" # Requires the WASM filter to be built and loaded. From a59ef517d0e06af94022e5c3d75762a8be2e8c7f Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 15:42:50 +0100 Subject: [PATCH 03/25] fix: resolve incorrect syntax in parsers configuration Signed-off-by: Patrick Stephens --- pipeline/parsers.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pipeline/parsers.md b/pipeline/parsers.md index 6b6cc0190..6f2f8fda6 100644 --- a/pipeline/parsers.md +++ b/pipeline/parsers.md @@ -65,13 +65,11 @@ For example, the following configuration file adds the default [`apache` parser] pipeline: inputs: - name: tail - path: /input/input.log - refresh_interval: 1 + path: /var/log/apache.log parser: apache - - name: http - listen: 0.0.0.0 - port: 8888 + - name: tail + path: /var/log/custom.log parser: custom_parser1 ``` From e9c40c5cf1ac87fe03b22b3ab806f907fc7ae115 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 15:46:54 +0100 Subject: [PATCH 04/25] fix: missing quote for dummy config in geoip filter Signed-off-by: Patrick Stephens --- pipeline/filters/geoip2-filter.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline/filters/geoip2-filter.md b/pipeline/filters/geoip2-filter.md index bbd981580..5e4f969c0 100644 --- a/pipeline/filters/geoip2-filter.md +++ b/pipeline/filters/geoip2-filter.md @@ -33,7 +33,7 @@ The following configuration processes the incoming `remote_addr` and appends cou pipeline: inputs: - name: dummy - dummy: {"remote_addr": "8.8.8.8"} + dummy: '{"remote_addr": "8.8.8.8"}' filters: - name: geoip2 From f8fa2d4c88537037b22c8d0a037dae0563071ea8 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 15:49:52 +0100 Subject: [PATCH 05/25] fix: incorrect comment format used in buffering examples Signed-off-by: Patrick Stephens --- pipeline/buffering.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pipeline/buffering.md b/pipeline/buffering.md index bb055ecec..f1947b465 100644 --- a/pipeline/buffering.md +++ b/pipeline/buffering.md @@ -102,7 +102,8 @@ pipeline: # Inherits storage.type: filesystem from service - name: mem - storage.type: memory # Overrides the inherited default + # Overrides the inherited default + storage.type: memory ``` {% endtab %} @@ -124,7 +125,8 @@ pipeline: [INPUT] Name mem - Storage.Type memory # Overrides the inherited default + # Overrides the inherited default + Storage.Type memory ``` {% endtab %} From 8123602886a31ee2eb491a6f951cfc4e837a1b93 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 15:51:26 +0100 Subject: [PATCH 06/25] fix: naming of parser files Signed-off-by: Patrick Stephens --- pipeline/filters/parser.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pipeline/filters/parser.md b/pipeline/filters/parser.md index 1db516612..60e9dbfbc 100644 --- a/pipeline/filters/parser.md +++ b/pipeline/filters/parser.md @@ -27,7 +27,7 @@ The plugin needs a parser file which defines how to parse each field. This is an example of parsing a record `{"data":"100 0.5 true This is example"}`. {% tabs %} -{% tab title="fluent-bit.yaml" %} +{% tab title="parser.yaml" %} ```yaml parsers: @@ -37,7 +37,7 @@ parsers: ``` {% endtab %} -{% tab title="fluent-bit.conf" %} +{% tab title="parser.conf" %} ```text [PARSER] From e509bf994331ccb002a2758d1c83d5bbb7546527 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 15:54:26 +0100 Subject: [PATCH 07/25] fix: incorrect format and quoting for dynatrace Signed-off-by: Patrick Stephens --- pipeline/outputs/dynatrace.md | 73 ++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/pipeline/outputs/dynatrace.md b/pipeline/outputs/dynatrace.md index 0b2ca0cb2..550d16d92 100644 --- a/pipeline/outputs/dynatrace.md +++ b/pipeline/outputs/dynatrace.md @@ -33,45 +33,48 @@ To get started with sending logs to Dynatrace: {% tabs %} {% tab title="fluent-bit.yaml" %} - ```yaml - pipeline: - - outputs: - - name: http - match: '*' - header: - - 'Content-Type application/json; charset=utf-8' - - 'Authorization Api-Token {your-API-token-here}' - allow_duplicated_headers: false - host: {your-environment-id}.live.dynatrace.com - port: 443 - uri: /api/v2/logs/ingest - format: json - json_date_format: iso8601 - json_date_key: timestamp - tls: on - tls.verify: on - ``` +```yaml +pipeline: + inputs: + - name: dummy + tag: test + + outputs: + - name: http + match: '*' + header: + - 'Content-Type application/json; charset=utf-8' + - 'Authorization Api-Token {your-API-token-here}' + allow_duplicated_headers: false + host: '{your-environment-id}.live.dynatrace.com' + port: 443 + uri: /api/v2/logs/ingest + format: json + json_date_format: iso8601 + json_date_key: timestamp + tls: on + tls.verify: on +``` {% endtab %} {% tab title="fluent-bit.conf" %} - ```text - [OUTPUT] - Name http - Match * - Header Content-Type application/json; charset=utf-8 - Header Authorization Api-Token {your-API-token-here} - Allow_Duplicated_Headers false - Host {your-environment-id}.live.dynatrace.com - Port 443 - Uri /api/v2/logs/ingest - Format json - Json_Date_Format iso8601 - Json_Date_Key timestamp - Tls On - Tls.verify On - ``` +```text +[OUTPUT] + Name http + Match * + Header Content-Type application/json; charset=utf-8 + Header Authorization Api-Token {your-API-token-here} + Allow_Duplicated_Headers false + Host {your-environment-id}.live.dynatrace.com + Port 443 + Uri /api/v2/logs/ingest + Format json + Json_Date_Format iso8601 + Json_Date_Key timestamp + Tls On + Tls.verify On +``` {% endtab %} {% endtabs %} From 06cf0c611a24976bbf940e9b3941792f80214ba0 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 15:56:44 +0100 Subject: [PATCH 08/25] fix: remove redundant and incorrect parser definition for gelf output Signed-off-by: Patrick Stephens --- pipeline/outputs/gelf.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/pipeline/outputs/gelf.md b/pipeline/outputs/gelf.md index e7c5d7e38..c20bfcda5 100644 --- a/pipeline/outputs/gelf.md +++ b/pipeline/outputs/gelf.md @@ -67,13 +67,6 @@ If you're using Fluent Bit for shipping Kubernetes logs, you can use something l {% tab title="fluent-bit.yaml" %} ```yaml -parsers: - - name: docker - format: json - time_key: time - time_format: '%Y-%m-%dT%H:%M:%S.%L' - time_keep: off - pipeline: inputs: - name: tail @@ -142,13 +135,6 @@ pipeline: Port 12201 Mode tcp Gelf_Short_Message_Key data - -[PARSER] - Name docker - Format json - Time_Key time - Time_Format %Y-%m-%dT%H:%M:%S.%L - Time_Keep Off ``` {% endtab %} From cdfbefe80de61c49b95e0c711f97d710946f0a20 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 15:58:50 +0100 Subject: [PATCH 09/25] fix: rename tab titles to match usage for containers Signed-off-by: Patrick Stephens --- installation/downloads/docker.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/installation/downloads/docker.md b/installation/downloads/docker.md index 758cbd3c8..312e5ebe4 100644 --- a/installation/downloads/docker.md +++ b/installation/downloads/docker.md @@ -15,7 +15,7 @@ docker run -ti cr.fluentbit.io/fluent/fluent-bit Use the following command to start Fluent Bit while using a configuration file: {% tabs %} -{% tab title="fluent-bit.conf" %} +{% tab title="Legacy Configuration: fluent-bit.conf" %} ```shell docker run -ti -v ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf \ @@ -24,7 +24,7 @@ docker run -ti -v ./fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf \ {% endtab %} -{% tab title="fluent-bit.yaml" %} +{% tab title="YAML configuration: fluent-bit.yaml" %} ```shell docker run -ti -v ./fluent-bit.yaml:/fluent-bit/etc/fluent-bit.yaml \ From 0f4b0ca1ecf0441f581c743c438f565dcac14b38 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 16:00:17 +0100 Subject: [PATCH 10/25] fix: use correct language block for legacy config in cpu-metrics input Signed-off-by: Patrick Stephens --- pipeline/inputs/cpu-metrics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pipeline/inputs/cpu-metrics.md b/pipeline/inputs/cpu-metrics.md index 7546e7f5b..669ab7702 100644 --- a/pipeline/inputs/cpu-metrics.md +++ b/pipeline/inputs/cpu-metrics.md @@ -82,7 +82,7 @@ pipeline: {% endtab %} {% tab title="fluent-bit.conf" %} -```shell +```text [INPUT] Name cpu Tag my_cpu From 68c4681cc2fb884afdbf7a542b4fd5f611be511e Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 16:01:59 +0100 Subject: [PATCH 11/25] fix: remove tab for conditional processing YAML definition block Signed-off-by: Patrick Stephens --- pipeline/processors/conditional-processing.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pipeline/processors/conditional-processing.md b/pipeline/processors/conditional-processing.md index 3a7c8e5b9..422794843 100644 --- a/pipeline/processors/conditional-processing.md +++ b/pipeline/processors/conditional-processing.md @@ -17,9 +17,6 @@ You can turn a standard processor into a conditional processor by adding a `cond These `condition` blocks use the following syntax: -{% tabs %} -{% tab title="fluent-bit.yaml" %} - ```yaml pipeline: inputs: @@ -41,9 +38,6 @@ pipeline: <...> ``` -{% endtab %} -{% endtabs %} - Each processor can only have a single `condition` block, but that condition can include multiple rules. These rules are stored as items in the `condition.rules` array. ### Condition evaluation From 55ff47d2d83d0970f6daf71c9302b9013f692d15 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 16:03:37 +0100 Subject: [PATCH 12/25] fix: suppress known failures due to Windows kubelet config Signed-off-by: Patrick Stephens --- scripts/test-config.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/test-config.sh b/scripts/test-config.sh index 3e19add20..1937538b4 100755 --- a/scripts/test-config.sh +++ b/scripts/test-config.sh @@ -53,6 +53,8 @@ SUPPRESSED_FILES=( "pipeline/inputs/windows-system-statistics.md" "pipeline/inputs/windows-exporter-metrics.md" "pipeline/inputs/windows-event-log.md" + # Windows-specific configuration examples are not supported in the Linux image. + "installation/downloads/kubernetes.md" ) for suppressed_file in "${SUPPRESSED_FILES[@]}"; do if [[ "$FILE" == "$suppressed_file" ]]; then From b8c6b37c56efa38c1aedb687f18bf47ae65a9a5f Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 16:22:23 +0100 Subject: [PATCH 13/25] feat: add multi-example validation Signed-off-by: Patrick Stephens --- scripts/README.md | 143 ++++++++++++++++++++++++++++++++++++++ scripts/extract-config.sh | 60 +++++++++++----- scripts/test-config.sh | 72 +++++++++++++------ 3 files changed, 239 insertions(+), 36 deletions(-) create mode 100644 scripts/README.md diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 000000000..8fe87c405 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,143 @@ +# Documentation Scripts + +This directory contains utility scripts for validating Fluent Bit configuration examples in the documentation. + +## Configuration Validation + +### `test-config.sh` + +Validates all Fluent Bit configuration examples in Markdown files by running them through `fluent-bit --dry-run`. + +**Usage:** +```bash +./scripts/test-config.sh +``` + +**How it works:** + +The script uses a **two-stage approach** to ensure reliable error reporting: + +1. **Count stage**: For each language (YAML, text), it counts how many examples exist using the `extract-config.sh count` mode +2. **Validation stage**: It extracts and validates each example by index in a predictable way + +This approach ensures that extraction errors are always legitimate—never due to reaching the end of examples. Any extraction error indicates a genuine problem with the file structure. + +**Features:** +- Supports multiple examples per Markdown file (processes all examples) +- Validates both YAML (`fluent-bit.yaml`) and legacy `.conf` (`fluent-bit.conf`) formats +- Reports all validation failures for a file (doesn't stop at first failure) +- Respects a suppression list for known failing configurations +- Requires Docker/Podman to run the Fluent Bit container +- Only reports errors that are legitimate (malformed examples or configuration issues) + +**Example - validate a single file:** +```bash +./scripts/test-config.sh pipeline/inputs/tail.md +``` + +**Example - validate all documentation files:** +```bash +find . -type f -iname "*.md" | while read -r file; do + if ! ./scripts/test-config.sh "$file"; then + echo "FAILED: $file" + fi +done +``` + +### `extract-config.sh` + +Extracts Fluent Bit configuration code blocks from Markdown files. This is used internally by `test-config.sh` but can also be called directly. + +**Usage:** +```bash +./scripts/extract-config.sh [index|count] +``` + +**Parameters:** +- `markdown-file`: Path to the Markdown file +- `tab-title`: The tab title (e.g., `"fluent-bit.yaml"` or `"fluent-bit.conf"`) +- `fence-language`: The code fence language (e.g., `yaml` or `text`) +- `index|count` (Optional): Extract a specific example by index, or use `count` to get the total number of examples. Defaults to 1 (first example) + +**Examples:** +```bash +# Count total YAML examples in a file +./scripts/extract-config.sh pipeline/inputs/tail.md "fluent-bit.yaml" yaml count + +# Extract the first YAML example +./scripts/extract-config.sh pipeline/inputs/tail.md "fluent-bit.yaml" yaml + +# Extract the second YAML example +./scripts/extract-config.sh pipeline/inputs/tail.md "fluent-bit.yaml" yaml 2 + +# Extract the third .conf example +./scripts/extract-config.sh pipeline/inputs/tail.md "fluent-bit.conf" text 3 +``` + +**Count mode:** + +The `count` parameter returns the total number of matching code fences for the specified language in the given tab. This is used by `test-config.sh` to: +1. Determine how many examples to validate +2. Avoid trying to extract examples that don't exist +3. Ensure all extraction errors are legitimate + +Example output: +```bash +$ ./scripts/extract-config.sh pipeline/inputs/tail.md "fluent-bit.yaml" yaml count +5 +``` + +## Markdown Format + +Configuration examples should be formatted using Gitbook-style tabs: + +```markdown +{% tabs %} +{% tab title="fluent-bit.yaml" %} +```yaml +service: + flush: 1 +``` +{% endtab %} +{% tab title="fluent-bit.conf" %} +```text +[SERVICE] + Flush 1 +``` +{% endtab %} +{% endtabs %} +``` + +Multiple examples in the same file are supported: + +```markdown +## First Example + +{% tabs %} +{% tab title="fluent-bit.yaml" %} +```yaml +# First example YAML +``` +{% endtab %} +{% endtabs %} + +## Second Example + +{% tabs %} +{% tab title="fluent-bit.yaml" %} +```yaml +# Second example YAML +``` +{% endtab %} +{% endtabs %} +``` + +## Suppression List + +Some configuration examples are intentionally skipped in validation. These are configured in the `SUPPRESSED_FILES` array in `test-config.sh`. Files are suppressed if they: + +- Require additional plugins not included in the standard container image +- Contain examples that are not yet supported +- Are Windows-specific configurations + +Refer to the comments in `test-config.sh` for the complete list and reasons for each suppression. diff --git a/scripts/extract-config.sh b/scripts/extract-config.sh index f2a80f33d..3d0fdd8e6 100755 --- a/scripts/extract-config.sh +++ b/scripts/extract-config.sh @@ -40,23 +40,31 @@ set -eu # END # We want to pull out the contents of the code fence with language "yaml"/"text" from the tab with title "fluent-bit.yaml"/"fluent-bit.conf". -# Usage: ./extract-config.sh +# Usage: ./extract-config.sh [index|count] # e.g. -# ./extract-config.sh README.md "fluent-bit.yaml" yaml +# ./extract-config.sh README.md "fluent-bit.yaml" yaml # Extract first example +# ./extract-config.sh README.md "fluent-bit.yaml" yaml 2 # Extract second example +# ./extract-config.sh README.md "fluent-bit.yaml" yaml count # Count total examples # -FILE=${1:?"Usage: $0 FILE TAB_TITLE LANGUAGE"} -TITLE=${2:?"Usage: $0 FILE TAB_TITLE LANGUAGE"} -LANGUAGE=${3:?"Usage: $0 FILE TAB_TITLE LANGUAGE"} +FILE=${1:?"Usage: $0 FILE TAB_TITLE LANGUAGE [INDEX|count]"} +TITLE=${2:?"Usage: $0 FILE TAB_TITLE LANGUAGE [INDEX|count]"} +LANGUAGE=${3:?"Usage: $0 FILE TAB_TITLE LANGUAGE [INDEX|count]"} +INDEX_OR_MODE=${4:-1} # Extract the Nth matching code fence, or use "count" to get total count # We only use AWK to keep it simple and portable. # We could use a more powerful parser, but that would require additional dependencies. # For macOS ensure AWK is GNU awk (gawk) and not the default BSD awk. You can install gawk via Homebrew: `brew install gawk`. -awk -v wanted_title="$TITLE" -v wanted_language="$LANGUAGE" ' +awk -v wanted_title="$TITLE" -v wanted_language="$LANGUAGE" -v target_index_or_mode="$INDEX_OR_MODE" ' BEGIN { tab_marker = "{% tab title=\"" wanted_title "\" %}" fence_marker = "```" wanted_language + fence_count = 0 + count_mode = (target_index_or_mode == "count") + if (!count_mode) { + target_index = int(target_index_or_mode) + } } { @@ -79,13 +87,27 @@ BEGIN { if (!in_fence) { if (marker == "{% endtab %}") { - reached_endtab = 1 - exit + if (count_mode) { + # In count mode, reset in_tab to look for more tabs + in_tab = 0 + } else if (fence_count < target_index) { + # Reset and look for the next tab if we havent found enough fences yet + in_tab = 0 + found_tab = 1 # Keep looking for more tabs + } + next } if (marker == fence_marker) { - found_fence = 1 - in_fence = 1 + fence_count++ + if (count_mode) { + # In count mode, just keep counting + next + } + if (fence_count == target_index) { + found_fence = 1 + in_fence = 1 + } } next @@ -100,12 +122,18 @@ BEGIN { } END { - if (found_tab) { - if (!found_fence) { - printf "ERROR: %s code fence not found in tab: %s\n", wanted_language, wanted_title > "/dev/stderr" - exit 1 - } else if (!closed_fence) { - printf "ERROR: code fence was not closed in tab: %s\n", wanted_title > "/dev/stderr" + if (count_mode) { + # In count mode, output the count + print fence_count + } else { + # In extract mode, validate that we found what we were looking for + if (found_tab && found_fence) { + if (!closed_fence) { + printf "ERROR: code fence was not closed in tab: %s\n", wanted_title > "/dev/stderr" + exit 1 + } + } else if (found_tab && !found_fence) { + printf "ERROR: %s code fence #%d not found in tab: %s\n", wanted_language, target_index, wanted_title > "/dev/stderr" exit 1 } } diff --git a/scripts/test-config.sh b/scripts/test-config.sh index 1937538b4..1bd4d0c4f 100755 --- a/scripts/test-config.sh +++ b/scripts/test-config.sh @@ -1,8 +1,9 @@ #!/usr/bin/env bash set -euo pipefail -# This script is used to test the configuration snippets extracted from the documentation. -# It will run the extracted configuration through Fluent Bit's dry-run mode to validate it. +# This script is used to test configuration snippets extracted from the documentation. +# It will run each extracted configuration through Fluent Bit's dry-run mode to validate it. +# Supports multiple examples per Markdown file and reports all validation failures. # Errors are on stderr and the script will exit with a non-zero status if any validation fails. # To run for all Markdown files in the repository, you can use the following command: @@ -80,6 +81,9 @@ if ! $CONTAINER_RUNTIME pull fluent/fluent-bit:latest &>/dev/null; then exit 1 fi +FAILED_VALIDATIONS=() +PASSED_VALIDATIONS=0 + # Loop over YAML and legacy .conf configurations for LANGUAGE in "yaml" "text"; do if [ "$LANGUAGE" = "yaml" ]; then @@ -90,25 +94,53 @@ for LANGUAGE in "yaml" "text"; do OUTPUT_FILE="/tmp/fluent-bit.conf" fi - # Extract the configuration snippet from the Markdown file - if ! "$SCRIPT_DIR/extract-config.sh" "$FILE" "$TAB_TITLE" "$LANGUAGE" > "$OUTPUT_FILE"; then - echo "ERROR: Failed to extract $LANGUAGE config from $FILE" >&2 - exit 1 - fi - - # If the output file is empty, skip validation - if [ ! -s "$OUTPUT_FILE" ]; then + # Stage 1: Count how many examples exist for this language in the file + # We do this so we know we should have X examples to validate, and we can report if any are missing or fail validation or extraction. + EXAMPLE_COUNT=$("$SCRIPT_DIR/extract-config.sh" "$FILE" "$TAB_TITLE" "$LANGUAGE" count 2>/dev/null || echo 0) + + if [ "$EXAMPLE_COUNT" -eq 0 ]; then continue fi - - # Use the Fluent Bit container to validate the configuration snippet - if ! $CONTAINER_RUNTIME run --rm -t -v "$OUTPUT_FILE":"$OUTPUT_FILE":ro fluent/fluent-bit:latest fluent-bit --dry-run --config="$OUTPUT_FILE" &>/dev/null; then - echo "ERROR: Validation failed for $LANGUAGE config in $FILE" >&2 - # Provide the configuration and failure output for debugging purposes on stderr - cat "$OUTPUT_FILE" >&2 - $CONTAINER_RUNTIME run --rm -t -v "$OUTPUT_FILE":"$OUTPUT_FILE":ro fluent/fluent-bit:latest fluent-bit --dry-run --config="$OUTPUT_FILE" >&2 - exit 1 - fi + + # Stage 2: Extract and validate each example by index + for ((EXAMPLE_INDEX = 1; EXAMPLE_INDEX <= EXAMPLE_COUNT; EXAMPLE_INDEX++)); do + # Extract the configuration snippet from the Markdown file + if ! "$SCRIPT_DIR/extract-config.sh" "$FILE" "$TAB_TITLE" "$LANGUAGE" "$EXAMPLE_INDEX" > "$OUTPUT_FILE"; then + # Extraction should not fail at this point since we counted the examples + echo "ERROR: Failed to extract $LANGUAGE example $EXAMPLE_INDEX from $FILE (count was $EXAMPLE_COUNT)" >&2 + FAILED_VALIDATIONS+=("$LANGUAGE example $EXAMPLE_INDEX") + continue + fi + + # If the output file is empty, skip validation + if [ ! -s "$OUTPUT_FILE" ]; then + continue + fi + + # Use the Fluent Bit container to validate the configuration snippet + if ! $CONTAINER_RUNTIME run --rm -t -v "$OUTPUT_FILE":"$OUTPUT_FILE":ro fluent/fluent-bit:latest fluent-bit --dry-run --config="$OUTPUT_FILE" &>/dev/null; then + FAILED_VALIDATIONS+=("$LANGUAGE example $EXAMPLE_INDEX") + # Provide the configuration and failure output for debugging purposes on stderr + echo "ERROR: Validation failed for $LANGUAGE example $EXAMPLE_INDEX in $FILE" >&2 + cat "$OUTPUT_FILE" >&2 + $CONTAINER_RUNTIME run --rm -t -v "$OUTPUT_FILE":"$OUTPUT_FILE":ro fluent/fluent-bit:latest fluent-bit --dry-run --config="$OUTPUT_FILE" >&2 || true + else + PASSED_VALIDATIONS=$((PASSED_VALIDATIONS + 1)) + fi + done done -echo "INFO: All checks passed for $FILE" +# Report results +if [ ${#FAILED_VALIDATIONS[@]} -gt 0 ]; then + echo "ERROR: $FILE had ${#FAILED_VALIDATIONS[@]} validation failure(s):" >&2 + for failed in "${FAILED_VALIDATIONS[@]}"; do + echo " - $failed" >&2 + done + exit 1 +fi + +if [ $PASSED_VALIDATIONS -eq 0 ]; then + echo "INFO: No configuration examples found in $FILE" +else + echo "INFO: All $PASSED_VALIDATIONS check(s) passed for $FILE" +fi From fb03366dcba768cd518b1557207874bb6aac9b67 Mon Sep 17 00:00:00 2001 From: Patrick Stephens Date: Sun, 19 Jul 2026 16:26:48 +0100 Subject: [PATCH 14/25] fix: additional failures in tail examples Signed-off-by: Patrick Stephens --- pipeline/inputs/tail.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pipeline/inputs/tail.md b/pipeline/inputs/tail.md index 746b1b5eb..2e0a61e9f 100644 --- a/pipeline/inputs/tail.md +++ b/pipeline/inputs/tail.md @@ -272,8 +272,8 @@ pipeline: path: /var/log/syslog outputs: - - stdout: - match: * + - name: stdout + match: '*' ``` {% endtab %} @@ -368,13 +368,13 @@ pipeline: {% tab title="fluent-bit.conf" %} ```text -# Note this is generally added to parsers.conf and referenced in [SERVICE] -[PARSER] - Name multiline - Format regex - Regex /(?