From 11e5274e2b7a639f578ed736b7d3df254007ebbc Mon Sep 17 00:00:00 2001 From: fabriziopandini Date: Mon, 14 Aug 2017 16:31:32 +0200 Subject: [PATCH] Add CLI commands --- cmd/kubeadm/app/cmd/init.go | 13 +- cmd/kubeadm/app/cmd/phases/controlplane.go | 107 +++++++++++++ .../app/cmd/phases/controlplane_test.go | 150 ++++++++++++++++++ cmd/kubeadm/app/cmd/phases/etcd.go | 76 +++++++++ cmd/kubeadm/app/cmd/phases/etcd_test.go | 86 ++++++++++ cmd/kubeadm/app/cmd/phases/phase.go | 8 +- cmd/kubeadm/app/cmd/phases/util.go | 50 ++++++ 7 files changed, 484 insertions(+), 6 deletions(-) create mode 100644 cmd/kubeadm/app/cmd/phases/controlplane.go create mode 100644 cmd/kubeadm/app/cmd/phases/controlplane_test.go create mode 100644 cmd/kubeadm/app/cmd/phases/etcd.go create mode 100644 cmd/kubeadm/app/cmd/phases/etcd_test.go create mode 100644 cmd/kubeadm/app/cmd/phases/util.go diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 9933ffd0647..af20f58c996 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -20,7 +20,6 @@ import ( "fmt" "io" "io/ioutil" - "path/filepath" "strconv" "text/template" @@ -40,6 +39,7 @@ import ( nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node" "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil" controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" + etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig" markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster" selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting" @@ -241,9 +241,16 @@ func (i *Init) Run(out io.Writer) error { } // PHASE 3: Bootstrap the control plane - if err := controlplanephase.WriteStaticPodManifests(i.cfg, k8sVersion, kubeadmconstants.GetStaticPodDirectory()); err != nil { + manifestPath := kubeadmconstants.GetStaticPodDirectory() + if err := controlplanephase.CreateInitStaticPodManifestFiles(manifestPath, i.cfg); err != nil { return err } + // Add etcd static pod spec only if external etcd is not configured + if len(i.cfg.Etcd.Endpoints) == 0 { + if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestPath, i.cfg); err != nil { + return err + } + } client, err := kubeadmutil.CreateClientAndWaitForAPI(kubeadmconstants.GetAdminKubeConfigPath()) if err != nil { @@ -319,7 +326,7 @@ func (i *Init) Run(out io.Writer) error { } ctx := map[string]string{ - "KubeConfigPath": filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName), + "KubeConfigPath": kubeadmconstants.GetAdminKubeConfigPath(), "KubeConfigName": kubeadmconstants.AdminKubeConfigFileName, "Token": i.cfg.Token, "CAPubKeyPin": pubkeypin.Hash(caCert), diff --git a/cmd/kubeadm/app/cmd/phases/controlplane.go b/cmd/kubeadm/app/cmd/phases/controlplane.go new file mode 100644 index 00000000000..0614dffef66 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/controlplane.go @@ -0,0 +1,107 @@ +/* +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 ( + "github.com/spf13/cobra" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane" + "k8s.io/kubernetes/pkg/api" +) + +// NewCmdControlplane return main command for Controlplane phase +func NewCmdControlplane() *cobra.Command { + cmd := &cobra.Command{ + Use: "controlplane", + Short: "Generate all static pod manifest files necessary to establish the control plane.", + RunE: subCmdRunE("controlplane"), + } + + manifestPath := kubeadmconstants.GetStaticPodDirectory() + cmd.AddCommand(getControlPlaneSubCommands(manifestPath)...) + return cmd +} + +// getControlPlaneSubCommands returns sub commands for Controlplane phase +func getControlPlaneSubCommands(outDir string) []*cobra.Command { + + cfg := &kubeadmapiext.MasterConfiguration{} + // Default values for the cobra help text + api.Scheme.Default(cfg) + + var cfgPath string + var subCmds []*cobra.Command + + subCmdProperties := []struct { + use string + short string + cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error + }{ + { + use: "all", + short: "Generate all static pod manifest files necessary to establish the control plane.", + cmdFunc: controlplanephase.CreateInitStaticPodManifestFiles, + }, + { + use: "apiserver", + short: "Generate apiserver static pod manifest.", + cmdFunc: controlplanephase.CreateAPIServerStaticPodManifestFile, + }, + { + use: "controller-manager", + short: "Generate controller-manager static pod manifest.", + cmdFunc: controlplanephase.CreateControllerManagerStaticPodManifestFile, + }, + { + use: "scheduler", + short: "Generate scheduler static pod manifest.", + cmdFunc: controlplanephase.CreateSchedulerStaticPodManifestFile, + }, + } + + for _, properties := range subCmdProperties { + // Creates the UX Command + cmd := &cobra.Command{ + Use: properties.use, + Short: properties.short, + Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg), + } + + // Add flags to the command + cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored`) + cmd.Flags().StringVar(&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion, `Choose a specific Kubernetes version for the control plane`) + + if properties.use == "all" || properties.use == "apiserver" { + cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API Server will advertise it's listening on. 0.0.0.0 means the default network interface's address.") + cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, "Port for the API Server to bind to") + cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Use alternative range of IP address for service VIPs") + } + + if properties.use == "all" || properties.use == "controller-manager" { + cmd.Flags().StringVar(&cfg.Networking.PodSubnet, "pod-network-cidr", cfg.Networking.PodSubnet, "Specify range of IP addresses for the pod network; if set, the control plane will automatically allocate CIDRs for every node") + } + + cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") + + subCmds = append(subCmds, cmd) + } + + return subCmds +} diff --git a/cmd/kubeadm/app/cmd/phases/controlplane_test.go b/cmd/kubeadm/app/cmd/phases/controlplane_test.go new file mode 100644 index 00000000000..671794bbc83 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/controlplane_test.go @@ -0,0 +1,150 @@ +/* +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" + "os" + "testing" + + // required for triggering api machinery startup when running unit tests + _ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install" + + testutil "k8s.io/kubernetes/cmd/kubeadm/test" + cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd" +) + +func TestControlPlaneSubCommandsHasFlags(t *testing.T) { + + subCmds := getControlPlaneSubCommands("") + + commonFlags := []string{ + "cert-dir", + "config", + } + + var tests = []struct { + command string + additionalFlags []string + }{ + { + command: "all", + additionalFlags: []string{ + "kubernetes-version", + "apiserver-advertise-address", + "apiserver-bind-port", + "service-cidr", + "pod-network-cidr", + }, + }, + { + command: "apiserver", + additionalFlags: []string{ + "kubernetes-version", + "apiserver-advertise-address", + "apiserver-bind-port", + "service-cidr", + }, + }, + { + command: "controller-manager", + additionalFlags: []string{ + "kubernetes-version", + "pod-network-cidr", + }, + }, + { + command: "scheduler", + additionalFlags: []string{ + "kubernetes-version", + }, + }, + } + + for _, test := range tests { + expectedFlags := append(commonFlags, test.additionalFlags...) + cmdtestutil.AssertSubCommandHasFlags(t, subCmds, test.command, expectedFlags...) + } +} + +func TestControlPlaneCreateFilesWithFlags(t *testing.T) { + + var tests = []struct { + command string + additionalFlags []string + expectedFiles []string + }{ + { + command: "all", + additionalFlags: []string{ + "--kubernetes-version=v1.7.0", + "--apiserver-advertise-address=1.2.3.4", + "--apiserver-bind-port=6443", + "--service-cidr=1.2.3.4/16", + "--pod-network-cidr=1.2.3.4/16", + }, + expectedFiles: []string{ + "kube-apiserver.yaml", + "kube-controller-manager.yaml", + "kube-scheduler.yaml", + }, + }, + { + command: "apiserver", + additionalFlags: []string{ + "--kubernetes-version=v1.7.0", + "--apiserver-advertise-address=1.2.3.4", + "--apiserver-bind-port=6443", + "--service-cidr=1.2.3.4/16", + }, + expectedFiles: []string{"kube-apiserver.yaml"}, + }, + { + command: "controller-manager", + additionalFlags: []string{ + "--kubernetes-version=v1.7.0", + "--pod-network-cidr=1.2.3.4/16", + }, + expectedFiles: []string{"kube-controller-manager.yaml"}, + }, + { + command: "scheduler", + additionalFlags: []string{ + "--kubernetes-version=v1.7.0", + }, + expectedFiles: []string{"kube-scheduler.yaml"}, + }, + } + + for _, test := range tests { + + // Create temp folder for the test case + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + + // Get subcommands working in the temporary directory + subCmds := getControlPlaneSubCommands(tmpdir) + + // Execute the subcommand + certDirFlag := fmt.Sprintf("--cert-dir=%s", tmpdir) + allFlags := append(test.additionalFlags, certDirFlag) + cmdtestutil.RunSubCommand(t, subCmds, test.command, allFlags...) + + // Checks that requested files are there + testutil.AssertFileExists(t, tmpdir, test.expectedFiles...) + } +} diff --git a/cmd/kubeadm/app/cmd/phases/etcd.go b/cmd/kubeadm/app/cmd/phases/etcd.go new file mode 100644 index 00000000000..7d205160fe5 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/etcd.go @@ -0,0 +1,76 @@ +/* +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 ( + "github.com/spf13/cobra" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd" + "k8s.io/kubernetes/pkg/api" +) + +// NewCmdEtcd return main command for Etcd phase +func NewCmdEtcd() *cobra.Command { + cmd := &cobra.Command{ + Use: "etcd", + Short: "Generate static pod manifest file for etcd.", + RunE: subCmdRunE("etcd"), + } + + manifestPath := kubeadmconstants.GetStaticPodDirectory() + cmd.AddCommand(getEtcdSubCommands(manifestPath)...) + return cmd +} + +// getEtcdSubCommands returns sub commands for etcd phase +func getEtcdSubCommands(outDir string) []*cobra.Command { + + cfg := &kubeadmapiext.MasterConfiguration{} + // Default values for the cobra help text + api.Scheme.Default(cfg) + + var cfgPath string + var subCmds []*cobra.Command + + properties := struct { + use string + short string + cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error + }{ + use: "local", + short: "Generate static pod manifest file for a local, single-node etcd instance.", + cmdFunc: etcdphase.CreateLocalEtcdStaticPodManifestFile, + } + + // Creates the UX Command + cmd := &cobra.Command{ + Use: properties.use, + Short: properties.short, + Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg), + } + + // Add flags to the command + cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored`) + cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)") + + subCmds = append(subCmds, cmd) + + return subCmds +} diff --git a/cmd/kubeadm/app/cmd/phases/etcd_test.go b/cmd/kubeadm/app/cmd/phases/etcd_test.go new file mode 100644 index 00000000000..0f6a1284307 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/etcd_test.go @@ -0,0 +1,86 @@ +/* +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" + "os" + "testing" + + // required for triggering api machinery startup when running unit tests + _ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install" + + testutil "k8s.io/kubernetes/cmd/kubeadm/test" + cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd" +) + +func TestEtcdSubCommandsHasFlags(t *testing.T) { + + subCmds := getEtcdSubCommands("") + + commonFlags := []string{ + "cert-dir", + "config", + } + + var tests = []struct { + command string + additionalFlags []string + }{ + { + command: "local", + }, + } + + for _, test := range tests { + expectedFlags := append(commonFlags, test.additionalFlags...) + cmdtestutil.AssertSubCommandHasFlags(t, subCmds, test.command, expectedFlags...) + } +} + +func TestEtcdCreateFilesWithFlags(t *testing.T) { + + var tests = []struct { + command string + additionalFlags []string + expectedFiles []string + }{ + { + command: "local", + expectedFiles: []string{"etcd.yaml"}, + additionalFlags: []string{}, + }, + } + + for _, test := range tests { + + // Create temp folder for the test case + tmpdir := testutil.SetupTempDir(t) + defer os.RemoveAll(tmpdir) + + // Get subcommands working in the temporary directory + subCmds := getEtcdSubCommands(tmpdir) + + // Execute the subcommand + certDirFlag := fmt.Sprintf("--cert-dir=%s", tmpdir) + allFlags := append(test.additionalFlags, certDirFlag) + cmdtestutil.RunSubCommand(t, subCmds, test.command, allFlags...) + + // Checks that requested files are there + testutil.AssertFileExists(t, tmpdir, test.expectedFiles...) + } +} diff --git a/cmd/kubeadm/app/cmd/phases/phase.go b/cmd/kubeadm/app/cmd/phases/phase.go index e2b7a95437d..fd6edbfed03 100644 --- a/cmd/kubeadm/app/cmd/phases/phase.go +++ b/cmd/kubeadm/app/cmd/phases/phase.go @@ -31,13 +31,15 @@ func NewCmdPhase(out io.Writer) *cobra.Command { RunE: subCmdRunE("phase"), } - cmd.AddCommand(NewCmdKubeConfig(out)) + cmd.AddCommand(NewCmdBootstrapToken()) cmd.AddCommand(NewCmdCerts()) + cmd.AddCommand(NewCmdControlplane()) + cmd.AddCommand(NewCmdEtcd()) + cmd.AddCommand(NewCmdKubeConfig(out)) + cmd.AddCommand(NewCmdMarkMaster()) cmd.AddCommand(NewCmdPreFlight()) cmd.AddCommand(NewCmdSelfhosting()) - cmd.AddCommand(NewCmdMarkMaster()) cmd.AddCommand(NewCmdUploadConfig()) - cmd.AddCommand(NewCmdBootstrapToken()) return cmd } diff --git a/cmd/kubeadm/app/cmd/phases/util.go b/cmd/kubeadm/app/cmd/phases/util.go new file mode 100644 index 00000000000..32b7f3fb1d4 --- /dev/null +++ b/cmd/kubeadm/app/cmd/phases/util.go @@ -0,0 +1,50 @@ +/* +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 ( + "github.com/spf13/cobra" + + kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" + kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" + "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation" + kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" +) + +// runCmdPhase creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters) +func runCmdPhase(cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error, outDir, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) { + + // the following statement build a clousure that wraps a call to a cmdFunc, binding + // the function itself with the specific parameters of each sub command. + // Please note that specific parameter should be passed as value, while other parameters - passed as reference - + // are shared between sub commands and gets access to current value e.g. flags value. + + return func(cmd *cobra.Command, args []string) { + if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil { + kubeadmutil.CheckErr(err) + } + + // This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags + internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(*cfgPath, cfg) + kubeadmutil.CheckErr(err) + + // Execute the cmdFunc + err = cmdFunc(*outDir, internalcfg) + kubeadmutil.CheckErr(err) + } +}