parent
7d3c8af2c9
commit
0c04b0e3f7
|
@ -34,6 +34,7 @@ import (
|
||||||
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
|
||||||
"google.golang.org/grpc/grpclog"
|
"google.golang.org/grpc/grpclog"
|
||||||
|
|
||||||
|
"github.com/letsencrypt/boulder/config"
|
||||||
"github.com/letsencrypt/boulder/core"
|
"github.com/letsencrypt/boulder/core"
|
||||||
blog "github.com/letsencrypt/boulder/log"
|
blog "github.com/letsencrypt/boulder/log"
|
||||||
"github.com/letsencrypt/boulder/strictyaml"
|
"github.com/letsencrypt/boulder/strictyaml"
|
||||||
|
@ -455,6 +456,9 @@ func ValidateJSONConfig(cv *ConfigValidator, in io.Reader) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register custom types for use with existing validation tags.
|
||||||
|
validate.RegisterCustomTypeFunc(config.DurationCustomTypeFunc, config.Duration{})
|
||||||
|
|
||||||
err := decodeJSONStrict(in, cv.Config)
|
err := decodeJSONStrict(in, cv.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -497,6 +501,9 @@ func ValidateYAMLConfig(cv *ConfigValidator, in io.Reader) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register custom types for use with existing validation tags.
|
||||||
|
validate.RegisterCustomTypeFunc(config.DurationCustomTypeFunc, config.Duration{})
|
||||||
|
|
||||||
inBytes, err := io.ReadAll(in)
|
inBytes, err := io.ReadAll(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -11,10 +11,12 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
|
"github.com/letsencrypt/boulder/config"
|
||||||
"github.com/letsencrypt/boulder/core"
|
"github.com/letsencrypt/boulder/core"
|
||||||
blog "github.com/letsencrypt/boulder/log"
|
blog "github.com/letsencrypt/boulder/log"
|
||||||
"github.com/letsencrypt/boulder/test"
|
"github.com/letsencrypt/boulder/test"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -196,9 +198,11 @@ func loadConfigFile(t *testing.T, path string) *os.File {
|
||||||
|
|
||||||
func TestFailedConfigValidation(t *testing.T) {
|
func TestFailedConfigValidation(t *testing.T) {
|
||||||
type FooConfig struct {
|
type FooConfig struct {
|
||||||
VitalValue string `yaml:"vitalValue" validate:"required"`
|
VitalValue string `yaml:"vitalValue" validate:"required"`
|
||||||
VoluntarilyVoid string `yaml:"voluntarilyVoid"`
|
VoluntarilyVoid string `yaml:"voluntarilyVoid"`
|
||||||
VisciouslyVetted string `yaml:"visciouslyVetted" validate:"omitempty,endswith=baz"`
|
VisciouslyVetted string `yaml:"visciouslyVetted" validate:"omitempty,endswith=baz"`
|
||||||
|
VolatileVagary config.Duration `yaml:"volatileVagary" validate:"required,lte=120s"`
|
||||||
|
VernalVeil config.Duration `yaml:"vernalVeil" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Violates 'endswith' tag JSON.
|
// Violates 'endswith' tag JSON.
|
||||||
|
@ -228,6 +232,34 @@ func TestFailedConfigValidation(t *testing.T) {
|
||||||
err = ValidateYAMLConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
err = ValidateYAMLConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||||
test.AssertError(t, err, "Expected validation error")
|
test.AssertError(t, err, "Expected validation error")
|
||||||
test.AssertContains(t, err.Error(), "'required'")
|
test.AssertContains(t, err.Error(), "'required'")
|
||||||
|
|
||||||
|
// Violates 'lte' tag JSON for config.Duration type.
|
||||||
|
cf = loadConfigFile(t, "testdata/3_configDuration_too_darn_big.json")
|
||||||
|
defer cf.Close()
|
||||||
|
err = ValidateJSONConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||||
|
test.AssertError(t, err, "Expected validation error")
|
||||||
|
test.AssertContains(t, err.Error(), "'lte'")
|
||||||
|
|
||||||
|
// Violates 'lte' tag JSON for config.Duration type.
|
||||||
|
cf = loadConfigFile(t, "testdata/3_configDuration_too_darn_big.json")
|
||||||
|
defer cf.Close()
|
||||||
|
err = ValidateJSONConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||||
|
test.AssertError(t, err, "Expected validation error")
|
||||||
|
test.AssertContains(t, err.Error(), "'lte'")
|
||||||
|
|
||||||
|
// Incorrect value for the config.Duration type.
|
||||||
|
cf = loadConfigFile(t, "testdata/4_incorrect_data_for_type.json")
|
||||||
|
defer cf.Close()
|
||||||
|
err = ValidateJSONConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||||
|
test.AssertError(t, err, "Expected error")
|
||||||
|
test.AssertContains(t, err.Error(), "missing unit in duration")
|
||||||
|
|
||||||
|
// Incorrect value for the config.Duration type.
|
||||||
|
cf = loadConfigFile(t, "testdata/4_incorrect_data_for_type.yaml")
|
||||||
|
defer cf.Close()
|
||||||
|
err = ValidateYAMLConfig(&ConfigValidator{&FooConfig{}, nil}, cf)
|
||||||
|
test.AssertError(t, err, "Expected error")
|
||||||
|
test.AssertContains(t, err.Error(), "missing unit in duration")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailExit(t *testing.T) {
|
func TestFailExit(t *testing.T) {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"vitalValue": "Gotcha",
|
||||||
|
"voluntarilyVoid": "Not used",
|
||||||
|
"visciouslyVetted": "Whateverbaz",
|
||||||
|
"volatileVagary": "121s"
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"vitalValue": "Gotcha",
|
||||||
|
"voluntarilyVoid": "Not used",
|
||||||
|
"visciouslyVetted": "Whateverbaz",
|
||||||
|
"volatileVagary": "120s",
|
||||||
|
"vernalVeil": "60"
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
vitalValue: "Gotcha"
|
||||||
|
voluntarilyVoid: "Not used"
|
||||||
|
visciouslyVetted: "Whateverbaz"
|
||||||
|
volatileVagary: "120s"
|
||||||
|
vernalVeil: "60"
|
|
@ -3,15 +3,27 @@ package config
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Duration is just an alias for time.Duration that allows
|
// Duration is custom type embedding a time.Duration which allows defining
|
||||||
// serialization to YAML as well as JSON.
|
// methods such as serialization to YAML or JSON.
|
||||||
type Duration struct {
|
type Duration struct {
|
||||||
time.Duration `validate:"required"`
|
time.Duration `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DurationCustomTypeFunc enables registration of our custom config.Duration
|
||||||
|
// type as a time.Duration and performing validation on the configured value
|
||||||
|
// using the standard suite of validation functions.
|
||||||
|
func DurationCustomTypeFunc(field reflect.Value) interface{} {
|
||||||
|
if c, ok := field.Interface().(Duration); ok {
|
||||||
|
return c.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.Invalid
|
||||||
|
}
|
||||||
|
|
||||||
// ErrDurationMustBeString is returned when a non-string value is
|
// ErrDurationMustBeString is returned when a non-string value is
|
||||||
// presented to be deserialized as a ConfigDuration
|
// presented to be deserialized as a ConfigDuration
|
||||||
var ErrDurationMustBeString = errors.New("cannot JSON unmarshal something other than a string into a ConfigDuration")
|
var ErrDurationMustBeString = errors.New("cannot JSON unmarshal something other than a string into a ConfigDuration")
|
||||||
|
|
Loading…
Reference in New Issue