mirror of https://github.com/linkerd/linkerd2.git
				
				
				
			cli: restrict repair cmd with the same server version (#5949)
* cli: restrict repair cmd with the same server version Fixes #5917 Currently, When a repair cmd is ran with a CLI that is different from the server version. This means it is possible for the cmd to fail as the `Values` struct changes between versions. This PR updates the cmd to restrict it to the version that the server is on, but as the repair cmd is available only on `2.9.4` - we fall back to an upgrade suggestion for all versions <= 2.8.1 - Suggest to use `2.9.4` for all `2.9.*` versions - Suggest the same version for everything else We also support `--force` flag when users want to skip this restriction. Signed-off-by: Tarun Pothulapati <tarunpothulapati@outlook.com>
This commit is contained in:
		
							parent
							
								
									069eb4e4f6
								
							
						
					
					
						commit
						5ee2ba0c08
					
				|  | @ -1,23 +1,39 @@ | |||
| package cmd | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"regexp" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/golang/protobuf/ptypes" | ||||
| 	"github.com/linkerd/linkerd2/controller/api/public" | ||||
| 	pb "github.com/linkerd/linkerd2/controller/gen/config" | ||||
| 	"github.com/linkerd/linkerd2/pkg/charts/linkerd2" | ||||
| 	"github.com/linkerd/linkerd2/pkg/healthcheck" | ||||
| 	"github.com/linkerd/linkerd2/pkg/k8s" | ||||
| 	"github.com/linkerd/linkerd2/pkg/version" | ||||
| 	"github.com/spf13/cobra" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"sigs.k8s.io/yaml" | ||||
| ) | ||||
| 
 | ||||
| var ( | ||||
| 	// repairNotApplicableVersionRegex matches older versions of Linkerd i.e
 | ||||
| 	// 2.8 and below
 | ||||
| 	repairNotApplicableVersionRegex = regexp.MustCompile(`^stable-2\.[0-8]\.([0-9]+)$`) | ||||
| 
 | ||||
| 	// repairApplicableVersionRegex matches versions 2.9 versions upto 2.9.4
 | ||||
| 	repairApplicableVersionRegex = regexp.MustCompile(`^stable-2\.9\.[0-4]$`) | ||||
| ) | ||||
| 
 | ||||
| // newCmdRepair creates a new cobra command `repair` which re-creates the
 | ||||
| // linkerd-config-overrides secret if it has been deleted.
 | ||||
