refactor: Split CRD & Control Plane upgrade logic (#8423)

This change follows on 4f3c374, which split the install logic for CRDs
and the core control plane, by splitting the upgrade logic for the CRDs
and the core control plane.

Signed-off-by: Oliver Gould <ver@buoyant.io>
This commit is contained in:
Oliver Gould 2022-05-04 16:11:48 -07:00 committed by GitHub
parent ddc5a981c8
commit 7d1e4a6953
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 70 additions and 53 deletions

View File

@ -97,11 +97,11 @@ func newCmdInstall() *cobra.Command {
This command provides all Kubernetes configs necessary to install the Linkerd This command provides all Kubernetes configs necessary to install the Linkerd
control plane.`, control plane.`,
Example: ` # Default install. Example: ` # Install CRDs first.
linkerd install | kubectl apply -f - linkerd install --crds | kubectl apply -f -
# Install Linkerd into a non-default namespace. # Install the core control plane.
linkerd install -L linkerdtest | kubectl apply -f - linkerd install | kubectl apply -f -
The installation can be configured by using the --set, --values, --set-string and --set-file flags. The installation can be configured by using the --set, --values, --set-string and --set-file flags.
A full list of configurable values can be found at https://www.github.com/linkerd/linkerd2/tree/main/charts/linkerd2/README.md`, A full list of configurable values can be found at https://www.github.com/linkerd/linkerd2/tree/main/charts/linkerd2/README.md`,
@ -130,7 +130,9 @@ A full list of configurable values can be found at https://www.github.com/linker
} }
if crds { if crds {
if err = installCRDs(cmd.Context(), k8sAPI, os.Stdout, values, options); err != nil { // The CRD chart is not configurable.
// TODO(ver): Error if values have been configured?
if err = installCRDs(cmd.Context(), k8sAPI, os.Stdout); err != nil {
return err return err
} }
@ -175,18 +177,12 @@ func checkNoConfig(ctx context.Context, k8sAPI *k8s.KubernetesAPI) error {
return nil return nil
} }
func installCRDs(ctx context.Context, k8sAPI *k8s.KubernetesAPI, w io.Writer, values *l5dcharts.Values, options valuespkg.Options) error { func installCRDs(ctx context.Context, k8sAPI *k8s.KubernetesAPI, w io.Writer) error {
if err := checkNoConfig(ctx, k8sAPI); err != nil { if err := checkNoConfig(ctx, k8sAPI); err != nil {
return err return err
} }
// Create values override return renderCRDs(w)
valuesOverrides, err := options.MergeValues(nil)
if err != nil {
return err
}
return renderCRDs(w, values, valuesOverrides)
} }
func installControlPlane(ctx context.Context, k8sAPI *k8s.KubernetesAPI, w io.Writer, values *l5dcharts.Values, flags []flag.Flag, options valuespkg.Options) error { func installControlPlane(ctx context.Context, k8sAPI *k8s.KubernetesAPI, w io.Writer, values *l5dcharts.Values, flags []flag.Flag, options valuespkg.Options) error {
@ -303,7 +299,7 @@ func renderChartToBuffer(files []*loader.BufferedFile, values *l5dcharts.Values,
return &buf, vals, nil return &buf, vals, nil
} }
func renderCRDs(w io.Writer, values *l5dcharts.Values, valuesOverrides map[string]interface{}) error { func renderCRDs(w io.Writer) error {
files := []*loader.BufferedFile{ files := []*loader.BufferedFile{
{Name: chartutil.ChartfileName}, {Name: chartutil.ChartfileName},
} }
@ -314,7 +310,12 @@ func renderCRDs(w io.Writer, values *l5dcharts.Values, valuesOverrides map[strin
return err return err
} }
buf, _, err := renderChartToBuffer(files, values, valuesOverrides) // The CRD chart does not take any value configuration.
values, err := l5dcharts.NewValues()
if err != nil {
return err
}
buf, _, err := renderChartToBuffer(files, values, map[string]interface{}{})
if err != nil { if err != nil {
return err return err
} }

View File

@ -265,7 +265,7 @@ func TestRenderCRDs(t *testing.T) {
addFakeTLSSecrets(defaultValues) addFakeTLSSecrets(defaultValues)
var buf bytes.Buffer var buf bytes.Buffer
if err := renderCRDs(&buf, defaultValues, map[string]interface{}{}); err != nil { if err := renderCRDs(&buf); err != nil {
t.Fatalf("Failed to render templates: %v", err) t.Fatalf("Failed to render templates: %v", err)
} }
testDataDiffer.DiffTestdata(t, "install_crds.golden", buf.String()) testDataDiffer.DiffTestdata(t, "install_crds.golden", buf.String())

View File

@ -91,12 +91,21 @@ A full list of configurable values can be found at https://www.github.com/linker
--prune-whitelist=rbac.authorization.k8s.io/v1/clusterrolebinding \ --prune-whitelist=rbac.authorization.k8s.io/v1/clusterrolebinding \
--prune-whitelist=apiregistration.k8s.io/v1/apiservice -f -`, --prune-whitelist=apiregistration.k8s.io/v1/apiservice -f -`,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
if crds {
// The CRD chart is not configurable.
// TODO(ver): Error if values have been configured?
if _, err := upgradeCRDs().WriteTo(os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
return nil
}
k, err := k8sClient(manifests) k, err := k8sClient(manifests)
if err != nil { if err != nil {
return err return err
} }
err = upgradeRunE(cmd.Context(), k, flags, crds, options) if err = upgradeControlPlaneRunE(cmd.Context(), k, flags, options); err != nil {
if err != nil {
fmt.Fprintln(os.Stderr, err.Error()) fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
@ -154,9 +163,8 @@ func makeUpgradeFlags() *pflag.FlagSet {
return upgradeFlags return upgradeFlags
} }
func upgradeRunE(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, crds bool, options valuespkg.Options) error { func upgradeControlPlaneRunE(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, options valuespkg.Options) error {
buf, err := upgradeControlPlane(ctx, k, flags, options)
buf, err := upgrade(ctx, k, flags, crds, options)
if err != nil { if err != nil {
return err return err
} }
@ -167,22 +175,29 @@ func upgradeRunE(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, c
} }
} }
buf.WriteTo(os.Stdout) _, err = buf.WriteTo(os.Stdout)
return err
return nil
} }
func upgrade(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, crds bool, options valuespkg.Options) (bytes.Buffer, error) { func upgradeCRDs() *bytes.Buffer {
var buf bytes.Buffer
if err := renderCRDs(&buf); err != nil {
upgradeErrorf("Could not render upgrade configuration: %s", err)
}
return &buf
}
func upgradeControlPlane(ctx context.Context, k *k8s.KubernetesAPI, flags []flag.Flag, options valuespkg.Options) (*bytes.Buffer, error) {
values, err := loadStoredValues(ctx, k) values, err := loadStoredValues(ctx, k)
if err != nil { if err != nil {
return bytes.Buffer{}, fmt.Errorf("failed to load stored values: %w", err) return nil, fmt.Errorf("failed to load stored values: %w", err)
} }
// If values is still nil, then the linkerd-config-overrides secret was not found. // If values is still nil, then the linkerd-config-overrides secret was not found.
// This means either means that Linkerd was installed with Helm or that the installation // This means either means that Linkerd was installed with Helm or that the installation
// needs to be repaired. // needs to be repaired.
if values == nil { if values == nil {
return bytes.Buffer{}, errors.New( return nil, errors.New(
`Could not find the Linkerd config. If Linkerd was installed with Helm, please `Could not find the Linkerd config. If Linkerd was installed with Helm, please
use Helm to perform upgrades. If Linkerd was not installed with Helm, please use use Helm to perform upgrades. If Linkerd was not installed with Helm, please use
the 'linkerd repair' command to repair the Linkerd config`) the 'linkerd repair' command to repair the Linkerd config`)
@ -190,54 +205,45 @@ the 'linkerd repair' command to repair the Linkerd config`)
err = flag.ApplySetFlags(values, flags) err = flag.ApplySetFlags(values, flags)
if err != nil { if err != nil {
return bytes.Buffer{}, err return nil, err
} }
if values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) { if values.Identity.Issuer.Scheme == string(corev1.SecretTypeTLS) {
for _, flag := range flags { for _, flag := range flags {
if (flag.Name() == "identity-issuer-certificate-file" || flag.Name() == "identity-issuer-key-file") && flag.IsSet() { if (flag.Name() == "identity-issuer-certificate-file" || flag.Name() == "identity-issuer-key-file") && flag.IsSet() {
return bytes.Buffer{}, errors.New("cannot update issuer certificates if you are using external cert management solution") return nil, errors.New("cannot update issuer certificates if you are using external cert management solution")
} }
} }
} }
err = validateValues(ctx, k, values) err = validateValues(ctx, k, values)
if err != nil { if err != nil {
return bytes.Buffer{}, err return nil, err
} }
if !force && values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd { if !force && values.Identity.Issuer.Scheme == k8s.IdentityIssuerSchemeLinkerd {
err = ensureIssuerCertWorksWithAllProxies(ctx, k, values) err = ensureIssuerCertWorksWithAllProxies(ctx, k, values)
if err != nil { if err != nil {
return bytes.Buffer{}, err return nil, err
} }
} }
// Create values override // Create values override
valuesOverrides, err := options.MergeValues(nil) valuesOverrides, err := options.MergeValues(nil)
if err != nil { if err != nil {
return bytes.Buffer{}, err return nil, err
} }
if !isRunAsRoot(valuesOverrides) { if !isRunAsRoot(valuesOverrides) {
err = healthcheck.CheckNodesHaveNonDockerRuntime(ctx, k) err = healthcheck.CheckNodesHaveNonDockerRuntime(ctx, k)
if err != nil { if err != nil {
return bytes.Buffer{}, err return nil, err
} }
} }
// rendering to a buffer and printing full contents of buffer after
// render is complete, to ensure that okStatus prints separately
var buf bytes.Buffer var buf bytes.Buffer
if crds {
if err = renderCRDs(&buf, values, valuesOverrides); err != nil {
upgradeErrorf("Could not render upgrade configuration: %s", err)
}
return buf, nil
}
if err = renderControlPlane(&buf, values, valuesOverrides); err != nil { if err = renderControlPlane(&buf, values, valuesOverrides); err != nil {
upgradeErrorf("Could not render upgrade configuration: %s", err) upgradeErrorf("Could not render upgrade configuration: %s", err)
} }
return buf, nil return &buf, nil
} }
func loadStoredValues(ctx context.Context, k *k8s.KubernetesAPI) (*charts.Values, error) { func loadStoredValues(ctx context.Context, k *k8s.KubernetesAPI) (*charts.Values, error) {

View File

@ -564,27 +564,37 @@ func pathMatch(path []string, template []string) bool {
return true return true
} }
func renderInstall(t *testing.T, values *linkerd2.Values) bytes.Buffer { func renderInstall(t *testing.T, values *linkerd2.Values) *bytes.Buffer {
var installBuf bytes.Buffer var buf bytes.Buffer
if err := renderControlPlane(&installBuf, values, nil); err != nil { if err := renderCRDs(&buf); err != nil {
t.Fatalf("could not render install manifests: %s", err) t.Fatalf("could not render install manifests: %s", err)
} }
return installBuf buf.WriteString("---\n")
if err := renderControlPlane(&buf, values, nil); err != nil {
t.Fatalf("could not render install manifests: %s", err)
}
return &buf
} }
func renderUpgrade(installManifest string, upgradeOpts []flag.Flag, templateOpts valuespkg.Options) (bytes.Buffer, error) { func renderUpgrade(installManifest string, upgradeOpts []flag.Flag, templateOpts valuespkg.Options) (*bytes.Buffer, error) {
k, err := k8s.NewFakeAPIFromManifests([]io.Reader{strings.NewReader(installManifest)}) k, err := k8s.NewFakeAPIFromManifests([]io.Reader{strings.NewReader(installManifest)})
if err != nil { if err != nil {
return bytes.Buffer{}, fmt.Errorf("failed to initialize fake API: %w\n\n%s", err, installManifest) return nil, fmt.Errorf("failed to initialize fake API: %w\n\n%s", err, installManifest)
} }
return upgrade(context.Background(), k, upgradeOpts, false, templateOpts) buf := upgradeCRDs()
buf.WriteString("---\n")
cpbuf, err := upgradeControlPlane(context.Background(), k, upgradeOpts, templateOpts)
if err != nil {
return nil, err
}
buf.Write(cpbuf.Bytes())
return buf, nil
} }
func renderInstallAndUpgrade(t *testing.T, installOpts *charts.Values, upgradeOpts []flag.Flag, templateOpts valuespkg.Options) (bytes.Buffer, bytes.Buffer, error) { func renderInstallAndUpgrade(t *testing.T, installOpts *charts.Values, upgradeOpts []flag.Flag, templateOpts valuespkg.Options) (*bytes.Buffer, *bytes.Buffer, error) {
err := validateValues(context.Background(), nil, installOpts) err := validateValues(context.Background(), nil, installOpts)
if err != nil { if err != nil {
return bytes.Buffer{}, bytes.Buffer{}, fmt.Errorf("failed to validate values: %w", err) return nil, nil, fmt.Errorf("failed to validate values: %w", err)
} }
installBuf := renderInstall(t, installOpts) installBuf := renderInstall(t, installOpts)
upgradeBuf, err := renderUpgrade(installBuf.String(), upgradeOpts, templateOpts) upgradeBuf, err := renderUpgrade(installBuf.String(), upgradeOpts, templateOpts)