blob: 2c7d72f902e9fb8f8f0bb608cce7a7b6bf440c91 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package mesh
import (
"fmt"
"regexp"
"sort"
"strings"
)
import (
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/durationpb"
"sigs.k8s.io/yaml"
)
import (
mesh_proto "github.com/apache/dubbo-kubernetes/api/mesh/v1alpha1"
"github.com/apache/dubbo-kubernetes/pkg/core/validators"
util_proto "github.com/apache/dubbo-kubernetes/pkg/util/proto"
)
const dnsLabel = `[a-z0-9]([-a-z0-9]*[a-z0-9])?`
var (
nameCharacterSet = regexp.MustCompile(`^[0-9a-z.\-_]*$`)
tagNameCharacterSet = regexp.MustCompile(`^[a-zA-Z0-9.\-_:/]*$`)
tagValueCharacterSet = regexp.MustCompile(`^[a-zA-Z0-9.\-_:]*$`)
selectorCharacterSet = regexp.MustCompile(`^([a-zA-Z0-9.\-_:/]*|\*)$`)
domainRegexp = regexp.MustCompile("^" + dnsLabel + "(\\." + dnsLabel + ")*" + "$")
)
type (
TagsValidatorFunc func(path validators.PathBuilder, selector map[string]string) validators.ValidationError
TagKeyValidatorFunc func(path validators.PathBuilder, key string) validators.ValidationError
TagValueValidatorFunc func(path validators.PathBuilder, key, value string) validators.ValidationError
)
type ValidateTagsOpts struct {
RequireAtLeastOneTag bool
RequireService bool
ExtraTagsValidators []TagsValidatorFunc
ExtraTagKeyValidators []TagKeyValidatorFunc
ExtraTagValueValidators []TagValueValidatorFunc
}
type ValidateSelectorsOpts struct {
ValidateTagsOpts
RequireAtMostOneSelector bool
RequireAtLeastOneSelector bool
}
func ValidateSelector(path validators.PathBuilder, tags map[string]string, opts ValidateTagsOpts) validators.ValidationError {
opts.ExtraTagValueValidators = append([]TagValueValidatorFunc{
func(path validators.PathBuilder, key, value string) validators.ValidationError {
var err validators.ValidationError
if !selectorCharacterSet.MatchString(value) {
err.AddViolationAt(path.Key(key), `tag value must consist of alphanumeric characters, dots, dashes, slashes and underscores or be "*"`)
}
return err
},
}, opts.ExtraTagValueValidators...)
return validateTagKeyValues(path, tags, opts)
}
func ValidateTags(path validators.PathBuilder, tags map[string]string, opts ValidateTagsOpts) validators.ValidationError {
opts.ExtraTagValueValidators = append([]TagValueValidatorFunc{
func(path validators.PathBuilder, key, value string) validators.ValidationError {
var err validators.ValidationError
if !tagValueCharacterSet.MatchString(value) {
err.AddViolationAt(path.Key(key), "tag value must consist of alphanumeric characters, dots, dashes and underscores")
}
return err
},
}, opts.ExtraTagValueValidators...)
return validateTagKeyValues(path, tags, opts)
}
func validateTagKeyValues(path validators.PathBuilder, keyValues map[string]string, opts ValidateTagsOpts) validators.ValidationError {
var err validators.ValidationError
if opts.RequireAtLeastOneTag && len(keyValues) == 0 {
err.AddViolationAt(path, "must have at least one tag")
}
for _, validate := range opts.ExtraTagsValidators {
err.Add(validate(path, keyValues))
}
for _, key := range Keys(keyValues) {
if key == "" {
err.AddViolationAt(path, "tag name must be non-empty")
}
if !tagNameCharacterSet.MatchString(key) {
err.AddViolationAt(path.Key(key), "tag name must consist of alphanumeric characters, dots, dashes, slashes and underscores")
}
for _, validate := range opts.ExtraTagKeyValidators {
err.Add(validate(path, key))
}
value := keyValues[key]
if value == "" {
err.AddViolationAt(path.Key(key), "tag value must be non-empty")
}
for _, validate := range opts.ExtraTagValueValidators {
err.Add(validate(path, key, value))
}
}
_, defined := keyValues[mesh_proto.ServiceTag]
if opts.RequireService && !defined {
err.AddViolationAt(path, fmt.Sprintf("mandatory tag %q is missing", mesh_proto.ServiceTag))
}
return err
}
var OnlyServiceTagAllowed = ValidateSelectorsOpts{
RequireAtLeastOneSelector: true,
ValidateTagsOpts: ValidateTagsOpts{
RequireService: true,
ExtraTagsValidators: []TagsValidatorFunc{
func(path validators.PathBuilder, selector map[string]string) validators.ValidationError {
var err validators.ValidationError
_, defined := selector[mesh_proto.ServiceTag]
if len(selector) != 1 || !defined {
err.AddViolationAt(path, fmt.Sprintf("must consist of exactly one tag %q", mesh_proto.ServiceTag))
}
return err
},
},
ExtraTagKeyValidators: []TagKeyValidatorFunc{
func(path validators.PathBuilder, key string) validators.ValidationError {
var err validators.ValidationError
if key != mesh_proto.ServiceTag {
err.AddViolationAt(path.Key(key), fmt.Sprintf("tag %q is not allowed", key))
}
return err
},
},
},
}
func Keys(tags map[string]string) []string {
// sort keys for consistency
var keys []string
for key := range tags {
keys = append(keys, key)
}
sort.Strings(keys)
return keys
}
func ValidateDuration(path validators.PathBuilder, duration *durationpb.Duration) validators.ValidationError {
var errs validators.ValidationError
if duration == nil {
errs.AddViolationAt(path, "must have a positive value")
return errs
}
if err := duration.CheckValid(); err != nil {
errs.AddViolationAt(path, "must have a valid value")
return errs
}
if duration.AsDuration() == 0 {
errs.AddViolationAt(path, "must have a positive value")
}
return errs
}
func ValidateThreshold(path validators.PathBuilder, threshold uint32) validators.ValidationError {
var err validators.ValidationError
if threshold == 0 {
err.AddViolationAt(path, "must have a positive value")
}
return err
}
// ValidatePort validates that port is a valid TCP or UDP port number.
func ValidatePort(path validators.PathBuilder, port uint32) validators.ValidationError {
err := validators.ValidationError{}
if port == 0 || port > 65535 {
err.AddViolationAt(path, "port must be in the range [1, 65535]")
}
return err
}
// ValidateHostname validates a gateway hostname field. A hostname may be one of
// - '*'
// - '*.domain.name'
// - 'domain.name'
func ValidateHostname(path validators.PathBuilder, hostname string) validators.ValidationError {
if hostname == "*" {
return validators.ValidationError{}
}
err := validators.ValidationError{}
if strings.HasPrefix(hostname, "*.") {
if !domainRegexp.MatchString(strings.TrimPrefix(hostname, "*.")) {
err.AddViolationAt(path, "invalid wildcard domain")
}
return err
}
if !domainRegexp.MatchString(hostname) {
err.AddViolationAt(path, "invalid hostname")
}
return err
}
func AllowedValuesHint(values ...string) string {
options := strings.Join(values, ", ")
if len(values) == 0 {
options = "(none)"
}
return fmt.Sprintf("Allowed values: %s", options)
}
func ProtocolValidator(protocols ...string) TagsValidatorFunc {
return func(path validators.PathBuilder, selector map[string]string) validators.ValidationError {
var err validators.ValidationError
v, defined := selector[mesh_proto.ProtocolTag]
if !defined {
err.AddViolationAt(path, "protocol must be specified")
return err
}
for _, protocol := range protocols {
if v == protocol {
return err
}
}
err.AddViolationAt(path.Key(mesh_proto.ProtocolTag), fmt.Sprintf("must be one of the [%s]",
strings.Join(protocols, ", ")))
return err
}
}
// Resource is considered valid if it pass validation of any message
func ValidateAnyResourceYAML(resYAML string, msgs ...proto.Message) error {
var err error
for _, msg := range msgs {
err = ValidateResourceYAML(msg, resYAML)
if err == nil {
return nil
}
}
return err
}
// Resource is considered valid if it pass validation of any message
func ValidateAnyResourceYAMLPatch(resYAML string, msgs ...proto.Message) error {
var err error
for _, msg := range msgs {
err = ValidateResourceYAMLPatch(msg, resYAML)
if err == nil {
return nil
}
}
return err
}
func ValidateResourceYAML(msg proto.Message, resYAML string) error {
json, err := yaml.YAMLToJSON([]byte(resYAML))
if err != nil {
json = []byte(resYAML)
}
if err := util_proto.FromJSON(json, msg); err != nil {
return err
}
if v, ok := msg.(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return err
}
}
return nil
}
func ValidateResourceYAMLPatch(msg proto.Message, resYAML string) error {
json, err := yaml.YAMLToJSON([]byte(resYAML))
if err != nil {
json = []byte(resYAML)
}
return util_proto.FromJSON(json, msg)
}
// SelectorKeyNotInSet returns a TagKeyValidatorFunc that checks the tag key
// is not any one of the given names.
func SelectorKeyNotInSet(keyName ...string) TagKeyValidatorFunc {
set := map[string]struct{}{}
for _, k := range keyName {
set[k] = struct{}{}
}
return TagKeyValidatorFunc(
func(path validators.PathBuilder, key string) validators.ValidationError {
err := validators.ValidationError{}
if _, ok := set[key]; ok {
err.AddViolationAt(
path.Key(key),
fmt.Sprintf("tag name must not be %q", key),
)
}
return err
})
}
func validateName(value string) validators.ValidationError {
var err validators.ValidationError
if !nameCharacterSet.MatchString(value) {
err.AddViolation(
"name",
"invalid characters: must consist of lower case alphanumeric characters, '-', '.' and '_'.",
)
}
return err
}