operatortrace-go/pkg/client/conditions.go (198 lines of code) (raw):

// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. // pkg/client/conditions.go package client import ( "fmt" "reflect" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/apiutil" ) // getConditionTime retrieves the time for a specific condition type from a Kubernetes object. func getConditionTime(conditionType string, obj client.Object, scheme *runtime.Scheme) (metav1.Time, error) { conditions, err := getConditionsAsMap(obj, scheme) if err != nil { return metav1.Time{}, err } for _, condition := range conditions { // Check if "Type" key exists conType, exists := condition["Type"] if !exists { return metav1.Time{}, fmt.Errorf("condition does not contain a 'Type' field") } // Convert conType to string using reflection conTypeStr, err := convertToString(conType) if err != nil { return metav1.Time{}, fmt.Errorf("failed to convert 'Type' field to string: %v", err) } if conTypeStr == conditionType { time := condition["LastTransitionTime"].(metav1.Time) return time, nil } } return metav1.Time{}, fmt.Errorf("condition of type %s not found", conditionType) } // getConditionMessage retrieves the message for a specific condition type from a Kubernetes object. func getConditionMessage(conditionType string, obj client.Object, scheme *runtime.Scheme) (string, error) { conditions, err := getConditionsAsMap(obj, scheme) if err != nil { return "", err } for _, condition := range conditions { // Check if "Type" key exists conType, exists := condition["Type"] if !exists { return "", fmt.Errorf("condition does not contain a 'Type' field") } // Convert conType to string using reflection conTypeStr, err := convertToString(conType) if err != nil { return "", fmt.Errorf("failed to convert 'Type' field to string: %v", err) } if conTypeStr == conditionType { message := condition["Message"].(string) return message, nil } } return "", fmt.Errorf("condition of type %s not found", conditionType) } // setConditionMessage sets the message for a specific condition type in a Kubernetes object. func setConditionMessage(conditionType, message string, obj client.Object, scheme *runtime.Scheme) error { deleteConditionAsMap(conditionType, obj, scheme) conditions, err := getConditionsAsMap(obj, scheme) if err != nil { return err } newCondition := map[string]interface{}{ "Type": conditionType, "Status": metav1.ConditionUnknown, "LastTransitionTime": metav1.Now(), "Message": message, } conditions = append(conditions, newCondition) return setConditionsFromMap(obj, conditions, scheme) } func deleteConditionAsMap(conditionType string, obj client.Object, scheme *runtime.Scheme) error { // Retrieve the current conditions as a map conditions, err := getConditionsAsMap(obj, scheme) if err != nil { return err } var outConditions []map[string]interface{} for _, condition := range conditions { // Check if "Type" key exists conType, exists := condition["Type"] if !exists { return fmt.Errorf("condition does not contain a 'Type' field") } // Convert conType to string using reflection conTypeStr, err := convertToString(conType) if err != nil { return fmt.Errorf("failed to convert 'Type' field to string: %v", err) } if conTypeStr != conditionType { outConditions = append(outConditions, condition) } } // Set the updated conditions back to the object return setConditionsFromMap(obj, outConditions, scheme) } func getConditionsAsMap(obj client.Object, scheme *runtime.Scheme) ([]map[string]interface{}, error) { gvk, err := apiutil.GVKForObject(obj, scheme) if err != nil { return nil, fmt.Errorf("problem getting the GVK: %w", err) } objTyped, err := scheme.New(gvk) if err != nil { return nil, fmt.Errorf("problem creating new object of kind %s: %w", gvk.Kind, err) } if err := scheme.Convert(obj, objTyped, nil); err != nil { return nil, fmt.Errorf("problem converting object to kind %s: %w", gvk.Kind, err) } val := reflect.ValueOf(objTyped) statusField := val.Elem().FieldByName("Status") if !statusField.IsValid() { return nil, fmt.Errorf("status field not found in kind %s", gvk.Kind) } conditionsField := statusField.FieldByName("Conditions") if !conditionsField.IsValid() { return nil, fmt.Errorf("conditions field not found in kind %s", gvk.Kind) } conditionsValue := conditionsField.Interface() val = reflect.ValueOf(conditionsValue) if val.Kind() != reflect.Slice { return nil, fmt.Errorf("conditions field is not a slice") } var conditionsAsMap []map[string]interface{} for i := 0; i < val.Len(); i++ { conditionVal := val.Index(i) if conditionVal.Kind() == reflect.Ptr { conditionVal = conditionVal.Elem() } conditionMap := make(map[string]interface{}) for _, field := range reflect.VisibleFields(conditionVal.Type()) { fieldValue := conditionVal.FieldByIndex(field.Index) conditionMap[field.Name] = fieldValue.Interface() } conditionsAsMap = append(conditionsAsMap, conditionMap) } return conditionsAsMap, nil } func setConditionsFromMap(obj client.Object, conditionsAsMap []map[string]interface{}, scheme *runtime.Scheme) error { gvk, err := apiutil.GVKForObject(obj, scheme) if err != nil { return fmt.Errorf("problem getting the GVK: %w", err) } objTyped, err := scheme.New(gvk) if err != nil { return fmt.Errorf("problem creating new object of kind %s: %w", gvk.Kind, err) } if err := scheme.Convert(obj, objTyped, nil); err != nil { return fmt.Errorf("problem converting object to kind %s: %w", gvk.Kind, err) } val := reflect.ValueOf(objTyped) statusField := val.Elem().FieldByName("Status") if !statusField.IsValid() { return fmt.Errorf("status field not found in kind %s", gvk.Kind) } conditionsField := statusField.FieldByName("Conditions") if !conditionsField.IsValid() { return fmt.Errorf("conditions field not found in kind %s", gvk.Kind) } elemType := conditionsField.Type().Elem() result := reflect.MakeSlice(conditionsField.Type(), len(conditionsAsMap), len(conditionsAsMap)) for i, conditionMap := range conditionsAsMap { targetCond := reflect.New(elemType).Elem() for key, value := range conditionMap { field := targetCond.FieldByName(key) if field.IsValid() { val := reflect.ValueOf(value) if val.Type().ConvertibleTo(field.Type()) { field.Set(val.Convert(field.Type())) } else { return fmt.Errorf("cannot convert value of field %s from %s to %s", key, val.Type(), field.Type()) } } } if conditionsField.Type().Elem().Kind() == reflect.Ptr { result.Index(i).Set(targetCond.Addr()) } else { result.Index(i).Set(targetCond) } } conditionsField.Set(result) if err := scheme.Convert(objTyped, obj, nil); err != nil { return fmt.Errorf("problem converting object back to unstructured: %w", err) } return nil } func mapToStruct(structVal reflect.Value, data map[string]interface{}) error { for key, value := range data { field := structVal.FieldByName(key) if field.IsValid() { switch field.Kind() { case reflect.String: field.SetString(value.(string)) case reflect.Bool: field.SetBool(value.(bool)) case reflect.Int32: field.SetInt(int64(value.(int32))) case reflect.Int64: field.SetInt(value.(int64)) case reflect.Float64: field.SetFloat(value.(float64)) default: field.Set(reflect.ValueOf(value)) } } } return nil }