diff --git a/cloud/qcloud/qcloud.go b/cloud/qcloud/qcloud.go index d9fcf73..3c7de19 100644 --- a/cloud/qcloud/qcloud.go +++ b/cloud/qcloud/qcloud.go @@ -47,7 +47,7 @@ func NewClient() *Client { return &Client{client} } -func (c *Client) Create(count int64, netaccess bool) error { +func (c *Client) Create(count int64, netaccess, windows bool) error { // 实例化一个请求对象,每个接口都会对应一个request对象 request := cvm.NewRunInstancesRequest() @@ -61,9 +61,16 @@ func (c *Client) Create(count int64, netaccess bool) error { } request.InstanceType = common.StringPtr(viper.GetString("qcloud.instance.type")) request.ImageId = common.StringPtr(viper.GetString("qcloud.instance.image")) + disk := viper.GetInt64("qcloud.instance.disk") + if disk == 0 { + disk = 50 + } + if windows && disk < 100 { + disk = 100 + } request.SystemDisk = &cvm.SystemDisk{ DiskType: common.StringPtr("CLOUD_PREMIUM"), - DiskSize: common.Int64Ptr(50), + DiskSize: common.Int64Ptr(disk), } request.VirtualPrivateCloud = &cvm.VirtualPrivateCloud{ VpcId: common.StringPtr(viper.GetString("qcloud.instance.network.vpc.id")), @@ -84,10 +91,18 @@ func (c *Client) Create(count int64, netaccess bool) error { } } request.InstanceCount = common.Int64Ptr(int64(count)) - request.InstanceName = common.StringPtr(fmt.Sprintf("spot-%s", time.Now().Format("20060102150405"))) - request.LoginSettings = &cvm.LoginSettings{ - KeyIds: common.StringPtrs(viper.GetStringSlice("qcloud.instance.auth.sshkey.ids")), + namePrefix := "spot" + if windows { + request.LoginSettings = &cvm.LoginSettings{ + KeepImageLogin: common.StringPtr("true"), + } + namePrefix = "spot-windows" + } else { + request.LoginSettings = &cvm.LoginSettings{ + KeyIds: common.StringPtrs(viper.GetStringSlice("qcloud.instance.auth.sshkey.ids")), + } } + request.InstanceName = common.StringPtr(fmt.Sprintf("%s-%s", namePrefix, time.Now().Format("20060102150405"))) request.SecurityGroupIds = common.StringPtrs(viper.GetStringSlice("qcloud.instance.securitygroup.ids")) request.EnhancedService = &cvm.EnhancedService{ SecurityService: &cvm.RunSecurityServiceEnabled{ @@ -141,11 +156,11 @@ func (c *Client) List() ([]Instance, error) { var ins []Instance for _, i := range response.Response.InstanceSet { if strings.HasPrefix(*i.InstanceName, "spot") { - ip := "" + ip := "-" if len(i.PrivateIpAddresses) != 0 { ip = *i.PrivateIpAddresses[0] } - eip := "" + eip := "-" if len(i.PublicIpAddresses) != 0 { eip = *i.PublicIpAddresses[0] } @@ -193,3 +208,24 @@ func (c *Client) Drop(ids []string) error { } return nil } + +func (c *Client) Restart(id string) error { + // 实例化一个请求对象,每个接口都会对应一个request对象 + request := cvm.NewRebootInstancesRequest() + + request.InstanceIds = common.StringPtrs([]string{id}) + // SOFT 表示软关机 + // HARD 表示硬关机 + // SOFT_FIRST 表示优先软关机,失败再执行硬关机 + request.StopType = common.StringPtr("SOFT_FIRST") + + // 返回的resp是一个RebootInstancesResponse的实例,与请求对象对应 + _, err := c.RebootInstances(request) + if _, ok := err.(*errors.TencentCloudSDKError); ok { + return fmt.Errorf("tencent api error has returned: %v", err) + } + if err != nil { + return err + } + return nil +} diff --git a/cmd/destroy.go b/cmd/destroy.go index 8a93e1b..f1851f0 100644 --- a/cmd/destroy.go +++ b/cmd/destroy.go @@ -22,30 +22,35 @@ func cmdDestroy() *cobra.Command { if err != nil { return err } - if len(vms) == 0 { + + okvms := []qcloud.Instance{} + var ids []string + for _, vm := range vms { + if vm.InstanceState == "RUNNING" { + ids = append(ids, vm.InstanceID) + okvms = append(okvms, vm) + } + } + if len(okvms) == 0 { logrus.Info("没有可销毁的虚拟机") return nil } if all { - logrus.Info("销毁所有虚拟机") - var ids []string - for _, vm := range vms { - ids = append(ids, vm.InstanceID) - } + logrus.Infof("销毁所有虚拟机, 数目: %d", len(ids)) return client.Drop(ids) } prompt := promptui.Select{ Label: "选择虚拟机", - Items: vms, + Items: okvms, Templates: &promptui.SelectTemplates{ Label: "{{ . }}?", - Active: "\U0001F449 {{ .PrivateIpAddresses | cyan }} ({{ .InstanceName | red }})", - Inactive: " {{ .PrivateIpAddresses | cyan }} ({{ .InstanceName | red }})", - Selected: "\U0001F389 {{ .PrivateIpAddresses | green }}", + Active: "\U0001F449 {{ .PrivateIPAddresses | cyan }} ({{ .InstanceName | red }})", + Inactive: " {{ .PrivateIPAddresses | cyan }} ({{ .InstanceName | red }})", + Selected: "\U0001F389 {{ .PrivateIPAddresses | green }}", }, Size: 4, Searcher: func(input string, index int) bool { - vm := vms[index] + vm := okvms[index] name := vm.PrivateIPAddresses return strings.Contains(name, input) }, @@ -56,7 +61,7 @@ func cmdDestroy() *cobra.Command { return err } - return client.Drop([]string{vms[i].InstanceID}) + return client.Drop([]string{okvms[i].InstanceID}) }, } c.Flags().BoolVarP(&all, "all", "a", false, "销毁所有虚拟机") diff --git a/cmd/new.go b/cmd/new.go index 1885854..54368a1 100644 --- a/cmd/new.go +++ b/cmd/new.go @@ -9,16 +9,18 @@ import ( func cmdNew() *cobra.Command { var count int64 var netaccess bool + var windows bool c := &cobra.Command{ Use: "new", Aliases: []string{"up", "create"}, Short: "新建腾讯云虚拟机", RunE: func(c *cobra.Command, args []string) error { client := qcloud.NewClient() - return client.Create(count, netaccess) + return client.Create(count, netaccess, windows) }, } c.Flags().Int64VarP(&count, "count", "c", 1, "虚拟机数量") c.Flags().BoolVar(&netaccess, "net", true, "是否开启公网访问, 单节点生效") + c.Flags().BoolVar(&windows, "windows", false, "windows节点") return c } diff --git a/cmd/restart.go b/cmd/restart.go new file mode 100644 index 0000000..db7b037 --- /dev/null +++ b/cmd/restart.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "github.com/manifoldco/promptui" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/ysicing/spot/cloud/qcloud" +) + +func cmdRestart() *cobra.Command { + c := &cobra.Command{ + Use: "restart", + Aliases: []string{"rs"}, + Short: "重启腾讯云竞价虚拟机", + RunE: func(c *cobra.Command, args []string) error { + client := qcloud.NewClient() + vms, err := client.List() + if err != nil { + return err + } + + okvms := []qcloud.Instance{} + for _, vm := range vms { + if vm.InstanceState == "RUNNING" { + okvms = append(okvms, vm) + } + } + okvms = append(okvms, qcloud.Instance{ + InstanceID: "ins-4yw9saqt", + InstanceName: "name", + PrivateIPAddresses: "pip", + InstanceState: "RUNNING", + }) + if len(okvms) == 0 { + logrus.Info("没有可重启的虚拟机") + return nil + } + templates := &promptui.SelectTemplates{ + Label: "{{ . }}", + Active: "\U0001F449 {{ .PrivateIPAddresses | cyan }} ({{ .InstanceName | red }})", + Inactive: " {{ .PrivateIPAddresses | cyan }} ({{ .InstanceName | red }})", + Selected: "\U0001F389 {{ .PrivateIPAddresses | green }}", + } + prompt := promptui.Select{ + Label: "选择虚拟机", + Items: okvms, + Templates: templates, + } + + i, _, err := prompt.Run() + if err != nil { + return err + } + return client.Restart(okvms[i].InstanceID) + }, + } + return c +} diff --git a/cmd/root.go b/cmd/root.go index ac72b0e..224dc9f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -34,6 +34,7 @@ func BuildRoot() *cobra.Command { rootCmd.AddCommand(cmdNew()) rootCmd.AddCommand(cmdList()) rootCmd.AddCommand(cmdDestroy()) + rootCmd.AddCommand(cmdRestart()) return rootCmd } @@ -44,7 +45,7 @@ func NewRootCmd() *cobra.Command { SilenceUsage: true, SilenceErrors: true, Short: "腾讯云虚拟机管理工具", - Version: "0.0.1", + Version: "0.0.2", PersistentPreRunE: func(cobraCmd *cobra.Command, args []string) error { if globalFlags.Debug { logrus.SetLevel(logrus.DebugLevel) diff --git a/config.example.yaml b/config.example.yaml index 215c30f..bcbd41d 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -9,6 +9,7 @@ qcloud: instance: image: img-id type: SA2.MEDIUM4 + disk: 50 network: vpc: id: vpc-id @@ -16,7 +17,7 @@ qcloud: id: subnet-id auth: sshkey: - ids: - - sshkey-id + ids: + - sshkey-id securitygroup: id: sg-id diff --git a/go.mod b/go.mod index f511f7b..25628ea 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/ysicing/spot go 1.19 require ( + github.com/davecgh/go-spew v1.1.1 github.com/ergoapi/util v0.2.21 github.com/gosuri/uitable v0.0.4 github.com/manifoldco/promptui v0.9.0 @@ -44,7 +45,7 @@ require ( github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.1 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect + golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect golang.org/x/text v0.3.7 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 009098a..470244b 100644 --- a/go.sum +++ b/go.sum @@ -569,8 +569,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=