Skip to content

Commit

Permalink
feat(MeshHTTPRoute): add basic validation (#5625)
Browse files Browse the repository at this point in the history
Signed-off-by: Mike Beaumont <mjboamail@gmail.com>
  • Loading branch information
michaelbeaumont authored Jan 11, 2023
1 parent e6552b6 commit 8e2cf89
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 118 deletions.
18 changes: 3 additions & 15 deletions pkg/core/resources/apis/mesh/gateway_route_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,13 @@ import (
. "github.com/onsi/ginkgo/v2"

. "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
"github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/core/validators"
_ "github.com/kumahq/kuma/pkg/plugins/runtime/gateway/register"
. "github.com/kumahq/kuma/pkg/test/resources"
)

// MeshGatewayRouteGenerator is a ResourceGenerator that creates MeshGatewayResource objects.
type MeshGatewayRouteGenerator func() *MeshGatewayRouteResource

func (g MeshGatewayRouteGenerator) New() model.Resource {
if g != nil {
return g()
}

return nil
}

var _ = Describe("MeshGatewayRoute", func() {
DescribeValidCases(MeshGatewayRouteGenerator(NewMeshGatewayRouteResource),

DescribeValidCases(NewMeshGatewayRouteResource,
Entry("HTTP route", `
type: MeshGatewayRoute
name: route
Expand Down Expand Up @@ -115,7 +103,7 @@ conf:
`),
)

DescribeErrorCases(MeshGatewayRouteGenerator(NewMeshGatewayRouteResource),
DescribeErrorCases(NewMeshGatewayRouteResource,
ErrorCase("missing conf", validators.Violation{
Field: "conf",
Message: "cannot be empty",
Expand Down
17 changes: 3 additions & 14 deletions pkg/core/resources/apis/mesh/gateway_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,14 @@ import (
. "github.com/onsi/ginkgo/v2"

. "github.com/kumahq/kuma/pkg/core/resources/apis/mesh"
"github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/core/validators"
_ "github.com/kumahq/kuma/pkg/plugins/runtime/gateway/register"
. "github.com/kumahq/kuma/pkg/test/resources"
)

// GatewayGenerateor is a ResourceGenerator that creates GatewayResource objects.
type GatewayGenerator func() *MeshGatewayResource

func (g GatewayGenerator) New() model.Resource {
if g != nil {
return g()
}

return nil
}

var _ = Describe("Gateway", func() {
DescribeValidCases(
GatewayGenerator(NewMeshGatewayResource),
NewMeshGatewayResource,
Entry("HTTPS listener", `
type: MeshGateway
name: gateway
Expand Down Expand Up @@ -112,7 +101,7 @@ conf:
)

DescribeErrorCases(
GatewayGenerator(NewMeshGatewayResource),
NewMeshGatewayResource,
ErrorCase("doesn't have any selectors",
validators.Violation{
Field: `selectors`,
Expand Down
89 changes: 0 additions & 89 deletions pkg/core/resources/apis/mesh/mesh_suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,98 +3,9 @@ package mesh_test
import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

core_model "github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/core/validators"
"github.com/kumahq/kuma/pkg/test"
)

func TestMesh(t *testing.T) {
test.RunSpecs(t, "Mesh Suite")
}

// ResourceGenerator creates a resource of a pre-defined type.
type ResourceGenerator interface {
New() core_model.Resource
}

// ResourceValidationCase captures a resource YAML and any corresponding validation error.
type ResourceValidationCase struct {
Resource string
Violations []validators.Violation
}

// DescribeValidCases creates a Ginkgo table test for the given entries,
// where each entry is a valid YAML resource. It ensures that each entry
// can be successfully validated.
func DescribeValidCases(generator ResourceGenerator, cases ...TableEntry) {
DescribeTable(
"should pass validation",
func(given string) {
// setup
resource := generator.New()

// when
err := core_model.FromYAML([]byte(given), resource.GetSpec())

// then
Expect(err).ToNot(HaveOccurred())

// when
verr := core_model.Validate(resource)

// then
Expect(verr).ToNot(HaveOccurred())
},
cases)
}

// DescribeErrorCases creates a Ginkgo table test for the given entries, where each entry
// is a ResourceValidationCase that contains an invalid resource YAML and the corresponding
// validation error.
func DescribeErrorCases(generator ResourceGenerator, cases ...TableEntry) {
DescribeTable(
"should validate all fields and return as many individual errors as possible",
func(given ResourceValidationCase) {
// setup
resource := generator.New()

// when
Expect(
core_model.FromYAML([]byte(given.Resource), resource.GetSpec()),
).ToNot(HaveOccurred())

expected := validators.ValidationError{
Violations: given.Violations,
}

// then
err := core_model.Validate(resource).(*validators.ValidationError)
Expect(err.Violations).To(ConsistOf(expected.Violations))
},
cases,
)
}

// ErrorCase is a helper that generates a table entry for DescribeErrorCases.
func ErrorCase(description string, err validators.Violation, yaml string) TableEntry {
return Entry(
description,
ResourceValidationCase{
Violations: []validators.Violation{err},
Resource: yaml,
},
)
}

func ErrorCases(description string, errs []validators.Violation, yaml string) TableEntry {
return Entry(
description,
ResourceValidationCase{
Violations: errs,
Resource: yaml,
},
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package v1alpha1_test

import (
"testing"

"github.com/kumahq/kuma/pkg/test"
)

func TestPlugin(t *testing.T) {
test.RunSpecs(t, "MeshHTTPRoute")
}
44 changes: 44 additions & 0 deletions pkg/plugins/policies/meshhttproute/api/v1alpha1/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package v1alpha1

import (
common_api "github.com/kumahq/kuma/api/common/v1alpha1"
"github.com/kumahq/kuma/pkg/core/validators"
matcher_validators "github.com/kumahq/kuma/pkg/plugins/policies/matchers/validators"
)

func (r *MeshHTTPRouteResource) validate() error {
var verr validators.ValidationError
path := validators.RootedAt("spec")
verr.AddErrorAt(path.Field("targetRef"), validateTop(r.Spec.TargetRef))
verr.AddErrorAt(path.Field("to"), validateRules(r.Spec.To))
return verr.OrNil()
}

func validateTop(targetRef common_api.TargetRef) validators.ValidationError {
return matcher_validators.ValidateTargetRef(targetRef, &matcher_validators.ValidateTargetRefOpts{
SupportedKinds: []common_api.TargetRefKind{
common_api.Mesh,
common_api.MeshSubset,
common_api.MeshService,
common_api.MeshServiceSubset,
},
})
}

func validateToRef(targetRef common_api.TargetRef) validators.ValidationError {
return matcher_validators.ValidateTargetRef(targetRef, &matcher_validators.ValidateTargetRefOpts{
SupportedKinds: []common_api.TargetRefKind{
common_api.MeshService,
},
})
}

func validateRules(tos []To) validators.ValidationError {
var errs validators.ValidationError

for i, to := range tos {
errs.AddErrorAt(validators.PathBuilder{}.Index(i).Field("targetRef"), validateToRef(to.TargetRef))
}

return errs
}
56 changes: 56 additions & 0 deletions pkg/plugins/policies/meshhttproute/api/v1alpha1/validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package v1alpha1_test

import (
. "github.com/onsi/ginkgo/v2"

"github.com/kumahq/kuma/pkg/core/validators"
api "github.com/kumahq/kuma/pkg/plugins/policies/meshhttproute/api/v1alpha1"
. "github.com/kumahq/kuma/pkg/test/resources"
)

var _ = Describe("validation", func() {
DescribeErrorCases(
api.NewMeshHTTPRouteResource,
ErrorCase("spec.targetRef error",
validators.Violation{
Field: `spec.targetRef.kind`,
Message: `value is not supported`,
}, `
type: MeshHTTPRoute
mesh: mesh-1
name: route-1
targetRef:
kind: BlahBlah
name: frontend
to: []
`),
ErrorCase("spec.to.targetRef error",
validators.Violation{
Field: `spec.to[0].targetRef.kind`,
Message: `value is not supported`,
}, `
type: MeshHTTPRoute
mesh: mesh-1
name: route-1
targetRef:
kind: MeshService
name: frontend
to:
- targetRef:
kind: BlahBlah
name: frontend
`),
)
DescribeValidCases(
api.NewMeshHTTPRouteResource,
Entry("accepts valid resource", `
type: MeshHTTPRoute
mesh: mesh-1
name: route-1
targetRef:
kind: MeshService
name: frontend
to: []
`),
)
})
93 changes: 93 additions & 0 deletions pkg/test/resources/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package resources

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

core_model "github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/core/validators"
)

// ResourceGenerator creates a resource of a pre-defined type.
type ResourceGenerator interface {
New() core_model.Resource
}

// ResourceValidationCase captures a resource YAML and any corresponding validation error.
type ResourceValidationCase struct {
Resource string
Violations []validators.Violation
}

// DescribeValidCases creates a Ginkgo table test for the given entries,
// where each entry is a valid YAML resource. It ensures that each entry
// can be successfully validated.
func DescribeValidCases[T core_model.Resource](generator func() T, cases ...TableEntry) {
DescribeTable(
"should pass validation",
func(given string) {
// setup
resource := generator()

// when
err := core_model.FromYAML([]byte(given), resource.GetSpec())

// then
Expect(err).ToNot(HaveOccurred())

// when
verr := core_model.Validate(resource)

// then
Expect(verr).ToNot(HaveOccurred())
},
cases)
}

// DescribeErrorCases creates a Ginkgo table test for the given entries, where each entry
// is a ResourceValidationCase that contains an invalid resource YAML and the corresponding
// validation error.
func DescribeErrorCases[T core_model.Resource](generator func() T, cases ...TableEntry) {
DescribeTable(
"should validate all fields and return as many individual errors as possible",
func(given ResourceValidationCase) {
// setup
resource := generator()

// when
Expect(
core_model.FromYAML([]byte(given.Resource), resource.GetSpec()),
).ToNot(HaveOccurred())

expected := validators.ValidationError{
Violations: given.Violations,
}

// then
err := core_model.Validate(resource).(*validators.ValidationError)
Expect(err.Violations).To(ConsistOf(expected.Violations))
},
cases,
)
}

// ErrorCase is a helper that generates a table entry for DescribeErrorCases.
func ErrorCase(description string, err validators.Violation, yaml string) TableEntry {
return Entry(
description,
ResourceValidationCase{
Violations: []validators.Violation{err},
Resource: yaml,
},
)
}

func ErrorCases(description string, errs []validators.Violation, yaml string) TableEntry {
return Entry(
description,
ResourceValidationCase{
Violations: errs,
Resource: yaml,
},
)
}

0 comments on commit 8e2cf89

Please sign in to comment.