diff --git a/Makefile b/Makefile index 9a344b6..823a671 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,15 @@ # Maintain semantic version VERSION_MAJOR ?= 0 VERSION_MINOR ?= 2 -BUILD_NUMBER ?= 3 -PATCH_NUMBER ?= +BUILD_NUMBER ?= 4 +PATCH_NUMBER ?= -alpha1 VERSION_STRING = $(VERSION_MAJOR).$(VERSION_MINOR).$(BUILD_NUMBER)$(PATCH_NUMBER) KUTTICMDFILES = cmd/kutti/*.go \ internal/pkg/cli/*.go \ internal/pkg/cmd/*.go \ internal/pkg/cmd/*/*.go \ + internal/pkg/sshclient/*.go \ go.mod \ Makefile diff --git a/go.mod b/go.mod index f96c327..16d0adb 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( github.com/containerd/console v1.0.2 github.com/kuttiproject/driver-vbox v0.2.1 - github.com/kuttiproject/kuttilib v0.2.0 + github.com/kuttiproject/kuttilib v0.3.0 github.com/kuttiproject/kuttilog v0.1.2 github.com/kuttiproject/workspace v0.2.2 github.com/spf13/cobra v1.1.3 diff --git a/go.sum b/go.sum index 62c0fed..606a846 100644 --- a/go.sum +++ b/go.sum @@ -108,8 +108,8 @@ github.com/kuttiproject/driver-vbox v0.2.1 h1:daC5F8dQEd3f1sxAThIwVZuTTtStWfTrOM github.com/kuttiproject/driver-vbox v0.2.1/go.mod h1:Qx1UUIEaT/GpZ2XYIuEKNCWwWCyTB3BlkTHrZRL0OFk= github.com/kuttiproject/drivercore v0.1.2 h1:t/AKAh48130SgOGi2kNmuoi3KkaIpHZx+vL6tqKsPGY= github.com/kuttiproject/drivercore v0.1.2/go.mod h1:1C1TMGfQ4u/5ltHUtl4hL7gYzXZyZQVhkOha4GuOzk0= -github.com/kuttiproject/kuttilib v0.2.0 h1:csbVAMZjLjTx6viMO/gVJFfzfJnRL78UH5XTHFLvbs4= -github.com/kuttiproject/kuttilib v0.2.0/go.mod h1:naguNcnr0FjTVm+Hzxb+LlXQHrDGuFN3igO9fF1AvbI= +github.com/kuttiproject/kuttilib v0.3.0 h1:PlSdyQVrV50wQNIKnf+qK/5N+ckmVjVEDo2GZK9R9Pc= +github.com/kuttiproject/kuttilib v0.3.0/go.mod h1:naguNcnr0FjTVm+Hzxb+LlXQHrDGuFN3igO9fF1AvbI= github.com/kuttiproject/kuttilog v0.1.2 h1:VEqVWrR3M6RME6aoUuVwXNO3bRpHKNp0ISAP/vqncpg= github.com/kuttiproject/kuttilog v0.1.2/go.mod h1:OO3dHpXm1/Pjlc57R4c0e/C+ZWkYlY3Fd9Ikn8xPXi4= github.com/kuttiproject/workspace v0.2.2 h1:1eNdMooB6Oq7jq2wodbuaY+IVZSYcEuXnqY3e00s64Y= diff --git a/internal/pkg/cmd/cluster/exports.go b/internal/pkg/cmd/cluster/exports.go index 053a4af..ed06fd4 100644 --- a/internal/pkg/cmd/cluster/exports.go +++ b/internal/pkg/cmd/cluster/exports.go @@ -22,7 +22,7 @@ func NameValidArgs(c *cobra.Command, args []string, toComplete string) ([]string } // StartNode starts a node. -func StartNode(cluster *kuttilib.Cluster, nodename string) error { +func StartNode(cluster *kuttilib.Cluster, nodename string, force bool) error { node, ok := cluster.GetNode(nodename) if !ok { return cli.WrapErrorMessagef( @@ -33,7 +33,8 @@ func StartNode(cluster *kuttilib.Cluster, nodename string) error { } nodestatus := node.Status() - if nodestatus == kuttilib.NodeStatusRunning { + + if nodestatus == kuttilib.NodeStatusRunning && (!force) { return cli.WrapErrorMessagef( 1, "node '%v' already started", @@ -52,7 +53,12 @@ func StartNode(cluster *kuttilib.Cluster, nodename string) error { } kuttilog.Printf(kuttilog.Info, "Starting node %v...", nodename) - err := node.Start() + var err error + if force { + err = node.ForceStart() + } else { + err = node.Start() + } if err != nil { kuttilog.Printf( kuttilog.Info, @@ -72,7 +78,7 @@ func StartNode(cluster *kuttilib.Cluster, nodename string) error { } // StopNode stops a node. -func StopNode(cluster *kuttilib.Cluster, nodename string) error { +func StopNode(cluster *kuttilib.Cluster, nodename string, force bool) error { node, ok := cluster.GetNode(nodename) if !ok { return cli.WrapErrorMessagef( @@ -83,7 +89,8 @@ func StopNode(cluster *kuttilib.Cluster, nodename string) error { } nodestatus := node.Status() - if nodestatus == kuttilib.NodeStatusStopped { + + if nodestatus == kuttilib.NodeStatusStopped && (!force) { return cli.WrapErrorMessagef( 1, "node '%v' already stopped", @@ -101,8 +108,13 @@ func StopNode(cluster *kuttilib.Cluster, nodename string) error { ) } + var err error kuttilog.Printf(kuttilog.Info, "Stopping node %v...", nodename) - err := node.Stop() + if force { + err = node.ForceStop() + } else { + err = node.Stop() + } if err != nil { return cli.WrapErrorMessagef( 1, diff --git a/internal/pkg/cmd/cluster/functions.go b/internal/pkg/cmd/cluster/functions.go index 05bb6b7..f92ca33 100644 --- a/internal/pkg/cmd/cluster/functions.go +++ b/internal/pkg/cmd/cluster/functions.go @@ -231,7 +231,7 @@ func clusterUpCommand(c *cobra.Command, args []string) error { } for _, nodename := range cluster.NodeNames() { - StartNode(cluster, nodename) + StartNode(cluster, nodename, false) } return nil @@ -255,7 +255,7 @@ func clusterDownCommand(c *cobra.Command, args []string) error { } for _, nodename := range cluster.NodeNames() { - StopNode(cluster, nodename) + StopNode(cluster, nodename, false) } return nil diff --git a/internal/pkg/cmd/node/commands.go b/internal/pkg/cmd/node/commands.go index 10421fc..c6d7b3a 100644 --- a/internal/pkg/cmd/node/commands.go +++ b/internal/pkg/cmd/node/commands.go @@ -74,7 +74,11 @@ var nodeCmd = &cli.Command{ RunE: nodeStartCommand, SilenceErrors: true, }, - SetFlagsFunc: SetClusterFlag, + SetFlagsFunc: func(c *cobra.Command) { + SetClusterFlag(c) + + c.Flags().BoolP("force", "f", false, "forcibly start node (emergency use only)") + }, }, { Cmd: &cobra.Command{ @@ -85,8 +89,23 @@ var nodeCmd = &cli.Command{ RunE: nodeStopCommand, SilenceErrors: true, }, - SetFlagsFunc: SetClusterFlag, + SetFlagsFunc: func(c *cobra.Command) { + SetClusterFlag(c) + + c.Flags().BoolP("force", "f", false, "forcibly stop node (emergency use only)") + }, }, + // { + // Cmd: &cobra.Command{ + // Use: "recover NODENAME", + // Short: "Try to recover an unresponsive node", + // Args: cobra.ExactValidArgs(1), + // ValidArgsFunction: NameValidArgs, + // RunE: nodeRecoverCommand, + // SilenceErrors: true, + // }, + // SetFlagsFunc: SetClusterFlag, + // }, { Cmd: &cobra.Command{ Use: "publish NODENAME", diff --git a/internal/pkg/cmd/node/functions.go b/internal/pkg/cmd/node/functions.go index 20ea480..ed4bde9 100644 --- a/internal/pkg/cmd/node/functions.go +++ b/internal/pkg/cmd/node/functions.go @@ -220,12 +220,13 @@ func nodeStartCommand(c *cobra.Command, args []string) error { ) } + forceflag, _ := c.Flags().GetBool("force") if len(args) == 1 { - return clustercmd.StartNode(cluster, args[0]) + return clustercmd.StartNode(cluster, args[0], forceflag) } for _, nodename := range args { - err = clustercmd.StartNode(cluster, nodename) + err = clustercmd.StartNode(cluster, nodename, forceflag) if err != nil { kuttilog.Printf(kuttilog.Info, "Warning: %v.", err) } @@ -249,12 +250,13 @@ func nodeStopCommand(c *cobra.Command, args []string) error { ) } + forceflag, _ := c.Flags().GetBool("force") if len(args) == 1 { - return clustercmd.StopNode(cluster, args[0]) + return clustercmd.StopNode(cluster, args[0], forceflag) } for _, nodename := range args { - err = clustercmd.StopNode(cluster, nodename) + err = clustercmd.StopNode(cluster, nodename, forceflag) if err != nil { kuttilog.Printf(kuttilog.Info, "Warning: %v.", err) } @@ -263,6 +265,74 @@ func nodeStopCommand(c *cobra.Command, args []string) error { return nil } +// Triaged to a future release +// func nodeRecoverCommand(c *cobra.Command, args []string) error { +// c.SilenceUsage = true + +// cluster, err := getCluster(c) +// if err != nil { +// return err +// } + +// node, ok := cluster.GetNode(args[0]) +// if !ok { +// return cli.WrapErrorMessagef( +// 2, +// "node %v not found", +// args[0], +// ) +// } + +// // First, capture the status +// status := node.Status() + +// // Then try to force start the node +// err = node.ForceStart() + +// // If that works, stop +// if err == nil { +// // TODO: Message here +// return nil +// } +// // Then try to force stop the node +// err = node.ForceStop() + +// // If that does not work, stop with error +// if err != nil { +// // TODO: Better error message here +// return cli.WrapError(1, err) +// } + +// // If captured status was stopped, stop +// if status == kuttilib.NodeStatusStopped { +// // TODO: Message here +// return nil +// } +// // Try to normal start the node +// err = node.Start() + +// // If that works, stop +// if err == nil { +// // TODO: Message here +// return nil +// } +// // Try to force start the node +// err = node.ForceStart() + +// // If that works, stop +// if err == nil { +// // TODO: Message here +// return nil +// } + +// // Stop with error +// return cli.WrapErrorMessagef( +// 1, +// "could not recover node. You may have to delete and recreate it. The last error returned was: %v", +// err, +// ) +// } + func nodePublishCommand(c *cobra.Command, args []string) error { c.SilenceUsage = true @@ -392,7 +462,7 @@ func nodeSSHCommand(c *cobra.Command, args []string) error { if !cluster.Driver().UsesNATNetworking() { return cli.WrapErrorMessage( 1, - "the SSH command currently on works on clusters that use NAT networking", + "the SSH command currently only works on clusters that use NAT networking", ) }