- 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
135 lines
4.2 KiB
Go
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
|
|
}
|