summary history files

backend/services/cluster_service.go
package services

import (
	"context"
	"encoding/json"
	"fmt"
	"kd/backend/config"
	"kd/backend/logwrap"
	"kd/backend/types"
	"strings"
	"sync"
	"time"

	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer"
	k8types "k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/util/retry"
	"k8s.io/kubectl/pkg/describe"
	describecmd "k8s.io/kubectl/pkg/describe"
	"k8s.io/kubectl/pkg/scheme"
)

type clusterService struct {
	ctx    context.Context
	conf   config.Config
	logger *logwrap.LogWrap
}

var cluster *clusterService
var onceCluster sync.Once

func Cluster() *clusterService {
	if cluster == nil {
		onceCluster.Do(func() {
			cluster = &clusterService{}
		})
	}
	return cluster
}

func (c *clusterService) Start(ctx context.Context, conf config.Config, logger *logwrap.LogWrap) {
	c.ctx = ctx
	c.conf = conf
	c.logger = logger
}

func (c *clusterService) GetClusters() types.ClustersResponse {
	r := types.NewClustersResponse()
	for k := range c.conf.KubeClusters {
		cluster := types.Cluster{ContextName: k.String()}
		r.Data = append(r.Data, cluster)
	}
	r.Success = true
	return r
}

func (c *clusterService) GetNamespace(contextName, name string) types.NamespaceResponse {
	r := types.NewNamespaceResponse()
	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	namespace, err := cs.CoreV1().Namespaces().Get(c.ctx, name, metav1.GetOptions{})
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to get Namespace: %s", err.Error())
		return r
	}
	r.Data = *namespace
	r.Success = true
	return r
}

func (c *clusterService) GetNamespaces(context string) types.NamespacesResponse {
	r := types.NewNamespacesResponse()

	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	namespacesClient := cs.CoreV1().Namespaces()
	namespacesList, err := namespacesClient.List(c.ctx, metav1.ListOptions{})
	if err != nil {
		r.Msg = "Failed to list Namespaces"
		return r
	}

	for _, namespace := range namespacesList.Items {
		r.Data = append(r.Data, namespace)
	}

	r.Success = true
	return r
}

func (c *clusterService) RestartDeployment(contextName, namespaceName, name string) types.DeploymentResponse {
	r := types.NewDeploymentResponse()

	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	deploymentsClient := cs.AppsV1().Deployments(namespaceName)
	deployment, err := deploymentsClient.Get(c.ctx, name, metav1.GetOptions{})
	if err != nil {
		r.Msg = "Failed to get Deployment"
		return r
	}

	deployment.Spec.Template.Annotations = make(map[string]string)
	deployment.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
	deployment, err = deploymentsClient.Update(c.ctx, deployment, metav1.UpdateOptions{})
	if err != nil {
		r.Msg = "Failed to update Deployment"
		return r
	}
	r.Data = *deployment
	r.Success = true
	return r
}

func (c *clusterService) RestartStatefulSet(contextName, namespaceName, name string) types.StatefulSetResponse {
	r := types.NewStatefulSetResponse()

	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	statefulSetsClient := cs.AppsV1().StatefulSets(namespaceName)
	statefulSet, err := statefulSetsClient.Get(c.ctx, name, metav1.GetOptions{})
	if err != nil {
		r.Msg = "Failed to get StatefulSet"
		return r
	}

	statefulSet.Spec.Template.Annotations = make(map[string]string)
	statefulSet.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
	statefulSet, err = statefulSetsClient.Update(c.ctx, statefulSet, metav1.UpdateOptions{})
	if err != nil {
		r.Msg = "Failed to update StatefulSet"
		return r
	}
	r.Data = *statefulSet
	r.Success = true
	return r
}

