diff --git a/cmd/kubeadm/app/cmd/phases/bootstraptoken.go b/cmd/kubeadm/app/cmd/phases/bootstraptoken.go new file mode 100644 index 00000000000..f9b45358720 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/bootstraptoken.go @@ -0,0 +1,139 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package phases + +import ( + "fmt" + + "github.com/spf13/cobra" + + clientset "k8s.io/client-go/kubernetes" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo" + "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" + versionutil "k8s.io/kubernetes/pkg/util/version" +) + +// NewCmdBootstrapToken returns the Cobra command for running the mark-master phase +func NewCmdBootstrapToken() *cobra.Command { + var kubeConfigFile string + cmd := &cobra.Command{ + Use: "bootstrap-token", + Short: "Manage kubeadm-specific Bootstrap Token functions.", + Aliases: []string{"bootstraptoken"}, + RunE: subCmdRunE("bootstrap-token"), + } + + cmd.PersistentFlags().StringVar(&kubeConfigFile, "kubeconfig", "/etc/kubernetes/admin.conf", "The KubeConfig file to use for talking to the cluster") + + // Add subcommands + cmd.AddCommand(NewSubCmdClusterInfo(&kubeConfigFile)) + cmd.AddCommand(NewSubCmdNodeBootstrapToken(&kubeConfigFile)) + + return cmd +} + +// NewSubCmdClusterInfo returns the Cobra command for running the cluster-info sub-phase +func NewSubCmdClusterInfo(kubeConfigFile *string) *cobra.Command { + cmd := &cobra.Command{ + Use: "cluster-info ", + Short: "Uploads and exposes the cluster-info ConfigMap publicly from the given cluster-info file", + Aliases: []string{"clusterinfo"}, + Run: func(cmd *cobra.Command, args []string) { + err := validateExactArgNumber(args, []string{"clusterinfo-file"}) + kubeadmutil.CheckErr(err) + + client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) + kubeadmutil.CheckErr(err) + + // Here it's safe to get args[0], since we've validated that the argument exists above in validateExactArgNumber + clusterInfoFile := args[0] + // Create the cluster-info ConfigMap or update if it already exists + err = clusterinfo.CreateBootstrapConfigMapIfNotExists(client, clusterInfoFile) + kubeadmutil.CheckErr(err) + + // Create the RBAC rules that expose the cluster-info ConfigMap properly + err = clusterinfo.CreateClusterInfoRBACRules(client) + kubeadmutil.CheckErr(err) + }, + } + return cmd +} + +// NewSubCmdNodeBootstrapToken returns the Cobra command for running the node sub-phase +func NewSubCmdNodeBootstrapToken(kubeConfigFile *string) *cobra.Command { + cmd := &cobra.Command{ + Use: "node", + Short: "Manages Node Bootstrap Tokens", + Aliases: []string{"clusterinfo"}, + RunE: subCmdRunE("node"), + } + + cmd.AddCommand(NewSubCmdNodeBootstrapTokenPostCSRs(kubeConfigFile)) + cmd.AddCommand(NewSubCmdNodeBootstrapTokenAutoApprove(kubeConfigFile)) + + return cmd +} + +// NewSubCmdNodeBootstrapTokenPostCSRs returns the Cobra command for running the allow-post-csrs sub-phase +func NewSubCmdNodeBootstrapTokenPostCSRs(kubeConfigFile *string) *cobra.Command { + cmd := &cobra.Command{ + Use: "allow-post-csrs", + Short: "Configure RBAC to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials", + Run: func(cmd *cobra.Command, args []string) { + client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) + kubeadmutil.CheckErr(err) + + err = node.AllowBootstrapTokensToPostCSRs(client) + kubeadmutil.CheckErr(err) + }, + } + return cmd +} + +// NewSubCmdNodeBootstrapToken returns the Cobra command for running the allow-auto-approve sub-phase +func NewSubCmdNodeBootstrapTokenAutoApprove(kubeConfigFile *string) *cobra.Command { + cmd := &cobra.Command{ + Use: "allow-auto-approve", + Short: "Configure RBAC rules to allow the csrapprover controller automatically approve CSRs from a Node Bootstrap Token", + Run: func(cmd *cobra.Command, args []string) { + client, err := kubeconfigutil.ClientSetFromFile(*kubeConfigFile) + kubeadmutil.CheckErr(err) + + clusterVersion, err := getClusterVersion(client) + kubeadmutil.CheckErr(err) + + err = node.AutoApproveNodeBootstrapTokens(client, clusterVersion) + kubeadmutil.CheckErr(err) + }, + } + return cmd +} + +// getClusterVersion fetches the API server version and parses it +func getClusterVersion(client clientset.Interface) (*versionutil.Version, error) { + clusterVersionInfo, err := client.Discovery().ServerVersion() + if err != nil { + return nil, fmt.Errorf("failed to check server version: %v", err) + } + clusterVersion, err := versionutil.ParseSemantic(clusterVersionInfo.String()) + if err != nil { + return nil, fmt.Errorf("failed to parse server version: %v", err) + } + return clusterVersion, nil +} diff --git a/cmd/kubeadm/app/cmd/phases/markmaster.go b/cmd/kubeadm/app/cmd/phases/markmaster.go index 0d95dc851d3..33b50134758 100644 --- a/cmd/kubeadm/app/cmd/phases/markmaster.go +++ b/cmd/kubeadm/app/cmd/phases/markmaster.go @@ -22,6 +22,7 @@ import ( "github.com/spf13/cobra" markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" ) @@ -33,16 +34,11 @@ func NewCmdMarkMaster() *cobra.Command { Short: "Create KubeConfig files from given credentials.", Aliases: []string{"markmaster"}, RunE: func(_ *cobra.Command, args []string) error { - if len(args) < 1 || len(args[0]) == 0 { - return fmt.Errorf("missing required argument node-name") - } - if len(args) > 1 { - return fmt.Errorf("too many arguments, only one argument supported: node-name") - } + err := validateExactArgNumber(args, []string{"node-name"}) + kubeadmutil.CheckErr(err) + client, err := kubeconfigutil.ClientSetFromFile(kubeConfigFile) - if err != nil { - return err - } + kubeadmutil.CheckErr(err) nodeName := args[0] fmt.Printf("[markmaster] Will mark node %s as master by adding a label and a taint\n", nodeName) diff --git a/cmd/kubeadm/app/cmd/phases/phase.go b/cmd/kubeadm/app/cmd/phases/phase.go index eea17f2ac10..e2b7a95437d 100644 --- a/cmd/kubeadm/app/cmd/phases/phase.go +++ b/cmd/kubeadm/app/cmd/phases/phase.go @@ -23,6 +23,7 @@ import ( "github.com/spf13/cobra" ) +// NewCmdPhase returns the cobra command for the "kubeadm phase" command (currently alpha-gated) func NewCmdPhase(out io.Writer) *cobra.Command { cmd := &cobra.Command{ Use: "phase", @@ -36,6 +37,7 @@ func NewCmdPhase(out io.Writer) *cobra.Command { cmd.AddCommand(NewCmdSelfhosting()) cmd.AddCommand(NewCmdMarkMaster()) cmd.AddCommand(NewCmdUploadConfig()) + cmd.AddCommand(NewCmdBootstrapToken()) return cmd } @@ -54,3 +56,22 @@ func subCmdRunE(name string) func(*cobra.Command, []string) error { return fmt.Errorf("invalid subcommand: %q", args[0]) } } + +// validateExactArgNumber validates that the required top-level arguments are specified +func validateExactArgNumber(args []string, supportedArgs []string) error { + validArgs := 0 + // Disregard possible "" arguments; they are invalid + for _, arg := range args { + if len(arg) > 0 { + validArgs++ + } + } + + if validArgs < len(supportedArgs) { + return fmt.Errorf("missing one or more required arguments. Required arguments: %v", supportedArgs) + } + if validArgs > len(supportedArgs) { + return fmt.Errorf("too many arguments, only %d argument(s) supported: %v", validArgs, supportedArgs) + } + return nil +} diff --git a/cmd/kubeadm/app/cmd/phases/phase_test.go b/cmd/kubeadm/app/cmd/phases/phase_test.go new file mode 100644 index 00000000000..67a5283337f --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/phase_test.go @@ -0,0 +1,64 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package phases + +import ( + "testing" +) + +func TestValidateExactArgNumber(t *testing.T) { + var tests = []struct { + args, supportedArgs []string + expectedErr bool + }{ + { // one arg given and one arg expected + args: []string{"my-node-1234"}, + supportedArgs: []string{"node-name"}, + expectedErr: false, + }, + { // two args given and two args expected + args: []string{"my-node-1234", "foo"}, + supportedArgs: []string{"node-name", "second-toplevel-arg"}, + expectedErr: false, + }, + { // too few supplied args + args: []string{}, + supportedArgs: []string{"node-name"}, + expectedErr: true, + }, + { // too few non-empty args + args: []string{""}, + supportedArgs: []string{"node-name"}, + expectedErr: true, + }, + { // too many args + args: []string{"my-node-1234", "foo"}, + supportedArgs: []string{"node-name"}, + expectedErr: true, + }, + } + for _, rt := range tests { + actual := validateExactArgNumber(rt.args, rt.supportedArgs) + if (actual != nil) != rt.expectedErr { + t.Errorf( + "failed validateExactArgNumber:\n\texpected error: %t\n\t actual error: %t", + rt.expectedErr, + (actual != nil), + ) + } + } +}