mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
8e2f1807ac
Part of #2745 Like most of his ideas, @anderseknert's suggestion to use Rego to replace the `validateAndInjectDefaults` functions throughout the codebase is another winner. This PR starts the migration by replacing the top-level `validateAndInjectDefaults` in `v1/config/config.go` with an embedded policy, `validate.rego`. The policy injects the top-level defaults (`default_decision`, `default_authorization_decision`, `labels`) and reports unrecognized configuration options, so a typo such as `decision_log` instead of `decision_logs` is logged as a warning at startup rather than silently ignored. It's evaluated in `ParseConfig` using the low-level `ast`/`topdown` packages rather than the top-level `rego` package. This keeps `config` off the heavy `rego → bundle → …` dependency web (which would otherwise create import cycles as more packages' tests reach `config`), and we don't need any of the `rego` package's conveniences here — it's one module compiled once and a single query. The Rego unit tests run in CI via `build/run-rego-tests.sh` (and locally with `make rego-test`). This sets the foundation for the other plugin `validateAndInjectDefaults` functions to migrate to Rego as well; where the logic isn't too complicated it should be a fairly easy replacement. At the moment all known keys live in `validate.rego` under `_specs` to support the "warn on unrecognized options" check, but the plugin-specific entries can move closer to each plugin as it migrates. It would also be nice for `_specs` to be auto-generated somehow in the future. Supporting extension of config validation with custom policies is something I'd like to follow up with, so keeping #2745 open for now. I also think these policies could be reusable with [java-opa-sdk](https://github.com/open-policy-agent/java-opa-sdk) 👀 Signed-off-by: Sebastian Spaink <sebastianspaink@gmail.com>
152 lines
3.8 KiB
Go
152 lines
3.8 KiB
Go
// Copyright 2025 The OPA Authors. All rights reserved.
|
|
// Use of this source code is governed by an Apache2
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
// PopulateAllFields uses reflection to populate all fields of a struct with test data.
|
|
// This is useful for testing code that must handle all fields but when new fields
|
|
// might be added and missed. It must be possible to set all fields, or the helper
|
|
// will fail until the fields are supported.
|
|
// Caveats: only supports types needed at time of implementation, will not work
|
|
// on recursive structs.
|
|
func PopulateAllFields[T any](t *testing.T) *T {
|
|
t.Helper()
|
|
|
|
var instance T
|
|
instancePtr := &instance
|
|
instanceType := reflect.TypeOf(instance)
|
|
instanceValue := reflect.ValueOf(instancePtr).Elem()
|
|
|
|
populateStruct(t, instanceType, instanceValue)
|
|
|
|
return instancePtr
|
|
}
|
|
|
|
func populateStruct(t *testing.T, structType reflect.Type, structValue reflect.Value) {
|
|
t.Helper()
|
|
|
|
for i := range structType.NumField() {
|
|
field := structType.Field(i)
|
|
fieldValue := structValue.Field(i)
|
|
|
|
if !fieldValue.CanSet() {
|
|
continue
|
|
}
|
|
|
|
if !populateDefaultTypes(t, field.Type, fieldValue, i) {
|
|
t.Fatalf("Unknown field type %s for field %s - update PopulateAllFields()", field.Type, field.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func populateDefaultTypes(t *testing.T, fieldType reflect.Type, fieldValue reflect.Value, index int) bool {
|
|
t.Helper()
|
|
|
|
switch fieldType.Kind() {
|
|
case reflect.Slice:
|
|
if fieldType == reflect.TypeOf(json.RawMessage{}) {
|
|
fieldValue.Set(reflect.ValueOf(fmt.Appendf(nil, `{"test": "bar-%d"}`, index)))
|
|
return true
|
|
}
|
|
|
|
if fieldType.Elem().Kind() == reflect.String {
|
|
fieldValue.Set(reflect.ValueOf([]string{
|
|
fmt.Sprintf("test-%d-a", index),
|
|
fmt.Sprintf("test-%d-b", index),
|
|
}))
|
|
return true
|
|
}
|
|
|
|
case reflect.Pointer:
|
|
switch fieldType.Elem().Kind() {
|
|
case reflect.String:
|
|
testString := fmt.Sprintf("test-value-%d", index)
|
|
fieldValue.Set(reflect.ValueOf(&testString))
|
|
|
|
return true
|
|
case reflect.Int:
|
|
testInt := 100 + index // unique value per field
|
|
fieldValue.Set(reflect.ValueOf(&testInt))
|
|
|
|
return true
|
|
case reflect.Int64:
|
|
testInt64 := int64(200 + index) // unique value per field
|
|
fieldValue.Set(reflect.ValueOf(&testInt64))
|
|
|
|
return true
|
|
case reflect.Struct:
|
|
newStruct := reflect.New(fieldType.Elem())
|
|
populateStruct(t, fieldType.Elem(), newStruct.Elem())
|
|
fieldValue.Set(newStruct)
|
|
|
|
return true
|
|
case reflect.Bool:
|
|
newBool := true
|
|
fieldValue.Set(reflect.ValueOf(&newBool))
|
|
|
|
return true
|
|
}
|
|
|
|
case reflect.Bool:
|
|
fieldValue.SetBool(true)
|
|
|
|
return true
|
|
|
|
case reflect.Struct:
|
|
populateStruct(t, fieldType, fieldValue)
|
|
|
|
return true
|
|
|
|
case reflect.Map:
|
|
switch {
|
|
case fieldType.Key().Kind() == reflect.String && fieldType.Elem().Kind() == reflect.String:
|
|
fieldValue.Set(reflect.ValueOf(map[string]string{
|
|
"env": fmt.Sprintf("test-%d", index),
|
|
"version": fmt.Sprintf("1.%d", index),
|
|
}))
|
|
|
|
return true
|
|
|
|
case fieldType.Key().Kind() == reflect.String &&
|
|
fieldType.Elem() == reflect.TypeOf(json.RawMessage{}):
|
|
|
|
fieldValue.Set(reflect.ValueOf(map[string]json.RawMessage{
|
|
"key1": fmt.Appendf(nil, `{"test": "bar-%d"}`, index),
|
|
"key2": fmt.Appendf(nil, `{"foo": "baz-%d"}`, index),
|
|
}))
|
|
|
|
return true
|
|
|
|
case fieldType.Key().Kind() == reflect.String &&
|
|
fieldType.Elem().Kind() == reflect.Pointer &&
|
|
fieldType.Elem().Elem().Kind() == reflect.Struct:
|
|
|
|
elemType := fieldType.Elem().Elem()
|
|
|
|
mapVal := reflect.MakeMap(fieldType)
|
|
|
|
for _, key := range []string{"test1", "test2"} {
|
|
newElem := reflect.New(elemType)
|
|
|
|
populateStruct(t, elemType, newElem.Elem())
|
|
|
|
mapVal.SetMapIndex(reflect.ValueOf(key), newElem)
|
|
}
|
|
|
|
fieldValue.Set(mapVal)
|
|
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|