mirror of
https://github.com/kubeshark/kubeshark.git
synced 2026-09-01 00:57:17 +00:00
values.yaml is generated by serialising CreateDefaultConfig, and that literal sets the Auth fields explicitly instead of leaving them to the struct tags. The tag said kubeshark-admin while the literal said kubeshark-viewer, so the chart's committed default only held until the next generate-helm-values, which would have rewritten every ungated install from admin to read-only and 403ed deployments that never configured authorization. Regenerating now reproduces the committed values.yaml byte for byte, which is what says the two sources agree rather than merely happening to look alike. The test compares the literal against the tag instead of asserting a role name, so it keeps holding if the default is deliberately changed and only fails when the two drift apart. Also documents that scripting.enabled closes the scripting API rather than only hiding the UI, now that it is the switch demo mode used to be.
416 lines
17 KiB
Go
416 lines
17 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
type ConfigMock struct {
|
|
SectionMock SectionMock `yaml:"section"`
|
|
Test string `yaml:"test"`
|
|
StringField string `yaml:"string-field"`
|
|
IntField int `yaml:"int-field"`
|
|
BoolField bool `yaml:"bool-field"`
|
|
UintField uint `yaml:"uint-field"`
|
|
StringSliceField []string `yaml:"string-slice-field"`
|
|
IntSliceField []int `yaml:"int-slice-field"`
|
|
BoolSliceField []bool `yaml:"bool-slice-field"`
|
|
UintSliceField []uint `yaml:"uint-slice-field"`
|
|
}
|
|
|
|
type SectionMock struct {
|
|
Test string `yaml:"test"`
|
|
}
|
|
|
|
type FieldSetValues struct {
|
|
SetValues []string
|
|
FieldName string
|
|
FieldValue interface{}
|
|
}
|
|
|
|
func TestMergeSetFlagNoSeparator(t *testing.T) {
|
|
tests := []struct {
|
|
Name string
|
|
SetValues []string
|
|
}{
|
|
{Name: "empty value", SetValues: []string{""}},
|
|
{Name: "single char", SetValues: []string{"t"}},
|
|
{Name: "combine empty value and single char", SetValues: []string{"", "t"}},
|
|
{Name: "two values without separator", SetValues: []string{"test", "test:true"}},
|
|
{Name: "four values without separator", SetValues: []string{"test", "test:true", "testing!", "true"}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
configMock := ConfigMock{}
|
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
|
|
|
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
|
|
|
if err == nil {
|
|
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
|
return
|
|
}
|
|
|
|
for i := 0; i < configMockElemValue.NumField(); i++ {
|
|
currentField := configMockElemValue.Type().Field(i)
|
|
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
|
|
|
if !currentFieldByName.IsZero() {
|
|
t.Errorf("unexpected value with not default value - SetValues: %v", test.SetValues)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeSetFlagInvalidFlagName(t *testing.T) {
|
|
tests := []struct {
|
|
Name string
|
|
SetValues []string
|
|
}{
|
|
{Name: "invalid flag name", SetValues: []string{"invalid_flag=true"}},
|
|
{Name: "invalid flag name inside section struct", SetValues: []string{"section.invalid_flag=test"}},
|
|
{Name: "flag name is a struct", SetValues: []string{"section=test"}},
|
|
{Name: "empty flag name", SetValues: []string{"=true"}},
|
|
{Name: "four tests combined", SetValues: []string{"invalid_flag=true", "config.invalid_flag=test", "section=test", "=true"}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
configMock := ConfigMock{}
|
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
|
|
|
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
|
|
|
if err == nil {
|
|
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
|
return
|
|
}
|
|
|
|
for i := 0; i < configMockElemValue.NumField(); i++ {
|
|
currentField := configMockElemValue.Type().Field(i)
|
|
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
|
|
|
if !currentFieldByName.IsZero() {
|
|
t.Errorf("unexpected case - SetValues: %v", test.SetValues)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeSetFlagInvalidFlagValue(t *testing.T) {
|
|
tests := []struct {
|
|
Name string
|
|
SetValues []string
|
|
}{
|
|
{Name: "bool value to int field", SetValues: []string{"int-field=true"}},
|
|
{Name: "int value to bool field", SetValues: []string{"bool-field:5"}},
|
|
{Name: "int value to uint field", SetValues: []string{"uint-field=-1"}},
|
|
{Name: "bool value to int slice field", SetValues: []string{"int-slice-field=true"}},
|
|
{Name: "int value to bool slice field", SetValues: []string{"bool-slice-field=5"}},
|
|
{Name: "int value to uint slice field", SetValues: []string{"uint-slice-field=-1"}},
|
|
{Name: "int slice value to int field", SetValues: []string{"int-field=6", "int-field=66"}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
configMock := ConfigMock{}
|
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
|
|
|
err := mergeSetFlag(configMockElemValue, test.SetValues)
|
|
|
|
if err == nil {
|
|
t.Errorf("unexpected unhandled error - SetValues: %v", test.SetValues)
|
|
return
|
|
}
|
|
|
|
for i := 0; i < configMockElemValue.NumField(); i++ {
|
|
currentField := configMockElemValue.Type().Field(i)
|
|
currentFieldByName := configMockElemValue.FieldByName(currentField.Name)
|
|
|
|
if !currentFieldByName.IsZero() {
|
|
t.Errorf("unexpected case - SetValues: %v", test.SetValues)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeSetFlagNotSliceValues(t *testing.T) {
|
|
tests := []struct {
|
|
Name string
|
|
FieldsSetValues []FieldSetValues
|
|
}{
|
|
{Name: "string field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"}}},
|
|
{Name: "int field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6}}},
|
|
{Name: "bool field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true}}},
|
|
{Name: "uint field", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)}}},
|
|
{Name: "four fields combined", FieldsSetValues: []FieldSetValues{
|
|
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
|
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
|
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
|
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
|
}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
configMock := ConfigMock{}
|
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
|
|
|
var setValues []string
|
|
for _, fieldSetValues := range test.FieldsSetValues {
|
|
setValues = append(setValues, fieldSetValues.SetValues...)
|
|
}
|
|
|
|
err := mergeSetFlag(configMockElemValue, setValues)
|
|
|
|
if err != nil {
|
|
t.Errorf("unexpected error result - err: %v", err)
|
|
return
|
|
}
|
|
|
|
for _, fieldSetValues := range test.FieldsSetValues {
|
|
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
|
if fieldValue != fieldSetValues.FieldValue {
|
|
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeSetFlagSliceValues(t *testing.T) {
|
|
tests := []struct {
|
|
Name string
|
|
FieldsSetValues []FieldSetValues
|
|
}{
|
|
{Name: "string slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}}}},
|
|
{Name: "int slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}}}},
|
|
{Name: "bool slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}}}},
|
|
{Name: "uint slice field single value", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}}}},
|
|
{Name: "four single value fields combined", FieldsSetValues: []FieldSetValues{
|
|
{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}},
|
|
{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}},
|
|
{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}},
|
|
{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}},
|
|
}},
|
|
{Name: "string slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}}}},
|
|
{Name: "int slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}}}},
|
|
{Name: "bool slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}}}},
|
|
{Name: "uint slice field two values", FieldsSetValues: []FieldSetValues{{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}}}},
|
|
{Name: "four two values fields combined", FieldsSetValues: []FieldSetValues{
|
|
{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}},
|
|
{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}},
|
|
{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}},
|
|
{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}},
|
|
}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
configMock := ConfigMock{}
|
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
|
|
|
var setValues []string
|
|
for _, fieldSetValues := range test.FieldsSetValues {
|
|
setValues = append(setValues, fieldSetValues.SetValues...)
|
|
}
|
|
|
|
err := mergeSetFlag(configMockElemValue, setValues)
|
|
|
|
if err != nil {
|
|
t.Errorf("unexpected error result - err: %v", err)
|
|
return
|
|
}
|
|
|
|
for _, fieldSetValues := range test.FieldsSetValues {
|
|
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
|
if !reflect.DeepEqual(fieldValue, fieldSetValues.FieldValue) {
|
|
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMergeSetFlagMixValues(t *testing.T) {
|
|
tests := []struct {
|
|
Name string
|
|
FieldsSetValues []FieldSetValues
|
|
}{
|
|
{Name: "single value all fields", FieldsSetValues: []FieldSetValues{
|
|
{SetValues: []string{"string-slice-field=test"}, FieldName: "StringSliceField", FieldValue: []string{"test"}},
|
|
{SetValues: []string{"int-slice-field=6"}, FieldName: "IntSliceField", FieldValue: []int{6}},
|
|
{SetValues: []string{"bool-slice-field=true"}, FieldName: "BoolSliceField", FieldValue: []bool{true}},
|
|
{SetValues: []string{"uint-slice-field=6"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6)}},
|
|
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
|
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
|
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
|
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
|
}},
|
|
{Name: "two values slice fields and single value fields", FieldsSetValues: []FieldSetValues{
|
|
{SetValues: []string{"string-slice-field=test", "string-slice-field=test2"}, FieldName: "StringSliceField", FieldValue: []string{"test", "test2"}},
|
|
{SetValues: []string{"int-slice-field=6", "int-slice-field=66"}, FieldName: "IntSliceField", FieldValue: []int{6, 66}},
|
|
{SetValues: []string{"bool-slice-field=true", "bool-slice-field=false"}, FieldName: "BoolSliceField", FieldValue: []bool{true, false}},
|
|
{SetValues: []string{"uint-slice-field=6", "uint-slice-field=66"}, FieldName: "UintSliceField", FieldValue: []uint{uint(6), uint(66)}},
|
|
{SetValues: []string{"string-field=test"}, FieldName: "StringField", FieldValue: "test"},
|
|
{SetValues: []string{"int-field=6"}, FieldName: "IntField", FieldValue: 6},
|
|
{SetValues: []string{"bool-field=true"}, FieldName: "BoolField", FieldValue: true},
|
|
{SetValues: []string{"uint-field=6"}, FieldName: "UintField", FieldValue: uint(6)},
|
|
}},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.Name, func(t *testing.T) {
|
|
configMock := ConfigMock{}
|
|
configMockElemValue := reflect.ValueOf(&configMock).Elem()
|
|
|
|
var setValues []string
|
|
for _, fieldSetValues := range test.FieldsSetValues {
|
|
setValues = append(setValues, fieldSetValues.SetValues...)
|
|
}
|
|
|
|
err := mergeSetFlag(configMockElemValue, setValues)
|
|
|
|
if err != nil {
|
|
t.Errorf("unexpected error result - err: %v", err)
|
|
return
|
|
}
|
|
|
|
for _, fieldSetValues := range test.FieldsSetValues {
|
|
fieldValue := configMockElemValue.FieldByName(fieldSetValues.FieldName).Interface()
|
|
if !reflect.DeepEqual(fieldValue, fieldSetValues.FieldValue) {
|
|
t.Errorf("unexpected result - expected: %v, actual: %v", fieldSetValues.FieldValue, fieldValue)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetParsedValueValidValue(t *testing.T) {
|
|
tests := []struct {
|
|
StringValue string
|
|
Kind reflect.Kind
|
|
ActualValue interface{}
|
|
}{
|
|
{StringValue: "test", Kind: reflect.String, ActualValue: "test"},
|
|
{StringValue: "123", Kind: reflect.String, ActualValue: "123"},
|
|
{StringValue: "true", Kind: reflect.Bool, ActualValue: true},
|
|
{StringValue: "false", Kind: reflect.Bool, ActualValue: false},
|
|
{StringValue: "6", Kind: reflect.Int, ActualValue: 6},
|
|
{StringValue: "-6", Kind: reflect.Int, ActualValue: -6},
|
|
{StringValue: "6", Kind: reflect.Int8, ActualValue: int8(6)},
|
|
{StringValue: "-6", Kind: reflect.Int8, ActualValue: int8(-6)},
|
|
{StringValue: "6", Kind: reflect.Int16, ActualValue: int16(6)},
|
|
{StringValue: "-6", Kind: reflect.Int16, ActualValue: int16(-6)},
|
|
{StringValue: "6", Kind: reflect.Int32, ActualValue: int32(6)},
|
|
{StringValue: "-6", Kind: reflect.Int32, ActualValue: int32(-6)},
|
|
{StringValue: "6", Kind: reflect.Int64, ActualValue: int64(6)},
|
|
{StringValue: "-6", Kind: reflect.Int64, ActualValue: int64(-6)},
|
|
{StringValue: "6", Kind: reflect.Uint, ActualValue: uint(6)},
|
|
{StringValue: "66", Kind: reflect.Uint, ActualValue: uint(66)},
|
|
{StringValue: "6", Kind: reflect.Uint8, ActualValue: uint8(6)},
|
|
{StringValue: "66", Kind: reflect.Uint8, ActualValue: uint8(66)},
|
|
{StringValue: "6", Kind: reflect.Uint16, ActualValue: uint16(6)},
|
|
{StringValue: "66", Kind: reflect.Uint16, ActualValue: uint16(66)},
|
|
{StringValue: "6", Kind: reflect.Uint32, ActualValue: uint32(6)},
|
|
{StringValue: "66", Kind: reflect.Uint32, ActualValue: uint32(66)},
|
|
{StringValue: "6", Kind: reflect.Uint64, ActualValue: uint64(6)},
|
|
{StringValue: "66", Kind: reflect.Uint64, ActualValue: uint64(66)},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v %v", test.Kind, test.StringValue), func(t *testing.T) {
|
|
parsedValue, err := getParsedValue(test.Kind, test.StringValue)
|
|
|
|
if err != nil {
|
|
t.Errorf("unexpected error result - err: %v", err)
|
|
return
|
|
}
|
|
|
|
if parsedValue.Interface() != test.ActualValue {
|
|
t.Errorf("unexpected result - expected: %v, actual: %v", test.ActualValue, parsedValue)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetParsedValueInvalidValue(t *testing.T) {
|
|
tests := []struct {
|
|
StringValue string
|
|
Kind reflect.Kind
|
|
}{
|
|
{StringValue: "test", Kind: reflect.Bool},
|
|
{StringValue: "123", Kind: reflect.Bool},
|
|
{StringValue: "test", Kind: reflect.Int},
|
|
{StringValue: "true", Kind: reflect.Int},
|
|
{StringValue: "test", Kind: reflect.Int8},
|
|
{StringValue: "true", Kind: reflect.Int8},
|
|
{StringValue: "test", Kind: reflect.Int16},
|
|
{StringValue: "true", Kind: reflect.Int16},
|
|
{StringValue: "test", Kind: reflect.Int32},
|
|
{StringValue: "true", Kind: reflect.Int32},
|
|
{StringValue: "test", Kind: reflect.Int64},
|
|
{StringValue: "true", Kind: reflect.Int64},
|
|
{StringValue: "test", Kind: reflect.Uint},
|
|
{StringValue: "-6", Kind: reflect.Uint},
|
|
{StringValue: "test", Kind: reflect.Uint8},
|
|
{StringValue: "-6", Kind: reflect.Uint8},
|
|
{StringValue: "test", Kind: reflect.Uint16},
|
|
{StringValue: "-6", Kind: reflect.Uint16},
|
|
{StringValue: "test", Kind: reflect.Uint32},
|
|
{StringValue: "-6", Kind: reflect.Uint32},
|
|
{StringValue: "test", Kind: reflect.Uint64},
|
|
{StringValue: "-6", Kind: reflect.Uint64},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(fmt.Sprintf("%v %v", test.Kind, test.StringValue), func(t *testing.T) {
|
|
parsedValue, err := getParsedValue(test.Kind, test.StringValue)
|
|
|
|
if err == nil {
|
|
t.Errorf("unexpected unhandled error - stringValue: %v, Kind: %v", test.StringValue, test.Kind)
|
|
return
|
|
}
|
|
|
|
if parsedValue != reflect.ValueOf(nil) {
|
|
t.Errorf("unexpected parsed value - parsedValue: %v", parsedValue)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// CreateDefaultConfig builds the struct literal that `kubeshark config`
|
|
// serialises into helm-chart/values.yaml, and it sets Auth fields explicitly
|
|
// rather than leaving them to the `default:` struct tags. Two sources of the
|
|
// same default drift silently: the literal wins at runtime, the tag is what a
|
|
// reader checks, and the divergence only surfaces the next time someone runs
|
|
// `make generate-helm-values`.
|
|
//
|
|
// The default role is the one worth pinning. An operator who never configured
|
|
// authorization gets it applied to every caller, so a literal that disagrees
|
|
// with the tag rewrites the chart's default posture from admin to read-only
|
|
// and 403s an install that used to work.
|
|
func TestCreateDefaultConfig_AuthDefaultsMatchStructTags(t *testing.T) {
|
|
cfg := CreateDefaultConfig()
|
|
|
|
field, ok := reflect.TypeOf(cfg.Tap.Auth).FieldByName("DefaultRole")
|
|
if !ok {
|
|
t.Fatal("AuthConfig has no DefaultRole field")
|
|
}
|
|
|
|
want := field.Tag.Get("default")
|
|
if want == "" {
|
|
t.Fatal("DefaultRole carries no default tag to compare against")
|
|
}
|
|
if cfg.Tap.Auth.DefaultRole != want {
|
|
t.Errorf("CreateDefaultConfig sets DefaultRole=%q, struct tag says %q; "+
|
|
"generate-helm-values would write the former into values.yaml",
|
|
cfg.Tap.Auth.DefaultRole, want)
|
|
}
|
|
}
|