Skip to content

Commit bdf72d8

Browse files
Merge pull request #8 from harche/update-api-types
OTA-1972: Update API types to match current agentic.openshift.io CRDs
2 parents 9778f4f + a1223ca commit bdf72d8

31 files changed

Lines changed: 1798 additions & 1739 deletions

Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.PHONY: generate-types build test lint
2+
3+
generate-types:
4+
python3 hack/generate-types.py
5+
6+
build:
7+
yarn build
8+
9+
test:
10+
yarn test
11+
12+
lint:
13+
yarn lint

hack/generate-types.py

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
#!/usr/bin/env python3
2+
"""Generate TypeScript types from Kubernetes CRD OpenAPI schemas.
3+
4+
Usage:
5+
python3 hack/generate-types.py # from live cluster
6+
python3 hack/generate-types.py --from-dir crds/ # from local CRD YAML files
7+
8+
Generates src/models/generated/*.ts with TypeScript interfaces matching
9+
the CRD spec and status schemas.
10+
"""
11+
12+
import json
13+
import os
14+
import subprocess
15+
import sys
16+
import textwrap
17+
from pathlib import Path
18+
19+
CRDS = {
20+
"proposals.agentic.openshift.io": "Proposal",
21+
"proposalapprovals.agentic.openshift.io": "ProposalApproval",
22+
"approvalpolicies.agentic.openshift.io": "ApprovalPolicy",
23+
"analysisresults.agentic.openshift.io": "AnalysisResult",
24+
}
25+
26+
OUT_DIR = Path(__file__).resolve().parent.parent / "src" / "models" / "generated"
27+
BANNER = "// Auto-generated from CRD — do not edit manually.\n// Regenerate with: make generate-types\n"
28+
29+
30+
def extract_from_cluster() -> dict[str, dict]:
31+
schemas = {}
32+
for crd_name in CRDS:
33+
print(f" Extracting {crd_name}...")
34+
result = subprocess.run(
35+
["oc", "get", "crd", crd_name, "-o",
36+
"jsonpath={.spec.versions[0].schema.openAPIV3Schema}"],
37+
capture_output=True, text=True, check=True,
38+
)
39+
schemas[crd_name] = json.loads(result.stdout)
40+
return schemas
41+
42+
43+
def extract_from_dir(d: str) -> dict[str, dict]:
44+
import yaml
45+
schemas = {}
46+
for crd_name in CRDS:
47+
for f in Path(d).glob("*.yaml"):
48+
with open(f) as fh:
49+
doc = yaml.safe_load(fh)
50+
if doc and doc.get("metadata", {}).get("name") == crd_name:
51+
schemas[crd_name] = doc["spec"]["versions"][0]["schema"]["openAPIV3Schema"]
52+
print(f" {f.name} -> {crd_name}")
53+
break
54+
else:
55+
print(f" WARNING: {crd_name} not found in {d}")
56+
return schemas
57+
58+
59+
def schema_to_ts(schema: dict, indent: int = 0) -> str:
60+
"""Convert an OpenAPI schema object to a TypeScript type string."""
61+
pad = " " * indent
62+
63+
if "x-kubernetes-preserve-unknown-fields" in schema:
64+
return "Record<string, unknown>"
65+
66+
typ = schema.get("type", "object")
67+
68+
if "enum" in schema:
69+
return " | ".join(f"'{v}'" for v in schema["enum"])
70+
71+
if typ == "string":
72+
fmt = schema.get("format", "")
73+
if fmt in ("date-time", "date"):
74+
return "string"
75+
return "string"
76+
77+
if typ == "integer":
78+
return "number"
79+
80+
if typ == "number":
81+
return "number"
82+
83+
if typ == "boolean":
84+
return "boolean"
85+
86+
if typ == "array":
87+
items = schema.get("items", {})
88+
item_type = schema_to_ts(items, indent)
89+
return f"({item_type})[]"
90+
91+
if typ == "object":
92+
props = schema.get("properties", {})
93+
if not props:
94+
additional = schema.get("additionalProperties")
95+
if additional and isinstance(additional, dict):
96+
val_type = schema_to_ts(additional, indent)
97+
return f"Record<string, {val_type}>"
98+
return "Record<string, unknown>"
99+
100+
required = set(schema.get("required", []))
101+
lines = ["{"]
102+
for name, prop_schema in sorted(props.items()):
103+
desc = prop_schema.get("description", "")
104+
optional = "?" if name not in required else ""
105+
prop_type = schema_to_ts(prop_schema, indent + 1)
106+
if desc:
107+
short = desc.replace("\n", " ").strip()
108+
if len(short) > 100:
109+
short = short[:97] + "..."
110+
lines.append(f"{pad} /** {short} */")
111+
lines.append(f"{pad} {name}{optional}: {prop_type};")
112+
lines.append(f"{pad}}}")
113+
return "\n".join(lines)
114+
115+
return "unknown"
116+
117+
118+
def generate_one(crd_name: str, type_name: str, schema: dict) -> str:
119+
"""Generate TypeScript for a single CRD."""
120+
lines = [BANNER, ""]
121+
122+
props = schema.get("properties", {})
123+
spec_schema = props.get("spec", {})
124+
status_schema = props.get("status", {})
125+
126+
# Generate Spec type
127+
if spec_schema.get("properties"):
128+
spec_ts = schema_to_ts(spec_schema, 0)
129+
lines.append(f"export type {type_name}Spec = {spec_ts};\n")
130+
131+
# Generate Status type
132+
if status_schema.get("properties"):
133+
status_ts = schema_to_ts(status_schema, 0)
134+
lines.append(f"export type {type_name}Status = {status_ts};\n")
135+
136+
# Generate nested types that are reusable (conditions, steps, etc.)
137+
# Extract any deeply nested object types that appear in spec or status
138+
139+
return "\n".join(lines)
140+
141+
142+
def main():
143+
if len(sys.argv) > 2 and sys.argv[1] == "--from-dir":
144+
schemas = extract_from_dir(sys.argv[2])
145+
else:
146+
print("Extracting CRD schemas from cluster...")
147+
schemas = extract_from_cluster()
148+
149+
OUT_DIR.mkdir(parents=True, exist_ok=True)
150+
151+
for crd_name, schema in schemas.items():
152+
type_name = CRDS[crd_name]
153+
out_file = OUT_DIR / f"{crd_name.split('.')[0]}.ts"
154+
print(f" Generating {out_file.name} ({type_name})...")
155+
156+
ts_code = generate_one(crd_name, type_name, schema)
157+
out_file.write_text(ts_code)
158+
159+
# Barrel export
160+
barrel = OUT_DIR / "index.ts"
161+
barrel_lines = [BANNER, ""]
162+
for crd_name in schemas:
163+
base = crd_name.split(".")[0]
164+
barrel_lines.append(f"export * from './{base}';")
165+
barrel_lines.append("")
166+
barrel.write_text("\n".join(barrel_lines))
167+
168+
print(f"Done. Generated types in {OUT_DIR}")
169+
170+
171+
if __name__ == "__main__":
172+
main()
Lines changed: 24 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,111 +1,73 @@
11
{
2-
"{{count}} incompatible with target_one": "{{count}} incompatible with target_one",
3-
"{{count}} incompatible with target_other": "{{count}} incompatible with target_other",
4-
"{{count}} manual approval_one": "{{count}} manual approval_one",
5-
"{{count}} manual approval_other": "{{count}} manual approval_other",
6-
"{{count}} pending upgrade_one": "{{count}} pending upgrade_one",
7-
"{{count}} pending upgrade_other": "{{count}} pending upgrade_other",
8-
"{{passed}}/{{total}} checks passed": "{{passed}}/{{total}} checks passed",
9-
"{{passed}}/{{total}} checks passed, {{failures}} failed": "{{passed}}/{{total}} checks passed, {{failures}} failed",
10-
"{{passed}}/{{total}} checks passed, {{warnings}} warnings": "{{passed}}/{{total}} checks passed, {{warnings}} warnings",
11-
"{{total}} operators managed by OLM": "{{total}} operators managed by OLM",
122
"Action failed": "Action failed",
133
"Active update plans": "Active update plans",
14-
"Activity summary and the current proposed plan - {{channel}} channel": "Activity summary and the current proposed plan - {{channel}} channel",
15-
"Affected: {{resources}}": "Affected: {{resources}}",
164
"Age": "Age",
17-
"Agent target version": "Agent target version",
5+
"AI agent is analysing cluster readiness...": "AI agent is analysing cluster readiness...",
186
"AI Assessment": "AI Assessment",
19-
"AI is analyzing your cluster for upgrade readiness...": "AI is analyzing your cluster for upgrade readiness...",
20-
"AI Update Agent": "AI Update Agent",
21-
"All Clear": "All Clear",
7+
"Analyse": "Analyse",
8+
"Analysis data not available.": "Analysis data not available.",
9+
"Analysis failed": "Analysis failed",
10+
"Analysis result not yet available.": "Analysis result not yet available.",
2211
"Analyzing": "Analyzing",
23-
"API Deprecations": "API Deprecations",
24-
"Approval": "Approval",
2512
"Approve & upgrade": "Approve & upgrade",
2613
"Awaiting Decision": "Awaiting Decision",
2714
"Channel": "Channel",
2815
"Check": "Check",
2916
"Close": "Close",
30-
"Cluster Metrics": "Cluster Metrics",
3117
"Cluster Update": "Cluster Update",
18+
"Compatible": "Compatible",
3219
"Completed": "Completed",
3320
"Confirm approve & upgrade": "Confirm approve & upgrade",
3421
"Confirm reject": "Confirm reject",
35-
"CPU utilization": "CPU utilization",
36-
"Detail": "Detail",
22+
"Details": "Details",
3723
"Duration": "Duration",
38-
"ends {{date}}": "ends {{date}}",
3924
"Error loading cluster version": "Error loading cluster version",
40-
"Estimated Duration": "Estimated Duration",
41-
"Estimated Impact": "Estimated Impact",
42-
"etcd fsync p99": "etcd fsync p99",
43-
"Failed to create plan": "Failed to create plan",
44-
"Findings": "Findings",
45-
"Findings table": "Findings table",
46-
"Generate plan": "Generate plan",
4725
"Generated": "Generated",
48-
"In the OCP 5.0 agent led experience, the update agent proposes plans and drives execution. OpenShift Lightspeed can still run a pre-check on cluster and operator readiness before you approve, so risks and prerequisites stay visible alongside automated planning.": "In the OCP 5.0 agent led experience, the update agent proposes plans and drives execution. OpenShift Lightspeed can still run a pre-check on cluster and operator readiness before you approve, so risks and prerequisites stay visible alongside automated planning.",
26+
"Lifecycle": "Lifecycle",
4927
"Lightspeed proposals unavailable": "Lightspeed proposals unavailable",
5028
"Loading": "Loading",
51-
"Maintenance Details": "Maintenance Details",
52-
"Memory utilization": "Memory utilization",
5329
"Minor": "Minor",
54-
"Monitor Command": "Monitor Command",
5530
"Name": "Name",
31+
"No": "No",
5632
"No active update plans": "No active update plans",
57-
"No deprecated APIs in use": "No deprecated APIs in use",
58-
"No issues found — all readiness checks passed.": "No issues found — all readiness checks passed.",
59-
"No maintenance details available.": "No maintenance details available.",
6033
"No update history available": "No update history available",
61-
"Not reversible": "Not reversible",
62-
"OCP Compat": "OCP Compat",
63-
"OLM Operator Lifecycle": "OLM Operator Lifecycle",
64-
"OLM operators table": "OLM operators table",
34+
"No update proposals available.": "No update proposals available.",
35+
"OLM operators": "OLM operators",
36+
"OLM Operators": "OLM Operators",
6537
"Operator": "Operator",
6638
"Pending": "Pending",
6739
"Phase": "Phase",
68-
"Pick a target from your channel (updates AI Assessment), then click Generate plan to build or refresh the proposed update below.": "Pick a target from your channel (updates AI Assessment), then click Generate plan to build or refresh the proposed update below.",
6940
"Plan ready — approving will start the cluster upgrade. You will be redirected to the cluster settings page to monitor progress.": "Plan ready — approving will start the cluster upgrade. You will be redirected to the cluster settings page to monitor progress.",
70-
"Plans Created": "Plans Created",
71-
"Pre-check with AI": "Pre-check with AI",
72-
"Prerequisites": "Prerequisites",
41+
"Prerequisite:": "Prerequisite:",
7342
"Proposed Update: {{current}}": "Proposed Update: {{current}}",
43+
"Re-analyse": "Re-analyse",
44+
"Re-analyse failed": "Re-analyse failed",
45+
"Readiness checks": "Readiness checks",
7446
"Readiness Checks": "Readiness Checks",
75-
"Readiness data not yet available.": "Readiness data not yet available.",
76-
"Ready to proceed": "Ready to proceed",
7747
"Reject plan": "Reject plan",
78-
"Reversibility": "Reversibility",
79-
"Reversible": "Reversible",
8048
"Review available versions, assess operator compatibility, and plan how this cluster version is newer OpenShift releases. Use Updates plan to prepare or start an update, Active update plans for in-flight work, and Update history for completed ones.": "Review available versions, assess operator compatibility, and plan how this cluster version is newer OpenShift releases. Use Updates plan to prepare or start an update, Active update plans for in-flight work, and Update history for completed ones.",
81-
"Risk assessment not yet available.": "Risk assessment not yet available.",
82-
"Risk Level": "Risk Level",
49+
"Sandbox: {{name}}": "Sandbox: {{name}}",
8350
"Schedule for later": "Schedule for later",
84-
"Select a version": "Select a version",
85-
"Severity": "Severity",
51+
"Select proposal": "Select proposal",
52+
"Select Update Path": "Select Update Path",
8653
"Started": "Started",
54+
"Starting analysis — waiting for agent sandbox...": "Starting analysis — waiting for agent sandbox...",
8755
"Status": "Status",
88-
"Support": "Support",
89-
"Target version": "Target version",
9056
"Target Version": "Target Version",
9157
"Target version {{version}} not found in available updates": "Target version {{version}} not found in available updates",
9258
"The Lightspeed Proposal CRD is not installed on this cluster. AI-driven update planning features are disabled.": "The Lightspeed Proposal CRD is not installed on this cluster. AI-driven update planning features are disabled.",
9359
"There are no update plans currently in progress.": "There are no update plans currently in progress.",
9460
"This cluster has no recorded update history.": "This cluster has no recorded update history.",
9561
"unknown": "unknown",
62+
"Unknown error": "Unknown error",
9663
"Update": "Update",
9764
"Update history": "Update history",
65+
"Update to {{version}}": "Update to {{version}}",
9866
"Update Type": "Update Type",
99-
"Updates Executed": "Updates Executed",
10067
"Updates plan": "Updates plan",
101-
"Upgrade Command": "Upgrade Command",
102-
"upgrade pending": "upgrade pending",
103-
"upgrade to {{version}}": "upgrade to {{version}}",
10468
"Verified": "Verified",
10569
"Version": "Version",
106-
"Version {{version}}, {{count}} available_one": "Version {{version}}, {{count}} available_one",
107-
"Version {{version}}, {{count}} available_other": "Version {{version}}, {{count}} available_other",
108-
"View etcd pods": "View etcd pods",
109-
"View monitoring": "View monitoring",
70+
"View pod logs": "View pod logs",
71+
"Yes": "Yes",
11072
"z-stream": "z-stream"
11173
}

0 commit comments

Comments
 (0)