Cloudcone是2017年成立于美国的一家主机供应商。继vultr变得“不靠谱”和低价vm资源经常“售尽”的情况下,我转而使用cloudcone提供的vps服务用于日常学习和开发。
除了支持按小时计费、提供更换IP服务及支持支付宝/paypal等灵活的支付方式外,我最看重的是,它的价格能做到真正的弹性,即在vm关机状态不计费CPU/内存等费用,比online费用大概减半。
类似于其他云供应商平台,cloudcone还提供了开放的API来管理云主机。
这里为了方便使用和合理控制资源,自己便用go写了一个小程序
代码实现
主要逻辑如下,通过cloudcone 提供的API。每天晚上23左右关机,次日早晨7点左右启动 如果调用失败的话,会再次重试一次。(如果依然不行的话,就当运气不好了:<)
以下是草稿版本代码
- secret.go
package main
var (
// 配置相关信息,这里我填写的是假的啦
serverID = 123456
appSecret = "Q2aQr9323QE233r2"
hashCode = "zXqV22222222222222222222222222222222225bL"
)
serverID
可以在compute菜单控制面板下查看,或查看浏览器url里的参数。
appSecret
和hashCode
访问https://app.cloudcone.com/user/api创建即可。
- main.go
/*
* cloudcone api https://api.cloudcone.com/
*/
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"sync"
"time"
"github.com/robfig/cron"
)
// CloudconeResponse represents the API Response
type CloudconeResponse struct {
Status int
Message string
Data interface{} `json:"_data"`
}
const (
baseAPIURL = "https://api.cloudcone.com/api/v1"
)
var (
client = &http.Client{}
)
func main() {
f, err := os.OpenFile("app.log", os.O_RDWR|os.O_CREATE|os.O_APPEND|os.O_TRUNC, 0644)
if err != nil {
log.Fatalf("Error while opening file: %v\n", err)
}
defer f.Close()
log.SetOutput(f)
wg := sync.WaitGroup{}
_, cancel := context.WithCancel(context.Background())
c := cron.New()
// boot at 07:00
c.AddFunc("0 0 7 * * *", func() {
log.Println("booting at time", time.Now())
if err := boot(); err != nil {
log.Println(err)
time.Sleep(15 * time.Second)
// try again
boot()
}
})
// shutdown at 23:45
c.AddFunc("0 45 23 * * *", func() {
log.Println("Shutdown at time", time.Now())
if err := shutdown(); err != nil {
log.Println(err)
time.Sleep(15 * time.Second)
// try again
shutdown()
}
})
c.Start()
wg.Add(1)
go func() {
defer wg.Done()
os.Stdin.Read(make([]byte, 1)) // wait for Enter keystroke
c.Stop()
cancel() // cancel the associated context
}()
log.Println("Running...")
wg.Wait()
}
func boot() error {
// compute/:id/boot
endpoint := fmt.Sprintf("%s/compute/%d/%s", baseAPIURL, serverID, "boot")
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return err
}
req.Header.Set("App-Secret", appSecret)
req.Header.Set("Hash", hashCode)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Println(string(body))
var dat CloudconeResponse
if err := json.Unmarshal(body, &dat); err != nil {
return err
}
if dat.Status != 1 {
return errors.New(dat.Message)
}
log.Println(dat.Message)
return nil
}
func shutdown() error {
// compute/:id/shutdown
endpoint := fmt.Sprintf("%s/compute/%d/%s", baseAPIURL, serverID, "shutdown")
req, err := http.NewRequest("GET", endpoint, nil)
if err != nil {
return err
}
req.Header.Set("App-Secret", appSecret)
req.Header.Set("Hash", hashCode)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
var dat CloudconeResponse
if err := json.Unmarshal(body, &dat); err != nil {
return err
}
if dat.Status != 1 {
return errors.New(dat.Message)
}
log.Println(dat.Message)
return nil
}