func (c *clusterService) GetStatefulSets(contextName string, namespace string) types.StatefulSetsResponse {
	r := types.NewStatefulSetsResponse()

	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to find context name %s", contextName)
		return r
	}

	statefulSetsClient := cs.AppsV1().StatefulSets(namespace)
	statefulSetsList, err := statefulSetsClient.List(c.ctx, metav1.ListOptions{})
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to list StatefulSets for %s: %v", contextName, err)
		return r
	}

	for _, statefulSet := range statefulSetsList.Items {
		r.Data = append(r.Data, statefulSet)
	}

	r.Success = true
	return r
}

func (c *clusterService) GetDeployments(context string, namespaceName string) types.DeploymentsResponse {
	r := types.NewDeploymentsResponse()

	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to find context name %s", context)
		return r
	}

	deploymentsClient := cs.AppsV1().Deployments(namespaceName)
	deploymentsList, err := deploymentsClient.List(c.ctx, metav1.ListOptions{})
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to list Deployments for %s: %v", context, err)
		return r
	}

	for _, deployment := range deploymentsList.Items {
		r.Data = append(r.Data, deployment)
	}

	r.Success = true
	return r
}

func (c *clusterService) GetPod(contextName, namespaceName, name string) types.PodResponse {
	r := types.NewPodResponse()
	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	podsClient := cs.CoreV1().Pods(namespaceName)
	pod, err := podsClient.Get(c.ctx, name, metav1.GetOptions{})
	if err != nil {
		r.Msg = "Failed to list Pods"
		return r
	}
	r.Data = *pod
	r.Success = true
	return r
}

func (c *clusterService) GetPods(contextName, namespaceName string) types.PodsResponse {
	r := types.NewPodsResponse()

	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	podsClient := cs.CoreV1().Pods(namespaceName)
	podsList, err := podsClient.List(c.ctx, metav1.ListOptions{})
	if err != nil {
		r.Msg = "Failed to list Pods"
		return r
	}
	r.Data = podsList.Items
	r.Success = true
	return r
}

func (c *clusterService) GetConfigMaps(contextName, namespaceName string) types.ConfigMapsResponse {
	r := types.NewConfigMapsResponse()

	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	configMapsClient := cs.CoreV1().ConfigMaps(namespaceName)
	configMapsList, err := configMapsClient.List(c.ctx, metav1.ListOptions{})
	if err != nil {
		r.Msg = "Failed to list ConfigMaps"
		return r
	}
	r.Data = configMapsList.Items
	r.Success = true
	return r
}

func (c *clusterService) GetIngresses(context, namespace string) types.IngressesResponse {
	r := types.NewIngressesResponse()

	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	client := cs.NetworkingV1().Ingresses(namespace)
	out, err := client.List(c.ctx, metav1.ListOptions{})
	if err != nil {
		r.Msg = "Failed to list ConfigMaps"
		return r
	}
	r.Data = out.Items
	r.Success = true
	return r
}

func (c *clusterService) DescribeConfigMap(context, namespace, name string) types.DescribeResponse {
	r := types.NewDescribeResponse()

	rc, err := c.conf.GetRestConfig(context)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to find rest config name: %s", err.Error())
		return r
	}

	gk := schema.GroupKind{Group: "", Kind: "ConfigMap"}
	d, ok := describecmd.DescriberFor(gk, rc)
	if !ok {
		r.Msg = "Failed to find describer"
		return r
	}

	out, err := d.Describe(namespace, name, describe.DescriberSettings{})
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to describe resource: %s", err.Error())
		return r
	}
	r.Data = out
	r.Success = true
	return r
}

func (c *clusterService) DescribePod(context, namespace, name string) types.DescribeResponse {
	r := types.NewDescribeResponse()

	rc, err := c.conf.GetRestConfig(context)
	if err != nil {
		r.Msg = "Failed to find rest config name"
		return r
	}

	gk := schema.GroupKind{Group: "", Kind: "Pod"}
	d, ok := describecmd.DescriberFor(gk, rc)
	if !ok {
		r.Msg = "Failed to find describer"
		return r
	}

	out, err := d.Describe(namespace, name, describe.DescriberSettings{})
	if err != nil {
		r.Msg = "Failed to describe pod"
		return r
	}
	r.Data = out
	r.Success = true
	return r
}

func (c *clusterService) DescribeDeployment(context, namespace, name string) types.DescribeResponse {
	r := types.NewDescribeResponse()

	rc, err := c.conf.GetRestConfig(context)
	if err != nil {
		r.Msg = "Failed to find rest config name"
		return r
	}

	gk := schema.GroupKind{Group: "apps", Kind: "Deployment"}
	d, ok := describecmd.DescriberFor(gk, rc)
	if !ok {
		r.Msg = "Failed to find describer"
		return r
	}

	out, err := d.Describe(namespace, name, describe.DescriberSettings{})
	if err != nil {
		r.Msg = "Failed to describe deployment"
		return r
	}
	r.Data = out
	r.Success = true
	return r
}

func (c *clusterService) DescribeStatefulSet(context, namespace, name string) types.DescribeResponse {
	r := types.NewDescribeResponse()

	rc, err := c.conf.GetRestConfig(context)
	if err != nil {
		r.Msg = "Failed to find rest config name"
		return r
	}

	gk := schema.GroupKind{Group: "apps", Kind: "StatefulSet"}
	d, ok := describecmd.DescriberFor(gk, rc)
	if !ok {
		r.Msg = "Failed to find describer"
		return r
	}

	out, err := d.Describe(namespace, name, describe.DescriberSettings{})
	if err != nil {
		r.Msg = "Failed to describe statefulset"
		return r
	}
	r.Data = out
	r.Success = true
	return r
}

func (c *clusterService) DescribeNamespace(context, namespace string) types.DescribeResponse {
	r := types.NewDescribeResponse()

	rc, err := c.conf.GetRestConfig(context)
	if err != nil {
		r.Msg = "Failed to find rest config name"
		return r
	}

	gk := schema.GroupKind{Group: "", Kind: "Namespace"}
	d, ok := describecmd.DescriberFor(gk, rc)
	if !ok {
		r.Msg = "Failed to find describer"
		return r
	}

	out, err := d.Describe(namespace, namespace, describe.DescriberSettings{})
	if err != nil {
		r.Msg = "Failed to describe namespace"
		return r
	}
	r.Data = out
	r.Success = true
	return r
}

func (c *clusterService) DescribeIngress(context, namespace, name string) types.DescribeResponse {
	r := types.NewDescribeResponse()

	rc, err := c.conf.GetRestConfig(context)
	if err != nil {
		r.Msg = "Failed to find rest config name"
		return r
	}

	gk := schema.GroupKind{Group: "networking.k8s.io", Kind: "Ingress"}
	d, ok := describecmd.DescriberFor(gk, rc)
	if !ok {
		r.Msg = "Failed to find describer"
		return r
	}

	out, err := d.Describe(namespace, name, describe.DescriberSettings{})
	if err != nil {
		r.Msg = "Failed to describe ingress"
		return r
	}
	r.Data = out
	r.Success = true
	return r
}

func (c *clusterService) DeletePod(contextName, namespaceName, podName string) types.Response {
	r := types.NewResponse()
	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	podsClient := cs.CoreV1().Pods(namespaceName)

	deletePolicy := metav1.DeletePropagationForeground
	if err := podsClient.Delete(c.ctx, podName, metav1.DeleteOptions{PropagationPolicy: &deletePolicy}); err != nil {
		r.Msg = "Failed to delete Pod"
		return r
	}

	r.Success = true
	return r
}

func (c *clusterService) DeleteIngress(context, namespace, name string) types.Response {
	r := types.NewResponse()
	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	client := cs.NetworkingV1().Ingresses(namespace)
	deletePolicy := metav1.DeletePropagationForeground
	if err := client.Delete(c.ctx, name, metav1.DeleteOptions{PropagationPolicy: &deletePolicy}); err != nil {
		r.Msg = "Failed to delete Ingress"
		return r
	}

	r.Success = true
	return r
}

