mirror of https://github.com/linkerd/linkerd2.git
Validate service profiles in all namespaces (#2237)
Fixes #2220 The service profile validation which is part of `linkerd check` only validates service profiles in the Linkerd namespace. Due to a recent change, service profiles now can exist in any namespace. Update the logic so that service profiles in all namespaces are validated. Additionally: * Relax validation of service profile names to support external names Signed-off-by: Alex Leong <alex@buoyant.io>
This commit is contained in:
parent
f6e75ec83a
commit
3f333c2860
|
@ -804,14 +804,6 @@ func (hc *HealthChecker) checkCanCreate(namespace, group, version, resource stri
|
|||
}
|
||||
|
||||
func (hc *HealthChecker) validateServiceProfiles() error {
|
||||
if hc.clientset == nil {
|
||||
var err error
|
||||
hc.clientset, err = kubernetes.NewForConfig(hc.kubeAPI.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if hc.spClientset == nil {
|
||||
var err error
|
||||
hc.spClientset, err = spclient.NewForConfig(hc.kubeAPI.Config)
|
||||
|
@ -820,22 +812,12 @@ func (hc *HealthChecker) validateServiceProfiles() error {
|
|||
}
|
||||
}
|
||||
|
||||
svcProfiles, err := hc.spClientset.LinkerdV1alpha1().ServiceProfiles(hc.ControlPlaneNamespace).List(meta_v1.ListOptions{})
|
||||
svcProfiles, err := hc.spClientset.LinkerdV1alpha1().ServiceProfiles("").List(meta_v1.ListOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, p := range svcProfiles.Items {
|
||||
service, namespace, err := profiles.ValidateName(p.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = hc.clientset.CoreV1().Services(namespace).Get(service, meta_v1.GetOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("ServiceProfile \"%s\" has unknown service: %s", p.Name, err)
|
||||
}
|
||||
|
||||
// TODO: remove this check once we implement ServiceProfile validation via a
|
||||
// ValidatingAdmissionWebhook
|
||||
result := hc.spClientset.RESTClient().Get().RequestURI(p.GetSelfLink()).Do()
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
|
@ -16,6 +15,7 @@ import (
|
|||
"github.com/linkerd/linkerd2/pkg/util"
|
||||
log "github.com/sirupsen/logrus"
|
||||
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
|
@ -51,7 +51,7 @@ var (
|
|||
minStatus uint32 = 100
|
||||
maxStatus uint32 = 599
|
||||
|
||||
clusterZoneSuffix = []string{"svc", "cluster", "local"}
|
||||
clusterZoneSuffix = "svc.cluster.local"
|
||||
|
||||
errRequestMatchField = errors.New("A request match must have a field set")
|
||||
errResponseMatchField = errors.New("A response match must have a field set")
|
||||
|
@ -332,9 +332,9 @@ func Validate(data []byte) error {
|
|||
return fmt.Errorf("failed to validate ServiceProfile: %s", err)
|
||||
}
|
||||
|
||||
_, _, err = ValidateName(serviceProfile.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
errs := validation.IsDNS1123Subdomain(serviceProfile.Name)
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("ServiceProfile \"%s\" has invalid name: %s", serviceProfile.Name, errs[0])
|
||||
}
|
||||
|
||||
if len(serviceProfile.Spec.Routes) == 0 {
|
||||
|
@ -388,22 +388,6 @@ func Validate(data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ValidateName validates that a ServiceProfile's name is of the form:
|
||||
// <service>.<namespace>.svc.cluster.local
|
||||
func ValidateName(name string) (string, string, error) {
|
||||
nameParts := strings.Split(name, ".")
|
||||
if len(nameParts) != 2+len(clusterZoneSuffix) {
|
||||
return "", "", fmt.Errorf("ServiceProfile \"%s\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")", name)
|
||||
}
|
||||
for i, part := range nameParts[2:] {
|
||||
if part != clusterZoneSuffix[i] {
|
||||
return "", "", fmt.Errorf("ServiceProfile \"%s\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")", name)
|
||||
}
|
||||
}
|
||||
|
||||
return nameParts[0], nameParts[1], nil
|
||||
}
|
||||
|
||||
// ValidateRequestMatch validates whether a ServiceProfile RequestMatch has at
|
||||
// least one field set.
|
||||
func ValidateRequestMatch(reqMatch *sp.RequestMatch) error {
|
||||
|
@ -498,7 +482,7 @@ func buildConfig(namespace, service string) *profileTemplateConfig {
|
|||
return &profileTemplateConfig{
|
||||
ServiceNamespace: namespace,
|
||||
ServiceName: service,
|
||||
ClusterZone: strings.Join(clusterZoneSuffix, "."),
|
||||
ClusterZone: clusterZoneSuffix,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -66,25 +66,11 @@ spec:
|
|||
min: 503`,
|
||||
},
|
||||
{
|
||||
err: errors.New("ServiceProfile \"bad.svc.cluster.local\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
|
||||
err: errors.New("ServiceProfile \"^.^\" has invalid name: a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"),
|
||||
sp: `apiVersion: linkerd.io/v1alpha1
|
||||
kind: ServiceProfile
|
||||
metadata:
|
||||
name: bad.svc.cluster.local
|
||||
namespace: linkerd-ns
|
||||
spec:
|
||||
routes:
|
||||
- name: name-1
|
||||
condition:
|
||||
method: GET
|
||||
pathRegex: /route-1`,
|
||||
},
|
||||
{
|
||||
err: errors.New("ServiceProfile \"name.ns.svc.cluster\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
|
||||
sp: `apiVersion: linkerd.io/v1alpha1
|
||||
kind: ServiceProfile
|
||||
metadata:
|
||||
name: name.ns.svc.cluster
|
||||
name: ^.^
|
||||
namespace: linkerd-ns
|
||||
spec:
|
||||
routes:
|
||||
|
@ -434,56 +420,3 @@ spec:
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateName(t *testing.T) {
|
||||
expectations := []struct {
|
||||
err error
|
||||
name string
|
||||
service string
|
||||
namespace string
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
"service.ns.svc.cluster.local",
|
||||
"service",
|
||||
"ns",
|
||||
},
|
||||
{
|
||||
errors.New("ServiceProfile \"bad.name\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
|
||||
"bad.name",
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
errors.New("ServiceProfile \"bad.svc.cluster.local\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
|
||||
"bad.svc.cluster.local",
|
||||
"",
|
||||
"",
|
||||
},
|
||||
{
|
||||
errors.New("ServiceProfile \"service.ns.svc.cluster.foo\" has invalid name (must be \"<service>.<namespace>.svc.cluster.local\")"),
|
||||
"service.ns.svc.cluster.foo",
|
||||
"",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for id, exp := range expectations {
|
||||
t.Run(fmt.Sprintf("%d", id), func(t *testing.T) {
|
||||
service, namespace, err := ValidateName(exp.name)
|
||||
if service != exp.service {
|
||||
t.Fatalf("Unexpected service (Expected: %s, Got: %s)", exp.service, service)
|
||||
}
|
||||
if namespace != exp.namespace {
|
||||
t.Fatalf("Unexpected namespace (Expected: %s, Got: %s)", exp.namespace, namespace)
|
||||
}
|
||||
if err != nil || exp.err != nil {
|
||||
if (err == nil && exp.err != nil) ||
|
||||
(err != nil && exp.err == nil) ||
|
||||
(err.Error() != exp.err.Error()) {
|
||||
t.Fatalf("Unexpected error (Expected: %s, Got: %s)", exp.err, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue