From 68b4286ae0e5275e498d028ee38b996fbe78c123 Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Tue, 16 Oct 2018 11:38:42 +0200 Subject: [PATCH] cmd init refactor --- cmd/kubeadm/app/cmd/BUILD | 1 + cmd/kubeadm/app/cmd/init.go | 173 +++++++++++++++++++++++------------- 2 files changed, 114 insertions(+), 60 deletions(-) diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 6bd0a75cee6..1900bc92803 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -27,6 +27,7 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library", "//cmd/kubeadm/app/cmd/options:go_default_library", "//cmd/kubeadm/app/cmd/phases:go_default_library", + "//cmd/kubeadm/app/cmd/phases/workflow:go_default_library", "//cmd/kubeadm/app/cmd/upgrade:go_default_library", "//cmd/kubeadm/app/cmd/util:go_default_library", "//cmd/kubeadm/app/componentconfigs:go_default_library", diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index ea67224aa0b..aa9047fa605 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -30,7 +30,6 @@ import ( "github.com/renstrom/dedent" "github.com/spf13/cobra" flag "github.com/spf13/pflag" - "k8s.io/apimachinery/pkg/util/sets" clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" @@ -38,6 +37,7 @@ import ( kubeadmapiv1beta1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta1" "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" + "k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow" cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" "k8s.io/kubernetes/cmd/kubeadm/app/features" @@ -106,48 +106,71 @@ var ( `))) ) +// initOptions defines all the init options exposed via flags by kubeadm init. +// Please note that this structure includes the public kubeadm config API, but only a subset of the options +// supported by this api will be exposed as a flag. +type initOptions struct { + cfgPath string + skipTokenPrint bool + dryRun bool + featureGatesString string + ignorePreflightErrors []string + bto *options.BootstrapTokenOptions + externalcfg *kubeadmapiv1beta1.InitConfiguration +} + +// initData defines all the runtime information used when running the kubeadm init worklow; +// this data is shared across all the phases that are included in the workflow. +type initData struct { + cfg *kubeadmapi.InitConfiguration + skipTokenPrint bool + dryRun bool + ignorePreflightErrors sets.String +} + // NewCmdInit returns "kubeadm init" command. func NewCmdInit(out io.Writer) *cobra.Command { - externalcfg := &kubeadmapiv1beta1.InitConfiguration{} - kubeadmscheme.Scheme.Default(externalcfg) - - var cfgPath string - var skipTokenPrint bool - var dryRun bool - var featureGatesString string - var ignorePreflightErrors []string - // Create the options object for the bootstrap token-related flags, and override the default value for .Description - bto := options.NewBootstrapTokenOptions() - bto.Description = "The default bootstrap token generated by 'kubeadm init'." + options := newInitOptions() + initRunner := workflow.NewRunner() cmd := &cobra.Command{ Use: "init", Short: "Run this command in order to set up the Kubernetes master.", Run: func(cmd *cobra.Command, args []string) { - var err error - if externalcfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil { - kubeadmutil.CheckErr(err) - } - - ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(ignorePreflightErrors) + c, err := initRunner.InitData() kubeadmutil.CheckErr(err) - err = validation.ValidateMixedArguments(cmd.Flags()) + data := c.(initData) + fmt.Printf("[init] using Kubernetes version: %s\n", data.cfg.KubernetesVersion) + + err = initRunner.Run() kubeadmutil.CheckErr(err) - err = bto.ApplyTo(externalcfg) + // TODO: the code in runInit should be progressively converted in phases; each phase will be exposed + // via the subcommands automatically created by initRunner.BindToCommand + err = runInit(&data, out) kubeadmutil.CheckErr(err) - - i, err := NewInit(cfgPath, externalcfg, ignorePreflightErrorsSet, skipTokenPrint, dryRun) - kubeadmutil.CheckErr(err) - kubeadmutil.CheckErr(i.Run(out)) }, } - AddInitConfigFlags(cmd.PersistentFlags(), externalcfg, &featureGatesString) - AddInitOtherFlags(cmd.PersistentFlags(), &cfgPath, &skipTokenPrint, &dryRun, &ignorePreflightErrors) - bto.AddTokenFlag(cmd.PersistentFlags()) - bto.AddTTLFlag(cmd.PersistentFlags()) + // adds command flags + AddInitConfigFlags(cmd.PersistentFlags(), options.externalcfg, &options.featureGatesString) + AddInitOtherFlags(cmd.PersistentFlags(), &options.cfgPath, &options.skipTokenPrint, &options.dryRun, &options.ignorePreflightErrors) + options.bto.AddTokenFlag(cmd.PersistentFlags()) + options.bto.AddTTLFlag(cmd.PersistentFlags()) + + // initialize the workflow runner with the list of phases + // TODO: add the phases to the runner. e.g. initRunner.AppendPhase(phases.PreflightMaster) + + // sets the data builder function, that will be used by the runner + // both when running the entire workflow or single phases + initRunner.SetDataInitializer(func() (workflow.RunData, error) { + return newInitData(cmd, options) + }) + + // binds the Runner to kubeadm init command by altering + // command help, adding --skip-phases flag and by adding phases subcommands + initRunner.BindToCommand(cmd) return cmd } @@ -220,56 +243,86 @@ func AddInitOtherFlags(flagSet *flag.FlagSet, cfgPath *string, skipTokenPrint, d ) } -// NewInit validates given arguments and instantiates Init struct with provided information. -func NewInit(cfgPath string, externalcfg *kubeadmapiv1beta1.InitConfiguration, ignorePreflightErrors sets.String, skipTokenPrint, dryRun bool) (*Init, error) { +// newInitOptions returns a struct ready for being used for creating cmd init flags. +func newInitOptions() *initOptions { + // initialize the public kubeadm config API by appling defaults + externalcfg := &kubeadmapiv1beta1.InitConfiguration{} + kubeadmscheme.Scheme.Default(externalcfg) - // Either use the config file if specified, or convert the defaults in the external to an internal cfg representation - cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, externalcfg) + // Create the options object for the bootstrap token-related flags, and override the default value for .Description + bto := options.NewBootstrapTokenOptions() + bto.Description = "The default bootstrap token generated by 'kubeadm init'." + + return &initOptions{ + externalcfg: externalcfg, + bto: bto, + } +} + +// newInitData returns a new initData struct to be used for the execution of the kubeadm init workflow. +// This func takes care of validating initOptions passed to the command, and then it converts +// options into the internal InitConfiguration type that is used as input all the phases in the kubeadm init workflow +func newInitData(cmd *cobra.Command, options *initOptions) (initData, error) { + // Re-apply defaults to the public kubeadm API (this will set only values not exposed/not set as a flags) + kubeadmscheme.Scheme.Default(options.externalcfg) + + // Validate standalone flags values and/or combination of flags and then assigns + // validated values to the public kubeadm config API when applicable + var err error + if options.externalcfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, options.featureGatesString); err != nil { + return initData{}, err + } + + ignorePreflightErrorsSet, err := validation.ValidateIgnorePreflightErrors(options.ignorePreflightErrors) + kubeadmutil.CheckErr(err) + + if err = validation.ValidateMixedArguments(cmd.Flags()); err != nil { + return initData{}, err + } + + if err = options.bto.ApplyTo(options.externalcfg); err != nil { + return initData{}, err + } + + // Either use the config file if specified, or convert public kubeadm API to the internal InitConfiguration + // and validates InitConfiguration + cfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(options.cfgPath, options.externalcfg) if err != nil { - return nil, err + return initData{}, err } if err := configutil.VerifyAPIServerBindAddress(cfg.APIEndpoint.AdvertiseAddress); err != nil { - return nil, err + return initData{}, err } - - glog.V(1).Infof("[init] validating feature gates") if err := features.ValidateVersion(features.InitFeatureGates, cfg.FeatureGates, cfg.KubernetesVersion); err != nil { - return nil, err + return initData{}, err } - fmt.Printf("[init] using Kubernetes version: %s\n", cfg.KubernetesVersion) + return initData{ + cfg: cfg, + skipTokenPrint: options.skipTokenPrint, + dryRun: options.dryRun, + ignorePreflightErrors: ignorePreflightErrorsSet, + }, nil +} +// runInit executes master node provisioning +func runInit(i *initData, out io.Writer) error { fmt.Println("[preflight] running pre-flight checks") - - if err := preflight.RunInitMasterChecks(utilsexec.New(), cfg, ignorePreflightErrors); err != nil { - return nil, err + if err := preflight.RunInitMasterChecks(utilsexec.New(), i.cfg, i.ignorePreflightErrors); err != nil { + return err } - if !dryRun { + if !i.dryRun { fmt.Println("[preflight/images] Pulling images required for setting up a Kubernetes cluster") fmt.Println("[preflight/images] This might take a minute or two, depending on the speed of your internet connection") fmt.Println("[preflight/images] You can also perform this action in beforehand using 'kubeadm config images pull'") - if err := preflight.RunPullImagesCheck(utilsexec.New(), cfg, ignorePreflightErrors); err != nil { - return nil, err + if err := preflight.RunPullImagesCheck(utilsexec.New(), i.cfg, i.ignorePreflightErrors); err != nil { + return err } } else { fmt.Println("[preflight/images] Would pull the required images (like 'kubeadm config images pull')") } - return &Init{cfg: cfg, skipTokenPrint: skipTokenPrint, dryRun: dryRun, ignorePreflightErrors: ignorePreflightErrors}, nil -} - -// Init defines struct used by "kubeadm init" command -type Init struct { - cfg *kubeadmapi.InitConfiguration - skipTokenPrint bool - dryRun bool - ignorePreflightErrors sets.String -} - -// Run executes master node provisioning, including certificates, needed static pod manifests, etc. -func (i *Init) Run(out io.Writer) error { - // Get directories to write files to; can be faked if we're dry-running glog.V(1).Infof("[init] Getting certificates directory from configuration") realCertsDir := i.cfg.CertificatesDir @@ -579,8 +632,8 @@ func printFilesIfDryRunning(dryRun bool, manifestDir string) error { } // getWaiter gets the right waiter implementation for the right occasion -func getWaiter(i *Init, client clientset.Interface) apiclient.Waiter { - if i.dryRun { +func getWaiter(ctx *initData, client clientset.Interface) apiclient.Waiter { + if ctx.dryRun { return dryrunutil.NewWaiter() }