func (c *clusterService) getNamespaceFromCustomNamespace(customNamespace any) (*corev1.Namespace, error) {
	namespace := &corev1.Namespace{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Namespace",
		},
		ObjectMeta: metav1.ObjectMeta{},
		Spec:       corev1.NamespaceSpec{},
	}

	namespaceMap, ok := customNamespace.(map[string]interface{})
	if !ok {
		return namespace, fmt.Errorf("unable to convert custom namespace to runtime object")
	}

	if metadata, ok := namespaceMap["metadata"].(map[string]interface{}); ok {
		name, ok := metadata["name"].(string)
		if ok {
			namespace.ObjectMeta.Name = name
		}
	}
	return namespace, nil
}

// getPodFromCustomPod accepts any and returns a corev1.Pod. customPod is sent
// from frontend and as of 2025-04-28 is a map[string]interface. Normalising of
// the customPod needs to occur so that it can be coerced into a corev1.Pod,
// such as the pod.Spec.Container[0].Command.
//
// As of 2025-04-28 this only supports guided customPods.
func (c *clusterService) getPodFromCustomPod(customPod any) (*corev1.Pod, error) {
	pod := &corev1.Pod{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Pod",
		},
		ObjectMeta: metav1.ObjectMeta{},
		Spec:       corev1.PodSpec{},
	}

	// Convert podDefinition to a runtime.Object
	podMap, ok := customPod.(map[string]interface{})
	if !ok {
		return pod, fmt.Errorf("unable to convert custom pod to runtime object")
	}

	// Extract metadata
	if metadataMap, ok := podMap["metadata"].(map[string]interface{}); ok {
		if name, ok := metadataMap["name"].(string); ok {
			pod.Name = name
		}
		if namespace, ok := metadataMap["namespace"].(string); ok {
			pod.Namespace = namespace
		}
	}

	// Extract containers
	if specMap, ok := podMap["spec"].(map[string]interface{}); ok {
		if containersSlice, ok := specMap["containers"].([]interface{}); ok {
			for _, containerInterface := range containersSlice {
				if containerMap, ok := containerInterface.(map[string]interface{}); ok {
					container := corev1.Container{}

					for key, value := range containerMap {
						switch key {
						case "name":
							if name, ok := value.(string); ok {
								container.Name = name
							}
						case "image":
							if image, ok := value.(string); ok {
								container.Image = image
							}
						case "command":
							switch cmdValue := value.(type) {
							case []interface{}:
								command := make([]string, 0, len(cmdValue))
								for _, cmdItem := range cmdValue {
									if cmdStr, ok := cmdItem.(string); ok {
										command = append(command, cmdStr)
									}
								}

								// Special handling for "sh -c" commands
								if len(command) >= 3 && command[0] == "sh" && command[1] == "-c" {
									// Combine all arguments after "sh -c" into a single string
									shellCommand := strings.Join(command[2:], " ")
									command = []string{"sh", "-c", shellCommand}
								}
								container.Command = command
							case string:
								var commandSlice []string

								// Try to unmarshal as JSON array
								if err := json.Unmarshal([]byte(cmdValue), &commandSlice); err == nil {
									// Special handling for "sh -c" commands
									if len(commandSlice) >= 3 && commandSlice[0] == "sh" && commandSlice[1] == "-c" {
										// Combine all arguments after "sh -c" into a single string
										shellCommand := strings.Join(commandSlice[2:], " ")
										commandSlice = []string{"sh", "-c", shellCommand}
									}

									container.Command = commandSlice
								} else {
									// If JSON parsing fails, manually parse the string
									cmdStr := strings.TrimSpace(cmdValue)

									// Check if it looks like a JSON array
									if strings.HasPrefix(cmdStr, "[") && strings.HasSuffix(cmdStr, "]") {
										// Remove the brackets
										cmdStr = cmdStr[1 : len(cmdStr)-1]

										// Split by commas and process each part
										parts := strings.Split(cmdStr, ",")
										command := make([]string, 0, len(parts))

										for _, part := range parts {
											// Trim spaces and quotes
											part = strings.TrimSpace(part)
											if strings.HasPrefix(part, "\"") && strings.HasSuffix(part, "\"") {
												part = part[1 : len(part)-1]
											}
											if part != "" {
												command = append(command, part)
											}
										}

										// Special handling for "sh -c" commands
										if len(command) >= 3 && command[0] == "sh" && command[1] == "-c" {
											// Combine all arguments after "sh -c" into a single string
											shellCommand := strings.Join(command[2:], " ")
											command = []string{"sh", "-c", shellCommand}
										}

										container.Command = command
									} else {
										// If it doesn't look like a JSON array, split by spaces
										parts := strings.Fields(cmdStr)

										// Special handling for "sh -c" commands
										if len(parts) >= 3 && parts[0] == "sh" && parts[1] == "-c" {
											// Combine all arguments after "sh -c" into a single string
											shellCommand := strings.Join(parts[2:], " ")
											parts = []string{"sh", "-c", shellCommand}
										}

										container.Command = parts
									}
								}
							}
						}
					}
					pod.Spec.Containers = append(pod.Spec.Containers, container)
				}
			}
		}
	}

	// Ensure namespace is set
	if pod.Namespace == "" {
		pod.Namespace = "default"
	}

	return pod, nil
}

func (c *clusterService) CreateNamespace(context string, definition any) types.Response {
	r := types.NewResponse()

	namespace, err := c.getNamespaceFromCustomNamespace(definition)
	if err != nil {
		r.Msg = "Failed to create namespace"
		c.logger.Error(fmt.Sprintf("%s: %s", r.Msg, err.Error()))
		return r
	}

	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to find context name: %s", err.Error())
		return r
	}

	client := cs.CoreV1().Namespaces()
	if _, err := client.Create(c.ctx, namespace, metav1.CreateOptions{}); err != nil {
		r.Msg = "Failed to create namespace"
		c.logger.Error(fmt.Sprintf("%s: %s", r.Msg, err.Error()))
		return r
	}

	r.Msg = fmt.Sprintf("Namespace created: %s", namespace.GetName())
	r.Success = true
	return r
}

func (c *clusterService) CreatePod(context string, podDefinition any) types.Response {
	r := types.NewResponse()

	pod, err := c.getPodFromCustomPod(podDefinition)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to create Pod: %s", err)
		c.logger.Error(r.Msg)
		return r
	}

	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = "Failed to find context name"
		c.logger.Error(r.Msg)
		return r
	}

	client := cs.CoreV1().Pods(pod.GetNamespace())
	if _, err := client.Create(c.ctx, pod, metav1.CreateOptions{}); err != nil {
		r.Msg = fmt.Sprintf("Failed to create Pod: %s", err)
		c.logger.Error(r.Msg)
		return r
	}

	r.Msg = fmt.Sprintf("Pod created: %s", pod.GetName())
	r.Success = true
	return r
}

func (c *clusterService) CreateNamespaceYAML(context string, s string) types.Response {
	r := types.NewResponse()

	namespace, err := c.createNamespaceFromYamlWithCodec(s)
	if err != nil {
		r.Msg = "Failed to create Namespace from YAML"
		c.logger.Error(fmt.Sprintf("%s: %s", r.Msg, err))
		return r
	}

	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = "Failed to find context name"
		c.logger.Error(r.Msg)
		return r
	}

	client := cs.CoreV1().Namespaces()
	if _, err := client.Create(c.ctx, namespace, metav1.CreateOptions{}); err != nil {
		r.Msg = fmt.Sprintf("Failed to create Namespace: %s", err)
		c.logger.Error(r.Msg)
		return r
	}

	r.Msg = fmt.Sprintf("Namespace created: %s", namespace.GetName())
	r.Success = true
	return r
}

