clcgo

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2015 License: Apache-2.0 Imports: 7 Imported by: 4

README

clcgo

Circle CI GoDoc

clcgo is an API wrapper for the CenturyLink Cloud V2 API written in Go. You can read about its usage in the GoDocs, and about the API's capabilities in CenturyLink Cloud documentation.

Documentation

Overview

Package clcgo is an API wrapper for the CenturyLink Cloud (CLC) API V2. While not a complete implementation of the API, it is capable of fulfilling common use cases around provisioning and querying servers.

It will be invaluable for you to read the API documentation to understand what is available:

https://t3n.zendesk.com/categories/20067994-API-v2-0-Beta-

This library attempts to follow the resource and JSON attribute names exactly in naming its structs and fields.

Usage

All API interactions require authentication. You should begin by instantiating a Client with the NewClient function. You can then authenticate with your CLC username and password using the GetAPICredentials function. You can read more about the details of authentication in the documentation for the APICredentials struct and the GetAPICredentials function.

Once authenticated, the Client provides a function for fetching resources, GetEntity, and one for saving resource, SaveEntity. Both of those functions have example documentation around their use. Each individual resource has documentation about its capabilities and requirements.

Development and Extension

If you want to use clcgo with an API resource that has not yet been implemented, you should look at the documentation for the Entity, SaveEntity, and CreationStatusProvidingEntity interfaces. Implementing one or more of those interfaces, depending on the resource, will allow you to write your own resources and interact with them in the standard fashion.

Index

Examples

Constants

This section is empty.

Variables

View Source
var DefaultHTTPClient = &http.Client{}

DefaultHTTPClient can be overridden in the same fashion as the DefaultClient for http, if you would like to implement some behavioral change around the requests that we could not anticipate.

Functions

This section is empty.

Types

type APICredentials

type APICredentials struct {
	BearerToken  string `json:"bearerToken"`
	AccountAlias string `json:"accountAlias"`
	Username     string `json:"username"` // TODO: nonexistent in get, extract to creation params?
	Password     string `json:"password"` // TODO: nonexistent in get, extract to creation params?
}

APICredentials are used by the Client to identify you to CenturyLink Cloud in any request. The BearerToken value will be good for a set period of time, defined in the API documentation.

If you anticipate making many API requests in several runs of your application, it may be a good idea to save the BearerToken and AccountAlias values somewhere so that they can be recalled when they are needed again, rather than having to re-authenticate with the API. You can build a APICredentials object with those two fields, then assign it to the Client's APICredentials value.

The Username and Password values will be present only if you've used GetAPICredentials, and can otherwise be ignored.

func (APICredentials) RequestForSave

func (c APICredentials) RequestForSave(a string) (request, error)

type Client

type Client struct {
	APICredentials APICredentials
	Requestor      requestor
}

The Client stores your current credentials and uses them to set or fetch data from the API. It should be instantiated with the NewClient function.

func NewClient

func NewClient() *Client

NewClient returns a Client that has been configured to communicate to CenturyLink Cloud. Before making any requests, the Client must be authorized by either setting its APICredentials field or calling the GetAPICredentials function.

func (*Client) DeleteEntity

func (c *Client) DeleteEntity(e Entity) (Status, error)

DeleteEntity is used to tell CenturyLink Cloud to remove a resource.

The method returns a Status, which can be used as described in the SaveEntity documentation to determine when the work completes. The Entity object will not be modified.

func (*Client) GetAPICredentials

func (c *Client) GetAPICredentials(u string, p string) error

GetAPICredentials accepts username and password strings to populate the Client instance with valid APICredentials.

CenturyLink Cloud requires a BearerToken to authorize all requests, and this method will fetch one for the user and associate it with the Client. Any further requests made by the Client will include that BearerToken.

Example (Failed)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"

	"github.com/CenturyLinkLabs/clcgo"
	"github.com/CenturyLinkLabs/clcgo/fakeapi"
)

var (
	exampleDefaultHTTPClient *http.Client
	fakeAPIServer            *httptest.Server
)

type exampleDomainRewriter struct {
	RewriteURL *url.URL
}

func (r exampleDomainRewriter) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req.URL.Scheme = "http"
	req.URL.Host = r.RewriteURL.Host

	t := http.Transport{}
	return t.RoundTrip(req)
}

func setupExample() {
	fakeAPIServer = fakeapi.CreateFakeServer()

	exampleDefaultHTTPClient = clcgo.DefaultHTTPClient
	u, _ := url.Parse(fakeAPIServer.URL)
	clcgo.DefaultHTTPClient = &http.Client{Transport: exampleDomainRewriter{RewriteURL: u}}
}

