mirror of
https://github.com/rancher/steve.git
synced 2025-09-12 13:31:57 +00:00
* added timestamp convertion to metadata.fields * fixed duration parsing * fixed tests * removed tags file * added comments * added better error handling * changed ParseHumanDuration to use Fscanf * added builtins handling * adding mock updates * fixing tests * another try * added timestamp convertion to metadata.fields * addressing comments from @ericpromislow * converting error to warning * added template options
This commit is contained in:
@@ -2,12 +2,17 @@ package common
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rancher/apiserver/pkg/types"
|
||||
"github.com/rancher/steve/pkg/accesscontrol"
|
||||
"github.com/rancher/steve/pkg/attributes"
|
||||
"github.com/rancher/steve/pkg/resources/virtual/common"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/rancher/steve/pkg/schema"
|
||||
metricsStore "github.com/rancher/steve/pkg/stores/metrics"
|
||||
"github.com/rancher/steve/pkg/stores/proxy"
|
||||
@@ -19,25 +24,32 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
schema2 "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
)
|
||||
|
||||
type TemplateOptions struct {
|
||||
InSQLMode bool
|
||||
}
|
||||
|
||||
func DefaultTemplate(clientGetter proxy.ClientGetter,
|
||||
summaryCache *summarycache.SummaryCache,
|
||||
asl accesscontrol.AccessSetLookup,
|
||||
namespaceCache corecontrollers.NamespaceCache) schema.Template {
|
||||
namespaceCache corecontrollers.NamespaceCache,
|
||||
options TemplateOptions) schema.Template {
|
||||
return schema.Template{
|
||||
Store: metricsStore.NewMetricsStore(proxy.NewProxyStore(clientGetter, summaryCache, asl, namespaceCache)),
|
||||
Formatter: formatter(summaryCache, asl),
|
||||
Formatter: formatter(summaryCache, asl, options),
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultTemplateForStore provides a default schema template which uses a provided, pre-initialized store. Primarily used when creating a Template that uses a Lasso SQL store internally.
|
||||
func DefaultTemplateForStore(store types.Store,
|
||||
summaryCache *summarycache.SummaryCache,
|
||||
asl accesscontrol.AccessSetLookup) schema.Template {
|
||||
asl accesscontrol.AccessSetLookup,
|
||||
options TemplateOptions) schema.Template {
|
||||
return schema.Template{
|
||||
Store: store,
|
||||
Formatter: formatter(summaryCache, asl),
|
||||
Formatter: formatter(summaryCache, asl, options),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +95,7 @@ func buildBasePath(gvr schema2.GroupVersionResource, namespace string, includeNa
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func formatter(summarycache common.SummaryCache, asl accesscontrol.AccessSetLookup) types.Formatter {
|
||||
func formatter(summarycache common.SummaryCache, asl accesscontrol.AccessSetLookup, options TemplateOptions) types.Formatter {
|
||||
return func(request *types.APIRequest, resource *types.RawResource) {
|
||||
if resource.Schema == nil {
|
||||
return
|
||||
@@ -140,6 +152,7 @@ func formatter(summarycache common.SummaryCache, asl accesscontrol.AccessSetLook
|
||||
delete(resource.Links, "patch")
|
||||
}
|
||||
|
||||
gvk := attributes.GVK(resource.Schema)
|
||||
if unstr, ok := resource.APIObject.Object.(*unstructured.Unstructured); ok {
|
||||
// with the sql cache, these were already added by the indexer. However, the sql cache
|
||||
// is only used for lists, so we need to re-add here for get/watch
|
||||
@@ -157,6 +170,10 @@ func formatter(summarycache common.SummaryCache, asl accesscontrol.AccessSetLook
|
||||
includeFields(request, unstr)
|
||||
excludeFields(request, unstr)
|
||||
excludeValues(request, unstr)
|
||||
|
||||
if options.InSQLMode {
|
||||
convertMetadataTimestampFields(request, gvk, unstr)
|
||||
}
|
||||
}
|
||||
|
||||
if permsQuery := request.Query.Get("checkPermissions"); permsQuery != "" {
|
||||
@@ -212,6 +229,56 @@ func excludeFields(request *types.APIRequest, unstr *unstructured.Unstructured)
|
||||
}
|
||||
}
|
||||
|
||||
// convertMetadataTimestampFields updates metadata timestamp fields to ensure they remain fresh and human-readable when sent back
|
||||
// to the client. Internally, fields are stored as Unix timestamps; on each request, we calculate the elapsed time since
|
||||
// those timestamps by subtracting them from time.Now(), then format the resulting duration into a human-friendly string.
|
||||
// This prevents cached durations (e.g. “2d” - 2 days) from becoming stale over time.
|
||||
func convertMetadataTimestampFields(request *types.APIRequest, gvk schema2.GroupVersionKind, unstr *unstructured.Unstructured) {
|
||||
if request.Schema != nil {
|
||||
cols := GetColumnDefinitions(request.Schema)
|
||||
for _, col := range cols {
|
||||
gvkDateFields, gvkFound := DateFieldsByGVKBuiltins[gvk]
|
||||
if col.Type == "date" || (gvkFound && slices.Contains(gvkDateFields, col.Name)) {
|
||||
index := GetIndexValueFromString(col.Field)
|
||||
if index == -1 {
|
||||
logrus.Errorf("field index not found at column.Field struct variable: %s", col.Field)
|
||||
return
|
||||
}
|
||||
|
||||
curValue, got, err := unstructured.NestedSlice(unstr.Object, "metadata", "fields")
|
||||
if err != nil {
|
||||
logrus.Errorf("failed to get metadata.fields slice from unstr.Object: %s", err.Error())
|
||||
}
|
||||
|
||||
if !got {
|
||||
logrus.Debugf("couldn't find metadata.fields at unstr.Object")
|
||||
return
|
||||
}
|
||||
|
||||
timeValue, ok := curValue[index].(string)
|
||||
if !ok {
|
||||
logrus.Debugf("time field isn't a string")
|
||||
return
|
||||
}
|
||||
millis, err := strconv.ParseInt(timeValue, 10, 64)
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to convert timestamp value: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := time.Unix(0, millis*int64(time.Millisecond))
|
||||
dur := time.Since(timestamp)
|
||||
|
||||
curValue[index] = duration.HumanDuration(dur)
|
||||
if err := unstructured.SetNestedSlice(unstr.Object, curValue, "metadata", "fields"); err != nil {
|
||||
logrus.Errorf("failed to set value back to metadata.fields slice: %s", err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func excludeValues(request *types.APIRequest, unstr *unstructured.Unstructured) {
|
||||
if values, ok := request.Query["excludeValues"]; ok {
|
||||
for _, f := range values {
|
||||
|
Reference in New Issue
Block a user