Update lego

This commit is contained in:
Daniel Becker
2019-08-05 18:08:04 +02:00
committed by Traefiker Bot
parent 0a89cccdc0
commit 73e0561610
782 changed files with 113827 additions and 17222 deletions

View File

@@ -8,14 +8,15 @@ type Account struct {
LastName string `json:"last_name"`
Email string `json:"email"`
Company string `json:"company"`
Address1 string `json:"address1"`
Address2 string `json:"address2"`
Address1 string `json:"address_1"`
Address2 string `json:"address_2"`
Balance float32 `json:"balance"`
City string `json:"city"`
State string `json:"state"`
Zip string `json:"zip"`
Country string `json:"country"`
TaxID string `json:"tax_id"`
Phone string `json:"phone"`
CreditCard *CreditCard `json:"credit_card"`
}

View File

@@ -54,59 +54,80 @@ type EventAction string
// EventAction constants represent the actions that cause an Event. New actions may be added in the future.
const (
ActionAccountUpdate EventAction = "account_update"
ActionAccountSettingsUpdate EventAction = "account_settings_update"
ActionBackupsEnable EventAction = "backups_enable"
ActionBackupsCancel EventAction = "backups_cancel"
ActionBackupsRestore EventAction = "backups_restore"
ActionCommunityQuestionReply EventAction = "community_question_reply"
ActionCommunityLike EventAction = "community_like"
ActionCreateCardUpdated EventAction = "credit_card_updated"
ActionDiskCreate EventAction = "disk_create"
ActionDiskDelete EventAction = "disk_delete"
ActionDiskUpdate EventAction = "disk_update"
ActionDiskDuplicate EventAction = "disk_duplicate"
ActionDiskImagize EventAction = "disk_imagize"
ActionDiskResize EventAction = "disk_resize"
ActionDNSRecordCreate EventAction = "dns_record_create"
ActionDNSRecordDelete EventAction = "dns_record_delete"
ActionDNSRecordUpdate EventAction = "dns_record_update"
ActionDNSZoneCreate EventAction = "dns_zone_create"
ActionDNSZoneDelete EventAction = "dns_zone_delete"
ActionDNSZoneUpdate EventAction = "dns_zone_update"
ActionHostReboot EventAction = "host_reboot"
ActionImageDelete EventAction = "image_delete"
ActionImageUpdate EventAction = "image_update"
ActionLassieReboot EventAction = "lassie_reboot"
ActionLinodeAddIP EventAction = "linode_addip"
ActionLinodeBoot EventAction = "linode_boot"
ActionLinodeClone EventAction = "linode_clone"
ActionLinodeCreate EventAction = "linode_create"
ActionLinodeDelete EventAction = "linode_delete"
ActionLinodeUpdate EventAction = "linode_update"
ActionLinodeDeleteIP EventAction = "linode_deleteip"
ActionLinodeMigrate EventAction = "linode_migrate"
ActionLinodeMutate EventAction = "linode_mutate"
ActionLinodeMutateCreate EventAction = "linode_mutate_create"
ActionLinodeReboot EventAction = "linode_reboot"
ActionLinodeRebuild EventAction = "linode_rebuild"
ActionLinodeResize EventAction = "linode_resize"
ActionLinodeResizeCreate EventAction = "linode_resize_create"
ActionLinodeShutdown EventAction = "linode_shutdown"
ActionLinodeSnapshot EventAction = "linode_snapshot"
ActionLinodeConfigCreate EventAction = "linode_config_create"
ActionLinodeConfigDelete EventAction = "linode_config_delete"
ActionLinodeConfigUpdate EventAction = "linode_config_update"
ActionLishBoot EventAction = "lish_boot"
ActionLongviewClientCreate EventAction = "longviewclient_create"
ActionLongviewClientDelete EventAction = "longviewclient_delete"
ActionLongviewClientUpdate EventAction = "longviewclient_update"
ActionManagedDisabled EventAction = "managed_disabled"
ActionManagedEnabled EventAction = "managed_enabled"
ActionManagedServiceCreate EventAction = "managed_service_create"
ActionManagedServiceDelete EventAction = "managed_service_delete"
ActionNodebalancerCreate EventAction = "nodebalancer_create"
ActionNodebalancerDelete EventAction = "nodebalancer_delete"
ActionNodebalancerUpdate EventAction = "nodebalancer_update"
ActionNodebalancerConfigCreate EventAction = "nodebalancer_config_create"
ActionNodebalancerConfigDelete EventAction = "nodebalancer_config_delete"
ActionNodebalancerConfigUpdate EventAction = "nodebalancer_config_update"
ActionPasswordReset EventAction = "password_reset"
ActionPaymentSubmitted EventAction = "payment_submitted"
ActionStackScriptCreate EventAction = "stackscript_create"
ActionStackScriptDelete EventAction = "stackscript_delete"
ActionStackScriptUpdate EventAction = "stackscript_update"
ActionStackScriptPublicize EventAction = "stackscript_publicize"
ActionStackScriptRevise EventAction = "stackscript_revise"
ActionTFADisabled EventAction = "tfa_disabled"
ActionTFAEnabled EventAction = "tfa_enabled"
ActionTicketAttachmentUpload EventAction = "ticket_attachment_upload"
ActionTicketCreate EventAction = "ticket_create"
ActionTicketReply EventAction = "ticket_reply"
ActionTicketUpdate EventAction = "ticket_update"
ActionVolumeAttach EventAction = "volume_attach"
ActionVolumeClone EventAction = "volume_clone"
ActionVolumeCreate EventAction = "volume_create"
ActionVolumeDelte EventAction = "volume_delete"
ActionVolumeUpdate EventAction = "volume_update"
ActionVolumeDetach EventAction = "volume_detach"
ActionVolumeResize EventAction = "volume_resize"
)
@@ -114,10 +135,12 @@ const (
// EntityType constants start with Entity and include Linode API Event Entity Types
type EntityType string
// EntityType contants are the entities an Event can be related to
// EntityType contants are the entities an Event can be related to.
const (
EntityLinode EntityType = "linode"
EntityDisk EntityType = "disk"
EntityLinode EntityType = "linode"
EntityDisk EntityType = "disk"
EntityDomain EntityType = "domain"
EntityNodebalancer EntityType = "nodebalancer"
)
// EventStatus constants start with Event and include Linode API Event Status values

View File

@@ -10,13 +10,14 @@ type Notification struct {
UntilStr string `json:"until"`
WhenStr string `json:"when"`
Label string `json:"label"`
Message string `json:"message"`
Type string `json:"type"`
Severity string `json:"severity"`
Entity *NotificationEntity `json:"entity"`
Until *time.Time `json:"-"`
When *time.Time `json:"-"`
Label string `json:"label"`
Body *string `json:"body"`
Message string `json:"message"`
Type NotificationType `json:"type"`
Severity NotificationSeverity `json:"severity"`
Entity *NotificationEntity `json:"entity"`
Until *time.Time `json:"-"`
When *time.Time `json:"-"`
}
// NotificationEntity adds detailed information about the Notification.
@@ -28,6 +29,33 @@ type NotificationEntity struct {
URL string `json:"url"`
}
// NotificationSeverity constants start with Notification and include all known Linode API Notification Severities.
type NotificationSeverity string
// NotificationSeverity constants represent the actions that cause a Notification. New severities may be added in the future.
const (
NotificationMinor NotificationSeverity = "minor"
NotificationMajor NotificationSeverity = "major"
NotificationCritical NotificationSeverity = "critical"
)
// NotificationType constants start with Notification and include all known Linode API Notification Types.
type NotificationType string
// NotificationType constants represent the actions that cause a Notification. New types may be added in the future.
const (
NotificationMigrationScheduled NotificationType = "migration_scheduled"
NotificationMigrationImminent NotificationType = "migration_imminent"
NotificationMigrationPending NotificationType = "migration_pending"
NotificationRebootScheduled NotificationType = "reboot_scheduled"
NotificationOutage NotificationType = "outage"
NotificationPaymentDue NotificationType = "payment_due"
NotificationTicketImportant NotificationType = "ticket_important"
NotificationTicketAbuse NotificationType = "ticket_abuse"
NotificationNotice NotificationType = "notice"
NotificationMaintenance NotificationType = "maintenance"
)
// NotificationsPagedResponse represents a paginated Notifications API response
type NotificationsPagedResponse struct {
*PageOptions

View File

@@ -0,0 +1,190 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
)
// OAuthClientStatus constants start with OAuthClient and include Linode API Instance Status values
type OAuthClientStatus string
// OAuthClientStatus constants reflect the current status of an OAuth Client
const (
OAuthClientActive OAuthClientStatus = "active"
OAuthClientDisabled OAuthClientStatus = "disabled"
OAuthClientSuspended OAuthClientStatus = "suspended"
)
// OAuthClient represents a OAuthClient object
type OAuthClient struct {
// The unique ID of this OAuth Client.
ID string `json:"id"`
// The location a successful log in from https://login.linode.com should be redirected to for this client. The receiver of this redirect should be ready to accept an OAuth exchange code and finish the OAuth exchange.
RedirectURI string `json:"redirect_uri"`
// The name of this application. This will be presented to users when they are asked to grant it access to their Account.
Label string `json:"label"`
// Current status of the OAuth Client, Enum: "active" "disabled" "suspended"
Status OAuthClientStatus `json:"status"`
// The OAuth Client secret, used in the OAuth exchange. This is returned as <REDACTED> except when an OAuth Client is created or its secret is reset. This is a secret, and should not be shared or disclosed publicly.
Secret string `json:"secret"`
// If this OAuth Client is public or private.
Public bool `json:"public"`
// The URL where this client's thumbnail may be viewed, or nil if this client does not have a thumbnail set.
ThumbnailURL *string `json:"thumbnail_url"`
}
// OAuthClientCreateOptions fields are those accepted by CreateOAuthClient
type OAuthClientCreateOptions struct {
// The location a successful log in from https://login.linode.com should be redirected to for this client. The receiver of this redirect should be ready to accept an OAuth exchange code and finish the OAuth exchange.
RedirectURI string `json:"redirect_uri"`
// The name of this application. This will be presented to users when they are asked to grant it access to their Account.
Label string `json:"label"`
// If this OAuth Client is public or private.
Public bool `json:"public"`
}
// OAuthClientUpdateOptions fields are those accepted by UpdateOAuthClient
type OAuthClientUpdateOptions struct {
// The location a successful log in from https://login.linode.com should be redirected to for this client. The receiver of this redirect should be ready to accept an OAuth exchange code and finish the OAuth exchange.
RedirectURI string `json:"redirect_uri"`
// The name of this application. This will be presented to users when they are asked to grant it access to their Account.
Label string `json:"label"`
// If this OAuth Client is public or private.
Public bool `json:"public"`
}
// GetCreateOptions converts a OAuthClient to OAuthClientCreateOptions for use in CreateOAuthClient
func (i OAuthClient) GetCreateOptions() (o OAuthClientCreateOptions) {
o.RedirectURI = i.RedirectURI
o.Label = i.Label
o.Public = i.Public
return
}
// GetUpdateOptions converts a OAuthClient to OAuthClientUpdateOptions for use in UpdateOAuthClient
func (i OAuthClient) GetUpdateOptions() (o OAuthClientUpdateOptions) {
o.RedirectURI = i.RedirectURI
o.Label = i.Label
o.Public = i.Public
return
}
// OAuthClientsPagedResponse represents a paginated OAuthClient API response
type OAuthClientsPagedResponse struct {
*PageOptions
Data []OAuthClient `json:"data"`
}
// endpoint gets the endpoint URL for OAuthClient
func (OAuthClientsPagedResponse) endpoint(c *Client) string {
endpoint, err := c.OAuthClients.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends OAuthClients when processing paginated OAuthClient responses
func (resp *OAuthClientsPagedResponse) appendData(r *OAuthClientsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListOAuthClients lists OAuthClients
func (c *Client) ListOAuthClients(ctx context.Context, opts *ListOptions) ([]OAuthClient, error) {
response := OAuthClientsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// GetOAuthClient gets the OAuthClient with the provided ID
func (c *Client) GetOAuthClient(ctx context.Context, id string) (*OAuthClient, error) {
e, err := c.OAuthClients.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&OAuthClient{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*OAuthClient), nil
}
// CreateOAuthClient creates an OAuthClient
func (c *Client) CreateOAuthClient(ctx context.Context, createOpts OAuthClientCreateOptions) (*OAuthClient, error) {
var body string
e, err := c.OAuthClients.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&OAuthClient{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*OAuthClient), nil
}
// UpdateOAuthClient updates the OAuthClient with the specified id
func (c *Client) UpdateOAuthClient(ctx context.Context, id string, updateOpts OAuthClientUpdateOptions) (*OAuthClient, error) {
var body string
e, err := c.OAuthClients.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
req := c.R(ctx).SetResult(&OAuthClient{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*OAuthClient), nil
}
// DeleteOAuthClient deletes the OAuthClient with the specified id
func (c *Client) DeleteOAuthClient(ctx context.Context, id string) error {
e, err := c.OAuthClients.Endpoint()
if err != nil {
return err
}
e = fmt.Sprintf("%s/%s", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

115
vendor/github.com/linode/linodego/account_payments.go generated vendored Normal file
View File

@@ -0,0 +1,115 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
"time"
)
// Payment represents a Payment object
type Payment struct {
// The unique ID of the Payment
ID int `json:"id"`
// The amount, in US dollars, of the Payment.
USD json.Number `json:"usd,Number"`
// When the Payment was made.
DateStr string `json:"date"`
Date *time.Time `json:"-"`
}
// PaymentCreateOptions fields are those accepted by CreatePayment
type PaymentCreateOptions struct {
// CVV (Card Verification Value) of the credit card to be used for the Payment
CVV string `json:"cvv,omitempty"`
// The amount, in US dollars, of the Payment
USD json.Number `json:"usd,Number"`
}
// GetCreateOptions converts a Payment to PaymentCreateOptions for use in CreatePayment
func (i Payment) GetCreateOptions() (o PaymentCreateOptions) {
o.USD = i.USD
return
}
// PaymentsPagedResponse represents a paginated Payment API response
type PaymentsPagedResponse struct {
*PageOptions
Data []Payment `json:"data"`
}
// endpoint gets the endpoint URL for Payment
func (PaymentsPagedResponse) endpoint(c *Client) string {
endpoint, err := c.Payments.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends Payments when processing paginated Payment responses
func (resp *PaymentsPagedResponse) appendData(r *PaymentsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListPayments lists Payments
func (c *Client) ListPayments(ctx context.Context, opts *ListOptions) ([]Payment, error) {
response := PaymentsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *Payment) fixDates() *Payment {
i.Date, _ = parseDates(i.DateStr)
return i
}
// GetPayment gets the payment with the provided ID
func (c *Client) GetPayment(ctx context.Context, id int) (*Payment, error) {
e, err := c.Payments.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Payment{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*Payment).fixDates(), nil
}
// CreatePayment creates a Payment
func (c *Client) CreatePayment(ctx context.Context, createOpts PaymentCreateOptions) (*Payment, error) {
var body string
e, err := c.Payments.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&Payment{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*Payment).fixDates(), nil
}

72
vendor/github.com/linode/linodego/account_settings.go generated vendored Normal file
View File

@@ -0,0 +1,72 @@
package linodego
import (
"context"
"encoding/json"
)
// AccountSettings are the account wide flags or plans that effect new resources
type AccountSettings struct {
// The default backups enrollment status for all new Linodes for all users on the account. When enabled, backups are mandatory per instance.
BackupsEnabled bool `json:"backups_enabled"`
// Wether or not Linode Managed service is enabled for the account.
Managed bool `json:"managed"`
// Wether or not the Network Helper is enabled for all new Linode Instance Configs on the account.
NetworkHelper bool `json:"network_helper"`
// A plan name like "longview-3"..."longview-100", or a nil value for to cancel any existing subscription plan.
LongviewSubscription *string `json:"longview_subscription"`
}
// AccountSettingsUpdateOptions are the updateable account wide flags or plans that effect new resources.
type AccountSettingsUpdateOptions struct {
// The default backups enrollment status for all new Linodes for all users on the account. When enabled, backups are mandatory per instance.
BackupsEnabled *bool `json:"backups_enabled,omitempty"`
// A plan name like "longview-3"..."longview-100", or a nil value for to cancel any existing subscription plan.
LongviewSubscription *string `json:"longview_subscription,omitempty"`
// The default network helper setting for all new Linodes and Linode Configs for all users on the account.
NetworkHelper *bool `json:"network_helper,omitempty"`
}
// GetAccountSettings gets the account wide flags or plans that effect new resources
func (c *Client) GetAccountSettings(ctx context.Context) (*AccountSettings, error) {
e, err := c.AccountSettings.Endpoint()
if err != nil {
return nil, err
}
r, err := coupleAPIErrors(c.R(ctx).SetResult(&AccountSettings{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*AccountSettings), nil
}
// UpdateAccountSettings updates the settings associated with the account
func (c *Client) UpdateAccountSettings(ctx context.Context, settings AccountSettingsUpdateOptions) (*AccountSettings, error) {
var body string
e, err := c.AccountSettings.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&AccountSettings{})
if bodyData, err := json.Marshal(settings); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*AccountSettings), nil
}

164
vendor/github.com/linode/linodego/account_users.go generated vendored Normal file
View File

@@ -0,0 +1,164 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
)
// User represents a User object
type User struct {
Username string `json:"username"`
Email string `json:"email"`
Restricted bool `json:"restricted"`
SSHKeys []string `json:"ssh_keys"`
}
// UserCreateOptions fields are those accepted by CreateUser
type UserCreateOptions struct {
Username string `json:"username"`
Email string `json:"email"`
Restricted bool `json:"restricted,omitempty"`
}
// UserUpdateOptions fields are those accepted by UpdateUser
type UserUpdateOptions struct {
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
Restricted *bool `json:"restricted,omitempty"`
SSHKeys *[]string `json:"ssh_keys,omitempty"`
}
// GetCreateOptions converts a User to UserCreateOptions for use in CreateUser
func (i User) GetCreateOptions() (o UserCreateOptions) {
o.Username = i.Username
o.Email = i.Email
o.Restricted = i.Restricted
return
}
// GetUpdateOptions converts a User to UserUpdateOptions for use in UpdateUser
func (i User) GetUpdateOptions() (o UserUpdateOptions) {
o.Username = i.Username
o.Email = i.Email
o.Restricted = copyBool(&i.Restricted)
return
}
// UsersPagedResponse represents a paginated User API response
type UsersPagedResponse struct {
*PageOptions
Data []User `json:"data"`
}
// endpoint gets the endpoint URL for User
func (UsersPagedResponse) endpoint(c *Client) string {
endpoint, err := c.Users.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends Users when processing paginated User responses
func (resp *UsersPagedResponse) appendData(r *UsersPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListUsers lists Users on the account
func (c *Client) ListUsers(ctx context.Context, opts *ListOptions) ([]User, error) {
response := UsersPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *User) fixDates() *User {
return i
}
// GetUser gets the user with the provided ID
func (c *Client) GetUser(ctx context.Context, id string) (*User, error) {
e, err := c.Users.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&User{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*User).fixDates(), nil
}
// CreateUser creates a User. The email address must be confirmed before the
// User account can be accessed.
func (c *Client) CreateUser(ctx context.Context, createOpts UserCreateOptions) (*User, error) {
var body string
e, err := c.Users.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&User{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*User).fixDates(), nil
}
// UpdateUser updates the User with the specified id
func (c *Client) UpdateUser(ctx context.Context, id string, updateOpts UserUpdateOptions) (*User, error) {
var body string
e, err := c.Users.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
req := c.R(ctx).SetResult(&User{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*User).fixDates(), nil
}
// DeleteUser deletes the User with the specified id
func (c *Client) DeleteUser(ctx context.Context, id string) error {
e, err := c.Users.Endpoint()
if err != nil {
return err
}
e = fmt.Sprintf("%s/%s", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@@ -3,33 +3,40 @@ package linodego
import (
"context"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strconv"
"time"
"github.com/go-resty/resty"
"gopkg.in/resty.v1"
)
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"
// APIProto connect to API with http(s)
APIProto = "https"
// Version of linodego
Version = "0.5.1"
Version = "0.10.0"
// APIEnvVar environment var to check for API token
APIEnvVar = "LINODE_TOKEN"
// APISecondsPerPoll how frequently to poll for new Events
APISecondsPerPoll = 10
// APISecondsPerPoll how frequently to poll for new Events or Status in WaitFor functions
APISecondsPerPoll = 3
// DefaultUserAgent is the default User-Agent sent in HTTP request headers
DefaultUserAgent = "linodego " + Version + " https://github.com/linode/linodego"
)
// DefaultUserAgent is the default User-Agent sent in HTTP request headers
const DefaultUserAgent = "linodego " + Version + " https://github.com/linode/linodego"
var envDebug = false
var (
envDebug = false
)
// Client is a wrapper around the Resty client
type Client struct {
@@ -38,12 +45,15 @@ type Client struct {
resources map[string]*Resource
debug bool
millisecondsPerPoll time.Duration
Images *Resource
InstanceDisks *Resource
InstanceConfigs *Resource
InstanceSnapshots *Resource
InstanceIPs *Resource
InstanceVolumes *Resource
InstanceStats *Resource
Instances *Resource
IPAddresses *Resource
IPv6Pools *Resource
@@ -63,13 +73,20 @@ type Client struct {
NodeBalancerNodes *Resource
SSHKeys *Resource
Tickets *Resource
Tokens *Resource
Token *Resource
Account *Resource
AccountSettings *Resource
Invoices *Resource
InvoiceItems *Resource
Events *Resource
Notifications *Resource
OAuthClients *Resource
Profile *Resource
Managed *Resource
Tags *Resource
Users *Resource
Payments *Resource
}
func init() {
@@ -115,6 +132,25 @@ func (c *Client) SetBaseURL(url string) *Client {
return c
}
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
}
// SetPollDelay sets the number of milliseconds to wait between events or status polls.
// Affects all WaitFor* functions.
func (c *Client) SetPollDelay(delay time.Duration) *Client {
c.millisecondsPerPoll = delay
return c
}
// Resource looks up a resource by name
func (c Client) Resource(resourceName string) *Resource {
selectedResource, ok := c.resources[resourceName]
@@ -126,10 +162,30 @@ func (c Client) Resource(resourceName string) *Resource {
// NewClient factory to create new Client struct
func NewClient(hc *http.Client) (client Client) {
restyClient := resty.NewWithClient(hc)
client.resty = restyClient
if hc != nil {
client.resty = resty.NewWithClient(hc)
} else {
client.resty = resty.New()
}
client.SetUserAgent(DefaultUserAgent)
client.SetBaseURL(fmt.Sprintf("%s://%s/%s", APIProto, APIHost, APIVersion))
baseURL, baseURLExists := os.LookupEnv(APIHostVar)
if baseURLExists {
client.SetBaseURL(baseURL)
} else {
client.SetBaseURL(fmt.Sprintf("%s://%s/%s", APIProto, APIHost, 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)
resources := map[string]*Resource{
stackscriptsName: NewResource(&client, stackscriptsName, stackscriptsEndpoint, false, Stackscript{}, StackscriptsPagedResponse{}),
@@ -140,8 +196,9 @@ func NewClient(hc *http.Client) (client Client) {
instanceSnapshotsName: NewResource(&client, instanceSnapshotsName, instanceSnapshotsEndpoint, true, InstanceSnapshot{}, nil),
instanceIPsName: NewResource(&client, instanceIPsName, instanceIPsEndpoint, true, InstanceIP{}, nil), // really?
instanceVolumesName: NewResource(&client, instanceVolumesName, instanceVolumesEndpoint, true, nil, InstanceVolumesPagedResponse{}), // really?
ipaddressesName: NewResource(&client, ipaddressesName, ipaddressesEndpoint, false, nil, IPAddressesPagedResponse{}), // really?
ipv6poolsName: NewResource(&client, ipv6poolsName, ipv6poolsEndpoint, false, nil, IPv6PoolsPagedResponse{}), // really?
instanceStatsName: NewResource(&client, instanceStatsName, instanceStatsEndpoint, true, InstanceStats{}, nil),
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{}),
regionsName: NewResource(&client, regionsName, regionsEndpoint, false, Region{}, RegionsPagedResponse{}),
volumesName: NewResource(&client, volumesName, volumesEndpoint, false, Volume{}, VolumesPagedResponse{}),
@@ -155,14 +212,21 @@ func NewClient(hc *http.Client) (client Client) {
nodebalancersName: NewResource(&client, nodebalancersName, nodebalancersEndpoint, false, NodeBalancer{}, NodeBalancerConfigsPagedResponse{}),
nodebalancerconfigsName: NewResource(&client, nodebalancerconfigsName, nodebalancerconfigsEndpoint, true, NodeBalancerConfig{}, NodeBalancerConfigsPagedResponse{}),
nodebalancernodesName: NewResource(&client, nodebalancernodesName, nodebalancernodesEndpoint, true, NodeBalancerNode{}, NodeBalancerNodesPagedResponse{}),
notificationsName: NewResource(&client, notificationsName, notificationsEndpoint, false, Notification{}, NotificationsPagedResponse{}),
oauthClientsName: NewResource(&client, oauthClientsName, oauthClientsEndpoint, false, OAuthClient{}, OAuthClientsPagedResponse{}),
sshkeysName: NewResource(&client, sshkeysName, sshkeysEndpoint, false, SSHKey{}, SSHKeysPagedResponse{}),
ticketsName: NewResource(&client, ticketsName, ticketsEndpoint, false, Ticket{}, TicketsPagedResponse{}),
accountName: NewResource(&client, accountName, accountEndpoint, false, Account{}, nil), // really?
tokensName: NewResource(&client, tokensName, tokensEndpoint, false, Token{}, TokensPagedResponse{}),
accountName: NewResource(&client, accountName, accountEndpoint, false, Account{}, nil), // really?
accountSettingsName: NewResource(&client, accountSettingsName, accountSettingsEndpoint, false, AccountSettings{}, nil), // really?
eventsName: NewResource(&client, eventsName, eventsEndpoint, false, Event{}, EventsPagedResponse{}),
invoicesName: NewResource(&client, invoicesName, invoicesEndpoint, false, Invoice{}, InvoicesPagedResponse{}),
invoiceItemsName: NewResource(&client, invoiceItemsName, invoiceItemsEndpoint, true, InvoiceItem{}, InvoiceItemsPagedResponse{}),
profileName: NewResource(&client, profileName, profileEndpoint, false, nil, nil), // really?
managedName: NewResource(&client, managedName, managedEndpoint, false, nil, nil), // really?
tagsName: NewResource(&client, tagsName, tagsEndpoint, false, Tag{}, TagsPagedResponse{}),
usersName: NewResource(&client, usersName, usersEndpoint, false, User{}, UsersPagedResponse{}),
paymentsName: NewResource(&client, paymentsName, paymentsEndpoint, false, Payment{}, PaymentsPagedResponse{}),
}
client.resources = resources
@@ -177,6 +241,7 @@ func NewClient(hc *http.Client) (client Client) {
client.InstanceSnapshots = resources[instanceSnapshotsName]
client.InstanceIPs = resources[instanceIPsName]
client.InstanceVolumes = resources[instanceVolumesName]
client.InstanceStats = resources[instanceStatsName]
client.IPAddresses = resources[ipaddressesName]
client.IPv6Pools = resources[ipv6poolsName]
client.IPv6Ranges = resources[ipv6rangesName]
@@ -190,12 +255,50 @@ func NewClient(hc *http.Client) (client Client) {
client.NodeBalancers = resources[nodebalancersName]
client.NodeBalancerConfigs = resources[nodebalancerconfigsName]
client.NodeBalancerNodes = resources[nodebalancernodesName]
client.Notifications = resources[notificationsName]
client.OAuthClients = resources[oauthClientsName]
client.SSHKeys = resources[sshkeysName]
client.Tickets = resources[ticketsName]
client.Tokens = resources[tokensName]
client.Account = resources[accountName]
client.Events = resources[eventsName]
client.Invoices = resources[invoicesName]
client.Profile = resources[profileName]
client.Managed = resources[managedName]
client.Tags = resources[tagsName]
client.Users = resources[usersName]
client.Payments = resources[paymentsName]
return
}
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
}

View File

@@ -80,22 +80,6 @@ func (d DomainRecord) GetUpdateOptions() (du DomainRecordUpdateOptions) {
return
}
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
}
// DomainRecordsPagedResponse represents a paginated DomainRecord API response
type DomainRecordsPagedResponse struct {
*PageOptions

View File

@@ -39,6 +39,9 @@ type Domain struct {
// The list of IPs that may perform a zone transfer for this Domain. This is potentially dangerous, and should be set to an empty list unless you intend to use it.
AXfrIPs []string `json:"axfr_ips"`
// An array of tags applied to this object. Tags are for organizational purposes only.
Tags []string `json:"tags"`
// The amount of time in seconds that may pass before this Domain is no longer authoritative. Valid values are 300, 3600, 7200, 14400, 28800, 57600, 86400, 172800, 345600, 604800, 1209600, and 2419200 - any other value will be rounded to the nearest valid value.
ExpireSec int `json:"expire_sec"`
@@ -81,6 +84,9 @@ type DomainCreateOptions struct {
// The list of IPs that may perform a zone transfer for this Domain. This is potentially dangerous, and should be set to an empty list unless you intend to use it.
AXfrIPs []string `json:"axfr_ips,omitempty"`
// An array of tags applied to this object. Tags are for organizational purposes only.
Tags []string `json:"tags"`
// The amount of time in seconds that may pass before this Domain is no longer authoritative. Valid values are 300, 3600, 7200, 14400, 28800, 57600, 86400, 172800, 345600, 604800, 1209600, and 2419200 - any other value will be rounded to the nearest valid value.
ExpireSec int `json:"expire_sec,omitempty"`
@@ -123,6 +129,9 @@ type DomainUpdateOptions struct {
// The list of IPs that may perform a zone transfer for this Domain. This is potentially dangerous, and should be set to an empty list unless you intend to use it.
AXfrIPs []string `json:"axfr_ips,omitempty"`
// An array of tags applied to this object. Tags are for organizational purposes only.
Tags []string `json:"tags"`
// The amount of time in seconds that may pass before this Domain is no longer authoritative. Valid values are 300, 3600, 7200, 14400, 28800, 57600, 86400, 172800, 345600, 604800, 1209600, and 2419200 - any other value will be rounded to the nearest valid value.
ExpireSec int `json:"expire_sec,omitempty"`
@@ -164,6 +173,7 @@ func (d Domain) GetUpdateOptions() (du DomainUpdateOptions) {
du.RetrySec = d.RetrySec
du.MasterIPs = d.MasterIPs
du.AXfrIPs = d.AXfrIPs
du.Tags = d.Tags
du.ExpireSec = d.ExpireSec
du.RefreshSec = d.RefreshSec
du.TTLSec = d.TTLSec

View File

@@ -6,7 +6,7 @@ import (
"net/http"
"strings"
"github.com/go-resty/resty"
"gopkg.in/resty.v1"
)
const (
@@ -60,7 +60,7 @@ func coupleAPIErrors(r *resty.Response, err error) (*resty.Response, error) {
}
func (e APIError) Error() string {
var x []string
x := []string{}
for _, msg := range e.Errors {
x = append(x, msg.Error())
}

View File

@@ -10,7 +10,7 @@ import (
// Image represents a deployable Image object for use with Linode Instances
type Image struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
ExpiryStr string `json:"expiry"`
ID string `json:"id"`
CreatedBy string `json:"created_by"`
Label string `json:"label"`
@@ -22,7 +22,7 @@ type Image struct {
Deprecated bool `json:"deprecated"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Expiry *time.Time `json:"-"`
}
// ImageCreateOptions fields are those accepted by CreateImage
@@ -40,7 +40,12 @@ type ImageUpdateOptions struct {
func (i *Image) fixDates() *Image {
i.Created, _ = parseDates(i.CreatedStr)
i.Updated, _ = parseDates(i.UpdatedStr)
if len(i.ExpiryStr) > 0 {
i.Expiry, _ = parseDates(i.ExpiryStr)
} else {
i.Expiry = nil
}
return i
}
@@ -94,7 +99,7 @@ func (c *Client) GetImage(ctx context.Context, id string) (*Image, error) {
if err != nil {
return nil, err
}
return r.Result().(*Image), nil
return r.Result().(*Image).fixDates(), nil
}
// CreateImage creates a Image

View File

@@ -14,7 +14,7 @@ type InstanceDisk struct {
ID int `json:"id"`
Label string `json:"label"`
Status string `json:"status"`
Status DiskStatus `json:"status"`
Size int `json:"size"`
Filesystem DiskFilesystem `json:"filesystem"`
Created time.Time `json:"-"`
@@ -33,6 +33,16 @@ const (
FilesystemInitrd DiskFilesystem = "initrd"
)
// DiskStatus constants have the prefix "Disk" and include Linode API Instance Disk Status
type DiskStatus string
// DiskStatus constants represent the status values an Instance Disk may have
const (
DiskReady DiskStatus = "ready"
DiskNotReady DiskStatus = "not ready"
DiskDeleting DiskStatus = "deleting"
)
// InstanceDisksPagedResponse represents a paginated InstanceDisk API response
type InstanceDisksPagedResponse struct {
*PageOptions
@@ -175,13 +185,13 @@ func (c *Client) RenameInstanceDisk(ctx context.Context, linodeID int, diskID in
}
// ResizeInstanceDisk resizes the size of the Instance disk
func (c *Client) ResizeInstanceDisk(ctx context.Context, linodeID int, diskID int, size int) (*InstanceDisk, error) {
func (c *Client) ResizeInstanceDisk(ctx context.Context, linodeID int, diskID int, size int) error {
var body string
e, err := c.InstanceDisks.endpointWithID(linodeID)
if err != nil {
return nil, err
return err
}
e = fmt.Sprintf("%s/%d", e, diskID)
e = fmt.Sprintf("%s/%d/resize", e, diskID)
req := c.R(ctx).SetResult(&InstanceDisk{})
updateOpts := map[string]interface{}{
@@ -191,17 +201,41 @@ func (c *Client) ResizeInstanceDisk(ctx context.Context, linodeID int, diskID in
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
return NewError(err)
}
r, err := coupleAPIErrors(req.
_, err = coupleAPIErrors(req.
SetBody(body).
Post(e))
return err
}
// PasswordResetInstanceDisk resets the "root" account password on the Instance disk
func (c *Client) PasswordResetInstanceDisk(ctx context.Context, linodeID int, diskID int, password string) error {
var body string
e, err := c.InstanceDisks.endpointWithID(linodeID)
if err != nil {
return nil, err
return err
}
return r.Result().(*InstanceDisk).fixDates(), nil
e = fmt.Sprintf("%s/%d/password", e, diskID)
req := c.R(ctx).SetResult(&InstanceDisk{})
updateOpts := map[string]interface{}{
"password": password,
}
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return NewError(err)
}
_, err = coupleAPIErrors(req.
SetBody(body).
Post(e))
return err
}
// DeleteInstanceDisk deletes a Linode Instance Disk

View File

@@ -14,22 +14,23 @@ type InstanceIPAddressResponse struct {
// InstanceIPv4Response contains the details of all IPv4 addresses associated with an Instance
type InstanceIPv4Response struct {
Public []*InstanceIP `json:"public"`
Private []*InstanceIP `json:"private"`
Shared []*InstanceIP `json:"shared"`
Public []*InstanceIP `json:"public"`
Private []*InstanceIP `json:"private"`
Shared []*InstanceIP `json:"shared"`
Reserved []*InstanceIP `json:"reserved"`
}
// InstanceIP represents an Instance IP with additional DNS and networking details
type InstanceIP struct {
Address string `json:"address"`
Gateway string `json:"gateway"`
SubnetMask string `json:"subnet_mask"`
Prefix int `json:"prefix"`
Type string `json:"type"`
Public bool `json:"public"`
RDNS string `json:"rdns"`
LinodeID int `json:"linode_id"`
Region string `json:"region"`
Address string `json:"address"`
Gateway string `json:"gateway"`
SubnetMask string `json:"subnet_mask"`
Prefix int `json:"prefix"`
Type InstanceIPType `json:"type"`
Public bool `json:"public"`
RDNS string `json:"rdns"`
LinodeID int `json:"linode_id"`
Region string `json:"region"`
}
// InstanceIPv6Response contains the IPv6 addresses and ranges for an Instance
@@ -43,8 +44,20 @@ type InstanceIPv6Response struct {
type IPv6Range struct {
Range string `json:"range"`
Region string `json:"region"`
Prefix int `json:"prefix"`
}
// InstanceIPType constants start with IPType and include Linode Instance IP Types
type InstanceIPType string
// InstanceIPType constants represent the IP types an Instance IP may be
const (
IPTypeIPv4 InstanceIPType = "ipv4"
IPTypeIPv6 InstanceIPType = "ipv6"
IPTypeIPv6Pool InstanceIPType = "ipv6/pool"
IPTypeIPv6Range InstanceIPType = "ipv6/range"
)
// GetInstanceIPAddresses gets the IPAddresses for a Linode instance
func (c *Client) GetInstanceIPAddresses(ctx context.Context, linodeID int) (*InstanceIPAddressResponse, error) {
e, err := c.InstanceIPs.endpointWithID(linodeID)
@@ -104,3 +117,30 @@ func (c *Client) AddInstanceIPAddress(ctx context.Context, linodeID int, public
return r.Result().(*InstanceIP), nil
}
// UpdateInstanceIPAddress updates the IPAddress with the specified instance id and IP address
func (c *Client) UpdateInstanceIPAddress(ctx context.Context, linodeID int, ipAddress string, updateOpts IPAddressUpdateOptions) (*InstanceIP, error) {
var body string
e, err := c.InstanceIPs.endpointWithID(linodeID)
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, ipAddress)
req := c.R(ctx).SetResult(&InstanceIP{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceIP), nil
}

68
vendor/github.com/linode/linodego/instance_stats.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
package linodego
import (
"context"
"fmt"
)
// StatsNet represents a network stats object
type StatsNet struct {
In [][]float64 `json:"in"`
Out [][]float64 `json:"out"`
PrivateIn [][]float64 `json:"private_in"`
PrivateOut [][]float64 `json:"private_out"`
}
// StatsIO represents an IO stats object
type StatsIO struct {
IO [][]float64 `json:"io"`
Swap [][]float64 `json:"swap"`
}
// InstanceStatsData represents an instance stats data object
type InstanceStatsData struct {
CPU [][]float64 `json:"cpu"`
IO StatsIO `json:"io"`
NetV4 StatsNet `json:"netv4"`
NetV6 StatsNet `json:"netv6"`
}
// InstanceStats represents an instance stats object
type InstanceStats struct {
Title string `json:"title"`
Data InstanceStatsData `json:"data"`
}
// endpointWithIDAndDate gets the endpoint URL for InstanceStats of a given Instance and Year/Month
func endpointWithIDAndDate(c *Client, id int, year int, month int) string {
endpoint, err := c.InstanceStats.endpointWithID(id)
if err != nil {
panic(err)
}
endpoint = fmt.Sprintf("%s/%d/%d", endpoint, year, month)
return endpoint
}
// GetInstanceStats gets the template with the provided ID
func (c *Client) GetInstanceStats(ctx context.Context, linodeID int) (*InstanceStats, error) {
e, err := c.InstanceStats.endpointWithID(linodeID)
if err != nil {
return nil, err
}
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceStats{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceStats), nil
}
// GetInstanceStatsByDate gets the template with the provided ID, year, and month
func (c *Client) GetInstanceStatsByDate(ctx context.Context, linodeID int, year int, month int) (*InstanceStats, error) {
e := endpointWithIDAndDate(c, linodeID, year, month)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&InstanceStats{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceStats), nil
}

View File

@@ -36,21 +36,23 @@ type Instance struct {
CreatedStr string `json:"created"`
UpdatedStr string `json:"updated"`
ID int `json:"id"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Region string `json:"region"`
Alerts *InstanceAlert `json:"alerts"`
Backups *InstanceBackup `json:"backups"`
Image string `json:"image"`
Group string `json:"group"`
IPv4 []*net.IP `json:"ipv4"`
IPv6 string `json:"ipv6"`
Label string `json:"label"`
Type string `json:"type"`
Status InstanceStatus `json:"status"`
Hypervisor string `json:"hypervisor"`
Specs *InstanceSpec `json:"specs"`
ID int `json:"id"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
Region string `json:"region"`
Alerts *InstanceAlert `json:"alerts"`
Backups *InstanceBackup `json:"backups"`
Image string `json:"image"`
Group string `json:"group"`
IPv4 []*net.IP `json:"ipv4"`
IPv6 string `json:"ipv6"`
Label string `json:"label"`
Type string `json:"type"`
Status InstanceStatus `json:"status"`
Hypervisor string `json:"hypervisor"`
Specs *InstanceSpec `json:"specs"`
WatchdogEnabled bool `json:"watchdog_enabled"`
Tags []string `json:"tags"`
}
// InstanceSpec represents a linode spec
@@ -79,6 +81,18 @@ type InstanceBackup struct {
}
}
// InstanceTransfer pool stats for a Linode Instance during the current billing month
type InstanceTransfer struct {
// Bytes of transfer this instance has consumed
Used int `json:"used"`
// GB of billable transfer this instance has consumed
Billable int `json:"billable"`
// GB of transfer this instance adds to the Transfer pool
Quota int `json:"quota"`
}
// InstanceCreateOptions require only Region and Type
type InstanceCreateOptions struct {
Region string `json:"region"`
@@ -94,6 +108,7 @@ type InstanceCreateOptions struct {
Image string `json:"image,omitempty"`
BackupsEnabled bool `json:"backups_enabled,omitempty"`
PrivateIP bool `json:"private_ip,omitempty"`
Tags []string `json:"tags,omitempty"`
// Creation fields that need to be set explicitly false, "", or 0 use pointers
SwapSize *int `json:"swap_size,omitempty"`
@@ -107,6 +122,19 @@ type InstanceUpdateOptions struct {
Backups *InstanceBackup `json:"backups,omitempty"`
Alerts *InstanceAlert `json:"alerts,omitempty"`
WatchdogEnabled *bool `json:"watchdog_enabled,omitempty"`
Tags *[]string `json:"tags,omitempty"`
}
// GetUpdateOptions converts an Instance to InstanceUpdateOptions for use in UpdateInstance
func (l *Instance) GetUpdateOptions() InstanceUpdateOptions {
return InstanceUpdateOptions{
Label: l.Label,
Group: l.Group,
Backups: l.Backups,
Alerts: l.Alerts,
WatchdogEnabled: &l.WatchdogEnabled,
Tags: &l.Tags,
}
}
// InstanceCloneOptions is an options struct sent when Cloning an Instance
@@ -123,6 +151,14 @@ type InstanceCloneOptions struct {
Configs []int `json:"configs,omitempty"`
}
// InstanceResizeOptions
type InstanceResizeOptions struct {
Type string `json:"type"`
// When enabled, an instance resize will also resize a data disk if the instance has no more than one data disk and one swap disk
AllowAutoDiskResize *bool `json:"allow_auto_disk_resize,omitempty"`
}
func (l *Instance) fixDates() *Instance {
l.Created, _ = parseDates(l.CreatedStr)
l.Updated, _ = parseDates(l.UpdatedStr)
@@ -178,6 +214,22 @@ func (c *Client) GetInstance(ctx context.Context, linodeID int) (*Instance, erro
return r.Result().(*Instance).fixDates(), nil
}
// GetInstance gets the instance with the provided ID
func (c *Client) GetInstanceTransfer(ctx context.Context, linodeID int) (*InstanceTransfer, error) {
e, err := c.Instances.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d/transfer", e, linodeID)
r, err := coupleAPIErrors(c.R(ctx).
SetResult(InstanceTransfer{}).
Get(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceTransfer), nil
}
// CreateInstance creates a Linode instance
func (c *Client) CreateInstance(ctx context.Context, instance InstanceCreateOptions) (*Instance, error) {
var body string
@@ -331,8 +383,8 @@ func (c *Client) RebootInstance(ctx context.Context, id int, configID int) error
return err
}
// RebuildInstanceOptions is a struct representing the options to send to the rebuild linode endpoint
type RebuildInstanceOptions struct {
// InstanceRebuildOptions is a struct representing the options to send to the rebuild linode endpoint
type InstanceRebuildOptions struct {
Image string `json:"image"`
RootPass string `json:"root_pass"`
AuthorizedKeys []string `json:"authorized_keys"`
@@ -344,7 +396,7 @@ type RebuildInstanceOptions struct {
// RebuildInstance Deletes all Disks and Configs on this Linode,
// then deploys a new Image to this Linode with the given attributes.
func (c *Client) RebuildInstance(ctx context.Context, id int, opts RebuildInstanceOptions) (*Instance, error) {
func (c *Client) RebuildInstance(ctx context.Context, id int, opts InstanceRebuildOptions) (*Instance, error) {
o, err := json.Marshal(opts)
if err != nil {
return nil, NewError(err)
@@ -365,8 +417,8 @@ func (c *Client) RebuildInstance(ctx context.Context, id int, opts RebuildInstan
return r.Result().(*Instance).fixDates(), nil
}
// RescueInstanceOptions fields are those accepted by RescueInstance
type RescueInstanceOptions struct {
// InstanceRescueOptions fields are those accepted by RescueInstance
type InstanceRescueOptions struct {
Devices InstanceConfigDeviceMap `json:"devices"`
}
@@ -374,7 +426,7 @@ type RescueInstanceOptions struct {
// Rescue Mode is based on the Finnix recovery distribution, a self-contained and bootable Linux distribution.
// You can also use Rescue Mode for tasks other than disaster recovery, such as formatting disks to use different filesystems,
// copying data between disks, and downloading files from a disk via SSH and SFTP.
func (c *Client) RescueInstance(ctx context.Context, id int, opts RescueInstanceOptions) error {
func (c *Client) RescueInstance(ctx context.Context, id int, opts InstanceRescueOptions) error {
o, err := json.Marshal(opts)
if err != nil {
return NewError(err)
@@ -394,9 +446,12 @@ func (c *Client) RescueInstance(ctx context.Context, id int, opts RescueInstance
}
// ResizeInstance resizes an instance to new Linode type
func (c *Client) ResizeInstance(ctx context.Context, id int, linodeType string) error {
body := fmt.Sprintf("{\"type\":\"%s\"}", linodeType)
func (c *Client) ResizeInstance(ctx context.Context, id int, opts InstanceResizeOptions) error {
o, err := json.Marshal(opts)
if err != nil {
return NewError(err)
}
body := string(o)
e, err := c.Instances.Endpoint()
if err != nil {
return err

View File

@@ -2,6 +2,7 @@ package linodego
import (
"context"
"encoding/json"
"fmt"
)
@@ -11,6 +12,18 @@ type IPAddressesPagedResponse struct {
Data []InstanceIP `json:"data"`
}
// IPAddressUpdateOptions fields are those accepted by UpdateToken
type IPAddressUpdateOptions struct {
// The reverse DNS assigned to this address. For public IPv4 addresses, this will be set to a default value provided by Linode if set to nil.
RDNS *string `json:"rdns"`
}
// GetUpdateOptions converts a IPAddress to IPAddressUpdateOptions for use in UpdateIPAddress
func (i InstanceIP) GetUpdateOptions() (o IPAddressUpdateOptions) {
o.RDNS = copyString(&i.RDNS)
return
}
// endpoint gets the endpoint URL for IPAddress
func (IPAddressesPagedResponse) endpoint(c *Client) string {
endpoint, err := c.IPAddresses.Endpoint()
@@ -48,3 +61,30 @@ func (c *Client) GetIPAddress(ctx context.Context, id string) (*InstanceIP, erro
}
return r.Result().(*InstanceIP), nil
}
// UpdateIPAddress updates the IPAddress with the specified id
func (c *Client) UpdateIPAddress(ctx context.Context, id string, updateOpts IPAddressUpdateOptions) (*InstanceIP, error) {
var body string
e, err := c.IPAddresses.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%s", e, id)
req := c.R(ctx).SetResult(&InstanceIP{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*InstanceIP), nil
}

View File

@@ -28,6 +28,9 @@ type NodeBalancer struct {
// Information about the amount of transfer this NodeBalancer has had so far this month.
Transfer NodeBalancerTransfer `json:"transfer"`
// An array of tags applied to this object. Tags are for organizational purposes only.
Tags []string `json:"tags"`
Created *time.Time `json:"-"`
Updated *time.Time `json:"-"`
}
@@ -48,12 +51,14 @@ type NodeBalancerCreateOptions struct {
Region string `json:"region,omitempty"`
ClientConnThrottle *int `json:"client_conn_throttle,omitempty"`
Configs []*NodeBalancerConfigCreateOptions `json:"configs,omitempty"`
Tags []string `json:"tags"`
}
// NodeBalancerUpdateOptions are the options permitted for UpdateNodeBalancer
type NodeBalancerUpdateOptions struct {
Label *string `json:"label,omitempty"`
ClientConnThrottle *int `json:"client_conn_throttle,omitempty"`
Label *string `json:"label,omitempty"`
ClientConnThrottle *int `json:"client_conn_throttle,omitempty"`
Tags *[]string `json:"tags,omitempty"`
}
// GetCreateOptions converts a NodeBalancer to NodeBalancerCreateOptions for use in CreateNodeBalancer
@@ -62,6 +67,7 @@ func (i NodeBalancer) GetCreateOptions() NodeBalancerCreateOptions {
Label: i.Label,
Region: i.Region,
ClientConnThrottle: &i.ClientConnThrottle,
Tags: i.Tags,
}
}
@@ -70,6 +76,7 @@ func (i NodeBalancer) GetUpdateOptions() NodeBalancerUpdateOptions {
return NodeBalancerUpdateOptions{
Label: i.Label,
ClientConnThrottle: &i.ClientConnThrottle,
Tags: &i.Tags,
}
}

View File

@@ -139,7 +139,7 @@ func (i NodeBalancerConfig) GetCreateOptions() NodeBalancerConfigCreateOptions {
CheckTimeout: i.CheckTimeout,
CheckPath: i.CheckPath,
CheckBody: i.CheckBody,
CheckPassive: &i.CheckPassive,
CheckPassive: copyBool(&i.CheckPassive),
CipherSuite: i.CipherSuite,
SSLCert: i.SSLCert,
SSLKey: i.SSLKey,
@@ -158,7 +158,7 @@ func (i NodeBalancerConfig) GetUpdateOptions() NodeBalancerConfigUpdateOptions {
CheckAttempts: i.CheckAttempts,
CheckPath: i.CheckPath,
CheckBody: i.CheckBody,
CheckPassive: &i.CheckPassive,
CheckPassive: copyBool(&i.CheckPassive),
CheckTimeout: i.CheckTimeout,
CipherSuite: i.CipherSuite,
SSLCert: i.SSLCert,
@@ -179,7 +179,7 @@ func (i NodeBalancerConfig) GetRebuildOptions() NodeBalancerConfigRebuildOptions
CheckTimeout: i.CheckTimeout,
CheckPath: i.CheckPath,
CheckBody: i.CheckBody,
CheckPassive: &i.CheckPassive,
CheckPassive: copyBool(&i.CheckPassive),
CipherSuite: i.CipherSuite,
SSLCert: i.SSLCert,
SSLKey: i.SSLKey,

View File

@@ -10,7 +10,7 @@ import (
"log"
"strconv"
"github.com/go-resty/resty"
"gopkg.in/resty.v1"
)
// PageOptions are the pagination parameters for List endpoints
@@ -28,8 +28,8 @@ type ListOptions struct {
// NewListOptions simplified construction of ListOptions using only
// the two writable properties, Page and Filter
func NewListOptions(Page int, Filter string) *ListOptions {
return &ListOptions{PageOptions: &PageOptions{Page: Page}, Filter: Filter}
func NewListOptions(page int, filter string) *ListOptions {
return &ListOptions{PageOptions: &PageOptions{Page: page}, Filter: filter}
}
@@ -102,7 +102,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
if r, err = coupleAPIErrors(req.SetResult(DomainsPagedResponse{}).Get(v.endpoint(c))); err == nil {
response, ok := r.Result().(*DomainsPagedResponse)
if !ok {
return fmt.Errorf("Response is not a *DomainsPagedResponse")
return fmt.Errorf("response is not a *DomainsPagedResponse")
}
pages = response.Pages
results = response.Results
@@ -149,7 +149,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
if r, err = coupleAPIErrors(req.SetResult(SSHKeysPagedResponse{}).Get(v.endpoint(c))); err == nil {
response, ok := r.Result().(*SSHKeysPagedResponse)
if !ok {
return fmt.Errorf("Response is not a *SSHKeysPagedResponse")
return fmt.Errorf("response is not a *SSHKeysPagedResponse")
}
pages = response.Pages
results = response.Results
@@ -173,101 +173,50 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
results = r.Result().(*NotificationsPagedResponse).Results
v.appendData(r.Result().(*NotificationsPagedResponse))
}
case *OAuthClientsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(OAuthClientsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*OAuthClientsPagedResponse).Pages
results = r.Result().(*OAuthClientsPagedResponse).Results
v.appendData(r.Result().(*OAuthClientsPagedResponse))
}
case *PaymentsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(PaymentsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*PaymentsPagedResponse).Pages
results = r.Result().(*PaymentsPagedResponse).Results
v.appendData(r.Result().(*PaymentsPagedResponse))
}
case *NodeBalancersPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(NodeBalancersPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*NodeBalancersPagedResponse).Pages
results = r.Result().(*NodeBalancersPagedResponse).Results
v.appendData(r.Result().(*NodeBalancersPagedResponse))
}
case *TagsPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(TagsPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*TagsPagedResponse).Pages
results = r.Result().(*TagsPagedResponse).Results
v.appendData(r.Result().(*TagsPagedResponse))
}
case *TokensPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(TokensPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*TokensPagedResponse).Pages
results = r.Result().(*TokensPagedResponse).Results
v.appendData(r.Result().(*TokensPagedResponse))
}
case *UsersPagedResponse:
if r, err = coupleAPIErrors(req.SetResult(UsersPagedResponse{}).Get(v.endpoint(c))); err == nil {
pages = r.Result().(*UsersPagedResponse).Pages
results = r.Result().(*UsersPagedResponse).Results
v.appendData(r.Result().(*UsersPagedResponse))
}
/**
case AccountOauthClientsPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*AccountOauthClientsPagedResponse).Pages
results = r.Result().(*AccountOauthClientsPagedResponse).Results
v.appendData(r.Result().(*AccountOauthClientsPagedResponse))
}
case AccountPaymentsPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*AccountPaymentsPagedResponse).Pages
results = r.Result().(*AccountPaymentsPagedResponse).Results
v.appendData(r.Result().(*AccountPaymentsPagedResponse))
}
case AccountUsersPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*AccountUsersPagedResponse).Pages
results = r.Result().(*AccountUsersPagedResponse).Results
v.appendData(r.Result().(*AccountUsersPagedResponse))
}
case ProfileAppsPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*ProfileAppsPagedResponse).Pages
results = r.Result().(*ProfileAppsPagedResponse).Results
v.appendData(r.Result().(*ProfileAppsPagedResponse))
}
case ProfileTokensPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*ProfileTokensPagedResponse).Pages
results = r.Result().(*ProfileTokensPagedResponse).Results
v.appendData(r.Result().(*ProfileTokensPagedResponse))
}
case ProfileWhitelistPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*ProfileWhitelistPagedResponse).Pages
results = r.Result().(*ProfileWhitelistPagedResponse).Results
v.appendData(r.Result().(*ProfileWhitelistPagedResponse))
}
case ManagedContactsPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*ManagedContactsPagedResponse).Pages
results = r.Result().(*ManagedContactsPagedResponse).Results
v.appendData(r.Result().(*ManagedContactsPagedResponse))
}
case ManagedCredentialsPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*ManagedCredentialsPagedResponse).Pages
results = r.Result().(*ManagedCredentialsPagedResponse).Results
v.appendData(r.Result().(*ManagedCredentialsPagedResponse))
}
case ManagedIssuesPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*ManagedIssuesPagedResponse).Pages
results = r.Result().(*ManagedIssuesPagedResponse).Results
v.appendData(r.Result().(*ManagedIssuesPagedResponse))
}
case ManagedLinodeSettingsPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*ManagedLinodeSettingsPagedResponse).Pages
results = r.Result().(*ManagedLinodeSettingsPagedResponse).Results
v.appendData(r.Result().(*ManagedLinodeSettingsPagedResponse))
}
case ManagedServicesPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
return NewError(r)
} else if err == nil {
pages = r.Result().(*ManagedServicesPagedResponse).Pages
results = r.Result().(*ManagedServicesPagedResponse).Results
v.appendData(r.Result().(*ManagedServicesPagedResponse))
}
**/
default:
log.Fatalf("listHelper interface{} %+v used", i)
@@ -278,7 +227,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
}
if opts == nil {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
if err := c.listHelper(ctx, i, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
return err
}
@@ -289,7 +238,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
}
if opts.Page == 0 {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
opts.Page = page
if err := c.listHelper(ctx, i, opts); err != nil {
return err
@@ -308,7 +257,7 @@ func (c *Client) listHelper(ctx context.Context, i interface{}, opts *ListOption
// When opts (or opts.Page) is nil, all pages will be fetched and
// returned in a single (endpoint-specific)PagedResponse
// opts.results and opts.pages will be updated from the API response
func (c *Client) listHelperWithID(ctx context.Context, i interface{}, id int, opts *ListOptions) error {
func (c *Client) listHelperWithID(ctx context.Context, i interface{}, idRaw interface{}, opts *ListOptions) error {
req := c.R(ctx)
if opts != nil && opts.Page > 0 {
req.SetQueryParam("page", strconv.Itoa(opts.Page))
@@ -321,6 +270,8 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, id int, op
r *resty.Response
)
id, _ := idRaw.(int)
if opts != nil && len(opts.Filter) > 0 {
req.SetHeader("X-Filter", opts.Filter)
}
@@ -336,7 +287,7 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, id int, op
if r, err = coupleAPIErrors(req.SetResult(DomainRecordsPagedResponse{}).Get(v.endpointWithID(c, id))); err == nil {
response, ok := r.Result().(*DomainRecordsPagedResponse)
if !ok {
return fmt.Errorf("Response is not a *DomainRecordsPagedResponse")
return fmt.Errorf("response is not a *DomainRecordsPagedResponse")
}
pages = response.Pages
results = response.Results
@@ -366,6 +317,14 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, id int, op
results = r.Result().(*InstanceVolumesPagedResponse).Results
v.appendData(r.Result().(*InstanceVolumesPagedResponse))
}
case *TaggedObjectsPagedResponse:
idStr := idRaw.(string)
if r, err = coupleAPIErrors(req.SetResult(TaggedObjectsPagedResponse{}).Get(v.endpointWithID(c, idStr))); err == nil {
pages = r.Result().(*TaggedObjectsPagedResponse).Pages
results = r.Result().(*TaggedObjectsPagedResponse).Results
v.appendData(r.Result().(*TaggedObjectsPagedResponse))
}
/**
case TicketAttachmentsPagedResponse:
if r, err = req.SetResult(v).Get(v.endpoint(c)); r.Error() != nil {
@@ -393,7 +352,7 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, id int, op
}
if opts == nil {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
if err := c.listHelperWithID(ctx, i, id, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
return err
}
@@ -403,7 +362,7 @@ func (c *Client) listHelperWithID(ctx context.Context, i interface{}, id int, op
opts.PageOptions = &PageOptions{}
}
if opts.Page == 0 {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
opts.Page = page
if err := c.listHelperWithID(ctx, i, id, opts); err != nil {
return err
@@ -456,7 +415,7 @@ func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstI
}
if opts == nil {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
if err := c.listHelper(ctx, i, &ListOptions{PageOptions: &PageOptions{Page: page}}); err != nil {
return err
}
@@ -466,7 +425,7 @@ func (c *Client) listHelperWithTwoIDs(ctx context.Context, i interface{}, firstI
opts.PageOptions = &PageOptions{}
}
if opts.Page == 0 {
for page := 2; page <= pages; page = page + 1 {
for page := 2; page <= pages; page++ {
opts.Page = page
if err := c.listHelperWithTwoIDs(ctx, i, firstID, secondID, opts); err != nil {
return err

View File

@@ -1 +1,117 @@
package linodego
/*
- copy profile_test.go and do the same
- When updating Profile structs,
- use pointers where ever null'able would have a different meaning if the wrapper
supplied "" or 0 instead
- Add "NameOfResource" to client.go, resources.go, pagination.go
*/
import (
"context"
"encoding/json"
)
// LishAuthMethod constants start with AuthMethod and include Linode API Lish Authentication Methods
type LishAuthMethod string
// LishAuthMethod constants are the methods of authentication allowed when connecting via Lish
const (
AuthMethodPasswordKeys LishAuthMethod = "password_keys"
AuthMethodKeysOnly LishAuthMethod = "keys_only"
AuthMethodDisabled LishAuthMethod = "disabled"
)
// ProfileReferrals represent a User's status in the Referral Program
type ProfileReferrals struct {
Total int `json:"total"`
Completed int `json:"completed"`
Pending int `json:"pending"`
Credit float64 `json:"credit"`
Code string `json:"code"`
URL string `json:"url"`
}
// Profile represents a Profile object
type Profile struct {
UID int `json:"uid"`
Username string `json:"username"`
Email string `json:"email"`
Timezone string `json:"timezone"`
EmailNotifications bool `json:"email_notifications"`
IPWhitelistEnabled bool `json:"ip_whitelist_enabled"`
TwoFactorAuth bool `json:"two_factor_auth"`
Restricted bool `json:"restricted"`
LishAuthMethod LishAuthMethod `json:"lish_auth_method"`
Referrals ProfileReferrals `json:"referrals"`
AuthorizedKeys []string `json:"authorized_keys"`
}
// ProfileUpdateOptions fields are those accepted by UpdateProfile
type ProfileUpdateOptions struct {
Email string `json:"email,omitempty"`
Timezone string `json:"timezone,omitempty"`
EmailNotifications *bool `json:"email_notifications,omitempty"`
IPWhitelistEnabled *bool `json:"ip_whitelist_enabled,omitempty"`
LishAuthMethod LishAuthMethod `json:"lish_auth_method,omitempty"`
AuthorizedKeys *[]string `json:"authorized_keys,omitempty"`
TwoFactorAuth *bool `json:"two_factor_auth,omitempty"`
Restricted *bool `json:"restricted,omitempty"`
}
// GetUpdateOptions converts a Profile to ProfileUpdateOptions for use in UpdateProfile
func (i Profile) GetUpdateOptions() (o ProfileUpdateOptions) {
o.Email = i.Email
o.Timezone = i.Timezone
o.EmailNotifications = copyBool(&i.EmailNotifications)
o.IPWhitelistEnabled = copyBool(&i.IPWhitelistEnabled)
o.LishAuthMethod = i.LishAuthMethod
authorizedKeys := make([]string, len(i.AuthorizedKeys))
copy(authorizedKeys, i.AuthorizedKeys)
o.AuthorizedKeys = &authorizedKeys
o.TwoFactorAuth = copyBool(&i.TwoFactorAuth)
o.Restricted = copyBool(&i.Restricted)
return
}
// GetProfile returns the Profile of the authenticated user
func (c *Client) GetProfile(ctx context.Context) (*Profile, error) {
e, err := c.Profile.Endpoint()
if err != nil {
return nil, err
}
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Profile{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*Profile), nil
}
// UpdateProfile updates the Profile with the specified id
func (c *Client) UpdateProfile(ctx context.Context, updateOpts ProfileUpdateOptions) (*Profile, error) {
var body string
e, err := c.Profile.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&Profile{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*Profile), nil
}

195
vendor/github.com/linode/linodego/profile_tokens.go generated vendored Normal file
View File

@@ -0,0 +1,195 @@
package linodego
import (
"context"
"encoding/json"
"fmt"
"time"
)
// Token represents a Token object
type Token struct {
// This token's unique ID, which can be used to revoke it.
ID int `json:"id"`
// The scopes this token was created with. These define what parts of the Account the token can be used to access. Many command-line tools, such as the Linode CLI, require tokens with access to *. Tokens with more restrictive scopes are generally more secure.
Scopes string `json:"scopes"`
// This token's label. This is for display purposes only, but can be used to more easily track what you're using each token for. (1-100 Characters)
Label string `json:"label"`
// The token used to access the API. When the token is created, the full token is returned here. Otherwise, only the first 16 characters are returned.
Token string `json:"token"`
// The date and time this token was created.
Created *time.Time `json:"-"`
CreatedStr string `json:"created"`
// When this token will expire. Personal Access Tokens cannot be renewed, so after this time the token will be completely unusable and a new token will need to be generated. Tokens may be created with "null" as their expiry and will never expire unless revoked.
Expiry *time.Time `json:"-"`
ExpiryStr string `json:"expiry"`
}
// TokenCreateOptions fields are those accepted by CreateToken
type TokenCreateOptions struct {
// The scopes this token was created with. These define what parts of the Account the token can be used to access. Many command-line tools, such as the Linode CLI, require tokens with access to *. Tokens with more restrictive scopes are generally more secure.
Scopes string `json:"scopes"`
// This token's label. This is for display purposes only, but can be used to more easily track what you're using each token for. (1-100 Characters)
Label string `json:"label"`
// When this token will expire. Personal Access Tokens cannot be renewed, so after this time the token will be completely unusable and a new token will need to be generated. Tokens may be created with "null" as their expiry and will never expire unless revoked.
Expiry *time.Time `json:"expiry"`
}
// TokenUpdateOptions fields are those accepted by UpdateToken
type TokenUpdateOptions struct {
// This token's label. This is for display purposes only, but can be used to more easily track what you're using each token for. (1-100 Characters)
Label string `json:"label"`
}
// GetCreateOptions converts a Token to TokenCreateOptions for use in CreateToken
func (i Token) GetCreateOptions() (o TokenCreateOptions) {
o.Label = i.Label
o.Expiry = copyTime(i.Expiry)
o.Scopes = i.Scopes
return
}
// GetUpdateOptions converts a Token to TokenUpdateOptions for use in UpdateToken
func (i Token) GetUpdateOptions() (o TokenUpdateOptions) {
o.Label = i.Label
return
}
// TokensPagedResponse represents a paginated Token API response
type TokensPagedResponse struct {
*PageOptions
Data []Token `json:"data"`
}
// endpoint gets the endpoint URL for Token
func (TokensPagedResponse) endpoint(c *Client) string {
endpoint, err := c.Tokens.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// appendData appends Tokens when processing paginated Token responses
func (resp *TokensPagedResponse) appendData(r *TokensPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListTokens lists Tokens
func (c *Client) ListTokens(ctx context.Context, opts *ListOptions) ([]Token, error) {
response := TokensPagedResponse{}
err := c.listHelper(ctx, &response, opts)
for i := range response.Data {
response.Data[i].fixDates()
}
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixDates converts JSON timestamps to Go time.Time values
func (i *Token) fixDates() *Token {
i.Created, _ = parseDates(i.CreatedStr)
i.Expiry, _ = parseDates(i.ExpiryStr)
return i
}
// GetToken gets the token with the provided ID
func (c *Client) GetToken(ctx context.Context, id int) (*Token, error) {
e, err := c.Tokens.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
r, err := coupleAPIErrors(c.R(ctx).SetResult(&Token{}).Get(e))
if err != nil {
return nil, err
}
return r.Result().(*Token).fixDates(), nil
}
// CreateToken creates a Token
func (c *Client) CreateToken(ctx context.Context, createOpts TokenCreateOptions) (*Token, error) {
var body string
e, err := c.Tokens.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&Token{})
// Format the Time as a string to meet the ISO8601 requirement
createOptsFixed := struct {
Label string `json:"label"`
Scopes string `json:"scopes"`
Expiry *string `json:"expiry"`
}{}
createOptsFixed.Label = createOpts.Label
createOptsFixed.Scopes = createOpts.Scopes
if createOpts.Expiry != nil {
iso8601Expiry := createOpts.Expiry.UTC().Format("2006-01-02T15:04:05")
createOptsFixed.Expiry = &iso8601Expiry
}
if bodyData, err := json.Marshal(createOptsFixed); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*Token).fixDates(), nil
}
// UpdateToken updates the Token with the specified id
func (c *Client) UpdateToken(ctx context.Context, id int, updateOpts TokenUpdateOptions) (*Token, error) {
var body string
e, err := c.Tokens.Endpoint()
if err != nil {
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
req := c.R(ctx).SetResult(&Token{})
if bodyData, err := json.Marshal(updateOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return r.Result().(*Token).fixDates(), nil
}
// DeleteToken deletes the Token with the specified id
func (c *Client) DeleteToken(ctx context.Context, id int) error {
e, err := c.Tokens.Endpoint()
if err != nil {
return err
}
e = fmt.Sprintf("%s/%d", e, id)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@@ -6,7 +6,7 @@ import (
"fmt"
"text/template"
"github.com/go-resty/resty"
"gopkg.in/resty.v1"
)
const (
@@ -18,6 +18,7 @@ const (
instanceIPsName = "ips"
instanceSnapshotsName = "snapshots"
instanceVolumesName = "instancevolumes"
instanceStatsName = "instancestats"
ipaddressesName = "ipaddresses"
ipv6poolsName = "ipv6pools"
ipv6rangesName = "ipv6ranges"
@@ -33,15 +34,21 @@ const (
nodebalancersName = "nodebalancers"
nodebalancerconfigsName = "nodebalancerconfigs"
nodebalancernodesName = "nodebalancernodes"
notificationsName = "notifications"
oauthClientsName = "oauthClients"
sshkeysName = "sshkeys"
ticketsName = "tickets"
tokensName = "tokens"
accountName = "account"
accountSettingsName = "accountsettings"
eventsName = "events"
invoicesName = "invoices"
invoiceItemsName = "invoiceitems"
profileName = "profile"
managedName = "managed"
// notificationsName = "notifications"
tagsName = "tags"
usersName = "users"
paymentsName = "payments"
stackscriptsEndpoint = "linode/stackscripts"
imagesEndpoint = "images"
@@ -51,9 +58,10 @@ const (
instanceSnapshotsEndpoint = "linode/instances/{{ .ID }}/backups"
instanceIPsEndpoint = "linode/instances/{{ .ID }}/ips"
instanceVolumesEndpoint = "linode/instances/{{ .ID }}/volumes"
ipaddressesEndpoint = "network/ips"
ipv6poolsEndpoint = "network/ipv6/pools"
ipv6rangesEndpoint = "network/ipv6/ranges"
instanceStatsEndpoint = "linode/instances/{{ .ID }}/stats"
ipaddressesEndpoint = "networking/ips"
ipv6poolsEndpoint = "networking/ipv6/pools"
ipv6rangesEndpoint = "networking/ipv6/ranges"
regionsEndpoint = "regions"
volumesEndpoint = "volumes"
kernelsEndpoint = "linode/kernels"
@@ -72,13 +80,19 @@ const (
nodebalancernodesEndpoint = "nodebalancers/{{ .ID }}/configs/{{ .SecondID }}/nodes"
sshkeysEndpoint = "profile/sshkeys"
ticketsEndpoint = "support/tickets"
tokensEndpoint = "profile/tokens"
accountEndpoint = "account"
accountSettingsEndpoint = "account/settings"
eventsEndpoint = "account/events"
invoicesEndpoint = "account/invoices"
invoiceItemsEndpoint = "account/invoices/{{ .ID }}/items"
profileEndpoint = "profile"
managedEndpoint = "managed"
// notificationsEndpoint = "account/notifications"
tagsEndpoint = "tags"
usersEndpoint = "account/users"
notificationsEndpoint = "account/notifications"
oauthClientsEndpoint = "account/oauth-clients"
paymentsEndpoint = "account/payments"
)
// Resource represents a linode API resource
@@ -118,14 +132,15 @@ func (r Resource) render(data ...interface{}) (string, error) {
buf := bytes.NewBufferString(out)
var substitutions interface{}
if len(data) == 1 {
switch len(data) {
case 1:
substitutions = struct{ ID interface{} }{data[0]}
} else if len(data) == 2 {
case 2:
substitutions = struct {
ID interface{}
SecondID interface{}
}{data[0], data[1]}
} else {
default:
return "", NewError("Too many arguments to render template (expected 1 or 2)")
}
if err := r.endpointTemplate.Execute(buf, substitutions); err != nil {

View File

@@ -16,6 +16,8 @@ type Stackscript struct {
Username string `json:"username"`
Label string `json:"label"`
Description string `json:"description"`
Ordinal int `json:"ordinal"`
LogoURL string `json:"logo_url"`
Images []string `json:"images"`
DeploymentsTotal int `json:"deployments_total"`
DeploymentsActive int `json:"deployments_active"`

219
vendor/github.com/linode/linodego/tags.go generated vendored Normal file
View File

@@ -0,0 +1,219 @@
package linodego
import (
"context"
"encoding/json"
"errors"
"fmt"
)
// Tag represents a Tag object
type Tag struct {
Label string `json:"label"`
}
// TaggedObject represents a Tagged Object object
type TaggedObject struct {
Type string `json:"type"`
RawData json.RawMessage `json:"data"`
Data interface{} `json:"-"`
}
// SortedObjects currently only includes Instances
type SortedObjects struct {
Instances []Instance
Domains []Domain
Volumes []Volume
NodeBalancers []NodeBalancer
/*
StackScripts []Stackscript
*/
}
// TaggedObjectList are a list of TaggedObjects, as returning by ListTaggedObjects
type TaggedObjectList []TaggedObject
// TagCreateOptions fields are those accepted by CreateTag
type TagCreateOptions struct {
Label string `json:"label"`
Linodes []int `json:"linodes,omitempty"`
Domains []int `json:"domains,omitempty"`
Volumes []int `json:"volumes,omitempty"`
NodeBalancers []int `json:"nodebalancers,omitempty"`
}
// GetCreateOptions converts a Tag to TagCreateOptions for use in CreateTag
func (i Tag) GetCreateOptions() (o TagCreateOptions) {
o.Label = i.Label
return
}
// TaggedObjectsPagedResponse represents a paginated Tag API response
type TaggedObjectsPagedResponse struct {
*PageOptions
Data []TaggedObject `json:"data"`
}
// TagsPagedResponse represents a paginated Tag API response
type TagsPagedResponse struct {
*PageOptions
Data []Tag `json:"data"`
}
// endpoint gets the endpoint URL for Tag
func (TagsPagedResponse) endpoint(c *Client) string {
endpoint, err := c.Tags.Endpoint()
if err != nil {
panic(err)
}
return endpoint
}
// endpoint gets the endpoint URL for Tag
func (TaggedObjectsPagedResponse) endpointWithID(c *Client, id string) string {
endpoint, err := c.Tags.Endpoint()
if err != nil {
panic(err)
}
endpoint = fmt.Sprintf("%s/%s", endpoint, id)
return endpoint
}
// appendData appends Tags when processing paginated Tag responses
func (resp *TagsPagedResponse) appendData(r *TagsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// appendData appends TaggedObjects when processing paginated TaggedObjects responses
func (resp *TaggedObjectsPagedResponse) appendData(r *TaggedObjectsPagedResponse) {
resp.Data = append(resp.Data, r.Data...)
}
// ListTags lists Tags
func (c *Client) ListTags(ctx context.Context, opts *ListOptions) ([]Tag, error) {
response := TagsPagedResponse{}
err := c.listHelper(ctx, &response, opts)
if err != nil {
return nil, err
}
return response.Data, nil
}
// fixData stores an object of the type defined by Type in Data using RawData
func (i *TaggedObject) fixData() (*TaggedObject, error) {
switch i.Type {
case "linode":
obj := Instance{}
if err := json.Unmarshal(i.RawData, &obj); err != nil {
return nil, err
}
i.Data = obj
case "nodebalancer":
obj := NodeBalancer{}
if err := json.Unmarshal(i.RawData, &obj); err != nil {
return nil, err
}
i.Data = obj
case "domain":
obj := Domain{}
if err := json.Unmarshal(i.RawData, &obj); err != nil {
return nil, err
}
i.Data = obj
case "volume":
obj := Volume{}
if err := json.Unmarshal(i.RawData, &obj); err != nil {
return nil, err
}
i.Data = obj
}
return i, nil
}
// ListTaggedObjects lists Tagged Objects
func (c *Client) ListTaggedObjects(ctx context.Context, label string, opts *ListOptions) (TaggedObjectList, error) {
response := TaggedObjectsPagedResponse{}
err := c.listHelperWithID(ctx, &response, label, opts)
if err != nil {
return nil, err
}
for i := range response.Data {
if _, err := response.Data[i].fixData(); err != nil {
return nil, err
}
}
return response.Data, nil
}
// SortedObjects converts a list of TaggedObjects into a Sorted Objects struct, for easier access
func (t TaggedObjectList) SortedObjects() (SortedObjects, error) {
so := SortedObjects{}
for _, o := range t {
switch o.Type {
case "linode":
if instance, ok := o.Data.(Instance); ok {
so.Instances = append(so.Instances, instance)
} else {
return so, errors.New("expected an Instance when Type was \"linode\"")
}
case "domain":
if domain, ok := o.Data.(Domain); ok {
so.Domains = append(so.Domains, domain)
} else {
return so, errors.New("expected a Domain when Type was \"domain\"")
}
case "volume":
if volume, ok := o.Data.(Volume); ok {
so.Volumes = append(so.Volumes, volume)
} else {
return so, errors.New("expected an Volume when Type was \"volume\"")
}
case "nodebalancer":
if nodebalancer, ok := o.Data.(NodeBalancer); ok {
so.NodeBalancers = append(so.NodeBalancers, nodebalancer)
} else {
return so, errors.New("expected an NodeBalancer when Type was \"nodebalancer\"")
}
}
}
return so, nil
}
// CreateTag creates a Tag
func (c *Client) CreateTag(ctx context.Context, createOpts TagCreateOptions) (*Tag, error) {
var body string
e, err := c.Tags.Endpoint()
if err != nil {
return nil, err
}
req := c.R(ctx).SetResult(&Tag{})
if bodyData, err := json.Marshal(createOpts); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Post(e))
if err != nil {
return nil, err
}
return r.Result().(*Tag), nil
}
// DeleteTag deletes the Tag with the specified id
func (c *Client) DeleteTag(ctx context.Context, label string) error {
e, err := c.Tags.Endpoint()
if err != nil {
return err
}
e = fmt.Sprintf("%s/%s", e, label)
_, err = coupleAPIErrors(c.R(ctx).Delete(e))
return err
}

View File

@@ -36,14 +36,14 @@ type TemplateUpdateOptions struct {
// GetCreateOptions converts a Template to TemplateCreateOptions for use in CreateTemplate
func (i Template) GetCreateOptions() (o TemplateCreateOptions) {
// o.Label = i.Label
// o.Description = copyString(o.Description)
// o.Description = copyString(i.Description)
return
}
// GetUpdateOptions converts a Template to TemplateUpdateOptions for use in UpdateTemplate
func (i Template) GetUpdateOptions() (o TemplateUpdateOptions) {
// o.Label = i.Label
// o.Description = copyString(o.Description)
// o.Description = copyString(i.Description)
return
}

View File

@@ -9,7 +9,7 @@ import (
type LinodeType struct {
ID string `json:"id"`
Disk int `json:"disk"`
Class LinodeTypeClass `json:"class"` // enum: nanode, standard, highmem
Class LinodeTypeClass `json:"class"` // enum: nanode, standard, highmem, dedicated
Price *LinodePrice `json:"price"`
Label string `json:"label"`
Addons *LinodeAddons `json:"addons"`
@@ -40,9 +40,10 @@ type LinodeTypeClass string
// LinodeTypeClass contants are the Instance Type Classes that an Instance Type can be assigned
const (
ClassNanode LinodeTypeClass = "nanode"
ClassStandard LinodeTypeClass = "standard"
ClassHighmem LinodeTypeClass = "highmem"
ClassNanode LinodeTypeClass = "nanode"
ClassStandard LinodeTypeClass = "standard"
ClassHighmem LinodeTypeClass = "highmem"
ClassDedicated LinodeTypeClass = "dedicated"
)
// LinodeTypesPagedResponse represents a linode types API response for listing

View File

@@ -36,6 +36,7 @@ type Volume struct {
Size int `json:"size"`
LinodeID *int `json:"linode_id"`
FilesystemPath string `json:"filesystem_path"`
Tags []string `json:"tags"`
Created time.Time `json:"-"`
Updated time.Time `json:"-"`
}
@@ -48,12 +49,22 @@ type VolumeCreateOptions struct {
ConfigID int `json:"config_id,omitempty"`
// The Volume's size, in GiB. Minimum size is 10GiB, maximum size is 10240GiB. A "0" value will result in the default size.
Size int `json:"size,omitempty"`
// An array of tags applied to this object. Tags are for organizational purposes only.
Tags []string `json:"tags"`
PersistAcrossBoots *bool `json:"persist_across_boots,omitempty"`
}
// VolumeUpdateOptions fields are those accepted by UpdateVolume
type VolumeUpdateOptions struct {
Label string `json:"label,omitempty"`
Tags *[]string `json:"tags,omitempty"`
}
// VolumeAttachOptions fields are those accepted by AttachVolume
type VolumeAttachOptions struct {
LinodeID int `json:"linode_id"`
ConfigID int `json:"config_id,omitempty"`
LinodeID int `json:"linode_id"`
ConfigID int `json:"config_id,omitempty"`
PersistAcrossBoots *bool `json:"persist_across_boots,omitempty"`
}
// VolumesPagedResponse represents a linode API response for listing of volumes
@@ -62,6 +73,25 @@ type VolumesPagedResponse struct {
Data []Volume `json:"data"`
}
// GetUpdateOptions converts a Volume to VolumeUpdateOptions for use in UpdateVolume
func (v Volume) GetUpdateOptions() (updateOpts VolumeUpdateOptions) {
updateOpts.Label = v.Label
updateOpts.Tags = &v.Tags
return
}
// GetCreateOptions converts a Volume to VolumeCreateOptions for use in CreateVolume
func (v Volume) GetCreateOptions() (createOpts VolumeCreateOptions) {
createOpts.Label = v.Label
createOpts.Tags = v.Tags
createOpts.Region = v.Region
createOpts.Size = v.Size
if v.LinodeID != nil && *v.LinodeID > 0 {
createOpts.LinodeID = *v.LinodeID
}
return
}
// endpoint gets the endpoint URL for Volume
func (VolumesPagedResponse) endpoint(c *Client) string {
endpoint, err := c.Volumes.Endpoint()
@@ -168,26 +198,37 @@ func (c *Client) CreateVolume(ctx context.Context, createOpts VolumeCreateOption
}
// RenameVolume renames the label of a Linode volume
// There is no UpdateVolume because the label is the only alterable field.
// DEPRECATED: use UpdateVolume
func (c *Client) RenameVolume(ctx context.Context, id int, label string) (*Volume, error) {
body, _ := json.Marshal(map[string]string{"label": label})
updateOpts := VolumeUpdateOptions{Label: label}
return c.UpdateVolume(ctx, id, updateOpts)
}
// UpdateVolume updates the Volume with the specified id
func (c *Client) UpdateVolume(ctx context.Context, id int, volume VolumeUpdateOptions) (*Volume, error) {
var body string
e, err := c.Volumes.Endpoint()
if err != nil {
return nil, NewError(err)
return nil, err
}
e = fmt.Sprintf("%s/%d", e, id)
resp, err := coupleAPIErrors(c.R(ctx).
SetResult(&Volume{}).
req := c.R(ctx).SetResult(&Volume{})
if bodyData, err := json.Marshal(volume); err == nil {
body = string(bodyData)
} else {
return nil, NewError(err)
}
r, err := coupleAPIErrors(req.
SetBody(body).
Put(e))
if err != nil {
return nil, err
}
return resp.Result().(*Volume).fixDates(), nil
return r.Result().(*Volume).fixDates(), nil
}
// CloneVolume clones a Linode volume

View File

@@ -16,7 +16,7 @@ func (client Client) WaitForInstanceStatus(ctx context.Context, instanceID int,
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
ticker := time.NewTicker(time.Second)
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
@@ -36,13 +36,47 @@ func (client Client) WaitForInstanceStatus(ctx context.Context, instanceID int,
}
}
// WaitForInstanceDiskStatus waits for the Linode instance disk to reach the desired state
// before returning. It will timeout with an error after timeoutSeconds.
func (client Client) WaitForInstanceDiskStatus(ctx context.Context, instanceID int, diskID int, status DiskStatus, timeoutSeconds int) (*InstanceDisk, error) {
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-ticker.C:
// GetInstanceDisk will 404 on newly created disks. use List instead.
// disk, err := client.GetInstanceDisk(ctx, instanceID, diskID)
disks, err := client.ListInstanceDisks(ctx, instanceID, nil)
if err != nil {
return nil, err
}
for _, disk := range disks {
disk := disk
if disk.ID == diskID {
complete := (disk.Status == status)
if complete {
return &disk, nil
}
break
}
}
case <-ctx.Done():
return nil, fmt.Errorf("Error waiting for Instance %d Disk %d status %s: %s", instanceID, diskID, status, ctx.Err())
}
}
}
// WaitForVolumeStatus waits for the Volume to reach the desired state
// before returning. It will timeout with an error after timeoutSeconds.
func (client Client) WaitForVolumeStatus(ctx context.Context, volumeID int, status VolumeStatus, timeoutSeconds int) (*Volume, error) {
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
ticker := time.NewTicker(time.Second)
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
@@ -68,7 +102,7 @@ func (client Client) WaitForSnapshotStatus(ctx context.Context, instanceID int,
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
ticker := time.NewTicker(time.Second)
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
@@ -96,7 +130,7 @@ func (client Client) WaitForVolumeLinodeID(ctx context.Context, volumeID int, li
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
ticker := time.NewTicker(time.Second)
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
defer ticker.Stop()
for {
select {
@@ -106,11 +140,12 @@ func (client Client) WaitForVolumeLinodeID(ctx context.Context, volumeID int, li
return volume, err
}
if linodeID == nil && volume.LinodeID == nil {
switch {
case linodeID == nil && volume.LinodeID == nil:
return volume, nil
} else if linodeID == nil || volume.LinodeID == nil {
case linodeID == nil || volume.LinodeID == nil:
// continue waiting
} else if *volume.LinodeID == *linodeID {
case *volume.LinodeID == *linodeID:
return volume, nil
}
@@ -125,15 +160,7 @@ func (client Client) WaitForVolumeLinodeID(ctx context.Context, volumeID int, li
// If the event indicates a failure both the failed event and the error will be returned.
func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, entityType EntityType, action EventAction, minStart time.Time, timeoutSeconds int) (*Event, error) {
titledEntityType := strings.Title(string(entityType))
filter, _ := json.Marshal(map[string]interface{}{
// Entity is not filtered by the API
// Perhaps one day they will permit Entity ID/Type filtering.
// We'll have to verify these values manually, for now.
//"entity": map[string]interface{}{
// "id": fmt.Sprintf("%v", id),
// "type": entityType,
//},
filterStruct := map[string]interface{}{
// Nor is action
//"action": action,
@@ -144,16 +171,33 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
//},
// With potentially 1000+ events coming back, we should filter on something
// Warning: This optimization has the potential to break if users are clearing
// events before we see them.
"seen": false,
// Float the latest events to page 1
"+order_by": "created",
"+order": "desc",
})
}
// Optimistically restrict results to page 1. We should remove this when more
// precise filtering options exist.
listOptions := NewListOptions(1, string(filter))
pages := 1
// The API has limitted filtering support for Event ID and Event Type
// Optimize the list, if possible
switch entityType {
case EntityDisk, EntityLinode, EntityDomain, EntityNodebalancer:
// All of the filter supported types have int ids
filterableEntityID, err := strconv.Atoi(fmt.Sprintf("%v", id))
if err != nil {
return nil, fmt.Errorf("Error parsing Entity ID %q for optimized WaitForEventFinished EventType %q: %s", id, entityType, err)
}
filterStruct["entity.id"] = filterableEntityID
filterStruct["entity.type"] = entityType
// TODO: are we conformatable with pages = 0 with the event type and id filter?
}
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSeconds)*time.Second)
defer cancel()
@@ -163,11 +207,28 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
log.Printf("[INFO] Waiting %d seconds for %s events since %v for %s %v", int(duration.Seconds()), action, minStart, titledEntityType, id)
}
ticker := time.NewTicker(time.Second)
ticker := time.NewTicker(client.millisecondsPerPoll * time.Millisecond)
// avoid repeating log messages
nextLog := ""
lastLog := ""
lastEventID := 0
defer ticker.Stop()
for {
select {
case <-ticker.C:
if lastEventID > 0 {
filterStruct["id"] = map[string]interface{}{
"+gte": lastEventID,
}
}
filter, err := json.Marshal(filterStruct)
if err != nil {
return nil, err
}
listOptions := NewListOptions(pages, string(filter))
events, err := client.ListEvents(ctx, listOptions)
if err != nil {
@@ -176,32 +237,34 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
// If there are events for this instance + action, inspect them
for _, event := range events {
event := event
if event.Action != action {
// log.Println("action mismatch", event.Action, action)
continue
}
if event.Entity.Type != entityType {
if event.Entity == nil || event.Entity.Type != entityType {
// log.Println("type mismatch", event.Entity.Type, entityType)
continue
}
var entID string
switch event.Entity.ID.(type) {
switch id := event.Entity.ID.(type) {
case float64, float32:
entID = fmt.Sprintf("%.f", event.Entity.ID)
entID = fmt.Sprintf("%.f", id)
case int:
entID = strconv.Itoa(event.Entity.ID.(int))
entID = strconv.Itoa(id)
default:
entID = fmt.Sprintf("%v", event.Entity.ID)
entID = fmt.Sprintf("%v", id)
}
var findID string
switch id.(type) {
switch id := id.(type) {
case float64, float32:
findID = fmt.Sprintf("%.f", id)
case int:
findID = strconv.Itoa(id.(int))
findID = strconv.Itoa(id)
default:
findID = fmt.Sprintf("%v", id)
}
@@ -219,19 +282,28 @@ func (client Client) WaitForEventFinished(ctx context.Context, id interface{}, e
// Not the event we were looking for
// log.Println(event.Created, "is not >=", minStart)
continue
}
if event.Status == EventFailed {
// This is the event we are looking for. Save our place.
if lastEventID == 0 {
lastEventID = event.ID
}
switch event.Status {
case EventFailed:
return &event, fmt.Errorf("%s %v action %s failed", titledEntityType, id, action)
} else if event.Status == EventScheduled {
log.Printf("[INFO] %s %v action %s is scheduled", titledEntityType, id, action)
} else if event.Status == EventFinished {
case EventFinished:
log.Printf("[INFO] %s %v action %s is finished", titledEntityType, id, action)
return &event, nil
}
// TODO(displague) can we bump the ticker to TimeRemaining/2 (>=1) when non-nil?
log.Printf("[INFO] %s %v action %s is %s", titledEntityType, id, action, event.Status)
nextLog = fmt.Sprintf("[INFO] %s %v action %s is %s", titledEntityType, id, action, event.Status)
}
// de-dupe logging statements
if nextLog != lastLog {
log.Print(nextLog)
lastLog = nextLog
}
case <-ctx.Done():
return nil, fmt.Errorf("Error waiting for Event Status '%s' of %s %v action '%s': %s", EventFinished, titledEntityType, id, action, ctx.Err())