Repository: linkerd/linkerd2
Pull request #5033 (2020-10-01 18:00:04)

Edge-20.10.1 changes
## edge-20.10.1

This edge release includes a couple of external contributions towards
cert-manager improved support and Grafana charts fixes, among other
enhancements.

* Changed the type of the injector and tap API secrets to `kubernetes.io/tls`,
  so they can be provisioned by cert-manager (thanks @cypherfox!)
* Fixed the "Kubernetes cluster monitoring" Grafana dashboard that had a few
  charts with incomplete data (thanks @aimbot31!)
* Fixed the `service-mirror` multicluster component so that it retries
  connections to the target cluster's Kubernetes API when it's not reachable,
  instead of blocking
* Increased the proxy's default timeout for DNS resolution to 500ms, as there
  were reports that 100ms was too restrictive


Patch:
diff --git a/CHANGES.md b/CHANGES.md
index 7e82a87db..cefce74c6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,21 @@
 # Changes
 
+## edge-20.10.1
+
+This edge release includes a couple of external contributions towards
+improved cert-manager support and Grafana charts fixes, among other
+enhancements.
+
+* Changed the type of the injector and tap API secrets to `kubernetes.io/tls`,
+  so they can be provisioned by cert-manager (thanks @cypherfox!)
+* Fixed the "Kubernetes cluster monitoring" Grafana dashboard that had a few
+  charts with incomplete data (thanks @aimbot31!)
+* Fixed the `service-mirror` multicluster component so that it retries
+  connections to the target cluster's Kubernetes API when it's not reachable,
+  instead of blocking
+* Increased the proxy's default timeout for DNS resolution to 500ms, as there
+  were reports that 100ms was too restrictive
+
 ## edge-20.9.4
 
 This edge release introduces support for authenticated docker registries and
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/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/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(`