通过 Go 编程语言控制 SoftLayer API

panocha 4年前
   <p>想了解通过 Go 编程语言使用 SoftLayer Cloud API 的方方面面?本教程通过简明且符合语言习惯的方式,展示如何使用服务、导航,使用数据结构,建立对象掩码 (object mask) 和过滤器,以及如何订购虚拟机等。</p>    <p>SoftLayer-Go 是一个针对 Go 编程语言的 SoftLayer API 客户机库。系统根据描述不同服务端点和数据类型的 SoftLayer API 元数据自动生成该库。</p>    <p>softlayer-go的设计方式与导出给用户的实际 SoftLayer API 的方式相同。也就是说,它有一组与 SoftLayer 中的数据类型类似的 <strong>结构</strong> 。另一组结构类似于 SoftLayer 中的服务,每种结构都附加了所有想要的方法。服务拥有一些额外的方法(稍后将介绍),让您能指定方法的上下文。</p>    <p>下面对库中的所有 Go 包进行了分类:</p>    <ul>     <li><strong>数据类型(datatypes)</strong> 用于访问 API 返回的以及您需要传递给 API 的所有结构,具体取决于所使用的服务方法。</li>     <li><strong>服务(services)</strong> 用于所有服务(采用结构的形式)和对应的方法。</li>     <li><strong>会话(session )</strong> 包含与 API 端点通信时使用的所有传输逻辑;它同时支持 REST 和 XML-RPC 端点。</li>     <li><strong>sl</strong> 提供了从数据类型结构获取值和为结构设置值的便捷函数。</li>     <li><strong>过滤器(filter)</strong> 用于创建一个对象过滤器,指定您想从 API 获取哪些字段。</li>     <li><strong>帮助器(helpers)</strong> 执行更详细的工作,包括多次调用 API,或者获取数据中心名称的 ID 等常见任务。</li>    </ul>    <p>以下各节将更详细地介绍这些包。</p>    <h2>安装 softlayer-go</h2>    <p>安装该库之前,您需要执行以下操作:</p>    <ul>     <li>安装 Go</li>     <li>获取一个 GOPATH 环境变量</li>    </ul>    <p>为了方便,还应将 GOPATH/bin 添加到您的 PATH 环境变量中。</p>    <p>要安装该库,通过命令行输入以下命令:</p>    <pre>  <code class="language-go">go get github.com/softlayer/softlayer-go/...</code></pre>    <p>最后,还应拥有一定的 Go 代码阅读和编写经验。</p>    <h2>快速入门示例</h2>    <p>我们查看一个使用 softlayer-go 库的简短示例:</p>    <pre>  <code class="language-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)  }</code></pre>    <p>总结一下上述代码中执行的操作:</p>    <ol>     <li>导入所需的 softlayer-go 包。</li>     <li>传递您的 SoftLayer 用户名和 API 密钥,创建一个新会话。</li>     <li>获取 SoftLayer Account 服务的句柄。这里列出的所有 SoftLayer 服务都有一个 Get 方法。该方法名始终以 Get 为前缀,以 Service 为后缀,就像 services.GetServiceNameHereService(...) 中一样。与 API 文档中一样,此名称忽略了服务名称中出现的任何下划线。</li>     <li>在 Account 服务上调用一个方法;基本来讲,您会获得帐户信息。这可能返回一个错误,此时需要您进行检查。</li>     <li>输出帐户 ID 和帐户名。</li>    </ol>    <h2>创建一个会话</h2>    <p>我们仔细看看创建一个新会话的代码行:</p>    <pre>  <code class="language-go">sess := session.New(USERNAME, APIKEY)</code></pre>    <p>session.New() 可按如下顺序接受一个包含(最多 4 个)字符串参数的变量列表:</p>    <ol>     <li>用户名</li>     <li>API 密钥</li>     <li>API 端点</li>     <li>HTTP 超时(以秒为单位)</li>    </ol>    <p>API 端点和 HTTP 超时的缺省值分别为 https://api.softlayer.com/rest/v3 和 120 。可通过传递第三个和第四个参数,用您希望的任何方式覆盖它们。</p>    <p>也可以从环境变量中读取会话值。从中读取会话值的环境变量(分别)为:</p>    <ol>     <li>SL_USERNAME</li>     <li>SL_API_KEY</li>     <li>SL_ENDPOINT_URL</li>     <li>SL_TIMEOUT</li>    </ol>    <p>如果您在环境变量中设置了用户名和 API 密钥,那么无需在代码中包含参数就能创建一个新会话:</p>    <pre>  <code class="language-go">sess := session.New()</code></pre>    <p>此外,也可以从本地配置文件中读取这些会话值。该库将在 ~/.softlayer (在 Windows 上为 %USERPROFILE%/.softlayer )上查找该文件。文件格式应该类似于:</p>    <pre>  <code class="language-go">[softlayer]  username = <your username>  api_key = <your api key>  endpoint_url = <optional>  timeout = <optional></code></pre>    <p>向 New() 传递参数始终会覆盖环境变量或本地配置文件中的相应参数。环境变量优先于本地配置文件。</p>    <p>有了会话引用后,可以获取任何 SoftLayer 服务的句柄。此时需要传递会话引用:</p>    <pre>  <code class="language-go">accountService := services.GetAccountService(sess)</code></pre>    <h2>实例化一个虚拟访客</h2>    <p>让我们看看另一个示例。这次我们在 SoftLayer 上创建一个虚拟访客:</p>    <pre>  <code class="language-go">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)  }</code></pre>    <p>要创建虚拟访客实例,所使用的虚拟访客结构必须允许指定某些必需的属性。该结构会成为您的访客模板。按照上面的方式初始化此模板后,可以调用虚拟访客服务的 CreateObject() 方法,将该实例传递给访客模板,从而在云上创建实例。</p>    <p>请注意 sl 包中的帮助器方法,可使用它们在结构中设置值。这些方法用于提供字面值 (literal value) 的指针。还要注意,我们使用了链式 (chained) 方法 Mask("id;domain") ,该方法用于在底层 API 调用上设置对象掩码。以下各节将更详细地介绍帮助器和掩码。</p>    <h2>服务方法</h2>    <p>如前所述,SoftLayer 中的每个服务在服务包中都有一种结构。每种结构都包含该服务的所有预期方法。</p>    <p>我们看看 Virtual_Guest 服务中的一个方法签名:</p>    <pre>  <code class="language-go">func (r Virtual_Guest) GetCpuMetricDataByDate(      startDateTime *datatypes.Time,      endDateTime *datatypes.Time,      cpuIndexes []int) (resp []datatypes.Metric_Tracking_Object_Data, err error)</code></pre>    <p>在 softlayer-go 中,每个方法参数都要求是一个指针,切片 (slice) 参数除外。对于切片参数,只需直接传递它,因为在 Go 中切片是隐式的指针。</p>    <p>每个方法也会返回一个错误。将使用 sl.Error 封装所有错误(以下各节将更详细地介绍它)。如果方法有其他返回值,这些值也会与错误一起返回,如上面的示例中所示。</p>    <p>调用上述方法的代码如下所示:</p>    <pre>  <code class="language-go">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},  )</code></pre>    <p>处理时间值时,该库将它们作为自己的 datatypes.Time 类型来处理。但与时间相关的方法参数,以及与时间相关的任何数据类型字段是个例外。这样库能正确地从/向 SoftLayer API 端点解码/编码时间值,并始终一致地处理该值类型。</p>    <p>因此,在需要以方法参数的形式传递时间值时,用户必须将它们封装在 datatypes.Time 结构内。注意,我们传递了这些结构的引用,而整数数组则按原样传递。</p>    <h2>服务上下文修饰符(Service context modifiers)</h2>    <p>使用 SoftLayer API 时,可以引用某个资源或对象的特定实例。事实上,一些方法必须这么做。例如,在请求获得特定用户的信息时,您必须指定用户的 ID。但是,API 不会将此 ID 当作方法参数来处理,所以该库将它作为上下文参数来处理:</p>    <pre>  <code class="language-go">service := services.GetUserCustomerService(sess)  user, err := service.Id(6786566).GetObject()</code></pre>    <p>每个服务都有一组函数,您可使用它们传递这些上下文参数,比如您想从中获取数据的资源的 ID,如上面的代码所示。以下是此类函数的完整列表:</p>    <ul>     <li>Id(id int) — 资源的 ID,将在该资源上执行操作。</li>     <li>Limit(limit int) — 对于返回一个集合的方法,设置要返回的结果的最大数量。</li>     <li>Offset(offset int) — 对于返回一个集合的方法,设置在标记所返回集合的起点前要跳过多少个结果。</li>     <li>Mask(id string) — 从 API 抓取对象时用于获取关系属性;也支持您指定您感兴趣的字段,让 API 仅返回该数据。</li>     <li>Filter(filter string) — 对于返回一个集合的方法,用于限制所返回的结果数量。</li>    </ul>    <p>下面的代码段将返回一个列表,其中仅包含一个公共映像,因为您已在过滤器中显式指定了该映像的全局标识符。此外,API 仅返回在对象掩码中已设置的字段。</p>    <pre>  <code class="language-go">service := services.GetVirtualGuestBlockDeviceTemplateGroupService(sess)    publicImages, err := service.      Mask("id;name;note;summary").      Filter(`{"globalIdentifier":{"operation":"2e61f677-752b-4020-a447-b138f5daa387"}}`).GetPublicImages()</code></pre>    <p>对象掩码和过滤器中使用的字段名需采用小写形式。它们必须与 API 数据类型文档相匹配。但是,出于可视性的原因,Go 中的相应数据结构对这些字段名称采用大写形式。例如,使用 *publicImages[0].Summary 访问您在对象掩码中指定的公共映像摘要。请参阅对象掩码文档了解更多信息。</p>    <p>此外,掩码和过滤器仅应用于该请求。也就是说,如果您再次使用相同的服务引用,掩码和过滤器将是空的。上面列出的其他上下文参数也是如此。如果想为以后的方法调用保留所设置的掩码和过滤器,可保存上下文参数函数所返回的服务句柄:</p>    <pre>  <code class="language-go">serviceWithMaskAndFilter := service.      Mask("id;name;note;summary").      Filter(`{"globalIdentifier":{"operation":"2e61f677-752b-4020-a447-b138f5daa387"}}`)    publicImages, err := serviceWithMaskAndFilter.GetPublicImages()</code></pre>    <h3>对象过滤器</h3>    <p>应将过滤器指定为字符串。考虑到对象过滤器可能变得很复杂,所以该库提供了一个过滤器构建器让此过程变得更轻松。例如,您可通过以下方式,使用过滤器构建器指定与上一段代码中相同的过滤器:</p>    <pre>  <code class="language-go">service.Filter(      filter.Path("globalIdentifier").          Eq("2e61f677-752b-4020-a447-b138f5daa387").Build(),  ).GetPublicImages()</code></pre>    <p>也可以提前、批量和根据其他条件创建过滤器:</p>    <pre>  <code class="language-go">filters := filter.New(      filter.Path("virtualGuests.hostname").StartsWith("demo"),      filter.Path("virtualGuests.id").NotEq(1234),  )</code></pre>    <p>随后,可向该组中附加并使用另一个过滤器:</p>    <pre>  <code class="language-go">filters = append(filters, filter.Path("virtualGuests.domain").EndsWith("example.com"))    guests, err := accountService.Filter(filters.Build()).GetVirtualGuests()</code></pre>    <p>可使用 filter.Path() 在 SoftLayer 中对象的嵌套关系属性上指定一个条件。例如:</p>    <pre>  <code class="language-go">filter.Path("datacenter.locationStatus.status").Eq("ACTIVE")</code></pre>    <h2>sl 包中便捷的函数</h2>    <p>与方法参数一样,每种数据类型结构的每个字段都需要一个指针(用作切片的字段除外)。这样,如果字段没有值,该库在将这些无用字段传递到 API 时可以在数据类型结构中省略它们,因为在 Go 中只有指针(和 interface{} 类型)可以是空的。</p>    <p>该库有一个帮助器包,用于在从 SoftLayer 结构获取值和设置值时帮助处理此情况。再看看前面的一个例子:</p>    <pre>  <code class="language-go">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),  }</code></pre>    <p>sl. 包拥有针对字符串、整数、无符号整数和布尔值等原生类型的指针帮助器。它们仅返回您所传递的值的指针引用。如果没有帮助器,就必须按如下方式重写上述代码:</p>    <pre>  <code class="language-go">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,  }</code></pre>    <p>这在某些情况下可能很方便,尤其是希望将值始终用作常量时。但在其他情况下,它过于冗长。请尽量使用帮助器,让代码更简洁。</p>    <p>需要在结构中 <em>设置</em> 值时,这些帮助器很有用,也有一些帮助器可从 SoftLayer 结构中 <em>获取</em> 值。例如,在下面这段代码中,您需要获取虚拟访客的安装后脚本 URI:</p>    <pre>  <code class="language-go">// Don't assume the post install script uri is non-nil.  var scriptURI string  if guest.PostInstallScriptUri != nil {      scriptURI = *guest.PostInstallScriptUri  }</code></pre>    <p>借助 sl.Get 帮助器,可将上述代码段缩写为以下形式(借助 Go 类型断言):</p>    <pre>  <code class="language-go">scriptURI := sl.Get(guest.PostInstallScriptUri).(string)</code></pre>    <p>sl 包的 getter 帮助器包括:</p>    <ul>     <li><strong>Get(p interface{}, d ...interface{}) interface{}</strong> 返回 p 的值。如果 p 是一个指针,帮助器会为您解除对它的引用并返回值。也可传入一个可选的缺省值作为第二个参数。如果 p 是空的,则返回 d 。否则,如果 p 是空的且没有缺省值,则获得 p 的 0 值(也就是说,如果 p 是一个字符串或字符串的指针,您将获得空字符串。如果它是一个整数或整数的指针,您将获得 0)。</li>     <li><strong>GetOk(p interface{}) (interface{}, bool)</strong> 与 Get() 基本相同,但它将返回一个表示 p 是否为非空指针的额外布尔值。缺省值选项在这里没有必要,因为重点在于您拥有自己的逻辑来处理没有值的情况 — 这可能包括确定合适的缺省值。</li>    </ul>    <p>有时您可能需要从 SoftLayer 结构中获取一个深度嵌套的值。因为字段可以是指针,所以在 Go 语言中避免意外发生的最安全方法是在每个结构级别上测试是否包含空值:</p>    <pre>  <code class="language-go">var vlanId int  if guest.PrimaryNetworkComponent != nil {      if guest.PrimaryNetworkComponent.NetworkVlan != nil {          if guest.PrimaryNetworkComponent.NetworkVlan.Id != nil {              vlanId = *guest.PrimaryNetworkComponent.NetworkVlan.Id          }      }  }</code></pre>    <p>另一个帮助器可帮助您从类似这样的嵌套位置获取值,而且可消除大量的样板代码:</p>    <pre>  <code class="language-go">vlanId := sl.Grab(guest, "PrimaryNetworkComponent.NetworkVlan.Id").(int)</code></pre>    <p>这些是 sl 包的 grab 帮助器:</p>    <ul>     <li><strong>Grab(s interface{}, path string, d ...interface{}) interface{}</strong> 返回您使用 s 作为起点的给定路径所指定的值。 path 是一个点分格式的字段集合,它遍历 s 来获取要抓取的值。如果在遍历的路径中的任何位置得到一个空指针值,则返回一个具有合适类型的 0 值。</li>     <li><strong>GrabOk(s interface{}, path string) (interface{}, bool)</strong> 类似于 Grab() ,但它返回另一个布尔值来表明它是否成功找到了一个值(如果未找到,它必须为您创建一个 0 值)。</li>    </ul>    <p>在没有可用的值并且您希望以特殊方式处理该情形时, GrabOk 很有用:</p>    <pre>  <code class="language-go">if vlanId, ok := sl.GrabOk(guest, "PrimaryNetworkComponent.NetworkVlan.Id").(int); !ok {      log.Println("No VLAN ID found!")  }</code></pre>    <h2>处理错误</h2>    <p>所有服务方法都可以返回错误。除了实现 Go 的 Error 接口,下面给出的错误还是某种 SoftLayer 类型的错误,您可获取更多的相关信息。</p>    <pre>  <code class="language-go">type Error struct {      StatusCode int      Exception  string      Message    string      Wrapped    error  }</code></pre>    <p>举例而言,如果您想准确地确定一个错误是由资源不存在还是其他某个传输或 API 错误所引起的,这会很有帮助。</p>    <pre>  <code class="language-go">_, 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  }</code></pre>    <p>如果需要执行进一步的解封装操作,始终可以在 .Wrapped 中找到原始错误。</p>    <pre>  <code class="language-go">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  }</code></pre>    <h2>XML-RPC 和基于密码的身份验证</h2>    <p>SoftLayer API 同时支持 REST 和 XML-RPC 端点。二者的功能几乎相同,但我知道有一种情况仅适用于 XML-RPC — 具体来讲,当您需要使用密码而不是 API 密钥来向 API 执行身份验证时。</p>    <p>为此,您需要在会话中指定 XML-RPC 端点并执行密码身份验证,如下所示:</p>    <pre>  <code class="language-go">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)  }</code></pre>    <h2>调试</h2>    <p>要查看 API 请求和响应日志,可按如下方式打开会话调试标志:</p>    <pre>  <code class="language-go">sess := session.New()  sess.Debug = true  // use the session reference throughout  accountService := services.GetAccountService(sess)  // ...</code></pre>    <p>在试验该库和 SoftLayer API 时,这可能非常有用。</p>    <h2>结束语</h2>    <p>现在您已了解如何通过 Go 编程语言控制 SoftLayer API 了。您了解了会话初始化,获取服务的句柄,方法调用,数据类型结构,以及如何更好地初始化和导航它们,如何实例化虚拟服务器,设置对象掩码,使用过滤器构建器,处理错误,以及其他一些方面。做得好!</p>    <p>当然,这只是使用强大且功能丰富的云平台 SoftLayer 所能完成工作的冰山一角。您可以访问 “相关主题” 部分中的链接,寻找更高级的示例。</p>    <p>致谢:非常感谢 Michael Rieth 提出该库和指令的第一个版本。</p>    <p> </p>    <p>来自:http://www.ibm.com/developerworks/cn/cloud/library/cl-softlayer-go-overview/index.html?ca=drs-</p>    <p> </p>