mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-08 17:25:14 -04:00
cli: state file validation (#2523)
* re-use `ReadFromFile` in `CreateOrRead` Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip]: add constraints Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip] error formatting Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * wip Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * formatted error messages Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * state file validation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * linter fixes Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * allow overriding the constraints Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * dont validate on read Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add pre-create constraints Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * [wip] Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * finish pre-init validation test Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * finish post-init validation Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * use state file validation in CLI Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix apply tests Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * Update internal/validation/errors.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * use transformator for tests * tidy * use empty check directly Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * Update cli/internal/state/state.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * Update cli/internal/state/state.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * Update cli/internal/state/state.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * Update cli/internal/state/state.go Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com> * conditional validation per CSP Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * tidy Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * fix rebase Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * add default case Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> * validate state-file as last input Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> --------- Signed-off-by: Moritz Sanft <58110325+msanft@users.noreply.github.com> Co-authored-by: Daniel Weiße <66256922+daniel-weisse@users.noreply.github.com>
This commit is contained in:
parent
eaec73cca4
commit
744a605602
21 changed files with 1779 additions and 247 deletions
|
@ -8,6 +8,7 @@ package validation
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
@ -15,8 +16,10 @@ import (
|
|||
// Constraint is a constraint on a document or a field of a document.
|
||||
type Constraint struct {
|
||||
// Satisfied returns no error if the constraint is satisfied.
|
||||
// Otherwise, it returns the reason why the constraint is not satisfied.
|
||||
Satisfied func() error
|
||||
// Otherwise, it returns the reason why the constraint is not satisfied,
|
||||
// possibly including its child errors, i.e., errors returned by constraints
|
||||
// that are embedded in this constraint.
|
||||
Satisfied func() *TreeError
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -36,7 +39,7 @@ Example for a pointer field:
|
|||
Due to Go's addressability limititations regarding maps, if a map field is
|
||||
to be validated, WithMapFieldTrace must be used instead of WithFieldTrace.
|
||||
*/
|
||||
func (c *Constraint) WithFieldTrace(doc any, field any) Constraint {
|
||||
func (c *Constraint) WithFieldTrace(doc any, field any) *Constraint {
|
||||
// we only want to dereference the needle once to dereference the pointer
|
||||
// used to pass it to the function without losing reference to it, as the
|
||||
// needle could be an arbitrarily long chain of pointers. The same
|
||||
|
@ -69,7 +72,7 @@ Example:
|
|||
|
||||
For non-map fields, WithFieldTrace should be used instead of WithMapFieldTrace.
|
||||
*/
|
||||
func (c *Constraint) WithMapFieldTrace(doc any, field any, mapKey string) Constraint {
|
||||
func (c *Constraint) WithMapFieldTrace(doc any, field any, mapKey string) *Constraint {
|
||||
// we only want to dereference the needle once to dereference the pointer
|
||||
// used to pass it to the function without losing reference to it, as the
|
||||
// needle could be an arbitrarily long chain of pointers. The same
|
||||
|
@ -91,11 +94,11 @@ func (c *Constraint) WithMapFieldTrace(doc any, field any, mapKey string) Constr
|
|||
}
|
||||
|
||||
// withTrace wraps the constraint's error message with a well-formatted trace.
|
||||
func (c *Constraint) withTrace(docRef, fieldRef referenceableValue) Constraint {
|
||||
return Constraint{
|
||||
Satisfied: func() error {
|
||||
func (c *Constraint) withTrace(docRef, fieldRef referenceableValue) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
if err := c.Satisfied(); err != nil {
|
||||
return newError(docRef, fieldRef, err)
|
||||
return newTraceError(docRef, fieldRef, err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
@ -105,49 +108,207 @@ func (c *Constraint) withTrace(docRef, fieldRef referenceableValue) Constraint {
|
|||
// MatchRegex is a constraint that if s matches regex.
|
||||
func MatchRegex(s string, regex string) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() error {
|
||||
Satisfied: func() *TreeError {
|
||||
if !regexp.MustCompile(regex).MatchString(s) {
|
||||
return fmt.Errorf("%s must match the pattern %s", s, regex)
|
||||
return NewErrorTree(fmt.Errorf("%s must match the pattern %s", s, regex))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Equal is a constraint that if s is equal to t.
|
||||
// Equal is a constraint that checks if s is equal to t.
|
||||
func Equal[T comparable](s T, t T) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() error {
|
||||
Satisfied: func() *TreeError {
|
||||
if s != t {
|
||||
return fmt.Errorf("%v must be equal to %v", s, t)
|
||||
return NewErrorTree(fmt.Errorf("%v must be equal to %v", s, t))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NotEmpty is a constraint that if s is not empty.
|
||||
func NotEmpty[T comparable](s T) *Constraint {
|
||||
// NotEqual is a constraint that checks if s is not equal to t.
|
||||
func NotEqual[T comparable](s T, t T) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() error {
|
||||
var zero T
|
||||
if s == zero {
|
||||
return fmt.Errorf("%v must not be empty", s)
|
||||
Satisfied: func() *TreeError {
|
||||
if Equal(s, t).Satisfied() == nil {
|
||||
return NewErrorTree(fmt.Errorf("%v must not be equal to %v", s, t))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Empty is a constraint that if s is empty.
|
||||
// Empty is a constraint that checks if s is empty.
|
||||
func Empty[T comparable](s T) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() error {
|
||||
Satisfied: func() *TreeError {
|
||||
var zero T
|
||||
if s != zero {
|
||||
return fmt.Errorf("%v must be empty", s)
|
||||
return NewErrorTree(fmt.Errorf("%v must be empty", s))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NotEmpty is a constraint that checks if s is not empty.
|
||||
func NotEmpty[T comparable](s T) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
if Empty(s).Satisfied() == nil {
|
||||
return NewErrorTree(fmt.Errorf("must not be empty"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OneOf is a constraint that s is in the set of values p.
|
||||
func OneOf[T comparable](s T, p []T) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
for _, v := range p {
|
||||
if s == v {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return NewErrorTree(fmt.Errorf("%v must be one of %v", s, p))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// IPAddress is a constraint that checks if s is a valid IP address.
|
||||
func IPAddress(s string) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
if net.ParseIP(s) == nil {
|
||||
return NewErrorTree(fmt.Errorf("%s must be a valid IP address", s))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CIDR is a constraint that checks if s is a valid CIDR.
|
||||
func CIDR(s string) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
if _, _, err := net.ParseCIDR(s); err != nil {
|
||||
return NewErrorTree(fmt.Errorf("%s must be a valid CIDR", s))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// DNSName is a constraint that checks if s is a valid DNS name.
|
||||
func DNSName(s string) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
if _, err := net.LookupHost(s); err != nil {
|
||||
return NewErrorTree(fmt.Errorf("%s must be a valid DNS name", s))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// EmptySlice is a constraint that checks if s is an empty slice.
|
||||
func EmptySlice[T comparable](s []T) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
if len(s) != 0 {
|
||||
return NewErrorTree(fmt.Errorf("%v must be empty", s))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NotEmptySlice is a constraint that checks if slice s is not empty.
|
||||
func NotEmptySlice[T comparable](s []T) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
if EmptySlice(s).Satisfied() == nil {
|
||||
return NewErrorTree(fmt.Errorf("must not be empty"))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// All is a constraint that checks if all elements of s satisfy the constraint c.
|
||||
// The constraint should be parametric in regards to the index of the element in s,
|
||||
// as well as the element itself.
|
||||
func All[T comparable](s []T, c func(i int, v T) *Constraint) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
retErr := NewErrorTree(fmt.Errorf("all of the constraints must be satisfied: "))
|
||||
for i, v := range s {
|
||||
if err := c(i, v).Satisfied(); err != nil {
|
||||
retErr.appendChild(err)
|
||||
}
|
||||
}
|
||||
if len(retErr.children) == 0 {
|
||||
return nil
|
||||
}
|
||||
return retErr
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// And groups multiple constraints in an "and" relation and fails according to the given strategy.
|
||||
func And(errStrat ErrStrategy, constraints ...*Constraint) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
retErr := NewErrorTree(fmt.Errorf("all of the constraints must be satisfied: "))
|
||||
for _, constraint := range constraints {
|
||||
if err := constraint.Satisfied(); err != nil {
|
||||
if errStrat == FailFast {
|
||||
return err
|
||||
}
|
||||
retErr.appendChild(err)
|
||||
}
|
||||
}
|
||||
if len(retErr.children) == 0 {
|
||||
return nil
|
||||
}
|
||||
return retErr
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Or groups multiple constraints in an "or" relation.
|
||||
func Or(constraints ...*Constraint) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
retErr := NewErrorTree(fmt.Errorf("at least one of the constraints must be satisfied: "))
|
||||
for _, constraint := range constraints {
|
||||
err := constraint.Satisfied()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
retErr.appendChild(err)
|
||||
}
|
||||
if len(retErr.children) == 0 {
|
||||
return nil
|
||||
}
|
||||
return retErr
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// IfNotNil evaluates a constraint if and only if s is not nil.
|
||||
func IfNotNil[T comparable](s *T, c func() *Constraint) *Constraint {
|
||||
return &Constraint{
|
||||
Satisfied: func() *TreeError {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
return c().Satisfied()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue