// internal/runner/k8s_runner.go
type K8sRunner struct {
config *core.RunnerConfig
clientset *kubernetes.Clientset
pod *v1.Pod
}
func NewK8sRunner(config *core.RunnerConfig) (*K8sRunner, error) {
kubeconfig, err := clientcmd.BuildConfigFromFlags("", config.KubeConfigPath)
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(kubeconfig)
if err != nil {
return nil, err
}
return &K8sRunner{
config: config,
clientset: clientset,
}, nil
}
func (r *K8sRunner) Setup(ctx context.Context) error {
// Create pod for execution
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("osmedeus-%s", uuid.New().String()[:8]),
Namespace: r.config.Namespace,
},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: "runner",
Image: r.config.Image,
Command: []string{"sleep", "infinity"},
}},
RestartPolicy: v1.RestartPolicyNever,
},
}
created, err := r.clientset.CoreV1().Pods(r.config.Namespace).Create(ctx, pod, metav1.CreateOptions{})
if err != nil {
return err
}
r.pod = created
// Wait for pod to be ready
return r.waitForPod(ctx)
}
func (r *K8sRunner) Execute(ctx context.Context, command string) (*CommandResult, error) {
req := r.clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(r.pod.Name).
Namespace(r.config.Namespace).
SubResource("exec").
VersionedParams(&v1.PodExecOptions{
Container: "runner",
Command: []string{"sh", "-c", command},
Stdout: true,
Stderr: true,
}, scheme.ParameterCodec)
exec, err := remotecommand.NewSPDYExecutor(r.config, "POST", req.URL())
if err != nil {
return nil, err
}
var stdout, stderr bytes.Buffer
err = exec.StreamWithContext(ctx, remotecommand.StreamOptions{
Stdout: &stdout,
Stderr: &stderr,
})
return &CommandResult{
Output: stdout.String() + stderr.String(),
// Extract exit code from error if available
}, err
}
func (r *K8sRunner) Cleanup(ctx context.Context) error {
if r.pod != nil {
return r.clientset.CoreV1().Pods(r.config.Namespace).Delete(ctx, r.pod.Name, metav1.DeleteOptions{})
}
return nil
}