Repository: linkerd/linkerd2
Pull request #5023 (2020-09-30 08:25:54)

Update check to use new linkerd-config.values
This branch updates the check functionality to read
the new `linkerd-config.values` which contains the full
Values struct showing the current state of the Linkerd
installation. (being added in #5020 )

This is done by adding a new `FetchCurrentConfiguraiton`
which first tries to get the latest, if not falls back
to the older `linkerd-config` protobuf format.`

Signed-off-by: Tarun Pothulapati <tarunpothulapati@outlook.com>

<!--  Thanks for sending a pull request!

If you already have a well-structured git commit message, chances are GitHub
set the title and description of this PR to the git commit message subject and
body, respectively. If so, you may delete these instructions and submit your PR.

If this is your first time, please read our contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md

The title and description of your Pull Request should match the git commit
subject and body, respectively. Git commit messages are structured as follows:

```
Subject

Problem

Solution

Validation

Fixes #[GitHub issue ID]

DCO Sign off
```

Example git commit message:

```
Introduce Pull Request Template

GitHub's community guidelines recommend a pull request template, the repo was
lacking one.

Introduce a `PULL_REQUEST_TEMPLATE.md` file.

Once merged, the
[Community profile checklist](https://github.com/linkerd/linkerd2/community)
should indicate the repo now provides a pull request template.

Fixes #3321

Signed-off-by: Jane Smith <jane.smith@example.com>
```

Note the git commit message subject becomes the pull request title.

For more details around git commits, see the section on Committing in our
contributor guide:
https://github.com/linkerd/linkerd2/blob/main/CONTRIBUTING.md#committing
-->


alpeb: Eager to see `main` merged here to give this a full try :partying_face: 

Patch:
diff --git a/cli/cmd/install-sp.go b/cli/cmd/install-sp.go
index a46d44af6..33d0ec0bb 100644
--- a/cli/cmd/install-sp.go
+++ b/cli/cmd/install-sp.go
@@ -27,12 +27,12 @@ support, verify "kubectl api-versions" outputs "linkerd.io/v1alpha2".`,
 				return err
 			}
 
-			_, configs, err := healthcheck.FetchLinkerdConfigMap(cmd.Context(), k8sAPI, controlPlaneNamespace)
+			_, values, err := healthcheck.FetchCurrentConfiguration(cmd.Context(), k8sAPI, controlPlaneNamespace)
 			if err != nil {
 				return err
 			}
 
-			clusterDomain := configs.GetGlobal().GetClusterDomain()
+			clusterDomain := values.Global.ClusterDomain
 			if clusterDomain == "" {
 				clusterDomain = defaultClusterDomain
 			}
diff --git a/cli/cmd/install.go b/cli/cmd/install.go
index 3b4906220..fba6ea6b5 100644
--- a/cli/cmd/install.go
+++ b/cli/cmd/install.go
@@ -1124,7 +1124,7 @@ func errIfLinkerdConfigConfigMapExists(ctx context.Context) error {
 		return err
 	}
 
-	_, _, err = healthcheck.FetchLinkerdConfigMap(ctx, kubeAPI, controlPlaneNamespace)
+	_, _, err = healthcheck.FetchCurrentConfiguration(ctx, kubeAPI, controlPlaneNamespace)
 	if err != nil {
 		if kerrors.IsNotFound(err) {
 			return nil
diff --git a/cli/cmd/multicluster.go b/cli/cmd/multicluster.go
index 035911d5d..d872271f0 100644
--- a/cli/cmd/multicluster.go
+++ b/cli/cmd/multicluster.go
@@ -9,9 +9,9 @@ import (
 	"strings"
 
 	"github.com/linkerd/linkerd2/cli/table"
-	configPb "github.com/linkerd/linkerd2/controller/gen/config"
 	pb "github.com/linkerd/linkerd2/controller/gen/public"
 	"github.com/linkerd/linkerd2/pkg/charts"
+	"github.com/linkerd/linkerd2/pkg/charts/linkerd2"
 	"github.com/linkerd/linkerd2/pkg/charts/multicluster"
 	mccharts "github.com/linkerd/linkerd2/pkg/charts/multicluster"
 	"github.com/linkerd/linkerd2/pkg/healthcheck"
@@ -116,18 +116,18 @@ func newLinkOptionsWithDefault() (*linkOptions, error) {
 	}, nil
 }
 
-func getLinkerdConfigMap(ctx context.Context) (*configPb.All, error) {
+func getLinkerdConfigMap(ctx context.Context) (*linkerd2.Values, error) {
 	kubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
 	if err != nil {
 		return nil, err
 	}
 
-	_, global, err := healthcheck.FetchLinkerdConfigMap(ctx, kubeAPI, controlPlaneNamespace)
+	_, values, err := healthcheck.FetchCurrentConfiguration(ctx, kubeAPI, controlPlaneNamespace)
 	if err != nil {
 		return nil, err
 	}
 
-	return global, nil
+	return values, nil
 }
 
 func buildServiceMirrorValues(opts *linkOptions) (*multicluster.Values, error) {
@@ -165,7 +165,7 @@ func buildServiceMirrorValues(opts *linkOptions) (*multicluster.Values, error) {
 
 func buildMulticlusterInstallValues(ctx context.Context, opts *multiclusterInstallOptions) (*multicluster.Values, error) {
 
-	global, err := getLinkerdConfigMap(ctx)
+	values, err := getLinkerdConfigMap(ctx)
 	if err != nil {
 		if kerrors.IsNotFound(err) {
 			return nil, errors.New("you need Linkerd to be installed in order to install multicluster addons")
@@ -193,9 +193,9 @@ func buildMulticlusterInstallValues(ctx context.Context, opts *multiclusterInsta
 	defaults.GatewayProbePort = opts.gatewayProbePort
 	defaults.GatewayNginxImage = opts.gatewayNginxImage
 	defaults.GatewayNginxImageVersion = opts.gatewayNginxVersion
-	defaults.IdentityTrustDomain = global.Global.IdentityContext.TrustDomain
+	defaults.IdentityTrustDomain = values.Global.IdentityTrustDomain
 	defaults.LinkerdNamespace = controlPlaneNamespace
-	defaults.ProxyOutboundPort = global.Proxy.OutboundPort.Port
+	defaults.ProxyOutboundPort = uint32(values.Global.Proxy.Ports.Outbound)
 	defaults.LinkerdVersion = version.Version
 	defaults.RemoteMirrorServiceAccount = opts.remoteMirrorCredentials
 
diff --git a/cli/cmd/profile.go b/cli/cmd/profile.go
index 02fa1a513..2e58db4c1 100644
--- a/cli/cmd/profile.go
+++ b/cli/cmd/profile.go
@@ -105,12 +105,12 @@ func newCmdProfile() *cobra.Command {
 				return err
 			}
 
-			_, configs, err := healthcheck.FetchLinkerdConfigMap(cmd.Context(), k8sAPI, controlPlaneNamespace)
+			_, values, err := healthcheck.FetchCurrentConfiguration(cmd.Context(), k8sAPI, controlPlaneNamespace)
 			if err != nil {
 				return err
 			}
 
-			clusterDomain := configs.GetGlobal().GetClusterDomain()
+			clusterDomain := values.Global.ClusterDomain
 			if clusterDomain == "" {
 				clusterDomain = defaultClusterDomain
 			}
diff --git a/controller/api/destination/profile_translator.go b/controller/api/destination/profile_translator.go
index a0ad49cf8..33579353a 100644
--- a/controller/api/destination/profile_translator.go
+++ b/controller/api/destination/profile_translator.go
@@ -2,10 +2,12 @@ package destination
 
 import (
 	"errors"
+	"fmt"
 	"time"
 
 	"github.com/golang/protobuf/ptypes/duration"
 	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
+	"github.com/linkerd/linkerd2/controller/api/destination/watcher"
 	sp "github.com/linkerd/linkerd2/controller/gen/apis/serviceprofile/v1alpha2"
 	"github.com/linkerd/linkerd2/pkg/profiles"
 	"github.com/linkerd/linkerd2/pkg/util"
@@ -16,23 +18,29 @@ const millisPerDecimilli = 10
 
 // implements the ProfileUpdateListener interface
 type profileTranslator struct {
-	stream pb.Destination_GetProfileServer
-	log    *logging.Entry
+	stream             pb.Destination_GetProfileServer
+	log                *logging.Entry
+	fullyQualifiedName string
 }
 
-func newProfileTranslator(stream pb.Destination_GetProfileServer, log *logging.Entry) *profileTranslator {
+func newProfileTranslator(stream pb.Destination_GetProfileServer, log *logging.Entry, id *watcher.ServiceID, clusterDomain string) *profileTranslator {
+	var fullyQualifiedName string
+	if id != nil {
+		fullyQualifiedName = fmt.Sprintf("%s.%s.svc.%s", id.Name, id.Namespace, clusterDomain)
+	}
 	return &profileTranslator{
-		stream: stream,
-		log:    log.WithField("component", "profile-translator"),
+		stream:             stream,
+		log:                log.WithField("component", "profile-translator"),
+		fullyQualifiedName: fullyQualifiedName,
 	}
 }
 
 func (pt *profileTranslator) Update(profile *sp.ServiceProfile) {
 	if profile == nil {
-		pt.stream.Send(defaultServiceProfile())
+		pt.stream.Send(pt.defaultServiceProfile())
 		return
 	}
-	destinationProfile, err := toServiceProfile(profile)
+	destinationProfile, err := pt.toServiceProfile(profile)
 	if err != nil {
 		pt.log.Error(err)
 		return
@@ -41,10 +49,11 @@ func (pt *profileTranslator) Update(profile *sp.ServiceProfile) {
 	pt.stream.Send(destinationProfile)
 }
 
-func defaultServiceProfile() *pb.DestinationProfile {
+func (pt *profileTranslator) defaultServiceProfile() *pb.DestinationProfile {
 	return &pb.DestinationProfile{
-		Routes:      []*pb.Route{},
-		RetryBudget: defaultRetryBudget(),
+		Routes:             []*pb.Route{},
+		RetryBudget:        defaultRetryBudget(),
+		FullyQualifiedName: pt.fullyQualifiedName,
 	}
 }
 
@@ -70,7 +79,7 @@ func toDuration(d time.Duration) *duration.Duration {
 
 // toServiceProfile returns a Proxy API DestinationProfile, given a
 // ServiceProfile.
-func toServiceProfile(profile *sp.ServiceProfile) (*pb.DestinationProfile, error) {
+func (pt *profileTranslator) toServiceProfile(profile *sp.ServiceProfile) (*pb.DestinationProfile, error) {
 	routes := make([]*pb.Route, 0)
 	for _, route := range profile.Spec.Routes {
 		pbRoute, err := toRoute(profile, route)
@@ -90,9 +99,10 @@ func toServiceProfile(profile *sp.ServiceProfile) (*pb.DestinationProfile, error
 		budget.Ttl = toDuration(ttl)
 	}
 	return &pb.DestinationProfile{
-		Routes:       routes,
-		RetryBudget:  budget,
-		DstOverrides: toDstOverrides(profile.Spec.DstOverrides),
+		Routes:             routes,
+		RetryBudget:        budget,
+		DstOverrides:       toDstOverrides(profile.Spec.DstOverrides),
+		FullyQualifiedName: pt.fullyQualifiedName,
 	}, nil
 }
 
diff --git a/controller/api/destination/server.go b/controller/api/destination/server.go
index d9e3598a6..352753d16 100644
--- a/controller/api/destination/server.go
+++ b/controller/api/destination/server.go
@@ -165,11 +165,6 @@ func (s *server) GetProfile(dest *pb.GetDestination, stream pb.Destination_GetPr
 	}
 	log.Debugf("GetProfile(%+v)", dest)
 
-	// We build up the pipeline of profile updaters backwards, starting from
-	// the translator which takes profile updates, translates them to protobuf
-	// and pushes them onto the gRPC stream.
-	translator := newProfileTranslator(stream, log)
-
 	// The host must be fully-qualified or be an IP address.
 	host, port, err := getHostAndPort(dest.GetPath())
 	if err != nil {
@@ -196,6 +191,7 @@ func (s *server) GetProfile(dest *pb.GetDestination, stream pb.Destination_GetPr
 			// If no service or error are returned, the IP address does not map
 			// to a service. Send the default profile and return the stream
 			// without subscribing for future updates.
+			translator := newProfileTranslator(stream, log, nil, "")
 			translator.Update(nil)
 
 			select {
@@ -215,6 +211,11 @@ func (s *server) GetProfile(dest *pb.GetDestination, stream pb.Destination_GetPr
 		path = dest.GetPath()
 	}
 
+	// We build up the pipeline of profile updaters backwards, starting from
+	// the translator which takes profile updates, translates them to protobuf
+	// and pushes them onto the gRPC stream.
+	translator := newProfileTranslator(stream, log, &service, s.clusterDomain)
+
 	// The adaptor merges profile updates with traffic split updates and
 	// publishes the result to the translator.
 	tsAdaptor := newTrafficSplitAdaptor(translator, service, port, s.clusterDomain)
diff --git a/go.mod b/go.mod
index 4ec3fbe59..6a1717088 100644
--- a/go.mod
+++ b/go.mod
@@ -26,7 +26,7 @@ require (
 	github.com/huandu/xstrings v1.2.0 // indirect
 	github.com/imdario/mergo v0.3.7
 	github.com/julienschmidt/httprouter v1.2.0
-	github.com/linkerd/linkerd2-proxy-api v0.1.13
+	github.com/linkerd/linkerd2-proxy-api v0.1.14
 	github.com/linkerd/linkerd2-proxy-init v1.3.6
 	github.com/mattn/go-isatty v0.0.12
 	github.com/mattn/go-runewidth v0.0.2
diff --git a/go.sum b/go.sum
index cb4e7d677..1d8329544 100644
--- a/go.sum
+++ b/go.sum
@@ -352,6 +352,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/linkerd/linkerd2-proxy-api v0.1.13 h1:wk0EZBk1zD1cvnRK7myv+exKUQQbykGWZqVhly6Ws8I=
 github.com/linkerd/linkerd2-proxy-api v0.1.13/go.mod h1:Fpew8Tsm1D70GtuYg7km/PYeZRMUqlxIxwCrId/G+oQ=
+github.com/linkerd/linkerd2-proxy-api v0.1.14-0.20200928162511-7fcd01ab736d h1:VI7miCRbT0LdzQM7GajDrgOQk/gQpkpvr0taBgjw1KM=
+github.com/linkerd/linkerd2-proxy-api v0.1.14-0.20200928162511-7fcd01ab736d/go.mod h1:Fpew8Tsm1D70GtuYg7km/PYeZRMUqlxIxwCrId/G+oQ=
+github.com/linkerd/linkerd2-proxy-api v0.1.14 h1:gjVLh175xxPoM/kFMpWScyLVGLhbp1uihajBdvvTYwI=
+github.com/linkerd/linkerd2-proxy-api v0.1.14/go.mod h1:Fpew8Tsm1D70GtuYg7km/PYeZRMUqlxIxwCrId/G+oQ=
 github.com/linkerd/linkerd2-proxy-init v1.3.6 h1:qRJJJFc2hn/fWGeO4pyyZZvtkhLrE68THg+MRUyCphE=
 github.com/linkerd/linkerd2-proxy-init v1.3.6/go.mod h1:M6iaaLLi06ofuIV6x74SDknSFi7VS/MFqa5m+CwHgLY=
 github.com/linkerd/stern v0.0.0-20200928153157-a99050c4c372 h1:o1YYpjHmI8t61ihkD6Rexs2zK+J2M2tT/sExx89TPVo=
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 29685b8f8..0cf7cf17e 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -8,6 +8,7 @@ import (
 	"github.com/golang/protobuf/jsonpb"
 	"github.com/golang/protobuf/proto"
 	pb "github.com/linkerd/linkerd2/controller/gen/config"
+	l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
 	log "github.com/sirupsen/logrus"
 )
 
@@ -96,3 +97,110 @@ func ToJSON(configs *pb.All) (global, proxy, install string, err error) {
 	install, err = m.MarshalToString(configs.GetInstall())
 	return
 }
+
+// ToValues converts configuration into a Values struct, i.e to be consumed by check
+// TODO: Remove this once the newer configuration becomes the default i.e 2.10
+func ToValues(configs *pb.All) *l5dcharts.Values {
+
+	// convert install flags into values
+	values := &l5dcharts.Values{
+		Global: &l5dcharts.Global{
+			CNIEnabled:              configs.GetGlobal().GetCniEnabled(),
+			Namespace:               configs.GetGlobal().GetLinkerdNamespace(),
+			IdentityTrustAnchorsPEM: configs.GetGlobal().GetIdentityContext().GetTrustAnchorsPem(),
+			IdentityTrustDomain:     configs.GetGlobal().GetIdentityContext().GetTrustDomain(),
+			ClusterDomain:           configs.GetGlobal().GetClusterDomain(),
+			LinkerdVersion:          configs.GetGlobal().GetVersion(),
+			Proxy: &l5dcharts.Proxy{
+				Image: &l5dcharts.Image{
+					Name:       configs.GetProxy().GetProxyImage().GetImageName(),
+					PullPolicy: configs.GetProxy().GetProxyImage().GetPullPolicy(),
+					Version:    configs.GetProxy().GetProxyVersion(),
+				},
+				Ports: &l5dcharts.Ports{
+					Control:  int32(configs.GetProxy().GetControlPort().GetPort()),
+					Inbound:  int32(configs.GetProxy().GetInboundPort().GetPort()),
+					Admin:    int32(configs.GetProxy().GetAdminPort().GetPort()),
+					Outbound: int32(configs.GetProxy().GetOutboundPort().GetPort()),
+				},
+				Resources: &l5dcharts.Resources{
+					CPU: l5dcharts.Constraints{
+						Limit:   configs.GetProxy().GetResource().GetLimitCpu(),
+						Request: configs.GetProxy().GetResource().GetRequestCpu(),
+					},
+					Memory: l5dcharts.Constraints{
+						Limit:   configs.GetProxy().GetResource().GetLimitMemory(),
+						Request: configs.GetProxy().GetResource().GetRequestMemory(),
+					},
+				},
+				EnableExternalProfiles: !configs.Proxy.GetDisableExternalProfiles(),
+				DestinationGetNetworks: configs.GetProxy().GetDestinationGetNetworks(),
+				LogFormat:              configs.GetProxy().GetLogFormat(),
+				OutboundConnectTimeout: configs.GetProxy().GetOutboundConnectTimeout(),
+				InboundConnectTimeout:  configs.GetProxy().GetInboundConnectTimeout(),
+			},
+			ProxyInit: &l5dcharts.ProxyInit{
+				IgnoreInboundPorts:  toString(configs.GetProxy().GetIgnoreInboundPorts()),
+				IgnoreOutboundPorts: toString(configs.GetProxy().GetIgnoreOutboundPorts()),
+				Image: &l5dcharts.Image{
+					Name:       configs.GetProxy().GetProxyInitImage().GetImageName(),
+					PullPolicy: configs.GetProxy().GetProxyInitImage().GetPullPolicy(),
+					Version:    configs.GetProxy().GetProxyInitImageVersion(),
+				},
+			},
+		},
+		Identity: &l5dcharts.Identity{
+			Issuer: &l5dcharts.Issuer{
+				Scheme: configs.GetGlobal().GetIdentityContext().GetScheme(),
+			},
+		},
+		OmitWebhookSideEffects: configs.GetGlobal().GetOmitWebhookSideEffects(),
+		DebugContainer: &l5dcharts.DebugContainer{
+			Image: &l5dcharts.Image{
+				Name:       configs.GetProxy().GetDebugImage().GetImageName(),
+				PullPolicy: configs.GetProxy().GetDebugImage().GetPullPolicy(),
+				Version:    configs.GetProxy().GetDebugImageVersion(),
+			},
+		},
+	}
+
+	// for non-primitive types set only if they are not nil
+	if configs.GetGlobal().GetIdentityContext().GetIssuanceLifetime() != nil {
+		values.Identity.Issuer.IssuanceLifetime = configs.GetGlobal().GetIdentityContext().GetIssuanceLifetime().String()
+	}
+	if configs.GetGlobal().GetIdentityContext().GetClockSkewAllowance() != nil {
+		values.Identity.Issuer.ClockSkewAllowance = configs.GetGlobal().GetIdentityContext().GetClockSkewAllowance().String()
+	}
+
+	if configs.GetProxy().GetLogLevel() != nil {
+		values.Global.Proxy.LogLevel = configs.GetProxy().GetLogLevel().String()
+
+	}
+
+	// set HA, and Heartbeat flags as health-check needs them for old config installs
+	for _, flag := range configs.GetInstall().GetFlags() {
+		if flag.GetName() == "ha" && flag.GetValue() == "true" {
+			values.Global.HighAvailability = true
+		}
+
+		if flag.GetName() == "disable-heartbeat" && flag.GetValue() == "true" {
+			values.DisableHeartBeat = true
+		}
+	}
+
+	return values
+}
+
+func toString(portRanges []*pb.PortRange) string {
+
+	var portRangeString string
+	if len(portRanges) > 0 {
+		for i := 0; i < len(portRanges)-1; i++ {
+			portRangeString += portRanges[i].GetPortRange() + ","
+		}
+
+		portRangeString += portRanges[len(portRanges)-1].GetPortRange()
+	}
+
+	return portRangeString
+}
diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go
index eb94a1959..adaef35a6 100644
--- a/pkg/healthcheck/healthcheck.go
+++ b/pkg/healthcheck/healthcheck.go
@@ -15,6 +15,7 @@ import (
 	healthcheckPb "github.com/linkerd/linkerd2/controller/gen/common/healthcheck"
 	configPb "github.com/linkerd/linkerd2/controller/gen/config"
 	pb "github.com/linkerd/linkerd2/controller/gen/public"
+	l5dcharts "github.com/linkerd/linkerd2/pkg/charts/linkerd2"
 	"github.com/linkerd/linkerd2/pkg/config"
 	"github.com/linkerd/linkerd2/pkg/identity"
 	"github.com/linkerd/linkerd2/pkg/issuercerts"
@@ -371,7 +372,7 @@ type HealthChecker struct {
 	apiClient        public.APIClient
 	latestVersions   version.Channels
 	serverVersion    string
-	linkerdConfig    *configPb.All
+	linkerdConfig    *l5dcharts.Values
 	uuid             string
 	issuerCert       *tls.Cred
 	trustAnchors     []*x509.Certificate
@@ -617,8 +618,9 @@ func (hc *HealthChecker) allCategories() []category {
 					fatal:       true,
 					check: func(ctx context.Context) (err error) {
 						hc.uuid, hc.linkerdConfig, err = hc.checkLinkerdConfigConfigMap(ctx)
+
 						if hc.linkerdConfig != nil {
-							hc.CNIEnabled = hc.linkerdConfig.Global.CniEnabled
+							hc.CNIEnabled = hc.linkerdConfig.Global.CNIEnabled
 						}
 						return
 					},
@@ -1385,7 +1387,7 @@ func (hc *HealthChecker) checkMinReplicasAvailable(ctx context.Context) error {
 }
 
 func (hc *HealthChecker) issuerIdentity() string {
-	return fmt.Sprintf("identity.%s.%s", hc.ControlPlaneNamespace, hc.linkerdConfig.Global.IdentityContext.TrustDomain)
+	return fmt.Sprintf("identity.%s.%s", hc.ControlPlaneNamespace, hc.linkerdConfig.Global.IdentityTrustDomain)
 }
 
 // Add adds an arbitrary checker. This should only be used for testing. For
@@ -1572,13 +1574,13 @@ func (hc *HealthChecker) PublicAPIClient() public.APIClient {
 	return hc.apiClient
 }
 
-func (hc *HealthChecker) checkLinkerdConfigConfigMap(ctx context.Context) (string, *configPb.All, error) {
-	cm, configPB, err := FetchLinkerdConfigMap(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
+func (hc *HealthChecker) checkLinkerdConfigConfigMap(ctx context.Context) (string, *l5dcharts.Values, error) {
+	configMap, values, err := FetchCurrentConfiguration(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
 	if err != nil {
 		return "", nil, err
 	}
 
-	return string(cm.GetUID()), configPB, nil
+	return string(configMap.GetUID()), values, nil
 }
 
 // Checks whether the configuration of the linkerd-identity-issuer is correct. This means:
@@ -1587,20 +1589,19 @@ func (hc *HealthChecker) checkLinkerdConfigConfigMap(ctx context.Context) (strin
 // 3. The trust anchors (if scheme == kubernetes.io/tls) in the secret equal the ones in config
 // 4. The certs and key are parsable
 func (hc *HealthChecker) checkCertificatesConfig(ctx context.Context) (*tls.Cred, []*x509.Certificate, error) {
-	_, configPB, err := FetchLinkerdConfigMap(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
+	_, values, err := FetchCurrentConfiguration(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	idctx := configPB.Global.IdentityContext
 	var data *issuercerts.IssuerCertData
 
-	if idctx.Scheme == "" || idctx.Scheme == k8s.IdentityIssuerSchemeLinkerd {
-		data, err = issuercerts.FetchIssuerData(ctx, hc.kubeAPI, idctx.TrustAnchorsPem, hc.ControlPlaneNamespace)
+	if values.Identity.Issuer.Scheme == "" || values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd {
+		data, err = issuercerts.FetchIssuerData(ctx, hc.kubeAPI, values.Global.IdentityTrustAnchorsPEM, hc.ControlPlaneNamespace)
 	} else {
 		data, err = issuercerts.FetchExternalIssuerData(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
 		// ensure trust anchors in config matches what's in the secret
-		if data != nil && strings.TrimSpace(idctx.TrustAnchorsPem) != strings.TrimSpace(data.TrustAnchors) {
+		if data != nil && strings.TrimSpace(values.Global.IdentityTrustAnchorsPEM) != strings.TrimSpace(data.TrustAnchors) {
 			errFormat := "IdentityContext.TrustAnchorsPem does not match %s in %s"
 			err = fmt.Errorf(errFormat, k8s.IdentityIssuerTrustAnchorsNameExternal, k8s.IdentityIssuerSecretName)
 		}
@@ -1623,6 +1624,29 @@ func (hc *HealthChecker) checkCertificatesConfig(ctx context.Context) (*tls.Cred
 	return issuerCreds, anchors, nil
 }
 
+// FetchCurrentConfiguration retrieves the current Linkerd configuration
+func FetchCurrentConfiguration(ctx context.Context, k kubernetes.Interface, controlPlaneNamespace string) (*corev1.ConfigMap, *l5dcharts.Values, error) {
+
+	// Get the linkerd-config values if present
+	configMap, configPb, err := FetchLinkerdConfigMap(ctx, k, controlPlaneNamespace)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	if rawValues := configMap.Data["values"]; rawValues != "" {
+		var fullValues l5dcharts.Values
+		err = yaml.Unmarshal([]byte(rawValues), &fullValues)
+		if err != nil {
+			return nil, nil, err
+		}
+		return configMap, &fullValues, nil
+	}
+
+	// fall back to the older configMap
+	// TODO: remove this once the newer config override secret becomes the default i.e 2.10
+	return configMap, config.ToValues(configPb), nil
+}
+
 func (hc *HealthChecker) fetchProxyInjectorCaBundle(ctx context.Context) ([]*x509.Certificate, error) {
 	mwh, err := hc.getProxyInjectorMutatingWebhook(ctx)
 	if err != nil {
@@ -1846,21 +1870,11 @@ func (hc *HealthChecker) checkClusterRoleBindings(ctx context.Context, shouldExi
 }
 
 func (hc *HealthChecker) isHA() bool {
-	for _, flag := range hc.linkerdConfig.GetInstall().GetFlags() {
-		if flag.GetName() == "ha" && flag.GetValue() == "true" {
-			return true
-		}
-	}
-	return false
+	return hc.linkerdConfig.Global.HighAvailability
 }
 
 func (hc *HealthChecker) isHeartbeatDisabled() bool {
-	for _, flag := range hc.linkerdConfig.GetInstall().GetFlags() {
-		if flag.GetName() == "disable-heartbeat" && flag.GetValue() == "true" {
-			return true
-		}
-	}
-	return false
+	return hc.linkerdConfig.DisableHeartBeat
 }
 
 func (hc *HealthChecker) checkServiceAccounts(ctx context.Context, saNames []string, ns, labelSelector string) error {
@@ -2013,12 +2027,12 @@ func (hc *HealthChecker) checkDataPlaneProxiesCertificate(ctx context.Context) e
 		return err
 	}
 
-	_, configPB, err := FetchLinkerdConfigMap(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
+	_, values, err := FetchCurrentConfiguration(ctx, hc.kubeAPI, hc.ControlPlaneNamespace)
 	if err != nil {
 		return err
 	}
 
-	trustAnchorsPem := configPB.GetGlobal().GetIdentityContext().GetTrustAnchorsPem()
+	trustAnchorsPem := values.Global.IdentityTrustAnchorsPEM
 	offendingPods := []string{}
 	for _, pod := range meshedPods {
 		if strings.TrimSpace(pod.Anchors) != strings.TrimSpace(trustAnchorsPem) {
diff --git a/pkg/healthcheck/healthcheck_multicluster.go b/pkg/healthcheck/healthcheck_multicluster.go
index 72d1a0134..e975b3fc3 100644
--- a/pkg/healthcheck/healthcheck_multicluster.go
+++ b/pkg/healthcheck/healthcheck_multicluster.go
@@ -341,7 +341,7 @@ func (hc *HealthChecker) checkRemoteClusterConnectivity(ctx context.Context) err
 }
 
 func (hc *HealthChecker) checkRemoteClusterAnchors(ctx context.Context) error {
-	localAnchors, err := tls.DecodePEMCertificates(hc.linkerdConfig.Global.IdentityContext.TrustAnchorsPem)
+	localAnchors, err := tls.DecodePEMCertificates(hc.linkerdConfig.Global.IdentityTrustAnchorsPEM)
 	if err != nil {
 		return fmt.Errorf("Cannot parse source trust anchors: %s", err)
 	}
@@ -373,13 +373,13 @@ func (hc *HealthChecker) checkRemoteClusterAnchors(ctx context.Context) error {
 			continue
 		}
 
-		_, cfMap, err := FetchLinkerdConfigMap(ctx, remoteAPI, link.TargetClusterLinkerdNamespace)
+		_, values, err := FetchCurrentConfiguration(ctx, remoteAPI, link.TargetClusterLinkerdNamespace)
 		if err != nil {
 			errors = append(errors, fmt.Sprintf("* %s: unable to fetch anchors: %s", link.TargetClusterName, err))
 			continue
 		}
 
-		remoteAnchors, err := tls.DecodePEMCertificates(cfMap.Global.IdentityContext.TrustAnchorsPem)
+		remoteAnchors, err := tls.DecodePEMCertificates(values.Global.IdentityTrustAnchorsPEM)
 		if err != nil {
 			errors = append(errors, fmt.Sprintf("* %s: cannot parse trust anchors", link.TargetClusterName))
 			continue


Test patch:
diff --git a/controller/api/destination/server_test.go b/controller/api/destination/server_test.go
index f93e5f767..ec1580ac8 100644
--- a/controller/api/destination/server_test.go
+++ b/controller/api/destination/server_test.go
@@ -1,6 +1,7 @@
 package destination
 
 import (
+	"fmt"
 	"testing"
 
 	pb "github.com/linkerd/linkerd2-proxy-api/go/destination"
@@ -11,6 +12,8 @@ import (
 	logging "github.com/sirupsen/logrus"
 )
 
+const fullyQualifiedName = "name1.ns.svc.mycluster.local"
+
 type mockDestinationGetServer struct {
 	util.MockServerStream
 	updatesReceived []*pb.Update
@@ -171,7 +174,7 @@ func TestGet(t *testing.T) {
 		// test.
 		stream.Cancel()
 
-		err := server.Get(&pb.GetDestination{Scheme: "k8s", Path: "name1.ns.svc.mycluster.local:8989"}, stream)
+		err := server.Get(&pb.GetDestination{Scheme: "k8s", Path: fmt.Sprintf("%s:8989", fullyQualifiedName)}, stream)
 		if err != nil {
 			t.Fatalf("Got error: %s", err)
 		}
@@ -213,7 +216,7 @@ func TestGetProfiles(t *testing.T) {
 		stream.Cancel() // See note above on pre-emptive cancellation.
 		err := server.GetProfile(&pb.GetDestination{
 			Scheme:       "k8s",
-			Path:         "name1.ns.svc.mycluster.local:8989",
+			Path:         fmt.Sprintf("%s:8989", fullyQualifiedName),
 			ContextToken: "ns:other",
 		}, stream)
 		if err != nil {
@@ -234,6 +237,12 @@ func TestGetProfiles(t *testing.T) {
 		if len(stream.updates) == 0 || len(stream.updates) > 3 {
 			t.Fatalf("Expected 1 to 3 updates but got %d: %v", len(stream.updates), stream.updates)
 		}
+
+		firstUpdate := stream.updates[0]
+		if firstUpdate.FullyQualifiedName != fullyQualifiedName {
+			t.Fatalf("Expected fully qualified name '%s', but got '%s'", fullyQualifiedName, firstUpdate.FullyQualifiedName)
+		}
+
 		lastUpdate := stream.updates[len(stream.updates)-1]
 		routes := lastUpdate.GetRoutes()
 		if len(routes) != 1 {
@@ -255,7 +264,7 @@ func TestGetProfiles(t *testing.T) {
 		stream.Cancel() // see note above on pre-emptive cancelling
 		err := server.GetProfile(&pb.GetDestination{
 			Scheme:       "k8s",
-			Path:         "name1.ns.svc.mycluster.local:8989",
+			Path:         fmt.Sprintf("%s:8989", fullyQualifiedName),
 			ContextToken: "{\"ns\":\"other\"}",
 		}, stream)
 		if err != nil {
@@ -276,6 +285,12 @@ func TestGetProfiles(t *testing.T) {
 		if len(stream.updates) == 0 || len(stream.updates) > 3 {
 			t.Fatalf("Expected 1 to 3 updates but got: %d: %v", len(stream.updates), stream.updates)
 		}
+
+		firstUpdate := stream.updates[0]
+		if firstUpdate.FullyQualifiedName != fullyQualifiedName {
+			t.Fatalf("Expected fully qualified name '%s', but got '%s'", fullyQualifiedName, firstUpdate.FullyQualifiedName)
+		}
+
 		lastUpdate := stream.updates[len(stream.updates)-1]
 		routes := lastUpdate.GetRoutes()
 		if len(routes) != 1 {
@@ -298,7 +313,7 @@ func TestGetProfiles(t *testing.T) {
 		stream.Cancel()
 		err := server.GetProfile(&pb.GetDestination{
 			Scheme:       "k8s",
-			Path:         "name1.ns.svc.mycluster.local:8989",
+			Path:         fmt.Sprintf("%s:8989", fullyQualifiedName),
 			ContextToken: "{\"ns\":\"client-ns\"}",
 		}, stream)
 		if err != nil {
@@ -334,7 +349,7 @@ func TestGetProfiles(t *testing.T) {
 		stream.Cancel()
 		err := server.GetProfile(&pb.GetDestination{
 			Scheme:       "k8s",
-			Path:         "name1.ns.svc.mycluster.local:8989",
+			Path:         fmt.Sprintf("%s:8989", fullyQualifiedName),
 			ContextToken: "ns:client-ns",
 		}, stream)
 		if err != nil {
diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go
index a380d5096..3c6cee08b 100644
--- a/pkg/healthcheck/healthcheck_test.go
+++ b/pkg/healthcheck/healthcheck_test.go
@@ -11,18 +11,18 @@ import (
 	"testing"
 	"time"
 
-	"github.com/linkerd/linkerd2/pkg/issuercerts"
-	"github.com/linkerd/linkerd2/pkg/tls"
-	"github.com/linkerd/linkerd2/testutil"
-
-	"github.com/golang/protobuf/proto"
 	"github.com/golang/protobuf/ptypes/duration"
 	"github.com/linkerd/linkerd2/controller/api/public"
 	healthcheckPb "github.com/linkerd/linkerd2/controller/gen/common/healthcheck"
 	configPb "github.com/linkerd/linkerd2/controller/gen/config"
 	pb "github.com/linkerd/linkerd2/controller/gen/public"
+	"github.com/linkerd/linkerd2/pkg/charts/linkerd2"
 	"github.com/linkerd/linkerd2/pkg/identity"
+	"github.com/linkerd/linkerd2/pkg/issuercerts"
 	"github.com/linkerd/linkerd2/pkg/k8s"
+	"github.com/linkerd/linkerd2/pkg/tls"
+	"github.com/linkerd/linkerd2/testutil"
+	"google.golang.org/protobuf/proto"
 	corev1 "k8s.io/api/core/v1"
 	k8sErrors "k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -2522,6 +2522,305 @@ data:
 	}
 }
 
+func TestFetchCurrentConfiguration(t *testing.T) {
+	defaultValues, err := linkerd2.NewValues(false)
+
+	if err != nil {
+		t.Fatalf("Unexpected error validating options: %v", err)
+	}
+
+	testCases := []struct {
+		k8sConfigs []string
+		expected   *linkerd2.Values
+		err        error
+	}{
+		{
+			[]string{`
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: linkerd-config
+  namespace: linkerd
+data:
+  global: |
+    {"linkerdNamespace":"linkerd","cniEnabled":false,"version":"install-control-plane-version","identityContext":{"trustDomain":"cluster.local","trustAnchorsPem":"fake-trust-anchors-pem","issuanceLifetime":"86400s","clockSkewAllowance":"20s"}}
+  proxy: |
+    {"proxyImage":{"imageName":"ghcr.io/linkerd/proxy","pullPolicy":"IfNotPresent"},"proxyInitImage":{"imageName":"ghcr.io/linkerd/proxy-init","pullPolicy":"IfNotPresent"},"controlPort":{"port":4190},"ignoreInboundPorts":[],"ignoreOutboundPorts":[],"inboundPort":{"port":4143},"adminPort":{"port":4191},"outboundPort":{"port":4140},"resource":{"requestCpu":"","requestMemory":"","limitCpu":"","limitMemory":""},"proxyUid":"2102","logLevel":{"level":"warn,linkerd=info"},"disableExternalProfiles":true,"proxyVersion":"install-proxy-version","proxy_init_image_version":"v1.3.6","debugImage":{"imageName":"ghcr.io/linkerd/debug","pullPolicy":"IfNotPresent"},"debugImageVersion":"install-debug-version"}
+  install: |
+    {"cliVersion":"dev-undefined","flags":[]}
+  values: |
+    controllerImage: ControllerImage
+    controllerImageVersion: ""
+    controllerReplicas: 1
+    controllerUID: 2103
+    dashboard:
+      replicas: 1
+    debugContainer: null
+    destinationProxyResources: null
+    destinationResources: null
+    disableHeartBeat: false
+    enableH2Upgrade: true
+    enablePodAntiAffinity: false
+    global:
+      cliVersion: CliVersion
+      clusterDomain: cluster.local
+      cniEnabled: false
+      controlPlaneTracing: false
+      controllerComponentLabel: ControllerComponentLabel
+      controllerImageVersion: ControllerImageVersion
+      controllerLogLevel: ControllerLogLevel
+      controllerNamespaceLabel: ControllerNamespaceLabel
+      createdByAnnotation: CreatedByAnnotation
+      enableEndpointSlices: false
+      grafanaUrl: ""
+      highAvailability: false
+      identityTrustDomain: cluster.local
+      imagePullPolicy: ImagePullPolicy
+      imagePullSecrets: null
+      linkerdNamespaceLabel: LinkerdNamespaceLabel
+      linkerdVersion: ""
+      namespace: Namespace
+      prometheusUrl: ""
+      proxy:
+        capabilities: null
+        component: linkerd-controller
+        destinationGetNetworks: DestinationGetNetworks
+        disableIdentity: false
+        disableTap: false
+        enableExternalProfiles: false
+        image:
+          name: ProxyImageName
+          pullPolicy: ImagePullPolicy
+          version: ProxyVersion
+        inboundConnectTimeout: ""
+        isGateway: false
+        logFormat: plain
+        logLevel: warn,linkerd=info
+        opaquePorts: ""
+        outboundConnectTimeout: ""
+        ports:
+          admin: 4191
+          control: 4190
+          inbound: 4143
+          outbound: 4140
+        requireIdentityOnInboundPorts: ""
+        resources: null
+        saMountPath: null
+        trace:
+          collectorSvcAccount: ""
+          collectorSvcAddr: ""
+        uid: 2102
+        waitBeforeExitSeconds: 0
+        workloadKind: deployment
+      proxyContainerName: ProxyContainerName
+      proxyInit:
+        capabilities: null
+        closeWaitTimeoutSecs: 0
+        ignoreInboundPorts: ""
+        ignoreOutboundPorts: ""
+        image:
+          name: ProxyInitImageName
+          pullPolicy: ImagePullPolicy
+          version: ProxyInitVersion
+        resources:
+          cpu:
+            limit: 100m
+            request: 10m
+          memory:
+            limit: 50Mi
+            request: 10Mi
+        saMountPath: null
+        xtMountPath:
+          mountPath: /run
+          name: linkerd-proxy-init-xtables-lock
+          readOnly: false
+      proxyInjectAnnotation: ProxyInjectAnnotation
+      proxyInjectDisabled: ProxyInjectDisabled
+      workloadNamespaceLabel: WorkloadNamespaceLabel
+    grafana:
+      enabled: true
+    heartbeatResources: null
+    heartbeatSchedule: ""
+    identityProxyResources: null
+    identityResources: null
+    installNamespace: true
+    nodeSelector:
+      beta.kubernetes.io/os: linux
+    omitWebhookSideEffects: false
+    prometheus:
+      enabled: true
+      image: PrometheusImage
+    proxyInjectorProxyResources: null
+    proxyInjectorResources: null
+    publicAPIProxyResources: null
+    publicAPIResources: null
+    restrictDashboardPrivileges: false
+    spValidatorProxyResources: null
+    spValidatorResources: null
+    stage: ""
+    tapProxyResources: null
+    tapResources: null
+    tolerations: null
+    tracing:
+      enabled: false
+    webImage: WebImage
+    webProxyResources: null
+    webResources: null
+    webhookFailurePolicy: WebhookFailurePolicy
+`,
+			},
+			&linkerd2.Values{
+				ControllerImage:             "ControllerImage",
+				WebImage:                    "WebImage",
+				ControllerUID:               2103,
+				EnableH2Upgrade:             true,
+				WebhookFailurePolicy:        "WebhookFailurePolicy",
+				OmitWebhookSideEffects:      false,
+				RestrictDashboardPrivileges: false,
+				InstallNamespace:            true,
+				NodeSelector:                defaultValues.NodeSelector,
+				Tolerations:                 defaultValues.Tolerations,
+				Global: &linkerd2.Global{
+					Namespace:                "Namespace",
+					ClusterDomain:            "cluster.local",
+					ImagePullPolicy:          "ImagePullPolicy",
+					CliVersion:               "CliVersion",
+					ControllerComponentLabel: "ControllerComponentLabel",
+					ControllerLogLevel:       "ControllerLogLevel",
+					ControllerImageVersion:   "ControllerImageVersion",
+					ControllerNamespaceLabel: "ControllerNamespaceLabel",
+					WorkloadNamespaceLabel:   "WorkloadNamespaceLabel",
+					CreatedByAnnotation:      "CreatedByAnnotation",
+					ProxyInjectAnnotation:    "ProxyInjectAnnotation",
+					ProxyInjectDisabled:      "ProxyInjectDisabled",
+					LinkerdNamespaceLabel:    "LinkerdNamespaceLabel",
+					ProxyContainerName:       "ProxyContainerName",
+					CNIEnabled:               false,
+					IdentityTrustDomain:      defaultValues.Global.IdentityTrustDomain,
+					Proxy: &linkerd2.Proxy{
+						Component:              "linkerd-controller",
+						DestinationGetNetworks: "DestinationGetNetworks",
+						Image: &linkerd2.Image{
+							Name:       "ProxyImageName",
+							PullPolicy: "ImagePullPolicy",
+							Version:    "ProxyVersion",
+						},
+						LogLevel:  "warn,linkerd=info",
+						LogFormat: "plain",
+						Ports: &linkerd2.Ports{
+							Admin:    4191,
+							Control:  4190,
+							Inbound:  4143,
+							Outbound: 4140,
+						},
+						UID:   2102,
+						Trace: &linkerd2.Trace{},
+					},
+					ProxyInit: &linkerd2.ProxyInit{
+						Image: &linkerd2.Image{
+							Name:       "ProxyInitImageName",
+							PullPolicy: "ImagePullPolicy",
+							Version:    "ProxyInitVersion",
+						},
+						Resources: &linkerd2.Resources{
+							CPU: linkerd2.Constraints{
+								Limit:   "100m",
+								Request: "10m",
+							},
+							Memory: linkerd2.Constraints{
+								Limit:   "50Mi",
+								Request: "10Mi",
+							},
+						},
+						XTMountPath: &linkerd2.VolumeMountPath{
+							MountPath: "/run",
+							Name:      "linkerd-proxy-init-xtables-lock",
+						},
+					},
+				},
+				ControllerReplicas: 1,
+				Dashboard: &linkerd2.Dashboard{
+					Replicas: 1,
+				},
+				Prometheus: linkerd2.Prometheus{
+					"enabled": true,
+					"image":   "PrometheusImage",
+				},
+				Tracing: map[string]interface{}{
+					"enabled": false,
+				},
+				Grafana: defaultValues.Grafana,
+			},
+			nil,
+		},
+		{
+			[]string{`
+kind: ConfigMap
+apiVersion: v1
+metadata:
+  name: linkerd-config
+  namespace: linkerd
+data:
+  global: |
+    {"linkerdNamespace":"ns","identityContext":null, "cniEnabled": true}
+  proxy: |
+    {"proxyImage":{"imageName":"registry", "pullPolicy":"Always"}}
+  install: |
+    {"flags":[{"name":"ha","value":"true"}]}`,
+			},
+			&linkerd2.Values{
+				Global: &linkerd2.Global{
+					Namespace:        "ns",
+					CNIEnabled:       true,
+					HighAvailability: true,
+					Proxy: &linkerd2.Proxy{
+						EnableExternalProfiles: true,
+						Image: &linkerd2.Image{
+							Name:       "registry",
+							PullPolicy: "Always",
+						},
+						LogLevel: "",
+						Ports:    &linkerd2.Ports{},
+						Resources: &linkerd2.Resources{
+							CPU:    linkerd2.Constraints{},
+							Memory: linkerd2.Constraints{},
+						},
+					},
+					ProxyInit: &linkerd2.ProxyInit{
+						Image: &linkerd2.Image{},
+					},
+				},
+				Identity: &linkerd2.Identity{
+					Issuer: &linkerd2.Issuer{},
+				},
+				DebugContainer: &linkerd2.DebugContainer{
+					Image: &linkerd2.Image{},
+				},
+			},
+			nil,
+		},
+	}
+
+	for i, tc := range testCases {
+		tc := tc // pin
+		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
+			clientset, err := k8s.NewFakeAPI(tc.k8sConfigs...)
+			if err != nil {
+				t.Fatalf("Unexpected error: %s", err)
+			}
+
+			_, values, err := FetchCurrentConfiguration(context.Background(), clientset, "linkerd")
+			if !reflect.DeepEqual(err, tc.err) {
+				t.Fatalf("Expected \"%+v\", got \"%+v\"", tc.err, err)
+			}
+
+			if !reflect.DeepEqual(values, tc.expected) {
+				t.Fatalf("Unexpected values:\nExpected:\n%+v\nGot:\n%+v", tc.expected, values)
+			}
+		})
+	}
+}
+
 func getFakeConfigMap(scheme string, issuerCerts *issuercerts.IssuerCertData) string {
 	anchors, _ := json.Marshal(issuerCerts.TrustAnchors)
 	return fmt.Sprintf(`