Skip to content

Commit

Permalink
feat: add kubernetes topology properties for cel
Browse files Browse the repository at this point in the history
  • Loading branch information
yashmehrotra authored and moshloop committed Jan 10, 2024
1 parent 985453a commit d9e5f78
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 21 deletions.
10 changes: 10 additions & 0 deletions conv/cel.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,13 @@ func AnyToMapStringAny(v any) (map[string]any, error) {
err = json.Unmarshal(b, &jsonObj)
return jsonObj, err
}

func AnyToListMapStringAny(v any) ([]map[string]any, error) {
var jsonObj []map[string]any
b, err := json.Marshal(v)
if err != nil {
return jsonObj, err
}
err = json.Unmarshal(b, &jsonObj)
return jsonObj, err
}
3 changes: 3 additions & 0 deletions kubernetes/cel_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ func Library() []cel.EnvOption {
k8sIsHealthy("k8s.isHealthy"), k8sIsHealthy("IsHealthy"), k8sIsHealthy("k8s.is_healthy"),
k8sCPUAsMillicores(),
k8sMemoryAsBytes(),
celPodProperties(),
celNodeProperties(),
celk8sLabels(),
}
}
51 changes: 30 additions & 21 deletions kubernetes/quantity.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,54 @@ import (
"github.com/google/cel-go/common/types/ref"
)

func _k8sCPUAsMillicores(objVal string) int64 {
var cpu int64
if strings.HasSuffix(objVal, "m") {
cpu = conv.ToInt64(strings.ReplaceAll(objVal, "m", ""))
} else {
cpu = int64(conv.ToFloat64(objVal) * 1000)
}
return cpu
}

func k8sCPUAsMillicores() cel.EnvOption {
return cel.Function("k8s.cpuAsMillicores",
cel.Overload("k8s.cpuAsMillicores_string",
[]*cel.Type{cel.StringType},
cel.IntType,
cel.UnaryBinding(func(obj ref.Val) ref.Val {
objVal := conv.ToString(obj.Value())
var cpu int64
if strings.HasSuffix(objVal, "m") {
cpu = conv.ToInt64(strings.ReplaceAll(objVal, "m", ""))
} else {
cpu = int64(conv.ToFloat64(objVal) * 1000)
}
return types.Int(cpu)
return types.Int(_k8sCPUAsMillicores(objVal))
}),
),
)
}

func _k8sMemoryAsBytes(objVal string) int64 {
objVal = strings.ToLower(objVal)

var memory int64
switch {
case strings.HasSuffix(objVal, "gi"):
memory = int64(conv.ToFloat64(strings.ReplaceAll(objVal, "gi", "")) * 1024 * 1024 * 1024)
case strings.HasSuffix(objVal, "mi"):
memory = int64(conv.ToFloat64(strings.ReplaceAll(objVal, "mi", "")) * 1024 * 1024)
case strings.HasSuffix(objVal, "ki"):
memory = int64(conv.ToFloat64(strings.ReplaceAll(objVal, "ki", "")) * 1024)
default:
memory = conv.ToInt64(objVal)
}
return memory
}