func teardownExample() {
	fakeAPIServer.Close()
	clcgo.DefaultHTTPClient = exampleDefaultHTTPClient
}

func main() {
	// Some test-related setup code which you can safely ignore.
	setupExample()
	defer teardownExample()

	c := clcgo.NewClient()
	err := c.GetAPICredentials("bad", "bad")

	fmt.Printf("Error: %s", err)
}
Output:

Error: there was a problem with your credentials
Example (Successful)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"

	"github.com/CenturyLinkLabs/clcgo"
	"github.com/CenturyLinkLabs/clcgo/fakeapi"
)

var (
	exampleDefaultHTTPClient *http.Client
	fakeAPIServer            *httptest.Server
)

type exampleDomainRewriter struct {
	RewriteURL *url.URL
}

func (r exampleDomainRewriter) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req.URL.Scheme = "http"
	req.URL.Host = r.RewriteURL.Host

	t := http.Transport{}
	return t.RoundTrip(req)
}

func setupExample() {
	fakeAPIServer = fakeapi.CreateFakeServer()

	exampleDefaultHTTPClient = clcgo.DefaultHTTPClient
	u, _ := url.Parse(fakeAPIServer.URL)
	clcgo.DefaultHTTPClient = &http.Client{Transport: exampleDomainRewriter{RewriteURL: u}}
}

func teardownExample() {
	fakeAPIServer.Close()
	clcgo.DefaultHTTPClient = exampleDefaultHTTPClient
}

func main() {
	// Some test-related setup code which you can safely ignore.
	setupExample()
	defer teardownExample()

	c := clcgo.NewClient()
	c.GetAPICredentials("user", "pass")

	fmt.Printf("Account Alias: %s", c.APICredentials.AccountAlias)
}
Output:

Account Alias: ACME

func (*Client) GetEntity

func (c *Client) GetEntity(e Entity) error

GetEntity is used to fetch a summary of a resource. When you pass a pointer to your resource, GetEntity will set its fields appropriately.

Different resources have different field requirements before their summaries can be fetched successfully. If you omit an ID field from a Server, for instance, GetEntity will return an error informing you of the missing field. An error from GetEntity likely means that your passed Entity was not modified.

Example (ExpiredToken)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"

	"github.com/CenturyLinkLabs/clcgo"
	"github.com/CenturyLinkLabs/clcgo/fakeapi"
)

var (
	exampleDefaultHTTPClient *http.Client
	fakeAPIServer            *httptest.Server
)

type exampleDomainRewriter struct {
	RewriteURL *url.URL
}

func (r exampleDomainRewriter) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req.URL.Scheme = "http"
	req.URL.Host = r.RewriteURL.Host

	t := http.Transport{}
	return t.RoundTrip(req)
}

func setupExample() {
	fakeAPIServer = fakeapi.CreateFakeServer()

	exampleDefaultHTTPClient = clcgo.DefaultHTTPClient
	u, _ := url.Parse(fakeAPIServer.URL)
	clcgo.DefaultHTTPClient = &http.Client{Transport: exampleDomainRewriter{RewriteURL: u}}
}

func teardownExample() {
	fakeAPIServer.Close()
	clcgo.DefaultHTTPClient = exampleDefaultHTTPClient
}

func main() {
	// Some test-related setup code which you can safely ignore.
	setupExample()
	defer teardownExample()

	c := clcgo.NewClient()
	// You are caching this Bearer Token value and it has either expired or for
	// some other reason become invalid.
	c.APICredentials = clcgo.APICredentials{BearerToken: "expired", AccountAlias: "ACME"}

	s := clcgo.Server{ID: "server1"}
	err := c.GetEntity(&s)

	rerr, _ := err.(clcgo.RequestError)
	fmt.Printf("Error: %s, Status Code: %d", rerr, rerr.StatusCode)
}
Output:

Error: your bearer token was rejected, Status Code: 401
Example (Successful)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"

	"github.com/CenturyLinkLabs/clcgo"
	"github.com/CenturyLinkLabs/clcgo/fakeapi"
)

var (
	exampleDefaultHTTPClient *http.Client
	fakeAPIServer            *httptest.Server
)

type exampleDomainRewriter struct {
	RewriteURL *url.URL
}

func (r exampleDomainRewriter) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req.URL.Scheme = "http"
	req.URL.Host = r.RewriteURL.Host

	t := http.Transport{}
	return t.RoundTrip(req)
}

func setupExample() {
	fakeAPIServer = fakeapi.CreateFakeServer()

	exampleDefaultHTTPClient = clcgo.DefaultHTTPClient
	u, _ := url.Parse(fakeAPIServer.URL)
	clcgo.DefaultHTTPClient = &http.Client{Transport: exampleDomainRewriter{RewriteURL: u}}
}

func teardownExample() {
	fakeAPIServer.Close()
	clcgo.DefaultHTTPClient = exampleDefaultHTTPClient
}

func main() {
	// Some test-related setup code which you can safely ignore.
	setupExample()
	defer teardownExample()

	c := clcgo.NewClient()
	c.GetAPICredentials("user", "pass")

	s := clcgo.Server{ID: "server1"}
	c.GetEntity(&s)

	fmt.Printf("Server Name: %s", s.Name)
}
Output:

Server Name: Test Name

func (*Client) SaveEntity

func (c *Client) SaveEntity(e SavableEntity) (Status, error)

SaveEntity is used to persist a changed resource to CenturyLink Cloud.

Beyond the fields absolutely required to form valid URLs, the presence or format of the fields on your resources are not validated before they are submitted. You should check the CenturyLink Cloud API documentation for this information. If your submission was unsuccessful, it is likely that the error returned is a RequestError, which may contain helpful error messages you can use to determine what went wrong.

Calling HasSucceeded on the returned Status will tell you if the resource is ready. Some resources are available immediately, but most are not. If the resource implements CreationStatusProvidingEntity it will likely take time, but regardless you can check with the returned Status to be sure.

The Status does not update itself, and you will need to call GetEntity on it periodically to determine when its resource is ready if it was not immediately successful.

Never ignore the error result from this call! Even if your code is perfect (congratulations, by the way), errors can still occur due to unexpected server states or networking issues.

Example (Successful)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"net/url"

	"github.com/CenturyLinkLabs/clcgo"
	"github.com/CenturyLinkLabs/clcgo/fakeapi"
)

var (
	exampleDefaultHTTPClient *http.Client
	fakeAPIServer            *httptest.Server
)

type exampleDomainRewriter struct {
	RewriteURL *url.URL
}

func (r exampleDomainRewriter) RoundTrip(req *http.Request) (resp *http.Response, err error) {
	req.URL.Scheme = "http"
	req.URL.Host = r.RewriteURL.Host

	t := http.Transport{}
	return t.RoundTrip(req)
}

func setupExample() {
	fakeAPIServer = fakeapi.CreateFakeServer()

	exampleDefaultHTTPClient = clcgo.DefaultHTTPClient
	u, _ := url.Parse(fakeAPIServer.URL)
	clcgo.DefaultHTTPClient = &http.Client{Transport: exampleDomainRewriter{RewriteURL: u}}
}

func teardownExample() {
	fakeAPIServer.Close()
	clcgo.DefaultHTTPClient = exampleDefaultHTTPClient
}

func main() {
	// Some test-related setup code which you can safely ignore.
	setupExample()
	defer teardownExample()

	c := clcgo.NewClient()
	c.GetAPICredentials("user", "pass")

	// Build a Server resource. In reality there are many more required fields.
	s := clcgo.Server{Name: "My Server"}

	// Request the Server be provisioned, returning a Status. In your code you
	// must NOT ignore the possibility of an error here.
	st, _ := c.SaveEntity(&s)

	// Refresh the Status until it has completed. In your code you should put a
	// delay between requests, as this can take a while.
	if !st.HasSucceeded() {
		c.GetEntity(&st)
	}

	// The Status says that the server is provisioned. You can now request its
	// details. Again, your code should not ignore errors as this is doing.
	c.GetEntity(&s)

	fmt.Printf("Server ID: %s", s.ID)
}
Output:

Server ID: test-id

type CreationStatusProvidingEntity

type CreationStatusProvidingEntity interface {
	StatusFromCreateResponse([]byte) (Status, error)
}

CreationStatusProvidingEntity will be implemented by some SavableEntity resources when information about the created resource is not immediately available. For instance, a Server or PublicIPAddress must first be provisioned, so a Status object is returned so that you can query it until the work has been successfully completed.

All StatusProvidingEntites must be SavableEntities, but not every SavableEntity is a CreationStatusProvidingEntity.

type Credentials

type Credentials struct {
	Server   Server `json:"-"`
	Username string `json:"userName"`
	Password string `json:"password"`
}

