From 3c4c03509689ebacc5324349966f8e64dc9f5576 Mon Sep 17 00:00:00 2001 From: Dominika Zemanovicova Date: Tue, 23 Jun 2026 15:28:15 +0200 Subject: [PATCH] Fetch plugin metadata with branch Assisted-By: Cursor Signed-off-by: Dominika Zemanovicova --- .claude-plugin/marketplace.json | 4 +- .claude-plugin/plugin.json | 2 +- pyproject.toml | 2 +- skills/overlay/SKILL.md | 39 ++++++++--- skills/overlay/references/metadata-format.md | 6 +- .../scripts/fetch-plugin-metadata.py | 68 +++++++++++++------ uv.lock | 2 +- 7 files changed, 85 insertions(+), 38 deletions(-) diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 3d1f67c..950f45d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -5,14 +5,14 @@ }, "metadata": { "description": "Orchestrator skill for RHDH plugin development - onboard, update, and maintain plugins in the Extensions Catalog", - "version": "0.5.0" + "version": "0.5.1" }, "plugins": [ { "name": "rhdh", "source": "./", "description": "Skills for RHDH plugin lifecycle management", - "version": "0.5.0", + "version": "0.5.1", "strict": true } ] diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 758136d..33826cb 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "rhdh", "description": "All-in-one toolkit for Red Hat Developer Hub (RHDH). Covers plugin development, overlay management, environment setup, version compatibility, CI/CD, and RHDH ecosystem navigation.", - "version": "0.5.0", + "version": "0.5.1", "author": { "name": "RHDH Store Manager" }, diff --git a/pyproject.toml b/pyproject.toml index bad9a86..7c709ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "rhdh-skill" -version = "0.5.0" +version = "0.5.1" description = "Claude Code skill for RHDH plugin development" readme = "README.md" license = "Apache-2.0" diff --git a/skills/overlay/SKILL.md b/skills/overlay/SKILL.md index 6cb0af2..ff71d62 100644 --- a/skills/overlay/SKILL.md +++ b/skills/overlay/SKILL.md @@ -3,11 +3,13 @@ name: overlay description: >- Manages the rhdh-plugin-export-overlays repository — onboards plugins to the Extensions Catalog, updates plugin versions, fixes overlay build failures, - triages and analyzes PRs, triggers publishes, and manages plugin workspaces. + triages and analyzes PRs, triggers publishes, manages plugin workspaces, and + looks up plugin metadata. Use when working with overlays, importing plugins, debugging CI, checking PRs, - bumping versions, or mentions "Extensions Catalog", "overlay build failed", - "plugin registry", "overlay PR", "overlay doctor", "plugin import", - "add plugin to catalog", "onboard plugin", or "plugin workspace". + bumping versions, looking up plugin metadata, or mentions "Extensions + Catalog", "overlay build failed", "plugin registry", "overlay PR", "overlay + doctor", "plugin import", "add plugin to catalog", "onboard plugin", "plugin + workspace", "plugin installation reference". --- @@ -70,14 +72,15 @@ What overlay task would you like to do? 2. **Update plugin version** — Bump to newer upstream commit/tag 3. **Check plugin status** — Verify health and compatibility 4. **Fix build failure** — Debug CI/publish issues +5. **Lookup plugin metadata** — Get plugin version and artifact reference by branch ### Core Team Tasks *For COPE/Plugins team managing the overlay repository* -5. **Triage overlay PRs** — Prioritize open PRs by criticality -6. **Analyze specific PR** — Check assignment, compatibility, merge readiness -7. **Trigger publish** — Add /publish comment to PR(s) +6. **Triage overlay PRs** — Prioritize open PRs by criticality +7. **Analyze specific PR** — Check assignment, compatibility, merge readiness +8. **Trigger publish** — Add /publish comment to PR(s) **Wait for response before proceeding.** @@ -91,14 +94,15 @@ What overlay task would you like to do? | 2, "update", "bump", "upgrade", "version" | `workflows/update-plugin.md` | | 3, "status", "check", "health" | Run inline status checks | | 4, "fix", "debug", "failure", "error" | `workflows/fix-build.md` | +| 5, "metadata", "artifact", "dynamicArtifact", "plugin version", "installation tag" | Run inline metadata lookup | ### Core Team Routes | Response | Workflow | |----------|----------| -| 5, "triage", "prioritize", "backlog" | `workflows/triage-prs.md` | -| 6, "analyze", "check PR", "PR #" | `workflows/analyze-pr.md` | -| 7, "publish", "trigger" | Run inline publish trigger | +| 6, "triage", "prioritize", "backlog" | `workflows/triage-prs.md` | +| 7, "analyze", "check PR", "PR #" | `workflows/analyze-pr.md` | +| 8, "publish", "trigger" | Run inline publish trigger | **After reading the workflow, follow it exactly.** @@ -123,6 +127,21 @@ gh pr list --repo redhat-developer/rhdh-plugin-export-overlays --search "" + +For plugin metadata lookup (e.g. version, package name) or for installation tag/reference lookup (`spec.dynamicArtifact` metadata), use: + +```bash +# Preferred (if rhdh-local skill is installed) +python skills/rhdh-local/scripts/fetch-plugin-metadata.py --branch +``` + +Fallback (overlay-only install): resolve `spec.packages` from +`catalog-entities/extensions/plugins/.yaml` first, then locate each +package metadata file under `workspaces//metadata/.yaml`, and +read metadata there. + + + For triggering publish on one or more PRs: diff --git a/skills/overlay/references/metadata-format.md b/skills/overlay/references/metadata-format.md index 2c3766d..2bc54ce 100644 --- a/skills/overlay/references/metadata-format.md +++ b/skills/overlay/references/metadata-format.md @@ -22,6 +22,9 @@ metadata: description: spec: packageName: # e.g., @aws/backstage-plugin-aws-codebuild + # Artifact reference used by dynamic plugin loading (OCI image, tgz archive, npm package, or a directory path) + dynamicArtifact: + version: # Dynamic plugin configuration dynamicPluginConfig: @@ -46,6 +49,7 @@ spec: **Key sections:** +- `dynamicArtifact` — artifact reference used for plugin loading (OCI image, tgz archive, npm package, or a directory path) - `dynamicPluginConfig` — how the plugin mounts in RHDH - `appConfigExamples` — configuration snippets for users @@ -103,5 +107,5 @@ Add to `catalog-entities/marketplace/plugins/all.yaml` (alphabetical order). -Full annotated example: [catalog-entities/marketplace/README.md](https://github.com/redhat-developer/rhdh-plugin-export-overlays/blob/main/catalog-entities/marketplace/README.md) +Full annotated example: [catalog-entities/extensions/README.md](https://github.com/redhat-developer/rhdh-plugin-export-overlays/blob/main/catalog-entities/extensions/README.md) diff --git a/skills/rhdh-local/scripts/fetch-plugin-metadata.py b/skills/rhdh-local/scripts/fetch-plugin-metadata.py index c61679d..bfd9170 100644 --- a/skills/rhdh-local/scripts/fetch-plugin-metadata.py +++ b/skills/rhdh-local/scripts/fetch-plugin-metadata.py @@ -14,17 +14,28 @@ import json import sys import urllib.error +import urllib.parse import urllib.request from typing import Any # --------------------------------------------------------------------------- # GitHub URLs # --------------------------------------------------------------------------- -_CONTENTS_URL = ( - "https://api.github.com/repos/redhat-developer/rhdh-plugin-export-overlays" - "/contents/catalog-entities/extensions/plugins" -) -_RAW_BASE = "https://raw.githubusercontent.com/redhat-developer/rhdh-plugin-export-overlays/main" +_API_REPO_BASE = "https://api.github.com/repos/redhat-developer/rhdh-plugin-export-overlays" +_RAW_REPO_BASE = "https://raw.githubusercontent.com/redhat-developer/rhdh-plugin-export-overlays" + + +def _contents_url(path: str, branch: str) -> str: + """Build a GitHub Contents API URL pinned to the requested branch.""" + encoded_branch = urllib.parse.quote(branch, safe="") + return f"{_API_REPO_BASE}/contents/{path}?ref={encoded_branch}" + + +def _raw_url(path: str, branch: str) -> str: + """Build a raw.githubusercontent URL pinned to the requested branch.""" + encoded_branch = urllib.parse.quote(branch, safe="") + return f"{_RAW_REPO_BASE}/{encoded_branch}/{path}" + # ANSI helpers _RED = "\033[0;31m" @@ -311,9 +322,9 @@ def _fetch_yaml(url: str) -> dict[str, Any]: # --------------------------------------------------------------------------- -def list_plugins() -> list[str]: +def list_plugins(branch: str = "main") -> list[str]: """Return sorted list of available plugin names.""" - entries = _fetch_json(_CONTENTS_URL) + entries = _fetch_json(_contents_url("catalog-entities/extensions/plugins", branch)) names: list[str] = [] for entry in entries: name = entry.get("name", "") @@ -333,15 +344,12 @@ def _get(d: dict, *keys: str, default: Any = None) -> Any: return cur -def _list_workspace_metadata(workspace: str) -> list[str]: +def _list_workspace_metadata(workspace: str, branch: str = "main") -> list[str]: """List metadata file names (without .yaml) in a workspace. Returns an empty list if the workspace or metadata dir doesn't exist. """ - url = ( - f"https://api.github.com/repos/redhat-developer/" - f"rhdh-plugin-export-overlays/contents/workspaces/{workspace}/metadata" - ) + url = _contents_url(f"workspaces/{workspace}/metadata", branch) try: entries = _fetch_json(url) except urllib.error.HTTPError: @@ -423,13 +431,13 @@ def _find_metadata_file( return None -def fetch_plugin_metadata(plugin_name: str) -> dict[str, Any]: +def fetch_plugin_metadata(plugin_name: str, branch: str = "main") -> dict[str, Any]: """Fetch plugin definition + per-package metadata. Returns a structured dict with plugin info and package details. """ # Step 1: plugin definition - plugin_url = f"{_RAW_BASE}/catalog-entities/extensions/plugins/{plugin_name}.yaml" + plugin_url = _raw_url(f"catalog-entities/extensions/plugins/{plugin_name}.yaml", branch) try: plugin_def = _fetch_yaml(plugin_url) except urllib.error.HTTPError as exc: @@ -453,7 +461,7 @@ def fetch_plugin_metadata(plugin_name: str) -> dict[str, Any]: # Step 2: per-package metadata # Pre-fetch the workspace metadata directory listing for the primary workspace - primary_available = _list_workspace_metadata(plugin_name) + primary_available = _list_workspace_metadata(plugin_name, branch) package_results: list[dict[str, Any]] = [] for pkg in packages: @@ -473,25 +481,28 @@ def fetch_plugin_metadata(plugin_name: str) -> dict[str, Any]: if "backstage" not in (plugin_name, alt_ws): fallback_workspaces.append("backstage") for ws in fallback_workspaces: - alt_available = _list_workspace_metadata(ws) + alt_available = _list_workspace_metadata(ws, branch) match = _find_metadata_file(pkg_name, ws, alt_available) if match is not None: break if match is not None: ws, file_name = match - pkg_url = f"{_RAW_BASE}/workspaces/{ws}/metadata/{file_name}.yaml" + pkg_url = _raw_url(f"workspaces/{ws}/metadata/{file_name}.yaml", branch) try: pkg_def = _fetch_yaml(pkg_url) except urllib.error.HTTPError: pkg_def = {} + version = _get(pkg_def, "spec", "version", default=None) dynamic_artifact = _get(pkg_def, "spec", "dynamicArtifact", default=None) role = _get(pkg_def, "spec", "backstage", "role", default=None) app_config_examples = _get(pkg_def, "spec", "appConfigExamples", default=None) part_of = _get(pkg_def, "spec", "partOf", default=None) pkg_result: dict[str, Any] = {"name": pkg_name} + if version: + pkg_result["version"] = str(version) if dynamic_artifact: pkg_result["dynamicArtifact"] = dynamic_artifact if role: @@ -505,6 +516,7 @@ def fetch_plugin_metadata(plugin_name: str) -> dict[str, Any]: return { "plugin": metadata_name, + "branch": branch, "categories": categories, "preInstalled": pre_installed, "packages": package_results, @@ -562,6 +574,8 @@ def _print_human_metadata(data: dict[str, Any]) -> None: return print(f"{_BOLD}Plugin:{_NC} {data['plugin']}") + if data.get("branch"): + print(f"{_BOLD}Branch:{_NC} {data['branch']}") if data.get("categories"): cats = data["categories"] if isinstance(cats, list): @@ -578,6 +592,9 @@ def _print_human_metadata(data: dict[str, Any]) -> None: role_color = _BLUE if "frontend" in str(role) else _GREEN print(f"\n {_BOLD}{pkg['name']}{_NC}") print(f" Role: {role_color}{role}{_NC}") + version = pkg.get("version") + if version: + print(f" Version: {version}") artifact = pkg.get("dynamicArtifact") if artifact: print(f" Artifact: {artifact}") @@ -603,8 +620,8 @@ def build_parser() -> argparse.ArgumentParser: description=( "Fetch RHDH plugin metadata from the plugin-export-overlays " "repository. Lists available plugins or retrieves detailed " - "metadata (OCI artifacts, roles, config examples) for a " - "specific plugin." + "metadata (versions, OCI artifacts, roles, config examples) " + "for a specific plugin." ), ) parser.add_argument( @@ -626,6 +643,11 @@ def build_parser() -> argparse.ArgumentParser: default=False, help="List all available plugins", ) + parser.add_argument( + "--branch", + default="main", + help="Overlay repository branch or ref to read from (default: main)", + ) return parser @@ -635,7 +657,7 @@ def main(argv: list[str] | None = None) -> int: if args.list_plugins: try: - plugins = list_plugins() + plugins = list_plugins(branch=args.branch) except (urllib.error.URLError, OSError) as exc: if args.json_output: print(json.dumps({"success": False, "error": str(exc)}, indent=2)) @@ -644,7 +666,9 @@ def main(argv: list[str] | None = None) -> int: return 1 if args.json_output: - print(json.dumps({"success": True, "plugins": plugins}, indent=2)) + print( + json.dumps({"success": True, "branch": args.branch, "plugins": plugins}, indent=2) + ) else: _print_human_list(plugins) return 0 @@ -654,7 +678,7 @@ def main(argv: list[str] | None = None) -> int: return 2 # argparse exits on error, but be explicit try: - data = fetch_plugin_metadata(args.plugin) + data = fetch_plugin_metadata(args.plugin, branch=args.branch) except (urllib.error.URLError, OSError) as exc: if args.json_output: print(json.dumps({"success": False, "error": str(exc)}, indent=2)) diff --git a/uv.lock b/uv.lock index 8d77fb0..472b994 100644 --- a/uv.lock +++ b/uv.lock @@ -195,7 +195,7 @@ wheels = [ [[package]] name = "rhdh-skill" -version = "0.5.0" +version = "0.5.1" source = { virtual = "." } [package.optional-dependencies]