diff --git a/pkg/cluster/internal/providers/docker/provider.go b/pkg/cluster/internal/providers/docker/provider.go index b1786fbc97..7e53a6c686 100644 --- a/pkg/cluster/internal/providers/docker/provider.go +++ b/pkg/cluster/internal/providers/docker/provider.go @@ -154,6 +154,25 @@ func (p *provider) DeleteNodes(n []nodes.Node) error { return nil } +// StartNodes is part of the providers.Provider interface +func (p *provider) StartNodes(n []nodes.Node) error { + if len(n) == 0 { + return nil + } + const command = "docker" + args := make([]string, 0, len(n)+1) // allocate once + args = append(args, + "start", + ) + for _, node := range n { + args = append(args, node.String()) + } + if err := exec.Command(command, args...).Run(); err != nil { + return errors.Wrap(err, "failed to start nodes") + } + return nil +} + // GetAPIServerEndpoint is part of the providers.Provider interface func (p *provider) GetAPIServerEndpoint(cluster string) (string, error) { // locate the node that hosts this diff --git a/pkg/cluster/internal/providers/podman/provider.go b/pkg/cluster/internal/providers/podman/provider.go index 856b07b046..cc249b88ac 100644 --- a/pkg/cluster/internal/providers/podman/provider.go +++ b/pkg/cluster/internal/providers/podman/provider.go @@ -171,6 +171,25 @@ func (p *provider) DeleteNodes(n []nodes.Node) error { return deleteVolumes(nodeVolumes) } +// StartNodes is part of the providers.Provider interface +func (p *provider) StartNodes(n []nodes.Node) error { + if len(n) == 0 { + return nil + } + const command = "podman" + args := make([]string, 0, len(n)+1) // allocate once + args = append(args, + "start", + ) + for _, node := range n { + args = append(args, node.String()) + } + if err := exec.Command(command, args...).Run(); err != nil { + return errors.Wrap(err, "failed to start nodes") + } + return nil +} + // GetAPIServerEndpoint is part of the providers.Provider interface func (p *provider) GetAPIServerEndpoint(cluster string) (string, error) { // locate the node that hosts this diff --git a/pkg/cluster/internal/providers/provider.go b/pkg/cluster/internal/providers/provider.go index cc42706090..a1c8ccae85 100644 --- a/pkg/cluster/internal/providers/provider.go +++ b/pkg/cluster/internal/providers/provider.go @@ -39,6 +39,8 @@ type Provider interface { // These should be from results previously returned by this provider // E.G. by ListNodes() DeleteNodes([]nodes.Node) error + // StartNodes starts the provided list of nodes + StartNodes([]nodes.Node) error // GetAPIServerEndpoint returns the host endpoint for the cluster's API server GetAPIServerEndpoint(cluster string) (string, error) // GetAPIServerInternalEndpoint returns the internal network endpoint for the cluster's API server diff --git a/pkg/cluster/internal/start/start.go b/pkg/cluster/internal/start/start.go new file mode 100644 index 0000000000..e7accbb78d --- /dev/null +++ b/pkg/cluster/internal/start/start.go @@ -0,0 +1,44 @@ +/* +Copyright 2023 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 start + +import ( + "sigs.k8s.io/kind/pkg/errors" + "sigs.k8s.io/kind/pkg/log" + + "sigs.k8s.io/kind/pkg/cluster/internal/providers" +) + +// Cluster starts the cluster identified by ctx +// explicitKubeconfigPath is --kubeconfig, following the rules from +// https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands +func Cluster(logger log.Logger, p providers.Provider, name string) error { + n, err := p.ListNodes(name) + if err != nil { + return errors.Wrap(err, "error listing nodes") + } + + if len(n) > 0 { + err = p.StartNodes(n) + if err != nil { + return err + } + logger.V(0).Infof("Started nodes: %q", n) + } + + return nil +} diff --git a/pkg/cluster/provider.go b/pkg/cluster/provider.go index 3cff174787..c23046fdbf 100644 --- a/pkg/cluster/provider.go +++ b/pkg/cluster/provider.go @@ -35,6 +35,7 @@ import ( internalproviders "sigs.k8s.io/kind/pkg/cluster/internal/providers" "sigs.k8s.io/kind/pkg/cluster/internal/providers/docker" "sigs.k8s.io/kind/pkg/cluster/internal/providers/podman" + internalstart "sigs.k8s.io/kind/pkg/cluster/internal/start" ) // DefaultName is the default cluster name @@ -186,6 +187,11 @@ func (p *Provider) Delete(name, explicitKubeconfigPath string) error { return internaldelete.Cluster(p.logger, p.provider, defaultName(name), explicitKubeconfigPath) } +// Start a kubernetes-in-docker cluster +func (p *Provider) Start(name string) error { + return internalstart.Cluster(p.logger, p.provider, defaultName(name)) +} + // List returns a list of clusters for which nodes exist func (p *Provider) List() ([]string, error) { return p.provider.ListClusters() diff --git a/pkg/cmd/kind/root.go b/pkg/cmd/kind/root.go index 09589b6197..335beec459 100644 --- a/pkg/cmd/kind/root.go +++ b/pkg/cmd/kind/root.go @@ -30,6 +30,7 @@ import ( "sigs.k8s.io/kind/pkg/cmd/kind/export" "sigs.k8s.io/kind/pkg/cmd/kind/get" "sigs.k8s.io/kind/pkg/cmd/kind/load" + "sigs.k8s.io/kind/pkg/cmd/kind/start" "sigs.k8s.io/kind/pkg/cmd/kind/version" "sigs.k8s.io/kind/pkg/log" ) @@ -82,6 +83,7 @@ func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { cmd.AddCommand(completion.NewCommand(logger, streams)) cmd.AddCommand(create.NewCommand(logger, streams)) cmd.AddCommand(delete.NewCommand(logger, streams)) + cmd.AddCommand(start.NewCommand(logger, streams)) cmd.AddCommand(export.NewCommand(logger, streams)) cmd.AddCommand(get.NewCommand(logger, streams)) cmd.AddCommand(version.NewCommand(logger, streams)) diff --git a/pkg/cmd/kind/start/cluster/startcluster.go b/pkg/cmd/kind/start/cluster/startcluster.go new file mode 100644 index 0000000000..baf38b3a91 --- /dev/null +++ b/pkg/cmd/kind/start/cluster/startcluster.go @@ -0,0 +1,72 @@ +/* +Copyright 2023 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 cluster implements the `start` command +package cluster + +import ( + "github.com/spf13/cobra" + + "sigs.k8s.io/kind/pkg/cluster" + "sigs.k8s.io/kind/pkg/cmd" + "sigs.k8s.io/kind/pkg/errors" + "sigs.k8s.io/kind/pkg/log" + + "sigs.k8s.io/kind/pkg/internal/cli" + "sigs.k8s.io/kind/pkg/internal/runtime" +) + +type flagpole struct { + Name string +} + +// NewCommand returns a new cobra.Command for cluster startion +func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { + flags := &flagpole{} + cmd := &cobra.Command{ + Args: cobra.NoArgs, + // TODO(bentheelder): more detailed usage + Use: "cluster", + Short: "Starts a cluster", + Long: "Starts a resource", + RunE: func(cmd *cobra.Command, args []string) error { + cli.OverrideDefaultName(cmd.Flags()) + return startCluster(logger, flags) + }, + } + cmd.Flags().StringVarP( + &flags.Name, + "name", + "n", + cluster.DefaultName, + "the cluster name", + ) + return cmd +} + +func startCluster(logger log.Logger, flags *flagpole) error { + provider := cluster.NewProvider( + cluster.ProviderWithLogger(logger), + runtime.GetDefault(logger), + ) + // Start individual cluster + logger.V(0).Infof("Start cluster %q ...", flags.Name) + if err := provider.Start(flags.Name); err != nil { + return errors.Wrapf(err, "failed to start cluster %q", flags.Name) + } + + return nil +} diff --git a/pkg/cmd/kind/start/start.go b/pkg/cmd/kind/start/start.go new file mode 100644 index 0000000000..636fd22346 --- /dev/null +++ b/pkg/cmd/kind/start/start.go @@ -0,0 +1,47 @@ +/* +Copyright 2018 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 create implements the `create` command +package start + +import ( + "errors" + + "github.com/spf13/cobra" + + "sigs.k8s.io/kind/pkg/cmd" + startcluster "sigs.k8s.io/kind/pkg/cmd/kind/start/cluster" + "sigs.k8s.io/kind/pkg/log" +) + +// NewCommand returns a new cobra.Command for cluster start +func NewCommand(logger log.Logger, streams cmd.IOStreams) *cobra.Command { + cmd := &cobra.Command{ + Args: cobra.NoArgs, + Use: "start", + Short: "Start one of [cluster]", + Long: "Start one of local Kubernetes cluster (cluster)", + RunE: func(cmd *cobra.Command, args []string) error { + err := cmd.Help() + if err != nil { + return err + } + return errors.New("Subcommand is required") + }, + } + cmd.AddCommand(startcluster.NewCommand(logger, streams)) + return cmd +}