392 lines
16 KiB
Go
392 lines
16 KiB
Go
package linodego
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/go-resty/resty/v2"
|
|
)
|
|
|
|
const (
|
|
// APIHost Linode API hostname
|
|
APIHost = "api.linode.com"
|
|
// APIHostVar environment var to check for alternate API URL
|
|
APIHostVar = "LINODE_URL"
|
|
// APIHostCert environment var containing path to CA cert to validate against
|
|
APIHostCert = "LINODE_CA"
|
|
// APIVersion Linode API version
|
|
APIVersion = "v4"
|
|
// APIVersionVar environment var to check for alternate API Version
|
|
APIVersionVar = "LINODE_API_VERSION"
|
|
// APIProto connect to API with http(s)
|
|
APIProto = "https"
|
|
// Version of linodego
|
|
Version = "0.12.0"
|
|
// APIEnvVar environment var to check for API token
|
|
APIEnvVar = "LINODE_TOKEN"
|
|
// APISecondsPerPoll how frequently to poll for new Events or Status in WaitFor functions
|
|
APISecondsPerPoll = 3
|
|
// Maximum wait time for retries
|
|
APIRetryMaxWaitTime = time.Duration(30) * time.Second
|
|
// DefaultUserAgent is the default User-Agent sent in HTTP request headers
|
|
DefaultUserAgent = "linodego " + Version + " https://github.com/linode/linodego"
|
|
)
|
|
|
|
var (
|
|
envDebug = false
|
|
)
|
|
|
|
// Client is a wrapper around the Resty client
|
|
type Client struct {
|
|
resty *resty.Client
|
|
userAgent string
|
|
resources map[string]*Resource
|
|
debug bool
|
|
retryConditionals []RetryConditional
|
|
|
|
millisecondsPerPoll time.Duration
|
|
|
|
Account *Resource
|
|
AccountSettings *Resource
|
|
DomainRecords *Resource
|
|
Domains *Resource
|
|
Events *Resource
|
|
Firewalls *Resource
|
|
IPAddresses *Resource
|
|
IPv6Pools *Resource
|
|
IPv6Ranges *Resource
|
|
Images *Resource
|
|
InstanceConfigs *Resource
|
|
InstanceDisks *Resource
|
|
InstanceIPs *Resource
|
|
InstanceSnapshots *Resource
|
|
InstanceStats *Resource
|
|
InstanceVolumes *Resource
|
|
Instances *Resource
|
|
InvoiceItems *Resource
|
|
Invoices *Resource
|
|
Kernels *Resource
|
|
LKEClusters *Resource
|
|
LKEClusterPools *Resource
|
|
LKEVersions *Resource
|
|
Longview *Resource
|
|
LongviewClients *Resource
|
|
LongviewSubscriptions *Resource
|
|
Managed *Resource
|
|
NodeBalancerConfigs *Resource
|
|
NodeBalancerNodes *Resource
|
|
NodeBalancerStats *Resource
|
|
NodeBalancers *Resource
|
|
Notifications *Resource
|
|
OAuthClients *Resource
|
|
ObjectStorageBuckets *Resource
|
|
ObjectStorageClusters *Resource
|
|
ObjectStorageKeys *Resource
|
|
Payments *Resource
|
|
Profile *Resource
|
|
Regions *Resource
|
|
SSHKeys *Resource
|
|
StackScripts *Resource
|
|
Tags *Resource
|
|
Tickets *Resource
|
|
Token *Resource
|
|
Tokens *Resource
|
|
Types *Resource
|
|
Users *Resource
|
|
Volumes *Resource
|
|
}
|
|
|
|
func init() {
|
|
// Wether or not we will enable Resty debugging output
|
|
if apiDebug, ok := os.LookupEnv("LINODE_DEBUG"); ok {
|
|
if parsed, err := strconv.ParseBool(apiDebug); err == nil {
|
|
envDebug = parsed
|
|
log.Println("[INFO] LINODE_DEBUG being set to", envDebug)
|
|
} else {
|
|
log.Println("[WARN] LINODE_DEBUG should be an integer, 0 or 1")
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetUserAgent sets a custom user-agent for HTTP requests
|
|
func (c *Client) SetUserAgent(ua string) *Client {
|
|
c.userAgent = ua
|
|
c.resty.SetHeader("User-Agent", c.userAgent)
|
|
|
|
return c
|
|
}
|
|
|
|
// R wraps resty's R method
|
|
func (c *Client) R(ctx context.Context) *resty.Request {
|
|
return c.resty.R().
|
|
ExpectContentType("application/json").
|
|
SetHeader("Content-Type", "application/json").
|
|
SetContext(ctx).
|
|
SetError(APIError{})
|
|
}
|
|
|
|
// SetDebug sets the debug on resty's client
|
|
func (c *Client) SetDebug(debug bool) *Client {
|
|
c.debug = debug
|
|
c.resty.SetDebug(debug)
|
|
|
|
return c
|
|
}
|
|
|
|
// SetBaseURL sets the base URL of the Linode v4 API (https://api.linode.com/v4)
|
|
func (c *Client) SetBaseURL(url string) *Client {
|
|
c.resty.SetHostURL(url)
|
|
return c
|
|
}
|
|
|
|
// SetAPIVersion sets the version of the API to interface with
|
|
func (c *Client) SetAPIVersion(apiVersion string) *Client {
|
|
c.SetBaseURL(fmt.Sprintf("%s://%s/%s", APIProto, APIHost, apiVersion))
|
|
return c
|
|
}
|
|
|
|
// SetRootCertificate adds a root certificate to the underlying TLS client config
|
|
func (c *Client) SetRootCertificate(path string) *Client {
|
|
c.resty.SetRootCertificate(path)
|
|
return c
|
|
}
|
|
|
|
// SetToken sets the API token for all requests from this client
|
|
// Only necessary if you haven't already provided an http client to NewClient() configured with the token.
|
|
func (c *Client) SetToken(token string) *Client {
|
|
c.resty.SetHeader("Authorization", fmt.Sprintf("Bearer %s", token))
|
|
return c
|
|
}
|
|
|
|
// SetRetries adds retry conditions for "Linode Busy." errors and 429s.
|
|
func (c *Client) SetRetries() *Client {
|
|
c.
|
|
addRetryConditional(linodeBusyRetryCondition).
|
|
addRetryConditional(tooManyRequestsRetryCondition).
|
|
SetRetryMaxWaitTime(APIRetryMaxWaitTime)
|
|
configureRetries(c)
|
|
return c
|
|
}
|
|
|
|
func (c *Client) addRetryConditional(retryConditional RetryConditional) *Client {
|
|
c.retryConditionals = append(c.retryConditionals, retryConditional)
|
|
return c
|
|
}
|
|
|
|
func (c *Client) SetRetryMaxWaitTime(max time.Duration) *Client {
|
|
c.resty.SetRetryMaxWaitTime(max)
|
|
return c
|
|
}
|
|
|
|
// SetPollDelay sets the number of milliseconds to wait between events or status polls.
|
|
// Affects all WaitFor* functions and retries.
|
|
func (c *Client) SetPollDelay(delay time.Duration) *Client {
|
|
c.millisecondsPerPoll = delay
|
|
c.resty.SetRetryWaitTime(delay * time.Millisecond)
|
|
return c
|
|
}
|
|
|
|
// Resource looks up a resource by name
|
|
func (c Client) Resource(resourceName string) *Resource {
|
|
selectedResource, ok := c.resources[resourceName]
|
|
if !ok {
|
|
log.Fatalf("Could not find resource named '%s', exiting.", resourceName)
|
|
}
|
|
|
|
return selectedResource
|
|
}
|
|
|
|
// NewClient factory to create new Client struct
|
|
func NewClient(hc *http.Client) (client Client) {
|
|
if hc != nil {
|
|
client.resty = resty.NewWithClient(hc)
|
|
} else {
|
|
client.resty = resty.New()
|
|
}
|
|
|
|
client.SetUserAgent(DefaultUserAgent)
|
|
|
|
baseURL, baseURLExists := os.LookupEnv(APIHostVar)
|
|
|
|
if baseURLExists {
|
|
client.SetBaseURL(baseURL)
|
|
} else {
|
|
apiVersion, apiVersionExists := os.LookupEnv(APIVersionVar)
|
|
if apiVersionExists {
|
|
client.SetAPIVersion(apiVersion)
|
|
} else {
|
|
client.SetAPIVersion(APIVersion)
|
|
}
|
|
}
|
|
|
|
certPath, certPathExists := os.LookupEnv(APIHostCert)
|
|
|
|
if certPathExists {
|
|
cert, err := ioutil.ReadFile(certPath)
|
|
if err != nil {
|
|
log.Fatalf("[ERROR] Error when reading cert at %s: %s\n", certPath, err.Error())
|
|
}
|
|
|
|
client.SetRootCertificate(certPath)
|
|
|
|
if envDebug {
|
|
log.Printf("[DEBUG] Set API root certificate to %s with contents %s\n", certPath, cert)
|
|
}
|
|
}
|
|
|
|
client.
|
|
SetPollDelay(1000 * APISecondsPerPoll).
|
|
SetRetries().
|
|
SetDebug(envDebug)
|
|
|
|
addResources(&client)
|
|
|
|
return
|
|
}
|
|
|
|
// nolint
|
|
func addResources(client *Client) {
|
|
resources := map[string]*Resource{
|
|
accountName: NewResource(client, accountName, accountEndpoint, false, Account{}, nil), // really?
|
|
accountSettingsName: NewResource(client, accountSettingsName, accountSettingsEndpoint, false, AccountSettings{}, nil), // really?
|
|
domainRecordsName: NewResource(client, domainRecordsName, domainRecordsEndpoint, true, DomainRecord{}, DomainRecordsPagedResponse{}),
|
|
domainsName: NewResource(client, domainsName, domainsEndpoint, false, Domain{}, DomainsPagedResponse{}),
|
|
eventsName: NewResource(client, eventsName, eventsEndpoint, false, Event{}, EventsPagedResponse{}),
|
|
firewallsName: NewResource(client, firewallsName, firewallsEndpoint, false, Firewall{}, FirewallsPagedResponse{}),
|
|
imagesName: NewResource(client, imagesName, imagesEndpoint, false, Image{}, ImagesPagedResponse{}),
|
|
instanceConfigsName: NewResource(client, instanceConfigsName, instanceConfigsEndpoint, true, InstanceConfig{}, InstanceConfigsPagedResponse{}),
|
|
instanceDisksName: NewResource(client, instanceDisksName, instanceDisksEndpoint, true, InstanceDisk{}, InstanceDisksPagedResponse{}),
|
|
instanceIPsName: NewResource(client, instanceIPsName, instanceIPsEndpoint, true, InstanceIP{}, nil), // really?
|
|
instanceSnapshotsName: NewResource(client, instanceSnapshotsName, instanceSnapshotsEndpoint, true, InstanceSnapshot{}, nil),
|
|
instanceStatsName: NewResource(client, instanceStatsName, instanceStatsEndpoint, true, InstanceStats{}, nil),
|
|
instanceVolumesName: NewResource(client, instanceVolumesName, instanceVolumesEndpoint, true, nil, InstanceVolumesPagedResponse{}), // really?
|
|
instancesName: NewResource(client, instancesName, instancesEndpoint, false, Instance{}, InstancesPagedResponse{}),
|
|
invoiceItemsName: NewResource(client, invoiceItemsName, invoiceItemsEndpoint, true, InvoiceItem{}, InvoiceItemsPagedResponse{}),
|
|
invoicesName: NewResource(client, invoicesName, invoicesEndpoint, false, Invoice{}, InvoicesPagedResponse{}),
|
|
ipaddressesName: NewResource(client, ipaddressesName, ipaddressesEndpoint, false, nil, IPAddressesPagedResponse{}), // really?
|
|
ipv6poolsName: NewResource(client, ipv6poolsName, ipv6poolsEndpoint, false, nil, IPv6PoolsPagedResponse{}), // really?
|
|
ipv6rangesName: NewResource(client, ipv6rangesName, ipv6rangesEndpoint, false, IPv6Range{}, IPv6RangesPagedResponse{}),
|
|
kernelsName: NewResource(client, kernelsName, kernelsEndpoint, false, LinodeKernel{}, LinodeKernelsPagedResponse{}),
|
|
lkeClustersName: NewResource(client, lkeClustersName, lkeClustersEndpoint, false, LKECluster{}, LKEClustersPagedResponse{}),
|
|
lkeClusterPoolsName: NewResource(client, lkeClusterPoolsName, lkeClusterPoolsEndpoint, true, LKEClusterPool{}, LKEClusterPoolsPagedResponse{}),
|
|
lkeVersionsName: NewResource(client, lkeVersionsName, lkeVersionsEndpoint, false, LKEVersion{}, LKEVersionsPagedResponse{}),
|
|
longviewName: NewResource(client, longviewName, longviewEndpoint, false, nil, nil), // really?
|
|
longviewclientsName: NewResource(client, longviewclientsName, longviewclientsEndpoint, false, LongviewClient{}, LongviewClientsPagedResponse{}),
|
|
longviewsubscriptionsName: NewResource(client, longviewsubscriptionsName, longviewsubscriptionsEndpoint, false, LongviewSubscription{}, LongviewSubscriptionsPagedResponse{}),
|
|
managedName: NewResource(client, managedName, managedEndpoint, false, nil, nil), // really?
|
|
nodebalancerconfigsName: NewResource(client, nodebalancerconfigsName, nodebalancerconfigsEndpoint, true, NodeBalancerConfig{}, NodeBalancerConfigsPagedResponse{}),
|
|
nodebalancernodesName: NewResource(client, nodebalancernodesName, nodebalancernodesEndpoint, true, NodeBalancerNode{}, NodeBalancerNodesPagedResponse{}),
|
|
nodebalancerStatsName: NewResource(client, nodebalancerStatsName, nodebalancerStatsEndpoint, true, NodeBalancerStats{}, nil),
|
|
nodebalancersName: NewResource(client, nodebalancersName, nodebalancersEndpoint, false, NodeBalancer{}, NodeBalancerConfigsPagedResponse{}),
|
|
notificationsName: NewResource(client, notificationsName, notificationsEndpoint, false, Notification{}, NotificationsPagedResponse{}),
|
|
oauthClientsName: NewResource(client, oauthClientsName, oauthClientsEndpoint, false, OAuthClient{}, OAuthClientsPagedResponse{}),
|
|
objectStorageBucketsName: NewResource(client, objectStorageBucketsName, objectStorageBucketsEndpoint, false, ObjectStorageBucket{}, ObjectStorageBucketsPagedResponse{}),
|
|
objectStorageClustersName: NewResource(client, objectStorageClustersName, objectStorageClustersEndpoint, false, ObjectStorageCluster{}, ObjectStorageClustersPagedResponse{}),
|
|
objectStorageKeysName: NewResource(client, objectStorageKeysName, objectStorageKeysEndpoint, false, ObjectStorageKey{}, ObjectStorageKeysPagedResponse{}),
|
|
paymentsName: NewResource(client, paymentsName, paymentsEndpoint, false, Payment{}, PaymentsPagedResponse{}),
|
|
profileName: NewResource(client, profileName, profileEndpoint, false, nil, nil), // really?
|
|
regionsName: NewResource(client, regionsName, regionsEndpoint, false, Region{}, RegionsPagedResponse{}),
|
|
sshkeysName: NewResource(client, sshkeysName, sshkeysEndpoint, false, SSHKey{}, SSHKeysPagedResponse{}),
|
|
stackscriptsName: NewResource(client, stackscriptsName, stackscriptsEndpoint, false, Stackscript{}, StackscriptsPagedResponse{}),
|
|
tagsName: NewResource(client, tagsName, tagsEndpoint, false, Tag{}, TagsPagedResponse{}),
|
|
ticketsName: NewResource(client, ticketsName, ticketsEndpoint, false, Ticket{}, TicketsPagedResponse{}),
|
|
tokensName: NewResource(client, tokensName, tokensEndpoint, false, Token{}, TokensPagedResponse{}),
|
|
typesName: NewResource(client, typesName, typesEndpoint, false, LinodeType{}, LinodeTypesPagedResponse{}),
|
|
usersName: NewResource(client, usersName, usersEndpoint, false, User{}, UsersPagedResponse{}),
|
|
volumesName: NewResource(client, volumesName, volumesEndpoint, false, Volume{}, VolumesPagedResponse{}),
|
|
}
|
|
|
|
client.resources = resources
|
|
|
|
client.Account = resources[accountName]
|
|
client.DomainRecords = resources[domainRecordsName]
|
|
client.Domains = resources[domainsName]
|
|
client.Events = resources[eventsName]
|
|
client.Firewalls = resources[firewallsName]
|
|
client.IPAddresses = resources[ipaddressesName]
|
|
client.IPv6Pools = resources[ipv6poolsName]
|
|
client.IPv6Ranges = resources[ipv6rangesName]
|
|
client.Images = resources[imagesName]
|
|
client.InstanceConfigs = resources[instanceConfigsName]
|
|
client.InstanceDisks = resources[instanceDisksName]
|
|
client.InstanceIPs = resources[instanceIPsName]
|
|
client.InstanceSnapshots = resources[instanceSnapshotsName]
|
|
client.InstanceStats = resources[instanceStatsName]
|
|
client.InstanceVolumes = resources[instanceVolumesName]
|
|
client.Instances = resources[instancesName]
|
|
client.Invoices = resources[invoicesName]
|
|
client.Kernels = resources[kernelsName]
|
|
client.LKEClusters = resources[lkeClustersName]
|
|
client.LKEClusterPools = resources[lkeClusterPoolsName]
|
|
client.LKEVersions = resources[lkeVersionsName]
|
|
client.Longview = resources[longviewName]
|
|
client.LongviewSubscriptions = resources[longviewsubscriptionsName]
|
|
client.Managed = resources[managedName]
|
|
client.NodeBalancerConfigs = resources[nodebalancerconfigsName]
|
|
client.NodeBalancerNodes = resources[nodebalancernodesName]
|
|
client.NodeBalancerStats = resources[nodebalancerStatsName]
|
|
client.NodeBalancers = resources[nodebalancersName]
|
|
client.Notifications = resources[notificationsName]
|
|
client.OAuthClients = resources[oauthClientsName]
|
|
client.ObjectStorageBuckets = resources[objectStorageBucketsName]
|
|
client.ObjectStorageClusters = resources[objectStorageClustersName]
|
|
client.ObjectStorageKeys = resources[objectStorageKeysName]
|
|
client.Payments = resources[paymentsName]
|
|
client.Profile = resources[profileName]
|
|
client.Regions = resources[regionsName]
|
|
client.SSHKeys = resources[sshkeysName]
|
|
client.StackScripts = resources[stackscriptsName]
|
|
client.Tags = resources[tagsName]
|
|
client.Tickets = resources[ticketsName]
|
|
client.Tokens = resources[tokensName]
|
|
client.Types = resources[typesName]
|
|
client.Users = resources[usersName]
|
|
client.Volumes = resources[volumesName]
|
|
}
|
|
|
|
func copyBool(bPtr *bool) *bool {
|
|
if bPtr == nil {
|
|
return nil
|
|
}
|
|
|
|
var t = *bPtr
|
|
|
|
return &t
|
|
}
|
|
|
|
func copyInt(iPtr *int) *int {
|
|
if iPtr == nil {
|
|
return nil
|
|
}
|
|
|
|
var t = *iPtr
|
|
|
|
return &t
|
|
}
|
|
|
|
func copyString(sPtr *string) *string {
|
|
if sPtr == nil {
|
|
return nil
|
|
}
|
|
|
|
var t = *sPtr
|
|
|
|
return &t
|
|
}
|
|
|
|
func copyTime(tPtr *time.Time) *time.Time {
|
|
if tPtr == nil {
|
|
return nil
|
|
}
|
|
|
|
var t = *tPtr
|
|
|
|
return &t
|
|
}
|