May 23, 2022

Classes
Tags go Utility cli article

Understanding the softlayer-go library

An in depth explanation of the softlayer-go library and how to make use of it in your applications.

The softlayer-go library helps users make API calls to the SoftLayer (aka IBM Cloud Classic Infrastructure) API.

To get started, here is a simple example.

(We are working in the go/src/github.ibm.com/SoftLayer/sl-test directory for these examples) This file will be main.go

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
        "fmt"
        "github.com/softlayer/softlayer-go/sl"
        "github.com/softlayer/softlayer-go/session"
        "github.com/softlayer/softlayer-go/services"
)

func main() {
        // softlayer-go/session looks for username and apikey in ~/.softlayer file
        // or the SL_USERNAME/SL_API_KEY environment variables.
        sess := session.New()
        sess.Debug = true

        objectMask := "mask[id,hostname,domain]"

        // Looks for hostname starting with `spectrum`
        filterObject := string(`{"hardware":{"hostname":{"operation":"^=spectrum"}}}`)
        servers, err := services.GetAccountService(sess).Filter(filterObject).Mask(objectMask).GetHardware()
        if err != nil {
                fmt.Printf("\n Unable to get server object:\n - %s\n", err)
                return
        }
        fmt.Printf("Id, Hostname, Domain\n")
        for _,server := range servers {
            // use `sl.Get()` since API results are always memory addresses. This helper function makes
            // them a little easier to use
            fmt.Printf("%v, %v, %v\n", sl.Get(server.Id), sl.Get(server.Hostname), sl.Get(server.Domain))
        }
}

Then we need to download the libraries into the go.mod file.

go mod init
go mod tidy

Then just build and run

go build main.go
./main
Calling services.GetAccountService(sess).Filter(filterObject).Mask(objectMask).GetHardware()
2022/05/24 15:24:44 [DEBUG] Request URL:  GET https://api.softlayer.com/rest/v3.1/SoftLayer_Account/getHardware.json?objectFilter=%7B%22hardware%22%3A%7B%22hostname%22%3A%7B%22operation%22%3A%22%5E%3Dspectrum%22%7D%7D%7D&objectMask=mask%5Bid%2Chostname%2Cdomain%5D
2022/05/24 15:24:44 [DEBUG] Parameters:
2022/05/24 15:24:44 [DEBUG] Status Code:  200
2022/05/24 15:24:44 [DEBUG] Response:  [{"domain":"test.com","hostname":"spectrum-virtualize-par-1","id":3071920}]
Id, Hostname, Domain
3071920, spectrum-virtualize-par-1, test.com

Package Name

Line 1 is easy enough, just set the package name, main works well if this is just for testing.

1
package main

Imports

3
4
5
6
7
8
import (
        "fmt"
        "github.com/softlayer/softlayer-go/sl"
        "github.com/softlayer/softlayer-go/session"
        "github.com/softlayer/softlayer-go/services"
)

The softlayer-go package has several sub-packages we can make use of.

  1. github.com/softlayer/softlayer-go/sl Has some helper and error handling functions
  2. github.com/softlayer/softlayer-go/session Will always be required, as it sets up the Authentication Session for actually making the API call
  3. github.com/softlayer/softlayer-go/services These are autogenerated from the list of available Services in the SoftLayer API. When building an API call, you will first need an instance of the specific service you are working with.

Some other imports that are available, but not used here:

  1. github.com/softlayer/softlayer-go/datatypes Has the datatype definitions, if you need them. Generally these are required if you are sending in data to the SoftLayer API, for example when ordering, or making changes to an object.
  2. github.com/softlayer/softlayer-go/filter Has some helper functions for building more complex fiters, although we just use a string in our example
  3. github.com/softlayer/softlayer-go/helpers Contains some functions that might be useful when working some of the more complex API calls, like ordering. A good souce of example on how the softlayer-go client makes API calls as well
  4. github.com/softlayer/softlayer-go/examples Has some simple examples.

Main()

