mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-06-13 14:11:14 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d488a7e387 |
@@ -18,8 +18,8 @@ Please add a detailed description of how to review this PR.
|
||||
|
||||
<summary><b>Community Checklist</b></summary>
|
||||
|
||||
- [ ] This feature/issue is listed in the [open issues](https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or roadmap.prowler.com
|
||||
- [ ] Is it assigned to me, if not, request it via the [open issues](https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or [Prowler Community Slack](goto.prowler.com/slack)
|
||||
- [ ] This feature/issue is listed in [here](https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or roadmap.prowler.com
|
||||
- [ ] Is it assigned to me, if not, request it via the issue/feature in [here](https://github.com/prowler-cloud/prowler/issues?q=sort%3Aupdated-desc+is%3Aissue+is%3Aopen) or [Prowler Community Slack](goto.prowler.com/slack)
|
||||
|
||||
</details>
|
||||
|
||||
@@ -28,7 +28,7 @@ Please add a detailed description of how to review this PR.
|
||||
- [ ] Review if code is being documented following this specification https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings
|
||||
- [ ] Review if backport is needed.
|
||||
- [ ] Review if is needed to change the [Readme.md](https://github.com/prowler-cloud/prowler/blob/master/README.md)
|
||||
- [ ] Ensure a changelog fragment is added under [prowler/changelog.d/](https://github.com/prowler-cloud/prowler/tree/master/prowler/changelog.d), if applicable.
|
||||
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/prowler/CHANGELOG.md), if applicable.
|
||||
|
||||
#### SDK/CLI
|
||||
- Are there new checks included in this PR? Yes / No
|
||||
@@ -40,7 +40,7 @@ Please add a detailed description of how to review this PR.
|
||||
- [ ] Screenshots/Video of the functionality flow (if applicable) - Mobile (X < 640px)
|
||||
- [ ] Screenshots/Video of the functionality flow (if applicable) - Table (640px > X < 1024px)
|
||||
- [ ] Screenshots/Video of the functionality flow (if applicable) - Desktop (X > 1024px)
|
||||
- [ ] Ensure a changelog fragment is added under [ui/changelog.d/](https://github.com/prowler-cloud/prowler/tree/master/ui/changelog.d), if applicable.
|
||||
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/ui/CHANGELOG.md), if applicable.
|
||||
|
||||
#### API
|
||||
- [ ] All issue/task requirements work as expected on the API
|
||||
@@ -50,7 +50,7 @@ Please add a detailed description of how to review this PR.
|
||||
- [ ] Any other relevant evidence of the implementation (if applicable)
|
||||
- [ ] Verify if API specs need to be regenerated.
|
||||
- [ ] Check if version updates are required (e.g., specs, uv, etc.).
|
||||
- [ ] Ensure a changelog fragment is added under [api/changelog.d/](https://github.com/prowler-cloud/prowler/tree/master/api/changelog.d), if applicable.
|
||||
- [ ] Ensure new entries are added to [CHANGELOG.md](https://github.com/prowler-cloud/prowler/blob/master/api/CHANGELOG.md), if applicable.
|
||||
|
||||
### License
|
||||
|
||||
|
||||
@@ -1,150 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rename changelog fragments to their PR number before running towncrier.
|
||||
|
||||
For every <slug>.<type>.md in <component_dir>/changelog.d/, find the commit that
|
||||
added it, resolve its PR via the GitHub API (falling back to the squash-commit
|
||||
subject), and `git mv` it to <PR>.<type>.md so towncrier renders the PR link.
|
||||
Unresolvable fragments become +<slug>.<type>.md orphans (rendered without link).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
FRAGMENT_RE = re.compile(
|
||||
r"^(?P<slug>[A-Za-z0-9][A-Za-z0-9._-]*?)"
|
||||
r"\.(?P<type>added|changed|deprecated|removed|fixed|security)"
|
||||
r"(?:\.(?P<counter>[0-9]+))?\.md$"
|
||||
)
|
||||
SUBJECT_PR_RE = re.compile(r" \(#([0-9]+)\)$")
|
||||
IGNORED_FILES = {".gitkeep", "README.md"}
|
||||
API_TIMEOUT_SECONDS = 10
|
||||
|
||||
|
||||
def git(*args: str) -> str:
|
||||
result = subprocess.run(["git", *args], check=True, capture_output=True, text=True)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def find_adding_commit(path: str) -> str | None:
|
||||
"""Find the commit that added a file, following renames.
|
||||
|
||||
Falls back to a plain (no --follow) lookup: rename detection can lose the
|
||||
add event for degenerate content (e.g. files identical to many others).
|
||||
"""
|
||||
sha = git("log", "--follow", "--diff-filter=A", "--format=%H", "-1", "--", path)
|
||||
if not sha:
|
||||
sha = git("log", "--diff-filter=A", "--format=%H", "-1", "--", path)
|
||||
return sha or None
|
||||
|
||||
|
||||
def pr_from_api(repo: str, sha: str) -> int | None:
|
||||
"""Resolve the PR associated with a commit via the GitHub API.
|
||||
|
||||
Returns None on any network/API failure so the caller can fall back to
|
||||
parsing the squash-commit subject.
|
||||
"""
|
||||
url = f"https://api.github.com/repos/{repo}/commits/{sha}/pulls"
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"User-Agent": "prowler-changelog-attribution",
|
||||
}
|
||||
token = os.environ.get("GITHUB_TOKEN")
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
request = urllib.request.Request(url, headers=headers)
|
||||
try:
|
||||
with urllib.request.urlopen(request, timeout=API_TIMEOUT_SECONDS) as response:
|
||||
pulls = json.load(response)
|
||||
except (urllib.error.URLError, TimeoutError, json.JSONDecodeError):
|
||||
return None
|
||||
if isinstance(pulls, list) and pulls:
|
||||
return pulls[0].get("number")
|
||||
return None
|
||||
|
||||
|
||||
def pr_from_subject(sha: str) -> int | None:
|
||||
subject = git("log", "-1", "--format=%s", sha)
|
||||
match = SUBJECT_PR_RE.search(subject)
|
||||
return int(match.group(1)) if match else None
|
||||
|
||||
|
||||
def unique_destination(directory: str, base_name: str, fragment_type: str) -> str:
|
||||
"""Return a non-colliding fragment path, appending a numeric counter if needed."""
|
||||
candidate = os.path.join(directory, f"{base_name}.{fragment_type}.md")
|
||||
counter = 0
|
||||
while os.path.exists(candidate):
|
||||
counter += 1
|
||||
candidate = os.path.join(directory, f"{base_name}.{fragment_type}.{counter}.md")
|
||||
return candidate
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("component_dir", help="Component directory, e.g. prowler")
|
||||
parser.add_argument("--repo", default="prowler-cloud/prowler")
|
||||
parser.add_argument(
|
||||
"--no-api",
|
||||
action="store_true",
|
||||
help="Skip the GitHub API and resolve PRs from commit subjects only",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
fragments_dir = os.path.join(args.component_dir, "changelog.d")
|
||||
if not os.path.isdir(fragments_dir):
|
||||
print(f"::error::Fragments directory not found: {fragments_dir}")
|
||||
return 1
|
||||
|
||||
malformed = []
|
||||
for name in sorted(os.listdir(fragments_dir)):
|
||||
if name in IGNORED_FILES or name.startswith("+"):
|
||||
continue
|
||||
match = FRAGMENT_RE.match(name)
|
||||
if not match:
|
||||
malformed.append(name)
|
||||
continue
|
||||
slug, fragment_type = match.group("slug"), match.group("type")
|
||||
if slug.isdigit():
|
||||
continue
|
||||
|
||||
path = os.path.join(fragments_dir, name)
|
||||
sha = find_adding_commit(path)
|
||||
pr_number = None
|
||||
if sha:
|
||||
if not args.no_api:
|
||||
pr_number = pr_from_api(args.repo, sha)
|
||||
if pr_number is None:
|
||||
pr_number = pr_from_subject(sha)
|
||||
|
||||
if pr_number is not None:
|
||||
destination = unique_destination(
|
||||
fragments_dir, str(pr_number), fragment_type
|
||||
)
|
||||
else:
|
||||
destination = unique_destination(fragments_dir, f"+{slug}", fragment_type)
|
||||
print(
|
||||
f"::warning::Could not resolve a PR for {path}; renamed to "
|
||||
f"{os.path.basename(destination)} (entry will render without a PR link)"
|
||||
)
|
||||
git("mv", path, destination)
|
||||
print(f"{path} -> {destination}")
|
||||
|
||||
if malformed:
|
||||
for name in malformed:
|
||||
print(
|
||||
f"::error::Malformed fragment filename in {fragments_dir}: {name} "
|
||||
"(expected <slug>.<type>.md with type one of added|changed|"
|
||||
"deprecated|removed|fixed|security)"
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -1,13 +0,0 @@
|
||||
{% for section, _ in sections.items() %}
|
||||
{% for category, val in definitions.items() if category in sections[section] %}
|
||||
### {{ definitions[category]['name'] }}
|
||||
|
||||
{% for text, values in sections[section][category].items() %}
|
||||
- {{ text }}{% if values %} {{ values|join(', ') }}{% endif %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
---
|
||||
{{ "\n" }}
|
||||
@@ -62,7 +62,6 @@ jobs:
|
||||
api/docs/**
|
||||
api/README.md
|
||||
api/CHANGELOG.md
|
||||
api/changelog.d/**
|
||||
api/AGENTS.md
|
||||
|
||||
- name: Setup Python with uv
|
||||
|
||||
@@ -215,7 +215,7 @@ jobs:
|
||||
|
||||
- name: Install regctl
|
||||
if: always()
|
||||
uses: regclient/actions/regctl-installer@da9319db8e44e8b062b3a147e1dfb2f574d41a03 # main
|
||||
uses: regclient/actions/regctl-installer@14f9d37db17b5dc41fefd1ffdd1af4b9e2490560 # main
|
||||
|
||||
- name: Cleanup intermediate architecture tags
|
||||
if: always()
|
||||
|
||||
@@ -111,7 +111,6 @@ jobs:
|
||||
api/docs/**
|
||||
api/README.md
|
||||
api/CHANGELOG.md
|
||||
api/changelog.d/**
|
||||
api/AGENTS.md
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
|
||||
@@ -84,7 +84,6 @@ jobs:
|
||||
api/docs/**
|
||||
api/README.md
|
||||
api/CHANGELOG.md
|
||||
api/changelog.d/**
|
||||
api/AGENTS.md
|
||||
|
||||
- name: Setup Python with uv
|
||||
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:17@sha256:2cd82735a36356842d5eb1ef80db3ae8f1154172f0f653db48fde079b2a0b7f7
|
||||
image: postgres:17@sha256:2203e6282d9e7de7c24d7da234e2a744fb325df366a3fd8ed940e8abbee39527
|
||||
env:
|
||||
POSTGRES_HOST: ${{ env.POSTGRES_HOST }}
|
||||
POSTGRES_PORT: ${{ env.POSTGRES_PORT }}
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
valkey:
|
||||
image: valkey/valkey:7-alpine3.19
|
||||
image: valkey/valkey:7-alpine3.19@sha256:4054fe7fc607b9326ac7c4691ed26e9670d2ff17a9fb28c2577adecf928acbcc
|
||||
env:
|
||||
VALKEY_HOST: ${{ env.VALKEY_HOST }}
|
||||
VALKEY_PORT: ${{ env.VALKEY_PORT }}
|
||||
@@ -111,7 +111,6 @@ jobs:
|
||||
api/docs/**
|
||||
api/README.md
|
||||
api/CHANGELOG.md
|
||||
api/changelog.d/**
|
||||
api/AGENTS.md
|
||||
|
||||
- name: Setup Python with uv
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
name: 'Tools: Compile Changelogs'
|
||||
|
||||
run-name: 'Compile changelogs for Prowler ${{ inputs.prowler_version }}'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
prowler_version:
|
||||
description: 'Prowler version being released (e.g., 5.31.0)'
|
||||
required: true
|
||||
type: string
|
||||
target_branch:
|
||||
description: 'Branch to compile on (master for minor releases, v5.X for patches)'
|
||||
required: true
|
||||
type: string
|
||||
sdk_version:
|
||||
description: 'SDK version override (empty = auto-derive from prowler/CHANGELOG.md + pending fragment types; "skip" = hold this component back)'
|
||||
required: false
|
||||
type: string
|
||||
api_version:
|
||||
description: 'API version override (empty = auto-derive; "skip" = hold back)'
|
||||
required: false
|
||||
type: string
|
||||
ui_version:
|
||||
description: 'UI version override (empty = auto-derive; "skip" = hold back)'
|
||||
required: false
|
||||
type: string
|
||||
mcp_version:
|
||||
description: 'MCP Server version override (empty = auto-derive; "skip" = hold back)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ inputs.prowler_version }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
PROWLER_VERSION: ${{ inputs.prowler_version }}
|
||||
TARGET_BRANCH: ${{ inputs.target_branch }}
|
||||
SDK_VERSION: ${{ inputs.sdk_version }}
|
||||
API_VERSION: ${{ inputs.api_version }}
|
||||
UI_VERSION: ${{ inputs.ui_version }}
|
||||
MCP_VERSION: ${{ inputs.mcp_version }}
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
compile-changelogs:
|
||||
if: github.event_name == 'workflow_dispatch' && github.repository == 'prowler-cloud/prowler'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.target_branch }}
|
||||
fetch-depth: 0 # PR attribution resolves each fragment's adding commit from history
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install towncrier
|
||||
run: pip install --no-cache-dir towncrier==25.8.0
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --global user.name 'prowler-bot'
|
||||
git config --global user.email '179230569+prowler-bot@users.noreply.github.com'
|
||||
|
||||
- name: Validate version inputs
|
||||
run: |
|
||||
if [[ ! "$PROWLER_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Invalid prowler_version syntax: '$PROWLER_VERSION' (must be N.N.N)"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$TARGET_BRANCH" != "master" ] && [[ ! "$TARGET_BRANCH" =~ ^v[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Invalid target_branch syntax: '$TARGET_BRANCH' (must be 'master' or vN.N, e.g. v5.31)"
|
||||
exit 1
|
||||
fi
|
||||
for pair in "sdk_version:$SDK_VERSION" "api_version:$API_VERSION" "ui_version:$UI_VERSION" "mcp_version:$MCP_VERSION"; do
|
||||
input_name="${pair%%:*}"
|
||||
input_value="${pair#*:}"
|
||||
if [ -n "$input_value" ] && [ "$input_value" != "skip" ] && [[ ! "$input_value" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::Invalid $input_name syntax: '$input_value' (must be N.N.N, empty for auto-derivation, or 'skip')"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Compile changelogs
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
component_input() {
|
||||
case "$1" in
|
||||
prowler) echo "$SDK_VERSION" ;;
|
||||
api) echo "$API_VERSION" ;;
|
||||
ui) echo "$UI_VERSION" ;;
|
||||
mcp_server) echo "$MCP_VERSION" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
pending_fragments() {
|
||||
find "$1/changelog.d" -maxdepth 1 -type f ! -name '.gitkeep' ! -name 'README.md' | sort
|
||||
}
|
||||
|
||||
# The component's last released version is the first stamped heading
|
||||
# of its CHANGELOG.md, the same source prepare-release.yml greps.
|
||||
latest_released_version() {
|
||||
grep -m1 -E '^## \[v?[0-9]+\.[0-9]+\.[0-9]+\]' "$1/CHANGELOG.md" | sed -E 's/^## \[v?([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/'
|
||||
}
|
||||
|
||||
# Resolve every component's effective version before compiling
|
||||
# anything, so a wrong input cannot leave the tree half-compiled.
|
||||
# Empty input = auto-derive (latest released version + semver bump
|
||||
# from the pending fragment types). 'skip' = hold the component back.
|
||||
errors=0
|
||||
compiling=""
|
||||
for component in prowler api ui mcp_server; do
|
||||
input=$(component_input "$component")
|
||||
fragments=$(pending_fragments "$component")
|
||||
|
||||
if [ "$input" = "skip" ]; then
|
||||
if [ -n "$fragments" ]; then
|
||||
echo "::warning::${component}: held back by request; these pending fragments stay for a future release:"
|
||||
echo "$fragments"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
if [ -n "$input" ] && [ -z "$fragments" ]; then
|
||||
echo "::error::${component}: version input '$input' provided but ${component}/changelog.d/ has no pending fragments (wrong input?)"
|
||||
errors=1
|
||||
continue
|
||||
fi
|
||||
if [ -z "$fragments" ]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
if [ -n "$input" ]; then
|
||||
effective="$input"
|
||||
mode="explicit"
|
||||
else
|
||||
if echo "$fragments" | grep -qE '\.removed(\.[0-9]+)?\.md$'; then
|
||||
echo "::error::${component}: pending 'removed' fragments imply a major bump (breaking change); provide its version input explicitly"
|
||||
errors=1
|
||||
continue
|
||||
fi
|
||||
current=$(latest_released_version "$component")
|
||||
if [ -z "$current" ]; then
|
||||
echo "::error::${component}: could not read the latest released version from ${component}/CHANGELOG.md; provide its version input explicitly"
|
||||
errors=1
|
||||
continue
|
||||
fi
|
||||
IFS=. read -r major minor patch <<< "$current"
|
||||
if echo "$fragments" | grep -qE '\.(added|changed|deprecated)(\.[0-9]+)?\.md$'; then
|
||||
effective="${major}.$((minor + 1)).0"
|
||||
else
|
||||
effective="${major}.${minor}.$((patch + 1))"
|
||||
fi
|
||||
mode="auto"
|
||||
echo "::notice::${component}: version auto-derived ${current} -> ${effective} from the pending fragment types"
|
||||
fi
|
||||
|
||||
# Without the marker the build would insert the new block above the
|
||||
# file header instead of below it.
|
||||
if ! grep -q '^<!-- changelog: release notes start -->$' "$component/CHANGELOG.md"; then
|
||||
echo "::error::${component}/CHANGELOG.md is missing the '<!-- changelog: release notes start -->' marker; restore it after the intro line before compiling"
|
||||
errors=1
|
||||
continue
|
||||
fi
|
||||
# A hand-written UNRELEASED block means someone followed the old
|
||||
# convention; its entries would be left out of the compiled block
|
||||
# and out of the release notes extraction.
|
||||
if grep -q '(Prowler UNRELEASED)' "$component/CHANGELOG.md"; then
|
||||
echo "::error::${component}/CHANGELOG.md contains a hand-written '(Prowler UNRELEASED)' block; convert its entries to fragments in ${component}/changelog.d/ and delete the block before compiling"
|
||||
errors=1
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "${effective} ${mode}" > "${RUNNER_TEMP}/version-${component}.txt"
|
||||
compiling="${compiling}${component} "
|
||||
done
|
||||
if [ "$errors" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
if [ -z "$compiling" ]; then
|
||||
echo "::error::Nothing to compile: no component has pending fragments to release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
body_file="${RUNNER_TEMP}/compile-changelogs-pr-body.md"
|
||||
{
|
||||
echo "### Description"
|
||||
echo ""
|
||||
echo "Compiles the pending changelog fragments into the per-component \`CHANGELOG.md\` files for Prowler v${PROWLER_VERSION}, replacing the manual stamping PR."
|
||||
echo ""
|
||||
echo "| Component | Version | Fragments consumed |"
|
||||
echo "|---|---|---|"
|
||||
} > "$body_file"
|
||||
|
||||
compiled_components=""
|
||||
for component in prowler api ui mcp_server; do
|
||||
if [ ! -f "${RUNNER_TEMP}/version-${component}.txt" ]; then
|
||||
echo "Skipping ${component} (no pending fragments or held back)"
|
||||
echo "| \`${component}\` | - | 0 |" >> "$body_file"
|
||||
continue
|
||||
fi
|
||||
read -r version mode < "${RUNNER_TEMP}/version-${component}.txt"
|
||||
version_label="$version"
|
||||
if [ "$mode" = "auto" ]; then
|
||||
version_label="${version} (auto)"
|
||||
fi
|
||||
|
||||
count=$(pending_fragments "$component" | wc -l | tr -d ' ')
|
||||
echo "Compiling ${component} ${version} (${count} fragments, ${mode} version)..."
|
||||
|
||||
# Captured before attribution renames them: these original paths are
|
||||
# what the forward-sync deletes on master (backports copy fragments
|
||||
# verbatim, so filenames match across branches).
|
||||
pending_fragments "$component" > "${RUNNER_TEMP}/consumed-${component}.txt"
|
||||
pre_lines=$(wc -l < "$component/CHANGELOG.md")
|
||||
|
||||
# Attribution must run before the build: towncrier renders the
|
||||
# first dotted segment of each filename as the PR number.
|
||||
python .github/scripts/changelog_attribution.py "$component"
|
||||
towncrier build --config "$component/towncrier.toml" --version "$version" --name "Prowler v${PROWLER_VERSION}" --yes
|
||||
|
||||
# The build only inserts lines right after the marker, so the new
|
||||
# stamped block is exactly the added lines following it. Captured
|
||||
# for the forward-sync to master.
|
||||
post_lines=$(wc -l < "$component/CHANGELOG.md")
|
||||
delta=$((post_lines - pre_lines))
|
||||
marker_line=$(grep -n -m1 '^<!-- changelog: release notes start -->$' "$component/CHANGELOG.md" | cut -d: -f1)
|
||||
sed -n "$((marker_line + 1)),$((marker_line + delta))p" "$component/CHANGELOG.md" > "${RUNNER_TEMP}/block-${component}.md"
|
||||
|
||||
compiled_components="${compiled_components}${component} "
|
||||
echo "| \`${component}\` | ${version_label} | ${count} |" >> "$body_file"
|
||||
done
|
||||
echo "COMPILED_COMPONENTS=${compiled_components}" >> "$GITHUB_ENV"
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "Review that no pending fragment was dropped (the diff must delete every consumed fragment) and that each new version block is correct, then squash-merge."
|
||||
echo ""
|
||||
echo "### License"
|
||||
echo ""
|
||||
echo "By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license."
|
||||
} >> "$body_file"
|
||||
|
||||
echo "PR_BODY_FILE=${body_file}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Create compile PR
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
commit-message: 'chore(changelog): v${{ env.PROWLER_VERSION }}'
|
||||
branch: compile-changelogs-${{ env.PROWLER_VERSION }}
|
||||
base: ${{ env.TARGET_BRANCH }}
|
||||
title: 'chore(changelog): v${{ env.PROWLER_VERSION }}'
|
||||
body-path: ${{ env.PR_BODY_FILE }}
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
labels: |
|
||||
no-changelog
|
||||
|
||||
# Patch compiles (target_branch = v5.X) leave master holding the consumed
|
||||
# fragments and missing the new version block. This applies the equivalent
|
||||
# change to master: insert the same stamped blocks under the marker and
|
||||
# delete the consumed fragments, so the next minor compile cannot
|
||||
# re-release entries that already shipped in the patch.
|
||||
- name: Apply forward-sync to master
|
||||
if: env.TARGET_BRANCH != 'master'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
git checkout -B master origin/master
|
||||
|
||||
sync_body="${RUNNER_TEMP}/forward-sync-pr-body.md"
|
||||
{
|
||||
echo "### Description"
|
||||
echo ""
|
||||
echo "Forward-syncs the v${PROWLER_VERSION} compiled changelogs from \`${TARGET_BRANCH}\` to \`master\`: inserts the same stamped version blocks under the insertion marker and deletes the consumed fragments, so the next minor compile cannot re-release entries that already shipped in this patch. Opened automatically by the same run that opened the compile PR; review and squash-merge after it."
|
||||
echo ""
|
||||
echo "| Component | Fragments deleted on master | Skipped (only on ${TARGET_BRANCH}) |"
|
||||
echo "|---|---|---|"
|
||||
} > "$sync_body"
|
||||
|
||||
for component in $COMPILED_COMPONENTS; do
|
||||
block_file="${RUNNER_TEMP}/block-${component}.md"
|
||||
consumed_file="${RUNNER_TEMP}/consumed-${component}.txt"
|
||||
|
||||
if ! grep -qm1 '^<!-- changelog: release notes start -->$' "$component/CHANGELOG.md"; then
|
||||
echo "::error::${component}/CHANGELOG.md on master is missing the insertion marker; cannot forward-sync"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
deleted=0
|
||||
skipped=0
|
||||
while IFS= read -r fragment; do
|
||||
if [ -z "$fragment" ]; then
|
||||
continue
|
||||
fi
|
||||
if [ -f "$fragment" ]; then
|
||||
git rm -q "$fragment"
|
||||
deleted=$((deleted + 1))
|
||||
else
|
||||
echo "::notice::${fragment} does not exist on master (change landed only on ${TARGET_BRANCH}); skipping its deletion"
|
||||
skipped=$((skipped + 1))
|
||||
fi
|
||||
done < "$consumed_file"
|
||||
|
||||
marker_line=$(grep -n -m1 '^<!-- changelog: release notes start -->$' "$component/CHANGELOG.md" | cut -d: -f1)
|
||||
{
|
||||
head -n "$marker_line" "$component/CHANGELOG.md"
|
||||
cat "$block_file"
|
||||
tail -n +"$((marker_line + 1))" "$component/CHANGELOG.md"
|
||||
} > "${RUNNER_TEMP}/changelog.tmp"
|
||||
mv "${RUNNER_TEMP}/changelog.tmp" "$component/CHANGELOG.md"
|
||||
|
||||
echo "| \`${component}\` | ${deleted} | ${skipped} |" >> "$sync_body"
|
||||
done
|
||||
|
||||
{
|
||||
echo ""
|
||||
echo "### License"
|
||||
echo ""
|
||||
echo "By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license."
|
||||
} >> "$sync_body"
|
||||
|
||||
echo "SYNC_BODY_FILE=${sync_body}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Create forward-sync PR
|
||||
if: env.TARGET_BRANCH != 'master'
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
|
||||
with:
|
||||
token: ${{ secrets.PROWLER_BOT_ACCESS_TOKEN }}
|
||||
commit-message: 'chore(changelog): v${{ env.PROWLER_VERSION }} forward-sync to master'
|
||||
branch: forward-sync-changelogs-${{ env.PROWLER_VERSION }}
|
||||
base: master
|
||||
title: 'chore(changelog): v${{ env.PROWLER_VERSION }} forward-sync to master'
|
||||
body-path: ${{ env.SYNC_BODY_FILE }}
|
||||
author: prowler-bot <179230569+prowler-bot@users.noreply.github.com>
|
||||
labels: |
|
||||
no-changelog
|
||||
@@ -206,7 +206,7 @@ jobs:
|
||||
|
||||
- name: Install regctl
|
||||
if: always()
|
||||
uses: regclient/actions/regctl-installer@da9319db8e44e8b062b3a147e1dfb2f574d41a03 # main
|
||||
uses: regclient/actions/regctl-installer@14f9d37db17b5dc41fefd1ffdd1af4b9e2490560 # main
|
||||
|
||||
- name: Cleanup intermediate architecture tags
|
||||
if: always()
|
||||
|
||||
@@ -105,7 +105,6 @@ jobs:
|
||||
files_ignore: |
|
||||
mcp_server/README.md
|
||||
mcp_server/CHANGELOG.md
|
||||
mcp_server/changelog.d/**
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
|
||||
@@ -62,140 +62,53 @@ jobs:
|
||||
uv.lock
|
||||
pyproject.toml
|
||||
|
||||
- name: Check for folder changes and changelog fragment presence
|
||||
- name: Check for folder changes and changelog presence
|
||||
id: check-folders
|
||||
run: |
|
||||
fragment_name_re='^[A-Za-z0-9][A-Za-z0-9._-]*\.(added|changed|deprecated|removed|fixed|security)(\.[0-9]+)?\.md$'
|
||||
|
||||
missing_fragments=""
|
||||
invalid_fragments=""
|
||||
linked_fragments=""
|
||||
|
||||
all_changed=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n')
|
||||
added=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES}" | tr ' ' '\n')
|
||||
added_or_modified=$(printf '%s\n%s' "${STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES}" "${STEPS_CHANGED_FILES_OUTPUTS_MODIFIED_FILES}" | tr ' ' '\n')
|
||||
|
||||
# Returns success if the folder has a valid fragment added/modified, or
|
||||
# (transition mode, to be removed after the migration grace period) a
|
||||
# direct CHANGELOG.md edit.
|
||||
has_changelog_update() {
|
||||
local folder="$1"
|
||||
if echo "$added_or_modified" | grep "^${folder}/changelog.d/" | sed "s|^${folder}/changelog.d/||" | grep -qE "$fragment_name_re"; then
|
||||
return 0
|
||||
fi
|
||||
if echo "$all_changed" | grep -q "^${folder}/CHANGELOG.md$"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
missing_changelogs=""
|
||||
|
||||
if [[ "${STEPS_CHANGED_FILES_OUTPUTS_ANY_CHANGED}" == "true" ]]; then
|
||||
# Check monitored folders
|
||||
for folder in $MONITORED_FOLDERS; do
|
||||
changed_in_folder=$(echo "$all_changed" | grep "^${folder}/" || true)
|
||||
# Get files changed in this folder
|
||||
changed_in_folder=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | grep "^${folder}/" || true)
|
||||
|
||||
if [ -n "$changed_in_folder" ]; then
|
||||
echo "Detected changes in ${folder}/"
|
||||
|
||||
if ! has_changelog_update "$folder"; then
|
||||
echo "No changelog fragment found for ${folder}/"
|
||||
missing_fragments="${missing_fragments}- \`${folder}\`"$'\n'
|
||||
# Check if CHANGELOG.md was updated
|
||||
if ! echo "$changed_in_folder" | grep -q "^${folder}/CHANGELOG.md$"; then
|
||||
echo "No changelog update found for ${folder}/"
|
||||
missing_changelogs="${missing_changelogs}- \`${folder}\`"$'\n'
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Check root-level dependency files (uv.lock, pyproject.toml)
|
||||
# These are associated with the prowler folder changelog
|
||||
root_deps_changed=$(echo "$all_changed" | grep -E "^(uv\.lock|pyproject\.toml)$" || true)
|
||||
root_deps_changed=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | grep -E "^(uv\.lock|pyproject\.toml)$" || true)
|
||||
if [ -n "$root_deps_changed" ]; then
|
||||
echo "Detected changes in root dependency files: $root_deps_changed"
|
||||
if ! has_changelog_update "prowler"; then
|
||||
# Check if prowler/CHANGELOG.md was already updated (might have been caught above)
|
||||
prowler_changelog_updated=$(echo "${STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES}" | tr ' ' '\n' | grep "^prowler/CHANGELOG.md$" || true)
|
||||
if [ -z "$prowler_changelog_updated" ]; then
|
||||
# Only add if prowler wasn't already flagged
|
||||
if ! echo "$missing_fragments" | grep -q "prowler"; then
|
||||
echo "No changelog fragment found for root dependency changes"
|
||||
missing_fragments="${missing_fragments}- \`prowler\` (root dependency files changed)"$'\n'
|
||||
if ! echo "$missing_changelogs" | grep -q "prowler"; then
|
||||
echo "No changelog update found for root dependency changes"
|
||||
missing_changelogs="${missing_changelogs}- \`prowler\` (root dependency files changed)"$'\n'
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate the filename of every fragment added by this PR
|
||||
added_fragments=$(echo "$added" | grep -E '^(api|ui|prowler|mcp_server)/changelog\.d/' || true)
|
||||
for fragment in $added_fragments; do
|
||||
name=$(basename "$fragment")
|
||||
if [ "$name" = ".gitkeep" ] || [ "$name" = "README.md" ]; then
|
||||
continue
|
||||
fi
|
||||
if ! echo "$name" | grep -qE "$fragment_name_re"; then
|
||||
echo "Invalid fragment filename: $fragment"
|
||||
invalid_fragments="${invalid_fragments}- \`${fragment}\`"$'\n'
|
||||
fi
|
||||
done
|
||||
|
||||
# Lint fragment content: the PR link is attached automatically at
|
||||
# compile time, so a hand-written one would end up duplicated
|
||||
touched_fragments=$(echo "$added_or_modified" | grep -E '^(api|ui|prowler|mcp_server)/changelog\.d/' || true)
|
||||
for fragment in $touched_fragments; do
|
||||
name=$(basename "$fragment")
|
||||
if [ "$name" = ".gitkeep" ] || [ "$name" = "README.md" ] || [ ! -f "$fragment" ]; then
|
||||
continue
|
||||
fi
|
||||
if grep -qE '\[\(#[0-9]+\)\]' "$fragment"; then
|
||||
echo "Fragment contains a hand-written PR link: $fragment"
|
||||
linked_fragments="${linked_fragments}- \`${fragment}\`"$'\n'
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
# Suggest a slug derived from the branch name for the bot comment
|
||||
suggested_slug=$(echo "$HEAD_REF" | tr '[:upper:]' '[:lower:]' | sed 's|.*/||; s/[^a-z0-9._-]/-/g; s/^[^a-z0-9]*//')
|
||||
if [ -z "$suggested_slug" ]; then
|
||||
suggested_slug="my-change"
|
||||
fi
|
||||
|
||||
fragment_help="A changelog fragment is a small Markdown file named \`<slug>.<type>.md\` under \`<component>/changelog.d/\`, where \`<type>\` is one of \`added\`, \`changed\`, \`deprecated\`, \`removed\`, \`fixed\` or \`security\`. Its content is the changelog entry text, without the PR link (added automatically at release time) and without a trailing period. For example:
|
||||
|
||||
\`\`\`
|
||||
echo 'Entry text describing the change' > <component>/changelog.d/${suggested_slug}.fixed.md
|
||||
\`\`\`
|
||||
|
||||
If this PR does not need a changelog entry, add the \`no-changelog\` label instead."
|
||||
|
||||
if [ -n "$missing_fragments" ] || [ -n "$invalid_fragments" ] || [ -n "$linked_fragments" ]; then
|
||||
comment_body=""
|
||||
if [ -n "$missing_fragments" ]; then
|
||||
comment_body="⚠️ **Changes detected in the following folders without a changelog fragment:**"$'\n\n'"${missing_fragments}"$'\n'
|
||||
fi
|
||||
if [ -n "$invalid_fragments" ]; then
|
||||
comment_body="${comment_body}⚠️ **Changelog fragment filenames that do not follow the naming convention:**"$'\n\n'"${invalid_fragments}"$'\n'
|
||||
fi
|
||||
if [ -n "$linked_fragments" ]; then
|
||||
comment_body="${comment_body}⚠️ **Changelog fragments containing a hand-written PR link (remove it; the link is attached automatically at release time):**"$'\n\n'"${linked_fragments}"$'\n'
|
||||
fi
|
||||
comment_body="${comment_body}${fragment_help}"
|
||||
else
|
||||
comment_body="✅ All required changelog fragments are present."
|
||||
fi
|
||||
|
||||
{
|
||||
echo "missing_fragments<<EOF"
|
||||
echo -e "${missing_fragments}"
|
||||
echo "EOF"
|
||||
echo "invalid_fragments<<EOF"
|
||||
echo -e "${invalid_fragments}"
|
||||
echo "EOF"
|
||||
echo "linked_fragments<<EOF"
|
||||
echo -e "${linked_fragments}"
|
||||
echo "EOF"
|
||||
echo "comment_body<<EOF"
|
||||
echo "${comment_body}"
|
||||
echo "missing_changelogs<<EOF"
|
||||
echo -e "${missing_changelogs}"
|
||||
echo "EOF"
|
||||
} >> $GITHUB_OUTPUT
|
||||
env:
|
||||
STEPS_CHANGED_FILES_OUTPUTS_ANY_CHANGED: ${{ steps.changed-files.outputs.any_changed }}
|
||||
STEPS_CHANGED_FILES_OUTPUTS_ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
|
||||
STEPS_CHANGED_FILES_OUTPUTS_ADDED_FILES: ${{ steps.changed-files.outputs.added_files }}
|
||||
STEPS_CHANGED_FILES_OUTPUTS_MODIFIED_FILES: ${{ steps.changed-files.outputs.modified_files }}
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
- name: Find existing changelog comment
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
@@ -215,10 +128,14 @@ jobs:
|
||||
edit-mode: replace
|
||||
body: |
|
||||
<!-- changelog-check -->
|
||||
${{ steps.check-folders.outputs.comment_body }}
|
||||
${{ steps.check-folders.outputs.missing_changelogs != '' && format('⚠️ **Changes detected in the following folders without a corresponding update to the `CHANGELOG.md`:**
|
||||
|
||||
- name: Fail if changelog fragment is missing or invalid
|
||||
if: steps.check-folders.outputs.missing_fragments != '' || steps.check-folders.outputs.invalid_fragments != '' || steps.check-folders.outputs.linked_fragments != ''
|
||||
{0}
|
||||
|
||||
Please add an entry to the corresponding `CHANGELOG.md` file to maintain a clear history of changes.', steps.check-folders.outputs.missing_changelogs) || '✅ All necessary `CHANGELOG.md` files have been updated.' }}
|
||||
|
||||
- name: Fail if changelog is missing
|
||||
if: steps.check-folders.outputs.missing_changelogs != ''
|
||||
run: |
|
||||
echo "::error::Missing or invalid changelog fragments"
|
||||
echo "::error::Missing changelog updates in some folders"
|
||||
exit 1
|
||||
|
||||
@@ -54,7 +54,6 @@ jobs:
|
||||
files_ignore: |
|
||||
.github/**
|
||||
prowler/CHANGELOG.md
|
||||
prowler/changelog.d/**
|
||||
docs/**
|
||||
permissions/**
|
||||
api/**
|
||||
|
||||
@@ -12,7 +12,6 @@ on:
|
||||
- '.github/workflows/sdk-codeql.yml'
|
||||
- '.github/codeql/sdk-codeql-config.yml'
|
||||
- '!prowler/CHANGELOG.md'
|
||||
- '!prowler/changelog.d/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
@@ -24,7 +23,6 @@ on:
|
||||
- '.github/workflows/sdk-codeql.yml'
|
||||
- '.github/codeql/sdk-codeql-config.yml'
|
||||
- '!prowler/CHANGELOG.md'
|
||||
- '!prowler/changelog.d/**'
|
||||
schedule:
|
||||
- cron: '00 12 * * *'
|
||||
|
||||
|
||||
@@ -299,7 +299,7 @@ jobs:
|
||||
|
||||
- name: Install regctl
|
||||
if: always()
|
||||
uses: regclient/actions/regctl-installer@da9319db8e44e8b062b3a147e1dfb2f574d41a03 # main
|
||||
uses: regclient/actions/regctl-installer@14f9d37db17b5dc41fefd1ffdd1af4b9e2490560 # main
|
||||
|
||||
- name: Cleanup intermediate architecture tags
|
||||
if: always()
|
||||
|
||||
@@ -115,7 +115,6 @@ jobs:
|
||||
files_ignore: |
|
||||
.github/**
|
||||
prowler/CHANGELOG.md
|
||||
prowler/changelog.d/**
|
||||
docs/**
|
||||
permissions/**
|
||||
api/**
|
||||
|
||||
@@ -77,7 +77,6 @@ jobs:
|
||||
files_ignore: |
|
||||
.github/**
|
||||
prowler/CHANGELOG.md
|
||||
prowler/changelog.d/**
|
||||
docs/**
|
||||
permissions/**
|
||||
api/**
|
||||
|
||||
@@ -76,7 +76,6 @@ jobs:
|
||||
files_ignore: |
|
||||
.github/**
|
||||
prowler/CHANGELOG.md
|
||||
prowler/changelog.d/**
|
||||
docs/**
|
||||
permissions/**
|
||||
api/**
|
||||
@@ -541,7 +540,7 @@ jobs:
|
||||
with:
|
||||
flags: prowler-py${{ matrix.python-version }}-vercel
|
||||
files: ./vercel_coverage.xml
|
||||
|
||||
|
||||
# Scaleway Provider
|
||||
- name: Check if Scaleway files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
@@ -589,7 +588,7 @@ jobs:
|
||||
with:
|
||||
flags: prowler-py${{ matrix.python-version }}-stackit
|
||||
files: ./stackit_coverage.xml
|
||||
|
||||
|
||||
# External Provider (dynamic loading)
|
||||
- name: Check if External Provider files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
@@ -609,14 +608,14 @@ jobs:
|
||||
|
||||
- name: Upload External Provider coverage to Codecov
|
||||
if: steps.changed-external.outputs.any_changed == 'true'
|
||||
|
||||
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
with:
|
||||
flags: prowler-py${{ matrix.python-version }}-external
|
||||
files: ./external_coverage.xml
|
||||
|
||||
|
||||
# Lib
|
||||
- name: Check if Lib files changed
|
||||
if: steps.check-changes.outputs.any_changed == 'true'
|
||||
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: '3.12'
|
||||
python-version: '3.12.13'
|
||||
|
||||
- name: Install PyYAML
|
||||
run: pip install pyyaml
|
||||
|
||||
@@ -10,7 +10,6 @@ on:
|
||||
- '.github/workflows/ui-codeql.yml'
|
||||
- '.github/codeql/ui-codeql-config.yml'
|
||||
- '!ui/CHANGELOG.md'
|
||||
- '!ui/changelog.d/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
@@ -20,7 +19,6 @@ on:
|
||||
- '.github/workflows/ui-codeql.yml'
|
||||
- '.github/codeql/ui-codeql-config.yml'
|
||||
- '!ui/CHANGELOG.md'
|
||||
- '!ui/changelog.d/**'
|
||||
schedule:
|
||||
- cron: '00 12 * * *'
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ jobs:
|
||||
|
||||
- name: Install regctl
|
||||
if: always()
|
||||
uses: regclient/actions/regctl-installer@da9319db8e44e8b062b3a147e1dfb2f574d41a03 # main
|
||||
uses: regclient/actions/regctl-installer@14f9d37db17b5dc41fefd1ffdd1af4b9e2490560 # main
|
||||
|
||||
- name: Cleanup intermediate architecture tags
|
||||
if: always()
|
||||
|
||||
@@ -105,7 +105,6 @@ jobs:
|
||||
files: ui/**
|
||||
files_ignore: |
|
||||
ui/CHANGELOG.md
|
||||
ui/changelog.d/**
|
||||
ui/README.md
|
||||
ui/AGENTS.md
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ jobs:
|
||||
.github/workflows/ui-tests.yml
|
||||
files_ignore: |
|
||||
ui/CHANGELOG.md
|
||||
ui/changelog.d/**
|
||||
ui/README.md
|
||||
ui/AGENTS.md
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
All notable changes to the **Prowler API** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
|
||||
## [1.31.1] (Prowler v5.30.1)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Changelog fragments
|
||||
|
||||
Each PR adds one small file here instead of editing `CHANGELOG.md` directly, so concurrent PRs never conflict.
|
||||
|
||||
- Filename: `<slug>.<type>.md`, e.g. `my-new-check.added.md` (slug is free-form: letters, digits, `.`, `_`, `-`)
|
||||
- `<type>` is one of: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`
|
||||
- Content: one line with the changelog entry text, without the PR link and without a trailing period (the PR link is attached automatically at release time)
|
||||
- A PR adds as many fragment files as entries it needs, freely mixing types (one file per entry); same-type entries just use different slugs
|
||||
|
||||
Fragments are compiled into `CHANGELOG.md` when a release is prepared. Full conventions: `skills/prowler-changelog/SKILL.md`.
|
||||
@@ -1,39 +0,0 @@
|
||||
[tool.towncrier]
|
||||
directory = "changelog.d"
|
||||
filename = "CHANGELOG.md"
|
||||
start_string = "<!-- changelog: release notes start -->\n"
|
||||
title_format = "## [{version}] ({name})"
|
||||
issue_format = "[(#{issue})](https://github.com/prowler-cloud/prowler/pull/{issue})"
|
||||
template = "../.github/towncrier/template.md.jinja"
|
||||
underlines = ["", "", ""]
|
||||
ignore = [".gitkeep", "README.md"]
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "added"
|
||||
name = "🚀 Added"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "changed"
|
||||
name = "🔄 Changed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "deprecated"
|
||||
name = "⚠️ Deprecated"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "removed"
|
||||
name = "❌ Removed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "fixed"
|
||||
name = "🐞 Fixed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "security"
|
||||
name = "🔐 Security"
|
||||
showcontent = true
|
||||
@@ -3421,7 +3421,7 @@ Use existing providers as templates, this will help you to understand better the
|
||||
- **Documentation & Maintenance**
|
||||
|
||||
- **README Updates**: Update provider-specific documentation
|
||||
- **Changelog**: Document changes and new features with a fragment under `prowler/changelog.d/` (see the [Pull Request Template](https://github.com/prowler-cloud/prowler/blob/master/.github/pull_request_template.md))
|
||||
- **Changelog**: Document changes and new features
|
||||
- **Examples**: Provide usage examples and common scenarios
|
||||
- **Troubleshooting**: Include common issues and solutions
|
||||
- **Documentation**: Update the provider documentation to include your new tool provider in the examples and implementation guidance.
|
||||
|
||||
@@ -599,7 +599,7 @@ Before opening the pull request:
|
||||
uv run pre-commit run --all-files
|
||||
uv run pytest -n auto
|
||||
```
|
||||
2. Add a changelog fragment `prowler/changelog.d/<slug>.added.md`, describing the new framework and the providers it covers (no PR link in the text; it is attached automatically at release time).
|
||||
2. Add a changelog entry under the `### 🚀 Added` section of `prowler/CHANGELOG.md`, describing the new framework and the providers it covers.
|
||||
3. Follow the [Pull Request Template](https://github.com/prowler-cloud/prowler/blob/master/.github/pull_request_template.md) and set the PR title using Conventional Commits, e.g. `feat(compliance): add My Framework 1.0 for AWS`.
|
||||
4. Request review from the compliance codeowners listed in `.github/CODEOWNERS`.
|
||||
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
All notable changes to the **Prowler MCP Server** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
|
||||
## [0.7.2] (Prowler v5.28.1)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Changelog fragments
|
||||
|
||||
Each PR adds one small file here instead of editing `CHANGELOG.md` directly, so concurrent PRs never conflict.
|
||||
|
||||
- Filename: `<slug>.<type>.md`, e.g. `my-new-check.added.md` (slug is free-form: letters, digits, `.`, `_`, `-`)
|
||||
- `<type>` is one of: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`
|
||||
- Content: one line with the changelog entry text, without the PR link and without a trailing period (the PR link is attached automatically at release time)
|
||||
- A PR adds as many fragment files as entries it needs, freely mixing types (one file per entry); same-type entries just use different slugs
|
||||
|
||||
Fragments are compiled into `CHANGELOG.md` when a release is prepared. Full conventions: `skills/prowler-changelog/SKILL.md`.
|
||||
@@ -1,39 +0,0 @@
|
||||
[tool.towncrier]
|
||||
directory = "changelog.d"
|
||||
filename = "CHANGELOG.md"
|
||||
start_string = "<!-- changelog: release notes start -->\n"
|
||||
title_format = "## [{version}] ({name})"
|
||||
issue_format = "[(#{issue})](https://github.com/prowler-cloud/prowler/pull/{issue})"
|
||||
template = "../.github/towncrier/template.md.jinja"
|
||||
underlines = ["", "", ""]
|
||||
ignore = [".gitkeep", "README.md"]
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "added"
|
||||
name = "🚀 Added"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "changed"
|
||||
name = "🔄 Changed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "deprecated"
|
||||
name = "⚠️ Deprecated"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "removed"
|
||||
name = "❌ Removed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "fixed"
|
||||
name = "🐞 Fixed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "security"
|
||||
name = "🔐 Security"
|
||||
showcontent = true
|
||||
+12
-1
@@ -2,7 +2,18 @@
|
||||
|
||||
All notable changes to the **Prowler SDK** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
## [5.31.0] (Prowler UNRELEASED)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- `securityhub_delegated_admin_enabled_all_regions` check for AWS provider, verifying that Security Hub has a delegated administrator, is active in all opted-in regions, and has organization auto-enable on [(#11259)](https://github.com/prowler-cloud/prowler/pull/11259)
|
||||
- `config_delegated_admin_and_org_aggregator_all_regions` check for AWS provider, verifying that AWS Config has a delegated administrator and an organization aggregator covering all AWS regions [(#11259)](https://github.com/prowler-cloud/prowler/pull/11259)
|
||||
- `sagemaker_clarify_exists` check for AWS provider [(#11211)](https://github.com/prowler-cloud/prowler/pull/11211)
|
||||
- `cloudsql_instance_high_availability_enabled` check for GCP provider, verifying Cloud SQL primary instances use `REGIONAL` availability for automatic zone failover [(#11024)](https://github.com/prowler-cloud/prowler/pull/11024)
|
||||
- `identity_storage_service_level_admins_scoped` check for OCI provider CIS 3.1 control 1.15, ensuring storage service-level administrators exclude delete permissions [(#11523)](https://github.com/prowler-cloud/prowler/pull/11523)
|
||||
- `cosmosdb_account_automatic_failover_enabled` check for Azure provider [(#11031)](https://github.com/prowler-cloud/prowler/pull/11031)
|
||||
|
||||
---
|
||||
|
||||
## [5.30.0] (Prowler v5.30.0)
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
`cloudsql_instance_high_availability_enabled` check for GCP provider, verifying Cloud SQL primary instances use `REGIONAL` availability for automatic zone failover
|
||||
@@ -1 +0,0 @@
|
||||
`cosmosdb_account_automatic_failover_enabled` check for Azure provider
|
||||
@@ -1 +0,0 @@
|
||||
`sagemaker_clarify_exists` check for AWS provider
|
||||
@@ -1 +0,0 @@
|
||||
`config_delegated_admin_and_org_aggregator_all_regions` check for AWS provider, verifying that AWS Config has a delegated administrator and an organization aggregator covering all AWS regions
|
||||
@@ -1 +0,0 @@
|
||||
`securityhub_delegated_admin_enabled_all_regions` check for AWS provider, verifying that Security Hub has a delegated administrator, is active in all opted-in regions, and has organization auto-enable on
|
||||
@@ -1 +0,0 @@
|
||||
`identity_storage_service_level_admins_scoped` check for OCI provider CIS 3.1 control 1.15, ensuring storage service-level administrators exclude delete permissions
|
||||
@@ -1,10 +0,0 @@
|
||||
# Changelog fragments
|
||||
|
||||
Each PR adds one small file here instead of editing `CHANGELOG.md` directly, so concurrent PRs never conflict.
|
||||
|
||||
- Filename: `<slug>.<type>.md`, e.g. `my-new-check.added.md` (slug is free-form: letters, digits, `.`, `_`, `-`)
|
||||
- `<type>` is one of: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`
|
||||
- Content: one line with the changelog entry text, without the PR link and without a trailing period (the PR link is attached automatically at release time)
|
||||
- A PR adds as many fragment files as entries it needs, freely mixing types (one file per entry); same-type entries just use different slugs
|
||||
|
||||
Fragments are compiled into `CHANGELOG.md` when a release is prepared. Full conventions: `skills/prowler-changelog/SKILL.md`.
|
||||
@@ -1,39 +0,0 @@
|
||||
[tool.towncrier]
|
||||
directory = "changelog.d"
|
||||
filename = "CHANGELOG.md"
|
||||
start_string = "<!-- changelog: release notes start -->\n"
|
||||
title_format = "## [{version}] ({name})"
|
||||
issue_format = "[(#{issue})](https://github.com/prowler-cloud/prowler/pull/{issue})"
|
||||
template = "../.github/towncrier/template.md.jinja"
|
||||
underlines = ["", "", ""]
|
||||
ignore = [".gitkeep", "README.md"]
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "added"
|
||||
name = "🚀 Added"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "changed"
|
||||
name = "🔄 Changed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "deprecated"
|
||||
name = "⚠️ Deprecated"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "removed"
|
||||
name = "❌ Removed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "fixed"
|
||||
name = "🐞 Fixed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "security"
|
||||
name = "🔐 Security"
|
||||
showcontent = true
|
||||
+190
-106
@@ -6,7 +6,7 @@ description: >
|
||||
license: Apache-2.0
|
||||
metadata:
|
||||
author: prowler-cloud
|
||||
version: "2.0"
|
||||
version: "1.0"
|
||||
scope: [root, ui, api, sdk, mcp_server]
|
||||
auto_invoke:
|
||||
- "Add changelog entry for a PR or feature"
|
||||
@@ -16,80 +16,70 @@ metadata:
|
||||
allowed-tools: Read, Edit, Write, Glob, Grep, Bash
|
||||
---
|
||||
|
||||
## How changelog entries work: fragments
|
||||
## Changelog Locations
|
||||
|
||||
A PR never edits `CHANGELOG.md` directly. Instead it adds one small **fragment file** per entry under the component's `changelog.d/` directory. Fragments are compiled into the component's `CHANGELOG.md` at release time (deleting the consumed fragments), so concurrent PRs never conflict on the changelog.
|
||||
| Component | File | Version Prefix | Current Version |
|
||||
|-----------|------|----------------|-----------------|
|
||||
| UI | `ui/CHANGELOG.md` | None | 1.x.x |
|
||||
| API | `api/CHANGELOG.md` | None | 1.x.x |
|
||||
| MCP Server | `mcp_server/CHANGELOG.md` | None | 0.x.x |
|
||||
| SDK | `prowler/CHANGELOG.md` | None | 5.x.x |
|
||||
|
||||
| Component | Fragments directory | Compiled file |
|
||||
|-----------|---------------------|---------------|
|
||||
| UI | `ui/changelog.d/` | `ui/CHANGELOG.md` |
|
||||
| API | `api/changelog.d/` | `api/CHANGELOG.md` |
|
||||
| MCP Server | `mcp_server/changelog.d/` | `mcp_server/CHANGELOG.md` |
|
||||
| SDK | `prowler/changelog.d/` | `prowler/CHANGELOG.md` |
|
||||
## Format Rules (keepachangelog.com)
|
||||
|
||||
"What's unreleased" = "what's in `changelog.d/`". The compiled `CHANGELOG.md` files contain only released versions.
|
||||
### Section Order (ALWAYS this order)
|
||||
|
||||
## Fragment filename
|
||||
```markdown
|
||||
## [X.Y.Z] (Prowler vA.B.C) OR (Prowler UNRELEASED)
|
||||
|
||||
```text
|
||||
<slug>.<type>.md
|
||||
### Added
|
||||
### Changed
|
||||
### Deprecated
|
||||
### Removed
|
||||
### Fixed
|
||||
### Security
|
||||
```
|
||||
|
||||
- `<slug>` is free-form (`[A-Za-z0-9][A-Za-z0-9._-]*`), chosen by the author, ideally descriptive of the change (e.g. `securityhub-delegated-admin`). The PR number is also a valid slug (e.g. `11259`) when it is already known; it is never required.
|
||||
- `<type>` maps 1:1 to the keepachangelog sections:
|
||||
### Emoji Prefixes (REQUIRED for ALL components)
|
||||
|
||||
| `<type>` | Section | Usage |
|
||||
|----------|---------|-------|
|
||||
| `added` | `### 🚀 Added` | New features, checks, endpoints |
|
||||
| `changed` | `### 🔄 Changed` | Modifications to existing functionality |
|
||||
| `deprecated` | `### ⚠️ Deprecated` | Features marked for removal |
|
||||
| `removed` | `### ❌ Removed` | Deleted features |
|
||||
| `fixed` | `### 🐞 Fixed` | Bug fixes |
|
||||
| `security` | `### 🔐 Security` | Security patches, CVE fixes |
|
||||
| Section | Emoji | Usage |
|
||||
|---------|-------|-------|
|
||||
| Added | `### 🚀 Added` | New features, checks, endpoints |
|
||||
| Changed | `### 🔄 Changed` | Modifications to existing functionality |
|
||||
| Deprecated | `### ⚠️ Deprecated` | Features marked for removal |
|
||||
| Removed | `### ❌ Removed` | Deleted features |
|
||||
| Fixed | `### 🐞 Fixed` | Bug fixes |
|
||||
| Security | `### 🔐 Security` | Security patches, CVE fixes |
|
||||
|
||||
- A PR adds as many fragment files as entries it needs, freely mixing types: one file per entry. E.g. a PR touching Added, Changed and Fixed ships `kms-rotation-check.added.md` + `kms-metadata-cache.changed.md` + `kms-disabled-keys.fixed.md`, and all compile with the same PR link into their own sections.
|
||||
- Several entries of the SAME type: a different slug per entry (`kms-rotation-check.added.md`, `kms-rotation-docs.added.md`).
|
||||
- At least one fragment per touched component, same as the old one-entry-per-changelog rule.
|
||||
### Entry Format
|
||||
|
||||
## Fragment content
|
||||
```markdown
|
||||
### Added
|
||||
|
||||
The file contains ONLY the entry text, exactly as it should appear in the changelog, on a single line ending with a trailing newline:
|
||||
- Existing entry one [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
- Existing entry two [(#YYYY)](https://github.com/prowler-cloud/prowler/pull/YYYY)
|
||||
- NEW ENTRY GOES HERE at the BOTTOM [(#ZZZZ)](https://github.com/prowler-cloud/prowler/pull/ZZZZ)
|
||||
|
||||
```bash
|
||||
echo '`securityhub_delegated_admin_enabled_all_regions` check for AWS provider, verifying that Security Hub has a delegated administrator, is active in all opted-in regions, and has organization auto-enable on' > prowler/changelog.d/securityhub-delegated-admin.added.md
|
||||
### Changed
|
||||
|
||||
- Existing change [(#AAAA)](https://github.com/prowler-cloud/prowler/pull/AAAA)
|
||||
- NEW CHANGE ENTRY at BOTTOM [(#BBBB)](https://github.com/prowler-cloud/prowler/pull/BBBB)
|
||||
```
|
||||
|
||||
**Rules (same prose conventions as always):**
|
||||
|
||||
- **NEVER write the PR link in the text.** It is attached automatically at compile time (the compile workflow resolves the PR that added the fragment from git history). Writing `[(#NNNN)](...)` in a fragment produces a duplicated link.
|
||||
- No period at the end
|
||||
- Do NOT start with redundant verbs (the section header already provides the action)
|
||||
**Rules:**
|
||||
- **ADD NEW ENTRIES AT THE BOTTOM of each section** (before next section header or `---`)
|
||||
- **Blank line after section header** before first entry
|
||||
- **Blank line between sections**
|
||||
- Be specific: what changed, not why (that's in the PR)
|
||||
- Keep entries readable: use spaces around inline code and product names, and wrap endpoints, commands, errors, task names, and file paths in backticks
|
||||
- Avoid long run-on sentences; split complex changes into one concise result plus one concise context clause
|
||||
- One entry per PR (can link multiple PRs for related changes)
|
||||
- No period at the end
|
||||
- Do NOT start with redundant verbs (section header already provides the action)
|
||||
- **CRITICAL: Preserve section order** — when adding a new section to the UNRELEASED block, insert it in the correct position relative to existing sections (Added → Changed → Deprecated → Removed → Fixed → Security). Never append a new section at the top or bottom without checking order
|
||||
- **CRITICAL: ALWAYS link to the PR, NEVER to the issue.** Every entry MUST use `https://github.com/prowler-cloud/prowler/pull/N`. Linking to `/issues/N` is FORBIDDEN, even when the PR fixes an issue. The issue↔PR relationship belongs in the PR body (`Fixes #N`), not in the changelog. If a fix has no PR yet, do not add the entry until the PR exists.
|
||||
|
||||
### Good fragments
|
||||
|
||||
```text
|
||||
# ui/changelog.d/provider-search-bar.added.md
|
||||
Search bar when adding a provider
|
||||
|
||||
# api/changelog.d/scan-dispatch-race.fixed.md
|
||||
`POST /api/v1/scans` no longer intermittently fails with `Scan matching query does not exist`; scan dispatch now publishes the `scan-perform` Celery task after the transaction commits
|
||||
|
||||
# ui/changelog.d/node-24-bump.security.md
|
||||
Node.js from 20.x to 24.13.0 LTS, patching 8 CVEs
|
||||
```
|
||||
|
||||
### Bad fragments
|
||||
|
||||
```text
|
||||
Fixed bug. # Too vague, has period, redundant verb
|
||||
Add search bar # Redundant verb (the section already says "Added")
|
||||
Search bar [(#9634)](https://github.com/prowler-cloud/prowler/pull/9634) # NEVER include the PR link; it is added at compile time
|
||||
```
|
||||
|
||||
## Semantic Versioning Rules
|
||||
### Semantic Versioning Rules
|
||||
|
||||
Prowler follows [semver.org](https://semver.org/):
|
||||
|
||||
@@ -99,26 +89,69 @@ Prowler follows [semver.org](https://semver.org/):
|
||||
| New features (backwards compatible) | MINOR (x.**Y**.0) | 1.16.2 → 1.17.0 |
|
||||
| Breaking changes, removals | MAJOR (**X**.0.0) | 1.17.0 → 2.0.0 |
|
||||
|
||||
**CRITICAL:** `removed` fragments MUST only ship in MAJOR version releases. Removing features is a breaking change.
|
||||
**CRITICAL:** `### ❌ Removed` entries MUST only appear in MAJOR version releases. Removing features is a breaking change.
|
||||
|
||||
### Released Versions Are Immutable
|
||||
|
||||
**NEVER modify already released versions.** Once a version is released (has a Prowler version tag like `v5.16.0`), its changelog section is frozen.
|
||||
|
||||
**Common issue:** A PR is created during release cycle X, includes a changelog entry, but merges after release. The entry is now in the wrong section.
|
||||
|
||||
```markdown
|
||||
## [1.16.0] (Prowler v5.16.0) ← RELEASED, DO NOT MODIFY
|
||||
|
||||
### Added
|
||||
- Feature from merged PR [(#9999)] ← WRONG! PR merged after release
|
||||
|
||||
## [1.17.0] (Prowler UNRELEASED) ← Move entry HERE
|
||||
```
|
||||
|
||||
**Fix:** Move the entry from the released version to the UNRELEASED section.
|
||||
|
||||
### Version Header Format
|
||||
|
||||
```markdown
|
||||
## [1.17.0] (Prowler UNRELEASED) # For unreleased changes
|
||||
## [1.16.0] (Prowler v5.16.0) # For released versions
|
||||
|
||||
--- # Horizontal rule between versions
|
||||
```
|
||||
|
||||
## Mandatory Changelog Preflight
|
||||
|
||||
Before editing any `CHANGELOG.md`, always inspect the active release boundary:
|
||||
|
||||
1. Read the UNRELEASED block plus the latest three released version blocks:
|
||||
```bash
|
||||
awk '/^## \[/{n++} n<=4 {print}' ui/CHANGELOG.md
|
||||
```
|
||||
2. Identify the **only writable block**: the block whose header contains `(Prowler UNRELEASED)`.
|
||||
3. Treat every block whose header contains `(Prowler vX.Y.Z)` as immutable. Do not add, move, reword, reorder, or deduplicate entries there.
|
||||
4. If your PR's entry appears in any of the latest three released blocks, remove it from the released block and add it to the correct section in the UNRELEASED block.
|
||||
5. If there is no UNRELEASED block at the top, stop and ask before editing.
|
||||
|
||||
**Do not trust the current topmost matching section name.** A released block can contain the same section heading (`### 🚀 Added`, `### 🔄 Changed`, etc.). Always anchor edits to the `Prowler UNRELEASED` version block first.
|
||||
|
||||
## Mandatory Human Confirmation Gate
|
||||
|
||||
Before creating or editing any changelog fragment or `CHANGELOG.md` file, the agent MUST stop and get explicit user confirmation. This applies even when the changelog gate is failing, the required file seems obvious, or the user asked to "fix the changelog".
|
||||
Before creating or editing any changelog file (`CHANGELOG.md`), the agent MUST stop and get explicit user confirmation. This applies even when the changelog gate is failing, the required edit seems obvious, or the user asked to "fix the changelog".
|
||||
|
||||
Present the proposed action before writing:
|
||||
Present the proposed changelog action before writing:
|
||||
|
||||
1. Target fragment path (component, slug, type) or CHANGELOG.md edit.
|
||||
2. Exact entry text.
|
||||
3. Reason the changelog entry is needed.
|
||||
1. Target file path.
|
||||
2. Target version block and section.
|
||||
3. Exact entry to add, move, remove, or rewrite.
|
||||
4. Reason the changelog is needed.
|
||||
|
||||
Only proceed after an explicit approval such as "confirm", "approved", "sí", or equivalent. If the user rejects or does not answer, do not create or edit anything. Offer alternatives such as adding `no-changelog` when appropriate.
|
||||
Only proceed after an explicit approval such as "confirm", "approved", "sí", or equivalent. If the user rejects or does not answer, do not edit or create the changelog. Offer alternatives such as adding `no-changelog` when appropriate.
|
||||
|
||||
## Adding a Changelog Entry
|
||||
|
||||
### Step 1: Determine Affected Component(s)
|
||||
|
||||
```bash
|
||||
git diff master...HEAD --name-only | grep -E '^(ui|api|mcp_server|prowler)/' | cut -d/ -f1 | sort -u
|
||||
# Check which files changed
|
||||
git diff main...HEAD --name-only
|
||||
```
|
||||
|
||||
| Path Pattern | Component |
|
||||
@@ -127,62 +160,113 @@ git diff master...HEAD --name-only | grep -E '^(ui|api|mcp_server|prowler)/' | c
|
||||
| `api/**` | API |
|
||||
| `mcp_server/**` | MCP Server |
|
||||
| `prowler/**` | SDK |
|
||||
| Root `uv.lock` / `pyproject.toml` | SDK (the gate requires a `prowler/changelog.d/` fragment) |
|
||||
| Multiple | One fragment per affected component |
|
||||
| Multiple | Update ALL affected changelogs |
|
||||
|
||||
### Step 2: Create the fragment(s)
|
||||
### Step 2: Determine Change Type
|
||||
|
||||
```bash
|
||||
echo 'Entry text describing the change' > <component>/changelog.d/<slug>.<type>.md
|
||||
| Change | Section |
|
||||
|--------|---------|
|
||||
| New feature, check, endpoint | 🚀 Added |
|
||||
| Behavior change, refactor | 🔄 Changed |
|
||||
| Bug fix | 🐞 Fixed |
|
||||
| CVE patch, security improvement | 🔐 Security |
|
||||
| Feature removal | ❌ Removed |
|
||||
| Deprecation notice | ⚠️ Deprecated |
|
||||
|
||||
### Step 3: Add Entry at BOTTOM of Appropriate Section
|
||||
|
||||
**CRITICAL:** Add new entries at the BOTTOM of each section, NOT at the top.
|
||||
|
||||
**CRITICAL:** The link MUST point to the PR (`/pull/N`). Linking to `/issues/N` is FORBIDDEN. If the PR closes an issue, that mapping goes in the PR body via `Fixes #N` — never in the changelog entry.
|
||||
|
||||
```markdown
|
||||
## [1.17.0] (Prowler UNRELEASED)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Existing fix one [(#9997)](https://github.com/prowler-cloud/prowler/pull/9997)
|
||||
- Existing fix two [(#9998)](https://github.com/prowler-cloud/prowler/pull/9998)
|
||||
- Button alignment in dashboard header [(#9999)](https://github.com/prowler-cloud/prowler/pull/9999) ← NEW ENTRY AT BOTTOM
|
||||
|
||||
### 🔐 Security
|
||||
```
|
||||
|
||||
### Step 3: Check pending fragments
|
||||
This maintains chronological order within each section (oldest at top, newest at bottom).
|
||||
|
||||
```bash
|
||||
ls prowler/changelog.d/ api/changelog.d/ ui/changelog.d/ mcp_server/changelog.d/
|
||||
## Examples
|
||||
|
||||
### Good Entries
|
||||
|
||||
```markdown
|
||||
### 🚀 Added
|
||||
- Search bar when adding a provider [(#9634)](https://github.com/prowler-cloud/prowler/pull/9634)
|
||||
|
||||
### 🐞 Fixed
|
||||
- OCI update credentials form failing silently due to missing provider UID [(#9746)](https://github.com/prowler-cloud/prowler/pull/9746)
|
||||
|
||||
### 🔐 Security
|
||||
- Node.js from 20.x to 24.13.0 LTS, patching 8 CVEs [(#9797)](https://github.com/prowler-cloud/prowler/pull/9797)
|
||||
```
|
||||
|
||||
### Readable Technical Entries
|
||||
|
||||
```markdown
|
||||
# GOOD - Technical but readable
|
||||
### 🐞 Fixed
|
||||
- `POST /api/v1/scans` no longer intermittently fails with `Scan matching query does not exist`; scan dispatch now publishes the `scan-perform` Celery task after the transaction commits [(#11122)](https://github.com/prowler-cloud/prowler/pull/11122)
|
||||
- `entra_users_mfa_capable` no longer flags disabled guest users; Microsoft Graph is now the source of truth for `account_enabled` because EXO `Get-User` omits guest users [(#11002)](https://github.com/prowler-cloud/prowler/pull/11002)
|
||||
```
|
||||
|
||||
### Bad Entries
|
||||
|
||||
```markdown
|
||||
# BAD - Wrong section order (Fixed before Added)
|
||||
### 🐞 Fixed
|
||||
- Some bug fix [(#123)](...)
|
||||
|
||||
### 🚀 Added
|
||||
- Some new feature [(#456)](...)
|
||||
|
||||
- Fixed bug. # Too vague, has period
|
||||
- Added new feature for users # Missing PR link, redundant verb
|
||||
- Add search bar [(#123)] # Redundant verb (section already says "Added")
|
||||
- This PR adds a cool new thing (#123) # Wrong link format, conversational
|
||||
- Some bug fix [(#123)](https://github.com/prowler-cloud/prowler/issues/123) # FORBIDDEN: must link to /pull/N, never /issues/N
|
||||
- POST /api/v1/scanswas intermittently failing withScan matching query does not existin thescan-performworker (#11122) # Missing spaces/backticks, unreadable
|
||||
- entra_users_mfa_capable no longer flags disabled guest users by requesting accountEnabled and userType from Microsoft Graph via $select and using Graph as the source of truth for account_enabled (EXO Get-User does not return guest users) (#11002) # Run-on sentence, identifiers not formatted
|
||||
```
|
||||
|
||||
## PR Changelog Gate
|
||||
|
||||
The `pr-check-changelog.yml` workflow enforces fragments:
|
||||
The `pr-check-changelog.yml` workflow enforces changelog entries:
|
||||
|
||||
1. **REQUIRED**: PRs touching `ui/`, `api/`, `mcp_server/`, or `prowler/` MUST add (or fix) a fragment under the corresponding `changelog.d/`
|
||||
2. **VALIDATED**: added fragment filenames must match `<slug>.<type>.md` with a valid type
|
||||
3. **LINTED**: fragment content must NOT contain a hand-written PR link (`[(#N)](...)`); the gate fails if one is found because the link is attached automatically at compile time
|
||||
4. **SKIP**: Add `no-changelog` label to bypass (use sparingly for docs-only, CI-only changes)
|
||||
1. **REQUIRED**: PRs touching `ui/`, `api/`, `mcp_server/`, or `prowler/` MUST update the corresponding changelog
|
||||
2. **SKIP**: Add `no-changelog` label to bypass (use sparingly for docs-only, CI-only changes)
|
||||
|
||||
## Release flow (compile)
|
||||
## Commands
|
||||
|
||||
- At release time, the `compile-changelogs` workflow (manual dispatch: `prowler_version` + `target_branch`; per-component versions are auto-derived from each changelog's latest stamped heading plus the pending fragment types, with optional explicit overrides or `skip`) resolves each fragment's PR from git history, runs the compiler per component, and opens a `chore(changelog): vX.Y.Z` PR (labeled `no-changelog`) that inserts the stamped `## [X.Y.Z] (Prowler vX.Y.Z)` block into each `CHANGELOG.md` and deletes the consumed fragments. A human reviews and squash-merges it. `prepare-release.yml` then extracts the stamped sections exactly as before.
|
||||
- **Minor release (X.Y.0):** compile on `master` and merge the compile PR BEFORE cutting the `v5.X` branch.
|
||||
- **Patch release (X.Y.Z):** fixes are backported to `v5.X` with their fragment files (conflict-free); compile on `v5.X` and merge its PR there. The same workflow run automatically opens a second forward-sync PR against master (labeled `no-changelog`) that inserts the same stamped block under master's marker and deletes the consumed fragments, so the next minor cannot re-release them; merge it right after. Fragments that only existed on `v5.X` are skipped with a notice. No manual git is involved.
|
||||
- Entries within a section are ordered by PR number ascending (approximately chronological). Do not fight this ordering.
|
||||
```bash
|
||||
# Check which changelogs need updates based on changed files
|
||||
git diff main...HEAD --name-only | grep -E '^(ui|api|mcp_server|prowler)/' | cut -d/ -f1 | sort -u
|
||||
|
||||
## Fixing an already-released entry
|
||||
|
||||
Released version blocks in `CHANGELOG.md` are otherwise immutable, but typo/correction fixes to already-released entries are the one case where a PR edits `CHANGELOG.md` directly: make the edit and add the `no-changelog` label.
|
||||
|
||||
If a PR's entry shipped in the wrong released block (e.g. the PR merged after its release was cut), move the entry back to a fragment: delete it from the released block and recreate it as `<component>/changelog.d/<PR>.<type>.md` (label the PR `no-changelog` since it edits `CHANGELOG.md`).
|
||||
|
||||
## Compiled CHANGELOG.md format (for reference)
|
||||
|
||||
The compiler renders, per release, into each `CHANGELOG.md` right under the `<!-- changelog: release notes start -->` marker (never remove that marker):
|
||||
|
||||
```markdown
|
||||
## [X.Y.Z] (Prowler vA.B.C)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- Entry text [(#NNNN)](https://github.com/prowler-cloud/prowler/pull/NNNN)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- Fix entry [(#NNNN)](https://github.com/prowler-cloud/prowler/pull/NNNN)
|
||||
|
||||
---
|
||||
# View current UNRELEASED section
|
||||
head -50 ui/CHANGELOG.md
|
||||
head -50 api/CHANGELOG.md
|
||||
head -50 mcp_server/CHANGELOG.md
|
||||
head -50 prowler/CHANGELOG.md
|
||||
```
|
||||
|
||||
Section order is always: Added → Changed → Deprecated → Removed → Fixed → Security. `X.Y.Z` is the COMPONENT version; `A.B.C` is the Prowler release version. Every entry ends with its PR link; linking to `/issues/N` is forbidden (the issue↔PR mapping belongs in the PR body via `Fixes #N`).
|
||||
## Migration Note
|
||||
|
||||
**API, MCP Server, and SDK changelogs currently lack emojis.** When editing these files, add emoji prefixes to section headers as you update them:
|
||||
|
||||
```markdown
|
||||
# Before (legacy)
|
||||
### Added
|
||||
|
||||
# After (standardized)
|
||||
### 🚀 Added
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -1,73 +1,101 @@
|
||||
# Changelog Fragment Templates
|
||||
# Changelog Entry Templates
|
||||
|
||||
## Fragment basics
|
||||
## Entry Placement Rule
|
||||
|
||||
One fragment file per entry, under the component's `changelog.d/`:
|
||||
**CRITICAL:** Always add new entries at the **BOTTOM** of each section (before the next section header or `---`).
|
||||
|
||||
```text
|
||||
<component>/changelog.d/<slug>.<type>.md
|
||||
This maintains chronological order: oldest entries at top, newest at bottom.
|
||||
|
||||
## Section Headers
|
||||
|
||||
```markdown
|
||||
### 🚀 Added
|
||||
### 🔄 Changed
|
||||
### ⚠️ Deprecated
|
||||
### ❌ Removed
|
||||
### 🐞 Fixed
|
||||
### 🔐 Security
|
||||
```
|
||||
|
||||
- `<slug>`: free-form, descriptive (`[A-Za-z0-9][A-Za-z0-9._-]*`)
|
||||
- `<type>`: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`
|
||||
- Content: a single line with the entry text. **No PR link** (attached automatically at compile time) and no trailing period.
|
||||
## Entry Patterns
|
||||
|
||||
> **Note:** The section header already provides the verb. Entries describe WHAT, not the action.
|
||||
> **Note:** Section headers already provide the verb. Entries describe WHAT, not the action.
|
||||
>
|
||||
> **Link target rule:** Every entry MUST link to the PR (`https://github.com/prowler-cloud/prowler/pull/N`). Linking to `/issues/N` is FORBIDDEN — even when the PR fixes an issue. The issue↔PR mapping belongs in the PR body (`Fixes #N`), not here.
|
||||
|
||||
## Content Patterns by Type
|
||||
|
||||
### Feature Addition (`.added.md`)
|
||||
|
||||
```text
|
||||
Search bar when adding a provider
|
||||
`{check_id}` check for {provider} provider
|
||||
`/api/v1/{endpoint}` endpoint to {description}
|
||||
### Feature Addition (🚀 Added)
|
||||
```markdown
|
||||
- Search bar when adding a provider [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
- `{check_id}` check for {provider} provider [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
- `/api/v1/{endpoint}` endpoint to {description} [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
```
|
||||
|
||||
### Behavior Change (`.changed.md`)
|
||||
|
||||
```text
|
||||
Lighthouse AI MCP tool filtering from blacklist to whitelist approach
|
||||
{package} from {old} to {new}
|
||||
### Behavior Change (🔄 Changed)
|
||||
```markdown
|
||||
- Lighthouse AI MCP tool filtering from blacklist to whitelist approach [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
- {package} from {old} to {new} [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
```
|
||||
|
||||
### Bug Fix (`.fixed.md`)
|
||||
|
||||
```text
|
||||
OCI update credentials form failing silently due to missing provider UID
|
||||
{What was broken} in {component}
|
||||
### Bug Fix (🐞 Fixed)
|
||||
```markdown
|
||||
- OCI update credentials form failing silently due to missing provider UID [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
- {What was broken} in {component} [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
```
|
||||
|
||||
### Security Patch (`.security.md`)
|
||||
> When a PR fixes a reported issue, the link still goes to the PR (`/pull/N`), never the issue (`/issues/N`). Reference the issue from the PR body with `Fixes #N`.
|
||||
|
||||
```text
|
||||
Node.js from 20.x to 24.13.0 LTS, patching 8 CVEs
|
||||
{package} to version {version} (CVE-XXXX-XXXXX)
|
||||
### Security Patch (🔐 Security)
|
||||
```markdown
|
||||
- Node.js from 20.x to 24.13.0 LTS, patching 8 CVEs [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
- {package} to version {version} (CVE-XXXX-XXXXX) [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
```
|
||||
|
||||
### Removal (`.removed.md`)
|
||||
|
||||
```text
|
||||
Deprecated {feature} from {location}
|
||||
### Removal (❌ Removed)
|
||||
```markdown
|
||||
- Deprecated {feature} from {location} [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
```
|
||||
|
||||
## Full Examples
|
||||
## Version Header Templates
|
||||
|
||||
```bash
|
||||
echo 'Search bar when adding a provider' > ui/changelog.d/provider-search-bar.added.md
|
||||
|
||||
echo '`kms_key_rotation_max_90_days` check for GCP provider, verifying KMS customer-managed keys are rotated every 90 days or less' > prowler/changelog.d/kms-rotation-90d.added.md
|
||||
|
||||
echo 'OCI update credentials form failing silently due to missing provider UID' > ui/changelog.d/oci-credentials-form.fixed.md
|
||||
### Unreleased
|
||||
```markdown
|
||||
## [X.Y.Z] (Prowler UNRELEASED)
|
||||
```
|
||||
|
||||
Several entries in one PR → one file per entry, freely mixing types (different slugs when the type repeats):
|
||||
### Released
|
||||
```markdown
|
||||
## [X.Y.Z] (Prowler vA.B.C)
|
||||
|
||||
```text
|
||||
prowler/changelog.d/kms-rotation-check.added.md
|
||||
prowler/changelog.d/kms-rotation-docs.added.md
|
||||
prowler/changelog.d/kms-metadata-cache.changed.md
|
||||
prowler/changelog.d/kms-disabled-keys.fixed.md
|
||||
---
|
||||
```
|
||||
|
||||
> **Remember:** never include the PR link in the fragment text; the compile step resolves and appends it automatically.
|
||||
## Full Entry Example
|
||||
|
||||
```markdown
|
||||
## [1.17.0] (Prowler UNRELEASED)
|
||||
|
||||
### 🚀 Added
|
||||
|
||||
- Search bar when adding a provider [(#9634)](https://github.com/prowler-cloud/prowler/pull/9634)
|
||||
- New findings table UI with new design system components [(#9699)](https://github.com/prowler-cloud/prowler/pull/9699)
|
||||
- YOUR NEW ENTRY GOES HERE AT BOTTOM [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
- Lighthouse AI MCP tool filtering from blacklist to whitelist approach [(#9802)](https://github.com/prowler-cloud/prowler/pull/9802)
|
||||
- YOUR NEW CHANGE GOES HERE AT BOTTOM [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
- OCI update credentials form failing silently due to missing provider UID [(#9746)](https://github.com/prowler-cloud/prowler/pull/9746)
|
||||
- YOUR NEW FIX GOES HERE AT BOTTOM [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
|
||||
### 🔐 Security
|
||||
|
||||
- Node.js from 20.x to 24.13.0 LTS, patching 8 CVEs [(#9797)](https://github.com/prowler-cloud/prowler/pull/9797)
|
||||
- YOUR NEW SECURITY FIX GOES HERE AT BOTTOM [(#XXXX)](https://github.com/prowler-cloud/prowler/pull/XXXX)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
> **Remember:** Each new entry is added at the BOTTOM of its section to maintain chronological order.
|
||||
|
||||
@@ -31,8 +31,7 @@ Use this skill whenever you are:
|
||||
|
||||
- PR template: `.github/pull_request_template.md`
|
||||
- PR title validation: `.github/workflows/conventional-commit.yml`
|
||||
- Changelog gate: `.github/workflows/pr-check-changelog.yml` (requires a fragment under `<component>/changelog.d/`)
|
||||
- Changelog compile (release time): `.github/workflows/compile-changelogs.yml`
|
||||
- Changelog gate: `.github/workflows/pr-check-changelog.yml`
|
||||
- Conflict markers check: `.github/workflows/pr-conflict-checker.yml`
|
||||
- Secret scanning: `.github/workflows/find-secrets.yml`
|
||||
- Auto labels: `.github/workflows/labeler.yml` and `.github/labeler.yml`
|
||||
@@ -43,7 +42,7 @@ Use this skill whenever you are:
|
||||
1. Identify which workflow/job is failing (name + file under `.github/workflows/`).
|
||||
2. Check path filters: is the workflow supposed to run for your changed files?
|
||||
3. If it's a title check: verify PR title matches Conventional Commits.
|
||||
4. If it's changelog: verify a valid fragment exists under the right `<component>/changelog.d/` OR apply `no-changelog` label.
|
||||
4. If it's changelog: verify the right `CHANGELOG.md` is updated OR apply `no-changelog` label.
|
||||
5. If it's conflict checker: remove `<<<<<<<`, `=======`, `>>>>>>>` markers.
|
||||
6. If it's secrets (TruffleHog): see section below.
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
||||
| JSON Valid | `python3 -m json.tool file.json` | No syntax errors |
|
||||
| All Checks Exist | Run validation script | 0 missing checks |
|
||||
| No Duplicate IDs | Run validation script | 0 duplicate requirement IDs |
|
||||
| Changelog fragment | Manual review | Fragment present under `prowler/changelog.d/` |
|
||||
| CHANGELOG Entry | Manual review | Present under correct version |
|
||||
| Dashboard File | Compare with existing | Follows established pattern |
|
||||
| Framework Metadata | Manual review | All required fields populated |
|
||||
|
||||
@@ -63,8 +63,8 @@ JSON Valid?
|
||||
Duplicate Requirement IDs?
|
||||
├── Yes → FAIL: Fix duplicate IDs
|
||||
└── No ↓
|
||||
Changelog Fragment Present?
|
||||
├── No → REQUEST CHANGES: Add changelog fragment
|
||||
CHANGELOG Entry Present?
|
||||
├── No → REQUEST CHANGES: Add CHANGELOG entry
|
||||
└── Yes ↓
|
||||
Dashboard File Follows Pattern?
|
||||
├── No → REQUEST CHANGES: Fix dashboard pattern
|
||||
@@ -124,7 +124,7 @@ Compliance frameworks are JSON files in: `prowler/compliance/{provider}/{framewo
|
||||
| Empty Checks for Automated | AssessmentStatus is Automated but Checks is empty | Add checks or change to Manual |
|
||||
| Wrong file location | Framework not in `prowler/compliance/{provider}/` | Move to correct directory |
|
||||
| Missing dashboard file | No corresponding `dashboard/compliance/{framework}.py` | Create dashboard file following pattern |
|
||||
| Changelog fragment missing | No fragment file in the PR diff | Add a fragment under prowler/changelog.d/ |
|
||||
| CHANGELOG missing | Not under correct version section | Add entry to prowler/CHANGELOG.md |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|-----------|----------|
|
||||
| Compliance JSON | `prowler/compliance/{provider}/{framework}.json` |
|
||||
| Dashboard | `dashboard/compliance/{framework}_{provider}.py` |
|
||||
| Changelog fragment | `prowler/changelog.d/` |
|
||||
| CHANGELOG | `prowler/CHANGELOG.md` |
|
||||
| Checks | `prowler/providers/{provider}/services/{service}/{check}/` |
|
||||
|
||||
## Validation Script
|
||||
@@ -40,7 +40,7 @@ When completing a compliance framework review, use this summary format:
|
||||
| JSON Valid | PASS/FAIL |
|
||||
| All Checks Exist | PASS/FAIL (N missing) |
|
||||
| No Duplicate IDs | PASS/FAIL |
|
||||
| Changelog fragment | PASS/FAIL |
|
||||
| CHANGELOG Entry | PASS/FAIL |
|
||||
| Dashboard File | PASS/FAIL |
|
||||
|
||||
### Statistics
|
||||
|
||||
+10
-10
@@ -56,7 +56,7 @@ allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
||||
- [ ] Review if code is being documented following https://github.com/google/styleguide/blob/gh-pages/pyguide.md#38-comments-and-docstrings
|
||||
- [ ] Review if backport is needed.
|
||||
- [ ] Review if is needed to change the Readme.md
|
||||
- [ ] Ensure a changelog fragment is added under <component>/changelog.d/, if applicable.
|
||||
- [ ] Ensure new entries are added to CHANGELOG.md, if applicable.
|
||||
|
||||
#### SDK/CLI
|
||||
- Are there new checks included in this PR? Yes / No
|
||||
@@ -67,7 +67,7 @@ allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
||||
- [ ] Screenshots/Video - Mobile (X < 640px)
|
||||
- [ ] Screenshots/Video - Tablet (640px > X < 1024px)
|
||||
- [ ] Screenshots/Video - Desktop (X > 1024px)
|
||||
- [ ] Ensure a changelog fragment is added under ui/changelog.d/
|
||||
- [ ] Ensure new entries are added to ui/CHANGELOG.md
|
||||
|
||||
#### API (if applicable)
|
||||
- [ ] All issue/task requirements work as expected on the API
|
||||
@@ -77,7 +77,7 @@ allowed-tools: Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task
|
||||
- [ ] Any other relevant evidence of the implementation (if applicable)
|
||||
- [ ] Verify if API specs need to be regenerated.
|
||||
- [ ] Check if version updates are required.
|
||||
- [ ] Ensure a changelog fragment is added under api/changelog.d/
|
||||
- [ ] Ensure new entries are added to api/CHANGELOG.md
|
||||
|
||||
### License
|
||||
|
||||
@@ -86,12 +86,12 @@ By submitting this pull request, I confirm that my contribution is made under th
|
||||
|
||||
## Component-Specific Rules
|
||||
|
||||
| Component | Changelog fragment | Extra Checks |
|
||||
|-----------|--------------------|--------------|
|
||||
| SDK | `prowler/changelog.d/` | New checks → permissions update? |
|
||||
| API | `api/changelog.d/` | API specs, version bump, endpoint output, EXPLAIN ANALYZE, performance |
|
||||
| UI | `ui/changelog.d/` | Screenshots for Mobile/Tablet/Desktop |
|
||||
| MCP | `mcp_server/changelog.d/` | N/A |
|
||||
| Component | CHANGELOG | Extra Checks |
|
||||
|-----------|-----------|--------------|
|
||||
| SDK | `prowler/CHANGELOG.md` | New checks → permissions update? |
|
||||
| API | `api/CHANGELOG.md` | API specs, version bump, endpoint output, EXPLAIN ANALYZE, performance |
|
||||
| UI | `ui/CHANGELOG.md` | Screenshots for Mobile/Tablet/Desktop |
|
||||
| MCP | `mcp_server/CHANGELOG.md` | N/A |
|
||||
|
||||
## Commands
|
||||
|
||||
@@ -128,7 +128,7 @@ Follow conventional commits:
|
||||
|
||||
1. ✅ All tests pass locally
|
||||
2. ✅ Linting passes (`make lint` or component-specific)
|
||||
3. ✅ Changelog fragment added (if applicable)
|
||||
3. ✅ CHANGELOG updated (if applicable)
|
||||
4. ✅ Branch is up to date with main
|
||||
5. ✅ Commits are clean and descriptive
|
||||
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
import importlib.util
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
MODULE_PATH = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ ".github"
|
||||
/ "scripts"
|
||||
/ "changelog_attribution.py"
|
||||
)
|
||||
SPEC = importlib.util.spec_from_file_location("changelog_attribution", MODULE_PATH)
|
||||
changelog_attribution = importlib.util.module_from_spec(SPEC)
|
||||
assert SPEC.loader is not None
|
||||
SPEC.loader.exec_module(changelog_attribution)
|
||||
|
||||
|
||||
def make_component(tmp_path):
|
||||
component = tmp_path / "prowler"
|
||||
fragments_dir = component / "changelog.d"
|
||||
fragments_dir.mkdir(parents=True)
|
||||
return component, fragments_dir
|
||||
|
||||
|
||||
def fake_git_mv(*args):
|
||||
if args[0] != "mv":
|
||||
raise AssertionError(f"Unexpected git command: {args}")
|
||||
shutil.move(args[1], args[2])
|
||||
return ""
|
||||
|
||||
|
||||
def fail_git(*args):
|
||||
raise AssertionError(f"Unexpected git command: {args}")
|
||||
|
||||
|
||||
def fail_api(*args):
|
||||
raise AssertionError(f"Unexpected GitHub API call: {args}")
|
||||
|
||||
|
||||
def run_main(monkeypatch, component, *extra_args):
|
||||
monkeypatch.setattr(
|
||||
sys,
|
||||
"argv",
|
||||
["changelog_attribution.py", str(component), *extra_args],
|
||||
)
|
||||
return changelog_attribution.main()
|
||||
|
||||
|
||||
class TestChangelogAttribution:
|
||||
def test_renames_slug_fragment_to_resolved_pr_number(self, tmp_path, monkeypatch):
|
||||
component, fragments_dir = make_component(tmp_path)
|
||||
fragment = fragments_dir / "add-workflow.fixed.md"
|
||||
fragment.write_text("Fix changelog workflow.\n")
|
||||
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution, "find_adding_commit", lambda path: "abc123"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution, "pr_from_api", lambda repo, sha: 11572
|
||||
)
|
||||
monkeypatch.setattr(changelog_attribution, "git", fake_git_mv)
|
||||
|
||||
assert run_main(monkeypatch, component) == 0
|
||||
assert not fragment.exists()
|
||||
assert (fragments_dir / "11572.fixed.md").read_text() == (
|
||||
"Fix changelog workflow.\n"
|
||||
)
|
||||
|
||||
def test_appends_counter_when_pr_fragment_already_exists(
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
component, fragments_dir = make_component(tmp_path)
|
||||
(fragments_dir / "11572.fixed.md").write_text("Existing fix.\n")
|
||||
fragment = fragments_dir / "another-fix.fixed.md"
|
||||
fragment.write_text("Another fix.\n")
|
||||
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution, "find_adding_commit", lambda path: "abc123"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution, "pr_from_api", lambda repo, sha: 11572
|
||||
)
|
||||
monkeypatch.setattr(changelog_attribution, "git", fake_git_mv)
|
||||
|
||||
assert run_main(monkeypatch, component) == 0
|
||||
assert (fragments_dir / "11572.fixed.md").read_text() == "Existing fix.\n"
|
||||
assert (fragments_dir / "11572.fixed.1.md").read_text() == "Another fix.\n"
|
||||
|
||||
def test_uses_subject_fallback_when_api_is_disabled(self, tmp_path, monkeypatch):
|
||||
component, fragments_dir = make_component(tmp_path)
|
||||
fragment = fragments_dir / "fallback.added.md"
|
||||
fragment.write_text("Add fallback behavior.\n")
|
||||
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution, "find_adding_commit", lambda path: "abc123"
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution,
|
||||
"pr_from_api",
|
||||
fail_api,
|
||||
)
|
||||
monkeypatch.setattr(changelog_attribution, "pr_from_subject", lambda sha: 42)
|
||||
monkeypatch.setattr(changelog_attribution, "git", fake_git_mv)
|
||||
|
||||
assert run_main(monkeypatch, component, "--no-api") == 0
|
||||
assert not fragment.exists()
|
||||
assert (fragments_dir / "42.added.md").read_text() == "Add fallback behavior.\n"
|
||||
|
||||
def test_renames_unresolved_fragment_to_orphan(self, tmp_path, monkeypatch, capsys):
|
||||
component, fragments_dir = make_component(tmp_path)
|
||||
fragment = fragments_dir / "manual.changed.md"
|
||||
fragment.write_text("Change manual entry.\n")
|
||||
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution, "find_adding_commit", lambda path: None
|
||||
)
|
||||
monkeypatch.setattr(changelog_attribution, "git", fake_git_mv)
|
||||
|
||||
assert run_main(monkeypatch, component) == 0
|
||||
assert not fragment.exists()
|
||||
assert (fragments_dir / "+manual.changed.md").read_text() == (
|
||||
"Change manual entry.\n"
|
||||
)
|
||||
assert "Could not resolve a PR" in capsys.readouterr().out
|
||||
|
||||
def test_rejects_malformed_fragment_names(self, tmp_path, monkeypatch, capsys):
|
||||
component, fragments_dir = make_component(tmp_path)
|
||||
fragment = fragments_dir / "bad.bugfix.md"
|
||||
fragment.write_text("Invalid type.\n")
|
||||
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution,
|
||||
"git",
|
||||
fail_git,
|
||||
)
|
||||
|
||||
assert run_main(monkeypatch, component) == 1
|
||||
assert fragment.exists()
|
||||
assert "Malformed fragment filename" in capsys.readouterr().out
|
||||
|
||||
def test_skips_fragments_that_already_start_with_pr_number(
|
||||
self, tmp_path, monkeypatch
|
||||
):
|
||||
component, fragments_dir = make_component(tmp_path)
|
||||
fragment = fragments_dir / "11572.added.md"
|
||||
fragment.write_text("Already attributed.\n")
|
||||
|
||||
monkeypatch.setattr(
|
||||
changelog_attribution,
|
||||
"git",
|
||||
fail_git,
|
||||
)
|
||||
|
||||
assert run_main(monkeypatch, component) == 0
|
||||
assert fragment.read_text() == "Already attributed.\n"
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
All notable changes to the **Prowler UI** are documented in this file.
|
||||
|
||||
<!-- changelog: release notes start -->
|
||||
|
||||
## [1.30.1] (Prowler v5.30.1)
|
||||
|
||||
### 🐞 Fixed
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
# Changelog fragments
|
||||
|
||||
Each PR adds one small file here instead of editing `CHANGELOG.md` directly, so concurrent PRs never conflict.
|
||||
|
||||
- Filename: `<slug>.<type>.md`, e.g. `my-new-check.added.md` (slug is free-form: letters, digits, `.`, `_`, `-`)
|
||||
- `<type>` is one of: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`
|
||||
- Content: one line with the changelog entry text, without the PR link and without a trailing period (the PR link is attached automatically at release time)
|
||||
- A PR adds as many fragment files as entries it needs, freely mixing types (one file per entry); same-type entries just use different slugs
|
||||
|
||||
Fragments are compiled into `CHANGELOG.md` when a release is prepared. Full conventions: `skills/prowler-changelog/SKILL.md`.
|
||||
@@ -1,39 +0,0 @@
|
||||
[tool.towncrier]
|
||||
directory = "changelog.d"
|
||||
filename = "CHANGELOG.md"
|
||||
start_string = "<!-- changelog: release notes start -->\n"
|
||||
title_format = "## [{version}] ({name})"
|
||||
issue_format = "[(#{issue})](https://github.com/prowler-cloud/prowler/pull/{issue})"
|
||||
template = "../.github/towncrier/template.md.jinja"
|
||||
underlines = ["", "", ""]
|
||||
ignore = [".gitkeep", "README.md"]
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "added"
|
||||
name = "🚀 Added"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "changed"
|
||||
name = "🔄 Changed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "deprecated"
|
||||
name = "⚠️ Deprecated"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "removed"
|
||||
name = "❌ Removed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "fixed"
|
||||
name = "🐞 Fixed"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "security"
|
||||
name = "🔐 Security"
|
||||
showcontent = true
|
||||
Reference in New Issue
Block a user