通過 Go 編程語言控制 SoftLayer API

panocha 7年前發布 | 26K 次閱讀 API Go語言 Google Go/Golang開發

想了解通過 Go 編程語言使用 SoftLayer Cloud API 的方方面面?本教程通過簡明且符合語言習慣的方式,展示如何使用服務、導航,使用數據結構,建立對象掩碼 (object mask) 和過濾器,以及如何訂購虛擬機等。

SoftLayer-Go 是一個針對 Go 編程語言的 SoftLayer API 客戶機庫。系統根據描述不同服務端點和數據類型的 SoftLayer API 元數據自動生成該庫。

softlayer-go的設計方式與導出給用戶的實際 SoftLayer API 的方式相同。也就是說,它有一組與 SoftLayer 中的數據類型類似的 結構 。另一組結構類似于 SoftLayer 中的服務,每種結構都附加了所有想要的方法。服務擁有一些額外的方法(稍后將介紹),讓您能指定方法的上下文。

下面對庫中的所有 Go 包進行了分類:

  • 數據類型(datatypes) 用于訪問 API 返回的以及您需要傳遞給 API 的所有結構,具體取決于所使用的服務方法。
  • 服務(services) 用于所有服務(采用結構的形式)和對應的方法。
  • 會話(session ) 包含與 API 端點通信時使用的所有傳輸邏輯;它同時支持 REST 和 XML-RPC 端點。
  • sl 提供了從數據類型結構獲取值和為結構設置值的便捷函數。
  • 過濾器(filter) 用于創建一個對象過濾器,指定您想從 API 獲取哪些字段。
  • 幫助器(helpers) 執行更詳細的工作,包括多次調用 API,或者獲取數據中心名稱的 ID 等常見任務。

以下各節將更詳細地介紹這些包。

安裝 softlayer-go

安裝該庫之前,您需要執行以下操作:

  • 安裝 Go
  • 獲取一個 GOPATH 環境變量

為了方便,還應將 GOPATH/bin 添加到您的 PATH 環境變量中。

要安裝該庫,通過命令行輸入以下命令:

go get github.com/softlayer/softlayer-go/...

最后,還應擁有一定的 Go 代碼閱讀和編寫經驗。

快速入門示例

我們查看一個使用 softlayer-go 庫的簡短示例:

package main

import (
    "fmt"
    "os"

    "github.com/softlayer/softlayer-go/session"
    "github.com/softlayer/softlayer-go/services"
)

const (
    USERNAME = "your user name"
    APIKEY = "your api key"
)

func main() {
    sess := session.New(USERNAME, APIKEY)
    accountService := services.GetAccountService(sess)
    account, err := accountService.GetObject()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }

    fmt.Println(*account.Id, *account.CompanyName)
}

總結一下上述代碼中執行的操作:

  1. 導入所需的 softlayer-go 包。
  2. 傳遞您的 SoftLayer 用戶名和 API 密鑰,創建一個新會話。
  3. 獲取 SoftLayer Account 服務的句柄。這里列出的所有 SoftLayer 服務都有一個 Get 方法。該方法名始終以 Get 為前綴,以 Service 為后綴,就像 services.GetServiceNameHereService(...) 中一樣。與 API 文檔中一樣,此名稱忽略了服務名稱中出現的任何下劃線。
  4. 在 Account 服務上調用一個方法;基本來講,您會獲得帳戶信息。這可能返回一個錯誤,此時需要您進行檢查。
  5. 輸出帳戶 ID 和帳戶名。

創建一個會話

我們仔細看看創建一個新會話的代碼行:

sess := session.New(USERNAME, APIKEY)

session.New() 可按如下順序接受一個包含(最多 4 個)字符串參數的變量列表:

  1. 用戶名
  2. API 密鑰
  3. API 端點
  4. HTTP 超時(以秒為單位)

API 端點和 HTTP 超時的缺省值分別為 https://api.softlayer.com/rest/v3 和 120 。可通過傳遞第三個和第四個參數,用您希望的任何方式覆蓋它們。

也可以從環境變量中讀取會話值。從中讀取會話值的環境變量(分別)為:

  1. SL_USERNAME
  2. SL_API_KEY
  3. SL_ENDPOINT_URL
  4. SL_TIMEOUT

