From 36051948c938ff959973b4cc8fd5c4d13ffcbe50 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Wed, 29 Jul 2026 12:12:21 +0100 Subject: [PATCH 1/4] fix: remove stale alias-keyed iceberg property override (DOC-1889) --- docs-data/property-overrides.json | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docs-data/property-overrides.json b/docs-data/property-overrides.json index e1ed2af316..5bf7411820 100644 --- a/docs-data/property-overrides.json +++ b/docs-data/property-overrides.json @@ -1071,13 +1071,6 @@ ], "config_scope": "cluster" }, - "iceberg_rest_catalog_aws_credentials_source": { - "description": "*Accepted values*: `aws_instance_metadata`, `azure_aks_oidc_federation`, `azure_vm_instance_metadata`, `config_file`, `gcp_instance_metadata`, `sts`.", - "related_topics": [ - "xref:reference:properties/object-storage-properties.adoc#cloud_storage_credentials_source[`cloud_storage_credentials_source`]" - ], - "config_scope": "cluster" - }, "iceberg_rest_catalog_aws_region": { "description": "AWS region for Iceberg REST catalog SigV4 authentication. If not set, falls back to xref:reference:properties/object-storage-properties.adoc#cloud_storage_region[`cloud_storage_region`] when using aws_sigv4 authentication mode.", "related_topics": [ @@ -1110,7 +1103,8 @@ "iceberg_rest_catalog_credentials_source": { "related_topics": [ "xref:reference:properties/object-storage-properties.adoc#cloud_storage_credentials_source[`cloud_storage_credentials_source`]" - ] + ], + "config_scope": "cluster" }, "iceberg_rest_catalog_crl": { "description": "The contents of a certificate revocation list for `iceberg_rest_catalog_trust`. Takes precedence over `iceberg_rest_catalog_crl_file`.", From b8b58b2aa82a0e6caa13bf03bd905b3b21b606cd Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Wed, 29 Jul 2026 12:12:21 +0100 Subject: [PATCH 2/4] ci: support cloud-property-sync dispatch and harden property docs workflow (DOC-1886) --- .github/workflows/update-property-docs.yaml | 83 +++++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/.github/workflows/update-property-docs.yaml b/.github/workflows/update-property-docs.yaml index 82cc434515..866508db6a 100644 --- a/.github/workflows/update-property-docs.yaml +++ b/.github/workflows/update-property-docs.yaml @@ -9,7 +9,14 @@ on: required: true type: string repository_dispatch: - types: [trigger-property-docs-generation] + types: [trigger-property-docs-generation, trigger-cloud-property-sync] + +# Serialize runs that target the same PR branch. Cloud-sync dispatches share a +# fixed branch, so repeat dispatches queue instead of racing. Release runs are +# keyed by the incoming tag. +concurrency: + group: ${{ github.workflow }}-${{ github.event.action == 'trigger-cloud-property-sync' && 'cloud-sync' || github.event.inputs.tag || github.event.client_payload.tag || github.ref }} + cancel-in-progress: false jobs: generate-property-docs: @@ -43,6 +50,18 @@ jobs: uses: actions/setup-node@v4 with: node-version: 20 + cache: npm + + - name: Cache pip + uses: actions/cache@v4 + with: + path: ~/.cache/pip + # Doc-tools installs its Python requirements at run time. The pinned + # doc-tools version in package-lock.json determines those requirements, + # so the lockfile hash is the closest stable cache key. + key: ${{ runner.os }}-pip-${{ hashFiles('package-lock.json') }} + restore-keys: | + ${{ runner.os }}-pip- - name: Install dependencies run: npm ci @@ -50,7 +69,17 @@ jobs: - name: Determine tag id: tag run: | - if [ -n "${{ github.event.inputs.tag }}" ]; then + if [ "${{ github.event.action }}" = "trigger-cloud-property-sync" ]; then + # Cloud-sync regenerates docs for the version already published in + # antora.yml, so resolve the tag from there instead of the payload. + TAG=$(grep 'latest-redpanda-tag:' antora.yml | awk '{print $2}' | tr -d "\"'") + if [ -z "$TAG" ]; then + echo "❌ Could not resolve latest-redpanda-tag from antora.yml" >&2 + exit 1 + fi + echo "Resolved tag from antora.yml: $TAG" + echo "tag=$TAG" >> $GITHUB_OUTPUT + elif [ -n "${{ github.event.inputs.tag }}" ]; then echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT elif [ -n "${{ github.event.client_payload.tag }}" ]; then echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT @@ -64,6 +93,16 @@ jobs: run: | set -euo pipefail TAG="${{ steps.tag.outputs.tag }}" + + if [ "${{ github.event.action }}" = "trigger-cloud-property-sync" ]; then + # Cloud-sync regenerates docs for the current tag on purpose. + # Doc-tools' same-tag path skips the diff phase and version bump, + # so bypassing the newer-tag guard is safe here. + echo "Cloud property sync event — regenerating docs for $TAG" + echo "is_newer=true" >> $GITHUB_OUTPUT + exit 0 + fi + CURRENT=$(grep 'latest-redpanda-tag:' antora.yml | awk '{print $2}' | tr -d "\"'") echo "📄 Current latest-redpanda-tag in antora.yml: $CURRENT" @@ -95,17 +134,49 @@ jobs: env: GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} + - name: Prepare pull request metadata + if: steps.version_check.outputs.is_newer == 'true' + id: pr_meta + env: + CLOUD_COMMIT_SHA: ${{ github.event.client_payload.commit_sha }} + run: | + set -euo pipefail + TAG="${{ steps.tag.outputs.tag }}" + + if [ "${{ github.event.action }}" = "trigger-cloud-property-sync" ]; then + # Use a fixed branch so repeat cloud-sync dispatches update one + # open PR instead of colliding with the release branch. + echo "branch=update-property-docs-cloud-sync" >> $GITHUB_OUTPUT + echo "labels=auto-docs,cloud-property-sync" >> $GITHUB_OUTPUT + { + echo "body<> $GITHUB_OUTPUT + else + echo "branch=update-property-docs-$TAG" >> $GITHUB_OUTPUT + echo "labels=auto-docs" >> $GITHUB_OUTPUT + { + echo "body<> $GITHUB_OUTPUT + fi + - name: Create pull request if: steps.version_check.outputs.is_newer == 'true' uses: peter-evans/create-pull-request@v6 with: token: ${{ env.ACTIONS_BOT_TOKEN }} commit-message: "auto-docs: Update property docs for ${{ steps.tag.outputs.tag }}" - branch: update-property-docs-${{ steps.tag.outputs.tag }} + branch: ${{ steps.pr_meta.outputs.branch }} title: "auto-docs: Update property docs for tag ${{ steps.tag.outputs.tag }}" - body: | - This PR auto-generates updated Redpanda property documentation for **${{ steps.tag.outputs.tag }}**. - labels: auto-docs + body: ${{ steps.pr_meta.outputs.body }} + labels: ${{ steps.pr_meta.outputs.labels }} - name: Skip notice if: steps.version_check.outputs.is_newer != 'true' From 9a9da44b8126eacc5ded43d4974707111100b4d2 Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Wed, 29 Jul 2026 19:51:41 +0100 Subject: [PATCH 3/4] ci: route dispatch payload values through env to prevent shell injection (CodeRabbit) --- .github/workflows/update-property-docs.yaml | 51 ++++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update-property-docs.yaml b/.github/workflows/update-property-docs.yaml index 866508db6a..735b97cfc6 100644 --- a/.github/workflows/update-property-docs.yaml +++ b/.github/workflows/update-property-docs.yaml @@ -68,8 +68,14 @@ jobs: - name: Determine tag id: tag + # Dispatch-controlled values are passed through env so the shell never + # re-parses them as script text (prevents template injection). + env: + EVENT_ACTION: ${{ github.event.action }} + INPUT_TAG: ${{ github.event.inputs.tag }} + PAYLOAD_TAG: ${{ github.event.client_payload.tag }} run: | - if [ "${{ github.event.action }}" = "trigger-cloud-property-sync" ]; then + if [ "$EVENT_ACTION" = "trigger-cloud-property-sync" ]; then # Cloud-sync regenerates docs for the version already published in # antora.yml, so resolve the tag from there instead of the payload. TAG=$(grep 'latest-redpanda-tag:' antora.yml | awk '{print $2}' | tr -d "\"'") @@ -78,23 +84,34 @@ jobs: exit 1 fi echo "Resolved tag from antora.yml: $TAG" - echo "tag=$TAG" >> $GITHUB_OUTPUT - elif [ -n "${{ github.event.inputs.tag }}" ]; then - echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT - elif [ -n "${{ github.event.client_payload.tag }}" ]; then - echo "tag=${{ github.event.client_payload.tag }}" >> $GITHUB_OUTPUT + elif [ -n "$INPUT_TAG" ]; then + TAG="$INPUT_TAG" + elif [ -n "$PAYLOAD_TAG" ]; then + TAG="$PAYLOAD_TAG" else echo "❌ No tag provided via input or dispatch payload" >&2 exit 1 fi + # Reject anything outside the release-tag charset (no control + # characters, newlines, whitespace, or shell metacharacters) before + # the value reaches GITHUB_OUTPUT and downstream steps. Bash =~ + # matches the whole string, so embedded newlines cannot sneak past. + if ! [[ "$TAG" =~ ^[A-Za-z0-9._-]+$ ]]; then + echo "❌ Invalid tag format: $TAG" >&2 + exit 1 + fi + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + - name: Check if tag is newer than antora.yml latest id: version_check + env: + EVENT_ACTION: ${{ github.event.action }} + TAG: ${{ steps.tag.outputs.tag }} run: | set -euo pipefail - TAG="${{ steps.tag.outputs.tag }}" - if [ "${{ github.event.action }}" = "trigger-cloud-property-sync" ]; then + if [ "$EVENT_ACTION" = "trigger-cloud-property-sync" ]; then # Cloud-sync regenerates docs for the current tag on purpose. # Doc-tools' same-tag path skips the diff phase and version bump, # so bypassing the newer-tag guard is safe here. @@ -125,25 +142,35 @@ jobs: if: steps.version_check.outputs.is_newer == 'true' run: | set -euo pipefail - echo "Running doc generation for: ${{ steps.tag.outputs.tag }}" + echo "Running doc generation for: $TAG" npx doc-tools generate property-docs \ - --tag "${{ steps.tag.outputs.tag }}" \ + --tag "$TAG" \ --generate-partials \ --cloud-support \ --overrides docs-data/property-overrides.json env: GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} + TAG: ${{ steps.tag.outputs.tag }} - name: Prepare pull request metadata if: steps.version_check.outputs.is_newer == 'true' id: pr_meta env: CLOUD_COMMIT_SHA: ${{ github.event.client_payload.commit_sha }} + EVENT_ACTION: ${{ github.event.action }} + TAG: ${{ steps.tag.outputs.tag }} run: | set -euo pipefail - TAG="${{ steps.tag.outputs.tag }}" - if [ "${{ github.event.action }}" = "trigger-cloud-property-sync" ]; then + # Only trust commit_sha if it looks like a commit hash. Bash =~ + # matches the whole string, so a newline-containing payload cannot + # terminate the BODY_EOF block and inject extra outputs. + if [ -n "$CLOUD_COMMIT_SHA" ] && ! [[ "$CLOUD_COMMIT_SHA" =~ ^[0-9a-fA-F]{7,40}$ ]]; then + echo "⚠️ Ignoring commit_sha from payload — not a valid commit hash" >&2 + CLOUD_COMMIT_SHA="" + fi + + if [ "$EVENT_ACTION" = "trigger-cloud-property-sync" ]; then # Use a fixed branch so repeat cloud-sync dispatches update one # open PR instead of colliding with the release branch. echo "branch=update-property-docs-cloud-sync" >> $GITHUB_OUTPUT From 99672f38acc4163a84cbdfda908c1fa0244120eb Mon Sep 17 00:00:00 2001 From: JakeSCahill Date: Tue, 4 Aug 2026 16:16:44 +0100 Subject: [PATCH 4/4] Apply review hardening to the property workflow - Serialize all runs under one concurrency group: cloud-sync and release runs regenerate the same partials, and the resolved tag is not known at concurrency-evaluation time, so a shared group is the only safe key. - Distinct PR title for cloud-sync runs so the PR list shows which trigger fired. - Quote every remaining $GITHUB_OUTPUT redirect. - Document that --regenerate-old-baseline applies only to manual --diff runs and is deliberately absent from this workflow. --- .github/workflows/update-property-docs.yaml | 35 ++++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/workflows/update-property-docs.yaml b/.github/workflows/update-property-docs.yaml index 735b97cfc6..822979784e 100644 --- a/.github/workflows/update-property-docs.yaml +++ b/.github/workflows/update-property-docs.yaml @@ -14,8 +14,12 @@ on: # Serialize runs that target the same PR branch. Cloud-sync dispatches share a # fixed branch, so repeat dispatches queue instead of racing. Release runs are # keyed by the incoming tag. +# One global group: cloud-sync and release runs regenerate the same +# partials, so running them concurrently produces two PRs that clobber each +# other on merge. The resolved tag isn't known at concurrency-evaluation time +# (cloud-sync reads it from antora.yml at runtime), so serialize everything. concurrency: - group: ${{ github.workflow }}-${{ github.event.action == 'trigger-cloud-property-sync' && 'cloud-sync' || github.event.inputs.tag || github.event.client_payload.tag || github.ref }} + group: ${{ github.workflow }} cancel-in-progress: false jobs: @@ -116,7 +120,7 @@ jobs: # Doc-tools' same-tag path skips the diff phase and version bump, # so bypassing the newer-tag guard is safe here. echo "Cloud property sync event — regenerating docs for $TAG" - echo "is_newer=true" >> $GITHUB_OUTPUT + echo "is_newer=true" >> "$GITHUB_OUTPUT" exit 0 fi @@ -132,10 +136,10 @@ jobs: # Compare semver using sort -V if [ "$(printf "%s\n%s" "$CUR_NUM" "$NEW_NUM" | sort -V | tail -n1)" = "$NEW_NUM" ] && [ "$CUR_NUM" != "$NEW_NUM" ]; then echo "$TAG is newer than $CURRENT" - echo "is_newer=true" >> $GITHUB_OUTPUT + echo "is_newer=true" >> "$GITHUB_OUTPUT" else echo "$TAG is not newer than $CURRENT — skipping doc generation" - echo "is_newer=false" >> $GITHUB_OUTPUT + echo "is_newer=false" >> "$GITHUB_OUTPUT" fi - name: Generate property docs @@ -152,6 +156,10 @@ jobs: GITHUB_TOKEN: ${{ env.ACTIONS_BOT_TOKEN }} TAG: ${{ steps.tag.outputs.tag }} + # Note: the --regenerate-old-baseline doc-tools flag mentioned in review + # discussions applies only to manual local --diff runs (it rebuilds a + # stale committed baseline). This workflow never passes --diff, so the + # flag is deliberately absent here. - name: Prepare pull request metadata if: steps.version_check.outputs.is_newer == 'true' id: pr_meta @@ -172,9 +180,11 @@ jobs: if [ "$EVENT_ACTION" = "trigger-cloud-property-sync" ]; then # Use a fixed branch so repeat cloud-sync dispatches update one - # open PR instead of colliding with the release branch. - echo "branch=update-property-docs-cloud-sync" >> $GITHUB_OUTPUT - echo "labels=auto-docs,cloud-property-sync" >> $GITHUB_OUTPUT + # open PR instead of colliding with the release branch. Distinct + # title so the PR list shows which trigger fired. + echo "branch=update-property-docs-cloud-sync" >> "$GITHUB_OUTPUT" + echo "labels=auto-docs,cloud-property-sync" >> "$GITHUB_OUTPUT" + echo "title=auto-docs: Sync cloud property availability ($TAG)" >> "$GITHUB_OUTPUT" { echo "body<> $GITHUB_OUTPUT + } >> "$GITHUB_OUTPUT" else - echo "branch=update-property-docs-$TAG" >> $GITHUB_OUTPUT - echo "labels=auto-docs" >> $GITHUB_OUTPUT + echo "branch=update-property-docs-$TAG" >> "$GITHUB_OUTPUT" + echo "labels=auto-docs" >> "$GITHUB_OUTPUT" + echo "title=auto-docs: Update property docs for tag $TAG" >> "$GITHUB_OUTPUT" { echo "body<> $GITHUB_OUTPUT + } >> "$GITHUB_OUTPUT" fi - name: Create pull request @@ -201,7 +212,7 @@ jobs: token: ${{ env.ACTIONS_BOT_TOKEN }} commit-message: "auto-docs: Update property docs for ${{ steps.tag.outputs.tag }}" branch: ${{ steps.pr_meta.outputs.branch }} - title: "auto-docs: Update property docs for tag ${{ steps.tag.outputs.tag }}" + title: ${{ steps.pr_meta.outputs.title }} body: ${{ steps.pr_meta.outputs.body }} labels: ${{ steps.pr_meta.outputs.labels }}