Files
ocdp-go/backend/internal/adapter/output/k8s/scale_client.go
Ivan087 4441f58299 fix: direct K8s scaling, replicas from K8s API, button labels, modify fetch
- Add ScaleClient using K8s API (like kubectl scale deploy --replicas=N)
  - ScaleDeployment: patch Deployment.Spec.Replicas directly
  - GetDeploymentReplicas: query actual K8s deployment replicas
  - Search by labels then fallback to deployment name match
- Wire ScaleClient to InstanceService via SetScaleClient in main.go
- ModifyModal: fetch full instance detail on open (list excludes values)
- InstanceCard: add text labels to action buttons (Entries/Diag/Modify/Delete)
  - Text visible on sm+ screens, icon-only on xs
2026-05-13 14:54:24 +08:00

135 lines
4.2 KiB
Go

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
}