mirror of
https://github.com/run-llama/workflows-py.git
synced 2026-07-18 16:14:58 -04:00
197 lines
5.9 KiB
Python
197 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Process generated manifests from kubebuilder and create Helm templates.
|
|
This preserves Helm templating while keeping kubebuilder annotations as source of truth.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import yaml
|
|
|
|
# Resolve repo root relative to this script's location (scripts/ or ../scripts/ from operator/)
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
def process_rbac() -> bool:
|
|
"""Process RBAC manifest from kubebuilder output."""
|
|
rbac_file = REPO_ROOT / "operator/config/rbac/role.yaml"
|
|
output_file = REPO_ROOT / "charts/llama-agents/templates/rbac.yaml"
|
|
|
|
if not rbac_file.exists():
|
|
print(f"Error: {rbac_file} not found")
|
|
return False
|
|
|
|
# Read generated RBAC
|
|
with open(rbac_file) as f:
|
|
rbac = yaml.safe_load(f)
|
|
|
|
if rbac.get("kind") != "ClusterRole":
|
|
print(f"Error: Expected ClusterRole, got {rbac.get('kind')}")
|
|
return False
|
|
|
|
# Create Helm template with conditional and proper metadata.
|
|
# Split mode (apps namespace != release namespace): apps-ns Role carries
|
|
# every rule except leader-election leases, release-ns Role carries only
|
|
# the leases rule (controller-runtime places the Lease in the operator
|
|
# pod's own namespace). Single-namespace mode collapses to one Role.
|
|
template = """{{- if .Values.rbac.create }}
|
|
{{- $appsNs := include "llama-agents.apps.namespace" . -}}
|
|
{{- $releaseNs := .Release.Namespace -}}
|
|
{{- $split := include "llama-agents.apps.splitNamespace" . -}}
|
|
{{- $saName := include "llama-agents.serviceAccountName" . -}}
|
|
{{- /*
|
|
Split mode (apps != release): apps-ns Role with everything except leases,
|
|
release-ns Role with only leases (leader-election Lease lives in the
|
|
operator pod's namespace). Single-namespace mode: one combined Role.
|
|
*/ -}}
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: Role
|
|
metadata:
|
|
name: {{ $saName }}
|
|
namespace: {{ $appsNs }}
|
|
{{- with .Values.rbac.roleAnnotations }}
|
|
annotations:
|
|
{{- toYaml . | nindent 4 }}
|
|
{{- end }}
|
|
rules:
|
|
"""
|
|
|
|
# Add the generated rules. The leader-election leases rule is conditional:
|
|
# in split mode it moves to a separate release-ns Role below.
|
|
for rule in rbac.get("rules", []):
|
|
api_groups = rule.get("apiGroups", [])
|
|
resources = rule.get("resources", [])
|
|
verbs = rule.get("verbs", [])
|
|
|
|
is_leases = api_groups == ["coordination.k8s.io"] and resources == ["leases"]
|
|
if is_leases:
|
|
template += "{{- if not $split }}\n"
|
|
template += f"- apiGroups: {api_groups}\n"
|
|
template += f" resources: {resources}\n"
|
|
template += f" verbs: {verbs}\n"
|
|
if is_leases:
|
|
template += "{{- end }}\n"
|
|
|
|
# Add RoleBinding (namespace-scoped) instead of ClusterRoleBinding
|
|
template += """---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: RoleBinding
|
|
metadata:
|
|
name: {{ $saName }}
|
|
namespace: {{ $appsNs }}
|
|
{{- with .Values.rbac.roleBindingAnnotations }}
|
|
annotations:
|
|
{{- toYaml . | nindent 4 }}
|
|
{{- end }}
|
|
roleRef:
|
|
apiGroup: rbac.authorization.k8s.io
|
|
kind: Role
|
|
name: {{ $saName }}
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: {{ $saName }}
|
|
namespace: {{ $releaseNs }}
|
|
{{- if $split }}
|
|
---
|
|
# Leader-election Lease lives in the operator pod's namespace.
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: Role
|
|
metadata:
|
|
name: {{ $saName }}-leader-election
|
|
namespace: {{ $releaseNs }}
|
|
{{- with .Values.rbac.roleAnnotations }}
|
|
annotations:
|
|
{{- toYaml . | nindent 4 }}
|
|
{{- end }}
|
|
rules:
|
|
- apiGroups: ['coordination.k8s.io']
|
|
resources: ['leases']
|
|
verbs: ['create', 'delete', 'get', 'list', 'patch', 'update', 'watch']
|
|
---
|
|
apiVersion: rbac.authorization.k8s.io/v1
|
|
kind: RoleBinding
|
|
metadata:
|
|
name: {{ $saName }}-leader-election
|
|
namespace: {{ $releaseNs }}
|
|
{{- with .Values.rbac.roleBindingAnnotations }}
|
|
annotations:
|
|
{{- toYaml . | nindent 4 }}
|
|
{{- end }}
|
|
roleRef:
|
|
apiGroup: rbac.authorization.k8s.io
|
|
kind: Role
|
|
name: {{ $saName }}-leader-election
|
|
subjects:
|
|
- kind: ServiceAccount
|
|
name: {{ $saName }}
|
|
namespace: {{ $releaseNs }}
|
|
{{- end }}
|
|
{{- end }}
|
|
"""
|
|
|
|
# Write to Helm template
|
|
with open(output_file, "w") as f:
|
|
f.write(template)
|
|
|
|
print(f"✅ Generated RBAC template with {len(rbac.get('rules', []))} rules")
|
|
return True
|
|
|
|
|
|
def process_crds() -> bool:
|
|
"""Process CRD manifests from kubebuilder output."""
|
|
crd_files = [
|
|
REPO_ROOT
|
|
/ "operator/config/crd/bases/deploy.llamaindex.ai_llamadeployments.yaml",
|
|
REPO_ROOT
|
|
/ "operator/config/crd/bases/deploy.llamaindex.ai_llamadeploymenttemplates.yaml",
|
|
]
|
|
|
|
# Output 1: Raw CRD files in main chart's crds/ directory (install-only, no templating)
|
|
crds_dir = REPO_ROOT / "charts/llama-agents/crds"
|
|
crds_dir.mkdir(exist_ok=True)
|
|
|
|
# Output 2: Raw CRD files in CRD chart's files/ directory (Helm template adds annotations)
|
|
crd_chart_files_dir = REPO_ROOT / "charts/llama-agents-crds/files"
|
|
crd_chart_files_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
for crd_file in crd_files:
|
|
if not crd_file.exists():
|
|
print(f"Error: {crd_file} not found")
|
|
return False
|
|
content = crd_file.read_text()
|
|
|
|
# Copy raw CRD to both destinations
|
|
(crds_dir / crd_file.name).write_text(content)
|
|
(crd_chart_files_dir / crd_file.name).write_text(content)
|
|
|
|
print("Generated CRD files in crds/ directory (install-only)")
|
|
print("Generated CRD files in llama-agents-crds/files/ (for CRD chart)")
|
|
return True
|
|
|
|
|
|
def main() -> None:
|
|
"""Main entry point."""
|
|
print("Processing kubebuilder-generated manifests...")
|
|
|
|
success = True
|
|
|
|
# Process RBAC
|
|
if not process_rbac():
|
|
success = False
|
|
|
|
# Process CRDs
|
|
if not process_crds():
|
|
success = False
|
|
|
|
if success:
|
|
print("✅ All manifests processed successfully")
|
|
else:
|
|
print("❌ Some manifests failed to process")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|