diff --git a/multicluster/cmd/check.go b/multicluster/cmd/check.go index 622ef9cbe738c..7672e67b4ceba 100644 --- a/multicluster/cmd/check.go +++ b/multicluster/cmd/check.go @@ -176,6 +176,11 @@ func multiclusterCategory(hc *healthChecker, wait time.Duration) *healthcheck.Ca WithHintAnchor("l5d-multicluster-links-are-valid"). Fatal(). WithCheck(func(ctx context.Context) error { return hc.checkLinks(ctx) })) + checkers = append(checkers, + *healthcheck.NewChecker("Link and CLI versions match"). + WithHintAnchor("l5d-multicluster-links-version"). + Warning(). + WithCheck(func(ctx context.Context) error { return hc.checkLinkVersions() })) checkers = append(checkers, *healthcheck.NewChecker("remote cluster access credentials are valid"). WithHintAnchor("l5d-smc-target-clusters-access"). @@ -332,6 +337,30 @@ func (hc *healthChecker) checkLinks(ctx context.Context) error { return healthcheck.VerboseSuccess{Message: strings.Join(linkNames, "\n")} } +func (hc *healthChecker) checkLinkVersions() error { + errors := []error{} + links := []string{} + for _, link := range hc.links { + parts := strings.Split(link.CreatedBy, " ") + if len(parts) == 2 && parts[0] == "linkerd/cli" { + if parts[1] == version.Version { + links = append(links, fmt.Sprintf("\t* %s", link.TargetClusterName)) + } else { + errors = append(errors, fmt.Errorf("* %s: CLI version is %s but Link version is %s", link.TargetClusterName, version.Version, parts[1])) + } + } else { + errors = append(errors, fmt.Errorf("* %s: unable to determine version", link.TargetClusterName)) + } + } + if len(errors) > 0 { + return joinErrors(errors, 2) + } + if len(links) == 0 { + return healthcheck.SkipError{Reason: "no links"} + } + return healthcheck.VerboseSuccess{Message: strings.Join(links, "\n")} +} + func (hc *healthChecker) checkRemoteClusterConnectivity(ctx context.Context) error { errors := []error{} links := []string{} @@ -668,7 +697,7 @@ func (hc *healthChecker) checkIfMirrorServicesHaveEndpoints(ctx context.Context) func (hc *healthChecker) checkForOrphanedServices(ctx context.Context) error { errors := []error{} - selector := fmt.Sprintf("%s, !%s", k8s.MirroredResourceLabel, k8s.MirroredGatewayLabel) + selector := fmt.Sprintf("%s, !%s, %s", k8s.MirroredResourceLabel, k8s.MirroredGatewayLabel, k8s.RemoteClusterNameLabel) mirrorServices, err := hc.KubeAPIClient().CoreV1().Services(metav1.NamespaceAll).List(ctx, metav1.ListOptions{LabelSelector: selector}) if err != nil { return err diff --git a/pkg/multicluster/link.go b/pkg/multicluster/link.go index a54be7024eb9e..5fbe9c47fc111 100644 --- a/pkg/multicluster/link.go +++ b/pkg/multicluster/link.go @@ -39,6 +39,7 @@ type ( Link struct { Name string Namespace string + CreatedBy string TargetClusterName string TargetClusterDomain string TargetClusterLinkerdNamespace string @@ -177,6 +178,7 @@ func NewLink(u unstructured.Unstructured) (Link, error) { return Link{ Name: u.GetName(), Namespace: u.GetNamespace(), + CreatedBy: u.GetAnnotations()[k8s.CreatedByAnnotation], TargetClusterName: targetClusterName, TargetClusterDomain: targetClusterDomain, TargetClusterLinkerdNamespace: targetClusterLinkerdNamespace, @@ -260,6 +262,9 @@ func (l Link) ToUnstructured() (unstructured.Unstructured, error) { "metadata": map[string]interface{}{ "name": l.Name, "namespace": l.Namespace, + "annotations": map[string]string{ + k8s.CreatedByAnnotation: k8s.CreatedByAnnotationValue(), + }, }, "spec": spec, "status": map[string]interface{}{},