func (c *clusterService) CreatePodYAML(context string, s string) types.Response {
	r := types.NewResponse()

	pod, err := c.createPodFromYamlWithCodec(s)
	if err != nil {
		r.Msg = "Failed to create Pod from YAML"
		c.logger.Error(fmt.Sprintf("%s: %s", r.Msg, err))
		return r
	}

	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = "Failed to find context name"
		c.logger.Error(r.Msg)
		return r
	}

	client := cs.CoreV1().Pods(pod.GetNamespace())
	if _, err := client.Create(c.ctx, pod, metav1.CreateOptions{}); err != nil {
		r.Msg = fmt.Sprintf("Failed to create Pod: %s", err)
		c.logger.Error(r.Msg)
		return r
	}

	r.Msg = fmt.Sprintf("Pod created: %s", pod.GetName())
	r.Success = true
	return r
}

func (c *clusterService) DeleteNamespace(context, name string) types.Response {
	r := types.NewResponse()
	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	namespaceClient := cs.CoreV1().Namespaces()
	deletePolicy := metav1.DeletePropagationForeground
	if err := namespaceClient.Delete(c.ctx, name, metav1.DeleteOptions{PropagationPolicy: &deletePolicy}); err != nil {
		r.Msg = "Failed to delete Namespace"
		return r
	}

	r.Success = true
	return r
}

func (c *clusterService) createNamespaceFromYamlWithCodec(yamlContent string) (*corev1.Namespace, error) {
	decode := serializer.NewCodecFactory(scheme.Scheme).UniversalDeserializer().Decode
	obj, gvk, err := decode([]byte(yamlContent), nil, nil)
	if err != nil {
		return nil, err
	}

	if gvk.Kind != "Namespace" {
		return nil, fmt.Errorf("YAML does not define a Namespace resource, got: %s", gvk.Kind)
	}

	namespace, ok := obj.(*corev1.Namespace)
	if !ok {
		return nil, fmt.Errorf("failed to convert object to Namespace")
	}

	return namespace, nil
}

func (c *clusterService) createPodFromYamlWithCodec(yamlContent string) (*corev1.Pod, error) {
	decode := serializer.NewCodecFactory(scheme.Scheme).UniversalDeserializer().Decode
	obj, gvk, err := decode([]byte(yamlContent), nil, nil)
	if err != nil {
		return nil, err
	}

	// Check if the object is a Pod
	if gvk.Kind != "Pod" {
		return nil, fmt.Errorf("YAML does not define a Pod resource, got: %s", gvk.Kind)
	}

	pod, ok := obj.(*corev1.Pod)
	if !ok {
		return nil, fmt.Errorf("failed to convert object to Pod")
	}

	return pod, nil
}

// patchNamespace takes a namespace name and a slice of bytes which represents
// a patch to the namespace resource. It will attempt to patch the namespace.
func (c *clusterService) patchNamespace(clientset *kubernetes.Clientset, name string, patchData []byte) error {
	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
		_, err := clientset.CoreV1().Namespaces().Patch(
			context.TODO(),
			name,
			k8types.StrategicMergePatchType, // You can also use types.MergePatchType or types.JSONPatchType
			patchData,
			metav1.PatchOptions{},
		)
		return err
	})
}

func (c *clusterService) UpdateNamespaceFromYaml(context, yamlContent, originalName string) types.NamespaceResponse {
	r := types.NewNamespaceResponse()

	cs, err := c.conf.GetClientSet(context)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	decode := serializer.NewCodecFactory(scheme.Scheme).UniversalDeserializer().Decode
	obj, gvk, err := decode([]byte(yamlContent), nil, nil)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to parse YAML: %v", err)
		return r
	}

	if gvk.Kind != "Namespace" {
		r.Msg = "YAML does not define a Namespace resource"
		return r
	}

	b, err := json.Marshal(obj)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to convert object to JSON: %v", err)
		return r
	}

	if err := c.patchNamespace(cs, originalName, b); err != nil {
		r.Msg = fmt.Sprintf("Failed to patch Namespace: %v", err)
		return r
	}

	r.Msg = "Namespace updated"
	r.Success = true
	return r
}