如果您在環境變量中設置了用戶名和 API 密鑰,那么無需在代碼中包含參數就能創建一個新會話:

sess := session.New()

此外,也可以從本地配置文件中讀取這些會話值。該庫將在 ~/.softlayer (在 Windows 上為 %USERPROFILE%/.softlayer )上查找該文件。文件格式應該類似于:

[softlayer]
username = <your username>
api_key = <your api key>
endpoint_url = <optional>
timeout = <optional>

向 New() 傳遞參數始終會覆蓋環境變量或本地配置文件中的相應參數。環境變量優先于本地配置文件。

有了會話引用后,可以獲取任何 SoftLayer 服務的句柄。此時需要傳遞會話引用:

accountService := services.GetAccountService(sess)

實例化一個虛擬訪客

讓我們看看另一個示例。這次我們在 SoftLayer 上創建一個虛擬訪客:

package main

import (
    "fmt"
    "os"

    "github.com/softlayer/softlayer-go/datatypes"
    "github.com/softlayer/softlayer-go/services"
    "github.com/softlayer/softlayer-go/session"
    "github.com/softlayer/softlayer-go/sl"
)

func main() {
    sess := session.New()
    service := services.GetVirtualGuestService(sess)

    guestTpl := datatypes.Virtual_Guest{
        Hostname: sl.String("sample"),
        Domain: sl.String("example.com"),
        MaxMemory: sl.Int(2048),
        StartCpus: sl.Int(1),
        Datacenter: &datatypes.Location{Name: sl.String("sjc01")},
        OperatingSystemReferenceCode: sl.String("UBUNTU_LATEST"),
        LocalDiskFlag: sl.Bool(true),
    }

    guest, err := service.Mask("id;domain").CreateObject(&guestTpl)
    if err != nil {
        fmt.Println(err)
        os.Exit(-1)
    }

    fmt.Printf("New Virtual Guest created with ID %d\n", *guest.Id)
    fmt.Printf("Domain: %s\n", *guest.Domain)
}

要創建虛擬訪客實例,所使用的虛擬訪客結構必須允許指定某些必需的屬性。該結構會成為您的訪客模板。按照上面的方式初始化此模板后,可以調用虛擬訪客服務的 CreateObject() 方法,將該實例傳遞給訪客模板,從而在云上創建實例。

請注意 sl 包中的幫助器方法,可使用它們在結構中設置值。這些方法用于提供字面值 (literal value) 的指針。還要注意,我們使用了鏈式 (chained) 方法 Mask("id;domain") ,該方法用于在底層 API 調用上設置對象掩碼。以下各節將更詳細地介紹幫助器和掩碼。

服務方法

如前所述,SoftLayer 中的每個服務在服務包中都有一種結構。每種結構都包含該服務的所有預期方法。

我們看看 Virtual_Guest 服務中的一個方法簽名:

func (r Virtual_Guest) GetCpuMetricDataByDate(
    startDateTime *datatypes.Time,
    endDateTime *datatypes.Time,
    cpuIndexes []int) (resp []datatypes.Metric_Tracking_Object_Data, err error)

在 softlayer-go 中,每個方法參數都要求是一個指針,切片 (slice) 參數除外。對于切片參數,只需直接傳遞它,因為在 Go 中切片是隱式的指針。

每個方法也會返回一個錯誤。將使用 sl.Error 封裝所有錯誤(以下各節將更詳細地介紹它)。如果方法有其他返回值,這些值也會與錯誤一起返回,如上面的示例中所示。

調用上述方法的代碼如下所示:

sess := session.New()
service := services.GetVirtualGuestService(sess)

start := time.Date(2010, 12, 31, 0, 0, 0, 0, time.FixedZone("Atlantic", -60*60*4))
end := time.Now()

resp, err := service.GetCpuMetricDataByDate(
    &datatypes.Time{start}, &datatypes.Time{end},
    []int{1, 2, 3},
)

處理時間值時,該庫將它們作為自己的 datatypes.Time 類型來處理。但與時間相關的方法參數,以及與時間相關的任何數據類型字段是個例外。這樣庫能正確地從/向 SoftLayer API 端點解碼/編碼時間值,并始終一致地處理該值類型。