func k8sMemoryAsBytes() cel.EnvOption {
return cel.Function("k8s.memoryAsBytes",
cel.Overload("k8s.memoryAsBytes_string",
[]*cel.Type{cel.StringType},
cel.IntType,
cel.UnaryBinding(func(obj ref.Val) ref.Val {
objVal := strings.ToLower(conv.ToString(obj.Value()))
var memory int64
switch {
case strings.HasSuffix(objVal, "gi"):
memory = int64(conv.ToFloat64(strings.ReplaceAll(objVal, "gi", "")) * 1024 * 1024 * 1024)
case strings.HasSuffix(objVal, "mi"):
memory = int64(conv.ToFloat64(strings.ReplaceAll(objVal, "mi", "")) * 1024 * 1024)
case strings.HasSuffix(objVal, "ki"):
memory = int64(conv.ToFloat64(strings.ReplaceAll(objVal, "ki", "")) * 1024)
default:
memory = conv.ToInt64(objVal)
}

return types.Int(memory)
objVal := conv.ToString(obj.Value())
return types.Int(_k8sMemoryAsBytes(objVal))
}),
),
)
Expand Down
150 changes: 150 additions & 0 deletions kubernetes/topology.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package kubernetes

import (
"strings"

"github.com/flanksource/gomplate/v3/conv"
"github.com/google/cel-go/cel"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
)

func celk8sLabels() cel.EnvOption {
return cel.Function("k8s.labels",
cel.Overload("k8s.labels_map_map",
[]*cel.Type{cel.AnyType},
cel.AnyType,
cel.UnaryBinding(func(obj ref.Val) ref.Val {
val := k8sLabels(obj.Value())
return types.NewStringStringMap(types.DefaultTypeAdapter, val)
}),
),
)
}

func k8sLabels(input any) map[string]string {
labels := make(map[string]string)

obj := GetUnstructured(input)
if obj == nil {
return labels
}

if ns := obj.GetNamespace(); ns != "" {
labels["namespace"] = ns
}

for k, v := range obj.GetLabels() {
if strings.HasSuffix(k, "-hash") {
continue
}
labels[k] = v
}

return labels
}

func celPodProperties() cel.EnvOption {
return cel.Function("k8s.podProperties",
cel.Overload("k8s.podProperties_list_dyn_map",
[]*cel.Type{cel.AnyType},
cel.AnyType,
cel.UnaryBinding(func(obj ref.Val) ref.Val {
jsonObj, _ := conv.AnyToListMapStringAny(PodComponentProperties(obj.Value()))
return types.NewDynamicList(types.DefaultTypeAdapter, jsonObj)
}),
),
)
}

func PodComponentProperties(input any) []map[string]any {
obj := GetUnstructured(input)
if obj == nil {
return nil
}

var pod corev1.Pod
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &pod)
if err != nil {
return nil
}

var totalCPU int64
for _, container := range pod.Spec.Containers {
cpu := container.Resources.Limits.Cpu()
if cpu != nil {
totalCPU += _k8sCPUAsMillicores(cpu.String())
}
}

var totalMemBytes int64
for _, container := range pod.Spec.Containers {
mem := container.Resources.Limits.Memory()
if mem != nil {
totalMemBytes += _k8sMemoryAsBytes(mem.String())
}
}

rootContainer := pod.Spec.Containers[0]
return []map[string]any{
{"name": "image", "text": rootContainer.Image},
{"name": "cpu", "max": totalCPU, "unit": "millicores", "headline": true},
{"name": "memory", "max": totalMemBytes, "unit": "bytes", "headline": true},
{"name": "node", "text": pod.Spec.NodeName},
{"name": "created_at", "text": pod.ObjectMeta.CreationTimestamp.String()},
{"name": "ips", "text": pod.Status.PodIP},
}
}

func celNodeProperties() cel.EnvOption {
return cel.Function("k8s.nodeProperties",
cel.Overload("k8s.nodeProperties_list_dyn_map",
[]*cel.Type{cel.AnyType},
cel.AnyType,
cel.UnaryBinding(func(obj ref.Val) ref.Val {
jsonObj, _ := conv.AnyToListMapStringAny(NodeComponentProperties(obj.Value()))
return types.NewDynamicList(types.DefaultTypeAdapter, jsonObj)
}),
),
)
}

func NodeComponentProperties(input any) []map[string]any {
obj := GetUnstructured(input)
if obj == nil {
return nil
}

var node corev1.Node
err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, &node)
if err != nil {
return nil
}

totalCPU := _k8sCPUAsMillicores(node.Status.Allocatable.Cpu().String())
totalMemBytes := _k8sMemoryAsBytes(node.Status.Allocatable.Memory().String())
totalStorage := _k8sMemoryAsBytes(node.Status.Allocatable.StorageEphemeral().String())

var internalIP, externalIP string
for _, addr := range node.Status.Addresses {
if addr.Type == corev1.NodeInternalIP {
internalIP = addr.Address
}
if addr.Type == corev1.NodeExternalIP {
externalIP = addr.Address
}
}

return []map[string]any{
{"name": "cpu", "max": totalCPU, "unit": "millicores", "headline": true},
{"name": "memory", "max": totalMemBytes, "unit": "bytes", "headline": true},
{"name": "ephemeral-storage", "max": totalStorage, "unit": "bytes", "headline": true},
{"name": "zone", "text": node.GetLabels()["topology.kubernetes.io/zone"]},
{"name": "instance-type", "text": node.GetLabels()["node.kubernetes.io/instance-type"]},
{"name": "os-image", "text": node.Status.NodeInfo.OSImage},
{"name": "internal-ip", "text": internalIP},
{"name": "external-ip", "text": externalIP},
}
}

0 comments on commit d9e5f78

Please sign in to comment.