pkg/log/structure/reader.go (253 lines of code) (raw):

// Copyright 2024 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package structure import ( "fmt" "strings" "time" "github.com/GoogleCloudPlatform/khi/pkg/log/structure/structuredata" "github.com/GoogleCloudPlatform/khi/pkg/log/structure/structuredatastore" "k8s.io/apimachinery/pkg/runtime" ) type Reader struct { dataRef structuredatastore.StructureDataStorageRef } func NewReader(store structuredatastore.StructureDataStorageRef) *Reader { return &Reader{store} } // Reader navigates a hierarchical data structure using the provided path and optional filters. // If the path resolves to multiple elements (typically array elements), an array of // corresponding Readers is returned. Filters allow you to selectively include // elements at each array level encountered during the path navigation. // This function won't work with a path including `.` in the route. Use `ReaderFromArrayRoute` for the case. // // The 'filters' argument accepts a variable number of ReaderFilter arrays. Each array // contains filters corresponding to a specific array level within the path. // // Example Paths: // // "data.items[].name" // Access 'name' in each item within the 'items' // "config" // Access root-level 'config' func (r *Reader) Reader(path string, filters ...[]ReaderFilter) ([]*Reader, error) { if path == "" { return []*Reader{r}, nil } route := strings.Split(path, ".") return r.ReaderFromArrayRoute(route, filters...) } // ChildReader get a Reader from direct children of this node. func (r *Reader) ChildReader(childName string) (*Reader, error) { readers, err := r.ReaderFromArrayRoute([]string{childName}) if err != nil { return nil, err } if len(readers) == 0 { return nil, fmt.Errorf("child %s was not found", childName) } return readers[0], nil } func (r *Reader) ReaderFromArrayRoute(route []string, filters ...[]ReaderFilter) ([]*Reader, error) { current := []*Reader{r} arrayPathIndex := 0 for i := 0; i < len(route); i++ { next := []*Reader{} for _, currentElement := range current { key := route[i] sd, err := currentElement.dataRef.Get() if err != nil { return nil, err } if strings.HasSuffix(key, "[]") { sequenceElementKey := strings.TrimSuffix(key, "[]") sequenceAny, err := sd.Value(sequenceElementKey) if err != nil { continue } if sequence, convertible := sequenceAny.(structuredata.StructureData); convertible { elemType, err := sequence.Type() if err != nil { return nil, err } if elemType != structuredata.StructuredTypeArray { continue } keys, err := sequence.Keys() if err != nil { return nil, err } for _, key := range keys { anyData, err := sequence.Value(key) if err != nil { return nil, err } if child, convertible := anyData.(structuredata.StructureData); convertible { childReader, err := getEphemeralReader(child) if err != nil { return nil, err } if arrayPathIndex < len(filters) { ignore := false for _, filter := range filters[arrayPathIndex] { nonIgnore, err := filter.Filter(childReader) if err != nil { return nil, err } if !nonIgnore { ignore = true break } } if ignore { continue } } next = append(next, childReader) } } } arrayPathIndex += 1 } else { nextAny, err := sd.Value(key) if err != nil { continue } if nextElem, ok := nextAny.(structuredata.StructureData); ok { reader, err := getEphemeralReader(nextElem) if err != nil { return nil, err } next = append(next, reader) } else { return nil, err } } } current = next } return current, nil } // ReaderSingle calls the Reader method with expecting it to return a single reader as the result. func (r *Reader) ReaderSingle(path string) (*Reader, error) { readers, err := r.Reader(path) if err != nil { return nil, err } if len(readers) == 0 { return nil, fmt.Errorf("path `%s` not found", path) } if len(readers) > 1 { return nil, fmt.Errorf("multiple readers are returned for %s", path) } return readers[0], nil } // Has returns true only when fields matching with the path is contained in the data func (r *Reader) Has(path string) bool { readers, err := r.Reader(path) if err != nil { return false } return len(readers) > 0 } // ReadString attempts to read and return a string value at the specified path. // It returns an error if the path is not found, is not a string type, or if more than one reader matches the path. func (r *Reader) ReadString(path string) (string, error) { reader, err := r.ReaderSingle(path) if err != nil { return "", err } return readScalarTyped[string](reader, "") } // ReadTimeAsString attempts to read the path as string or time.Time. func (r *Reader) ReadTimeAsString(path string) (string, error) { reader, err := r.ReaderSingle(path) if err != nil { return "", nil } timeAsString, err := readScalarTyped[string](reader, "") if err == nil { return timeAsString, nil } timeAsTime, err := readScalarTyped[time.Time](reader, time.Time{}) if err == nil { return timeAsTime.Format(time.RFC3339), nil } return "", fmt.Errorf("path %s can't be parsed as string or time.Time", path) } func (r *Reader) ReadInt(path string) (int, error) { reader, err := r.ReaderSingle(path) if err != nil { return 0, err } return readScalarTyped[int](reader, 0) } func (r *Reader) ReadStringOrDefault(path string, defaultValue string) string { val, err := r.ReadString(path) if err != nil { return defaultValue } return val } func (r *Reader) ReadIntOrDefault(path string, defaultValue int) int { val, err := r.ReadInt(path) if err != nil { return defaultValue } return val } func (r *Reader) ReadReflect(path string, target interface{}) error { reader, err := r.ReaderSingle(path) if err != nil { return err } sd, err := reader.dataRef.Get() if err != nil { return err } return structuredata.ReadReflect(sd, target) } func (r *Reader) ReadReflectK8sManifest(path string, target runtime.Object) error { reader, err := r.ReaderSingle(path) if err != nil { return err } sd, err := reader.dataRef.Get() if err != nil { return err } return structuredata.ReadReflectK8sManifest(sd, target) } func (r *Reader) ToYaml(path string) (string, error) { reader, err := r.ReaderSingle(path) if err != nil { return "", err } sd, err := reader.dataRef.Get() if err != nil { return "", err } return structuredata.ToYaml(sd) } func (r *Reader) ToJson(path string) (string, error) { reader, err := r.ReaderSingle(path) if err != nil { return "", err } sd, err := reader.dataRef.Get() if err != nil { return "", err } return structuredata.ToJson(sd) } func (r *Reader) readScalar() (any, error) { sd, err := r.dataRef.Get() if err != nil { return nil, err } strType, err := sd.Type() if err != nil { return nil, err } if strType != structuredata.StructuredTypeScalar { return nil, fmt.Errorf("current data is not a scalar") } return sd.Value("") } func getEphemeralReader(data structuredata.StructureData) (*Reader, error) { store := structuredatastore.OnMemoryStructureDataStore{} ref, err := store.StoreStructureData(data) if err != nil { return nil, err } return &Reader{ dataRef: ref, }, nil } func readScalarTyped[T any](r *Reader, defaultValue T) (T, error) { raw, err := r.readScalar() if err != nil { return defaultValue, err } if converted, convertible := raw.(T); convertible { return converted, nil } return defaultValue, fmt.Errorf("the given value %v couldn't be converted to %T", raw, *new(T)) }