因此,在需要以方法參數的形式傳遞時間值時,用戶必須將它們封裝在 datatypes.Time 結構內。注意,我們傳遞了這些結構的引用,而整數數組則按原樣傳遞。

服務上下文修飾符(Service context modifiers)

使用 SoftLayer API 時,可以引用某個資源或對象的特定實例。事實上,一些方法必須這么做。例如,在請求獲得特定用戶的信息時,您必須指定用戶的 ID。但是,API 不會將此 ID 當作方法參數來處理,所以該庫將它作為上下文參數來處理:

service := services.GetUserCustomerService(sess)
user, err := service.Id(6786566).GetObject()

每個服務都有一組函數,您可使用它們傳遞這些上下文參數,比如您想從中獲取數據的資源的 ID,如上面的代碼所示。以下是此類函數的完整列表:

  • Id(id int) — 資源的 ID,將在該資源上執行操作。
  • Limit(limit int) — 對于返回一個集合的方法,設置要返回的結果的最大數量。
  • Offset(offset int) — 對于返回一個集合的方法,設置在標記所返回集合的起點前要跳過多少個結果。
  • Mask(id string) — 從 API 抓取對象時用于獲取關系屬性;也支持您指定您感興趣的字段,讓 API 僅返回該數據。
  • Filter(filter string) — 對于返回一個集合的方法,用于限制所返回的結果數量。

下面的代碼段將返回一個列表,其中僅包含一個公共映像,因為您已在過濾器中顯式指定了該映像的全局標識符。此外,API 僅返回在對象掩碼中已設置的字段。

service := services.GetVirtualGuestBlockDeviceTemplateGroupService(sess)

publicImages, err := service.
    Mask("id;name;note;summary").
    Filter(`{"globalIdentifier":{"operation":"2e61f677-752b-4020-a447-b138f5daa387"}}`).GetPublicImages()

對象掩碼和過濾器中使用的字段名需采用小寫形式。它們必須與 API 數據類型文檔相匹配。但是,出于可視性的原因,Go 中的相應數據結構對這些字段名稱采用大寫形式。例如,使用 *publicImages[0].Summary 訪問您在對象掩碼中指定的公共映像摘要。請參閱對象掩碼文檔了解更多信息。

此外,掩碼和過濾器僅應用于該請求。也就是說,如果您再次使用相同的服務引用,掩碼和過濾器將是空的。上面列出的其他上下文參數也是如此。如果想為以后的方法調用保留所設置的掩碼和過濾器,可保存上下文參數函數所返回的服務句柄:

serviceWithMaskAndFilter := service.
    Mask("id;name;note;summary").
    Filter(`{"globalIdentifier":{"operation":"2e61f677-752b-4020-a447-b138f5daa387"}}`)

publicImages, err := serviceWithMaskAndFilter.GetPublicImages()

對象過濾器

應將過濾器指定為字符串。考慮到對象過濾器可能變得很復雜,所以該庫提供了一個過濾器構建器讓此過程變得更輕松。例如,您可通過以下方式,使用過濾器構建器指定與上一段代碼中相同的過濾器:

service.Filter(
    filter.Path("globalIdentifier").
        Eq("2e61f677-752b-4020-a447-b138f5daa387").Build(),
).GetPublicImages()

也可以提前、批量和根據其他條件創建過濾器:

filters := filter.New(
    filter.Path("virtualGuests.hostname").StartsWith("demo"),
    filter.Path("virtualGuests.id").NotEq(1234),
)

隨后,可向該組中附加并使用另一個過濾器:

filters = append(filters, filter.Path("virtualGuests.domain").EndsWith("example.com"))

guests, err := accountService.Filter(filters.Build()).GetVirtualGuests()

可使用 filter.Path() 在 SoftLayer 中對象的嵌套關系屬性上指定一個條件。例如:

filter.Path("datacenter.locationStatus.status").Eq("ACTIVE")

sl 包中便捷的函數

與方法參數一樣,每種數據類型結構的每個字段都需要一個指針(用作切片的字段除外)。這樣,如果字段沒有值,該庫在將這些無用字段傳遞到 API 時可以在數據類型結構中省略它們,因為在 Go 中只有指針(和 interface{} 類型)可以是空的。

該庫有一個幫助器包,用于在從 SoftLayer 結構獲取值和設置值時幫助處理此情況。再看看前面的一個例子:

guestTpl := datatypes.Virtual_Guest{
    Hostname: sl.String("sample"),
    Domain: sl.String("example.com"),
    MaxMemory: sl.Int(2048),
    StartCpus: sl.Int(1),
    Datacenter: &datatypes.Location{Name: sl.String("sjc01")},
    OperatingSystemReferenceCode: sl.String("UBUNTU_LATEST"),
    LocalDiskFlag: sl.Bool(true),
}

sl. 包擁有針對字符串、整數、無符號整數和布爾值等原生類型的指針幫助器。它們僅返回您所傳遞的值的指針引用。如果沒有幫助器,就必須按如下方式重寫上述代碼:

hostname := "sample"
domain := "example.com"
maxMemory := 2048
startCpus := 1
datacenterName := "sjc01"
osName := "UBUNTU_LATEST"
localDiskFlag := true

guestTpl := datatypes.Virtual_Guest{
    Hostname: &hostname,
    Domain: &domain,
    MaxMemory: &maxMemory,
    StartCpus: &startCpus,
    Datacenter: &datatypes.Location{Name: &datacenterName},
    OperatingSystemReferenceCode: &osName,
    LocalDiskFlag: &localDiskFlag,
}

這在某些情況下可能很方便,尤其是希望將值始終用作常量時。但在其他情況下,它過于冗長。請盡量使用幫助器,讓代碼更簡潔。

需要在結構中 設置 值時,這些幫助器很有用,也有一些幫助器可從 SoftLayer 結構中 獲取 值。例如,在下面這段代碼中,您需要獲取虛擬訪客的安裝后腳本 URI:

// Don't assume the post install script uri is non-nil.
var scriptURI string
if guest.PostInstallScriptUri != nil {
    scriptURI = *guest.PostInstallScriptUri
}

借助 sl.Get 幫助器,可將上述代碼段縮寫為以下形式(借助 Go 類型斷言):

scriptURI := sl.Get(guest.PostInstallScriptUri).(string)

sl 包的 getter 幫助器包括:

  • Get(p interface{}, d ...interface{}) interface{} 返回 p 的值。如果 p 是一個指針,幫助器會為您解除對它的引用并返回值。也可傳入一個可選的缺省值作為第二個參數。如果 p 是空的,則返回 d 。否則,如果 p 是空的且沒有缺省值,則獲得 p 的 0 值(也就是說,如果 p 是一個字符串或字符串的指針,您將獲得空字符串。如果它是一個整數或整數的指針,您將獲得 0)。
  • GetOk(p interface{}) (interface{}, bool) 與 Get() 基本相同,但它將返回一個表示 p 是否為非空指針的額外布爾值。缺省值選項在這里沒有必要,因為重點在于您擁有自己的邏輯來處理沒有值的情況 — 這可能包括確定合適的缺省值。

有時您可能需要從 SoftLayer 結構中獲取一個深度嵌套的值。因為字段可以是指針,所以在 Go 語言中避免意外發生的最安全方法是在每個結構級別上測試是否包含空值:

var vlanId int
if guest.PrimaryNetworkComponent != nil {
    if guest.PrimaryNetworkComponent.NetworkVlan != nil {
        if guest.PrimaryNetworkComponent.NetworkVlan.Id != nil {
            vlanId = *guest.PrimaryNetworkComponent.NetworkVlan.Id
        }
    }
}

另一個幫助器可幫助您從類似這樣的嵌套位置獲取值,而且可消除大量的樣板代碼:

vlanId := sl.Grab(guest, "PrimaryNetworkComponent.NetworkVlan.Id").(int)

