Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,37 +25,37 @@ import (
googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract"
)

var inputKindNameAliasMap gcpqueryutil.SetFilterAliasToItemsMap = map[string][]string{
"default": strings.Split("pods replicasets daemonsets nodes deployments namespaces statefulsets services servicenetworkendpointgroups ingresses poddisruptionbudgets jobs cronjobs endpointslices persistentvolumes persistentvolumeclaims storageclasses horizontalpodautoscalers verticalpodautoscalers multidimpodautoscalers", " "),
var inputKindsAliasMap gcpqueryutil.SetFilterAliasToItemsMap = map[string][]string{
"legacy_default": strings.Split("pods replicasets daemonsets nodes deployments namespaces statefulsets services servicenetworkendpointgroups ingresses poddisruptionbudgets jobs cronjobs endpointslices persistentvolumes persistentvolumeclaims storageclasses horizontalpodautoscalers verticalpodautoscalers multidimpodautoscalers", " "),
}

// InputKindFilterTask is a form task for inputting the kind filter.
var InputKindFilterTask = formtask.NewSetFormTaskBuilder(googlecloudk8scommon_contract.InputKindFilterTaskID, googlecloudcommon_contract.PriorityForK8sResourceFilterGroup+5000, "Kind").
WithDefaultValueConstant([]string{"@default"}, true).
WithDescription("The kinds of resources to gather logs. `@default` is a alias of set of kinds that frequently queried. Specify `@any` to query every kinds of resources").
WithDefaultValueConstant([]string{"@any", "-leases"}, true).
WithDescription("The kinds of resources to gather logs. Specify `@any` to query all kinds of resources, or prefix with `-` to exclude specific kinds (e.g., `-leases`). `@legacy_default` matches a set of kinds frequently queried in legacy KHI versions.").
WithAllowAddAll(false).
WithAllowRemoveAll(false).
WithAllowCustomValue(true).
WithOptionsFunc(func(ctx context.Context, previousValues []string) ([]inspectionmetadata.SetParameterFormFieldOptionItem, error) {
result := []inspectionmetadata.SetParameterFormFieldOptionItem{}
result = append(result, inspectionmetadata.SetParameterFormFieldOptionItem{ID: "@any", Description: "[Alias] An alias matches any of the kinds"})
result = append(result, inspectionmetadata.SetParameterFormFieldOptionItem{ID: "@default", Description: "[Alias] An alias matches a set of kinds that frequently queried."})
return result, nil
return []inspectionmetadata.SetParameterFormFieldOptionItem{
{ID: "@any", Description: "[Alias] An alias matches any of the kinds"},
{ID: "@legacy_default", Description: "[Alias] An alias matches a set of kinds frequently queried in legacy KHI versions."},
}, nil
}).
WithValidator(func(ctx context.Context, value []string) (string, error) {
if len(value) == 0 {
return "kind filter can't be empty", nil
}
filterInStr := strings.Join(value, " ")
result, err := gcpqueryutil.ParseSetFilter(filterInStr, inputKindNameAliasMap, true, true, true)
result, err := gcpqueryutil.ParseSetFilter(filterInStr, inputKindsAliasMap, true, true, true)
if err != nil {
return "", err
}
return result.ValidationError, nil
}).
WithConverter(func(ctx context.Context, value []string) (*gcpqueryutil.SetFilterParseResult, error) {
filterInStr := strings.Join(value, " ")
result, err := gcpqueryutil.ParseSetFilter(filterInStr, inputKindNameAliasMap, true, true, true)
result, err := gcpqueryutil.ParseSetFilter(filterInStr, inputKindsAliasMap, true, true, true)
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2026 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 googlecloudk8scommon_impl

import (
"context"
"slices"
"strings"
"testing"

"github.com/GoogleCloudPlatform/khi/pkg/common/typedmap"
"github.com/GoogleCloudPlatform/khi/pkg/core/inspection/gcpqueryutil"
inspectionmetadata "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/metadata"
inspectiontest "github.com/GoogleCloudPlatform/khi/pkg/core/inspection/test"
googlecloudk8scommon_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/googlecloudk8scommon/contract"
inspectioncore_contract "github.com/GoogleCloudPlatform/khi/pkg/task/inspection/inspectioncore/contract"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
)

var expectedLegacyDefaultKinds = func() []string {
kinds := strings.Split("pods replicasets daemonsets nodes deployments namespaces statefulsets services servicenetworkendpointgroups ingresses poddisruptionbudgets jobs cronjobs endpointslices persistentvolumes persistentvolumeclaims storageclasses horizontalpodautoscalers verticalpodautoscalers multidimpodautoscalers", " ")
slices.Sort(kinds)
return kinds
}()

func TestInputKindFilterTask_Metadata(t *testing.T) {
ctx := inspectiontest.WithDefaultTestInspectionTaskContext(context.Background())
_, metadata, err := inspectiontest.RunInspectionTask(ctx, InputKindFilterTask, inspectioncore_contract.TaskModeDryRun, nil)
if err != nil {
t.Fatalf("unexpected error on DryRun mode: %v", err)
}

fields, found := typedmap.Get(metadata, inspectionmetadata.FormFieldSetMetadataKey)
if !found {
t.Fatal("FormFieldSet not found on metadata")
}

rawField := fields.DangerouslyGetField(googlecloudk8scommon_contract.InputKindFilterTaskID.ReferenceIDString())
field, ok := rawField.(inspectionmetadata.SetParameterFormField)
if !ok {
t.Fatalf("expected SetParameterFormField, got %T", rawField)
}

wantDefault := []string{"@any", "-leases"}
if diff := cmp.Diff(wantDefault, field.Default); diff != "" {
t.Errorf("default value mismatch (-want +got):\n%s", diff)
}

wantOptions := []inspectionmetadata.SetParameterFormFieldOptionItem{
{ID: "@any", Description: "[Alias] An alias matches any of the kinds"},
{ID: "@legacy_default", Description: "[Alias] An alias matches a set of kinds frequently queried in legacy KHI versions."},
}
if diff := cmp.Diff(wantOptions, field.Options); diff != "" {
t.Errorf("options mismatch (-want +got):\n%s", diff)
}
}

func TestInputKindFilterTask_Run(t *testing.T) {
testCases := []struct {
name string
inputValue any
wantResult *gcpqueryutil.SetFilterParseResult
wantErrSub string
}{
{
name: "default value used when input is nil",
inputValue: nil,
wantResult: &gcpqueryutil.SetFilterParseResult{
SubtractMode: true,
Subtractives: []string{"leases"},
Additives: []string{},
},
},
{
name: "explicit default @any -leases",
inputValue: []any{"@any", "-leases"},
wantResult: &gcpqueryutil.SetFilterParseResult{
SubtractMode: true,
Subtractives: []string{"leases"},
Additives: []string{},
},
},
{
name: "legacy_default alias expands to legacy kinds",
inputValue: []any{"@legacy_default"},
wantResult: &gcpqueryutil.SetFilterParseResult{
SubtractMode: false,
Subtractives: []string{},
Additives: expectedLegacyDefaultKinds,
},
},
{
name: "legacy_default with subtractive element",
inputValue: []any{"@legacy_default", "-pods"},
wantResult: func() *gcpqueryutil.SetFilterParseResult {
withoutPods := make([]string, 0, len(expectedLegacyDefaultKinds)-1)
for _, k := range expectedLegacyDefaultKinds {
if k != "pods" {
withoutPods = append(withoutPods, k)
}
}
return &gcpqueryutil.SetFilterParseResult{
SubtractMode: false,
Subtractives: []string{},
Additives: withoutPods,
}
}(),
},
{
name: "@any with multiple subtractive kinds",
inputValue: []any{"@any", "-leases", "-configmaps"},
wantResult: &gcpqueryutil.SetFilterParseResult{
SubtractMode: true,
Subtractives: []string{"configmaps", "leases"},
Additives: []string{},
},
},
{
name: "custom kinds",
inputValue: []any{"pods", "services"},
wantResult: &gcpqueryutil.SetFilterParseResult{
SubtractMode: false,
Subtractives: []string{},
Additives: []string{"pods", "services"},
},
},
{
name: "empty filter produces validation error",
inputValue: []any{},
wantErrSub: "kind filter can't be empty",
},
{
name: "old @default alias is rejected",
inputValue: []any{"@default"},
wantErrSub: "alias `default` was not found",
},
{
name: "invalid character produces validation error",
inputValue: []any{"invalid$$$"},
wantErrSub: "filter value must be whitespace split series",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := inspectiontest.WithDefaultTestInspectionTaskContext(context.Background())
inputMap := map[string]any{}
if tc.inputValue != nil {
inputMap[googlecloudk8scommon_contract.InputKindFilterTaskID.ReferenceIDString()] = tc.inputValue
}

result, _, err := inspectiontest.RunInspectionTask(ctx, InputKindFilterTask, inspectioncore_contract.TaskModeRun, inputMap)
if tc.wantErrSub != "" {
if err == nil {
t.Fatalf("expected error containing %q, got nil", tc.wantErrSub)
}
if !strings.Contains(err.Error(), tc.wantErrSub) {
t.Fatalf("expected error containing %q, got %q", tc.wantErrSub, err.Error())
}
return
}

if err != nil {
t.Fatalf("unexpected error: %v", err)
}

if diff := cmp.Diff(tc.wantResult, result, cmpopts.EquateEmpty()); diff != "" {
t.Errorf("RunInspectionTask() mismatch (-want +got):\n%s", diff)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func getAuditLogJobTestConfig() *taskrecord.JobTestConfig {
"cloud.google.com/common/input-query-resource-names/cloud.google.com/log/k8s-audit/audit-list-log-entries": "projects/khi-testing-with-auditlog",
"cloud.google.com/k8s/input-cluster-name": "p0-gke-basic-1",
"cloud.google.com/k8s/input-kinds": []any{
"@default",
"@legacy_default",
},
"cloud.google.com/k8s/input-namespaces": []any{
"@all_cluster_scoped",
Expand Down
Loading