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

cli: Introduce an upgrade command #2564

Merged
merged 12 commits into from
Apr 1, 2019
11 changes: 8 additions & 3 deletions cli/cmd/inject.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,16 @@ sub-folders, or coming from stdin.`,
},
}

cmd.PersistentFlags().AddFlagSet(options.proxyConfigOptions.flagSet(pflag.ExitOnError))
cmd.PersistentFlags().BoolVar(
flags := options.proxyConfigOptions.flagSet(pflag.ExitOnError)
flags.BoolVar(
&options.disableIdentity, "disable-identity", options.disableIdentity,
"Disables resources from participating in TLS identity",
)
flags.BoolVar(
&options.ignoreCluster, "ignore-cluster", options.ignoreCluster,
"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)",
)
cmd.PersistentFlags().AddFlagSet(flags)

return cmd
}
Expand Down Expand Up @@ -295,7 +300,7 @@ func (options *injectOptions) fetchConfigsOrDefault() (*config.All, error) {
// overrideConfigs uses command-line overrides to update the provided configs.
// the overrideAnnotations map keeps track of which configs are overridden, by
// storing the corresponding annotations and values.
func (options *injectOptions) overrideConfigs(configs *config.All, overrideAnnotations map[string]string) {
func (options *proxyConfigOptions) overrideConfigs(configs *config.All, overrideAnnotations map[string]string) {
if options.linkerdVersion != "" {
configs.Global.Version = options.linkerdVersion
}
Expand Down
2 changes: 1 addition & 1 deletion cli/cmd/inject_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func testUninjectAndInject(t *testing.T, tc testCase) {
}

func testInstallConfig() *pb.All {
_, c, err := testInstallOptions().validateAndBuild()
_, c, err := testInstallOptions().validateAndBuild(nil)
if err != nil {
log.Fatalf("test install options must be valid: %s", err)
}
Expand Down
128 changes: 71 additions & 57 deletions cli/cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,32 +201,61 @@ func newInstallIdentityOptionsWithDefaults() *installIdentityOptions {

func newCmdInstall() *cobra.Command {
options := newInstallOptionsWithDefaults()
flags := options.flagSet(pflag.ExitOnError)

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

cmd := &cobra.Command{
Use: "install [flags]",
Short: "Output Kubernetes configs to install Linkerd",
Long: "Output Kubernetes configs to install Linkerd.",
RunE: func(cmd *cobra.Command, args []string) error {
options.recordFlags(flags)
if !options.ignoreCluster {
exitIfClusterExists()
}

values, configs, err := options.validateAndBuild()
values, configs, err := options.validateAndBuild(flags)
if err != nil {
return err
}

return values.render(os.Stdout, configs)
},
}

cmd.PersistentFlags().AddFlagSet(flags)

// Issuer flags are currently only supported on the initial install.
cmd.PersistentFlags().AddFlagSet(options.issuerFlagSet(pflag.ExitOnError))
// Some flags are not available during upgrade, etc.
cmd.PersistentFlags().AddFlagSet(options.installOnlyFlagSet(pflag.ExitOnError))

return cmd
}

func (options *installOptions) flagSet(e pflag.ErrorHandling) *pflag.FlagSet {
func (options *installOptions) validateAndBuild(flags *pflag.FlagSet) (*installValues, *pb.All, error) {
if err := options.validate(); err != nil {
return nil, nil, err
}
options.recordFlags(flags)

identityValues, err := options.identityOptions.validateAndBuild()
if err != nil {
return nil, nil, err
}

configs := options.configs(identityValues.toIdentityContext())

values, err := options.buildValuesWithoutIdentity(configs)
if err != nil {
return nil, nil, err
}
values.Identity = identityValues

return values, configs, nil
}

// recordableFlagSet returns flags usable during install or upgrade.
func (options *installOptions) recordableFlagSet(e pflag.ErrorHandling) *pflag.FlagSet {
flags := pflag.NewFlagSet("install", e)

flags.AddFlagSet(options.proxyConfigOptions.flagSet(e))
Expand Down Expand Up @@ -259,12 +288,18 @@ func (options *installOptions) flagSet(e pflag.ErrorHandling) *pflag.FlagSet {
&options.identityOptions.issuanceLifetime, "identity-issuance-lifetime", options.identityOptions.issuanceLifetime,
"The amount of time for which the Identity issuer should certify identity",
)
flags.DurationVar(
&options.identityOptions.clockSkewAllowance, "identity-clock-skew-allowance", options.identityOptions.clockSkewAllowance,
"The amount of time to allow for clock skew within a Linkerd cluster",
)

return flags
}

func (options *installOptions) issuerFlagSet(e pflag.ErrorHandling) *pflag.FlagSet {
flags := pflag.NewFlagSet("issuer", e)
// installOnlyFlagSet includes flags that are only accessible at install-time
// and not at upgrade-time.
func (options *installOptions) installOnlyFlagSet(e pflag.ErrorHandling) *pflag.FlagSet {
olix0r marked this conversation as resolved.
Show resolved Hide resolved
flags := options.recordableFlagSet(e)

flags.StringVar(
&options.identityOptions.trustDomain, "identity-trust-domain", options.identityOptions.trustDomain,
Expand All @@ -282,13 +317,10 @@ func (options *installOptions) issuerFlagSet(e pflag.ErrorHandling) *pflag.FlagS
&options.identityOptions.keyPEMFile, "identity-issuer-key-file", options.identityOptions.keyPEMFile,
"A path to a PEM-encoded file containing the Linkerd Identity issuer private key (generated by default)",
)
flags.DurationVar(
&options.identityOptions.clockSkewAllowance, "identity-clock-skew-allowance", options.identityOptions.clockSkewAllowance,
"The amount of time to allow for clock skew within a Linkerd cluster",
)
flags.DurationVar(
&options.identityOptions.issuanceLifetime, "identity-issuance-lifetime", options.identityOptions.issuanceLifetime,
"The amount of time for which the Identity issuer should certify identity",

flags.BoolVar(
&options.ignoreCluster, "ignore-cluster", options.ignoreCluster,
"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)",
)

return flags
Expand Down Expand Up @@ -331,26 +363,6 @@ func (options *installOptions) validate() error {
return errors.New("--proxy-log-level must not be empty")
}

if !options.ignoreCluster {
exists, err := linkerdConfigAlreadyExistsInCluster()
if err != nil {
fmt.Fprintln(os.Stderr, "Unable to connect to a Kubernetes cluster to check for configuration. If this expected, use the --ignore-cluster flag.")
os.Exit(1)
}
if exists {
fmt.Fprintln(os.Stderr, "You are already running a control plane. If you would like to ignore its configuration, use the --ignore-cluster flag.")
os.Exit(1)
}
}

return nil
}

func (options *installOptions) validateAndBuild() (*installValues, *pb.All, error) {
if err := options.validate(); err != nil {
return nil, nil, err
}

if options.highAvailability {
if options.controllerReplicas == defaultControllerReplicas {
options.controllerReplicas = defaultHAControllerReplicas
Expand All @@ -366,16 +378,14 @@ func (options *installOptions) validateAndBuild() (*installValues, *pb.All, erro
}

options.identityOptions.replicas = options.controllerReplicas
identityValues, err := options.identityOptions.validateAndBuild()
if err != nil {
return nil, nil, err
}

configs := options.configs(identityValues.toIdentityContext())
return nil
}

func (options *installOptions) buildValuesWithoutIdentity(configs *pb.All) (*installValues, error) {
globalJSON, proxyJSON, installJSON, err := config.ToJSON(configs)
if err != nil {
return nil, nil, err
return nil, err
}

values := &installValues{
Expand Down Expand Up @@ -410,8 +420,6 @@ func (options *installOptions) validateAndBuild() (*installValues, *pb.All, erro
Proxy: proxyJSON,
Install: installJSON,
},

Identity: identityValues,
}

if options.highAvailability {
Expand All @@ -438,7 +446,7 @@ func (options *installOptions) validateAndBuild() (*installValues, *pb.All, erro
}
}

return values, configs, nil
return values, nil
}

func toPromLogLevel(level string) string {
Expand Down Expand Up @@ -622,42 +630,48 @@ func (options *installOptions) proxyConfig() *pb.Proxy {
}
}

// linkerdConfigAlreadyExistsInCluster checks the kubernetes API to determine
// whether a config exists.
// exitIfClusterExists checks the kubernetes API to determine
// whether a config exists and exits if it does exist or if an error is
// encountered.
//
// This bypasses the public API so that public API errors cannot cause us to
// misdiagnose a controller error to indicate that no control plane exists.
//
// If we cannot determine whether the configuration exists, an error is returned.
func linkerdConfigAlreadyExistsInCluster() (bool, error) {
api, err := k8s.NewAPI(kubeconfigPath, kubeContext)
func exitIfClusterExists() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Other than the error handling, this looks very similar to the newK8s() and fetchConfigs() in upgrade.go. If we can remove the dependency on upgradeOption(), can we re-use them here?

 func exitIfClusterExists() {
-       kubeConfig, err := k8s.GetConfig(kubeconfigPath, kubeContext)
+       k, err := newK8s()
        if err != nil {
                fmt.Fprintln(os.Stderr, "Unable to build a Kubernetes client to check for configuration. If this expected, use the --ignore-cluster flag.")
                fmt.Fprintf(os.Stderr, "Error: %s\n", err)
                os.Exit(1)
        }

-       k, err := kubernetes.NewForConfig(kubeConfig)
+       _, err = fetchConfigs(k)
        if err != nil {
-               fmt.Fprintln(os.Stderr, "Unable to build a Kubernetes client to check for configuration. If this expected, use the --ignore-cluster flag.")
-               fmt.Fprintf(os.Stderr, "Error: %s\n", err)
-               os.Exit(1)
-       }
-
-       c := k.CoreV1().ConfigMaps(controlPlaneNamespace)
-       if _, err = c.Get(k8s.ConfigConfigMapName, metav1.GetOptions{}); err != nil {
                if kerrors.IsNotFound(err) {
                        return
                }

kubeConfig, err := k8s.GetConfig(kubeconfigPath, kubeContext)
if err != nil {
return false, err
fmt.Fprintln(os.Stderr, "Unable to build a Kubernetes client to check for configuration. If this expected, use the --ignore-cluster flag.")
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}

k, err := kubernetes.NewForConfig(api.Config)
k, err := kubernetes.NewForConfig(kubeConfig)
if err != nil {
return false, err
fmt.Fprintln(os.Stderr, "Unable to build a Kubernetes client to check for configuration. If this expected, use the --ignore-cluster flag.")
olix0r marked this conversation as resolved.
Show resolved Hide resolved
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}

c := k.CoreV1().ConfigMaps(controlPlaneNamespace)
if _, err = c.Get(k8s.ConfigConfigMapName, metav1.GetOptions{}); err != nil {
if kerrors.IsNotFound(err) {
return false, nil
return
}

return false, err
fmt.Fprintln(os.Stderr, "Unable to build a Kubernetes client to check for configuration. If this expected, use the --ignore-cluster flag.")
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}

return true, nil
fmt.Fprintln(os.Stderr, "Linkerd has already been installed on your cluster in the linkerd namespace. Please run upgrade if you'd like to update this installation. Otherwise, use the --ignore-cluster flag.")
os.Exit(1)
}

func (idopts *installIdentityOptions) validate() error {
if idopts == nil {
return nil
}

if idopts.trustDomain == "" {
if idopts.trustDomain != "" {
if errs := validation.IsDNS1123Subdomain(idopts.trustDomain); len(errs) > 0 {
return fmt.Errorf("invalid trust domain '%s': %s", idopts.trustDomain, errs[0])
}
Expand Down
10 changes: 5 additions & 5 deletions cli/cmd/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

func TestRender(t *testing.T) {
defaultOptions := testInstallOptions()
defaultValues, defaultConfig, err := defaultOptions.validateAndBuild()
defaultValues, defaultConfig, err := defaultOptions.validateAndBuild(nil)
if err != nil {
t.Fatalf("Unexpected error validating options: %v", err)
}
Expand Down Expand Up @@ -53,7 +53,7 @@ func TestRender(t *testing.T) {
haOptions := testInstallOptions()
haOptions.recordedFlags = []*config.Install_Flag{{Name: "ha", Value: "true"}}
haOptions.highAvailability = true
haValues, haConfig, _ := haOptions.validateAndBuild()
haValues, haConfig, _ := haOptions.validateAndBuild(nil)

haWithOverridesOptions := testInstallOptions()
haWithOverridesOptions.recordedFlags = []*config.Install_Flag{
Expand All @@ -66,12 +66,12 @@ func TestRender(t *testing.T) {
haWithOverridesOptions.controllerReplicas = 2
haWithOverridesOptions.proxyCPURequest = "400m"
haWithOverridesOptions.proxyMemoryRequest = "300Mi"
haWithOverridesValues, haWithOverridesConfig, _ := haWithOverridesOptions.validateAndBuild()
haWithOverridesValues, haWithOverridesConfig, _ := haWithOverridesOptions.validateAndBuild(nil)

noInitContainerOptions := testInstallOptions()
noInitContainerOptions.recordedFlags = []*config.Install_Flag{{Name: "linkerd-cni-enabled", Value: "true"}}
noInitContainerOptions.noInitContainer = true
noInitContainerValues, noInitContainerConfig, _ := noInitContainerOptions.validateAndBuild()
noInitContainerValues, noInitContainerConfig, _ := noInitContainerOptions.validateAndBuild(nil)

noInitContainerWithProxyAutoInjectOptions := testInstallOptions()
noInitContainerWithProxyAutoInjectOptions.recordedFlags = []*config.Install_Flag{
Expand All @@ -80,7 +80,7 @@ func TestRender(t *testing.T) {
}
noInitContainerWithProxyAutoInjectOptions.noInitContainer = true
noInitContainerWithProxyAutoInjectOptions.proxyAutoInject = true
noInitContainerWithProxyAutoInjectValues, noInitContainerWithProxyAutoInjectConfig, _ := noInitContainerWithProxyAutoInjectOptions.validateAndBuild()
noInitContainerWithProxyAutoInjectValues, noInitContainerWithProxyAutoInjectConfig, _ := noInitContainerWithProxyAutoInjectOptions.validateAndBuild(nil)

testCases := []struct {
values *installValues
Expand Down
6 changes: 1 addition & 5 deletions cli/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func init() {
RootCmd.AddCommand(newCmdTap())
RootCmd.AddCommand(newCmdTop())
RootCmd.AddCommand(newCmdUninject())
RootCmd.AddCommand(newCmdUpgrade())
RootCmd.AddCommand(newCmdVersion())
}

Expand Down Expand Up @@ -300,10 +301,5 @@ func (options *proxyConfigOptions) flagSet(e pflag.ErrorHandling) *pflag.FlagSet
flags.MarkDeprecated("proxy-memory", "use --proxy-memory-request instead")
flags.MarkDeprecated("proxy-cpu", "use --proxy-cpu-request instead")

flags.BoolVar(
&options.ignoreCluster, "ignore-cluster", options.ignoreCluster,
"Ignore the current Kubernetes cluster when checking for existing cluster configuration (default false)",
)

return flags
}
Loading