這些是 sl 包的 grab 幫助器:

  • Grab(s interface{}, path string, d ...interface{}) interface{} 返回您使用 s 作為起點的給定路徑所指定的值。 path 是一個點分格式的字段集合,它遍歷 s 來獲取要抓取的值。如果在遍歷的路徑中的任何位置得到一個空指針值,則返回一個具有合適類型的 0 值。
  • GrabOk(s interface{}, path string) (interface{}, bool) 類似于 Grab() ,但它返回另一個布爾值來表明它是否成功找到了一個值(如果未找到,它必須為您創建一個 0 值)。

在沒有可用的值并且您希望以特殊方式處理該情形時, GrabOk 很有用:

if vlanId, ok := sl.GrabOk(guest, "PrimaryNetworkComponent.NetworkVlan.Id").(int); !ok {
    log.Println("No VLAN ID found!")
}

處理錯誤

所有服務方法都可以返回錯誤。除了實現 Go 的 Error 接口,下面給出的錯誤還是某種 SoftLayer 類型的錯誤,您可獲取更多的相關信息。

type Error struct {
    StatusCode int
    Exception  string
    Message    string
    Wrapped    error
}

舉例而言,如果您想準確地確定一個錯誤是由資源不存在還是其他某個傳輸或 API 錯誤所引起的,這會很有幫助。

_, err := service.Id(0).      // invalid object ID
    GetObject()

if err != nil {
    // Note: type assertion is only necessary for inspecting individual fields
    apiErr, ok := err.(sl.Error)
    if apiError.StatusCode == 404 {
        // handle not found error here
    }
    // more generic error handling here
}

如果需要執行進一步的解封裝操作,始終可以在 .Wrapped 中找到原始錯誤。

if err != nil {
    // Note: type assertion is only necessary for inspecting individual fields
    apiErr, ok := err.(sl.Error)
    if apiError.StatusCode == 404 {
        // handle not found error here
    } else if netError, ok := apiError.Wrapped.(net.Error); ok && netError.Timeout() {
        // handle transport timeout error here
    }
    // more generic error handling here
}

XML-RPC 和基于密碼的身份驗證

SoftLayer API 同時支持 REST 和 XML-RPC 端點。二者的功能幾乎相同,但我知道有一種情況僅適用于 XML-RPC — 具體來講,當您需要使用密碼而不是 API 密鑰來向 API 執行身份驗證時。

為此,您需要在會話中指定 XML-RPC 端點并執行密碼身份驗證,如下所示:

func main() {
    // Create a session specifying an XML-RPC endpoint url.
    sess := &session.Session{
        Endpoint: "https://api.softlayer.com/xmlrpc/v3",
    }

    // Get a token from the api using your username and password
    userService := services.GetUserCustomerService(sess)
    token, err := userService.GetPortalLoginToken(USERNAME, PASSWORD, nil, nil)
    if err != nil {
        log.Fatal(err)
    }

    // Add user id and token to the session.
    sess.UserId = *token.UserId
    sess.AuthToken = *token.Hash
    // You have a complete authenticated session now.
    // Call any api from this point on as normal...

    keys, err := userService.Id(sess.UserId).GetApiAuthenticationKeys()
    if err != nil {
        log.Fatal(err)
    }

    log.Println("API Key:", *keys[0].AuthenticationKey)
}

調試

要查看 API 請求和響應日志,可按如下方式打開會話調試標志:

sess := session.New()
sess.Debug = true
// use the session reference throughout
accountService := services.GetAccountService(sess)
// ...

在試驗該庫和 SoftLayer API 時,這可能非常有用。

結束語

現在您已了解如何通過 Go 編程語言控制 SoftLayer API 了。您了解了會話初始化,獲取服務的句柄,方法調用,數據類型結構,以及如何更好地初始化和導航它們,如何實例化虛擬服務器,設置對象掩碼,使用過濾器構建器,處理錯誤,以及其他一些方面。做得好!

當然,這只是使用強大且功能豐富的云平臺 SoftLayer 所能完成工作的冰山一角。您可以訪問 “相關主題” 部分中的鏈接,尋找更高級的示例。

致謝:非常感謝 Michael Rieth 提出該庫和指令的第一個版本。

 

來自:http://www.ibm.com/developerworks/cn/cloud/library/cl-softlayer-go-overview/index.html?ca=drs-

 

 本文由用戶 panocha 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!