| func newCmdRepair() *cobra.Command { | ||||
| 
 | ||||
| 	var force bool | ||||
| 	cmd := &cobra.Command{ | ||||
| 		Use:   "repair", | ||||
| 		Short: "Output the secret/linkerd-config-overrides resource if it has been deleted", | ||||
|  | @ -32,82 +48,119 @@ linkerd upgrade.`, | |||
| 		Example: "  linkerd repair | kubectl apply -f -", | ||||
| 		Args:    cobra.NoArgs, | ||||
| 		RunE: func(cmd *cobra.Command, args []string) error { | ||||
| 			k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0) | ||||
| 			err := repair(cmd.Context(), force) | ||||
| 			if err != nil { | ||||
| 				return err | ||||
| 				fmt.Fprintf(os.Stderr, err.Error()+"\n") | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 
 | ||||
| 			// Error out if linkerd-config-overrides already exists.
 | ||||
| 			_, err = k8sAPI.CoreV1().Secrets(controlPlaneNamespace).Get(cmd.Context(), "linkerd-config-overrides", metav1.GetOptions{}) | ||||
| 			if err == nil { | ||||
| 				return errors.New("secret/linkerd-config-overrides already exists. If you need to regenerate this resource, please delete it before proceeding") | ||||
| 			} | ||||
| 
 | ||||
| 			// Load the stored config
 | ||||
| 			config, err := k8sAPI.CoreV1().ConfigMaps(controlPlaneNamespace).Get(cmd.Context(), "linkerd-config", metav1.GetOptions{}) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Failed to load linkerd-config: %s", err) | ||||
| 			} | ||||
| 
 | ||||
| 			values := linkerd2.Values{} | ||||
| 
 | ||||
| 			valuesRaw, ok := config.Data["values"] | ||||
| 			if !ok { | ||||
| 				return errors.New("values not found in linkerd-config") | ||||
| 			} | ||||
| 
 | ||||
| 			err = yaml.Unmarshal([]byte(valuesRaw), &values) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Failed to load values from linkerd-config: %s", err) | ||||
| 			} | ||||
| 
 | ||||
| 			// Reset version fields
 | ||||
| 			err = resetVersion(&values) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Failed to reset version fields in linkerd-config: %s", err) | ||||
| 			} | ||||
| 
 | ||||
| 			clockSkewDuration, err := time.ParseDuration(values.Identity.Issuer.ClockSkewAllowance) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Failed to parse ClockSkewAllowance from linkerd-config: %s", err) | ||||
| 			} | ||||
| 			issuanceLifetime, err := time.ParseDuration(values.Identity.Issuer.IssuanceLifetime) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Failed to parse IssuanceLifetime from linkerd-config: %s", err) | ||||
| 			} | ||||
| 			idCtx := pb.IdentityContext{ | ||||
| 				TrustAnchorsPem:    values.IdentityTrustAnchorsPEM, | ||||
| 				Scheme:             values.Identity.Issuer.Scheme, | ||||
| 				ClockSkewAllowance: ptypes.DurationProto(clockSkewDuration), | ||||
| 				IssuanceLifetime:   ptypes.DurationProto(issuanceLifetime), | ||||
| 				TrustDomain:        values.IdentityTrustDomain, | ||||
| 			} | ||||
| 
 | ||||
| 			// Populate identity values
 | ||||
| 			err = fetchIdentityValues(cmd.Context(), k8sAPI, &idCtx, &values) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Failed to load issuer credentials: %s", err) | ||||
| 			} | ||||
| 
 | ||||
| 			valuesMap, err := values.ToMap() | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Failed to convert Values into a map: %s", err) | ||||
| 			} | ||||
| 
 | ||||
| 			overrides, err := renderOverrides(valuesMap, true) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("Failed to render overrides: %s", err) | ||||
| 			} | ||||
| 
 | ||||
| 			fmt.Print(string(overrides)) | ||||
| 
 | ||||
| 			return nil | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	cmd.Flags().BoolVarP(&force, "force", "f", force, "Force render even if the CLI and control-plane versions don't match") | ||||
| 
 | ||||
| 	return cmd | ||||
| } | ||||
| 
 | ||||
| func repair(ctx context.Context, forced bool) error { | ||||
| 	k8sAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	// Error out if linkerd-config-overrides already exists.
 | ||||
| 	_, err = k8sAPI.CoreV1().Secrets(controlPlaneNamespace).Get(ctx, "linkerd-config-overrides", metav1.GetOptions{}) | ||||
| 	if err == nil { | ||||
| 		return errors.New("secret/linkerd-config-overrides already exists. If you need to regenerate this resource, please delete it before proceeding") | ||||
| 	} | ||||
| 
 | ||||
| 	// Check if the CLI version matches with that of the server
 | ||||
| 	clientVersion := version.Version | ||||
| 	var serverVersion string | ||||
| 	publicClient, err := public.NewExternalClient(ctx, controlPlaneNamespace, k8sAPI) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	serverVersion, err = healthcheck.GetServerVersion(ctx, publicClient) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if !forced && serverVersion != clientVersion { | ||||
| 		// Suggest directly upgrading to 2.9.4 or above for older versions
 | ||||
| 		if repairNotApplicableVersionRegex.Match([]byte(serverVersion)) { | ||||
| 			return fmt.Errorf("repair command is only applicable to 2.9 control-plane versions. Please try upgrading to the latest supported versions of Linkerd i.e 2.9.4 and above") | ||||
| 		} | ||||
| 
 | ||||
| 		// Suggest 2.9.4 CLI version for all 2.9 server versions upto 2.9.4
 | ||||
| 		if repairApplicableVersionRegex.Match([]byte(serverVersion)) { | ||||
| 			return fmt.Errorf("Please run the repair command with a `stable-2.9.4` CLI.\nRun `curl -sL https://run.linkerd.io/install | LINKERD2_VERSION=\"stable-2.9.4\" sh` to install the server version of the CLI") | ||||
| 		} | ||||
| 
 | ||||
| 		// Suggest server version for everything else. This includes all edge versions
 | ||||
| 		return fmt.Errorf("Please run the repair command with a CLI that has the same version as the control plane.\nRun `curl -sL https://run.linkerd.io/install | LINKERD2_VERSION=\"%s\" sh` to install the server version of the CLI", serverVersion) | ||||
| 	} | ||||
| 
 | ||||
