Skip to content

Commit

Permalink
Add defaults and validation for ServiceDefaults
Browse files Browse the repository at this point in the history
  • Loading branch information
thisisnotashwin committed Sep 3, 2020
1 parent 3a61dfe commit 9ea49fa
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 13 deletions.
68 changes: 55 additions & 13 deletions api/v1alpha1/servicedefaults_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ package v1alpha1

import (
capi "github.com/hashicorp/consul/api"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/validation/field"
)

// ServiceDefaultsSpec defines the desired state of ServiceDefaults
Expand Down Expand Up @@ -52,25 +55,49 @@ func init() {
}

// ToConsul converts the entry into it's Consul equivalent struct.
func (s *ServiceDefaults) ToConsul() *capi.ServiceConfigEntry {
func (in *ServiceDefaults) ToConsul() *capi.ServiceConfigEntry {
return &capi.ServiceConfigEntry{
Kind: capi.ServiceDefaults,
Name: s.Name,
//Namespace: s.Namespace, // todo: don't set this unless enterprise
Protocol: s.Spec.Protocol,
MeshGateway: s.Spec.MeshGateway.toConsul(),
Expose: s.Spec.Expose.toConsul(),
ExternalSNI: s.Spec.ExternalSNI,
Name: in.Name,
//Namespace: in.Namespace, // todo: don't set this unless enterprise
Protocol: in.Spec.Protocol,
MeshGateway: in.Spec.MeshGateway.toConsul(),
Expose: in.Spec.Expose.toConsul(),
ExternalSNI: in.Spec.ExternalSNI,
}
}

// MatchesConsul returns true if entry has the same config as this struct.
func (s *ServiceDefaults) MatchesConsul(entry *capi.ServiceConfigEntry) bool {
return s.Name == entry.GetName() &&
s.Spec.Protocol == entry.Protocol &&
s.Spec.MeshGateway.Mode == string(entry.MeshGateway.Mode) &&
s.Spec.Expose.matches(entry.Expose) &&
s.Spec.ExternalSNI == entry.ExternalSNI
func (in *ServiceDefaults) MatchesConsul(entry *capi.ServiceConfigEntry) bool {
return in.Name == entry.GetName() &&
in.Spec.Protocol == entry.Protocol &&
in.Spec.MeshGateway.Mode == string(entry.MeshGateway.Mode) &&
in.Spec.Expose.matches(entry.Expose) &&
in.Spec.ExternalSNI == entry.ExternalSNI
}

func (in *ServiceDefaults) Default() {
if in.Spec.Protocol == "" {
in.Spec.Protocol = "tcp"
}
in.Spec.Expose.defaultConfig()
}

func (in *ServiceDefaults) Validate() error {
var allErrs field.ErrorList
if err := in.Spec.MeshGateway.validate(); err != nil {
allErrs = append(allErrs, err)
}
if err := in.Spec.Expose.validate(); err != nil {
allErrs = append(allErrs, err)
}
if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(
schema.GroupKind{Group: "consul.hashicorp.com", Kind: "ServiceDefaults"},
in.Name, allErrs)
}

// ExposeConfig describes HTTP paths to expose through Envoy outside of Connect.
Expand Down Expand Up @@ -144,3 +171,18 @@ func (e ExposeConfig) toConsul() capi.ExposeConfig {
Paths: paths,
}
}

func (e *ExposeConfig) defaultConfig() {
for i, path := range e.Paths {
if path.Protocol == "" {
e.Paths[i].Protocol = "http"
}
}
}

func (e ExposeConfig) validate() *field.Error {
//for i, path := range e.Paths {
//
//}
return nil
}
81 changes: 81 additions & 0 deletions api/v1alpha1/servicedefaults_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -654,3 +654,84 @@ func TestMatchesConsul(t *testing.T) {
})
}
}

func TestDefault(t *testing.T) {
cases := map[string]struct {
input *ServiceDefaults
expected *ServiceDefaults
}{
"protocol": {
&ServiceDefaults{
Spec: ServiceDefaultsSpec{
Protocol: "",
},
},
&ServiceDefaults{
Spec: ServiceDefaultsSpec{
Protocol: "tcp",
},
},
},
"expose.path.protocol": {
&ServiceDefaults{
Spec: ServiceDefaultsSpec{
Protocol: "tcp",
Expose: ExposeConfig{
Paths: []ExposePath{
{
Protocol: "",
},
},
},
},
},
&ServiceDefaults{
Spec: ServiceDefaultsSpec{
Protocol: "tcp",
Expose: ExposeConfig{
Paths: []ExposePath{
{
Protocol: "http",
},
},
},
},
},
},
}

for name, testCase := range cases {
t.Run(name, func(t *testing.T) {
testCase.input.Default()
require.Equal(t, testCase.expected, testCase.input)
})
}
}

func TestValidate(t *testing.T) {
cases := map[string]struct {
input *ServiceDefaults
expectedErrMsg string
}{
"meshgateway.mode": {
&ServiceDefaults{
ObjectMeta: metav1.ObjectMeta{
Name: "my-service",
},
Spec: ServiceDefaultsSpec{
MeshGateway: MeshGatewayConfig{
Mode: "foobar",
},
},
},
`ServiceDefaults.consul.hashicorp.com "my-service" is invalid: spec.meshGateway.mode: Invalid value: "foobar": must be on of "remote", "local", "none" or ""`,
},
}

for name, testCase := range cases {
t.Run(name, func(t *testing.T) {
err := testCase.input.Validate()
require.EqualError(t, err, testCase.expectedErrMsg)
})
}
}
4 changes: 4 additions & 0 deletions api/v1alpha1/servicedefaults_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ func (v *serviceDefaultsValidator) Handle(ctx context.Context, req admission.Req
}
}
}
svcDefaults.Default()
if err := svcDefaults.Validate(); err != nil {
return admission.Errored(http.StatusBadRequest, err)
}
return admission.Allowed("Valid Service Defaults Request")
}

Expand Down
10 changes: 10 additions & 0 deletions api/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package v1alpha1

import (
capi "github.com/hashicorp/consul/api"
"k8s.io/apimachinery/pkg/util/validation/field"
)

type MeshGatewayMode string
Expand Down Expand Up @@ -47,3 +48,12 @@ func (m MeshGatewayConfig) toConsul() capi.MeshGatewayConfig {
}
}
}

func (m MeshGatewayConfig) validate() *field.Error {
switch m.Mode {
case "", "local", "remote", "none":
return nil
default:
return field.Invalid(field.NewPath("spec").Child("meshGateway").Child("mode"), m.Mode, `must be one of "remote", "local", "none" or ""`)
}
}

0 comments on commit 9ea49fa

Please sign in to comment.