package k8s import ( "context" "fmt" "github.com/ocdp/cluster-service/internal/domain/entity" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" ) // ScaleClient provides K8s-native workload scaling (bypasses Helm) type ScaleClient struct{} // NewScaleClient creates a ScaleClient func NewScaleClient() *ScaleClient { return &ScaleClient{} } // findDeployment searches for a deployment matching the release name using various label strategies. func (c *ScaleClient) findDeployment(ctx context.Context, clientset *kubernetes.Clientset, namespace, releaseName string) (*appsv1.Deployment, error) { labelQueries := []string{ fmt.Sprintf("app.kubernetes.io/instance=%s", releaseName), fmt.Sprintf("release=%s", releaseName), fmt.Sprintf("app=%s", releaseName), fmt.Sprintf("app.kubernetes.io/name=%s", releaseName), } for _, query := range labelQueries { deployments, err := clientset.AppsV1().Deployments(namespace).List(ctx, metav1.ListOptions{ LabelSelector: query, }) if err != nil { continue } if len(deployments.Items) > 0 { return &deployments.Items[0], nil } } // Fallback: get by name directly dep, err := clientset.AppsV1().Deployments(namespace).Get(ctx, releaseName, metav1.GetOptions{}) if err == nil && dep != nil { return dep, nil } return nil, nil } // GetDeploymentReplicas returns the current replicas count for a deployment. func (c *ScaleClient) GetDeploymentReplicas(ctx context.Context, cluster *entity.Cluster, namespace, releaseName string) (int32, error) { clientset, err := c.clientsetForCluster(cluster) if err != nil { return 0, fmt.Errorf("failed to create k8s client: %w", err) } dep, err := c.findDeployment(ctx, clientset, namespace, releaseName) if err != nil { return 0, err } if dep != nil && dep.Spec.Replicas != nil { return *dep.Spec.Replicas, nil } // Fallback to statefulsets return c.getStatefulSetReplicas(ctx, clientset, namespace, releaseName) } func (c *ScaleClient) getStatefulSetReplicas(ctx context.Context, clientset *kubernetes.Clientset, namespace, releaseName string) (int32, error) { stsList, err := clientset.AppsV1().StatefulSets(namespace).List(ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("app.kubernetes.io/instance=%s", releaseName), }) if err != nil { return 0, err } if len(stsList.Items) == 0 { return 0, nil // No replicable workload found } sts := stsList.Items[0] if sts.Spec.Replicas != nil { return *sts.Spec.Replicas, nil } return 0, nil } // ScaleDeployment scales the K8s deployment directly (bypasses Helm). func (c *ScaleClient) ScaleDeployment(ctx context.Context, cluster *entity.Cluster, namespace, releaseName string, replicas int32) error { clientset, err := c.clientsetForCluster(cluster) if err != nil { return fmt.Errorf("failed to create k8s client: %w", err) } dep, err := c.findDeployment(ctx, clientset, namespace, releaseName) if err != nil { return err } if dep != nil { dep.Spec.Replicas = &replicas _, err = clientset.AppsV1().Deployments(namespace).Update(ctx, dep, metav1.UpdateOptions{}) if err != nil { return fmt.Errorf("failed to scale deployment %s: %w", dep.Name, err) } return nil } // Try StatefulSets stsList, err := clientset.AppsV1().StatefulSets(namespace).List(ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("app.kubernetes.io/instance=%s", releaseName), }) if err == nil && len(stsList.Items) > 0 { sts := stsList.Items[0] sts.Spec.Replicas = &replicas _, err = clientset.AppsV1().StatefulSets(namespace).Update(ctx, &sts, metav1.UpdateOptions{}) if err != nil { return fmt.Errorf("failed to scale statefulset %s: %w", sts.Name, err) } return nil } return fmt.Errorf("no deployment or statefulset found for release %s in namespace %s", releaseName, namespace) } func (c *ScaleClient) clientsetForCluster(cluster *entity.Cluster) (*kubernetes.Clientset, error) { restConfig, err := restConfigFromCluster(cluster) if err != nil { return nil, fmt.Errorf("failed to create rest config: %w", err) } clientset, err := kubernetes.NewForConfig(restConfig) if err != nil { return nil, fmt.Errorf("failed to create clientset: %w", err) } return clientset, nil }