diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 0ed2081c25c..5376a18cd46 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -87,6 +87,7 @@ go_test( "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/config:go_default_library", + "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/output:go_default_library", "//cmd/kubeadm/app/util/runtime:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library", diff --git a/cmd/kubeadm/app/cmd/join.go b/cmd/kubeadm/app/cmd/join.go index 87670da9e2e..12ae45832cf 100644 --- a/cmd/kubeadm/app/cmd/join.go +++ b/cmd/kubeadm/app/cmd/join.go @@ -20,6 +20,7 @@ import ( "fmt" "io" "os" + "strings" "text/template" "github.com/lithammer/dedent" @@ -127,6 +128,7 @@ type joinOptions struct { controlPlane bool ignorePreflightErrors []string externalcfg *kubeadmapiv1beta2.JoinConfiguration + joinControlPlane *kubeadmapiv1beta2.JoinControlPlane kustomizeDir string } @@ -199,7 +201,7 @@ func NewCmdJoin(out io.Writer, joinOptions *joinOptions) *cobra.Command { Args: cobra.MaximumNArgs(1), } - addJoinConfigFlags(cmd.Flags(), joinOptions.externalcfg) + addJoinConfigFlags(cmd.Flags(), joinOptions.externalcfg, joinOptions.joinControlPlane) addJoinOtherFlags(cmd.Flags(), joinOptions) joinRunner.AppendPhase(phases.NewPreflightPhase()) @@ -211,7 +213,7 @@ func NewCmdJoin(out io.Writer, joinOptions *joinOptions) *cobra.Command { // sets the data builder function, that will be used by the runner // both when running the entire workflow or single phases joinRunner.SetDataInitializer(func(cmd *cobra.Command, args []string) (workflow.RunData, error) { - return newJoinData(cmd, args, joinOptions, out) + return newJoinData(cmd, args, joinOptions, out, kubeadmconstants.GetAdminKubeConfigPath()) }) // binds the Runner to kubeadm join command by altering @@ -222,22 +224,22 @@ func NewCmdJoin(out io.Writer, joinOptions *joinOptions) *cobra.Command { } // addJoinConfigFlags adds join flags bound to the config to the specified flagset -func addJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta2.JoinConfiguration) { +func addJoinConfigFlags(flagSet *flag.FlagSet, cfg *kubeadmapiv1beta2.JoinConfiguration, jcp *kubeadmapiv1beta2.JoinControlPlane) { flagSet.StringVar( &cfg.NodeRegistration.Name, options.NodeName, cfg.NodeRegistration.Name, `Specify the node name.`, ) flagSet.StringVar( - &cfg.ControlPlane.CertificateKey, options.CertificateKey, "", + &jcp.CertificateKey, options.CertificateKey, jcp.CertificateKey, "Use this key to decrypt the certificate secrets uploaded by init.", ) // add control plane endpoint flags to the specified flagset flagSet.StringVar( - &cfg.ControlPlane.LocalAPIEndpoint.AdvertiseAddress, options.APIServerAdvertiseAddress, cfg.ControlPlane.LocalAPIEndpoint.AdvertiseAddress, + &jcp.LocalAPIEndpoint.AdvertiseAddress, options.APIServerAdvertiseAddress, jcp.LocalAPIEndpoint.AdvertiseAddress, "If the node should host a new control plane instance, the IP address the API Server will advertise it's listening on. If not set the default network interface will be used.", ) flagSet.Int32Var( - &cfg.ControlPlane.LocalAPIEndpoint.BindPort, options.APIServerBindPort, cfg.ControlPlane.LocalAPIEndpoint.BindPort, + &jcp.LocalAPIEndpoint.BindPort, options.APIServerBindPort, jcp.LocalAPIEndpoint.BindPort, "If the node should host a new control plane instance, the port for the API Server to bind to.", ) // adds bootstrap token specific discovery flags to the specified flagset @@ -297,18 +299,29 @@ func newJoinOptions() *joinOptions { externalcfg.Discovery.BootstrapToken = &kubeadmapiv1beta2.BootstrapTokenDiscovery{} externalcfg.ControlPlane = &kubeadmapiv1beta2.JoinControlPlane{} + // This object is used for storage of control-plane flags. + joinControlPlane := &kubeadmapiv1beta2.JoinControlPlane{} + // Apply defaults kubeadmscheme.Scheme.Default(externalcfg) + kubeadmapiv1beta2.SetDefaults_JoinControlPlane(joinControlPlane) return &joinOptions{ - externalcfg: externalcfg, + externalcfg: externalcfg, + joinControlPlane: joinControlPlane, } } // newJoinData returns a new joinData struct to be used for the execution of the kubeadm join workflow. // This func takes care of validating joinOptions passed to the command, and then it converts // options into the internal JoinConfiguration type that is used as input all the phases in the kubeadm join workflow -func newJoinData(cmd *cobra.Command, args []string, opt *joinOptions, out io.Writer) (*joinData, error) { +func newJoinData(cmd *cobra.Command, args []string, opt *joinOptions, out io.Writer, adminKubeConfigPath string) (*joinData, error) { + + // Validate the mixed arguments with --config and return early on errors + if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { + return nil, err + } + // Re-apply defaults to the public kubeadm API (this will set only values not exposed/not set as a flags) kubeadmscheme.Scheme.Default(opt.externalcfg) @@ -340,17 +353,32 @@ func newJoinData(cmd *cobra.Command, args []string, opt *joinOptions, out io.Wri opt.externalcfg.Discovery.BootstrapToken.APIServerEndpoint = args[0] } - // if not joining a control plane, unset the ControlPlane object + // Include the JoinControlPlane with user flags + opt.externalcfg.ControlPlane = opt.joinControlPlane + + // If not passing --control-plane, unset the ControlPlane object if !opt.controlPlane { - if opt.externalcfg.ControlPlane != nil { - klog.Warningf("[preflight] WARNING: JoinControlPane.controlPlane settings will be ignored when %s flag is not set.", options.ControlPlane) + // Use a defaulted JoinControlPlane object to detect if the user has passed + // other control-plane related flags. + defaultJCP := &kubeadmapiv1beta2.JoinControlPlane{} + kubeadmapiv1beta2.SetDefaults_JoinControlPlane(defaultJCP) + + // This list must match the JCP flags in addJoinConfigFlags() + joinControlPlaneFlags := []string{ + options.CertificateKey, + options.APIServerAdvertiseAddress, + options.APIServerBindPort, + } + + if *opt.joinControlPlane != *defaultJCP { + klog.Warningf("[preflight] WARNING: --%s is also required when passing control-plane "+ + "related flags such as [%s]", options.ControlPlane, strings.Join(joinControlPlaneFlags, ", ")) } opt.externalcfg.ControlPlane = nil } // if the admin.conf file already exists, use it for skipping the discovery process. // NB. this case can happen when we are joining a control-plane node only (and phases are invoked atomically) - var adminKubeConfigPath = kubeadmconstants.GetAdminKubeConfigPath() var tlsBootstrapCfg *clientcmdapi.Config if _, err := os.Stat(adminKubeConfigPath); err == nil && opt.controlPlane { // use the admin.conf as tlsBootstrapCfg, that is the kubeconfig file used for reading the kubeadm-config during discovery @@ -361,10 +389,6 @@ func newJoinData(cmd *cobra.Command, args []string, opt *joinOptions, out io.Wri } } - if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { - return nil, err - } - // Either use the config file if specified, or convert public kubeadm API to the internal JoinConfiguration // and validates JoinConfiguration if opt.externalcfg.NodeRegistration.Name == "" { diff --git a/cmd/kubeadm/app/cmd/join_test.go b/cmd/kubeadm/app/cmd/join_test.go index 8a466a9f68b..e9146d739b0 100644 --- a/cmd/kubeadm/app/cmd/join_test.go +++ b/cmd/kubeadm/app/cmd/join_test.go @@ -24,6 +24,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/kubernetes/cmd/kubeadm/app/cmd/options" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" ) const ( @@ -51,6 +52,11 @@ func TestNewJoinData(t *testing.T) { } defer os.RemoveAll(tmpDir) + // create kubeconfig + kubeconfigFilePath := filepath.Join(tmpDir, "test-kubeconfig-file") + kubeconfig := kubeconfigutil.CreateBasic("", "", "", []byte{}) + kubeconfigutil.WriteToDisk(kubeconfigFilePath, kubeconfig) + // create config file configFilePath := filepath.Join(tmpDir, "test-config-file") cfgFile, err := os.Create(configFilePath) @@ -257,7 +263,7 @@ func TestNewJoinData(t *testing.T) { } // test newJoinData method - data, err := newJoinData(cmd, tc.args, joinOptions, nil) + data, err := newJoinData(cmd, tc.args, joinOptions, nil, kubeconfigFilePath) if err != nil && !tc.expectError { t.Fatalf("newJoinData returned unexpected error: %v", err) }