| 	// Load the stored config
 | ||||
| 	config, err := k8sAPI.CoreV1().ConfigMaps(controlPlaneNamespace).Get(ctx, "linkerd-config", metav1.GetOptions{}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to load linkerd-config: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	values := linkerd2.Values{} | ||||
| 
 | ||||
| 	valuesRaw, ok := config.Data["values"] | ||||
| 	if !ok { | ||||
| 		return errors.New("values not found in linkerd-config") | ||||
| 	} | ||||
| 
 | ||||
| 	err = yaml.Unmarshal([]byte(valuesRaw), &values) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to load values from linkerd-config: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	err = resetVersion(&values) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to reset version fields in linkerd-config: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	clockSkewDuration, err := time.ParseDuration(values.Identity.Issuer.ClockSkewAllowance) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to parse ClockSkewAllowance from linkerd-config: %s", err) | ||||
| 	} | ||||
| 	issuanceLifetime, err := time.ParseDuration(values.Identity.Issuer.IssuanceLifetime) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to parse IssuanceLifetime from linkerd-config: %s", err) | ||||
| 	} | ||||
| 	idCtx := pb.IdentityContext{ | ||||
| 		TrustAnchorsPem:    values.IdentityTrustAnchorsPEM, | ||||
| 		Scheme:             values.Identity.Issuer.Scheme, | ||||
| 		ClockSkewAllowance: ptypes.DurationProto(clockSkewDuration), | ||||
| 		IssuanceLifetime:   ptypes.DurationProto(issuanceLifetime), | ||||
| 		TrustDomain:        values.IdentityTrustDomain, | ||||
| 	} | ||||
| 
 | ||||
| 	// Populate identity values
 | ||||
| 	err = fetchIdentityValues(ctx, k8sAPI, &idCtx, &values) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to load issuer credentials: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	valuesMap, err := values.ToMap() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to convert Values into a map: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	overrides, err := renderOverrides(valuesMap, true) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("Failed to render overrides: %s", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Print(string(overrides)) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // resetVersion sets all linkerd version fields to the chart's default version
 | ||||
| // to ensure that these fields will be absent from the overrides secret.
 | ||||
| // This is important because `linkerd repair` will likely be run from a
 | ||||
|  | @ -119,8 +172,19 @@ func resetVersion(values *linkerd2.Values) error { | |||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	values.DebugContainer.Image.Version = defaults.DebugContainer.Image.Version | ||||
| 	values.Proxy.Image.Version = defaults.Proxy.Image.Version | ||||
| 
 | ||||
| 	if values.DebugContainer != nil && values.DebugContainer.Image != nil { | ||||
| 		values.DebugContainer.Image.Version = defaults.DebugContainer.Image.Version | ||||
| 	} | ||||
| 
 | ||||
| 	if values.Proxy != nil && values.Proxy.Image != nil { | ||||
| 		values.Proxy.Image.Version = defaults.Proxy.Image.Version | ||||
| 	} | ||||
| 
 | ||||
| 	if values.ProxyInit != nil && values.ProxyInit.Image != nil { | ||||
| 		values.ProxyInit.Image.Version = defaults.ProxyInit.Image.Version | ||||
| 	} | ||||
| 
 | ||||
| 	values.CliVersion = defaults.CliVersion | ||||
| 	values.ControllerImageVersion = defaults.ControllerImageVersion | ||||
| 	values.LinkerdVersion = defaults.LinkerdVersion | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue