Adds app endpoints for configuration and status.
This commit is contained in:
@@ -4,11 +4,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Kubectl provides a thin wrapper around the kubectl command-line tool
|
||||
// Kubectl provides a comprehensive wrapper around the kubectl command-line tool
|
||||
type Kubectl struct {
|
||||
kubeconfigPath string
|
||||
}
|
||||
@@ -20,6 +22,115 @@ func NewKubectl(kubeconfigPath string) *Kubectl {
|
||||
}
|
||||
}
|
||||
|
||||
// Pod Information Structures
|
||||
|
||||
// PodInfo represents pod information from kubectl
|
||||
type PodInfo struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Ready string `json:"ready"`
|
||||
Restarts int `json:"restarts"`
|
||||
Age string `json:"age"`
|
||||
Node string `json:"node,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Containers []ContainerInfo `json:"containers,omitempty"`
|
||||
Conditions []PodCondition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// ContainerInfo represents detailed container information
|
||||
type ContainerInfo struct {
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Ready bool `json:"ready"`
|
||||
RestartCount int `json:"restartCount"`
|
||||
State ContainerState `json:"state"`
|
||||
}
|
||||
|
||||
// ContainerState represents the state of a container
|
||||
type ContainerState struct {
|
||||
Status string `json:"status"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Since time.Time `json:"since,omitempty"`
|
||||
}
|
||||
|
||||
// PodCondition represents a pod condition
|
||||
type PodCondition struct {
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Since time.Time `json:"since,omitempty"`
|
||||
}
|
||||
|
||||
// Deployment Information Structures
|
||||
|
||||
// DeploymentInfo represents deployment information
|
||||
type DeploymentInfo struct {
|
||||
Desired int32 `json:"desired"`
|
||||
Current int32 `json:"current"`
|
||||
Ready int32 `json:"ready"`
|
||||
Available int32 `json:"available"`
|
||||
}
|
||||
|
||||
// ReplicaInfo represents aggregated replica information
|
||||
type ReplicaInfo struct {
|
||||
Desired int `json:"desired"`
|
||||
Current int `json:"current"`
|
||||
Ready int `json:"ready"`
|
||||
Available int `json:"available"`
|
||||
}
|
||||
|
||||
// Resource Information Structures
|
||||
|
||||
// ResourceMetric represents resource usage for a specific resource type
|
||||
type ResourceMetric struct {
|
||||
Used string `json:"used"`
|
||||
Requested string `json:"requested"`
|
||||
Limit string `json:"limit"`
|
||||
Percentage float64 `json:"percentage"`
|
||||
}
|
||||
|
||||
// ResourceUsage represents aggregated resource usage
|
||||
type ResourceUsage struct {
|
||||
CPU *ResourceMetric `json:"cpu,omitempty"`
|
||||
Memory *ResourceMetric `json:"memory,omitempty"`
|
||||
Storage *ResourceMetric `json:"storage,omitempty"`
|
||||
}
|
||||
|
||||
// Event Information Structures
|
||||
|
||||
// KubernetesEvent represents a Kubernetes event
|
||||
type KubernetesEvent struct {
|
||||
Type string `json:"type"`
|
||||
Reason string `json:"reason"`
|
||||
Message string `json:"message"`
|
||||
Count int `json:"count"`
|
||||
FirstSeen time.Time `json:"firstSeen"`
|
||||
LastSeen time.Time `json:"lastSeen"`
|
||||
Object string `json:"object"`
|
||||
}
|
||||
|
||||
// Logging Structures
|
||||
|
||||
// LogOptions configures log retrieval
|
||||
type LogOptions struct {
|
||||
Container string
|
||||
Tail int
|
||||
Previous bool
|
||||
Since string
|
||||
SinceSeconds int
|
||||
}
|
||||
|
||||
// LogEntry represents a structured log entry
|
||||
type LogEntry struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Message string `json:"message"`
|
||||
Pod string `json:"pod"`
|
||||
}
|
||||
|
||||
// Pod Operations
|
||||
|
||||
// DeploymentExists checks if a deployment exists in the specified namespace
|
||||
func (k *Kubectl) DeploymentExists(name, namespace string) bool {
|
||||
args := []string{
|
||||
@@ -36,27 +147,9 @@ func (k *Kubectl) DeploymentExists(name, namespace string) bool {
|
||||
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) {
|
||||
// If detailed is true, includes containers and conditions
|
||||
func (k *Kubectl) GetPods(namespace string, detailed bool) ([]PodInfo, error) {
|
||||
args := []string{
|
||||
"get", "pods",
|
||||
"-n", namespace,
|
||||
@@ -80,14 +173,36 @@ func (k *Kubectl) GetPods(namespace string) ([]PodInfo, error) {
|
||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
||||
} `json:"metadata"`
|
||||
Spec struct {
|
||||
NodeName string `json:"nodeName"`
|
||||
NodeName string `json:"nodeName"`
|
||||
Containers []struct {
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
} `json:"containers"`
|
||||
} `json:"spec"`
|
||||
Status struct {
|
||||
Phase string `json:"phase"`
|
||||
PodIP string `json:"podIP"`
|
||||
Phase string `json:"phase"`
|
||||
PodIP string `json:"podIP"`
|
||||
Conditions []struct {
|
||||
Type string `json:"type"`
|
||||
Status string `json:"status"`
|
||||
LastTransitionTime time.Time `json:"lastTransitionTime"`
|
||||
Reason string `json:"reason"`
|
||||
Message string `json:"message"`
|
||||
} `json:"conditions"`
|
||||
ContainerStatuses []struct {
|
||||
Ready bool `json:"ready"`
|
||||
RestartCount int `json:"restartCount"`
|
||||
Name string `json:"name"`
|
||||
Image string `json:"image"`
|
||||
Ready bool `json:"ready"`
|
||||
RestartCount int `json:"restartCount"`
|
||||
State struct {
|
||||
Running *struct{ StartedAt time.Time } `json:"running,omitempty"`
|
||||
Waiting *struct{ Reason, Message string } `json:"waiting,omitempty"`
|
||||
Terminated *struct {
|
||||
Reason string
|
||||
Message string
|
||||
FinishedAt time.Time
|
||||
} `json:"terminated,omitempty"`
|
||||
} `json:"state"`
|
||||
} `json:"containerStatuses"`
|
||||
} `json:"status"`
|
||||
} `json:"items"`
|
||||
@@ -102,31 +217,117 @@ func (k *Kubectl) GetPods(namespace string) ([]PodInfo, error) {
|
||||
// Calculate ready containers
|
||||
readyCount := 0
|
||||
totalCount := len(pod.Status.ContainerStatuses)
|
||||
restarts := 0
|
||||
totalRestarts := 0
|
||||
|
||||
for _, cs := range pod.Status.ContainerStatuses {
|
||||
if cs.Ready {
|
||||
readyCount++
|
||||
}
|
||||
restarts += cs.RestartCount
|
||||
totalRestarts += cs.RestartCount
|
||||
}
|
||||
|
||||
// Calculate age
|
||||
age := formatAge(time.Since(pod.Metadata.CreationTimestamp))
|
||||
// Ensure status is never empty
|
||||
status := pod.Status.Phase
|
||||
if status == "" {
|
||||
status = "Unknown"
|
||||
}
|
||||
|
||||
pods = append(pods, PodInfo{
|
||||
podInfo := PodInfo{
|
||||
Name: pod.Metadata.Name,
|
||||
Status: pod.Status.Phase,
|
||||
Status: status,
|
||||
Ready: fmt.Sprintf("%d/%d", readyCount, totalCount),
|
||||
Restarts: restarts,
|
||||
Age: age,
|
||||
Restarts: totalRestarts,
|
||||
Age: formatAge(time.Since(pod.Metadata.CreationTimestamp)),
|
||||
Node: pod.Spec.NodeName,
|
||||
IP: pod.Status.PodIP,
|
||||
})
|
||||
}
|
||||
|
||||
// Include detailed information if requested
|
||||
if detailed {
|
||||
// Add container details
|
||||
containers := make([]ContainerInfo, 0, len(pod.Status.ContainerStatuses))
|
||||
for _, cs := range pod.Status.ContainerStatuses {
|
||||
containerState := ContainerState{Status: "unknown"}
|
||||
if cs.State.Running != nil {
|
||||
containerState.Status = "running"
|
||||
containerState.Since = cs.State.Running.StartedAt
|
||||
} else if cs.State.Waiting != nil {
|
||||
containerState.Status = "waiting"
|
||||
containerState.Reason = cs.State.Waiting.Reason
|
||||
containerState.Message = cs.State.Waiting.Message
|
||||
} else if cs.State.Terminated != nil {
|
||||
containerState.Status = "terminated"
|
||||
containerState.Reason = cs.State.Terminated.Reason
|
||||
containerState.Message = cs.State.Terminated.Message
|
||||
containerState.Since = cs.State.Terminated.FinishedAt
|
||||
}
|
||||
|
||||
containers = append(containers, ContainerInfo{
|
||||
Name: cs.Name,
|
||||
Image: cs.Image,
|
||||
Ready: cs.Ready,
|
||||
RestartCount: cs.RestartCount,
|
||||
State: containerState,
|
||||
})
|
||||
}
|
||||
podInfo.Containers = containers
|
||||
|
||||
// Add condition details
|
||||
conditions := make([]PodCondition, 0, len(pod.Status.Conditions))
|
||||
for _, cond := range pod.Status.Conditions {
|
||||
conditions = append(conditions, PodCondition{
|
||||
Type: cond.Type,
|
||||
Status: cond.Status,
|
||||
Reason: cond.Reason,
|
||||
Message: cond.Message,
|
||||
Since: cond.LastTransitionTime,
|
||||
})
|
||||
}
|
||||
podInfo.Conditions = conditions
|
||||
}
|
||||
|
||||
pods = append(pods, podInfo)
|
||||
}
|
||||
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
// GetFirstPodName returns the name of the first pod in a namespace
|
||||
func (k *Kubectl) GetFirstPodName(namespace string) (string, error) {
|
||||
pods, err := k.GetPods(namespace, false)
|
||||
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
|
||||
}
|
||||
|
||||
// Deployment Operations
|
||||
|
||||
// GetDeployment retrieves deployment information
|
||||
func (k *Kubectl) GetDeployment(name, namespace string) (*DeploymentInfo, error) {
|
||||
args := []string{
|
||||
@@ -169,25 +370,235 @@ func (k *Kubectl) GetDeployment(name, namespace string) (*DeploymentInfo, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetLogs retrieves logs from a pod
|
||||
func (k *Kubectl) GetLogs(namespace, podName string, opts LogOptions) (string, error) {
|
||||
args := []string{
|
||||
"logs", podName,
|
||||
"-n", namespace,
|
||||
// GetReplicas retrieves aggregated replica information for a namespace
|
||||
func (k *Kubectl) GetReplicas(namespace string) (*ReplicaInfo, error) {
|
||||
info := &ReplicaInfo{}
|
||||
|
||||
// Get deployments
|
||||
deployCmd := exec.Command("kubectl", "get", "deployments", "-n", namespace, "-o", "json")
|
||||
WithKubeconfig(deployCmd, k.kubeconfigPath)
|
||||
|
||||
deployOutput, err := deployCmd.Output()
|
||||
if err == nil {
|
||||
var deployList struct {
|
||||
Items []struct {
|
||||
Spec struct {
|
||||
Replicas int `json:"replicas"`
|
||||
} `json:"spec"`
|
||||
Status struct {
|
||||
Replicas int `json:"replicas"`
|
||||
ReadyReplicas int `json:"readyReplicas"`
|
||||
AvailableReplicas int `json:"availableReplicas"`
|
||||
} `json:"status"`
|
||||
} `json:"items"`
|
||||
}
|
||||
|
||||
if json.Unmarshal(deployOutput, &deployList) == nil {
|
||||
for _, deploy := range deployList.Items {
|
||||
info.Desired += deploy.Spec.Replicas
|
||||
info.Current += deploy.Status.Replicas
|
||||
info.Ready += deploy.Status.ReadyReplicas
|
||||
info.Available += deploy.Status.AvailableReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get statefulsets
|
||||
stsCmd := exec.Command("kubectl", "get", "statefulsets", "-n", namespace, "-o", "json")
|
||||
WithKubeconfig(stsCmd, k.kubeconfigPath)
|
||||
|
||||
stsOutput, err := stsCmd.Output()
|
||||
if err == nil {
|
||||
var stsList struct {
|
||||
Items []struct {
|
||||
Spec struct {
|
||||
Replicas int `json:"replicas"`
|
||||
} `json:"spec"`
|
||||
Status struct {
|
||||
Replicas int `json:"replicas"`
|
||||
ReadyReplicas int `json:"readyReplicas"`
|
||||
} `json:"status"`
|
||||
} `json:"items"`
|
||||
}
|
||||
|
||||
if json.Unmarshal(stsOutput, &stsList) == nil {
|
||||
for _, sts := range stsList.Items {
|
||||
info.Desired += sts.Spec.Replicas
|
||||
info.Current += sts.Status.Replicas
|
||||
info.Ready += sts.Status.ReadyReplicas
|
||||
// StatefulSets don't have availableReplicas, use ready as proxy
|
||||
info.Available += sts.Status.ReadyReplicas
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Resource Monitoring
|
||||
|
||||
// GetResources retrieves aggregated resource usage for a namespace
|
||||
func (k *Kubectl) GetResources(namespace string) (*ResourceUsage, error) {
|
||||
cmd := exec.Command("kubectl", "get", "pods", "-n", namespace, "-o", "json")
|
||||
WithKubeconfig(cmd, k.kubeconfigPath)
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get pods: %w", err)
|
||||
}
|
||||
|
||||
var podList struct {
|
||||
Items []struct {
|
||||
Spec struct {
|
||||
Containers []struct {
|
||||
Resources struct {
|
||||
Requests map[string]string `json:"requests,omitempty"`
|
||||
Limits map[string]string `json:"limits,omitempty"`
|
||||
} `json:"resources"`
|
||||
} `json:"containers"`
|
||||
} `json:"spec"`
|
||||
} `json:"items"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(output, &podList); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse pod list: %w", err)
|
||||
}
|
||||
|
||||
// Aggregate resources
|
||||
cpuRequests := int64(0)
|
||||
cpuLimits := int64(0)
|
||||
memRequests := int64(0)
|
||||
memLimits := int64(0)
|
||||
|
||||
for _, pod := range podList.Items {
|
||||
for _, container := range pod.Spec.Containers {
|
||||
if req, ok := container.Resources.Requests["cpu"]; ok {
|
||||
cpuRequests += parseResourceQuantity(req)
|
||||
}
|
||||
if lim, ok := container.Resources.Limits["cpu"]; ok {
|
||||
cpuLimits += parseResourceQuantity(lim)
|
||||
}
|
||||
if req, ok := container.Resources.Requests["memory"]; ok {
|
||||
memRequests += parseResourceQuantity(req)
|
||||
}
|
||||
if lim, ok := container.Resources.Limits["memory"]; ok {
|
||||
memLimits += parseResourceQuantity(lim)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build resource usage with metrics
|
||||
usage := &ResourceUsage{}
|
||||
|
||||
// CPU metrics (if any resources defined)
|
||||
if cpuRequests > 0 || cpuLimits > 0 {
|
||||
cpuUsed := cpuRequests // Approximate "used" as requests for now
|
||||
cpuPercentage := 0.0
|
||||
if cpuLimits > 0 {
|
||||
cpuPercentage = float64(cpuUsed) / float64(cpuLimits) * 100
|
||||
}
|
||||
usage.CPU = &ResourceMetric{
|
||||
Used: formatCPU(cpuUsed),
|
||||
Requested: formatCPU(cpuRequests),
|
||||
Limit: formatCPU(cpuLimits),
|
||||
Percentage: cpuPercentage,
|
||||
}
|
||||
}
|
||||
|
||||
// Memory metrics (if any resources defined)
|
||||
if memRequests > 0 || memLimits > 0 {
|
||||
memUsed := memRequests // Approximate "used" as requests for now
|
||||
memPercentage := 0.0
|
||||
if memLimits > 0 {
|
||||
memPercentage = float64(memUsed) / float64(memLimits) * 100
|
||||
}
|
||||
usage.Memory = &ResourceMetric{
|
||||
Used: formatMemory(memUsed),
|
||||
Requested: formatMemory(memRequests),
|
||||
Limit: formatMemory(memLimits),
|
||||
Percentage: memPercentage,
|
||||
}
|
||||
}
|
||||
|
||||
return usage, nil
|
||||
}
|
||||
|
||||
// GetRecentEvents retrieves recent events for a namespace
|
||||
func (k *Kubectl) GetRecentEvents(namespace string, limit int) ([]KubernetesEvent, error) {
|
||||
cmd := exec.Command("kubectl", "get", "events", "-n", namespace,
|
||||
"--sort-by=.lastTimestamp", "-o", "json")
|
||||
WithKubeconfig(cmd, k.kubeconfigPath)
|
||||
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get events: %w", err)
|
||||
}
|
||||
|
||||
var eventList struct {
|
||||
Items []struct {
|
||||
Type string `json:"type"`
|
||||
Reason string `json:"reason"`
|
||||
Message string `json:"message"`
|
||||
Count int `json:"count"`
|
||||
FirstTimestamp time.Time `json:"firstTimestamp"`
|
||||
LastTimestamp time.Time `json:"lastTimestamp"`
|
||||
InvolvedObject struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
} `json:"involvedObject"`
|
||||
} `json:"items"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(output, &eventList); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse events: %w", err)
|
||||
}
|
||||
|
||||
// Sort by last timestamp (most recent first)
|
||||
sort.Slice(eventList.Items, func(i, j int) bool {
|
||||
return eventList.Items[i].LastTimestamp.After(eventList.Items[j].LastTimestamp)
|
||||
})
|
||||
|
||||
// Limit results
|
||||
if limit > 0 && len(eventList.Items) > limit {
|
||||
eventList.Items = eventList.Items[:limit]
|
||||
}
|
||||
|
||||
events := make([]KubernetesEvent, 0, len(eventList.Items))
|
||||
for _, event := range eventList.Items {
|
||||
events = append(events, KubernetesEvent{
|
||||
Type: event.Type,
|
||||
Reason: event.Reason,
|
||||
Message: event.Message,
|
||||
Count: event.Count,
|
||||
FirstSeen: event.FirstTimestamp,
|
||||
LastSeen: event.LastTimestamp,
|
||||
Object: fmt.Sprintf("%s/%s", event.InvolvedObject.Kind, event.InvolvedObject.Name),
|
||||
})
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
// Logging Operations
|
||||
|
||||
// GetLogs retrieves logs from a pod
|
||||
func (k *Kubectl) GetLogs(namespace, podName string, opts LogOptions) ([]LogEntry, 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))
|
||||
args = append(args, "--tail", strconv.Itoa(opts.Tail))
|
||||
}
|
||||
if opts.SinceSeconds > 0 {
|
||||
args = append(args, "--since", fmt.Sprintf("%ds", opts.SinceSeconds))
|
||||
} else if opts.Since != "" {
|
||||
args = append(args, "--since", opts.Since)
|
||||
}
|
||||
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...)
|
||||
@@ -196,18 +607,24 @@ func (k *Kubectl) GetLogs(namespace, podName string, opts LogOptions) (string, e
|
||||
cmd := exec.Command("kubectl", args...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get logs: %w", err)
|
||||
return nil, fmt.Errorf("failed to get logs: %w", err)
|
||||
}
|
||||
|
||||
return string(output), nil
|
||||
}
|
||||
lines := strings.Split(string(output), "\n")
|
||||
entries := make([]LogEntry, 0, len(lines))
|
||||
|
||||
// LogOptions configures log retrieval
|
||||
type LogOptions struct {
|
||||
Container string
|
||||
Tail int
|
||||
Previous bool
|
||||
Since string
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
entries = append(entries, LogEntry{
|
||||
Timestamp: time.Now(), // Best effort - kubectl doesn't provide structured timestamps
|
||||
Message: line,
|
||||
Pod: podName,
|
||||
})
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// StreamLogs streams logs from a pod
|
||||
@@ -236,6 +653,8 @@ func (k *Kubectl) StreamLogs(namespace, podName string, opts LogOptions) (*exec.
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// Helper Functions
|
||||
|
||||
// formatAge converts a duration to a human-readable age string
|
||||
func formatAge(d time.Duration) string {
|
||||
if d < time.Minute {
|
||||
@@ -250,36 +669,71 @@ func formatAge(d time.Duration) string {
|
||||
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
|
||||
// parseResourceQuantity converts kubernetes resource quantities to millicores/bytes
|
||||
func parseResourceQuantity(quantity string) int64 {
|
||||
quantity = strings.TrimSpace(quantity)
|
||||
if quantity == "" {
|
||||
return 0
|
||||
}
|
||||
if len(pods) == 0 {
|
||||
return "", fmt.Errorf("no pods found in namespace %s", namespace)
|
||||
|
||||
// Handle CPU (cores)
|
||||
if strings.HasSuffix(quantity, "m") {
|
||||
val, _ := strconv.ParseInt(strings.TrimSuffix(quantity, "m"), 10, 64)
|
||||
return val
|
||||
}
|
||||
return pods[0].Name, nil
|
||||
|
||||
// Handle memory (bytes)
|
||||
multipliers := map[string]int64{
|
||||
"Ki": 1024,
|
||||
"Mi": 1024 * 1024,
|
||||
"Gi": 1024 * 1024 * 1024,
|
||||
"Ti": 1024 * 1024 * 1024 * 1024,
|
||||
"K": 1000,
|
||||
"M": 1000 * 1000,
|
||||
"G": 1000 * 1000 * 1000,
|
||||
"T": 1000 * 1000 * 1000 * 1000,
|
||||
}
|
||||
|
||||
for suffix, mult := range multipliers {
|
||||
if strings.HasSuffix(quantity, suffix) {
|
||||
val, _ := strconv.ParseInt(strings.TrimSuffix(quantity, suffix), 10, 64)
|
||||
return val * mult
|
||||
}
|
||||
}
|
||||
|
||||
// Plain number
|
||||
val, _ := strconv.ParseInt(quantity, 10, 64)
|
||||
return val
|
||||
}
|
||||
|
||||
// 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}",
|
||||
// formatCPU formats millicores to human-readable format
|
||||
func formatCPU(millicores int64) string {
|
||||
if millicores == 0 {
|
||||
return "0"
|
||||
}
|
||||
|
||||
if k.kubeconfigPath != "" {
|
||||
args = append([]string{"--kubeconfig", k.kubeconfigPath}, args...)
|
||||
if millicores < 1000 {
|
||||
return fmt.Sprintf("%dm", millicores)
|
||||
}
|
||||
|
||||
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
|
||||
return fmt.Sprintf("%.1f", float64(millicores)/1000.0)
|
||||
}
|
||||
|
||||
// formatMemory formats bytes to human-readable format
|
||||
func formatMemory(bytes int64) string {
|
||||
if bytes == 0 {
|
||||
return "0"
|
||||
}
|
||||
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return fmt.Sprintf("%dB", bytes)
|
||||
}
|
||||
|
||||
div, exp := int64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
|
||||
units := []string{"Ki", "Mi", "Gi", "Ti"}
|
||||
return fmt.Sprintf("%.1f%s", float64(bytes)/float64(div), units[exp])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user