608 lines
16 KiB
Go
608 lines
16 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/sacloud/libsacloud"
|
|
"github.com/sacloud/libsacloud/sacloud"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
// SakuraCloudAPIRoot APIリクエスト送信先ルートURL(末尾にスラッシュを含まない)
|
|
SakuraCloudAPIRoot = "https://secure.sakura.ad.jp/cloud/zone"
|
|
)
|
|
|
|
// Client APIクライアント
|
|
type Client struct {
|
|
// AccessToken アクセストークン
|
|
AccessToken string
|
|
// AccessTokenSecret アクセストークンシークレット
|
|
AccessTokenSecret string
|
|
// Zone 対象ゾーン
|
|
Zone string
|
|
*API
|
|
// TraceMode トレースモード
|
|
TraceMode bool
|
|
// DefaultTimeoutDuration デフォルトタイムアウト間隔
|
|
DefaultTimeoutDuration time.Duration
|
|
// ユーザーエージェント
|
|
UserAgent string
|
|
// Accept-Language
|
|
AcceptLanguage string
|
|
// リクエストパラメーター トレーサー
|
|
RequestTracer io.Writer
|
|
// レスポンス トレーサー
|
|
ResponseTracer io.Writer
|
|
// 503エラー時のリトライ回数
|
|
RetryMax int
|
|
// 503エラー時のリトライ待ち時間
|
|
RetryInterval time.Duration
|
|
}
|
|
|
|
// NewClient APIクライアント作成
|
|
func NewClient(token, tokenSecret, zone string) *Client {
|
|
c := &Client{
|
|
AccessToken: token,
|
|
AccessTokenSecret: tokenSecret,
|
|
Zone: zone,
|
|
TraceMode: false,
|
|
DefaultTimeoutDuration: 20 * time.Minute,
|
|
UserAgent: fmt.Sprintf("libsacloud/%s", libsacloud.Version),
|
|
AcceptLanguage: "",
|
|
RetryMax: 0,
|
|
RetryInterval: 5 * time.Second,
|
|
}
|
|
c.API = newAPI(c)
|
|
return c
|
|
}
|
|
|
|
// Clone APIクライアント クローン作成
|
|
func (c *Client) Clone() *Client {
|
|
n := &Client{
|
|
AccessToken: c.AccessToken,
|
|
AccessTokenSecret: c.AccessTokenSecret,
|
|
Zone: c.Zone,
|
|
TraceMode: c.TraceMode,
|
|
DefaultTimeoutDuration: c.DefaultTimeoutDuration,
|
|
UserAgent: c.UserAgent,
|
|
AcceptLanguage: c.AcceptLanguage,
|
|
RetryMax: c.RetryMax,
|
|
RetryInterval: c.RetryInterval,
|
|
}
|
|
n.API = newAPI(n)
|
|
return n
|
|
}
|
|
|
|
func (c *Client) getEndpoint() string {
|
|
return fmt.Sprintf("%s/%s", SakuraCloudAPIRoot, c.Zone)
|
|
}
|
|
|
|
func (c *Client) isOkStatus(code int) bool {
|
|
codes := map[int]bool{
|
|
200: true,
|
|
201: true,
|
|
202: true,
|
|
204: true,
|
|
305: false,
|
|
400: false,
|
|
401: false,
|
|
403: false,
|
|
404: false,
|
|
405: false,
|
|
406: false,
|
|
408: false,
|
|
409: false,
|
|
411: false,
|
|
413: false,
|
|
415: false,
|
|
500: false,
|
|
503: false,
|
|
}
|
|
return codes[code]
|
|
}
|
|
|
|
func (c *Client) newRequest(method, uri string, body interface{}) ([]byte, error) {
|
|
var (
|
|
client = &retryableHTTPClient{
|
|
retryMax: c.RetryMax,
|
|
retryInterval: c.RetryInterval,
|
|
}
|
|
err error
|
|
req *request
|
|
)
|
|
var url = uri
|
|
if !strings.HasPrefix(url, "https://") {
|
|
url = fmt.Sprintf("%s/%s", c.getEndpoint(), uri)
|
|
}
|
|
|
|
if body != nil {
|
|
var bodyJSON []byte
|
|
bodyJSON, err = json.Marshal(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if method == "GET" {
|
|
url = fmt.Sprintf("%s?%s", url, bytes.NewBuffer(bodyJSON))
|
|
req, err = newRequest(method, url, nil)
|
|
} else {
|
|
req, err = newRequest(method, url, bytes.NewReader(bodyJSON))
|
|
}
|
|
b, _ := json.MarshalIndent(body, "", "\t")
|
|
if c.TraceMode {
|
|
log.Printf("[libsacloud:Client#request] method : %#v , url : %s , \nbody : %s", method, url, b)
|
|
}
|
|
if c.RequestTracer != nil {
|
|
c.RequestTracer.Write(b)
|
|
}
|
|
} else {
|
|
req, err = newRequest(method, url, nil)
|
|
if c.TraceMode {
|
|
log.Printf("[libsacloud:Client#request] method : %#v , url : %s ", method, url)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error with request: %v - %q", url, err)
|
|
}
|
|
|
|
req.SetBasicAuth(c.AccessToken, c.AccessTokenSecret)
|
|
req.Header.Add("X-Sakura-Bigint-As-Int", "1") //Use BigInt on resource ids.
|
|
//if c.TraceMode {
|
|
// req.Header.Add("X-Sakura-API-Beautify", "1") // format response-JSON
|
|
//}
|
|
req.Header.Add("User-Agent", c.UserAgent)
|
|
if c.AcceptLanguage != "" {
|
|
req.Header.Add("Accept-Language", c.AcceptLanguage)
|
|
}
|
|
req.Method = method
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
data, err := ioutil.ReadAll(resp.Body)
|
|
|
|
v := &map[string]interface{}{}
|
|
json.Unmarshal(data, v)
|
|
b, _ := json.MarshalIndent(v, "", "\t")
|
|
if c.ResponseTracer != nil {
|
|
c.ResponseTracer.Write(b)
|
|
}
|
|
|
|
if c.TraceMode {
|
|
log.Printf("[libsacloud:Client#response] : %s", b)
|
|
}
|
|
if !c.isOkStatus(resp.StatusCode) {
|
|
|
|
errResponse := &sacloud.ResultErrorValue{}
|
|
err := json.Unmarshal(data, errResponse)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error in response: %s", string(data))
|
|
}
|
|
return nil, NewError(resp.StatusCode, errResponse)
|
|
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
type lenReader interface {
|
|
Len() int
|
|
}
|
|
|
|
type request struct {
|
|
// body is a seekable reader over the request body payload. This is
|
|
// used to rewind the request data in between retries.
|
|
body io.ReadSeeker
|
|
|
|
// Embed an HTTP request directly. This makes a *Request act exactly
|
|
// like an *http.Request so that all meta methods are supported.
|
|
*http.Request
|
|
}
|
|
|
|
func newRequest(method, url string, body io.ReadSeeker) (*request, error) {
|
|
var rcBody io.ReadCloser
|
|
if body != nil {
|
|
rcBody = ioutil.NopCloser(body)
|
|
}
|
|
|
|
httpReq, err := http.NewRequest(method, url, rcBody)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if lr, ok := body.(lenReader); ok {
|
|
httpReq.ContentLength = int64(lr.Len())
|
|
}
|
|
|
|
return &request{body, httpReq}, nil
|
|
}
|
|
|
|
type retryableHTTPClient struct {
|
|
http.Client
|
|
retryInterval time.Duration
|
|
retryMax int
|
|
}
|
|
|
|
func (c *retryableHTTPClient) Do(req *request) (*http.Response, error) {
|
|
for i := 0; ; i++ {
|
|
|
|
if req.body != nil {
|
|
if _, err := req.body.Seek(0, 0); err != nil {
|
|
return nil, fmt.Errorf("failed to seek body: %v", err)
|
|
}
|
|
}
|
|
|
|
res, err := c.Client.Do(req.Request)
|
|
if res != nil && res.StatusCode != 503 {
|
|
return res, err
|
|
}
|
|
if res != nil && res.Body != nil {
|
|
res.Body.Close()
|
|
}
|
|
|
|
if err != nil {
|
|
return res, err
|
|
}
|
|
|
|
remain := c.retryMax - i
|
|
if remain == 0 {
|
|
break
|
|
}
|
|
time.Sleep(c.retryInterval)
|
|
}
|
|
|
|
return nil, fmt.Errorf("%s %s giving up after %d attempts",
|
|
req.Method, req.URL, c.retryMax+1)
|
|
}
|
|
|
|
// API libsacloudでサポートしているAPI群
|
|
type API struct {
|
|
AuthStatus *AuthStatusAPI // 認証状態API
|
|
AutoBackup *AutoBackupAPI // 自動バックアップAPI
|
|
Archive *ArchiveAPI // アーカイブAPI
|
|
Bill *BillAPI // 請求情報API
|
|
Bridge *BridgeAPI // ブリッジAPi
|
|
CDROM *CDROMAPI // ISOイメージAPI
|
|
Database *DatabaseAPI // データベースAPI
|
|
Disk *DiskAPI // ディスクAPI
|
|
DNS *DNSAPI // DNS API
|
|
Facility *FacilityAPI // ファシリティAPI
|
|
GSLB *GSLBAPI // GSLB API
|
|
Icon *IconAPI // アイコンAPI
|
|
Interface *InterfaceAPI // インターフェースAPI
|
|
Internet *InternetAPI // ルーターAPI
|
|
IPAddress *IPAddressAPI // IPアドレスAPI
|
|
IPv6Addr *IPv6AddrAPI // IPv6アドレスAPI
|
|
IPv6Net *IPv6NetAPI // IPv6ネットワークAPI
|
|
License *LicenseAPI // ライセンスAPI
|
|
LoadBalancer *LoadBalancerAPI // ロードバランサーAPI
|
|
MobileGateway *MobileGatewayAPI // モバイルゲートウェイAPI
|
|
NewsFeed *NewsFeedAPI // フィード(障害/メンテナンス情報)API
|
|
NFS *NFSAPI // NFS API
|
|
Note *NoteAPI // スタートアップスクリプトAPI
|
|
PacketFilter *PacketFilterAPI // パケットフィルタAPI
|
|
PrivateHost *PrivateHostAPI // 専有ホストAPI
|
|
Product *ProductAPI // 製品情報API
|
|
Server *ServerAPI // サーバーAPI
|
|
SIM *SIMAPI // SIM API
|
|
SimpleMonitor *SimpleMonitorAPI // シンプル監視API
|
|
SSHKey *SSHKeyAPI // 公開鍵API
|
|
Subnet *SubnetAPI // IPv4ネットワークAPI
|
|
Switch *SwitchAPI // スイッチAPI
|
|
VPCRouter *VPCRouterAPI // VPCルーターAPI
|
|
WebAccel *WebAccelAPI // ウェブアクセラレータAPI
|
|
}
|
|
|
|
// GetAuthStatusAPI 認証状態API取得
|
|
func (api *API) GetAuthStatusAPI() *AuthStatusAPI {
|
|
return api.AuthStatus
|
|
}
|
|
|
|
// GetAutoBackupAPI 自動バックアップAPI取得
|
|
func (api *API) GetAutoBackupAPI() *AutoBackupAPI {
|
|
return api.AutoBackup
|
|
}
|
|
|
|
// GetArchiveAPI アーカイブAPI取得
|
|
func (api *API) GetArchiveAPI() *ArchiveAPI {
|
|
return api.Archive
|
|
}
|
|
|
|
// GetBillAPI 請求情報API取得
|
|
func (api *API) GetBillAPI() *BillAPI {
|
|
return api.Bill
|
|
}
|
|
|
|
// GetBridgeAPI ブリッジAPI取得
|
|
func (api *API) GetBridgeAPI() *BridgeAPI {
|
|
return api.Bridge
|
|
}
|
|
|
|
// GetCDROMAPI ISOイメージAPI取得
|
|
func (api *API) GetCDROMAPI() *CDROMAPI {
|
|
return api.CDROM
|
|
}
|
|
|
|
// GetDatabaseAPI データベースAPI取得
|
|
func (api *API) GetDatabaseAPI() *DatabaseAPI {
|
|
return api.Database
|
|
}
|
|
|
|
// GetDiskAPI ディスクAPI取得
|
|
func (api *API) GetDiskAPI() *DiskAPI {
|
|
return api.Disk
|
|
}
|
|
|
|
// GetDNSAPI DNSAPI取得
|
|
func (api *API) GetDNSAPI() *DNSAPI {
|
|
return api.DNS
|
|
}
|
|
|
|
// GetRegionAPI リージョンAPI取得
|
|
func (api *API) GetRegionAPI() *RegionAPI {
|
|
return api.Facility.GetRegionAPI()
|
|
}
|
|
|
|
// GetZoneAPI ゾーンAPI取得
|
|
func (api *API) GetZoneAPI() *ZoneAPI {
|
|
return api.Facility.GetZoneAPI()
|
|
}
|
|
|
|
// GetGSLBAPI GSLB API取得
|
|
func (api *API) GetGSLBAPI() *GSLBAPI {
|
|
return api.GSLB
|
|
}
|
|
|
|
// GetIconAPI アイコンAPI取得
|
|
func (api *API) GetIconAPI() *IconAPI {
|
|
return api.Icon
|
|
}
|
|
|
|
// GetInterfaceAPI インターフェースAPI取得
|
|
func (api *API) GetInterfaceAPI() *InterfaceAPI {
|
|
return api.Interface
|
|
}
|
|
|
|
// GetInternetAPI ルーターAPI取得
|
|
func (api *API) GetInternetAPI() *InternetAPI {
|
|
return api.Internet
|
|
}
|
|
|
|
// GetIPAddressAPI IPアドレスAPI取得
|
|
func (api *API) GetIPAddressAPI() *IPAddressAPI {
|
|
return api.IPAddress
|
|
}
|
|
|
|
// GetIPv6AddrAPI IPv6アドレスAPI取得
|
|
func (api *API) GetIPv6AddrAPI() *IPv6AddrAPI {
|
|
return api.IPv6Addr
|
|
}
|
|
|
|
// GetIPv6NetAPI IPv6ネットワークAPI取得
|
|
func (api *API) GetIPv6NetAPI() *IPv6NetAPI {
|
|
return api.IPv6Net
|
|
}
|
|
|
|
// GetLicenseAPI ライセンスAPI取得
|
|
func (api *API) GetLicenseAPI() *LicenseAPI {
|
|
return api.License
|
|
}
|
|
|
|
// GetLoadBalancerAPI ロードバランサーAPI取得
|
|
func (api *API) GetLoadBalancerAPI() *LoadBalancerAPI {
|
|
return api.LoadBalancer
|
|
}
|
|
|
|
// GetMobileGatewayAPI モバイルゲートウェイAPI取得
|
|
func (api *API) GetMobileGatewayAPI() *MobileGatewayAPI {
|
|
return api.MobileGateway
|
|
}
|
|
|
|
// GetNewsFeedAPI フィード(障害/メンテナンス情報)API取得
|
|
func (api *API) GetNewsFeedAPI() *NewsFeedAPI {
|
|
return api.NewsFeed
|
|
}
|
|
|
|
// GetNFSAPI NFS API取得
|
|
func (api *API) GetNFSAPI() *NFSAPI {
|
|
return api.NFS
|
|
}
|
|
|
|
// GetNoteAPI スタートアップAPI取得
|
|
func (api *API) GetNoteAPI() *NoteAPI {
|
|
return api.Note
|
|
}
|
|
|
|
// GetPacketFilterAPI パケットフィルタAPI取得
|
|
func (api *API) GetPacketFilterAPI() *PacketFilterAPI {
|
|
return api.PacketFilter
|
|
}
|
|
|
|
// GetPrivateHostAPI 専有ホストAPI取得
|
|
func (api *API) GetPrivateHostAPI() *PrivateHostAPI {
|
|
return api.PrivateHost
|
|
}
|
|
|
|
// GetProductServerAPI サーバープランAPI取得
|
|
func (api *API) GetProductServerAPI() *ProductServerAPI {
|
|
return api.Product.GetProductServerAPI()
|
|
}
|
|
|
|
// GetProductLicenseAPI ライセンスプランAPI取得
|
|
func (api *API) GetProductLicenseAPI() *ProductLicenseAPI {
|
|
return api.Product.GetProductLicenseAPI()
|
|
}
|
|
|
|
// GetProductDiskAPI ディスクプランAPI取得
|
|
func (api *API) GetProductDiskAPI() *ProductDiskAPI {
|
|
return api.Product.GetProductDiskAPI()
|
|
}
|
|
|
|
// GetProductInternetAPI ルータープランAPI取得
|
|
func (api *API) GetProductInternetAPI() *ProductInternetAPI {
|
|
return api.Product.GetProductInternetAPI()
|
|
}
|
|
|
|
// GetPublicPriceAPI 価格情報API取得
|
|
func (api *API) GetPublicPriceAPI() *PublicPriceAPI {
|
|
return api.Product.GetPublicPriceAPI()
|
|
}
|
|
|
|
// GetServerAPI サーバーAPI取得
|
|
func (api *API) GetServerAPI() *ServerAPI {
|
|
return api.Server
|
|
}
|
|
|
|
// GetSIMAPI SIM API取得
|
|
func (api *API) GetSIMAPI() *SIMAPI {
|
|
return api.SIM
|
|
}
|
|
|
|
// GetSimpleMonitorAPI シンプル監視API取得
|
|
func (api *API) GetSimpleMonitorAPI() *SimpleMonitorAPI {
|
|
return api.SimpleMonitor
|
|
}
|
|
|
|
// GetSSHKeyAPI SSH公開鍵API取得
|
|
func (api *API) GetSSHKeyAPI() *SSHKeyAPI {
|
|
return api.SSHKey
|
|
}
|
|
|
|
// GetSubnetAPI サブネットAPI取得
|
|
func (api *API) GetSubnetAPI() *SubnetAPI {
|
|
return api.Subnet
|
|
}
|
|
|
|
// GetSwitchAPI スイッチAPI取得
|
|
func (api *API) GetSwitchAPI() *SwitchAPI {
|
|
return api.Switch
|
|
}
|
|
|
|
// GetVPCRouterAPI VPCルーターAPI取得
|
|
func (api *API) GetVPCRouterAPI() *VPCRouterAPI {
|
|
return api.VPCRouter
|
|
}
|
|
|
|
// GetWebAccelAPI ウェブアクセラレータAPI取得
|
|
func (api *API) GetWebAccelAPI() *WebAccelAPI {
|
|
return api.WebAccel
|
|
}
|
|
|
|
// ProductAPI 製品情報関連API群
|
|
type ProductAPI struct {
|
|
Server *ProductServerAPI // サーバープランAPI
|
|
License *ProductLicenseAPI // ライセンスプランAPI
|
|
Disk *ProductDiskAPI // ディスクプランAPI
|
|
Internet *ProductInternetAPI // ルータープランAPI
|
|
PrivateHost *ProductPrivateHostAPI // 専有ホストプランAPI
|
|
Price *PublicPriceAPI // 価格情報API
|
|
}
|
|
|
|
// GetProductServerAPI サーバープランAPI取得
|
|
func (api *ProductAPI) GetProductServerAPI() *ProductServerAPI {
|
|
return api.Server
|
|
}
|
|
|
|
// GetProductLicenseAPI ライセンスプランAPI取得
|
|
func (api *ProductAPI) GetProductLicenseAPI() *ProductLicenseAPI {
|
|
return api.License
|
|
}
|
|
|
|
// GetProductDiskAPI ディスクプランAPI取得
|
|
func (api *ProductAPI) GetProductDiskAPI() *ProductDiskAPI {
|
|
return api.Disk
|
|
}
|
|
|
|
// GetProductInternetAPI ルータープランAPI取得
|
|
func (api *ProductAPI) GetProductInternetAPI() *ProductInternetAPI {
|
|
return api.Internet
|
|
}
|
|
|
|
// GetProductPrivateHostAPI 専有ホストプラン取得API
|
|
func (api *ProductAPI) GetProductPrivateHostAPI() *ProductPrivateHostAPI {
|
|
return api.PrivateHost
|
|
}
|
|
|
|
// GetPublicPriceAPI 価格情報API取得
|
|
func (api *ProductAPI) GetPublicPriceAPI() *PublicPriceAPI {
|
|
return api.Price
|
|
}
|
|
|
|
// FacilityAPI ファシリティ関連API群
|
|
type FacilityAPI struct {
|
|
Region *RegionAPI // リージョンAPI
|
|
Zone *ZoneAPI // ゾーンAPI
|
|
}
|
|
|
|
// GetRegionAPI リージョンAPI取得
|
|
func (api *FacilityAPI) GetRegionAPI() *RegionAPI {
|
|
return api.Region
|
|
}
|
|
|
|
// GetZoneAPI ゾーンAPI取得
|
|
func (api *FacilityAPI) GetZoneAPI() *ZoneAPI {
|
|
return api.Zone
|
|
}
|
|
|
|
func newAPI(client *Client) *API {
|
|
return &API{
|
|
AuthStatus: NewAuthStatusAPI(client),
|
|
AutoBackup: NewAutoBackupAPI(client),
|
|
Archive: NewArchiveAPI(client),
|
|
Bill: NewBillAPI(client),
|
|
Bridge: NewBridgeAPI(client),
|
|
CDROM: NewCDROMAPI(client),
|
|
Database: NewDatabaseAPI(client),
|
|
Disk: NewDiskAPI(client),
|
|
DNS: NewDNSAPI(client),
|
|
Facility: &FacilityAPI{
|
|
Region: NewRegionAPI(client),
|
|
Zone: NewZoneAPI(client),
|
|
},
|
|
GSLB: NewGSLBAPI(client),
|
|
Icon: NewIconAPI(client),
|
|
Interface: NewInterfaceAPI(client),
|
|
Internet: NewInternetAPI(client),
|
|
IPAddress: NewIPAddressAPI(client),
|
|
IPv6Addr: NewIPv6AddrAPI(client),
|
|
IPv6Net: NewIPv6NetAPI(client),
|
|
License: NewLicenseAPI(client),
|
|
LoadBalancer: NewLoadBalancerAPI(client),
|
|
MobileGateway: NewMobileGatewayAPI(client),
|
|
NewsFeed: NewNewsFeedAPI(client),
|
|
NFS: NewNFSAPI(client),
|
|
Note: NewNoteAPI(client),
|
|
PacketFilter: NewPacketFilterAPI(client),
|
|
PrivateHost: NewPrivateHostAPI(client),
|
|
Product: &ProductAPI{
|
|
Server: NewProductServerAPI(client),
|
|
License: NewProductLicenseAPI(client),
|
|
Disk: NewProductDiskAPI(client),
|
|
Internet: NewProductInternetAPI(client),
|
|
PrivateHost: NewProductPrivateHostAPI(client),
|
|
Price: NewPublicPriceAPI(client),
|
|
},
|
|
Server: NewServerAPI(client),
|
|
SIM: NewSIMAPI(client),
|
|
SimpleMonitor: NewSimpleMonitorAPI(client),
|
|
SSHKey: NewSSHKeyAPI(client),
|
|
Subnet: NewSubnetAPI(client),
|
|
Switch: NewSwitchAPI(client),
|
|
VPCRouter: NewVPCRouterAPI(client),
|
|
WebAccel: NewWebAccelAPI(client),
|
|
}
|
|
}
|