Credentials can be used to fetch the username and password for a Server. You must supply the associated Server.

This uses an undocumented API endpoint and could be changed or removed.

Example (Persisted)
package main

import (
	"github.com/CenturyLinkLabs/clcgo"
)

func main() {
	client := clcgo.NewClient()
	creds := clcgo.APICredentials{BearerToken: "TOKEN", AccountAlias: "ACME"}
	client.APICredentials = creds // Client now ready for authenticated requests.
}
Output:

func (Credentials) URL

func (c Credentials) URL(a string) (string, error)

type DataCenter

type DataCenter struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

A DataCenter resource can either be returned by the DataCenters resource, or built manually. It should be used in conjunction with the DataCenterCapabilities resource to request information about it.

You must supply the ID if you are building this object manually.

type DataCenterCapabilities

type DataCenterCapabilities struct {
	DataCenter         DataCenter          `json:"-"`
	DeployableNetworks []DeployableNetwork `json:"deployableNetworks"`
	Templates          []struct {
		Name        string `json:"name"`
		Description string `json:"description"`
	} `json:"templates"`
}

DataCenterCapabilities gets more information about a specific DataCenter. You must supply the associated DataCenter object.

func (DataCenterCapabilities) URL

type DataCenterGroup

type DataCenterGroup struct {
	DataCenter DataCenter `json:"-"`
	ID         string     `json:"id"`
	Name       string     `json:"name"`
	Links      []Link     `json:"links"`
}

DataCenterGroup can be used to find associated groups for a datacenter and your account. The linked hardware group can be used with the Group object to query for your account's groups within that datacenter.

You must supply the associated DataCenter object.

func (DataCenterGroup) URL

func (d DataCenterGroup) URL(a string) (string, error)

type DataCenters

type DataCenters []DataCenter

The DataCenters resource can retrieve a list of available DataCenters.

func (DataCenters) URL

func (d DataCenters) URL(a string) (string, error)

type DeletionStatusProvidingEntity

type DeletionStatusProvidingEntity interface {
	StatusFromDeleteResponse([]byte) (Status, error)
}

type DeployableNetwork

type DeployableNetwork struct {
	Name      string `json:"name"`
	NetworkID string `json:"networkId"`
	Type      string `json:"type"`
	AccountID string `json:"accountID"`
}

A DeployableNetwork describes a private network that is scoped to an Account and DataCenter. It can be fetched via the DataCenterCapabilities and can optionally be used to put a Server in a specific network.

type Entity

type Entity interface {
	URL(string) (string, error)
}

The Entity interface is implemented by any resource type that can be asked for its details via GetEntity. This seems basic and is implemented in most cases, but a few - PublicIPAddress for instance - can only be sent and not subsequently read.

type Group

type Group struct {
	ParentGroup   *Group  `json:"-"`
	ID            string  `json:"id"`
	ParentGroupID string  `json:"parentGroupId"` // TODO: not in get, extract to creation params?
	Name          string  `json:"name"`
	Description   string  `json:"description"`
	Type          string  `json:"type"`
	Groups        []Group `json:"groups"`
}

A Group resource can be used to discover the hierarchy of the created groups for your account for a given datacenter. The Group's ID can either be for one you have created, or for a datacenter's hardware group (which can be determined via the DataCenterGroup resource).

When creating a new group, the ParentGroup field must be set before you attempt to save.

func (*Group) RequestForSave

func (g *Group) RequestForSave(a string) (request, error)

func (*Group) URL

func (g *Group) URL(a string) (string, error)
type Link struct {
	ID   string `json:"id"`
	Rel  string `json:"rel"`
	HRef string `json:"href"`
}

A Link is a meta object returned in many API responses to help find resources related to the one you've requested.

type OperationType

type OperationType string

An OperationType should be used in conjunction with a ServerOperation to instruct a server instance to perform one of several operations.

const (
	PauseServer    OperationType = "pause"
	ShutDownServer OperationType = "shutDown"
	RebootServer   OperationType = "reboot"
	ResetServer    OperationType = "reset"
	PowerOnServer  OperationType = "powerOn"
	PowerOffServer OperationType = "powerOff"
)

More information on precisely what each operation does can be found in the CenturyLink Cloud V2 API documentation.

type Port

type Port struct {
	Protocol string `json:"protocol"`
	Port     int    `json:"port"`
}

A Port object specifies a network port that should be made available on a PublicIPAddress. It can only be used in conjunction with the PublicIPAddress resource.

type PublicIPAddress

