func getControlPlaneVersions(info *istioversion.MeshInfo) (map[string]*manifest.IstioDistribution, error) {
	if info == nil {
		return map[string]*manifest.IstioDistribution{}, nil
	}
	in := make([]*manifest.IstioDistribution, len(*info))
	for i, raw := range *info {
		v, err := manifest.IstioDistributionFromString(raw.Info.Version)
		if err != nil {
			return nil, fmt.Errorf("error parsing control's version %s: %v", raw.Info.Version, err)
		}
		in[i] = v
	}
	return findLowestPatchVersionsInGroup(in)
}
construct {version's group -> lowest patch version} map from control plane versions
package checkupgrade
import (
	"errors"
	"fmt"
	"sort"
	"strings"
	istioversion "istio.io/pkg/version"
	"github.com/tetratelabs/getmesh/internal/manifest"
	"github.com/tetratelabs/getmesh/internal/util/logger"
)
var ErrIssueFound = errors.New("version issue found")
func IstioVersion(iv istioversion.Version, manifest *manifest.Manifest) error {
	logger.Infof("[Summary of your Istio mesh]\n")
	printSummary(iv)
	logger.Infof("[GetMesh Check]\n")
	return printgetmeshCheck(iv, manifest)
}
func printSummary(iv istioversion.Version) {
	msg := fmt.Sprintf("active istioctl version: %s\n", iv.ClientVersion.Version)
	if dv := iv.DataPlaneVersion; dv != nil && len(*dv) > 0 {
		versions := make(map[string]int, len(*dv))
		for _, info := range *dv {
			versions[info.IstioVersion]++
		}
		counts := make([]string, 0, len(versions))
		for ver, num := range versions {
			counts = append(counts, fmt.Sprintf("%s (%d proxies)", ver, num))
		}
		sort.Strings(counts)  
		msg += fmt.Sprintf("data plane version: %s\n", strings.Join(counts, ", "))
	}
	if mv := iv.MeshVersion; mv != nil && len(*mv) > 0 {
		vm := make(map[string]struct{}, len(*mv))
		vs := make([]string, 0, len(*mv))
		for _, m := range *mv {
			if _, ok := vm[m.Info.Version]; !ok {
				vm[m.Info.Version] = struct{}{}
				vs = append(vs, m.Info.Version)
			}
		}
		sort.Strings(vs)  
		msg += fmt.Sprintf("control plane version: %s\n", strings.Join(vs, ", "))
	}
	logger.Infof("%s\n", msg)
}
func printgetmeshCheck(iv istioversion.Version, manifest *manifest.Manifest) error {
	var multiple bool
	dpVersions, err := getDataPlaneVersions(iv.DataPlaneVersion)
	if err != nil {
		return fmt.Errorf("collecting data plane versions: %v", err)
	}
	cpVersions, err := getControlPlaneVersions(iv.MeshVersion)
	if err != nil {
		return fmt.Errorf("collecting control plane versions: %v", err)
	}
	if len(dpVersions) > 1 {
		multiple = true
		logger.Infof(getMultipleMinorVersionRunningMsg("data plane", dpVersions))
	}
	if len(cpVersions) > 1 {
		multiple = true
		logger.Infof(getMultipleMinorVersionRunningMsg("control plane", cpVersions))
	}
	if len(cpVersions) == 0 && len(dpVersions) == 0 {
		logger.Infof("nothing to check.\n")
		return nil
	}
	if !multiple &&
		(len(cpVersions) == len(dpVersions) && len(cpVersions) == 1) {
		var cpV, dpV string
		for v := range cpVersions {
			cpV = v
		}
		for v := range dpVersions {
			dpV = v
		}
		if cpV != dpV {
			logger.Infof("- Your data plane running in the minor version %s but control plane in %s\n", dpV, cpV)
		}
	}
	versionToLowestPatches := dpVersions
	for k, next := range cpVersions {
		if prev, ok := versionToLowestPatches[k]; !ok {
			versionToLowestPatches[k] = next
		} else if ok, _ = prev.GreaterThan(next); ok {
			versionToLowestPatches[k] = next
		}
	}
	var okCount int
	for group, v := range versionToLowestPatches {
		msg, ok, err := getLatestPatchInManifestMsg(v, manifest)
		if err != nil {
			return fmt.Errorf("checking the latest patch for %s: %v", group, err)
		}
		if ok {
			okCount++
		}
		logger.Infof(msg)
	}
	if okCount == len(versionToLowestPatches) {
		return nil
	}
	return ErrIssueFound
}
func getLatestPatchInManifestMsg(target *manifest.IstioDistribution, ms *manifest.Manifest) (string, bool, error) {
	tg, err := target.Group()
	if err != nil {
		return "", false, err
	}
	foundLatest, includeSecurityPatch, err := manifest.GetLatestDistribution(target, ms)
	if err != nil {
		return "", false, err
	}
	if foundLatest == nil {
		return fmt.Sprintf("- The minor version %s is no longer supported by getmesh. "+
			"We recommend you use the higher minor versions in \"getmesh list\"\n", tg), false, nil
	}
	if foundLatest.Equal(target) {
		return fmt.Sprintf("- %s is the latest version in %s\n", target.String(), tg), true, nil
	}
	msg := fmt.Sprintf("- There is the available patch for the minor version %s", tg)
	if includeSecurityPatch {
		msg += fmt.Sprintf(" which includes **security upgrades**. We strongly recommend upgrading all %s versions -> %s\n", tg, foundLatest.String())
	} else {
		msg += fmt.Sprintf(". We recommend upgrading all %s versions -> %s\n", tg, foundLatest.String())
	}
	return msg, false, nil
}
func getMultipleMinorVersionRunningMsg(t string, mvs map[string]*manifest.IstioDistribution) string {
	const template = "- Your %s running in multiple minor versions: %s\n"
	vs := make([]string, 0, len(mvs))
	for v := range mvs {
		vs = append(vs, v)
	}
	sort.Strings(vs)
	return fmt.Sprintf(template, t, strings.Join(vs, ", "))
}