Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(MeshHTTPRoute): add basic validation #5625

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(path, 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(path validators.PathBuilder, tos []To) validators.ValidationError {
var errs validators.ValidationError

for i, to := range tos {
errs.AddErrorAt(path.Index(i), validateToRef(to.TargetRef))
}

return errs
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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("basic 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: []
`),
)
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 {
michaelbeaumont marked this conversation as resolved.
Show resolved Hide resolved
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,
},
)
}