Skip to content

Commit

Permalink
Add godo's rate limiter configuration & retryable http client #546 (#967
Browse files Browse the repository at this point in the history
)

* add godo's rate limit configuration to provider

* set default rate limit to 240 req/m

* docs: add requests_per_second argument

* use hashicorp's retryablehttp client on godo

* disable rate limiter by default

* docs: improve requests per second text

* docs: fix and improve http_retry_max description

* configs: completely disable retryable client when max retries is 0
  • Loading branch information
DanielHLelis committed Apr 11, 2023
1 parent 332f739 commit f66b7e9
Show file tree
Hide file tree
Showing 12 changed files with 1,401 additions and 7 deletions.
32 changes: 30 additions & 2 deletions digitalocean/config/config.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package config

import (
"context"
"fmt"
"html/template"
"log"
"net/http"
"net/url"
"strings"
"time"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/digitalocean/godo"
"github.com/hashicorp/go-retryablehttp"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging"
"golang.org/x/oauth2"
)
Expand All @@ -21,7 +25,11 @@ type Config struct {
SpacesAPIEndpoint string
AccessID string
SecretKey string
RequestsPerSecond float64
TerraformVersion string
HTTPRetryMax int
HTTPRetryWaitMax float64
HTTPRetryWaitMin float64
}

type CombinedConfig struct {
Expand Down Expand Up @@ -67,11 +75,31 @@ func (c *Config) Client() (*CombinedConfig, error) {
})

userAgent := fmt.Sprintf("Terraform/%s", c.TerraformVersion)
client := oauth2.NewClient(oauth2.NoContext, tokenSrc)
var client *http.Client

if c.HTTPRetryMax > 0 {
retryableClient := retryablehttp.NewClient()
retryableClient.RetryMax = c.HTTPRetryMax
retryableClient.RetryWaitMin = time.Duration(c.HTTPRetryWaitMin * float64(time.Second))
retryableClient.RetryWaitMax = time.Duration(c.HTTPRetryWaitMax * float64(time.Second))

client = retryableClient.StandardClient()
client.Transport = &oauth2.Transport{
Base: client.Transport,
Source: oauth2.ReuseTokenSource(nil, tokenSrc),
}
} else {
client = oauth2.NewClient(context.Background(), tokenSrc)
}

client.Transport = logging.NewTransport("DigitalOcean", client.Transport)

godoClient, err := godo.New(client, godo.SetUserAgent(userAgent))
godoOpts := []godo.ClientOpt{godo.SetUserAgent(userAgent)}
if c.RequestsPerSecond > 0.0 {
godoOpts = append(godoOpts, godo.SetStaticRateLimit(c.RequestsPerSecond))
}

