From f0ae56b8ea08f13b36e612f960453d8c713fb980 Mon Sep 17 00:00:00 2001 From: lydiavilchez <114735608+lydiavilchez@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:35:52 +0200 Subject: [PATCH] feat(docs): auto-generate provider cards in Prowler App tutorial (#11865) --- .pre-commit-config.yaml | 20 ++- docs/scripts/generate_provider_cards.py | 155 ++++++++++++++++++++++ docs/snippets/provider-cards.mdx | 23 ++++ docs/user-guide/tutorials/prowler-app.mdx | 30 +---- 4 files changed, 196 insertions(+), 32 deletions(-) create mode 100644 docs/scripts/generate_provider_cards.py create mode 100644 docs/snippets/provider-cards.mdx diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 159c1f5a16..8f4caec382 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -72,13 +72,13 @@ repos: exclude: contrib priority: 30 - ## PYTHON — SDK (prowler/, tests/, dashboard/, util/, scripts/) + ## PYTHON — SDK (prowler/, tests/, dashboard/, util/, scripts/, docs/scripts/) - repo: https://github.com/myint/autoflake rev: v2.3.3 hooks: - id: autoflake name: "SDK - autoflake" - files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] } + files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] } args: ["--in-place", "--remove-all-unused-imports", "--remove-unused-variable"] priority: 20 @@ -87,7 +87,7 @@ repos: hooks: - id: isort name: "SDK - isort" - files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] } + files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] } args: ["--profile", "black"] stages: ["pre-commit"] priority: 20 @@ -97,7 +97,7 @@ repos: hooks: - id: black name: "SDK - black" - files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] } + files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] } priority: 20 - repo: https://github.com/pycqa/flake8 @@ -105,7 +105,7 @@ repos: hooks: - id: flake8 name: "SDK - flake8" - files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] } + files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] } args: ["--ignore=E266,W503,E203,E501,W605"] priority: 30 @@ -142,6 +142,14 @@ repos: files: { glob: ["mcp_server/**/*.py"] } priority: 20 + - id: generate-provider-cards + name: "Docs - regenerate provider cards snippet" + entry: python docs/scripts/generate_provider_cards.py + language: system + files: { glob: ["docs/user-guide/providers/**/getting-started-*.mdx", "docs/scripts/generate_provider_cards.py", "docs/snippets/provider-cards.mdx", "api/src/backend/api/models.py"] } + pass_filenames: false + priority: 20 + ## PYTHON — uv (API + SDK) - repo: https://github.com/astral-sh/uv-pre-commit rev: 0.11.14 @@ -183,7 +191,7 @@ repos: entry: pylint --disable=W,C,R,E -j 0 -rn -sn language: system types: [python] - files: { glob: ["{prowler,tests,dashboard,util,scripts}/**/*.py"] } + files: { glob: ["{prowler,tests,dashboard,util,scripts,docs/scripts}/**/*.py"] } priority: 30 - id: trufflehog diff --git a/docs/scripts/generate_provider_cards.py b/docs/scripts/generate_provider_cards.py new file mode 100644 index 0000000000..3b02574978 --- /dev/null +++ b/docs/scripts/generate_provider_cards.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Generate docs/snippets/provider-cards.mdx from provider getting-started pages. + +Scans docs/user-guide/providers//getting-started-*.mdx, keeps only the +providers that Prowler App/Cloud actually supports (source of truth: the +`ProviderChoices` enum in api/src/backend/api/models.py — CLI-only providers +such as Linode/LLM/Scaleway/StackIT are excluded), reads the frontmatter +`title`, derives a display name, and emits a snippet exporting a +`ProviderCards` component. Wired into pre-commit so the snippet stays in sync +whenever a provider page or the API enum changes. +""" + +from __future__ import annotations + +import ast +import re +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +PROVIDERS_DIR = REPO_ROOT / "docs" / "user-guide" / "providers" +SNIPPET_PATH = REPO_ROOT / "docs" / "snippets" / "provider-cards.mdx" +API_MODELS_PATH = REPO_ROOT / "api" / "src" / "backend" / "api" / "models.py" + +# Docs folder names that don't match the API enum key. Keep tiny — only rename +# entries when the docs folder disagrees with the API-side identifier. +DOCS_DIR_TO_API_KEY = { + "microsoft365": "m365", + "oci": "oraclecloud", +} + +# Folder-name → Mintlify icon override. Providers not listed fall back to +# DEFAULT_ICON. Add an entry only when the default looks wrong for a provider. +ICON_OVERRIDES = { + "alibabacloud": "cloud", + "aws": "aws", + "azure": "microsoft", + "cloudflare": "cloudflare", + "gcp": "google", + "github": "github", + "googleworkspace": "users", + "iac": "code", + "image": "docker", + "kubernetes": "dharmachakra", + "microsoft365": "briefcase", + "mongodbatlas": "leaf", + "oci": "database", + "okta": "key", + "openstack": "cubes", + "vercel": "triangle", +} +DEFAULT_ICON = "cloud" + +TITLE_RE = re.compile(r"^\s*title\s*:\s*['\"](?P.+?)['\"]\s*$", re.MULTILINE) +NAME_CLEANUP_RE = re.compile( + r"^Getting Started [Ww]ith (?:the )?(?P<name>.+?)(?: on Prowler)?(?: Provider)?$" +) + + +def app_supported_provider_keys() -> set[str]: + """Return the set of provider keys declared in the API's ProviderChoices enum. + + Uses ast rather than regex so formatting changes, decorators, comments, or + multi-line values in the enum body don't silently drop or invent providers. + """ + tree = ast.parse(API_MODELS_PATH.read_text(encoding="utf-8")) + for node in ast.walk(tree): + if not (isinstance(node, ast.ClassDef) and node.name == "ProviderChoices"): + continue + keys: set[str] = set() + for item in node.body: + if not isinstance(item, ast.Assign): + continue + value = item.value + # Django TextChoices members look like: NAME = "key", _("Label") + # which parses as an ast.Tuple whose first element is the key. + if isinstance(value, ast.Tuple) and value.elts: + first = value.elts[0] + if isinstance(first, ast.Constant) and isinstance(first.value, str): + keys.add(first.value) + return keys + raise RuntimeError( + f"Could not locate ProviderChoices class in {API_MODELS_PATH.relative_to(REPO_ROOT)}" + ) + + +def extract_title(mdx_path: Path) -> str: + text = mdx_path.read_text(encoding="utf-8") + match = TITLE_RE.search(text) + if not match: + raise ValueError(f"No frontmatter title in {mdx_path}") + return match.group("title") + + +def display_name(title: str) -> str: + match = NAME_CLEANUP_RE.match(title) + return match.group("name") if match else title + + +def collect_providers() -> list[dict]: + supported = app_supported_provider_keys() + providers = [] + for provider_dir in sorted(PROVIDERS_DIR.iterdir()): + if not provider_dir.is_dir(): + continue + api_key = DOCS_DIR_TO_API_KEY.get(provider_dir.name, provider_dir.name) + if api_key not in supported: + continue + pages = sorted(provider_dir.glob("getting-started-*.mdx")) + if not pages: + continue + page = pages[0] + name = display_name(extract_title(page)) + href = f"/user-guide/providers/{provider_dir.name}/{page.stem}" + icon = ICON_OVERRIDES.get(provider_dir.name, DEFAULT_ICON) + providers.append({"name": name, "href": href, "icon": icon}) + providers.sort(key=lambda p: p["name"].lower()) + return providers + + +def render_snippet(providers: list[dict]) -> str: + cards = "\n".join( + f' <Card title="{p["name"]}" icon="{p["icon"]}" href="{p["href"]}" />' + for p in providers + ) + return ( + "{/* AUTO-GENERATED by docs/scripts/generate_provider_cards.py — do not edit by hand. */}\n" + "{/* Regenerated on pre-commit whenever any provider getting-started page changes. */}\n" + "\n" + "export const ProviderCards = () => (\n" + " <Columns cols={3}>\n" + f"{cards}\n" + " </Columns>\n" + ");\n" + ) + + +def main() -> int: + providers = collect_providers() + if not providers: + print("No provider getting-started pages found", file=sys.stderr) + return 1 + new_content = render_snippet(providers) + current = SNIPPET_PATH.read_text(encoding="utf-8") if SNIPPET_PATH.exists() else "" + if new_content == current: + return 0 + SNIPPET_PATH.write_text(new_content, encoding="utf-8") + print( + f"Regenerated {SNIPPET_PATH.relative_to(REPO_ROOT)} ({len(providers)} providers)" + ) + return 1 # signal pre-commit that the file changed + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/snippets/provider-cards.mdx b/docs/snippets/provider-cards.mdx new file mode 100644 index 0000000000..f469616cde --- /dev/null +++ b/docs/snippets/provider-cards.mdx @@ -0,0 +1,23 @@ +{/* AUTO-GENERATED by docs/scripts/generate_provider_cards.py — do not edit by hand. */} +{/* Regenerated on pre-commit whenever any provider getting-started page changes. */} + +export const ProviderCards = () => ( + <Columns cols={3}> + <Card title="Alibaba Cloud" icon="cloud" href="/user-guide/providers/alibabacloud/getting-started-alibabacloud" /> + <Card title="AWS" icon="aws" href="/user-guide/providers/aws/getting-started-aws" /> + <Card title="Azure" icon="microsoft" href="/user-guide/providers/azure/getting-started-azure" /> + <Card title="Cloudflare" icon="cloudflare" href="/user-guide/providers/cloudflare/getting-started-cloudflare" /> + <Card title="GCP" icon="google" href="/user-guide/providers/gcp/getting-started-gcp" /> + <Card title="GitHub" icon="github" href="/user-guide/providers/github/getting-started-github" /> + <Card title="Google Workspace" icon="users" href="/user-guide/providers/googleworkspace/getting-started-googleworkspace" /> + <Card title="IaC" icon="code" href="/user-guide/providers/iac/getting-started-iac" /> + <Card title="Image" icon="docker" href="/user-guide/providers/image/getting-started-image" /> + <Card title="Kubernetes" icon="dharmachakra" href="/user-guide/providers/kubernetes/getting-started-k8s" /> + <Card title="Microsoft 365" icon="briefcase" href="/user-guide/providers/microsoft365/getting-started-m365" /> + <Card title="MongoDB Atlas" icon="leaf" href="/user-guide/providers/mongodbatlas/getting-started-mongodbatlas" /> + <Card title="Okta" icon="key" href="/user-guide/providers/okta/getting-started-okta" /> + <Card title="OpenStack" icon="cubes" href="/user-guide/providers/openstack/getting-started-openstack" /> + <Card title="Oracle Cloud Infrastructure (OCI)" icon="database" href="/user-guide/providers/oci/getting-started-oci" /> + <Card title="Vercel" icon="triangle" href="/user-guide/providers/vercel/getting-started-vercel" /> + </Columns> +); diff --git a/docs/user-guide/tutorials/prowler-app.mdx b/docs/user-guide/tutorials/prowler-app.mdx index 19bd48ee60..51ebdde861 100644 --- a/docs/user-guide/tutorials/prowler-app.mdx +++ b/docs/user-guide/tutorials/prowler-app.mdx @@ -2,6 +2,8 @@ title: 'Prowler Cloud' --- +import { ProviderCards } from "/snippets/provider-cards.mdx" + **Prowler Cloud** is a web application that simplifies running Prowler. This tutorial will guide you through setting up and using it. We refer to **Prowler App** as the self-hosted version of **Prowler Cloud**. @@ -79,32 +81,8 @@ Select the cloud provider to scan and configure authentication credentials. Each For detailed instructions on configuring credentials for each provider, refer to the provider-specific getting started guides: -<Columns cols={3}> - <Card title="AWS" icon="aws" href="/user-guide/providers/aws/getting-started-aws"> - Configure AWS authentication using IAM Access Keys or Assumed Role credentials. - </Card> - <Card title="Azure" icon="microsoft" href="/user-guide/providers/azure/getting-started-azure"> - Set up Azure authentication using Service Principal credentials. - </Card> - <Card title="Google Cloud" icon="google" href="/user-guide/providers/gcp/getting-started-gcp"> - Configure GCP authentication with Service Account or Application Default Credentials. - </Card> - <Card title="Oracle Cloud Infrastructure" icon="cloud" href="/user-guide/providers/oci/getting-started-oci"> - Connect OCI with API key credentials to scan compartments and regions. - </Card> - <Card title="Kubernetes" icon="cloud" href="/user-guide/providers/kubernetes/getting-started-k8s"> - Set up Kubernetes authentication using kubeconfig files for cluster access. - </Card> - <Card title="Microsoft 365" icon="microsoft" href="/user-guide/providers/microsoft365/getting-started-m365"> - Configure M365 authentication with Application Certificate or Client Secret. - </Card> - <Card title="GitHub" icon="github" href="/user-guide/providers/github/getting-started-github"> - Set up GitHub authentication using Personal Access Token, OAuth App, or GitHub App. - </Card> - <Card title="Infrastructure as Code" icon="code" href="/user-guide/providers/iac/getting-started-iac"> - Scan IaC public or private repositories for security issues. - </Card> -</Columns> +<ProviderCards /> + ## Step 5: Test Connection After adding your credentials of your cloud account, click the `Launch` button to verify that Prowler App can successfully connect to your provider: