govultr.go raw

   1  // Package govultr contains the functionality to interact with the Vultr public
   2  // HTTP REST API.
   3  package govultr
   4  
   5  import (
   6  	"bytes"
   7  	"context"
   8  	"encoding/json"
   9  	"errors"
  10  	"fmt"
  11  	"io"
  12  	"net"
  13  	"net/http"
  14  	"net/url"
  15  	"strings"
  16  	"time"
  17  
  18  	"github.com/hashicorp/go-retryablehttp"
  19  )
  20  
  21  const (
  22  	version     = "3.26.1"
  23  	defaultBase = "https://api.vultr.com"
  24  	userAgent   = "govultr/" + version
  25  	rateLimit   = 500 * time.Millisecond
  26  	retryLimit  = 3
  27  )
  28  
  29  // RequestBody is used to create JSON bodies for one off calls
  30  type RequestBody map[string]interface{}
  31  
  32  // Client manages interaction with the Vultr API
  33  type Client struct {
  34  	// Http Client used to interact with the Vultr API
  35  	client *retryablehttp.Client
  36  
  37  	// BASE URL for APIs
  38  	BaseURL *url.URL
  39  
  40  	// User Agent for the client
  41  	UserAgent string
  42  
  43  	// Services used to interact with the API
  44  	Account                  AccountService
  45  	Application              ApplicationService
  46  	Backup                   BackupService
  47  	BareMetalServer          BareMetalServerService
  48  	Billing                  BillingService
  49  	BlockStorage             BlockStorageService
  50  	CDN                      CDNService
  51  	ContainerRegistry        ContainerRegistryService
  52  	Database                 DatabaseService
  53  	Domain                   DomainService
  54  	DomainRecord             DomainRecordService
  55  	FirewallGroup            FirewallGroupService
  56  	FirewallRule             FireWallRuleService
  57  	Instance                 InstanceService
  58  	ISO                      ISOService
  59  	Kubernetes               KubernetesService
  60  	LoadBalancer             LoadBalancerService
  61  	Logs                     LogsService
  62  	Marketplace              MarketplaceService
  63  	ObjectStorage            ObjectStorageService
  64  	OS                       OSService
  65  	Plan                     PlanService
  66  	Region                   RegionService
  67  	ReservedIP               ReservedIPService
  68  	Inference                InferenceService
  69  	Snapshot                 SnapshotService
  70  	SSHKey                   SSHKeyService
  71  	StartupScript            StartupScriptService
  72  	SubAccount               SubAccountService
  73  	User                     UserService
  74  	VirtualFileSystemStorage VirtualFileSystemStorageService
  75  	VPC                      VPCService
  76  	// Deprecated: VPC2 is no longer supported
  77  	VPC2 VPC2Service
  78  
  79  	// Optional function called after every successful request made to the Vultr API
  80  	onRequestCompleted RequestCompletionCallback
  81  }
  82  
  83  // RequestCompletionCallback defines the type of the request callback function
  84  type RequestCompletionCallback func(*http.Request, *http.Response)
  85  
  86  // NewClient returns a Vultr API Client
  87  func NewClient(httpClient *http.Client) *Client {
  88  	if httpClient == nil {
  89  		httpClient = &http.Client{
  90  			Transport: &http.Transport{
  91  				DialContext: (&net.Dialer{
  92  					Timeout:   90 * time.Second,
  93  					KeepAlive: 90 * time.Second,
  94  					DualStack: true,
  95  				}).DialContext,
  96  				MaxIdleConns:          100,
  97  				IdleConnTimeout:       90 * time.Second,
  98  				TLSHandshakeTimeout:   30 * time.Second,
  99  				ExpectContinueTimeout: 1 * time.Second,
 100  				MaxIdleConnsPerHost:   -1,
 101  				DisableKeepAlives:     true,
 102  			},
 103  			Timeout: 60 * time.Second,
 104  		}
 105  	}
 106  
 107  	baseURL, _ := url.Parse(defaultBase)
 108  
 109  	client := &Client{
 110  		client:    retryablehttp.NewClient(),
 111  		BaseURL:   baseURL,
 112  		UserAgent: userAgent,
 113  	}
 114  
 115  	client.client.HTTPClient = httpClient
 116  	client.client.Logger = nil
 117  	client.client.ErrorHandler = client.vultrErrorHandler
 118  	client.SetRetryLimit(retryLimit)
 119  	client.SetRateLimit(rateLimit)
 120  
 121  	client.Account = &AccountServiceHandler{client}
 122  	client.Application = &ApplicationServiceHandler{client}
 123  	client.Backup = &BackupServiceHandler{client}
 124  	client.BareMetalServer = &BareMetalServerServiceHandler{client}
 125  	client.Billing = &BillingServiceHandler{client}
 126  	client.BlockStorage = &BlockStorageServiceHandler{client}
 127  	client.ContainerRegistry = &ContainerRegistryServiceHandler{client}
 128  	client.CDN = &CDNServiceHandler{client}
 129  	client.Database = &DatabaseServiceHandler{client}
 130  	client.Domain = &DomainServiceHandler{client}
 131  	client.DomainRecord = &DomainRecordsServiceHandler{client}
 132  	client.FirewallGroup = &FireWallGroupServiceHandler{client}
 133  	client.FirewallRule = &FireWallRuleServiceHandler{client}
 134  	client.Instance = &InstanceServiceHandler{client}
 135  	client.ISO = &ISOServiceHandler{client}
 136  	client.Kubernetes = &KubernetesHandler{client}
 137  	client.LoadBalancer = &LoadBalancerHandler{client}
 138  	client.Logs = &LogsServiceHandler{client}
 139  	client.Marketplace = &MarketplaceServiceHandler{client}
 140  	client.ObjectStorage = &ObjectStorageServiceHandler{client}
 141  	client.OS = &OSServiceHandler{client}
 142  	client.Plan = &PlanServiceHandler{client}
 143  	client.Region = &RegionServiceHandler{client}
 144  	client.ReservedIP = &ReservedIPServiceHandler{client}
 145  	client.Inference = &InferenceServiceHandler{client}
 146  	client.Snapshot = &SnapshotServiceHandler{client}
 147  	client.SSHKey = &SSHKeyServiceHandler{client}
 148  	client.StartupScript = &StartupScriptServiceHandler{client}
 149  	client.SubAccount = &SubAccountServiceHandler{client}
 150  	client.User = &UserServiceHandler{client}
 151  	client.VirtualFileSystemStorage = &VirtualFileSystemStorageServiceHandler{client}
 152  	client.VPC = &VPCServiceHandler{client}
 153  	client.VPC2 = &VPC2ServiceHandler{client}
 154  
 155  	return client
 156  }
 157  
 158  // NewRequest creates an API Request
 159  func (c *Client) NewRequest(ctx context.Context, method, uri string, body interface{}) (*http.Request, error) {
 160  	resolvedURL, err := c.BaseURL.Parse(uri)
 161  	if err != nil {
 162  		return nil, err
 163  	}
 164  
 165  	buf := new(bytes.Buffer)
 166  	if body != nil {
 167  		if err2 := json.NewEncoder(buf).Encode(body); err2 != nil {
 168  			return nil, err2
 169  		}
 170  	}
 171  
 172  	req, err := http.NewRequestWithContext(ctx, method, resolvedURL.String(), buf)
 173  	if err != nil {
 174  		return nil, err
 175  	}
 176  
 177  	req.Header.Add("User-Agent", c.UserAgent)
 178  	req.Header.Add("Accept", "application/json")
 179  	req.Header.Add("Content-Type", "application/json")
 180  
 181  	return req, nil
 182  }
 183  
 184  // DoWithContext sends an API Request and returns back the response. The API response is checked  to see if it was
 185  // a successful call. A successful call is then checked to see if we need to unmarshal since some resources
 186  // have their own implements of unmarshal.
 187  func (c *Client) DoWithContext(ctx context.Context, r *http.Request, data interface{}) (*http.Response, error) {
 188  	rreq, err := retryablehttp.FromRequest(r)
 189  	if err != nil {
 190  		return nil, err
 191  	}
 192  
 193  	rreq = rreq.WithContext(ctx)
 194  
 195  	res, errDo := c.client.Do(rreq)
 196  
 197  	if c.onRequestCompleted != nil {
 198  		c.onRequestCompleted(r, res)
 199  	}
 200  
 201  	if errDo != nil {
 202  		return nil, errDo
 203  	}
 204  
 205  	defer func() {
 206  		if rerr := res.Body.Close(); err == nil {
 207  			err = rerr
 208  		}
 209  	}()
 210  
 211  	body, err := io.ReadAll(res.Body)
 212  	if err != nil {
 213  		return nil, err
 214  	}
 215  
 216  	res.Body = io.NopCloser(bytes.NewBuffer(body))
 217  
 218  	if res.StatusCode >= http.StatusOK && res.StatusCode <= http.StatusNoContent {
 219  		if data != nil {
 220  			if err := json.Unmarshal(body, data); err != nil {
 221  				return nil, err
 222  			}
 223  		}
 224  		return res, nil
 225  	}
 226  
 227  	return res, errors.New(string(body))
 228  }
 229  
 230  // SetBaseURL Overrides the default BaseUrl
 231  func (c *Client) SetBaseURL(baseURL string) error {
 232  	updatedURL, err := url.Parse(baseURL)
 233  
 234  	if err != nil {
 235  		return err
 236  	}
 237  
 238  	c.BaseURL = updatedURL
 239  	return nil
 240  }
 241  
 242  // SetRateLimit Overrides the default rateLimit. For performance, exponential
 243  // backoff is used with the minimum wait being 2/3rds the time provided.
 244  func (c *Client) SetRateLimit(t time.Duration) {
 245  	c.client.RetryWaitMin = t / 3 * 2
 246  	c.client.RetryWaitMax = t
 247  }
 248  
 249  // SetUserAgent Overrides the default UserAgent
 250  func (c *Client) SetUserAgent(ua string) {
 251  	c.UserAgent = ua
 252  }
 253  
 254  // OnRequestCompleted sets the API request completion callback
 255  func (c *Client) OnRequestCompleted(rc RequestCompletionCallback) {
 256  	c.onRequestCompleted = rc
 257  }
 258  
 259  // SetRetryLimit overrides the default RetryLimit
 260  func (c *Client) SetRetryLimit(n int) {
 261  	c.client.RetryMax = n
 262  }
 263  
 264  func (c *Client) vultrErrorHandler(resp *http.Response, err error, numTries int) (*http.Response, error) {
 265  	if resp == nil {
 266  		if err != nil {
 267  			return nil, fmt.Errorf("gave up after %d attempts, last error : %s", numTries, err.Error())
 268  		}
 269  		return nil, fmt.Errorf("gave up after %d attempts, last error unavailable (resp == nil)", numTries)
 270  	}
 271  
 272  	buf, err := io.ReadAll(resp.Body)
 273  	if err != nil {
 274  		return nil, fmt.Errorf("gave up after %d attempts, last error unavailable (error reading response body: %v)", numTries, err)
 275  	}
 276  	return nil, fmt.Errorf("gave up after %d attempts, last error: %#v", numTries, strings.TrimSpace(string(buf)))
 277  }
 278  
 279  // BoolToBoolPtr helper function that returns a pointer from your bool value
 280  func BoolToBoolPtr(value bool) *bool {
 281  	return &value
 282  }
 283  
 284  // StringToStringPtr helper function that returns a pointer from your string value
 285  func StringToStringPtr(value string) *string {
 286  	return &value
 287  }
 288  
 289  // IntToIntPtr helper function that returns a pointer from your string value
 290  func IntToIntPtr(value int) *int {
 291  	return &value
 292  }
 293