godoClient, err := godo.New(client, godoOpts...)
if err != nil {
return nil, err
}
Expand Down
38 changes: 33 additions & 5 deletions digitalocean/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,30 @@ func Provider() *schema.Provider {
DefaultFunc: schema.EnvDefaultFunc("SPACES_SECRET_ACCESS_KEY", nil),
Description: "The secret access key for Spaces API operations.",
},
"requests_per_second": {
Type: schema.TypeFloat,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DIGITALOCEAN_REQUESTS_PER_SECOND", 0.0),
Description: "The rate of requests per second to limit the HTTP client.",
},
"http_retry_max": {
Type: schema.TypeInt,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DIGITALOCEAN_HTTP_RETRY_MAX", 0),
Description: "The maximum number of retries on a failed API request.",
},
"http_retry_wait_min": {
Type: schema.TypeFloat,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DIGITALOCEAN_HTTP_RETRY_WAIT_MIN", 1.0),
Description: "The minimum wait time (in seconds) between failed API requests.",
},
"http_retry_wait_max": {
Type: schema.TypeFloat,
Optional: true,
DefaultFunc: schema.EnvDefaultFunc("DIGITALOCEAN_HTTP_RETRY_WAIT_MAX", 30.0),
Description: "The maximum wait time (in seconds) between failed API requests.",
},
},
DataSourcesMap: map[string]*schema.Resource{
"digitalocean_account": account.DataSourceDigitalOceanAccount(),
Expand Down Expand Up @@ -166,11 +190,15 @@ func Provider() *schema.Provider {

func providerConfigure(d *schema.ResourceData, terraformVersion string) (interface{}, error) {
conf := config.Config{
Token: d.Get("token").(string),
APIEndpoint: d.Get("api_endpoint").(string),
AccessID: d.Get("spaces_access_id").(string),
SecretKey: d.Get("spaces_secret_key").(string),
TerraformVersion: terraformVersion,
Token: d.Get("token").(string),
APIEndpoint: d.Get("api_endpoint").(string),
AccessID: d.Get("spaces_access_id").(string),
SecretKey: d.Get("spaces_secret_key").(string),
RequestsPerSecond: d.Get("requests_per_second").(float64),
HTTPRetryMax: d.Get("http_retry_max").(int),
HTTPRetryWaitMin: d.Get("http_retry_wait_min").(float64),
HTTPRetryWaitMax: d.Get("http_retry_wait_max").(float64),
TerraformVersion: terraformVersion,
}

if endpoint, ok := d.GetOk("spaces_endpoint"); ok {
Expand Down
17 changes: 17 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,20 @@ The following arguments are supported:
`SPACES_ENDPOINT_URL` environment variable or `https://{{.Region}}.digitaloceanspaces.com`
if unset.) The provider will replace `{{.Region}}` (via Go's templating engine) with the slug
of the applicable Spaces region.
* `requests_per_second` - (Optional) This can be used to enable throttling, overriding the limit
of API calls per second to avoid rate limit errors, can be disabled by setting the value
to `0.0` (Defaults to the value of the `DIGITALOCEAN_REQUESTS_PER_SECOND` environment
variable or `0.0` if unset).
* `http_retry_max` - (Optional) This can be used to override the maximum number
of retries on a failed API request (client errors, 422, 500, 502...), the exponential
backoff can be configured by the `http_retry_wait_min` and `http_retry_wait_max` arguments
(Defaults to the value of the `DIGITALOCEAN_HTTP_RETRY_MAX` environment variable or
`0`, which means no retries, if unset).
* `http_retry_wait_min` - (Optional) This can be used to configure the minimum
waiting time (**in seconds**) between failed requests for the backoff strategy
(Defaults to the value of the `DIGITALOCEAN_HTTP_RETRY_WAIT_MIN` environment
variable or `1.0` if unset).
* `http_retry_wait_max` - (Optional) This can be used to configure the maximum
waiting time (**in seconds**) between failed requests for the backoff strategy
(Defaults to the value of the `DIGITALOCEAN_HTTP_RETRY_WAIT_MAX` environment
variable or `30.0` if unset).
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ require (
github.com/aws/aws-sdk-go v1.42.18
github.com/digitalocean/godo v1.95.0
github.com/hashicorp/awspolicyequivalence v1.5.0
github.com/hashicorp/go-retryablehttp v0.7.2
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-version v1.3.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.10.1
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBM
github.com/hashicorp/go-getter v1.5.3 h1:NF5+zOlQegim+w/EUhSLh6QhXHmZMEeHLQzllkQ3ROU=
github.com/hashicorp/go-getter v1.5.3/go.mod h1:BrrV/1clo8cCYu6mxvboYg+KutTiFnXjMEgDD8+i7ZI=
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o=
github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
Expand All @@ -188,6 +189,8 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9
github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0=
github.com/hashicorp/go-plugin v1.4.1 h1:6UltRQlLN9iZO513VveELp5xyaFxVD2+1OVylE+2E+w=
github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ=
github.com/hashicorp/go-retryablehttp v0.7.2 h1:AcYqCvkpalPnPF2pn0KamgwamS42TqUDDYFRKq/RAd0=
github.com/hashicorp/go-retryablehttp v0.7.2/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
Expand Down
4 changes: 4 additions & 0 deletions vendor/github.com/hashicorp/go-retryablehttp/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f66b7e9

Please sign in to comment.