type PublicIPAddress struct {
	Server            Server
	Ports             []Port `json:"ports"`
	InternalIPAddress string `json:"internalIPAddress"`
}

A PublicIPAddress can be created and associated with an existing, provisioned Server. You must supply the associated Server object.

You must supply a slice of Port objects that will make the specified ports accessible at the address.

func (PublicIPAddress) RequestForSave

func (i PublicIPAddress) RequestForSave(a string) (request, error)

func (PublicIPAddress) StatusFromCreateResponse

func (i PublicIPAddress) StatusFromCreateResponse(r []byte) (Status, error)

type RequestError

type RequestError struct {
	Message    string
	StatusCode int
	Errors     modelStates
}

A RequestError can be returned from GetEntity and SaveEntity calls and contain specific information about the unexpected error from your request. It can be especially helpful on SaveEntity validation failures, for instance if you omitted a required field.

func (RequestError) Error

func (r RequestError) Error() string

type SavableEntity

type SavableEntity interface {
	RequestForSave(string) (request, error)
}

The SavableEntity interface is implemented on any resources that can be saved via SaveEntity.

type Server

type Server struct {
	ID                string            `json:"id"`
	Name              string            `json:"name"`
	GroupID           string            `json:"groupId"`
	Status            string            `json:"status"`
	SourceServerID    string            `json:"sourceServerId"` // TODO: nonexistent in get, extract to creation params?
	CPU               int               `json:"cpu"`
	MemoryGB          int               `json:"memoryGB"` // TODO: memoryMB in get, extract to creation params?
	Type              string            `json:"type"`
	DeployableNetwork DeployableNetwork `json:"-"`
	NetworkID         string            `json:"networkId"`
	Password          string            `json:"password"`
	Details           struct {
		PowerState  string `json:"powerState"`
		IPAddresses []struct {
			Public   string `json:"public"`
			Internal string `json:"internal"`
		} `json:"ipAddresses"`
	} `json:"details"`
	// contains filtered or unexported fields
}

A Server can be used to either fetch an existing Server or provision and new one. To fetch, you must supply an ID value. For creation, there are numerous required values. The API documentation should be consulted.

The SourceServerID is a required field that allows multiple values which are documented in the API. One of the allowed values is a Template ID, which can be retrieved with the DataCenterCapabilities resource.

To make your server a member of a specific network, you can set the DeployableNetwork field. This is optional. The Server will otherwise be a member of the default network. DeployableNetworks exist per account and DataCenter and can be retrieved via the DataCenterCapabilities resource. If you know the NetworkID, you can supply it instead.

A Password field can be set for a Server you are saving, but fetching the username and password for an existing Server can only be done via the Credentials resource.

func (Server) IsActive

func (s Server) IsActive() bool

IsActive will, unsurprisingly, tell you if the Server is both active and not paused.

func (Server) IsPaused

func (s Server) IsPaused() bool

IsPaused will tell you if the Server is paused or not.

func (*Server) RequestForSave

func (s *Server) RequestForSave(a string) (request, error)

func (*Server) StatusFromCreateResponse

func (s *Server) StatusFromCreateResponse(r []byte) (Status, error)

func (Server) URL

func (s Server) URL(a string) (string, error)

type ServerOperation

type ServerOperation struct {
	OperationType OperationType
	Server        Server
}

A ServerOperation is used to perform many tasks around starting/stopping/pausing server instances.

You are required to pass both a Server reference and an OperationType, and you will be notified of the operation's progress via the Status that is returned when the ServerOperation is passed to SaveEntity.

func (ServerOperation) RequestForSave

func (p ServerOperation) RequestForSave(a string) (request, error)

func (ServerOperation) StatusFromCreateResponse

func (p ServerOperation) StatusFromCreateResponse(r []byte) (Status, error)

type Status

type Status struct {
	Status string
	URI    string
}

A Status is returned by all SaveEntity calls and can be used to determine when long-running provisioning jobs have completed. Things like Server creation take time, and you can periodically call GetEntity on the returned Status to determine if the server is ready.

func (Status) HasSucceeded

func (s Status) HasSucceeded() bool

HasSucceeded will, unsurprisingly, tell you if the Status is successful.

func (Status) URL

func (s Status) URL(a string) (string, error)

Directories

Path Synopsis
Package fakeapi contains canned API responses from the CenturyLink Cloud API, and helpers to start an httptest.Server that will return those responses appropriately.
Package fakeapi contains canned API responses from the CenturyLink Cloud API, and helpers to start an httptest.Server that will return those responses appropriately.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL