diff --git a/README.md b/README.md index 7f58882d..679d56fa 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,22 @@ sudo autok3s ... \ --k3s-version="v1.18.9-k3s1" ``` +### Start K3s Cluster +```bash +sudo autok3s start \ + --provider alibaba \ + --region \ + --name +``` + +### Stop K3s Cluster +```bash +sudo autok3s stop \ + --provider alibaba \ + --region \ + --name +``` + ### Delete K3s Cluster ```bash sudo autok3s delete \ diff --git a/cmd/start.go b/cmd/start.go new file mode 100644 index 00000000..530dfa25 --- /dev/null +++ b/cmd/start.go @@ -0,0 +1,62 @@ +package cmd + +import ( + "github.com/cnrancher/autok3s/cmd/common" + "github.com/cnrancher/autok3s/pkg/providers" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + startCmd = &cobra.Command{ + Use: "start", + Short: "Start k3s cluster", + Example: ` autok3s start --name cluster`, + } + stProvider = "" + stP providers.Provider +) + +func init() { + startCmd.Flags().StringVarP(&stProvider, "provider", "p", stProvider, "Provider is a module which provides an interface for managing cloud resources") +} + +func StartCommand() *cobra.Command { + + pStr := common.FlagHackLookup("--provider") + + if pStr != "" { + if reg, err := providers.Register(pStr); err != nil { + logrus.Fatalln(err) + } else { + stP = reg + } + + startCmd.Flags().AddFlagSet(stP.GetCredentialFlags(startCmd)) + startCmd.Flags().AddFlagSet(stP.GetStartFlags(startCmd)) + } + + startCmd.Run = func(cmd *cobra.Command, args []string) { + common.BindPFlags(cmd, stP) + + // read options from config. + if err := viper.ReadInConfig(); err != nil { + logrus.Fatalln(err) + } + + // sync config data to local cfg path. + if err := viper.WriteConfig(); err != nil { + logrus.Fatalln(err) + } + + stP.GenerateClusterName() + + if err := stP.StartK3sCluster(); err != nil { + logrus.Fatalln(err) + } + } + + return startCmd +} diff --git a/cmd/stop.go b/cmd/stop.go new file mode 100644 index 00000000..e75df5f3 --- /dev/null +++ b/cmd/stop.go @@ -0,0 +1,64 @@ +package cmd + +import ( + "github.com/cnrancher/autok3s/cmd/common" + "github.com/cnrancher/autok3s/pkg/providers" + + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var ( + stopCmd = &cobra.Command{ + Use: "stop", + Short: "Stop k3s cluster", + Example: ` autok3s stop --name cluster`, + } + spProvider = "" + spForce = false + spP providers.Provider +) + +func init() { + stopCmd.Flags().StringVarP(&spProvider, "provider", "p", spProvider, "Provider is a module which provides an interface for managing cloud resources") + stopCmd.Flags().BoolVarP(&spForce, "force", "f", spForce, "Force stop cluster") +} + +func StopCommand() *cobra.Command { + + pStr := common.FlagHackLookup("--provider") + + if pStr != "" { + if reg, err := providers.Register(pStr); err != nil { + logrus.Fatalln(err) + } else { + spP = reg + } + + stopCmd.Flags().AddFlagSet(spP.GetCredentialFlags(stopCmd)) + stopCmd.Flags().AddFlagSet(spP.GetStopFlags(stopCmd)) + } + + stopCmd.Run = func(cmd *cobra.Command, args []string) { + common.BindPFlags(cmd, spP) + + // read options from config. + if err := viper.ReadInConfig(); err != nil { + logrus.Fatalln(err) + } + + // sync config data to local cfg path. + if err := viper.WriteConfig(); err != nil { + logrus.Fatalln(err) + } + + spP.GenerateClusterName() + + if err := spP.StopK3sCluster(spForce); err != nil { + logrus.Fatalln(err) + } + } + + return stopCmd +} diff --git a/main.go b/main.go index 02b1811b..8b95cc34 100644 --- a/main.go +++ b/main.go @@ -35,7 +35,8 @@ func main() { rootCmd := cmd.Command() rootCmd.AddCommand(cmd.CompletionCommand(), cmd.VersionCommand(gitVersion, gitCommit, gitTreeState, buildDate), - cmd.ListCommand(), cmd.CreateCommand(), cmd.JoinCommand(), cmd.KubectlCommand(), cmd.DeleteCommand()) + cmd.ListCommand(), cmd.CreateCommand(), cmd.JoinCommand(), cmd.KubectlCommand(), cmd.DeleteCommand(), + cmd.StartCommand(), cmd.StopCommand()) if err := rootCmd.Execute(); err != nil { os.Exit(1) diff --git a/pkg/providers/alibaba/alibaba.go b/pkg/providers/alibaba/alibaba.go index b53c7f81..6199d2c0 100644 --- a/pkg/providers/alibaba/alibaba.go +++ b/pkg/providers/alibaba/alibaba.go @@ -127,14 +127,18 @@ func (p *Alibaba) CreateK3sCluster(ssh *types.SSH) (err error) { workerEips []vpc.EipAddress ) + p.logger.Debugf("[%s] start to allocate %d eip(s) for master(s)\n", p.GetProviderName(), masterNum) if masterEips, err = p.allocateEipAddresses(masterNum); err != nil { return err } + p.logger.Debugf("[%s] successfully allocated %d eip(s) for master(s)\n", p.GetProviderName(), masterNum) if workerNum > 0 { + p.logger.Debugf("[%s] start to allocate %d eip(s) for worker(s)\n", p.GetProviderName(), workerNum) if workerEips, err = p.allocateEipAddresses(workerNum); err != nil { return err } + p.logger.Debugf("[%s] successfully allocated %d eip(s) for worker(s)\n", p.GetProviderName(), workerNum) } // run ecs master instances. @@ -150,7 +154,7 @@ func (p *Alibaba) CreateK3sCluster(ssh *types.SSH) (err error) { } // wait ecs instances to be running status. - if err = p.getInstanceStatus(); err != nil { + if err = p.getInstanceStatus(alibaba.StatusRunning); err != nil { return } @@ -158,6 +162,7 @@ func (p *Alibaba) CreateK3sCluster(ssh *types.SSH) (err error) { // associate master eip if masterEips != nil { + p.logger.Debugf("[%s] start to associate %d eip(s) for master(s)\n", p.GetProviderName(), masterNum) for i, master := range p.Status.MasterNodes { err := p.associateEipAddress(master.InstanceID, masterEips[i].AllocationId) if err != nil { @@ -167,10 +172,12 @@ func (p *Alibaba) CreateK3sCluster(ssh *types.SSH) (err error) { p.Status.MasterNodes[i].PublicIPAddress = append(p.Status.MasterNodes[i].PublicIPAddress, masterEips[i].IpAddress) associatedEipIds = append(associatedEipIds, masterEips[i].AllocationId) } + p.logger.Debugf("[%s] successfully associated %d eip(s) for master(s)\n", p.GetProviderName(), masterNum) } // associate worker eip if workerEips != nil { + p.logger.Debugf("[%s] start to associate %d eip(s) for worker(s)\n", p.GetProviderName(), workerNum) for i, worker := range p.Status.WorkerNodes { err := p.associateEipAddress(worker.InstanceID, workerEips[i].AllocationId) if err != nil { @@ -180,6 +187,7 @@ func (p *Alibaba) CreateK3sCluster(ssh *types.SSH) (err error) { p.Status.WorkerNodes[i].PublicIPAddress = append(p.Status.WorkerNodes[i].PublicIPAddress, workerEips[i].IpAddress) associatedEipIds = append(associatedEipIds, workerEips[i].AllocationId) } + p.logger.Debugf("[%s] successfully associated %d eip(s) for worker(s)\n", p.GetProviderName(), workerNum) } // wait eip to be InUse status. @@ -227,15 +235,19 @@ func (p *Alibaba) JoinK3sNode(ssh *types.SSH) error { ) if masterNum > 0 { + p.logger.Debugf("[%s] start to allocate %d eip(s) for master(s)\n", p.GetProviderName(), masterNum) if masterEips, err = p.allocateEipAddresses(masterNum); err != nil { return err } + p.logger.Debugf("[%s] successfully allocated %d eip(s) for master(s)\n", p.GetProviderName(), masterNum) } if workerNum > 0 { + p.logger.Debugf("[%s] start to allocate %d eip(s) for worker(s)\n", p.GetProviderName(), workerNum) if workerEips, err = p.allocateEipAddresses(workerNum); err != nil { return err } + p.logger.Debugf("[%s] successfully allocated %d eip(s) for worker(s)\n", p.GetProviderName(), workerNum) } // run ecs master instances. @@ -253,7 +265,7 @@ func (p *Alibaba) JoinK3sNode(ssh *types.SSH) error { } // wait ecs instances to be running status. - if err := p.getInstanceStatus(); err != nil { + if err := p.getInstanceStatus(alibaba.StatusRunning); err != nil { return err } @@ -262,6 +274,7 @@ func (p *Alibaba) JoinK3sNode(ssh *types.SSH) error { // associate master eip if masterEips != nil { j := 0 + p.logger.Debugf("[%s] start to associate %d eip(s) for master(s)\n", p.GetProviderName(), masterNum) for i, master := range p.Status.MasterNodes { if p.Status.MasterNodes[i].PublicIPAddress == nil { err := p.associateEipAddress(master.InstanceID, masterEips[j].AllocationId) @@ -274,11 +287,13 @@ func (p *Alibaba) JoinK3sNode(ssh *types.SSH) error { j++ } } + p.logger.Debugf("[%s] successfully associated %d eip(s) for master(s)\n", p.GetProviderName(), masterNum) } // associate worker eip if workerEips != nil { j := 0 + p.logger.Debugf("[%s] start to associate %d eip(s) for worker(s)\n", p.GetProviderName(), workerNum) for i, worker := range p.Status.WorkerNodes { if p.Status.WorkerNodes[i].PublicIPAddress == nil { err := p.associateEipAddress(worker.InstanceID, workerEips[j].AllocationId) @@ -291,6 +306,7 @@ func (p *Alibaba) JoinK3sNode(ssh *types.SSH) error { j++ } } + p.logger.Debugf("[%s] successfully associated %d eip(s) for worker(s)\n", p.GetProviderName(), workerNum) } // wait eip to be InUse status. @@ -423,6 +439,40 @@ func (p *Alibaba) DeleteK3sNode(f bool) error { return nil } +func (p *Alibaba) StartK3sCluster() error { + p.logger = common.NewLogger(common.Debug) + p.logger.Infof("[%s] executing start logic...\n", p.GetProviderName()) + + if err := p.generateClientSDK(); err != nil { + return err + } + + if err := p.startCluster(); err != nil { + return err + } + + p.logger.Infof("[%s] successfully executed start logic\n", p.GetProviderName()) + + return nil +} + +func (p *Alibaba) StopK3sCluster(f bool) error { + p.logger = common.NewLogger(common.Debug) + p.logger.Infof("[%s] executing stop logic...\n", p.GetProviderName()) + + if err := p.generateClientSDK(); err != nil { + return err + } + + if err := p.stopCluster(f); err != nil { + return err + } + + p.logger.Infof("[%s] successfully executed stop logic\n", p.GetProviderName()) + + return nil +} + func (p *Alibaba) generateClientSDK() error { if p.AccessKey == "" { p.AccessKey = viper.GetString(p.GetProviderName(), accessKeyID) @@ -548,7 +598,105 @@ func (p *Alibaba) deleteCluster(f bool) error { return nil } -func (p *Alibaba) getInstanceStatus() error { +func (p *Alibaba) startCluster() error { + exist, ids, err := p.IsClusterExist() + + if !exist { + return fmt.Errorf("[%s] calling preflight error: cluster name `%s` do not exist", + p.GetProviderName(), p.Name) + } + + if err == nil && len(ids) > 0 { + // ensure that the status of all instances is stopped. + if err := p.startAndStopCheck(alibaba.StatusStopped); err != nil { + return err + } + request := ecs.CreateStartInstancesRequest() + request.Scheme = "https" + request.InstanceId = &ids + + if _, err := p.c.StartInstances(request); err != nil { + return fmt.Errorf("[%s] calling startInstance error, msg: [%v]", p.GetProviderName(), err) + } + } + + for _, masterNode := range p.MasterNodes { + p.m.Store(masterNode.InstanceID, masterNode) + } + for _, workerNode := range p.WorkerNodes { + p.m.Store(workerNode.InstanceID, workerNode) + } + + // wait ecs instances to be running status. + if err = p.getInstanceStatus(alibaba.StatusRunning); err != nil { + return err + } + + err = cluster.SaveState(&types.Cluster{ + Metadata: p.Metadata, + Options: p.Options, + Status: p.Status, + }) + + if err != nil { + return fmt.Errorf("[%s] synchronizing .state file error, msg: [%v]", p.GetProviderName(), err) + } + return nil +} + +func (p *Alibaba) stopCluster(f bool) error { + exist, ids, err := p.IsClusterExist() + + if !exist { + return fmt.Errorf("[%s] calling preflight error: cluster name `%s` do not exist", p.GetProviderName(), p.Name) + } + + if err == nil && len(ids) > 0 { + // ensure that the status of all instances is running. + if err := p.startAndStopCheck(alibaba.StatusRunning); err != nil { + return err + } + request := ecs.CreateStopInstancesRequest() + request.Scheme = "https" + request.InstanceId = &ids + + if f { + // similar to power-off operation. + // all cached data not written to the storage device will be lost. + request.ForceStop = requests.NewBoolean(f) + } + + if _, err := p.c.StopInstances(request); err != nil { + return fmt.Errorf("[%s] calling stopInstance error, msg: [%v]", p.GetProviderName(), err) + } + } + + for _, masterNode := range p.MasterNodes { + p.m.Store(masterNode.InstanceID, masterNode) + } + for _, workerNode := range p.WorkerNodes { + p.m.Store(workerNode.InstanceID, workerNode) + } + + // wait ecs instances to be stopped status. + if err = p.getInstanceStatus(alibaba.StatusStopped); err != nil { + return err + } + + err = cluster.SaveState(&types.Cluster{ + Metadata: p.Metadata, + Options: p.Options, + Status: p.Status, + }) + + if err != nil { + return fmt.Errorf("[%s] synchronizing .state file error, msg: [%v]", p.GetProviderName(), err) + } + + return nil +} + +func (p *Alibaba) getInstanceStatus(aimStatus string) error { ids := make([]string, 0) p.m.Range(func(key, value interface{}) bool { ids = append(ids, key.(string)) @@ -574,10 +722,10 @@ func (p *Alibaba) getInstanceStatus() error { } for _, status := range response.InstanceStatuses.InstanceStatus { - if status.Status == alibaba.StatusRunning { + if status.Status == aimStatus { if value, ok := p.m.Load(status.InstanceId); ok { v := value.(types.Node) - v.InstanceStatus = alibaba.StatusRunning + v.InstanceStatus = aimStatus p.m.Store(status.InstanceId, v) } } else { @@ -839,6 +987,28 @@ func (p *Alibaba) joinCheck() error { return nil } +func (p *Alibaba) startAndStopCheck(aimStatus string) error { + response, err := p.describeInstances() + if err != nil { + return err + } + if response.IsSuccess() && len(response.Instances.Instance) > 0 { + unexpectedStatusCnt := 0 + for _, instance := range response.Instances.Instance { + if instance.Status != aimStatus { + unexpectedStatusCnt++ + p.logger.Warnf("[%s] instance [%s] status is %s, but it is expected to be %s\n", + p.GetProviderName(), instance.InstanceId, instance.Status, aimStatus) + } + } + if unexpectedStatusCnt > 0 { + return fmt.Errorf("[%s] status of %d instance(s) is unexpected", p.GetProviderName(), unexpectedStatusCnt) + } + return nil + } + return fmt.Errorf("[%s] unable to confirm the current status of instance(s)", p.GetProviderName()) +} + func (p *Alibaba) describeEipAddresses(allocationIds []string) (*vpc.DescribeEipAddressesResponse, error) { if allocationIds == nil { return nil, fmt.Errorf("[%s] allocationID can not be empty", p.GetProviderName()) @@ -875,10 +1045,11 @@ func (p *Alibaba) allocateEipAddresses(num int) ([]vpc.EipAddress, error) { eipIds = append(eipIds, eip.AllocationId) } tag := []vpc.TagResourcesTag{{Key: "autok3s", Value: "true"}, {Key: "cluster", Value: common.TagClusterPrefix + p.Name}} + p.logger.Debugf("[%s] start to tag eip(s):[%s]\n", p.GetProviderName(), eipIds) if err := p.tagVpcResources(resourceTypeEip, eipIds, tag); err != nil { p.logger.Errorf("[%s] error when tag eips: %s\n", p.GetProviderName(), err) } - + p.logger.Debugf("[%s] tagged eips\n", p.GetProviderName()) return eips, nil } @@ -942,24 +1113,28 @@ func (p *Alibaba) releaseEipAddresses(rollBack bool) { // unassociate master eip address. for _, master := range p.MasterNodes { if master.RollBack == rollBack { + p.logger.Debugf("[%s] start to unassociate eip address for %d master(s)\n", p.GetProviderName(), len(p.MasterNodes)) for _, allocationID := range master.EipAllocationIds { if err := p.unassociateEipAddress(allocationID); err != nil { p.logger.Errorf("[%s] error when unassociate eip address %s: %v\n", p.GetProviderName(), allocationID, err) } releaseEipIds = append(releaseEipIds, allocationID) } + p.logger.Debugf("[%s] unassociated eip address for master(s)\n", p.GetProviderName()) } } // unassociate worker eip address. for _, worker := range p.WorkerNodes { if worker.RollBack == rollBack { + p.logger.Debugf("[%s] start to unassociate eip address for %d worker(s)\n", p.GetProviderName(), len(p.WorkerNodes)) for _, allocationID := range worker.EipAllocationIds { if err := p.unassociateEipAddress(allocationID); err != nil { p.logger.Errorf("[%s] error when unassociate eip address %s: %v\n", p.GetProviderName(), allocationID, err) } releaseEipIds = append(releaseEipIds, allocationID) } + p.logger.Debugf("[%s] unassociated eip address for worker(s)\n", p.GetProviderName()) } } @@ -978,8 +1153,11 @@ func (p *Alibaba) releaseEipAddresses(rollBack bool) { // release eips. for _, allocationID := range allocationIds { + p.logger.Debugf("[%s] start to release eip: %s\n", p.GetProviderName(), allocationID) if err := p.releaseEipAddress(allocationID); err != nil { p.logger.Errorf("[%s] error when release eip address %s: %v\n", p.GetProviderName(), allocationID, err) + } else { + p.logger.Debugf("[%s] successfully released eip: %s\n", p.GetProviderName(), allocationID) } } } @@ -988,20 +1166,23 @@ func (p *Alibaba) getEipStatus(allocationIds []string, aimStatus string) error { if allocationIds == nil { return fmt.Errorf("[%s] allocationIds can not be empty", p.GetProviderName()) } + p.logger.Debugf("[%s] start to query eip status\n", p.GetProviderName()) if err := wait.ExponentialBackoff(common.Backoff, func() (bool, error) { response, err := p.describeEipAddresses(allocationIds) if err != nil || !response.IsSuccess() || len(response.EipAddresses.EipAddress) <= 0 { return false, nil } for _, eip := range response.EipAddresses.EipAddress { + p.logger.Debugf("[%s] status of eip [%s]:%s\n", p.GetProviderName(), eip.AllocationId, eip.Status) if eip.Status != aimStatus { return false, nil } } return true, nil }); err != nil { - return fmt.Errorf("[%s] error in querying eip %s status of [%s]: %v", p.GetProviderName(), aimStatus, strings.Join(allocationIds, ","), err) + return fmt.Errorf("[%s] error in querying eip %s status of [%s], msg: [%v]", p.GetProviderName(), aimStatus, allocationIds, err) } + p.logger.Debugf("[%s] successfully query eip status\n", p.GetProviderName()) return nil } diff --git a/pkg/providers/alibaba/flag.go b/pkg/providers/alibaba/flag.go index 9194208c..aca1d65f 100644 --- a/pkg/providers/alibaba/flag.go +++ b/pkg/providers/alibaba/flag.go @@ -77,6 +77,148 @@ func (p *Alibaba) GetCreateFlags(cmd *cobra.Command) *pflag.FlagSet { return cmd.Flags() } +func (p *Alibaba) GetStartFlags(cmd *cobra.Command) *pflag.FlagSet { + fs := []types.Flag{ + { + Name: "name", + P: &p.Name, + V: p.Name, + Usage: "Cluster name", + Required: true, + }, + { + Name: "region", + P: &p.Region, + V: p.Region, + Usage: "Region is physical locations (data centers) that spread all over the world to reduce the network latency", + Required: true, + }, + } + + for _, f := range fs { + if f.ShortHand == "" { + if cmd.Flags().Lookup(f.Name) == nil { + cmd.Flags().StringVar(f.P, f.Name, f.V, f.Usage) + } + } else { + if cmd.Flags().Lookup(f.Name) == nil { + cmd.Flags().StringVarP(f.P, f.Name, f.ShortHand, f.V, f.Usage) + } + } + } + + cmd.PreRunE = func(cmd *cobra.Command, args []string) error { + clusters, err := cluster.ReadFromState(&types.Cluster{ + Metadata: p.Metadata, + Options: p.Options, + }) + if err != nil { + return err + } + + var matched *types.Cluster + for _, c := range clusters { + if c.Provider == p.Provider && c.Name == fmt.Sprintf("%s.%s", p.Name, p.Region) { + matched = &c + } + } + + if matched != nil { + // start command need merge status value. + p.Status = matched.Status + p.mergeOptions(*matched) + } + + errFlags := make([]string, 0) + for _, f := range fs { + if f.Required && f.Name == "name" { + if *f.P == "" && f.V == "" { + errFlags = append(errFlags, f.Name) + } + } + } + + if len(errFlags) == 0 { + return nil + } + + return fmt.Errorf("required flags(s) \"%s\" not set", errFlags) + } + + return cmd.Flags() +} + +func (p *Alibaba) GetStopFlags(cmd *cobra.Command) *pflag.FlagSet { + fs := []types.Flag{ + { + Name: "name", + P: &p.Name, + V: p.Name, + Usage: "Cluster name", + Required: true, + }, + { + Name: "region", + P: &p.Region, + V: p.Region, + Usage: "Region is physical locations (data centers) that spread all over the world to reduce the network latency", + Required: true, + }, + } + + for _, f := range fs { + if f.ShortHand == "" { + if cmd.Flags().Lookup(f.Name) == nil { + cmd.Flags().StringVar(f.P, f.Name, f.V, f.Usage) + } + } else { + if cmd.Flags().Lookup(f.Name) == nil { + cmd.Flags().StringVarP(f.P, f.Name, f.ShortHand, f.V, f.Usage) + } + } + } + + cmd.PreRunE = func(cmd *cobra.Command, args []string) error { + clusters, err := cluster.ReadFromState(&types.Cluster{ + Metadata: p.Metadata, + Options: p.Options, + }) + if err != nil { + return err + } + + var matched *types.Cluster + for _, c := range clusters { + if c.Provider == p.Provider && c.Name == fmt.Sprintf("%s.%s", p.Name, p.Region) { + matched = &c + } + } + + if matched != nil { + // stop command need merge status value. + p.Status = matched.Status + p.mergeOptions(*matched) + } + + errFlags := make([]string, 0) + for _, f := range fs { + if f.Required && f.Name == "name" { + if *f.P == "" && f.V == "" { + errFlags = append(errFlags, f.Name) + } + } + } + + if len(errFlags) == 0 { + return nil + } + + return fmt.Errorf("required flags(s) \"%s\" not set", errFlags) + } + + return cmd.Flags() +} + func (p *Alibaba) GetDeleteFlags(cmd *cobra.Command) *pflag.FlagSet { fs := []types.Flag{ { diff --git a/pkg/providers/provider.go b/pkg/providers/provider.go index 839131cc..62de2cef 100644 --- a/pkg/providers/provider.go +++ b/pkg/providers/provider.go @@ -16,6 +16,10 @@ type Provider interface { GetCreateFlags(cmd *cobra.Command) *pflag.FlagSet // Join command flags. GetJoinFlags(cmd *cobra.Command) *pflag.FlagSet + // Stop command flags. + GetStopFlags(cmd *cobra.Command) *pflag.FlagSet + // Start command flags. + GetStartFlags(cmd *cobra.Command) *pflag.FlagSet // Delete command flags. GetDeleteFlags(cmd *cobra.Command) *pflag.FlagSet // Credential flags. @@ -34,6 +38,10 @@ type Provider interface { Rollback() error // K3s delete node interface. DeleteK3sNode(f bool) error + // K3s start cluster interface. + StartK3sCluster() error + // K3s stop cluster interface. + StopK3sCluster(f bool) error } func Register(provider string) (Provider, error) { diff --git a/pkg/types/alibaba/alibaba.go b/pkg/types/alibaba/alibaba.go index 84d7695a..e21a3032 100644 --- a/pkg/types/alibaba/alibaba.go +++ b/pkg/types/alibaba/alibaba.go @@ -1,8 +1,10 @@ package alibaba var ( - StatusPending = "Pending" - StatusRunning = "Running" + StatusPending = "Pending" + StatusRunning = "Running" + StatusStopping = "Stopping" + StatusStopped = "Stopped" ) type Options struct {