| 
 | 1 | +package kubernetes  | 
 | 2 | + | 
 | 3 | +import (  | 
 | 4 | +	"context"  | 
 | 5 | +	"fmt"  | 
 | 6 | +	"strings"  | 
 | 7 | + | 
 | 8 | +	v1 "k8s.io/api/core/v1"  | 
 | 9 | +	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"  | 
 | 10 | +	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"  | 
 | 11 | +	"k8s.io/apimachinery/pkg/runtime"  | 
 | 12 | +	"k8s.io/apimachinery/pkg/runtime/schema"  | 
 | 13 | +)  | 
 | 14 | + | 
 | 15 | +func (k *Kubernetes) ListNodes(ctx context.Context, options ResourceListOptions) (runtime.Unstructured, error) {  | 
 | 16 | +	return k.ResourcesList(ctx, &schema.GroupVersionKind{  | 
 | 17 | +		Group: "", Version: "v1", Kind: "Node",  | 
 | 18 | +	}, "", options)  | 
 | 19 | +}  | 
 | 20 | + | 
 | 21 | +func (k *Kubernetes) GetNodeAddress(ctx context.Context, name string) ([]interface{}, error) {  | 
 | 22 | +	node, err := k.ResourcesGet(ctx, &schema.GroupVersionKind{  | 
 | 23 | +		Group: "", Version: "v1", Kind: "Node",  | 
 | 24 | +	}, "", name)  | 
 | 25 | +	if err != nil {  | 
 | 26 | +		return nil, err  | 
 | 27 | +	}  | 
 | 28 | + | 
 | 29 | +	// Extract addresses from node status  | 
 | 30 | +	addresses, found, err := unstructured.NestedSlice(node.Object, "status", "addresses")  | 
 | 31 | +	if err != nil {  | 
 | 32 | +		return nil, err  | 
 | 33 | +	}  | 
 | 34 | +	if !found {  | 
 | 35 | +		return []interface{}{}, nil  | 
 | 36 | +	}  | 
 | 37 | + | 
 | 38 | +	return addresses, nil  | 
 | 39 | +}  | 
 | 40 | + | 
 | 41 | +func (k *Kubernetes) NodeCapacity(ctx context.Context, name string) (map[string]interface{}, error) {  | 
 | 42 | +	node, err := k.ResourcesGet(ctx, &schema.GroupVersionKind{  | 
 | 43 | +		Group: "", Version: "v1", Kind: "Node",  | 
 | 44 | +	}, "", name)  | 
 | 45 | +	if err != nil {  | 
 | 46 | +		return nil, err  | 
 | 47 | +	}  | 
 | 48 | + | 
 | 49 | +	// Extract capacity from node status  | 
 | 50 | +	capacity, found, err := unstructured.NestedMap(node.Object, "status", "capacity")  | 
 | 51 | +	if err != nil {  | 
 | 52 | +		return nil, err  | 
 | 53 | +	}  | 
 | 54 | +	if !found {  | 
 | 55 | +		return map[string]interface{}{}, nil  | 
 | 56 | +	}  | 
 | 57 | + | 
 | 58 | +	return capacity, nil  | 
 | 59 | +}  | 
 | 60 | + | 
 | 61 | +func (k *Kubernetes) NodeLog(ctx context.Context, name string, logPath string, tail int64) (string, error) {  | 
 | 62 | +	// Use the node proxy API to access logs from the kubelet  | 
 | 63 | +	// Common log paths:  | 
 | 64 | +	// - /var/log/kubelet.log - kubelet logs  | 
 | 65 | +	// - /var/log/kube-proxy.log - kube-proxy logs  | 
 | 66 | +	// - /var/log/containers/ - container logs  | 
 | 67 | + | 
 | 68 | +	if logPath == "" {  | 
 | 69 | +		logPath = "kubelet.log"  | 
 | 70 | +	}  | 
 | 71 | + | 
 | 72 | +	// Build the URL for the node proxy logs endpoint  | 
 | 73 | +	url := []string{"api", "v1", "nodes", name, "proxy", "logs", logPath}  | 
 | 74 | + | 
 | 75 | +	// Query parameters for tail  | 
 | 76 | +	params := make(map[string]string)  | 
 | 77 | +	if tail > 0 {  | 
 | 78 | +		params["tailLines"] = fmt.Sprintf("%d", tail)  | 
 | 79 | +	}  | 
 | 80 | + | 
 | 81 | +	req := k.manager.discoveryClient.RESTClient().  | 
 | 82 | +		Get().  | 
 | 83 | +		AbsPath(url...)  | 
 | 84 | + | 
 | 85 | +	// Add tail parameter if specified  | 
 | 86 | +	for key, value := range params {  | 
 | 87 | +		req.Param(key, value)  | 
 | 88 | +	}  | 
 | 89 | + | 
 | 90 | +	result := req.Do(ctx)  | 
 | 91 | +	if result.Error() != nil {  | 
 | 92 | +		return "", fmt.Errorf("failed to get node logs: %w", result.Error())  | 
 | 93 | +	}  | 
 | 94 | + | 
 | 95 | +	rawData, err := result.Raw()  | 
 | 96 | +	if err != nil {  | 
 | 97 | +		return "", fmt.Errorf("failed to read node log response: %w", err)  | 
 | 98 | +	}  | 
 | 99 | + | 
 | 100 | +	return string(rawData), nil  | 
 | 101 | +}  | 
 | 102 | + | 
 | 103 | +func (k *Kubernetes) NodeEvents(ctx context.Context, name string) ([]map[string]any, error) {  | 
 | 104 | +	var eventMap []map[string]any  | 
 | 105 | + | 
 | 106 | +	// Use field selector to filter events for the specific node  | 
 | 107 | +	raw, err := k.ResourcesList(ctx, &schema.GroupVersionKind{  | 
 | 108 | +		Group: "", Version: "v1", Kind: "Event",  | 
 | 109 | +	}, "", ResourceListOptions{  | 
 | 110 | +		ListOptions: metav1.ListOptions{  | 
 | 111 | +			FieldSelector: fmt.Sprintf("involvedObject.kind=Node,involvedObject.name=%s", name),  | 
 | 112 | +		},  | 
 | 113 | +	})  | 
 | 114 | +	if err != nil {  | 
 | 115 | +		return eventMap, err  | 
 | 116 | +	}  | 
 | 117 | + | 
 | 118 | +	unstructuredList := raw.(*unstructured.UnstructuredList)  | 
 | 119 | +	if len(unstructuredList.Items) == 0 {  | 
 | 120 | +		return eventMap, nil  | 
 | 121 | +	}  | 
 | 122 | + | 
 | 123 | +	for _, item := range unstructuredList.Items {  | 
 | 124 | +		event := &v1.Event{}  | 
 | 125 | +		if err = runtime.DefaultUnstructuredConverter.FromUnstructured(item.Object, event); err != nil {  | 
 | 126 | +			return eventMap, err  | 
 | 127 | +		}  | 
 | 128 | + | 
 | 129 | +		// Determine the most relevant timestamp  | 
 | 130 | +		timestamp := event.EventTime.Time  | 
 | 131 | +		if timestamp.IsZero() && event.Series != nil {  | 
 | 132 | +			timestamp = event.Series.LastObservedTime.Time  | 
 | 133 | +		} else if timestamp.IsZero() && event.Count > 1 {  | 
 | 134 | +			timestamp = event.LastTimestamp.Time  | 
 | 135 | +		} else if timestamp.IsZero() {  | 
 | 136 | +			timestamp = event.FirstTimestamp.Time  | 
 | 137 | +		}  | 
 | 138 | + | 
 | 139 | +		eventMap = append(eventMap, map[string]any{  | 
 | 140 | +			"Timestamp": timestamp.String(),  | 
 | 141 | +			"Type":      event.Type,  | 
 | 142 | +			"Reason":    event.Reason,  | 
 | 143 | +			"Message":   strings.TrimSpace(event.Message),  | 
 | 144 | +			"Count":     event.Count,  | 
 | 145 | +		})  | 
 | 146 | +	}  | 
 | 147 | + | 
 | 148 | +	return eventMap, nil  | 
 | 149 | +}  | 
0 commit comments