Move directory
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: chart_updater
|
||||
description: Pull a Helm chart version and extract it into manifests/helm/<chart>/<version>.
|
||||
---
|
||||
|
||||
# chart_updater
|
||||
|
||||
Pull a chart from helm repo and create a versioned directory in dip-catalog.
|
||||
`from_version`이 주어지면 이전 버전 디렉토리에서 아래 파일을 새 버전 디렉토리로 복사한다 (이미 존재하는 파일은 건너뜀):
|
||||
- `BUILD-README.md` — 복사 + `from_version` → `version` 버전 번호 치환
|
||||
- `CUSTOM-README.md` — 그대로 복사
|
||||
- `custom-values.yaml` — 그대로 복사
|
||||
|
||||
## Input schema
|
||||
```json
|
||||
{
|
||||
"catalog_root": "string",
|
||||
"chart": "string",
|
||||
"repo": "string",
|
||||
"version": "string",
|
||||
"from_version": "string (optional)"
|
||||
}
|
||||
```
|
||||
|
||||
## Output schema
|
||||
```json
|
||||
{
|
||||
"success": "boolean",
|
||||
"already_existed": "boolean",
|
||||
"dest_dir": "string",
|
||||
"copied_files": ["string"]
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--catalog-root", required=True)
|
||||
parser.add_argument("--chart", required=True)
|
||||
parser.add_argument("--repo", required=True)
|
||||
parser.add_argument("--version", required=True)
|
||||
parser.add_argument("--from-version", default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
repo_root = os.environ.get("UPDATE_CATALOG_ROOT")
|
||||
if repo_root:
|
||||
sys.path.insert(0, str(Path(repo_root) / "src"))
|
||||
else:
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
from update_catalog.chart_updater import chart_updater # type: ignore
|
||||
|
||||
out = chart_updater({
|
||||
"catalog_root": args.catalog_root,
|
||||
"chart": args.chart,
|
||||
"repo": args.repo,
|
||||
"version": args.version,
|
||||
"from_version": args.from_version,
|
||||
})
|
||||
print(json.dumps(out, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1 @@
|
||||
__all__ = ["skill_interface"]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,97 @@
|
||||
"""chart_updater skill implementation.
|
||||
|
||||
Pull latest chart from helm repo and extract into manifests/helm/<chart>/<version>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
import tarfile
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List
|
||||
|
||||
|
||||
def _run(cmd: list[str], cwd: str | None = None) -> str:
|
||||
res = subprocess.run(cmd, cwd=cwd, capture_output=True, text=True)
|
||||
if res.returncode != 0:
|
||||
raise RuntimeError(res.stderr.strip() or f"command failed: {' '.join(cmd)}")
|
||||
return res.stdout
|
||||
|
||||
|
||||
# Files to carry over from the previous version directory.
|
||||
# (filename, replace_version): replace_version=True → substitute from_version with to_version in content.
|
||||
_CARRY_OVER_FILES = [
|
||||
("BUILD-README.md", True),
|
||||
("CUSTOM-README.md", False),
|
||||
("custom-values.yaml", False),
|
||||
]
|
||||
|
||||
|
||||
def _copy_custom_files(
|
||||
src_dir: Path, dest_dir: Path, from_version: str, to_version: str
|
||||
) -> List[str]:
|
||||
"""Copy carry-over files from src_dir to dest_dir.
|
||||
|
||||
Skips files that already exist in dest_dir (idempotent).
|
||||
Returns list of copied file names.
|
||||
"""
|
||||
copied: List[str] = []
|
||||
for fname, needs_replace in _CARRY_OVER_FILES:
|
||||
src = src_dir / fname
|
||||
dest = dest_dir / fname
|
||||
if not src.exists() or dest.exists():
|
||||
continue
|
||||
content = src.read_text(encoding="utf-8")
|
||||
if needs_replace:
|
||||
content = content.replace(from_version, to_version)
|
||||
dest.write_text(content, encoding="utf-8")
|
||||
copied.append(fname)
|
||||
return copied
|
||||
|
||||
|
||||
def chart_updater(payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
catalog_root = Path(payload["catalog_root"])
|
||||
chart = payload["chart"]
|
||||
repo = payload["repo"]
|
||||
version = payload["version"]
|
||||
from_version: str | None = payload.get("from_version")
|
||||
|
||||
dest_dir = catalog_root / "manifests" / "helm" / chart / version
|
||||
already_existed = dest_dir.exists()
|
||||
|
||||
if not already_existed:
|
||||
work_dir = catalog_root / ".tmp_chart_pull"
|
||||
work_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
_run(["helm", "pull", f"{repo}/{chart}", "--version", version], cwd=str(work_dir))
|
||||
|
||||
tgz = next(work_dir.glob(f"{chart}-*.tgz"), None)
|
||||
if tgz is None:
|
||||
raise FileNotFoundError("pulled chart archive not found")
|
||||
|
||||
with tarfile.open(tgz, "r:gz") as tar:
|
||||
tar.extractall(path=work_dir)
|
||||
|
||||
extracted = work_dir / chart
|
||||
if not extracted.exists():
|
||||
# some charts may use different folder name; fallback to first dir
|
||||
dirs = [p for p in work_dir.iterdir() if p.is_dir() and p.name != "__pycache__"]
|
||||
if dirs:
|
||||
extracted = dirs[0]
|
||||
|
||||
dest_dir.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.move(str(extracted), str(dest_dir))
|
||||
tgz.unlink(missing_ok=True)
|
||||
|
||||
copied_files: List[str] = []
|
||||
if from_version:
|
||||
src_dir = catalog_root / "manifests" / "helm" / chart / from_version
|
||||
if src_dir.exists():
|
||||
copied_files = _copy_custom_files(src_dir, dest_dir, from_version, version)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"already_existed": already_existed,
|
||||
"dest_dir": str(dest_dir),
|
||||
"copied_files": copied_files,
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
"""Skill Interface v1.0 for Helm upgrade automation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
SKILL_INTERFACE_VERSION = "1.0"
|
||||
|
||||
|
||||
# -------------------------
|
||||
# Common Error Schema
|
||||
# -------------------------
|
||||
class SkillError(BaseModel):
|
||||
code: str
|
||||
message: str
|
||||
retryable: bool = False
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
error: SkillError
|
||||
|
||||
|
||||
# -------------------------
|
||||
# Shared Types
|
||||
# -------------------------
|
||||
Severity = Literal["critical", "high", "medium", "warning"]
|
||||
|
||||
|
||||
class BreakingReason(BaseModel):
|
||||
type: str
|
||||
resource: Optional[str] = None
|
||||
key: Optional[str] = None
|
||||
detail: Optional[str] = None
|
||||
|
||||
|
||||
# -------------------------
|
||||
# helm_diff
|
||||
# -------------------------
|
||||
class HelmDiffInput(BaseModel):
|
||||
chart: str
|
||||
repo: Optional[str] = None
|
||||
chart_path: Optional[str] = None
|
||||
from_version: str
|
||||
to_version: str
|
||||
values_override: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class HelmDiffOutput(BaseModel):
|
||||
chart: str
|
||||
from_version: str
|
||||
to_version: str
|
||||
generated_at: str
|
||||
values: Dict[str, Any]
|
||||
templates: Dict[str, Any]
|
||||
crd: Dict[str, Any]
|
||||
dependencies: Dict[str, Any]
|
||||
errors: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
|
||||
|
||||
# -------------------------
|
||||
# breaking_change_check
|
||||
# -------------------------
|
||||
class BreakingCheckInput(BaseModel):
|
||||
diff_json: Dict[str, Any]
|
||||
|
||||
|
||||
class BreakingCheckOutput(BaseModel):
|
||||
breaking: bool
|
||||
severity: Severity
|
||||
reasons: List[BreakingReason] = Field(default_factory=list)
|
||||
warnings: List[BreakingReason] = Field(default_factory=list)
|
||||
|
||||
|
||||
# -------------------------
|
||||
# generate_upgrade_doc
|
||||
# -------------------------
|
||||
class GenerateDocInput(BaseModel):
|
||||
diff_json: Dict[str, Any]
|
||||
breaking_result: Dict[str, Any]
|
||||
docs_context: Optional[Dict[str, str]] = None
|
||||
max_tokens: int = 50000
|
||||
|
||||
|
||||
class GenerateDocOutput(BaseModel):
|
||||
markdown: str
|
||||
truncated: bool = False
|
||||
|
||||
|
||||
# -------------------------
|
||||
# update_docs_file
|
||||
# -------------------------
|
||||
class UpdateDocsInput(BaseModel):
|
||||
repo_path: str
|
||||
docs_file: str = "docs/upgrade.md"
|
||||
version: str
|
||||
content: str
|
||||
overwrite: bool = False
|
||||
|
||||
|
||||
class UpdateDocsOutput(BaseModel):
|
||||
success: bool
|
||||
file_path: str
|
||||
already_existed: bool = False
|
||||
|
||||
|
||||
# -------------------------
|
||||
# create_pr
|
||||
# -------------------------
|
||||
class CreatePRInput(BaseModel):
|
||||
chart: str
|
||||
from_version: str
|
||||
to_version: str
|
||||
repo_path: str
|
||||
doc_content: str
|
||||
breaking: bool
|
||||
severity: Severity
|
||||
|
||||
|
||||
class CreatePROutput(BaseModel):
|
||||
pr_url: str
|
||||
branch_name: str
|
||||
labels: List[str]
|
||||
|
||||
|
||||
# -------------------------
|
||||
# deploy_validate
|
||||
# -------------------------
|
||||
class DeployValidateInput(BaseModel):
|
||||
chart: str
|
||||
repo: Optional[str] = None
|
||||
version: str
|
||||
values_override: Optional[Dict[str, Any]] = None
|
||||
namespace: str
|
||||
timeout: int = 300
|
||||
|
||||
|
||||
class DeployValidateOutput(BaseModel):
|
||||
success: bool
|
||||
dry_run_passed: bool
|
||||
pod_status: Dict[str, int]
|
||||
events: List[Dict[str, Any]] = Field(default_factory=list)
|
||||
logs: Optional[str] = None
|
||||
Reference in New Issue
Block a user