// UpdatePodFromYaml updates a Pod from YAML definition, handling name and namespace changes
func (c *clusterService) UpdatePodFromYaml(contextName, yamlContent, originalName, originalNamespace string) types.PodResponse {
	r := types.NewPodResponse()

	// Get the client set for the context
	cs, err := c.conf.GetClientSet(contextName)
	if err != nil {
		r.Msg = "Failed to find context name"
		return r
	}

	// Decode YAML to Kubernetes object
	decode := serializer.NewCodecFactory(scheme.Scheme).UniversalDeserializer().Decode
	obj, gvk, err := decode([]byte(yamlContent), nil, nil)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to parse YAML: %v", err)
		return r
	}

	// Check if the object is a Pod
	if gvk.Kind != "Pod" {
		r.Msg = "YAML does not define a Pod resource"
		return r
	}

	pod, ok := obj.(*corev1.Pod)
	if !ok {
		r.Msg = "Failed to convert object to Pod"
		return r
	}

	// Ensure namespace is set
	newNamespace := pod.Namespace
	if newNamespace == "" {
		newNamespace = "default"
		pod.Namespace = newNamespace
	}

	newName := pod.Name

	// Check if name or namespace changed
	nameChanged := newName != originalName
	namespaceChanged := newNamespace != originalNamespace

	// Get the original pod client
	originalPodsClient := cs.CoreV1().Pods(originalNamespace)

	// Get the current pod to check if it exists
	_, err = originalPodsClient.Get(c.ctx, originalName, metav1.GetOptions{})
	if err != nil {
		if errors.IsNotFound(err) {
			r.Msg = fmt.Sprintf("Original pod %s not found in namespace %s", originalName, originalNamespace)
		} else {
			r.Msg = fmt.Sprintf("Failed to get original pod: %v", err)
		}
		return r
	}

	// If name or namespace changed, check if the new pod already exists
	if nameChanged || namespaceChanged {
		newPodsClient := cs.CoreV1().Pods(newNamespace)
		_, err = newPodsClient.Get(c.ctx, newName, metav1.GetOptions{})
		if err == nil {
			// Pod with new name/namespace already exists
			r.Msg = fmt.Sprintf("Pod %s already exists in namespace %s", newName, newNamespace)
			return r
		} else if !errors.IsNotFound(err) {
			// Some other error occurred
			r.Msg = fmt.Sprintf("Error checking for existing pod: %v", err)
			return r
		}
		// If we get here, the new pod doesn't exist, which is what we want
	}

	err = originalPodsClient.Delete(c.ctx, originalName, kubernetesDeleteOptions)
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to delete original pod: %v", err)
		return r
	}

	// Wait for the original pod to be deleted
	// TODO(rene): move timeout duration to config option
	err = wait.PollImmediate(time.Second, time.Second*120, func() (bool, error) {
		_, err := originalPodsClient.Get(c.ctx, originalName, metav1.GetOptions{})
		if errors.IsNotFound(err) {
			return true, nil
		}
		if err != nil {
			return false, err
		}
		return false, nil
	})

	if err != nil {
		r.Msg = fmt.Sprintf("Timed out waiting for pod deletion: %v", err)
		c.logger.Error(r.Msg)
		return r
	}

	// Remove fields that shouldn't be set on creation
	pod.ResourceVersion = ""
	pod.UID = ""
	pod.CreationTimestamp = metav1.Time{}
	pod.Status = corev1.PodStatus{}

	// Create the pod with the updated spec
	newPodsClient := cs.CoreV1().Pods(newNamespace)
	createdPod, err := newPodsClient.Create(c.ctx, pod, metav1.CreateOptions{})
	if err != nil {
		r.Msg = fmt.Sprintf("Failed to create updated pod: %v", err)
		c.logger.Error(r.Msg)
		return r
	}

	r.Data = *createdPod
	r.Success = true

	if nameChanged || namespaceChanged {
		r.Msg = fmt.Sprintf("Pod renamed from %s/%s to %s/%s",
			originalNamespace, originalName, newNamespace, newName)
	} else {
		r.Msg = fmt.Sprintf("Pod %s updated successfully in namespace %s", newName, newNamespace)
	}

	return r
}