10
11
12
13
14
func main() {
        // softlayer-go/session looks for username and apikey in ~/.softlayer file
        // or the SL_USERNAME/SL_API_KEY environment variables.
        sess := session.New()
        sess.Debug = true

This sets up our main() function, which is required for any go program. From there it sets up a new session object. By default it will look in the ~/.softlayer config file, or environment variables for your username and API key so you don’t have to put them in the code itself. While SLDN has some examples where the username/api key is passed directly to session.New() this is a bit of a bad practice.

From there it sets sess.Debug = true which tells the softlayer-go library to print out all the API calls and results, which can be useful in understanding what exactly is going on under the hood.

The API Call

16
17
18
19
20
        objectMask := "mask[id,hostname,domain]"

        // Looks for hostname starting with `spectrum`
        filterObject := string(`{"hardware":{"hostname":{"operation":"^=spectrum"}}}`)
        servers, err := services.GetAccountService(sess).Filter(filterObject).Mask(objectMask).GetHardware()

In this example we are making an API call to the SoftLayer_Account service, which has a GetHardware() method. In golang any public method or property must start with a capital letter, which is why here the method is GetHardware but in the documentation it is getHardware. The same applies for any property returned, they will all need to start with a capital letter, as we will see as we continue this example.

Because SoftLayer_Account::getHardware() returns a SoftLayer_Hardware object, our object mask can include any property listed on the SoftLayer_Hardware datatype page. Here we just select the local properties id, hostname, domain. Read more about Object Masks

Next we setup an Object Filter, which are generally optional. For this example though we just want to get any hardware that have a hostname that starts with the string spectrum. Here we setup a simple string filter, but more complex filters might benefit from using the Filter Builder. Since this filter is using the Account service, the filter needs to start at the SoftLayer_Account datatype (unlike the object mask).

Lastly, we put all these pieces together to make an API call. Most API calls will return 2 objects, the first being whatever the API method itself is set to return (in this case a map of SoftLayer_Hardware objects), and an error if there was one. Usually this is an either/or situation, you won’t ever get an error and data back.

Error handling

21
22
23
24
        if err != nil {
                fmt.Printf("\n Unable to get server object:\n - %s\n", err)
                return
        }

Generally if there is an error, simply printing out a message and the actual error itself is enough.

Printing the result

25
26
27
28
29
30
        fmt.Printf("Id, Hostname, Domain\n")
        for _,server := range servers {
            // use `sl.Get()` since API results are always memory addresses. This helper function makes
            // them a little easier to use
            fmt.Printf("%v, %v, %v\n", sl.Get(server.Id), sl.Get(server.Hostname), sl.Get(server.Domain))
        }

Printing out data is usually simple enough. The only thing you might need to be aware of is that the results of the API calls are all going to be pointers. In this example we use sl.Get() to resolve those pointers, but the following would work too:

fmt.Printf("%v, %v, %v\n", *server.Id, *server.Hostname, *server.Domain)

Updating Services and Datatypes

The services and datatypes packages are kept updated monthly (although not usually attached to a new version every month). If for some reason there is an API you want to use, but not included in the latest version, please let us know about it on our github issues page.

To update those manually however, you will need to run make generate in the softlayer-go library in your build, and use the replace directive in go.mod to make sure go uses the local softlayer-go when building your app.

cd ~/go/src/github.com/softlayer/softlayer-go/
git pull origin master
make generate

If you dont have make installed: go run tools/main.go tools/loadmeta.go tools/common.go tools/version.go generate

Iteration

On occasions where the result you want from the API is too large to be done in one request, or takes too long for a single request, iterating (paginating) through the results is always a good idea.

ALWAYS use an orderBy objectFilter when paginating through results. Otherwise the API might not order the results the same way between queries.

For these cases, the following is a good pattern to use

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

func ListInstances()  ([]datatypes.Virtual_Guest, error) {
        sess := session.New()
        filters := filter.New()
        filters = append(filters, filter.Path("virtualGuests.id").OrderBy("DESC"))
        accountService := services.GetAccountService(session)
        limit := 50
        i := 0
        resourceList := []datatypes.Virtual_Guest{}
        for {
                // Get limit results to start off with
                resp, err := vs.AccountService.Mask(mask).Filter(filters.Build())
                                .Limit(limit).Offset(i * limit).GetMonthlyVirtualGuests()
                // increment the offset so we get a new page next request
                i++
                // If there was an error, return that and nothing else
                if err != nil {
                        return []datatypes.Virtual_Guest{}, err
                }
                // append the results to our resourceList
                resourceList = append(resourceList, resp...)
                // if we got fewer results than what we asked for, we are done.
                if len(resp) < limit {
                        break
                }
        }
        return resourceList, nil
}

Error Handling

For any error that occurs within one of the SoftLayer API services, a custom error type is returned, with individual fields that can be parsed separately.

Note that sl.Error implements the standard error interface, so it can be handled like any other error, if the above granularity is not needed:


_, err := service.Id(0).GetObject()

if err != nil {
        fmt.Println("Error during processing: ", err)
        // Note: type assertion is only necessary for inspecting individual fields
        apiErr := err.(sl.Error)
        fmt.Printf("API Error:")
        fmt.Printf("HTTP Status Code: %d\n", apiErr.StatusCode)
        fmt.Printf("API Code: %s\n", apiErr.Exception)
        fmt.Printf("API Error: %s\n", apiErr.Message)
}

Debugging

When running into issues, its always important to know exactly what the softlayer-go client is sending into the API, and getting back in return. For this, simply set session.Debug=True. This will cause softlayer-go to print out API calls it makes, and the JSON data it gets back.

sess := session.New()
sess.Debug = true

Output will look something like this:

2022/05/26 11:51:51 [DEBUG] Request URL:  GET https://api.softlayer.com/rest/v3.1/SoftLayer_Account/getHardware.json?objectFilter=%7B%22hardware%22%3A%7B%22hostname%22%3A%7B%22operation%22%3A%22%5E%3Dspectrum%22%7D%7D%7D&objectMask=mask%5Bid%2Chostname%2Cdomain%5D
2022/05/26 11:51:51 [DEBUG] Parameters:
2022/05/26 11:51:52 [DEBUG] Status Code:  200
2022/05/26 11:51:52 [DEBUG] Response:  [{"domain":"test.com","hostname":"spectrum-virtualize-par-1","id":3071920}]

ibmcloud sl and other softlayer-go examples

For a good example of how the softlayer-go library works, the ibmcloud sl command is a good place to start. SLDN GO Library also has a nice collection of smaller single use examples that could be useful.

Contributing to softlayer-go

If you ever feel the softlayer-go library could be improved please feel free to make a pull request, or even open an issue on the project’s github would be very helpful.

Further Reading


Feedback?

If this article contains any error, or leaves any of your questions unanswered, please help us out by opening up a github issue.
Open an issue