diff --git a/cmd/kind/build/build.go b/cmd/kind/build/build.go index 20d1e595e2..a3dbd602f4 100644 --- a/cmd/kind/build/build.go +++ b/cmd/kind/build/build.go @@ -32,9 +32,6 @@ func NewCommand() *cobra.Command { Use: "build", Short: "Build one of [base-image, node-image]", Long: "Build the base node image (base-image) or the node image (node-image)", - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, } // add subcommands cmd.AddCommand(baseimage.NewCommand()) diff --git a/cmd/kind/create/cluster/createcluster.go b/cmd/kind/create/cluster/createcluster.go index eba5906f4a..23603b8f3c 100644 --- a/cmd/kind/create/cluster/createcluster.go +++ b/cmd/kind/create/cluster/createcluster.go @@ -18,6 +18,7 @@ limitations under the License. package cluster import ( + "fmt" "time" "github.com/pkg/errors" @@ -49,7 +50,7 @@ func NewCommand() *cobra.Command { return runE(flags, cmd, args) }, } - cmd.Flags().StringVar(&flags.Name, "name", "1", "cluster context name") + cmd.Flags().StringVar(&flags.Name, "name", cluster.DefaultName, "cluster context name") cmd.Flags().StringVar(&flags.Config, "config", "", "path to a kind config file") cmd.Flags().StringVar(&flags.ImageName, "image", "", "node docker image to use for booting the cluster") cmd.Flags().BoolVar(&flags.Retain, "retain", false, "retain nodes for debugging when cluster creation fails") @@ -75,6 +76,15 @@ func runE(flags *flagpole, cmd *cobra.Command, args []string) error { return errors.New("aborting due to invalid configuration") } + // Check if the cluster name already exists + known, err := cluster.IsKnown(flags.Name) + if err != nil { + return err + } + if known { + return errors.Errorf("a cluster with the name %q already exists", flags.Name) + } + // create a cluster context and create the cluster ctx := cluster.NewContext(flags.Name) if flags.ImageName != "" { @@ -91,6 +101,7 @@ func runE(flags *flagpole, cmd *cobra.Command, args []string) error { return errors.New("aborting due to invalid configuration") } } + fmt.Printf("Creating cluster %q ...\n", flags.Name) if err = ctx.Create(cfg, flags.Retain, flags.Wait); err != nil { return errors.Wrap(err, "failed to create cluster") } diff --git a/cmd/kind/create/create.go b/cmd/kind/create/create.go index d91917a1f8..e99a303fdf 100644 --- a/cmd/kind/create/create.go +++ b/cmd/kind/create/create.go @@ -18,8 +18,6 @@ limitations under the License. package create import ( - "fmt" - "github.com/spf13/cobra" createcluster "sigs.k8s.io/kind/cmd/kind/create/cluster" @@ -32,15 +30,7 @@ func NewCommand() *cobra.Command { Use: "create", Short: "Creates one of [cluster]", Long: "Creates one of local Kubernetes cluster (cluster)", - RunE: run, } cmd.AddCommand(createcluster.NewCommand()) return cmd } - -func run(cmd *cobra.Command, args []string) error { - fmt.Println("You likely want `kind create cluster`, please migrate!") - fmt.Println() - cmd.Usage() - return nil -} diff --git a/cmd/kind/delete/cluster/deletecluster.go b/cmd/kind/delete/cluster/deletecluster.go index bcf0dca2b8..4d59f3ebbd 100644 --- a/cmd/kind/delete/cluster/deletecluster.go +++ b/cmd/kind/delete/cluster/deletecluster.go @@ -18,6 +18,8 @@ limitations under the License. package cluster import ( + "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" @@ -42,11 +44,21 @@ func NewCommand() *cobra.Command { return runE(flags, cmd, args) }, } - cmd.Flags().StringVar(&flags.Name, "name", "1", "the cluster name") + cmd.Flags().StringVar(&flags.Name, "name", cluster.DefaultName, "the cluster name") return cmd } func runE(flags *flagpole, cmd *cobra.Command, args []string) error { + // Check if the cluster name exists + known, err := cluster.IsKnown(flags.Name) + if err != nil { + return err + } + if !known { + return errors.Errorf("unknown cluster %q", flags.Name) + } + // Delete the cluster + fmt.Printf("Deleting cluster %q ...\n", flags.Name) ctx := cluster.NewContext(flags.Name) if err := ctx.Delete(); err != nil { return errors.Wrap(err, "failed to delete cluster") diff --git a/cmd/kind/delete/delete.go b/cmd/kind/delete/delete.go index b4ac18ef57..6711050647 100644 --- a/cmd/kind/delete/delete.go +++ b/cmd/kind/delete/delete.go @@ -18,8 +18,6 @@ limitations under the License. package delete import ( - "fmt" - "github.com/spf13/cobra" deletecluster "sigs.k8s.io/kind/cmd/kind/delete/cluster" @@ -33,15 +31,7 @@ func NewCommand() *cobra.Command { Use: "delete", Short: "Deletes one of [cluster]", Long: "Deletes one of [cluster]", - RunE: run, } cmd.AddCommand(deletecluster.NewCommand()) return cmd } - -func run(cmd *cobra.Command, args []string) error { - fmt.Println("You likely want `kind delete cluster`, please migrate!") - fmt.Println() - cmd.Usage() - return nil -} diff --git a/cmd/kind/export/export.go b/cmd/kind/export/export.go index 005c76196e..9ce1945483 100644 --- a/cmd/kind/export/export.go +++ b/cmd/kind/export/export.go @@ -31,9 +31,6 @@ func NewCommand() *cobra.Command { Use: "export", Short: "exports one of [logs]", Long: "exports one of [logs]", - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, } // add subcommands cmd.AddCommand(logs.NewCommand()) diff --git a/cmd/kind/export/logs/logs.go b/cmd/kind/export/logs/logs.go index 3628ad9c1a..8e30f2ec2c 100644 --- a/cmd/kind/export/logs/logs.go +++ b/cmd/kind/export/logs/logs.go @@ -20,6 +20,7 @@ package logs import ( "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" @@ -43,12 +44,19 @@ func NewCommand() *cobra.Command { return runE(flags, cmd, args) }, } - // TODO(bentheelder): this default should be a constant somewhere - cmd.Flags().StringVar(&flags.Name, "name", "1", "the cluster context name") + cmd.Flags().StringVar(&flags.Name, "name", cluster.DefaultName, "the cluster context name") return cmd } func runE(flags *flagpole, cmd *cobra.Command, args []string) error { + // Check if the cluster name exists + known, err := cluster.IsKnown(flags.Name) + if err != nil { + return err + } + if !known { + return errors.Errorf("unknown cluster %q", flags.Name) + } // get the optional directory argument, or create a tempdir var dir string if len(args) == 0 { diff --git a/cmd/kind/get/clusters/clusters.go b/cmd/kind/get/clusters/clusters.go index f3b139c318..753211038b 100644 --- a/cmd/kind/get/clusters/clusters.go +++ b/cmd/kind/get/clusters/clusters.go @@ -20,7 +20,6 @@ package clusters import ( "fmt" - "github.com/pkg/errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" @@ -44,7 +43,7 @@ func NewCommand() *cobra.Command { func runE(cmd *cobra.Command, args []string) error { clusters, err := cluster.List() if err != nil { - return errors.Wrap(err, "error listing clusters") + return err } for _, cluster := range clusters { fmt.Println(cluster.Name()) diff --git a/cmd/kind/get/get.go b/cmd/kind/get/get.go index efc52ea6c8..85a00adb58 100644 --- a/cmd/kind/get/get.go +++ b/cmd/kind/get/get.go @@ -22,6 +22,7 @@ import ( "sigs.k8s.io/kind/cmd/kind/get/clusters" "sigs.k8s.io/kind/cmd/kind/get/kubeconfigpath" + "sigs.k8s.io/kind/cmd/kind/get/nodes" ) // NewCommand returns a new cobra.Command for get @@ -31,13 +32,11 @@ func NewCommand() *cobra.Command { // TODO(bentheelder): more detailed usage Use: "get", Short: "Gets one of [clusters, kubeconfig-path]", - Long: "Gets one of [clusters, kubeconfig-path]", - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, + Long: "Gets one of [clusters, nodes, kubeconfig-path]", } // add subcommands cmd.AddCommand(clusters.NewCommand()) + cmd.AddCommand(nodes.NewCommand()) cmd.AddCommand(kubeconfigpath.NewCommand()) return cmd } diff --git a/cmd/kind/get/kubeconfigpath/kubeconfigpath.go b/cmd/kind/get/kubeconfigpath/kubeconfigpath.go index ec8781b46f..1a96be8a82 100644 --- a/cmd/kind/get/kubeconfigpath/kubeconfigpath.go +++ b/cmd/kind/get/kubeconfigpath/kubeconfigpath.go @@ -20,6 +20,7 @@ package kubeconfigpath import ( "fmt" + "github.com/pkg/errors" "github.com/spf13/cobra" "sigs.k8s.io/kind/pkg/cluster" @@ -45,13 +46,22 @@ func NewCommand() *cobra.Command { cmd.Flags().StringVar( &flags.Name, "name", - "1", + cluster.DefaultName, "the cluster context name", ) return cmd } func runE(flags *flagpole, cmd *cobra.Command, args []string) error { + // Check if a cluster with this name exists + known, err := cluster.IsKnown(flags.Name) + if err != nil { + return err + } + if !known { + return errors.Errorf("unknown cluster %q", flags.Name) + } + // Obtain the kubeconfig path for this cluster ctx := cluster.NewContext(flags.Name) fmt.Println(ctx.KubeConfigPath()) return nil diff --git a/cmd/kind/get/nodes/nodes.go b/cmd/kind/get/nodes/nodes.go new file mode 100644 index 0000000000..18e7f2d074 --- /dev/null +++ b/cmd/kind/get/nodes/nodes.go @@ -0,0 +1,69 @@ +/* +Copyright 2019 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 nodes implements the `nodes` command +package nodes + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "sigs.k8s.io/kind/pkg/cluster" + clusternodes "sigs.k8s.io/kind/pkg/cluster/nodes" +) + +type flagpole struct { + Name string +} + +// NewCommand returns a new cobra.Command for getting the list of nodes for a given cluster +func NewCommand() *cobra.Command { + flags := &flagpole{} + cmd := &cobra.Command{ + Args: cobra.NoArgs, + Use: "nodes", + Short: "lists existing kind nodes by their name", + Long: "lists existing kind nodes by their name", + RunE: func(cmd *cobra.Command, args []string) error { + return runE(flags, cmd, args) + }, + } + cmd.Flags().StringVar( + &flags.Name, + "name", + cluster.DefaultName, + "the cluster context name", + ) + return cmd +} + +func runE(flags *flagpole, cmd *cobra.Command, args []string) error { + // List nodes by cluster context name + n, err := clusternodes.ListByCluster() + if err != nil { + return err + } + nodes, known := n[flags.Name] + if !known { + return errors.Errorf("unknown cluster %q", flags.Name) + } + for _, node := range nodes { + fmt.Println(node.String()) + } + return nil +} diff --git a/cmd/kind/kind.go b/cmd/kind/kind.go index 8d62914f51..33c4c20e8f 100644 --- a/cmd/kind/kind.go +++ b/cmd/kind/kind.go @@ -52,10 +52,8 @@ func NewCommand() *cobra.Command { PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return runE(flags, cmd, args) }, - RunE: func(cmd *cobra.Command, args []string) error { - return cmd.Help() - }, - Version: version.Version, + SilenceUsage: true, + Version: version.Version, } cmd.PersistentFlags().StringVar( &flags.LogLevel, @@ -90,7 +88,7 @@ func Run() error { return NewCommand().Execute() } -// Main wraps Run, adding a log.Fatal(err) on error, and setting the log formatter +// Main wraps Run and sets the log formatter func Main() { // let's explicitly set stdout log.SetOutput(os.Stdout) @@ -105,7 +103,6 @@ func Main() { ForceColors: logutil.IsTerminal(log.StandardLogger().Out), }) if err := Run(); err != nil { - os.Stderr.WriteString(err.Error() + "\n") - os.Exit(-1) + os.Exit(1) } } diff --git a/docs/user/README.md b/docs/user/README.md index 10d5629267..d7cc5136cf 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -14,8 +14,7 @@ If you desire to build the node image yourself see the [building image](#building-images) section. To specify another image use the `--image` flag. -By default, the cluster will be given the name `kind-1`. "1," here, is the -default context name. +By default, the cluster will be given the name `kind`. Use the `--name` flag to assign the cluster a different context name. If you want the `create cluster` command to block until the control plane @@ -23,9 +22,9 @@ reaches a ready status, you can use the `--wait` flag and specify a timeout. To use `--wait` you must specify the units of the time to wait. For example, to wait for 30 seconds, do `--wait 30s`, for 5 minutes do `--wait 5m`, etc. -**Note**: If you are running `kind` on MacOS or Windows then it is recommended +**Note**: If you are running kind on MacOS or Windows then it is recommended that you have at least 4GB of RAM and disk space (these are estimates for a -single node `kind` cluster) dedicated to the virtual machine (VM) running the +single node kind cluster) dedicated to the virtual machine (VM) running the Docker engine otherwise the Kubernetes cluster might fail to start up. To change the resource limits for the Docker engine on Mac, you'll need to open the @@ -42,7 +41,7 @@ You may also try removing any unused data left by the Docker engine - e.g., ## Interacting With Your Cluster After [creating a cluster](#creating-a-cluster), you can use [kubectl][kubectl] -to interact with it by using the configuration file generated by `kind`: +to interact with it by using the configuration file generated by kind: ``` export KUBECONFIG="$(kind get kubeconfig-path)" kubectl cluster-info @@ -58,28 +57,27 @@ command. For example, let's say you create two clusters: ``` -$ kind create cluster # Default cluster context name is "1". +$ kind create cluster # Default cluster context name is `kind`. ... -$ kind create cluster --name 2 +$ kind create cluster --name kind-2 ``` -When you list your `kind` clusters, you will see something like the following: +When you list your kind clusters, you will see something like the following: ``` $ kind get clusters -2 -1 +kind +kind-2 ``` Both of these clusters will have a kubeconfig file to go along with them: ``` $ kind get kubeconfig-path -/home/user/.kube/kind-config-1 +/home/user/.kube/kind-config-kind -$ kind get kubeconfig-path --name 2 -/home/user/.kube/kind-config-2 +$ kind get kubeconfig-path --name kind-2 +/home/user/.kube/kind-config-kind-2 ``` - ## Deleting a Cluster If you created a cluster with `kind create cluster` then deleting is equally @@ -88,16 +86,13 @@ simple: $ kind delete cluster ``` -If the flag `--name` is not specified, `kind` will use the default cluster -context name "1" and delete that cluster. -Recall that cluster context names are prefixed with `kind-` so the default -cluster name is `kind-1`. - +If the flag `--name` is not specified, kind will use the default cluster +context name `kind` and delete that cluster. ## Building Images -`kind` runs a local Kubernetes cluster by using Docker containers as "nodes". -`kind` uses the [`node-image`][node image] to run Kubernetes artifacts, such +kind runs a local Kubernetes cluster by using Docker containers as "nodes". +kind uses the [`node-image`][node image] to run Kubernetes artifacts, such as `kubeadm` or `kubelet`. The `node-image` in turn is built off the [`base-image`][base image], which installs all the dependencies needed for Docker and Kubernetes to run in a @@ -105,11 +100,11 @@ container. See [building the base image](#building-the-base-image) for more advanced information. -Currently, `kind` supports three different ways to build a `node-image`: via +Currently, kind supports three different ways to build a `node-image`: via `apt`, or if you have the [Kubernetes][kubernetes] source in your host machine (`$GOPATH/src/k8s.io/kubernetes`), by using `docker` or `bazel`. To specify the build type use the flag `--type`. -`kind` will default to using the build type `docker` if none is specified. +kind will default to using the build type `docker` if none is specified. ``` $ kind build node-image --type apt @@ -132,7 +127,7 @@ $ kind build base-image If you want to specify the path to the base image source files you can use the `--source` flag. -If `--source` is not specified, `kind` has enbedded the contents of the in +If `--source` is not specified, kind has enbedded the contents of the in default base image in [`pkg/build/base/sources`][pkg/build/base/sources] and will use this to build it. @@ -144,8 +139,8 @@ $ kind build base-image --image base:v0.1.0 ``` -### Configuring Your `kind` Cluster -When creating your `kind` cluster, via `create cluster`, you can use a +### Configuring Your kind Cluster +When creating your kind cluster, via `create cluster`, you can use a configuration file to run specific commands before or after systemd or kubeadm run. To specify a configuration file when creating a cluster, use the `--config` @@ -164,9 +159,9 @@ nodes: replicas: 2 ``` -### Exporting `kind`'s Logs -`kind` has the ability to export all `kind` related logs for you to explore. -To export all logs from the default cluster (context name "1"): +### Exporting kind's Logs +kind has the ability to export all kind related logs for you to explore. +To export all logs from the default cluster (context name `kind`): ``` $ kind export logs Exported logs to: /tmp/396758314 @@ -175,7 +170,7 @@ Exported logs to: /tmp/396758314 Like all other commands, if you want to perform the action on a cluster with a different context name use the `--name` flag. -As you can see, `kind` placed all the logs for the cluster `kind-1` in a +As you can see, kind placed all the logs for the cluster `kind` in a temporary directory. If you want to specify a location then simply add the path to the directory after the command: ``` @@ -187,7 +182,7 @@ The structure of the logs will look more or less like this: ``` . ├── docker-info.txt -└── kind-1-control-plane/ +└── kind-control-plane/ ├── containers ├── docker.log ├── inspect.json @@ -197,7 +192,7 @@ The structure of the logs will look more or less like this: └── pods/ ``` The logs contain information about the Docker host, the containers running -`kind`, the Kubernetes cluster itself, etc. +kind, the Kubernetes cluster itself, etc. [node image]: ../design/node-image.md [base image]: ../design/base-image.md diff --git a/pkg/cluster/clusters.go b/pkg/cluster/clusters.go index ae4d35c0ee..379548d202 100644 --- a/pkg/cluster/clusters.go +++ b/pkg/cluster/clusters.go @@ -26,7 +26,7 @@ import ( func List() ([]Context, error) { n, err := nodes.ListByCluster() if err != nil { - return nil, errors.Wrap(err, "could not list clusters, failed to list nodes") + return nil, errors.Wrap(err, "could not list clusters") } clusters := []Context{} for name := range n { @@ -34,3 +34,18 @@ func List() ([]Context, error) { } return clusters, nil } + +// IsKnown return true if a cluster exists with the given name. +// If obtaining the list of known clusters fails the function returns an error. +func IsKnown(name string) (bool, error) { + list, err := List() + if err != nil { + return false, err + } + for _, cluster := range list { + if cluster.Name() == name { + return true, nil + } + } + return false, nil +} diff --git a/pkg/cluster/context.go b/pkg/cluster/context.go index 95e77cdab7..ecda19ab4d 100644 --- a/pkg/cluster/context.go +++ b/pkg/cluster/context.go @@ -50,10 +50,10 @@ var validNameRE = regexp.MustCompile(`^[a-zA-Z0-9_.-]+$`) // DefaultName is the default Context name // TODO(bentheelder): consider removing automatic prefixing in favor // of letting the user specify the full name.. -const DefaultName = "1" +const DefaultName = "kind" // NewContext returns a new cluster management context -// if name is "" the default ("1") will be used +// if name is "" the default name will be used func NewContext(name string) *Context { if name == "" { name = DefaultName @@ -96,9 +96,8 @@ func (c *Context) GetControlPlaneMeta() (*ControlPlaneMeta, error) { } // ClusterName returns the Kubernetes cluster name based on the context name -// currently this is .Name prefixed with "kind-" func (c *Context) ClusterName() string { - return fmt.Sprintf("kind-%s", c.Name()) + return c.Name() } // Create provisions and starts a kubernetes-in-docker cluster @@ -123,8 +122,6 @@ func (c *Context) Create(cfg *config.Config, retain bool, wait time.Duration) er return err } - fmt.Printf("Creating cluster '%s' ...\n", c.ClusterName()) - // init the create context and logging cc := &create.Context{ Config: cfg, diff --git a/pkg/cluster/internal/create/createcontext.go b/pkg/cluster/internal/create/createcontext.go index b20db39586..78d8bac808 100644 --- a/pkg/cluster/internal/create/createcontext.go +++ b/pkg/cluster/internal/create/createcontext.go @@ -121,7 +121,7 @@ func (cc *Context) ProvisionNodes() (nodeList map[string]*nodes.Node, err error) cc.Status.Start(fmt.Sprintf("[%s] Creating node container 📦", configNode.Name)) // create the node into a container (docker run, but it is paused, see createNode) - var name = fmt.Sprintf("kind-%s-%s", cc.Name(), configNode.Name) + var name = fmt.Sprintf("%s-%s", cc.Name(), configNode.Name) var node *nodes.Node switch configNode.Role {