Skip to main content
Glama
unstructured.go5.48 kB
// Package test provides utilities for testing unstructured Kubernetes objects. // // The primary functionality is JSONPath-like field access for unstructured.Unstructured objects, // making test assertions more readable and maintainable. package test import ( "fmt" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) // FieldString retrieves a string field from an unstructured object using JSONPath-like notation. // Returns the string value, or empty string if not found or not a string. // // IMPORTANT: This function cannot distinguish between "field doesn't exist", "field is nil", // and "field exists with empty string value". When asserting empty string values (""), // you should also verify the field exists using FieldExists: // // s.True(test.FieldExists(obj, "spec.emptyField"), "field should exist") // s.Equal("", test.FieldString(obj, "spec.emptyField"), "field should be empty string") // // Examples: // - "spec.runStrategy" // - "spec.template.spec.volumes[0].containerDisk.image" // - "spec.dataVolumeTemplates[0].spec.sourceRef.kind" func FieldString(obj *unstructured.Unstructured, path string) string { if obj == nil { return "" } value, _ := Field(obj.Object, path) if str, ok := value.(string); ok { return str } return "" } // FieldExists checks if a field exists at the given JSONPath-like path. func FieldExists(obj *unstructured.Unstructured, path string) bool { if obj == nil { return false } _, found := Field(obj.Object, path) return found } // FieldInt retrieves an integer field from an unstructured object using JSONPath-like notation. // Returns the integer value (int64), or 0 if not found or not an integer type (int, int64, int32). // // IMPORTANT: This function cannot distinguish between "field doesn't exist", "field is nil", // and "field exists with value 0". When asserting zero values (0), you should also verify // the field exists using FieldExists: // // s.True(test.FieldExists(obj, "spec.zeroValue"), "field should exist") // s.Equal(int64(0), test.FieldInt(obj, "spec.zeroValue"), "field should be 0") // // Examples: // - "spec.replicas" // - "spec.ports[0].containerPort" func FieldInt(obj *unstructured.Unstructured, path string) int64 { if obj == nil { return 0 } value, _ := Field(obj.Object, path) switch v := value.(type) { case int64: return v case int: return int64(v) case int32: return int64(v) default: return 0 } } // FieldValue retrieves any field value from an unstructured object using JSONPath-like notation. // Returns nil if the field is not found. This is useful when you need the raw value // without type conversion. // Examples: // - "spec.template.spec.containers[0]" - returns map[string]interface{} // - "metadata.labels" - returns map[string]interface{} func FieldValue(obj *unstructured.Unstructured, path string) interface{} { if obj == nil { return nil } value, _ := Field(obj.Object, path) return value } // Field is the core helper that traverses an unstructured object using JSONPath-like notation. // It supports both dot notation (foo.bar) and array indexing (foo[0].bar). // Returns (nil, false) if any intermediate field is nil, as we cannot traverse through nil. func Field(obj interface{}, path string) (interface{}, bool) { if obj == nil || path == "" { return nil, false } // Parse the path into segments segments := parsePath(path) current := obj for i, segment := range segments { if segment.isArray { // Handle array indexing slice, ok := current.([]interface{}) if !ok { return nil, false } if segment.index >= len(slice) || segment.index < 0 { return nil, false } current = slice[segment.index] } else { // Handle map field access m, ok := current.(map[string]interface{}) if !ok { return nil, false } val, exists := m[segment.field] if !exists { return nil, false } // If this is an intermediate field and value is nil, we can't traverse further if val == nil && i < len(segments)-1 { return nil, false } current = val } } return current, true } type pathSegment struct { field string isArray bool index int } // parsePath converts a JSONPath-like string into segments. // Examples: // - "spec.runStrategy" -> [{field: "spec"}, {field: "runStrategy"}] // - "spec.volumes[0].name" -> [{field: "spec"}, {field: "volumes"}, {isArray: true, index: 0}, {field: "name"}] func parsePath(path string) []pathSegment { var segments []pathSegment current := "" inBracket := false indexStr := "" for i := 0; i < len(path); i++ { ch := path[i] switch ch { case '.': if inBracket { indexStr += string(ch) } else if current != "" { segments = append(segments, pathSegment{field: current}) current = "" } case '[': if current != "" { segments = append(segments, pathSegment{field: current}) current = "" } inBracket = true indexStr = "" case ']': if inBracket { // Parse the index var idx int if _, err := fmt.Sscanf(indexStr, "%d", &idx); err != nil { // If parsing fails, use -1 as invalid index idx = -1 } segments = append(segments, pathSegment{isArray: true, index: idx}) inBracket = false indexStr = "" } default: if inBracket { indexStr += string(ch) } else { current += string(ch) } } } if current != "" { segments = append(segments, pathSegment{field: current}) } return segments }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/containers/kubernetes-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server