mesh: add more validations to Destinations resource (#19202)

This commit is contained in:
Iryna Shustava 2023-10-13 16:52:20 -06:00 committed by GitHub
parent e94d6ceca6
commit 2ea33e9b86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 288 additions and 36 deletions

View File

@ -21,6 +21,7 @@ Data {
DestinationPort = "tcp" DestinationPort = "tcp"
IpPort = { IpPort = {
Ip = "127.0.0.1"
Port = 1234 Port = 1234
} }
} }

View File

@ -110,3 +110,11 @@ func ValidateLocalServiceRefNoSection(ref *pbresource.Reference, wrapErr func(er
func ValidateSelector(sel *pbcatalog.WorkloadSelector, allowEmpty bool) error { func ValidateSelector(sel *pbcatalog.WorkloadSelector, allowEmpty bool) error {
return types.ValidateSelector(sel, allowEmpty) return types.ValidateSelector(sel, allowEmpty)
} }
func ValidatePortName(name string) error {
return types.ValidatePortName(name)
}
func IsValidUnixSocketPath(host string) bool {
return types.IsValidUnixSocketPath(host)
}

View File

@ -145,7 +145,7 @@ func ValidateFailoverPolicy(res *pbresource.Resource) error {
Wrapped: err, Wrapped: err,
} }
} }
if portNameErr := validatePortName(portName); portNameErr != nil { if portNameErr := ValidatePortName(portName); portNameErr != nil {
merr = multierror.Append(merr, resource.ErrInvalidMapKey{ merr = multierror.Append(merr, resource.ErrInvalidMapKey{
Map: "port_configs", Map: "port_configs",
Key: portName, Key: portName,
@ -245,7 +245,7 @@ func validateFailoverPolicyDestination(dest *pbcatalog.FailoverDestination, port
// assumed and will be reconciled. // assumed and will be reconciled.
if dest.Port != "" { if dest.Port != "" {
if ported { if ported {
if portNameErr := validatePortName(dest.Port); portNameErr != nil { if portNameErr := ValidatePortName(dest.Port); portNameErr != nil {
merr = multierror.Append(merr, wrapErr(resource.ErrInvalidField{ merr = multierror.Append(merr, wrapErr(resource.ErrInvalidField{
Name: "port", Name: "port",
Wrapped: portNameErr, Wrapped: portNameErr,

View File

@ -89,7 +89,7 @@ func ValidateService(res *pbresource.Resource) error {
} }
// validate the target port // validate the target port
if nameErr := validatePortName(port.TargetPort); nameErr != nil { if nameErr := ValidatePortName(port.TargetPort); nameErr != nil {
err = multierror.Append(err, resource.ErrInvalidListElement{ err = multierror.Append(err, resource.ErrInvalidListElement{
Name: "ports", Name: "ports",
Index: idx, Index: idx,

View File

@ -126,7 +126,7 @@ func validateEndpoint(endpoint *pbcatalog.Endpoint, res *pbresource.Resource) er
// Validate the endpoints ports // Validate the endpoints ports
for portName, port := range endpoint.Ports { for portName, port := range endpoint.Ports {
// Port names must be DNS labels // Port names must be DNS labels
if portNameErr := validatePortName(portName); portNameErr != nil { if portNameErr := ValidatePortName(portName); portNameErr != nil {
err = multierror.Append(err, resource.ErrInvalidMapKey{ err = multierror.Append(err, resource.ErrInvalidMapKey{
Map: "ports", Map: "ports",
Key: portName, Key: portName,

View File

@ -56,7 +56,7 @@ func isValidDNSLabel(label string) bool {
return dnsLabelMatcher.Match([]byte(label)) return dnsLabelMatcher.Match([]byte(label))
} }
func isValidUnixSocketPath(host string) bool { func IsValidUnixSocketPath(host string) bool {
if len(host) > maxUnixSocketPathLen || !strings.HasPrefix(host, "unix://") || strings.Contains(host, "\000") { if len(host) > maxUnixSocketPathLen || !strings.HasPrefix(host, "unix://") || strings.Contains(host, "\000") {
return false return false
} }
@ -71,7 +71,7 @@ func validateWorkloadHost(host string) error {
} }
// Check if the host represents an IP address, unix socket path or a DNS name // Check if the host represents an IP address, unix socket path or a DNS name
if !isValidIPAddress(host) && !isValidUnixSocketPath(host) && !isValidDNSName(host) { if !isValidIPAddress(host) && !IsValidUnixSocketPath(host) && !isValidDNSName(host) {
return errInvalidWorkloadHostFormat{Host: host} return errInvalidWorkloadHostFormat{Host: host}
} }
@ -139,7 +139,7 @@ func validateIPAddress(ip string) error {
return nil return nil
} }
func validatePortName(name string) error { func ValidatePortName(name string) error {
if name == "" { if name == "" {
return resource.ErrEmpty return resource.ErrEmpty
} }
@ -184,7 +184,7 @@ func validateWorkloadAddress(addr *pbcatalog.WorkloadAddress, ports map[string]*
// Ensure that unix sockets reference exactly 1 port. They may also indirectly reference 1 port // Ensure that unix sockets reference exactly 1 port. They may also indirectly reference 1 port
// by the workload having only a single port and omitting any explicit port assignment. // by the workload having only a single port and omitting any explicit port assignment.
if isValidUnixSocketPath(addr.Host) && if IsValidUnixSocketPath(addr.Host) &&
(len(addr.Ports) > 1 || (len(addr.Ports) == 0 && len(ports) > 1)) { (len(addr.Ports) > 1 || (len(addr.Ports) == 0 && len(ports) > 1)) {
err = multierror.Append(err, errUnixSocketMultiport) err = multierror.Append(err, errUnixSocketMultiport)
} }

View File

@ -178,7 +178,7 @@ func TestIsValidUnixSocketPath(t *testing.T) {
for name, tcase := range cases { for name, tcase := range cases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
require.Equal(t, tcase.valid, isValidUnixSocketPath(tcase.name)) require.Equal(t, tcase.valid, IsValidUnixSocketPath(tcase.name))
}) })
} }
} }
@ -361,15 +361,15 @@ func TestValidatePortName(t *testing.T) {
// test for the isValidDNSLabel function. // test for the isValidDNSLabel function.
t.Run("empty", func(t *testing.T) { t.Run("empty", func(t *testing.T) {
require.Equal(t, resource.ErrEmpty, validatePortName("")) require.Equal(t, resource.ErrEmpty, ValidatePortName(""))
}) })
t.Run("invalid", func(t *testing.T) { t.Run("invalid", func(t *testing.T) {
require.Equal(t, errNotDNSLabel, validatePortName("foo.com")) require.Equal(t, errNotDNSLabel, ValidatePortName("foo.com"))
}) })
t.Run("ok", func(t *testing.T) { t.Run("ok", func(t *testing.T) {
require.NoError(t, validatePortName("http")) require.NoError(t, ValidatePortName("http"))
}) })
} }

View File

@ -44,7 +44,7 @@ func ValidateWorkload(res *pbresource.Resource) error {
// Validate the Workload Ports // Validate the Workload Ports
for portName, port := range workload.Ports { for portName, port := range workload.Ports {
if portNameErr := validatePortName(portName); portNameErr != nil { if portNameErr := ValidatePortName(portName); portNameErr != nil {
err = multierror.Append(err, resource.ErrInvalidMapKey{ err = multierror.Append(err, resource.ErrInvalidMapKey{
Map: "ports", Map: "ports",
Key: portName, Key: portName,

View File

@ -553,7 +553,7 @@ func (suite *controllerTestSuite) TestController() {
}).Write(suite.T(), suite.client) }).Write(suite.T(), suite.client)
testutil.RunStep(suite.T(), "add explicit destinations and check that new proxy state is generated", func(t *testing.T) { testutil.RunStep(suite.T(), "add explicit destinations and check that new proxy state is generated", func(t *testing.T) {
webProxyStateTemplate = suite.client.WaitForNewVersion(t, webProxyStateTemplateID, webProxyStateTemplate.Version) webProxyStateTemplate = suite.client.WaitForNewVersion(suite.T(), webProxyStateTemplateID, webProxyStateTemplate.Version)
requireExplicitDestinationsFound(t, "api", webProxyStateTemplate) requireExplicitDestinationsFound(t, "api", webProxyStateTemplate)
}) })
@ -613,7 +613,7 @@ func (suite *controllerTestSuite) TestController() {
}) })
// We should get a new web proxy template resource because this destination should be removed. // We should get a new web proxy template resource because this destination should be removed.
webProxyStateTemplate = suite.client.WaitForNewVersion(t, webProxyStateTemplateID, webProxyStateTemplate.Version) webProxyStateTemplate = suite.client.WaitForNewVersion(suite.T(), webProxyStateTemplateID, webProxyStateTemplate.Version)
requireExplicitDestinationsNotFound(t, "api", webProxyStateTemplate) requireExplicitDestinationsNotFound(t, "api", webProxyStateTemplate)
}) })

View File

@ -4,6 +4,8 @@
package types package types
import ( import (
"net"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
@ -73,7 +75,6 @@ func ValidateDestinations(res *pbresource.Resource) error {
var merr error var merr error
// Validate the workload selector
if selErr := catalog.ValidateSelector(destinations.Workloads, false); selErr != nil { if selErr := catalog.ValidateSelector(destinations.Workloads, false); selErr != nil {
merr = multierror.Append(merr, resource.ErrInvalidField{ merr = multierror.Append(merr, resource.ErrInvalidField{
Name: "workloads", Name: "workloads",
@ -81,10 +82,17 @@ func ValidateDestinations(res *pbresource.Resource) error {
}) })
} }
if destinations.GetPqDestinations() != nil {
merr = multierror.Append(merr, resource.ErrInvalidField{
Name: "pq_destinations",
Wrapped: resource.ErrUnsupported,
})
}
for i, dest := range destinations.Destinations { for i, dest := range destinations.Destinations {
wrapDestErr := func(err error) error { wrapDestErr := func(err error) error {
return resource.ErrInvalidListElement{ return resource.ErrInvalidListElement{
Name: "upstreams", Name: "destinations",
Index: i, Index: i,
Wrapped: err, Wrapped: err,
} }
@ -101,8 +109,73 @@ func ValidateDestinations(res *pbresource.Resource) error {
merr = multierror.Append(merr, refErr) merr = multierror.Append(merr, refErr)
} }
// TODO(v2): validate port name using catalog validator if portErr := catalog.ValidatePortName(dest.DestinationPort); portErr != nil {
// TODO(v2): validate ListenAddr merr = multierror.Append(merr, wrapDestErr(resource.ErrInvalidField{
Name: "destination_port",
Wrapped: portErr,
}))
}
if dest.GetDatacenter() != "" {
merr = multierror.Append(merr, wrapDestErr(resource.ErrInvalidField{
Name: "datacenter",
Wrapped: resource.ErrUnsupported,
}))
}
if listenAddrErr := validateListenAddr(dest); listenAddrErr != nil {
merr = multierror.Append(merr, wrapDestErr(listenAddrErr))
}
}
return merr
}
func validateListenAddr(dest *pbmesh.Destination) error {
var merr error
if dest.GetListenAddr() == nil {
return multierror.Append(merr, resource.ErrInvalidFields{
Names: []string{"ip_port", "unix"},
Wrapped: resource.ErrMissingOneOf,
})
}
switch listenAddr := dest.GetListenAddr().(type) {
case *pbmesh.Destination_IpPort:
if ipPortErr := validateIPPort(listenAddr.IpPort); ipPortErr != nil {
merr = multierror.Append(merr, resource.ErrInvalidField{
Name: "ip_port",
Wrapped: ipPortErr,
})
}
case *pbmesh.Destination_Unix:
if !catalog.IsValidUnixSocketPath(listenAddr.Unix.GetPath()) {
merr = multierror.Append(merr, resource.ErrInvalidField{
Name: "unix",
Wrapped: resource.ErrInvalidField{
Name: "path",
Wrapped: errInvalidUnixSocketPath,
},
})
}
}
return merr
}
func validateIPPort(ipPort *pbmesh.IPPortAddress) error {
var merr error
if listenPortErr := validatePort(ipPort.GetPort(), "port"); listenPortErr != nil {
merr = multierror.Append(merr, listenPortErr)
}
if net.ParseIP(ipPort.GetIp()) == nil {
merr = multierror.Append(merr, resource.ErrInvalidField{
Name: "ip",
Wrapped: errInvalidIP,
})
} }
return merr return merr

View File

@ -17,7 +17,7 @@ import (
"github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil"
) )
func TestMutateUpstreams(t *testing.T) { func TestMutateDestinations(t *testing.T) {
type testcase struct { type testcase struct {
tenancy *pbresource.Tenancy tenancy *pbresource.Tenancy
data *pbmesh.Destinations data *pbmesh.Destinations
@ -86,7 +86,7 @@ func TestMutateUpstreams(t *testing.T) {
} }
} }
func TestValidateUpstreams(t *testing.T) { func TestValidateDestinations(t *testing.T) {
type testcase struct { type testcase struct {
data *pbmesh.Destinations data *pbmesh.Destinations
skipMutate bool skipMutate bool
@ -151,7 +151,7 @@ func TestValidateUpstreams(t *testing.T) {
{DestinationRef: nil}, {DestinationRef: nil},
}, },
}, },
expectErr: `invalid element at index 0 of list "upstreams": invalid "destination_ref" field: missing required field`, expectErr: `invalid element at index 0 of list "destinations": invalid "destination_ref" field: missing required field`,
}, },
"dest/bad type": { "dest/bad type": {
skipMutate: true, skipMutate: true,
@ -163,7 +163,7 @@ func TestValidateUpstreams(t *testing.T) {
{DestinationRef: newRefWithTenancy(pbcatalog.WorkloadType, "default.default", "api")}, {DestinationRef: newRefWithTenancy(pbcatalog.WorkloadType, "default.default", "api")},
}, },
}, },
expectErr: `invalid element at index 0 of list "upstreams": invalid "destination_ref" field: invalid "type" field: reference must have type catalog.v2beta1.Service`, expectErr: `invalid element at index 0 of list "destinations": invalid "destination_ref" field: invalid "type" field: reference must have type catalog.v2beta1.Service`,
}, },
"dest/nil tenancy": { "dest/nil tenancy": {
skipMutate: true, skipMutate: true,
@ -175,7 +175,7 @@ func TestValidateUpstreams(t *testing.T) {
{DestinationRef: &pbresource.Reference{Type: pbcatalog.ServiceType, Name: "api"}}, {DestinationRef: &pbresource.Reference{Type: pbcatalog.ServiceType, Name: "api"}},
}, },
}, },
expectErr: `invalid element at index 0 of list "upstreams": invalid "destination_ref" field: invalid "tenancy" field: missing required field`, expectErr: `invalid element at index 0 of list "destinations": invalid "destination_ref" field: invalid "tenancy" field: missing required field`,
}, },
"dest/bad dest tenancy/partition": { "dest/bad dest tenancy/partition": {
skipMutate: true, skipMutate: true,
@ -187,7 +187,7 @@ func TestValidateUpstreams(t *testing.T) {
{DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, ".bar", "api")}, {DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, ".bar", "api")},
}, },
}, },
expectErr: `invalid element at index 0 of list "upstreams": invalid "destination_ref" field: invalid "tenancy" field: invalid "partition" field: cannot be empty`, expectErr: `invalid element at index 0 of list "destinations": invalid "destination_ref" field: invalid "tenancy" field: invalid "partition" field: cannot be empty`,
}, },
"dest/bad dest tenancy/namespace": { "dest/bad dest tenancy/namespace": {
skipMutate: true, skipMutate: true,
@ -199,7 +199,7 @@ func TestValidateUpstreams(t *testing.T) {
{DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo", "api")}, {DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo", "api")},
}, },
}, },
expectErr: `invalid element at index 0 of list "upstreams": invalid "destination_ref" field: invalid "tenancy" field: invalid "namespace" field: cannot be empty`, expectErr: `invalid element at index 0 of list "destinations": invalid "destination_ref" field: invalid "tenancy" field: invalid "namespace" field: cannot be empty`,
}, },
"dest/bad dest tenancy/peer_name": { "dest/bad dest tenancy/peer_name": {
skipMutate: true, skipMutate: true,
@ -213,17 +213,158 @@ func TestValidateUpstreams(t *testing.T) {
Reference("")}, Reference("")},
}, },
}, },
expectErr: `invalid element at index 0 of list "upstreams": invalid "destination_ref" field: invalid "tenancy" field: invalid "peer_name" field: must be set to "local"`, expectErr: `invalid element at index 0 of list "destinations": invalid "destination_ref" field: invalid "tenancy" field: invalid "peer_name" field: must be set to "local"`,
},
"unsupported pq_destinations": {
skipMutate: true,
data: &pbmesh.Destinations{
Workloads: &pbcatalog.WorkloadSelector{Names: []string{"foo"}},
PqDestinations: []*pbmesh.PreparedQueryDestination{
{Name: "foo-query"},
},
},
expectErr: `invalid "pq_destinations" field: field is currently not supported`,
},
"missing destination port": {
skipMutate: true,
data: &pbmesh.Destinations{
Workloads: &pbcatalog.WorkloadSelector{Names: []string{"foo"}},
Destinations: []*pbmesh.Destination{
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api"),
ListenAddr: &pbmesh.Destination_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: "127.0.0.1",
Port: 1234,
},
},
},
},
},
expectErr: `invalid element at index 0 of list "destinations": invalid "destination_port" field: cannot be empty`,
},
"unsupported datacenter": {
skipMutate: true,
data: &pbmesh.Destinations{
Workloads: &pbcatalog.WorkloadSelector{Names: []string{"foo"}},
Destinations: []*pbmesh.Destination{
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api"),
DestinationPort: "p1",
Datacenter: "dc2",
ListenAddr: &pbmesh.Destination_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: "127.0.0.1",
Port: 1234,
},
},
},
},
},
expectErr: `invalid element at index 0 of list "destinations": invalid "datacenter" field: field is currently not supported`,
},
"missing listen addr": {
skipMutate: true,
data: &pbmesh.Destinations{
Workloads: &pbcatalog.WorkloadSelector{Names: []string{"foo"}},
Destinations: []*pbmesh.Destination{
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api"),
DestinationPort: "p1",
},
},
},
expectErr: `invalid "ip_port,unix" fields: missing one of the required fields`,
},
"invalid ip for listen addr": {
skipMutate: true,
data: &pbmesh.Destinations{
Workloads: &pbcatalog.WorkloadSelector{Names: []string{"foo"}},
Destinations: []*pbmesh.Destination{
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api"),
DestinationPort: "p1",
ListenAddr: &pbmesh.Destination_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: "invalid",
Port: 1234,
},
},
},
},
},
expectErr: `invalid "ip" field: IP address is not valid`,
},
"invalid port for listen addr": {
skipMutate: true,
data: &pbmesh.Destinations{
Workloads: &pbcatalog.WorkloadSelector{Names: []string{"foo"}},
Destinations: []*pbmesh.Destination{
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api"),
DestinationPort: "p1",
ListenAddr: &pbmesh.Destination_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: "127.0.0.1",
Port: 0,
},
},
},
},
},
expectErr: `invalid "port" field: port number is outside the range 1 to 65535`,
},
"invalid unix path for listen addr": {
skipMutate: true,
data: &pbmesh.Destinations{
Workloads: &pbcatalog.WorkloadSelector{Names: []string{"foo"}},
Destinations: []*pbmesh.Destination{
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api"),
DestinationPort: "p1",
ListenAddr: &pbmesh.Destination_Unix{
Unix: &pbmesh.UnixSocketAddress{
Path: "foo",
},
},
},
},
},
expectErr: `invalid "unix" field: invalid "path" field: unix socket path is not valid`,
}, },
"normal": { "normal": {
data: &pbmesh.Destinations{ data: &pbmesh.Destinations{
Workloads: &pbcatalog.WorkloadSelector{ Workloads: &pbcatalog.WorkloadSelector{Names: []string{"foo"}},
Names: []string{"blah"},
},
Destinations: []*pbmesh.Destination{ Destinations: []*pbmesh.Destination{
{DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api")}, {
{DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.zim", "api")}, DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api"),
{DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "gir.zim", "api")}, DestinationPort: "p1",
ListenAddr: &pbmesh.Destination_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: "127.0.0.1",
Port: 1234,
},
},
},
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.zim", "api"),
DestinationPort: "p2",
ListenAddr: &pbmesh.Destination_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: "127.0.0.1",
Port: 1235,
},
},
},
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "gir.zim", "api"),
DestinationPort: "p3",
ListenAddr: &pbmesh.Destination_Unix{
Unix: &pbmesh.UnixSocketAddress{
Path: "unix://foo/bar",
},
},
},
}, },
}, },
}, },
@ -234,9 +375,35 @@ func TestValidateUpstreams(t *testing.T) {
Filter: "metadata.foo == bar", Filter: "metadata.foo == bar",
}, },
Destinations: []*pbmesh.Destination{ Destinations: []*pbmesh.Destination{
{DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api")}, {
{DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.zim", "api")}, DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.bar", "api"),
{DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "gir.zim", "api")}, DestinationPort: "p1",
ListenAddr: &pbmesh.Destination_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: "127.0.0.1",
Port: 1234,
},
},
},
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "foo.zim", "api"),
DestinationPort: "p2",
ListenAddr: &pbmesh.Destination_IpPort{
IpPort: &pbmesh.IPPortAddress{
Ip: "127.0.0.1",
Port: 1235,
},
},
},
{
DestinationRef: newRefWithTenancy(pbcatalog.ServiceType, "gir.zim", "api"),
DestinationPort: "p3",
ListenAddr: &pbmesh.Destination_Unix{
Unix: &pbmesh.UnixSocketAddress{
Path: "unix://foo/bar",
},
},
},
}, },
}, },
}, },

View File

@ -9,6 +9,8 @@ import (
var ( var (
errInvalidPort = errors.New("port number is outside the range 1 to 65535") errInvalidPort = errors.New("port number is outside the range 1 to 65535")
errInvalidIP = errors.New("IP address is not valid")
errInvalidUnixSocketPath = errors.New("unix socket path is not valid")
errInvalidExposePathProtocol = errors.New("invalid protocol: only HTTP and HTTP2 protocols are allowed") errInvalidExposePathProtocol = errors.New("invalid protocol: only HTTP and HTTP2 protocols are allowed")
errMissingProxyConfigData = errors.New("at least one of \"bootstrap_config\" or \"dynamic_config\" fields must be set") errMissingProxyConfigData = errors.New("at least one of \"bootstrap_config\" or \"dynamic_config\" fields must be set")
) )

View File

@ -14,6 +14,7 @@ import (
var ( var (
ErrMissing = NewConstError("missing required field") ErrMissing = NewConstError("missing required field")
ErrMissingOneOf = NewConstError("missing one of the required fields")
ErrEmpty = NewConstError("cannot be empty") ErrEmpty = NewConstError("cannot be empty")
ErrReferenceTenancyNotEqual = NewConstError("resource tenancy and reference tenancy differ") ErrReferenceTenancyNotEqual = NewConstError("resource tenancy and reference tenancy differ")
ErrUnsupported = NewConstError("field is currently not supported") ErrUnsupported = NewConstError("field is currently not supported")