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