1
0
mirror of https://github.com/rancher/steve.git synced 2025-07-10 05:13:48 +00:00
steve/pkg/resources/virtual/virtual_test.go
Eric Promislow d794bfe4e8
Index arbitrary labels (#317)
* Add more fields to index when sql-caching is on.

* Restore the gvkKey helper, add event fields.

The UI team wasn't sure whether the event fields should go in the empty-string group or in 'events.k8s.io',
so let's go with both until/unless specified otherwise.

* More fixes to the fields to index:

- Remove the erroneously added management.cattle.io.nodes fields
- Use the builtin Event class, not events.k8s.io (by looking at the dashboard client code)

* Start on the virtual-field work.

* Map `Event.type` to `Event._type` for indexing.

* Add a unit test for field replacement for Event.type

* Add label processing.

* Don't test for transformation of event objects in the common module.

* Parse metadata.label queries differently.

* Improve a variable name that turned out to not be temporary.

* No need to specifically cache certain labels, as all are now cached.

* Add a test to verify simple label (m.labels.foo=blah) queries work.

* 'addLabelFields' never returns an error.

* Delete superseded function.

* Was calling 'addLabelFields' one time too many.

* Start using k8s ParseToRequirements

* Pull in the k8s parser.

* Successfully test for quotation marks.

* Add quoted strings to the lexer.

* Move to a forked k8s label lexer to include non-label tests.

* Improve and test the way quoted strings in the query are detected.

* Reinstate the original Apache license in the derived code.

Following clause 4.3 of the Apache license:
"You must cause any modified files to carry prominent notices stating that You changed the files..."

* Ignore case for operators.

* Test IN multiple-target-values

* Test the not-in operator.

* Ignore case for operators.

SQL is case-insensitive on field names and values, so this just adds consistency.

* Added tests for parsing EXISTS and NOT-EXISTS queries.

* Parse less-than and greater-than ops

* Lasso's `CacheFor` now takes a `watchable` argument.

* Support 'gt' and 'lt' as synonyms for '<' and '>'.

I see both types of operators being bandied about -- it's easy to support the aliases.

* typo fix

* Have the filter parser allow exist tests only on labels.

Also reduce the case where there's no namespace function.

* Specify hard-wired fields to index alphabetically.

* Remove unused variable.

* Parser: 'metadata.labels[FIELD]' is valid

* Pull in new gvk fields from main (and keep in alpha order).

* Fixed a couple of drops done during the last rebase.

* Add a reminder to keep the entries in alpha order.

* Test TransformLabels

* Remove TransformLabels

* Remove unused/unneeded code.

* Describe diffs between our label-selector parser and upstream's.

* Use the merged lasso 46333 work.

* Drop unused field.

* Tighten up the code.

* Specify which commit the label selector parser is based on.

* Allow both single-quoted and double-quoted value matching, doc difference.

* More review-driven changes:

- Stricter processing of m.l.name keys:
  Require ending close-bracket for a start-bracket

- Comment fix

- Moving sql processing from lasso to steve: some changes missed in rebase

* Drop support for double-quotes for string values.

For now on only single-quotes (or none where possible) are allowed.

* Renaming and dropping an init block.

* Quoted strings are dropped from the filter queries

In particular, label values have a specific syntax: they must
start and end with a letter, and their innards may contain
only alnums '.', '-' and '_'.  So there's no need for quoting.

And that means now that `=` and `==` do exact matches,
and the `~` operator does a partial match.

`!=` and `!~` negate -- note that `!~` is a stricter operation than `!=`,
in that given a set of possible string values, `!=` will accept more
of them than `!~`. Maybe I shouldn't have gone here, but these operators
reminded me of learning about `nicht durfen` and `nicht sollen` in German,
or something like that.

* Move a constant definition to the module level.

* Remove commented-out code.

* Remove unused func and adjacent redundant comment.
2025-01-30 11:57:23 -08:00

375 lines
10 KiB
Go

package virtual_test
import (
"fmt"
"strings"
"testing"
"github.com/rancher/steve/pkg/resources/virtual"
"github.com/rancher/steve/pkg/resources/virtual/common"
"github.com/rancher/steve/pkg/summarycache"
"github.com/rancher/wrangler/v3/pkg/summary"
"github.com/stretchr/testify/require"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)
func TestTransformChain(t *testing.T) {
tests := []struct {
name string
input any
hasSummary *summary.SummarizedObject
hasRelationships []summarycache.Relationship
wantOutput any
wantError bool
}{
{
name: "add summary + relationships + reserved fields",
hasSummary: &summary.SummarizedObject{
PartialObjectMetadata: v1.PartialObjectMetadata{
ObjectMeta: v1.ObjectMeta{
Name: "testobj",
Namespace: "test-ns",
},
TypeMeta: v1.TypeMeta{
APIVersion: "test.cattle.io/v1",
Kind: "TestResource",
},
},
Summary: summary.Summary{
State: "success",
Transitioning: false,
Error: false,
Message: []string{"resource 1 rolled out", "resource 2 rolled out"},
},
},
hasRelationships: []summarycache.Relationship{
{
ToID: "1345",
ToType: "SomeType",
ToNamespace: "some-ns",
FromID: "78901",
FromType: "TestResource",
Rel: "uses",
},
},
input: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test.cattle.io/v1",
"kind": "TestResource",
"metadata": map[string]interface{}{
"name": "testobj",
"namespace": "test-ns",
},
"id": "old-id",
},
},
wantOutput: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "test.cattle.io/v1",
"kind": "TestResource",
"metadata": map[string]interface{}{
"name": "testobj",
"namespace": "test-ns",
"state": map[string]interface{}{
"name": "success",
"error": false,
"transitioning": false,
"message": "resource 1 rolled out:resource 2 rolled out",
},
"relationships": []any{
map[string]any{
"toId": "1345",
"toType": "SomeType",
"toNamespace": "some-ns",
"fromId": "78901",
"fromType": "TestResource",
"rel": "uses",
},
},
},
"id": "test-ns/testobj",
"_id": "old-id",
},
},
},
{
name: "processable event",
input: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "/v1",
"kind": "Event",
"metadata": map[string]interface{}{
"name": "oswaldsFarm",
"namespace": "oswaldsNamespace",
},
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{
"status": "False",
"reason": "Error",
"message": "some error",
"lastTransitionTime": "2024-01-01",
},
},
},
"id": "eventTest2id",
"type": "Gorniplatz",
},
},
wantOutput: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "/v1",
"kind": "Event",
"metadata": map[string]interface{}{
"name": "oswaldsFarm",
"namespace": "oswaldsNamespace",
"relationships": []any(nil),
},
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{
"status": "False",
"reason": "Error",
"transitioning": false,
"error": true,
"message": "some error",
"lastTransitionTime": "2024-01-01",
"lastUpdateTime": "2024-01-01",
},
},
},
"id": "oswaldsNamespace/oswaldsFarm",
"_id": "eventTest2id",
"type": "Gorniplatz",
"_type": "Gorniplatz",
},
},
},
{
name: "don't fix non-default-group event fields",
input: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "palau.io/v1",
"kind": "Event",
"metadata": map[string]interface{}{
"name": "gregsFarm",
"namespace": "gregsNamespace",
"relationships": []any(nil),
},
"id": "eventTest1id",
"type": "Gorniplatz",
},
},
wantOutput: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "palau.io/v1",
"kind": "Event",
"metadata": map[string]interface{}{
"name": "gregsFarm",
"namespace": "gregsNamespace",
"relationships": []any(nil),
},
"id": "gregsNamespace/gregsFarm",
"_id": "eventTest1id",
"type": "Gorniplatz",
},
},
},
{
name: "a non-ready cluster",
input: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "management.cattle.io/v3",
"kind": "Cluster",
"id": 1,
"metadata": map[string]interface{}{
"name": "c-m-boris",
},
"spec": map[string]interface{}{
"displayName": "boris",
"internal": false,
},
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "BackingNamespaceCreated",
},
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "DefaultProjectCreated",
},
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "SystemProjectCreated",
},
},
},
},
},
wantOutput: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "management.cattle.io/v3",
"kind": "Cluster",
"id": "c-m-boris",
"_id": 1,
"metadata": map[string]interface{}{
"name": "c-m-boris",
"relationships": []any(nil),
},
"spec": map[string]interface{}{
"displayName": "boris",
"internal": false,
},
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "BackingNamespaceCreated",
},
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "DefaultProjectCreated",
},
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "SystemProjectCreated",
},
},
"connected": false,
},
},
},
},
{
name: "a ready cluster",
input: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "management.cattle.io/v3",
"kind": "Cluster",
"id": 2,
"metadata": map[string]interface{}{
"name": "c-m-natasha",
},
"spec": map[string]interface{}{
"displayName": "natasha",
"internal": false,
},
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "BackingNamespaceCreated",
},
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "Ready",
},
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "SystemProjectCreated",
},
},
},
},
},
wantOutput: &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "management.cattle.io/v3",
"kind": "Cluster",
"id": "c-m-natasha",
"_id": 2,
"metadata": map[string]interface{}{
"name": "c-m-natasha",
"relationships": []any(nil),
},
"spec": map[string]interface{}{
"displayName": "natasha",
"internal": false,
},
"status": map[string]interface{}{
"conditions": []interface{}{
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "BackingNamespaceCreated",
},
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "Ready",
},
map[string]interface{}{
"error": false,
"lastUpdateTime": "2025-01-10T22:52:16Z",
"status": "True",
"transitioning": false,
"type": "SystemProjectCreated",
},
},
"connected": true,
},
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fakeCache := common.FakeSummaryCache{
SummarizedObject: test.hasSummary,
Relationships: test.hasRelationships,
}
tb := virtual.NewTransformBuilder(&fakeCache)
raw, isSignal, err := common.GetUnstructured(test.input)
require.False(t, isSignal)
require.Nil(t, err)
apiVersion := raw.GetAPIVersion()
parts := strings.Split(apiVersion, "/")
gvk := schema.GroupVersionKind{Group: parts[0], Version: parts[1], Kind: raw.GetKind()}
if test.name == "a non-ready cluster" {
fmt.Printf("Stop here")
}
output, err := tb.GetTransformFunc(gvk)(test.input)
require.Equal(t, test.wantOutput, output)
if test.wantError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}