This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new fe19a27ce1c HDDS-11072. Publish user-facing configs to the website
(#6916)
fe19a27ce1c is described below
commit fe19a27ce1caba8381e4e893efd15d30c789c937
Author: Sarveksha Yeshavantha Raju
<[email protected]>
AuthorDate: Sun Feb 22 16:24:45 2026 +0530
HDDS-11072. Publish user-facing configs to the website (#6916)
---
.github/workflows/ci.yml | 12 ++
.github/workflows/generate-config-doc.yml | 58 +++++++
.github/workflows/update-ozone-site-config-doc.yml | 188 +++++++++++++++++++++
dev-support/ci/pr_body_config_doc.sh | 54 ++++++
dev-support/ci/xml_to_md.py | 158 +++++++++++++++++
5 files changed, 470 insertions(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 71af2b9824e..0d35d03c2ba 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -123,6 +123,18 @@ jobs:
with-coverage: ${{ fromJSON(needs.build-info.outputs.with-coverage) }}
secrets: inherit
+ generate-config-doc:
+ needs:
+ - build-info
+ - build
+ if: |
+ needs.build-info.outputs.needs-build == 'true' &&
+ (github.repository != 'apache/ozone' || github.event_name ==
'pull_request' ||
+ (github.event_name == 'push' && github.ref_name == 'master'))
+ uses: ./.github/workflows/generate-config-doc.yml
+ with:
+ sha: ${{ needs.build-info.outputs.sha }}
+
compile:
needs:
- build-info
diff --git a/.github/workflows/generate-config-doc.yml
b/.github/workflows/generate-config-doc.yml
new file mode 100644
index 00000000000..36452e497fd
--- /dev/null
+++ b/.github/workflows/generate-config-doc.yml
@@ -0,0 +1,58 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: generate-config-doc
+on:
+ workflow_call:
+ inputs:
+ sha:
+ type: string
+ required: true
+
+jobs:
+ generate:
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Checkout project
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ inputs.sha }}
+
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.x'
+
+ - name: Download ozone-bin artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: ozone-bin
+ path: ozone-bin
+
+ - name: Extract tarball
+ run: |
+ mkdir -p ozone-bin/extracted
+ tar -xzf ozone-bin/ozone-*.tar.gz -C ozone-bin/extracted
+
+ - name: Generate configuration documentation
+ run: |
+ python3 dev-support/ci/xml_to_md.py ozone-bin/extracted
Configurations.md
+
+ - name: Upload generated documentation
+ uses: actions/upload-artifact@v4
+ with:
+ name: config-documentation
+ path: Configurations.md
+ retention-days: 7
diff --git a/.github/workflows/update-ozone-site-config-doc.yml
b/.github/workflows/update-ozone-site-config-doc.yml
new file mode 100644
index 00000000000..9488a998383
--- /dev/null
+++ b/.github/workflows/update-ozone-site-config-doc.yml
@@ -0,0 +1,188 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: update-ozone-site-config-doc
+on:
+ workflow_run:
+ workflows: ["ci"]
+ types: [completed]
+ branches: [master]
+
+jobs:
+ update-ozone-site-config-doc:
+ if: |
+ github.event.workflow_run.conclusion == 'success' &&
+ github.repository == 'apache/ozone' &&
+ !startsWith(github.event.workflow_run.head_commit.message, '[Auto]')
+ runs-on: ubuntu-24.04
+ steps:
+ - name: Download generated documentation
+ uses: actions/download-artifact@v4
+ with:
+ name: config-documentation
+ path: .
+ run-id: ${{ github.event.workflow_run.id }}
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Check if ozone-site repository exists
+ id: check-site-repo
+ run: |
+ REPO_OWNER="${{ github.repository_owner }}"
+ if gh repo view "$REPO_OWNER/ozone-site" >/dev/null 2>&1; then
+ echo "exists=true" >> $GITHUB_OUTPUT
+ else
+ echo "exists=false" >> $GITHUB_OUTPUT
+ echo "ozone-site repository not found for $REPO_OWNER, skipping"
+ fi
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Checkout ozone-site repository
+ if: steps.check-site-repo.outputs.exists == 'true'
+ uses: actions/checkout@v4
+ with:
+ repository: ${{ github.repository_owner }}/ozone-site
+ ref: 'master'
+ path: ozone-site
+ token: ${{ secrets.OZONE_WEBSITE_BUILD }}
+
+ - name: Check if documentation changed
+ if: steps.check-site-repo.outputs.exists == 'true'
+ id: check-changes
+ run: |
+ cd ozone-site
+
+ # Find target directory
+ TARGET_DIR=$(ls -d docs/*-administrator-guide/*-configuration
2>/dev/null | head -1)
+
+ if [ -z "$TARGET_DIR" ]; then
+ echo "changed=false" >> $GITHUB_OUTPUT
+ echo "Target directory not found, skipping"
+ exit 0
+ fi
+
+ TARGET_FILE="$TARGET_DIR/05-appendix.md"
+ echo "Checking against $TARGET_FILE"
+
+ # Copy new file to target location
+ cp ../Configurations.md "$TARGET_FILE"
+
+ # Check if file changed using git status
+ if git status --porcelain "$TARGET_FILE" | grep -q .; then
+ echo "changed=true" >> $GITHUB_OUTPUT
+ echo "target_file=$TARGET_FILE" >> $GITHUB_OUTPUT
+ echo "Configurations.md has changed in ozone-site"
+ else
+ echo "changed=false" >> $GITHUB_OUTPUT
+ echo "No changes in ozone-site documentation"
+ fi
+
+ - name: Checkout ozone repository for script access
+ if: steps.check-changes.outputs.changed == 'true'
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.event.workflow_run.head_sha }}
+
+ - name: Extract JIRA ID from commit message
+ if: steps.check-changes.outputs.changed == 'true'
+ id: extract-jira
+ run: |
+ COMMIT_MSG=$(git log -1 --format='%s' ${{
github.event.workflow_run.head_sha }})
+ echo "Commit message: $COMMIT_MSG"
+
+ JIRA_ID=$(echo "$COMMIT_MSG" | grep -oE '(HDDS|OZONE)-[0-9]+' | head
-1 || echo "")
+
+ if [ -n "$JIRA_ID" ]; then
+ echo "jira_id=$JIRA_ID" >> $GITHUB_OUTPUT
+ echo "Using JIRA ID: $JIRA_ID"
+ else
+ echo "jira_id=" >> $GITHUB_OUTPUT
+ echo "No JIRA ID found"
+ fi
+
+ - name: Commit and push changes
+ if: steps.check-changes.outputs.changed == 'true'
+ run: |
+ cd ozone-site
+
+ git config user.name 'github-actions[bot]'
+ git config user.email 'github-actions[bot]@users.noreply.github.com'
+
+ # Use single branch name
+ BRANCH_NAME="automated-config-doc-update"
+
+ echo "Current branch: $(git branch --show-current)"
+ echo "Current commit: $(git rev-parse HEAD)"
+
+ # Create/reset branch at current commit
+ git checkout -B "$BRANCH_NAME"
+ echo "Created/reset branch $BRANCH_NAME at current commit"
+
+ TARGET_FILE="${{ steps.check-changes.outputs.target_file }}"
+ git add "$TARGET_FILE"
+
+ # Build commit message with JIRA ID if available
+ JIRA_ID="${{ steps.extract-jira.outputs.jira_id }}"
+ COMMIT_MSG="[Auto] Update configuration documentation from ozone ${{
github.event.workflow_run.head_sha }}"
+ if [ -n "$JIRA_ID" ]; then
+ COMMIT_MSG="$JIRA_ID. $COMMIT_MSG"
+ fi
+
+ git commit -m "$COMMIT_MSG"
+
+ echo "Pushing $BRANCH_NAME to origin"
+ git push -f origin "$BRANCH_NAME"
+
+ - name: Create or update Pull Request in ozone-site
+ if: steps.check-changes.outputs.changed == 'true' && github.repository
== 'apache/ozone'
+ env:
+ GH_TOKEN: ${{ secrets.OZONE_WEBSITE_BUILD }}
+ run: |
+ cd ozone-site
+
+ BRANCH_NAME="automated-config-doc-update"
+ REPO="${{ github.repository }}"
+ JIRA_ID="${{ steps.extract-jira.outputs.jira_id }}"
+
+ # Generate PR body
+ cd ..
+ dev-support/ci/pr_body_config_doc.sh \
+ "$REPO" \
+ "${{ github.workflow }}" \
+ "${{ github.run_id }}" \
+ "${{ github.event.workflow_run.head_branch }}" \
+ "${{ github.event.workflow_run.head_sha }}" \
+ "$JIRA_ID" > ozone-site/pr_body.txt
+
+ cd ozone-site
+
+ # Check if PR already exists
+ EXISTING_PR=$(gh pr list --repo "${{ github.repository_owner
}}/ozone-site" \
+ --head "$BRANCH_NAME" --base master --json number --jq
'.[0].number' || echo "")
+
+ if [ -n "$EXISTING_PR" ]; then
+ echo "Updating existing PR #$EXISTING_PR"
+ gh pr edit "$EXISTING_PR" --repo "${{ github.repository_owner
}}/ozone-site" --body-file pr_body.txt
+ else
+ echo "Creating new PR"
+ TITLE="[Auto] Update configuration documentation"
+ if [ -n "$JIRA_ID" ]; then
+ TITLE="$JIRA_ID. $TITLE"
+ fi
+ gh pr create --repo "${{ github.repository_owner }}/ozone-site" \
+ --base master --head "$BRANCH_NAME" \
+ --title "$TITLE" \
+ --body-file pr_body.txt
+ fi
diff --git a/dev-support/ci/pr_body_config_doc.sh
b/dev-support/ci/pr_body_config_doc.sh
new file mode 100755
index 00000000000..49ca4e67fb2
--- /dev/null
+++ b/dev-support/ci/pr_body_config_doc.sh
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generate PR body for configuration documentation update
+# Usage: pr_body_config_doc.sh <repo> <workflow_name> <workflow_run_id>
<branch_name> <commit_sha> <jira_id>
+
+set -euo pipefail
+
+REPO="${1}"
+WORKFLOW_NAME="${2}"
+WORKFLOW_RUN_ID="${3}"
+BRANCH_NAME="${4}"
+COMMIT_SHA="${5}"
+JIRA_ID="${6:-}"
+
+cat <<EOF
+## What changes were proposed in this pull request?
+
+This is an automated pull request triggered by the
[$WORKFLOW_NAME](https://github.com/${REPO}/actions/runs/${WORKFLOW_RUN_ID})
workflow run from
[\`${COMMIT_SHA}\`](https://github.com/${REPO}/commit/${COMMIT_SHA}) on
[\`${BRANCH_NAME}\`](https://github.com/${REPO}/tree/${BRANCH_NAME}).
+
+The configuration documentation has been automatically regenerated from the
latest \`ozone-default.xml\` files.
+
+EOF
+
+if [ -n "${JIRA_ID}" ]; then
+ cat <<EOF
+## What is the link to the Apache JIRA?
+
+[${JIRA_ID}](https://issues.apache.org/jira/browse/${JIRA_ID})
+
+EOF
+fi
+
+cat <<EOF
+## How was this patch tested?
+
+This is an auto-generated documentation update. Reviewers should verify:
+- The markdown file is properly formatted
+- Configuration keys are correctly extracted
+- No unexpected changes in existing configurations
+EOF
diff --git a/dev-support/ci/xml_to_md.py b/dev-support/ci/xml_to_md.py
new file mode 100644
index 00000000000..e3c6ef14ebc
--- /dev/null
+++ b/dev-support/ci/xml_to_md.py
@@ -0,0 +1,158 @@
+#!/usr/bin/python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Python file to convert XML properties into Markdown
+import os
+import re
+import zipfile
+import xml.etree.ElementTree as ET
+from collections import namedtuple
+from pathlib import Path
+import sys
+
+Property = namedtuple('Property', ['name', 'value', 'tag', 'description'])
+
+def extract_xml_from_jar(jar_path, xml_filename):
+ xml_files = []
+ with zipfile.ZipFile(jar_path, 'r') as jar:
+ for file_info in jar.infolist():
+ if file_info.filename.endswith(xml_filename):
+ with jar.open(file_info.filename) as xml_file:
+ xml_files.append(xml_file.read())
+ return xml_files
+
+def wrap_config_keys_in_description(description, config_keys):
+ words = description.split()
+ wrapped_words = []
+
+ for word in words:
+ # Strip punctuation to check if the word is a config key
+ stripped_word = word.strip('.,;:!?()[]{}')
+ if stripped_word in config_keys:
+ # Preserve punctuation around the wrapped key
+ prefix = word[:len(word) - len(word.lstrip('.,;:!?()[]{}'))]
+ suffix = word[len(stripped_word) + len(prefix):]
+ wrapped_words.append(f'{prefix}`{stripped_word}`{suffix}')
+ else:
+ wrapped_words.append(word)
+
+ return ' '.join(wrapped_words)
+
+def parse_xml_file(xml_content, properties):
+ root = ET.fromstring(xml_content)
+ for prop in root.findall('property'):
+ name = prop.findtext('name')
+ if not name:
+ raise ValueError("Property 'name' is required but missing in XML.")
+ description = prop.findtext('description', '')
+ if not description:
+ raise ValueError(f"Property '{name}' is missing a description.")
+ tag = prop.findtext('tag', '')
+
+ properties[name] = Property(
+ name=name,
+ value=prop.findtext('value', ''),
+ tag=tag,
+ description=' '.join(description.split()).strip()
+ )
+ return properties
+
+def format_properties(properties):
+ config_keys = set(properties.keys())
+ formatted_properties = {}
+
+ for name, prop in properties.items():
+ if prop.tag:
+ formatted_tag = ', '.join(f'`{t.strip()}`' for t in prop.tag.split(','))
+ else:
+ formatted_tag = ''
+
+ # Wrap config keys in description now that we have all configs
+ formatted_description = wrap_config_keys_in_description(prop.description,
config_keys)
+
+ formatted_properties[name] = Property(
+ name=prop.name,
+ value=prop.value,
+ tag=formatted_tag,
+ description=formatted_description
+ )
+ return formatted_properties
+
+def generate_markdown(properties):
+ markdown = """---
+sidebar_label: Appendix
+---
+
+# Configuration Key Appendix
+
+This page provides a comprehensive overview of the configuration keys
available in Ozone.
+
+| Name | Default Value | Tags | Description |
+|:-----|:--------------|:-----|:------------|
+"""
+
+ for prop in sorted(properties.values(), key=lambda p: p.name):
+ # Escape pipe characters in description to prevent breaking the table
+ description = prop.description.replace('|', '\\|')
+ value = prop.value if prop.value else ''
+
+ markdown += f"| `{prop.name}` | {value} | {prop.tag} | {description} |\n"
+
+ return markdown
+
+def main():
+ if len(sys.argv) < 2 or len(sys.argv) > 3:
+ print("Usage: python3 xml_to_md.py <base_path> [<output_path>]")
+ sys.exit(1)
+
+ base_path = sys.argv[1]
+ output_path = sys.argv[2] if len(sys.argv) == 3 else None
+
+ # Find ozone SNAPSHOT directory dynamically using regex
+ snapshot_dir = next(
+ (os.path.join(base_path, d) for d in os.listdir(base_path) if
re.match(r'ozone-[\d.]+\d-SNAPSHOT', d)),
+ None
+ )
+
+ if not snapshot_dir:
+ raise ValueError("SNAPSHOT directory not found in the specified base
path.")
+
+ extract_path = os.path.join(snapshot_dir, 'share', 'ozone', 'lib')
+ xml_filename = 'ozone-default.xml'
+
+ property_map = {}
+ for file_name in os.listdir(extract_path):
+ if file_name.startswith('hdds-common-') and file_name.endswith('.jar'):
+ jar_path = os.path.join(extract_path, file_name)
+ xml_contents = extract_xml_from_jar(jar_path, xml_filename)
+ for xml_content in xml_contents:
+ parse_xml_file(xml_content, property_map)
+
+ formatted_properties = format_properties(property_map)
+ markdown_content = generate_markdown(formatted_properties)
+
+ if output_path:
+ output_path = Path(output_path)
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+ with output_path.open('w', encoding='utf-8') as file:
+ file.write(markdown_content)
+ else:
+ print(markdown_content)
+
+if __name__ == '__main__':
+ main()
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]