Files
yoresee_codegen/internal/k8s/client.go
T
XingfenD 4e467e1136 feat: implement proto-gen platform
- Config loader with env var expansion and validation
- SQLite database with tasks and toolchain_versions tables
- Gitea REST API client for repository and branch operations
- K8s client for Job lifecycle management (with noop fallback for local dev)
- REST API: tasks CRUD, retry, plugins, version management
- Worker/scheduler with polling, concurrency control, and job tracking
- Static frontend: generate, tasks, settings pages
- Toolchain images: Go and Node.js Dockerfiles with entrypoint scripts
- Helm chart with deployment, service, configmap, ingress, PVC
- Multi-stage Dockerfile for service
- Unit tests for config, database, gitclient, k8s, api, worker
- E2E tests for idempotency, version switch, failure recovery
2026-08-10 16:54:30 +08:00

150 lines
3.4 KiB
Go

package k8s
import (
"bytes"
"context"
"fmt"
"io"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
type k8sClient struct {
clientset kubernetes.Interface
}
func NewClient(kubeconfig string) (K8sClient, error) {
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
if err != nil {
return nil, fmt.Errorf("building config: %w", err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("creating clientset: %w", err)
}
return &k8sClient{clientset: clientset}, nil
}
func NewClientFromInterface(clientset kubernetes.Interface) K8sClient {
return &k8sClient{clientset: clientset}
}
func (c *k8sClient) CreateJob(spec *JobSpec) (*Job, error) {
ctx := context.Background()
envVars := make([]corev1.EnvVar, 0, len(spec.EnvVars))
for k, v := range spec.EnvVars {
envVars = append(envVars, corev1.EnvVar{Name: k, Value: v})
}
backoffLimit := int32(0)
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: spec.Name,
Namespace: spec.Namespace,
},
Spec: batchv1.JobSpec{
BackoffLimit: &backoffLimit,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyNever,
Containers: []corev1.Container{
{
Name: "toolchain",
Image: spec.Image,
Env: envVars,
},
},
},
},
},
}
if spec.TimeoutSecond > 0 {
timeout := int64(spec.TimeoutSecond)
job.Spec.ActiveDeadlineSeconds = &timeout
}
result, err := c.clientset.BatchV1().Jobs(spec.Namespace).Create(ctx, job, metav1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf("creating job: %w", err)
}
return &Job{
Name: result.Name,
Namespace: result.Namespace,
Status: JobPending,
}, nil
}
func (c *k8sClient) GetJob(name, namespace string) (*Job, error) {
ctx := context.Background()
result, err := c.clientset.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, fmt.Errorf("getting job: %w", err)
}
status := JobPending
if result.Status.Succeeded > 0 {
status = JobSucceeded
} else if result.Status.Failed > 0 {
status = JobFailed
} else if result.Status.Active > 0 {
status = JobRunning
}
job := &Job{
Name: result.Name,
Namespace: result.Namespace,
Status: status,
}
if status == JobFailed {
logs, _ := c.getPodLogs(name, namespace)
job.Logs = logs
}
return job, nil
}
func (c *k8sClient) DeleteJob(name, namespace string) error {
ctx := context.Background()
propagation := metav1.DeletePropagationForeground
return c.clientset.BatchV1().Jobs(namespace).Delete(ctx, name, metav1.DeleteOptions{
PropagationPolicy: &propagation,
})
}
func (c *k8sClient) getPodLogs(jobName, namespace string) (string, error) {
ctx := context.Background()
pods, err := c.clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{
LabelSelector: fmt.Sprintf("job-name=%s", jobName),
})
if err != nil || len(pods.Items) == 0 {
return "", err
}
pod := pods.Items[0]
req := c.clientset.CoreV1().Pods(namespace).GetLogs(pod.Name, &corev1.PodLogOptions{})
stream, err := req.Stream(ctx)
if err != nil {
return "", err
}
defer stream.Close()
var buf bytes.Buffer
if _, err := io.Copy(&buf, stream); err != nil {
return "", err
}
return buf.String(), nil
}