286 lines
6.7 KiB
Go
286 lines
6.7 KiB
Go
package tools
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// Kubectl provides a thin wrapper around the kubectl command-line tool
|
|
type Kubectl struct {
|
|
kubeconfigPath string
|
|
}
|
|
|
|
// NewKubectl creates a new Kubectl wrapper
|
|
func NewKubectl(kubeconfigPath string) *Kubectl {
|
|
return &Kubectl{
|
|
kubeconfigPath: kubeconfigPath,
|
|
}
|
|
}
|
|
|
|
// DeploymentExists checks if a deployment exists in the specified namespace
|
|
func (k *Kubectl) DeploymentExists(name, namespace string) bool {
|
|
args := []string{
|
|
"get", "deployment", name,
|
|
"-n", namespace,
|
|
}
|
|
|
|
if k.kubeconfigPath != "" {
|
|
args = append([]string{"--kubeconfig", k.kubeconfigPath}, args...)
|
|
}
|
|
|
|
cmd := exec.Command("kubectl", args...)
|
|
err := cmd.Run()
|
|
return err == nil
|
|
}
|
|
|
|
// PodInfo represents pod information from kubectl
|
|
type PodInfo struct {
|
|
Name string
|
|
Status string
|
|
Ready string
|
|
Restarts int
|
|
Age string
|
|
Node string
|
|
IP string
|
|
}
|
|
|
|
// DeploymentInfo represents deployment information
|
|
type DeploymentInfo struct {
|
|
Desired int32
|
|
Current int32
|
|
Ready int32
|
|
Available int32
|
|
}
|
|
|
|
// GetPods retrieves pod information for a namespace
|
|
func (k *Kubectl) GetPods(namespace string) ([]PodInfo, error) {
|
|
args := []string{
|
|
"get", "pods",
|
|
"-n", namespace,
|
|
"-o", "json",
|
|
}
|
|
|
|
if k.kubeconfigPath != "" {
|
|
args = append([]string{"--kubeconfig", k.kubeconfigPath}, args...)
|
|
}
|
|
|
|
cmd := exec.Command("kubectl", args...)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get pods: %w", err)
|
|
}
|
|
|
|
var podList struct {
|
|
Items []struct {
|
|
Metadata struct {
|
|
Name string `json:"name"`
|
|
CreationTimestamp time.Time `json:"creationTimestamp"`
|
|
} `json:"metadata"`
|
|
Spec struct {
|
|
NodeName string `json:"nodeName"`
|
|
} `json:"spec"`
|
|
Status struct {
|
|
Phase string `json:"phase"`
|
|
PodIP string `json:"podIP"`
|
|
ContainerStatuses []struct {
|
|
Ready bool `json:"ready"`
|
|
RestartCount int `json:"restartCount"`
|
|
} `json:"containerStatuses"`
|
|
} `json:"status"`
|
|
} `json:"items"`
|
|
}
|
|
|
|
if err := json.Unmarshal(output, &podList); err != nil {
|
|
return nil, fmt.Errorf("failed to parse pod list: %w", err)
|
|
}
|
|
|
|
pods := make([]PodInfo, 0, len(podList.Items))
|
|
for _, pod := range podList.Items {
|
|
// Calculate ready containers
|
|
readyCount := 0
|
|
totalCount := len(pod.Status.ContainerStatuses)
|
|
restarts := 0
|
|
for _, cs := range pod.Status.ContainerStatuses {
|
|
if cs.Ready {
|
|
readyCount++
|
|
}
|
|
restarts += cs.RestartCount
|
|
}
|
|
|
|
// Calculate age
|
|
age := formatAge(time.Since(pod.Metadata.CreationTimestamp))
|
|
|
|
pods = append(pods, PodInfo{
|
|
Name: pod.Metadata.Name,
|
|
Status: pod.Status.Phase,
|
|
Ready: fmt.Sprintf("%d/%d", readyCount, totalCount),
|
|
Restarts: restarts,
|
|
Age: age,
|
|
Node: pod.Spec.NodeName,
|
|
IP: pod.Status.PodIP,
|
|
})
|
|
}
|
|
|
|
return pods, nil
|
|
}
|
|
|
|
// GetDeployment retrieves deployment information
|
|
func (k *Kubectl) GetDeployment(name, namespace string) (*DeploymentInfo, error) {
|
|
args := []string{
|
|
"get", "deployment", name,
|
|
"-n", namespace,
|
|
"-o", "json",
|
|
}
|
|
|
|
if k.kubeconfigPath != "" {
|
|
args = append([]string{"--kubeconfig", k.kubeconfigPath}, args...)
|
|
}
|
|
|
|
cmd := exec.Command("kubectl", args...)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get deployment: %w", err)
|
|
}
|
|
|
|
var deployment struct {
|
|
Status struct {
|
|
Replicas int32 `json:"replicas"`
|
|
UpdatedReplicas int32 `json:"updatedReplicas"`
|
|
ReadyReplicas int32 `json:"readyReplicas"`
|
|
AvailableReplicas int32 `json:"availableReplicas"`
|
|
} `json:"status"`
|
|
Spec struct {
|
|
Replicas int32 `json:"replicas"`
|
|
} `json:"spec"`
|
|
}
|
|
|
|
if err := json.Unmarshal(output, &deployment); err != nil {
|
|
return nil, fmt.Errorf("failed to parse deployment: %w", err)
|
|
}
|
|
|
|
return &DeploymentInfo{
|
|
Desired: deployment.Spec.Replicas,
|
|
Current: deployment.Status.Replicas,
|
|
Ready: deployment.Status.ReadyReplicas,
|
|
Available: deployment.Status.AvailableReplicas,
|
|
}, nil
|
|
}
|
|
|
|
// GetLogs retrieves logs from a pod
|
|
func (k *Kubectl) GetLogs(namespace, podName string, opts LogOptions) (string, error) {
|
|
args := []string{
|
|
"logs", podName,
|
|
"-n", namespace,
|
|
}
|
|
|
|
if opts.Container != "" {
|
|
args = append(args, "-c", opts.Container)
|
|
}
|
|
if opts.Tail > 0 {
|
|
args = append(args, "--tail", fmt.Sprintf("%d", opts.Tail))
|
|
}
|
|
if opts.Previous {
|
|
args = append(args, "--previous")
|
|
}
|
|
if opts.Since != "" {
|
|
args = append(args, "--since", opts.Since)
|
|
}
|
|
|
|
if k.kubeconfigPath != "" {
|
|
args = append([]string{"--kubeconfig", k.kubeconfigPath}, args...)
|
|
}
|
|
|
|
cmd := exec.Command("kubectl", args...)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get logs: %w", err)
|
|
}
|
|
|
|
return string(output), nil
|
|
}
|
|
|
|
// LogOptions configures log retrieval
|
|
type LogOptions struct {
|
|
Container string
|
|
Tail int
|
|
Previous bool
|
|
Since string
|
|
}
|
|
|
|
// StreamLogs streams logs from a pod
|
|
func (k *Kubectl) StreamLogs(namespace, podName string, opts LogOptions) (*exec.Cmd, error) {
|
|
args := []string{
|
|
"logs", podName,
|
|
"-n", namespace,
|
|
"-f", // follow
|
|
}
|
|
|
|
if opts.Container != "" {
|
|
args = append(args, "-c", opts.Container)
|
|
}
|
|
if opts.Tail > 0 {
|
|
args = append(args, "--tail", fmt.Sprintf("%d", opts.Tail))
|
|
}
|
|
if opts.Since != "" {
|
|
args = append(args, "--since", opts.Since)
|
|
}
|
|
|
|
if k.kubeconfigPath != "" {
|
|
args = append([]string{"--kubeconfig", k.kubeconfigPath}, args...)
|
|
}
|
|
|
|
cmd := exec.Command("kubectl", args...)
|
|
return cmd, nil
|
|
}
|
|
|
|
// formatAge converts a duration to a human-readable age string
|
|
func formatAge(d time.Duration) string {
|
|
if d < time.Minute {
|
|
return fmt.Sprintf("%ds", int(d.Seconds()))
|
|
}
|
|
if d < time.Hour {
|
|
return fmt.Sprintf("%dm", int(d.Minutes()))
|
|
}
|
|
if d < 24*time.Hour {
|
|
return fmt.Sprintf("%dh", int(d.Hours()))
|
|
}
|
|
return fmt.Sprintf("%dd", int(d.Hours()/24))
|
|
}
|
|
|
|
// GetFirstPodName returns the name of the first pod in a namespace
|
|
func (k *Kubectl) GetFirstPodName(namespace string) (string, error) {
|
|
pods, err := k.GetPods(namespace)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(pods) == 0 {
|
|
return "", fmt.Errorf("no pods found in namespace %s", namespace)
|
|
}
|
|
return pods[0].Name, nil
|
|
}
|
|
|
|
// GetPodContainers returns container names for a pod
|
|
func (k *Kubectl) GetPodContainers(namespace, podName string) ([]string, error) {
|
|
args := []string{
|
|
"get", "pod", podName,
|
|
"-n", namespace,
|
|
"-o", "jsonpath={.spec.containers[*].name}",
|
|
}
|
|
|
|
if k.kubeconfigPath != "" {
|
|
args = append([]string{"--kubeconfig", k.kubeconfigPath}, args...)
|
|
}
|
|
|
|
cmd := exec.Command("kubectl", args...)
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get pod containers: %w", err)
|
|
}
|
|
|
|
containerNames := strings.Fields(string(output))
|
|
return containerNames, nil
|
|
}
|