Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

upgrade: unit tests #2672

Merged
merged 2 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions cli/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func newCmdInstall() *cobra.Command {

// The base flags are recorded separately s that they can be serialized into
// the configuration in validateAndBuild.
flags := options.recordableFlagSet(pflag.ExitOnError)
flags := options.recordableFlagSet()

cmd := &cobra.Command{
Use: "install [flags]",
Expand All @@ -234,7 +234,7 @@ func newCmdInstall() *cobra.Command {
cmd.PersistentFlags().AddFlagSet(flags)

// Some flags are not available during upgrade, etc.
cmd.PersistentFlags().AddFlagSet(options.installOnlyFlagSet(pflag.ExitOnError))
cmd.PersistentFlags().AddFlagSet(options.installOnlyFlagSet())

return cmd
}
Expand Down Expand Up @@ -262,8 +262,9 @@ func (options *installOptions) validateAndBuild(flags *pflag.FlagSet) (*installV
}

// recordableFlagSet returns flags usable during install or upgrade.
//nolint:unparam
func (options *installOptions) recordableFlagSet(e pflag.ErrorHandling) *pflag.FlagSet {
func (options *installOptions) recordableFlagSet() *pflag.FlagSet {
e := pflag.ExitOnError

flags := pflag.NewFlagSet("install", e)

flags.AddFlagSet(options.proxyConfigOptions.flagSet(e))
Expand Down Expand Up @@ -311,8 +312,8 @@ func (options *installOptions) recordableFlagSet(e pflag.ErrorHandling) *pflag.F

// installOnlyFlagSet includes flags that are only accessible at install-time
// and not at upgrade-time.
func (options *installOptions) installOnlyFlagSet(e pflag.ErrorHandling) *pflag.FlagSet {
flags := pflag.NewFlagSet("install-only", e)
func (options *installOptions) installOnlyFlagSet() *pflag.FlagSet {
flags := pflag.NewFlagSet("install-only", pflag.ExitOnError)

flags.StringVar(
&options.identityOptions.trustDomain, "identity-trust-domain", options.identityOptions.trustDomain,
Expand Down Expand Up @@ -482,6 +483,7 @@ func toPromLogLevel(level string) string {
}
}

// TODO: are `installValues.Configs` and `configs` redundant?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think installValues.Configs is used in the chart/templates/config.yaml helm chart template. Essentially, it's the JSON of configs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my question is more around: If installValues.Configs and configs are just different representations of the same data, can we just keep one copy of that data around, and generate one version from the other on-demand? Any time we have multiple copies of the same data, I get concerned about which version is the source of truth, and what happens if they are not in sync.

func (values *installValues) render(w io.Writer, configs *pb.All) error {
// Render raw values and create chart config
rawValues, err := yaml.Marshal(values)
Expand Down
70 changes: 31 additions & 39 deletions cli/cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func newUpgradeOptionsWithDefaults() *upgradeOptions {

func newCmdUpgrade() *cobra.Command {
options := newUpgradeOptionsWithDefaults()
flags := options.recordableFlagSet(pflag.ExitOnError)
flags := options.recordableFlagSet()

cmd := &cobra.Command{
Use: "upgrade [flags]",
Expand All @@ -43,22 +43,31 @@ Note that the default flag values for this command come from the Linkerd control
plane. The default values displayed in the Flags section below only apply to the
install command.`,
RunE: func(cmd *cobra.Command, args []string) error {
if options.ignoreCluster {
panic("ignore cluster must be unset") // Programmer error.
}

// We need a Kubernetes client to fetch configs and issuer secrets.
k, err := options.newK8s()
c, err := k8s.GetConfig(kubeconfigPath, kubeContext)
if err != nil {
upgradeErrorf("Failed to get kubernetes config: %s", err)
}

k, err := kubernetes.NewForConfig(c)
if err != nil {
upgradeErrorf("Failed to create a kubernetes client: %s", err)
}

values, configs, err := options.validateAndBuild(k, flags)
if err != nil {
return err
upgradeErrorf("Failed to build upgrade configuration: %s", 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
if err = values.render(&buf, configs); err != nil {
upgradeErrorf("Could not render install configuration: %s", err)
upgradeErrorf("Could not render upgrade configuration: %s", err)
}

buf.WriteTo(os.Stdout)
Expand All @@ -74,102 +83,85 @@ install command.`,
}

func (options *upgradeOptions) validateAndBuild(k kubernetes.Interface, flags *pflag.FlagSet) (*installValues, *pb.All, error) {
if err := options.validate(); err != nil {
return nil, nil, err
}

// We fetch the configs directly from kubernetes because we need to be able
// to upgrade/reinstall the control plane when the API is not available; and
// this also serves as a passive check that we have privileges to access this
// control plane.
configs, err := fetchConfigs(k)
if err != nil {
upgradeErrorf("Could not fetch configs from kubernetes: %s", err)
return nil, nil, fmt.Errorf("could not fetch configs from kubernetes: %s", err)
}

// If the install config needs to be repaired--either because it did not
// exist or because it is missing expected fields, repair it.
options.repairInstall(configs.Install)
repairInstall(options.generateUUID, configs.Install)

// We recorded flags during a prior install. If we haven't overridden the
// flag on this upgrade, reset that prior value as if it were specified now.
//
// This implies that the default flag values for the upgrade command come
// from the control-plane, and not from the defaults specified in the FlagSet.
setOptionsFromInstall(flags, configs.GetInstall())

if err = options.validate(); err != nil {
return nil, nil, err
}
setFlagsFromInstall(flags, configs.GetInstall().GetFlags())

// Save off the updated set of flags into the installOptions so it gets
// persisted with the upgraded config.
options.recordFlags(flags)

// Update the configs from the synthesized options.
options.overrideConfigs(configs, map[string]string{})
if options.proxyAutoInject {
configs.GetGlobal().AutoInjectContext = &pb.AutoInjectContext{}
}
configs.GetInstall().Flags = options.recordedFlags

var identity *installIdentityValues
idctx := configs.GetGlobal().GetIdentityContext()
if idctx.GetTrustDomain() == "" || idctx.GetTrustAnchorsPem() == "" {
// If there wasn't an idctx, or if it doesn't specify the required fields, we
// must be upgrading from a version that didn't support identity, so generate it anew...
identity, err = options.installOptions.identityOptions.genValues()
identity, err = options.identityOptions.genValues()
if err != nil {
upgradeErrorf("Unable to generate issuer credentials.\nError: %s", err)
return nil, nil, fmt.Errorf("unable to generate issuer credentials: %s", err)
}
configs.GetGlobal().IdentityContext = identity.toIdentityContext()
} else {
identity, err = fetchIdentityValues(k, options.controllerReplicas, idctx)
if err != nil {
upgradeErrorf("Unable to fetch the existing issuer credentials from Kubernetes.\nError: %s", err)
return nil, nil, fmt.Errorf("unable to fetch the existing issuer credentials from Kubernetes: %s", err)
}
}

// Values have to be generated after any missing identity is generated,
// otherwise it will be missing from the generated configmap.
values, err := options.buildValuesWithoutIdentity(configs)
if err != nil {
upgradeErrorf("Could not build install configuration: %s", err)
return nil, nil, fmt.Errorf("could not build install configuration: %s", err)
}
values.Identity = identity

return values, configs, nil
}

func setOptionsFromInstall(flags *pflag.FlagSet, install *pb.Install) {
for _, i := range install.GetFlags() {
func setFlagsFromInstall(flags *pflag.FlagSet, installFlags []*pb.Install_Flag) {
for _, i := range installFlags {
if f := flags.Lookup(i.GetName()); f != nil && !f.Changed {
f.Value.Set(i.GetValue())
f.Changed = true
}
}
}
func (options *upgradeOptions) overrideConfigs(configs *pb.All, overrideAnnotations map[string]string) {
options.installOptions.overrideConfigs(configs, overrideAnnotations)

if options.proxyAutoInject {
configs.GetGlobal().AutoInjectContext = &pb.AutoInjectContext{}
}
}

func (options *upgradeOptions) newK8s() (kubernetes.Interface, error) {
if options.ignoreCluster {
panic("ignore cluster must be unset") // Programmer error.
}

c, err := k8s.GetConfig(kubeconfigPath, kubeContext)
if err != nil {
return nil, err
}

return kubernetes.NewForConfig(c)
}

func (options *upgradeOptions) repairInstall(install *pb.Install) {
func repairInstall(generateUUID func() string, install *pb.Install) {
if install == nil {
install = &pb.Install{}
}

if install.GetUuid() == "" {
install.Uuid = options.generateUUID()
install.Uuid = generateUUID()
}

// ALWAYS update the CLI version to the most recent.
Expand Down
Loading